pci_user.c revision 174932
1/*-
2 * Copyright (c) 1997, Stefan Esser <se@freebsd.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice unmodified, this list of conditions, and the following
10 *    disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/dev/pci/pci_user.c 174932 2007-12-26 21:50:59Z marius $");
29
30#include "opt_bus.h"	/* XXX trim includes */
31#include "opt_compat.h"
32
33#include <sys/param.h>
34#include <sys/systm.h>
35#include <sys/malloc.h>
36#include <sys/module.h>
37#include <sys/linker.h>
38#include <sys/fcntl.h>
39#include <sys/conf.h>
40#include <sys/kernel.h>
41#include <sys/proc.h>
42#include <sys/queue.h>
43#include <sys/types.h>
44
45#include <vm/vm.h>
46#include <vm/pmap.h>
47#include <vm/vm_extern.h>
48
49#include <sys/bus.h>
50#include <machine/bus.h>
51#include <sys/rman.h>
52#include <machine/resource.h>
53
54#include <sys/pciio.h>
55#include <dev/pci/pcireg.h>
56#include <dev/pci/pcivar.h>
57
58#include "pcib_if.h"
59#include "pci_if.h"
60
61/*
62 * This is the user interface to PCI configuration space.
63 */
64
65static d_open_t 	pci_open;
66static d_close_t	pci_close;
67static int	pci_conf_match(struct pci_match_conf *matches, int num_matches,
68			       struct pci_conf *match_buf);
69static d_ioctl_t	pci_ioctl;
70
71struct cdevsw pcicdev = {
72	.d_version =	D_VERSION,
73	.d_flags =	D_NEEDGIANT,
74	.d_open =	pci_open,
75	.d_close =	pci_close,
76	.d_ioctl =	pci_ioctl,
77	.d_name =	"pci",
78};
79
80static int
81pci_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
82{
83	int error;
84
85	if (oflags & FWRITE) {
86		error = securelevel_gt(td->td_ucred, 0);
87		if (error)
88			return (error);
89	}
90
91	return (0);
92}
93
94static int
95pci_close(struct cdev *dev, int flag, int devtype, struct thread *td)
96{
97	return 0;
98}
99
100/*
101 * Match a single pci_conf structure against an array of pci_match_conf
102 * structures.  The first argument, 'matches', is an array of num_matches
103 * pci_match_conf structures.  match_buf is a pointer to the pci_conf
104 * structure that will be compared to every entry in the matches array.
105 * This function returns 1 on failure, 0 on success.
106 */
107static int
108pci_conf_match(struct pci_match_conf *matches, int num_matches,
109	       struct pci_conf *match_buf)
110{
111	int i;
112
113	if ((matches == NULL) || (match_buf == NULL) || (num_matches <= 0))
114		return(1);
115
116	for (i = 0; i < num_matches; i++) {
117		/*
118		 * I'm not sure why someone would do this...but...
119		 */
120		if (matches[i].flags == PCI_GETCONF_NO_MATCH)
121			continue;
122
123		/*
124		 * Look at each of the match flags.  If it's set, do the
125		 * comparison.  If the comparison fails, we don't have a
126		 * match, go on to the next item if there is one.
127		 */
128		if (((matches[i].flags & PCI_GETCONF_MATCH_DOMAIN) != 0)
129		 && (match_buf->pc_sel.pc_domain !=
130		 matches[i].pc_sel.pc_domain))
131			continue;
132
133		if (((matches[i].flags & PCI_GETCONF_MATCH_BUS) != 0)
134		 && (match_buf->pc_sel.pc_bus != matches[i].pc_sel.pc_bus))
135			continue;
136
137		if (((matches[i].flags & PCI_GETCONF_MATCH_DEV) != 0)
138		 && (match_buf->pc_sel.pc_dev != matches[i].pc_sel.pc_dev))
139			continue;
140
141		if (((matches[i].flags & PCI_GETCONF_MATCH_FUNC) != 0)
142		 && (match_buf->pc_sel.pc_func != matches[i].pc_sel.pc_func))
143			continue;
144
145		if (((matches[i].flags & PCI_GETCONF_MATCH_VENDOR) != 0)
146		 && (match_buf->pc_vendor != matches[i].pc_vendor))
147			continue;
148
149		if (((matches[i].flags & PCI_GETCONF_MATCH_DEVICE) != 0)
150		 && (match_buf->pc_device != matches[i].pc_device))
151			continue;
152
153		if (((matches[i].flags & PCI_GETCONF_MATCH_CLASS) != 0)
154		 && (match_buf->pc_class != matches[i].pc_class))
155			continue;
156
157		if (((matches[i].flags & PCI_GETCONF_MATCH_UNIT) != 0)
158		 && (match_buf->pd_unit != matches[i].pd_unit))
159			continue;
160
161		if (((matches[i].flags & PCI_GETCONF_MATCH_NAME) != 0)
162		 && (strncmp(matches[i].pd_name, match_buf->pd_name,
163			     sizeof(match_buf->pd_name)) != 0))
164			continue;
165
166		return(0);
167	}
168
169	return(1);
170}
171
172#if defined(COMPAT_FREEBSD4) || defined(COMPAT_FREEBSD5) || \
173    defined(COMPAT_FREEBSD6)
174#define PRE7_COMPAT
175
176typedef enum {
177	PCI_GETCONF_NO_MATCH_OLD	= 0x00,
178	PCI_GETCONF_MATCH_BUS_OLD	= 0x01,
179	PCI_GETCONF_MATCH_DEV_OLD	= 0x02,
180	PCI_GETCONF_MATCH_FUNC_OLD	= 0x04,
181	PCI_GETCONF_MATCH_NAME_OLD	= 0x08,
182	PCI_GETCONF_MATCH_UNIT_OLD	= 0x10,
183	PCI_GETCONF_MATCH_VENDOR_OLD	= 0x20,
184	PCI_GETCONF_MATCH_DEVICE_OLD	= 0x40,
185	PCI_GETCONF_MATCH_CLASS_OLD	= 0x80
186} pci_getconf_flags_old;
187
188struct pcisel_old {
189	u_int8_t	pc_bus;		/* bus number */
190	u_int8_t	pc_dev;		/* device on this bus */
191	u_int8_t	pc_func;	/* function on this device */
192};
193
194struct pci_conf_old {
195	struct pcisel_old pc_sel;	/* bus+slot+function */
196	u_int8_t	pc_hdr;		/* PCI header type */
197	u_int16_t	pc_subvendor;	/* card vendor ID */
198	u_int16_t	pc_subdevice;	/* card device ID, assigned by
199					   card vendor */
200	u_int16_t	pc_vendor;	/* chip vendor ID */
201	u_int16_t	pc_device;	/* chip device ID, assigned by
202					   chip vendor */
203	u_int8_t	pc_class;	/* chip PCI class */
204	u_int8_t	pc_subclass;	/* chip PCI subclass */
205	u_int8_t	pc_progif;	/* chip PCI programming interface */
206	u_int8_t	pc_revid;	/* chip revision ID */
207	char		pd_name[PCI_MAXNAMELEN + 1];  /* device name */
208	u_long		pd_unit;	/* device unit number */
209};
210
211struct pci_match_conf_old {
212	struct pcisel_old	pc_sel;		/* bus+slot+function */
213	char			pd_name[PCI_MAXNAMELEN + 1];  /* device name */
214	u_long			pd_unit;	/* Unit number */
215	u_int16_t		pc_vendor;	/* PCI Vendor ID */
216	u_int16_t		pc_device;	/* PCI Device ID */
217	u_int8_t		pc_class;	/* PCI class */
218	pci_getconf_flags_old	flags;		/* Matching expression */
219};
220
221struct pci_io_old {
222	struct pcisel_old pi_sel;	/* device to operate on */
223	int		pi_reg;		/* configuration register to examine */
224	int		pi_width;	/* width (in bytes) of read or write */
225	u_int32_t	pi_data;	/* data to write or result of read */
226};
227
228#define	PCIOCGETCONF_OLD	_IOWR('p', 1, struct pci_conf_io)
229#define	PCIOCREAD_OLD		_IOWR('p', 2, struct pci_io_old)
230#define	PCIOCWRITE_OLD		_IOWR('p', 3, struct pci_io_old)
231
232static int	pci_conf_match_old(struct pci_match_conf_old *matches,
233		    int num_matches, struct pci_conf *match_buf);
234
235static int
236pci_conf_match_old(struct pci_match_conf_old *matches, int num_matches,
237    struct pci_conf *match_buf)
238{
239	int i;
240
241	if ((matches == NULL) || (match_buf == NULL) || (num_matches <= 0))
242		return(1);
243
244	for (i = 0; i < num_matches; i++) {
245		if (match_buf->pc_sel.pc_domain != 0)
246			continue;
247
248		/*
249		 * I'm not sure why someone would do this...but...
250		 */
251		if (matches[i].flags == PCI_GETCONF_NO_MATCH_OLD)
252			continue;
253
254		/*
255		 * Look at each of the match flags.  If it's set, do the
256		 * comparison.  If the comparison fails, we don't have a
257		 * match, go on to the next item if there is one.
258		 */
259		if (((matches[i].flags & PCI_GETCONF_MATCH_BUS_OLD) != 0)
260		 && (match_buf->pc_sel.pc_bus != matches[i].pc_sel.pc_bus))
261			continue;
262
263		if (((matches[i].flags & PCI_GETCONF_MATCH_DEV_OLD) != 0)
264		 && (match_buf->pc_sel.pc_dev != matches[i].pc_sel.pc_dev))
265			continue;
266
267		if (((matches[i].flags & PCI_GETCONF_MATCH_FUNC_OLD) != 0)
268		 && (match_buf->pc_sel.pc_func != matches[i].pc_sel.pc_func))
269			continue;
270
271		if (((matches[i].flags & PCI_GETCONF_MATCH_VENDOR_OLD) != 0)
272		 && (match_buf->pc_vendor != matches[i].pc_vendor))
273			continue;
274
275		if (((matches[i].flags & PCI_GETCONF_MATCH_DEVICE_OLD) != 0)
276		 && (match_buf->pc_device != matches[i].pc_device))
277			continue;
278
279		if (((matches[i].flags & PCI_GETCONF_MATCH_CLASS_OLD) != 0)
280		 && (match_buf->pc_class != matches[i].pc_class))
281			continue;
282
283		if (((matches[i].flags & PCI_GETCONF_MATCH_UNIT_OLD) != 0)
284		 && (match_buf->pd_unit != matches[i].pd_unit))
285			continue;
286
287		if (((matches[i].flags & PCI_GETCONF_MATCH_NAME_OLD) != 0)
288		 && (strncmp(matches[i].pd_name, match_buf->pd_name,
289			     sizeof(match_buf->pd_name)) != 0))
290			continue;
291
292		return(0);
293	}
294
295	return(1);
296}
297
298#endif
299
300static int
301pci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
302{
303	device_t pcidev, brdev;
304	void *confdata;
305	const char *name;
306	struct devlist *devlist_head;
307	struct pci_conf_io *cio;
308	struct pci_devinfo *dinfo;
309	struct pci_io *io;
310	struct pci_match_conf *pattern_buf;
311	size_t confsz, iolen, pbufsz;
312	int error, ionum, i, num_patterns;
313#ifdef PRE7_COMPAT
314	struct pci_conf_old conf_old;
315	struct pci_io iodata;
316	struct pci_io_old *io_old;
317	struct pci_match_conf_old *pattern_buf_old;
318
319	io_old = NULL;
320	pattern_buf_old = NULL;
321
322	if (!(flag & FWRITE) &&
323	    (cmd != PCIOCGETCONF && cmd != PCIOCGETCONF_OLD))
324		return EPERM;
325#else
326	if (!(flag & FWRITE) && cmd != PCIOCGETCONF)
327		return EPERM;
328#endif
329
330	switch(cmd) {
331#ifdef PRE7_COMPAT
332	case PCIOCGETCONF_OLD:
333		/* FALLTHROUGH */
334#endif
335	case PCIOCGETCONF:
336		cio = (struct pci_conf_io *)data;
337
338		pattern_buf = NULL;
339		num_patterns = 0;
340		dinfo = NULL;
341
342		cio->num_matches = 0;
343
344		/*
345		 * If the user specified an offset into the device list,
346		 * but the list has changed since they last called this
347		 * ioctl, tell them that the list has changed.  They will
348		 * have to get the list from the beginning.
349		 */
350		if ((cio->offset != 0)
351		 && (cio->generation != pci_generation)){
352			cio->status = PCI_GETCONF_LIST_CHANGED;
353			error = 0;
354			break;
355		}
356
357		/*
358		 * Check to see whether the user has asked for an offset
359		 * past the end of our list.
360		 */
361		if (cio->offset >= pci_numdevs) {
362			cio->status = PCI_GETCONF_LAST_DEVICE;
363			error = 0;
364			break;
365		}
366
367		/* get the head of the device queue */
368		devlist_head = &pci_devq;
369
370		/*
371		 * Determine how much room we have for pci_conf structures.
372		 * Round the user's buffer size down to the nearest
373		 * multiple of sizeof(struct pci_conf) in case the user
374		 * didn't specify a multiple of that size.
375		 */
376#ifdef PRE7_COMPAT
377		if (cmd == PCIOCGETCONF_OLD)
378			confsz = sizeof(struct pci_conf_old);
379		else
380#endif
381			confsz = sizeof(struct pci_conf);
382		iolen = min(cio->match_buf_len - (cio->match_buf_len % confsz),
383		    pci_numdevs * confsz);
384
385		/*
386		 * Since we know that iolen is a multiple of the size of
387		 * the pciconf union, it's okay to do this.
388		 */
389		ionum = iolen / confsz;
390
391		/*
392		 * If this test is true, the user wants the pci_conf
393		 * structures returned to match the supplied entries.
394		 */
395		if ((cio->num_patterns > 0) && (cio->num_patterns < pci_numdevs)
396		 && (cio->pat_buf_len > 0)) {
397			/*
398			 * pat_buf_len needs to be:
399			 * num_patterns * sizeof(struct pci_match_conf)
400			 * While it is certainly possible the user just
401			 * allocated a large buffer, but set the number of
402			 * matches correctly, it is far more likely that
403			 * their kernel doesn't match the userland utility
404			 * they're using.  It's also possible that the user
405			 * forgot to initialize some variables.  Yes, this
406			 * may be overly picky, but I hazard to guess that
407			 * it's far more likely to just catch folks that
408			 * updated their kernel but not their userland.
409			 */
410#ifdef PRE7_COMPAT
411			if (cmd == PCIOCGETCONF_OLD)
412				pbufsz = sizeof(struct pci_match_conf_old);
413			else
414#endif
415				pbufsz = sizeof(struct pci_match_conf);
416			if (cio->num_patterns * pbufsz != cio->pat_buf_len) {
417				/* The user made a mistake, return an error. */
418				cio->status = PCI_GETCONF_ERROR;
419				error = EINVAL;
420				break;
421			}
422
423			/*
424			 * Allocate a buffer to hold the patterns.
425			 */
426#ifdef PRE7_COMPAT
427			if (cmd == PCIOCGETCONF_OLD) {
428				pattern_buf_old = malloc(cio->pat_buf_len,
429				    M_TEMP, M_WAITOK);
430				error = copyin(cio->patterns,
431				    pattern_buf_old, cio->pat_buf_len);
432			} else
433#endif
434			{
435				pattern_buf = malloc(cio->pat_buf_len, M_TEMP,
436				    M_WAITOK);
437				error = copyin(cio->patterns, pattern_buf,
438				    cio->pat_buf_len);
439			}
440			if (error != 0) {
441				error = EINVAL;
442				goto getconfexit;
443			}
444			num_patterns = cio->num_patterns;
445		} else if ((cio->num_patterns > 0)
446			|| (cio->pat_buf_len > 0)) {
447			/*
448			 * The user made a mistake, spit out an error.
449			 */
450			cio->status = PCI_GETCONF_ERROR;
451			error = EINVAL;
452			break;
453		}
454
455		/*
456		 * Go through the list of devices and copy out the devices
457		 * that match the user's criteria.
458		 */
459		for (cio->num_matches = 0, error = 0, i = 0,
460		     dinfo = STAILQ_FIRST(devlist_head);
461		     (dinfo != NULL) && (cio->num_matches < ionum)
462		     && (error == 0) && (i < pci_numdevs) && (dinfo != NULL);
463		     dinfo = STAILQ_NEXT(dinfo, pci_links), i++) {
464
465			if (i < cio->offset)
466				continue;
467
468			/* Populate pd_name and pd_unit */
469			name = NULL;
470			if (dinfo->cfg.dev && dinfo->conf.pd_name[0] == '\0')
471				name = device_get_name(dinfo->cfg.dev);
472			if (name) {
473				strncpy(dinfo->conf.pd_name, name,
474					sizeof(dinfo->conf.pd_name));
475				dinfo->conf.pd_name[PCI_MAXNAMELEN] = 0;
476				dinfo->conf.pd_unit =
477					device_get_unit(dinfo->cfg.dev);
478			}
479
480#ifdef PRE7_COMPAT
481			if ((cmd == PCIOCGETCONF_OLD &&
482			    (pattern_buf_old == NULL ||
483			    pci_conf_match_old(pattern_buf_old, num_patterns,
484			    &dinfo->conf) == 0)) ||
485			    (cmd == PCIOCGETCONF &&
486			    (pattern_buf == NULL ||
487			    pci_conf_match(pattern_buf, num_patterns,
488			    &dinfo->conf) == 0))) {
489#else
490			if (pattern_buf == NULL ||
491			    pci_conf_match(pattern_buf, num_patterns,
492			    &dinfo->conf) == 0) {
493#endif
494				/*
495				 * If we've filled up the user's buffer,
496				 * break out at this point.  Since we've
497				 * got a match here, we'll pick right back
498				 * up at the matching entry.  We can also
499				 * tell the user that there are more matches
500				 * left.
501				 */
502				if (cio->num_matches >= ionum)
503					break;
504
505#ifdef PRE7_COMPAT
506				if (cmd == PCIOCGETCONF_OLD) {
507					conf_old.pc_sel.pc_bus =
508					    dinfo->conf.pc_sel.pc_bus;
509					conf_old.pc_sel.pc_dev =
510					    dinfo->conf.pc_sel.pc_dev;
511					conf_old.pc_sel.pc_func =
512					    dinfo->conf.pc_sel.pc_func;
513					conf_old.pc_hdr = dinfo->conf.pc_hdr;
514					conf_old.pc_subvendor =
515					    dinfo->conf.pc_subvendor;
516					conf_old.pc_subdevice =
517					    dinfo->conf.pc_subdevice;
518					conf_old.pc_vendor =
519					    dinfo->conf.pc_vendor;
520					conf_old.pc_device =
521					    dinfo->conf.pc_device;
522					conf_old.pc_class =
523					    dinfo->conf.pc_class;
524					conf_old.pc_subclass =
525					    dinfo->conf.pc_subclass;
526					conf_old.pc_progif =
527					    dinfo->conf.pc_progif;
528					conf_old.pc_revid =
529					    dinfo->conf.pc_revid;
530					strncpy(conf_old.pd_name,
531					    dinfo->conf.pd_name,
532					    sizeof(conf_old.pd_name));
533					conf_old.pd_name[PCI_MAXNAMELEN] = 0;
534					conf_old.pd_unit =
535					    dinfo->conf.pd_unit;
536					confdata = &conf_old;
537				} else
538#endif
539					confdata = &dinfo->conf;
540				/* Only if we can copy it out do we count it. */
541				if (!(error = copyout(confdata,
542				    (caddr_t)cio->matches +
543				    confsz * cio->num_matches, confsz)))
544					cio->num_matches++;
545			}
546		}
547
548		/*
549		 * Set the pointer into the list, so if the user is getting
550		 * n records at a time, where n < pci_numdevs,
551		 */
552		cio->offset = i;
553
554		/*
555		 * Set the generation, the user will need this if they make
556		 * another ioctl call with offset != 0.
557		 */
558		cio->generation = pci_generation;
559
560		/*
561		 * If this is the last device, inform the user so he won't
562		 * bother asking for more devices.  If dinfo isn't NULL, we
563		 * know that there are more matches in the list because of
564		 * the way the traversal is done.
565		 */
566		if (dinfo == NULL)
567			cio->status = PCI_GETCONF_LAST_DEVICE;
568		else
569			cio->status = PCI_GETCONF_MORE_DEVS;
570
571getconfexit:
572		if (pattern_buf != NULL)
573			free(pattern_buf, M_TEMP);
574#ifdef PRE7_COMPAT
575		if (pattern_buf_old != NULL)
576			free(pattern_buf_old, M_TEMP);
577#endif
578
579		break;
580
581#ifdef PRE7_COMPAT
582	case PCIOCREAD_OLD:
583	case PCIOCWRITE_OLD:
584		io_old = (struct pci_io_old *)data;
585		iodata.pi_sel.pc_domain = 0;
586		iodata.pi_sel.pc_bus = io_old->pi_sel.pc_bus;
587		iodata.pi_sel.pc_dev = io_old->pi_sel.pc_dev;
588		iodata.pi_sel.pc_func = io_old->pi_sel.pc_func;
589		iodata.pi_reg = io_old->pi_reg;
590		iodata.pi_width = io_old->pi_width;
591		iodata.pi_data = io_old->pi_data;
592		data = (caddr_t)&iodata;
593		/* FALLTHROUGH */
594#endif
595	case PCIOCREAD:
596	case PCIOCWRITE:
597		io = (struct pci_io *)data;
598		switch(io->pi_width) {
599		case 4:
600		case 2:
601		case 1:
602			/* Make sure register is in bounds and aligned. */
603			if (io->pi_reg < 0 ||
604			    io->pi_reg + io->pi_width > PCI_REGMAX + 1 ||
605			    io->pi_reg & (io->pi_width - 1)) {
606				error = EINVAL;
607				break;
608			}
609			/*
610			 * Assume that the user-level bus number is
611			 * in fact the physical PCI bus number.
612			 * Look up the grandparent, i.e. the bridge device,
613			 * so that we can issue configuration space cycles.
614			 */
615			pcidev = pci_find_dbsf(io->pi_sel.pc_domain,
616			    io->pi_sel.pc_bus, io->pi_sel.pc_dev,
617			    io->pi_sel.pc_func);
618			if (pcidev) {
619				brdev = device_get_parent(
620				    device_get_parent(pcidev));
621
622#ifdef PRE7_COMPAT
623				if (cmd == PCIOCWRITE || cmd == PCIOCWRITE_OLD)
624#else
625				if (cmd == PCIOCWRITE)
626#endif
627					PCIB_WRITE_CONFIG(brdev,
628							  io->pi_sel.pc_bus,
629							  io->pi_sel.pc_dev,
630							  io->pi_sel.pc_func,
631							  io->pi_reg,
632							  io->pi_data,
633							  io->pi_width);
634#ifdef PRE7_COMPAT
635				else if (cmd == PCIOCREAD_OLD)
636					io_old->pi_data =
637						PCIB_READ_CONFIG(brdev,
638							  io->pi_sel.pc_bus,
639							  io->pi_sel.pc_dev,
640							  io->pi_sel.pc_func,
641							  io->pi_reg,
642							  io->pi_width);
643#endif
644				else
645					io->pi_data =
646						PCIB_READ_CONFIG(brdev,
647							  io->pi_sel.pc_bus,
648							  io->pi_sel.pc_dev,
649							  io->pi_sel.pc_func,
650							  io->pi_reg,
651							  io->pi_width);
652				error = 0;
653			} else {
654#ifdef COMPAT_FREEBSD4
655				if (cmd == PCIOCREAD_OLD) {
656					io_old->pi_data = -1;
657					error = 0;
658				} else
659#endif
660					error = ENODEV;
661			}
662			break;
663		default:
664			error = EINVAL;
665			break;
666		}
667		break;
668
669	default:
670		error = ENOTTY;
671		break;
672	}
673
674	return (error);
675}
676