pci_user.c revision 172999
1119418Sobrien/*-
269953Smsmith * Copyright (c) 1997, Stefan Esser <se@freebsd.org>
369953Smsmith * All rights reserved.
469953Smsmith *
569953Smsmith * Redistribution and use in source and binary forms, with or without
669953Smsmith * modification, are permitted provided that the following conditions
769953Smsmith * are met:
869953Smsmith * 1. Redistributions of source code must retain the above copyright
969953Smsmith *    notice unmodified, this list of conditions, and the following
1069953Smsmith *    disclaimer.
1169953Smsmith * 2. Redistributions in binary form must reproduce the above copyright
1269953Smsmith *    notice, this list of conditions and the following disclaimer in the
1369953Smsmith *    documentation and/or other materials provided with the distribution.
1469953Smsmith *
1569953Smsmith * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1669953Smsmith * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1769953Smsmith * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1869953Smsmith * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
1969953Smsmith * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2069953Smsmith * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2169953Smsmith * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2269953Smsmith * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2369953Smsmith * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2469953Smsmith * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2569953Smsmith */
2669953Smsmith
27119418Sobrien#include <sys/cdefs.h>
28119418Sobrien__FBSDID("$FreeBSD: head/sys/dev/pci/pci_user.c 172999 2007-10-26 05:02:47Z imp $");
29119418Sobrien
3069953Smsmith#include "opt_bus.h"	/* XXX trim includes */
31149478Sps#include "opt_compat.h"
3269953Smsmith
3369953Smsmith#include <sys/param.h>
3469953Smsmith#include <sys/systm.h>
3569953Smsmith#include <sys/malloc.h>
3669953Smsmith#include <sys/module.h>
3769953Smsmith#include <sys/linker.h>
3869953Smsmith#include <sys/fcntl.h>
3969953Smsmith#include <sys/conf.h>
4069953Smsmith#include <sys/kernel.h>
4183975Srwatson#include <sys/proc.h>
4269953Smsmith#include <sys/queue.h>
4369953Smsmith#include <sys/types.h>
4469953Smsmith
4569953Smsmith#include <vm/vm.h>
4669953Smsmith#include <vm/pmap.h>
4769953Smsmith#include <vm/vm_extern.h>
4869953Smsmith
4969953Smsmith#include <sys/bus.h>
5069953Smsmith#include <machine/bus.h>
5169953Smsmith#include <sys/rman.h>
5269953Smsmith#include <machine/resource.h>
5369953Smsmith
5469953Smsmith#include <sys/pciio.h>
55119285Simp#include <dev/pci/pcireg.h>
56119285Simp#include <dev/pci/pcivar.h>
5769953Smsmith
5869953Smsmith#include "pcib_if.h"
5969953Smsmith#include "pci_if.h"
6069953Smsmith
6169953Smsmith/*
6269953Smsmith * This is the user interface to PCI configuration space.
6369953Smsmith */
6469953Smsmith
6583366Sjulianstatic d_open_t 	pci_open;
6683366Sjulianstatic d_close_t	pci_close;
6769953Smsmithstatic int	pci_conf_match(struct pci_match_conf *matches, int num_matches,
6869953Smsmith			       struct pci_conf *match_buf);
6983366Sjulianstatic d_ioctl_t	pci_ioctl;
7069953Smsmith
7169953Smsmithstruct cdevsw pcicdev = {
72126080Sphk	.d_version =	D_VERSION,
73126080Sphk	.d_flags =	D_NEEDGIANT,
74111815Sphk	.d_open =	pci_open,
75111815Sphk	.d_close =	pci_close,
76111815Sphk	.d_ioctl =	pci_ioctl,
77111815Sphk	.d_name =	"pci",
7869953Smsmith};
7969953Smsmith
8069953Smsmithstatic int
81130585Sphkpci_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
8269953Smsmith{
8383975Srwatson	int error;
8483975Srwatson
8583975Srwatson	if (oflags & FWRITE) {
8691406Sjhb		error = securelevel_gt(td->td_ucred, 0);
8783975Srwatson		if (error)
8883975Srwatson			return (error);
8969953Smsmith	}
9083975Srwatson
9183975Srwatson	return (0);
9269953Smsmith}
9369953Smsmith
9469953Smsmithstatic int
95130585Sphkpci_close(struct cdev *dev, int flag, int devtype, struct thread *td)
9669953Smsmith{
9769953Smsmith	return 0;
9869953Smsmith}
9969953Smsmith
10069953Smsmith/*
10169953Smsmith * Match a single pci_conf structure against an array of pci_match_conf
10269953Smsmith * structures.  The first argument, 'matches', is an array of num_matches
10369953Smsmith * pci_match_conf structures.  match_buf is a pointer to the pci_conf
10469953Smsmith * structure that will be compared to every entry in the matches array.
10569953Smsmith * This function returns 1 on failure, 0 on success.
10669953Smsmith */
10769953Smsmithstatic int
10869953Smsmithpci_conf_match(struct pci_match_conf *matches, int num_matches,
10969953Smsmith	       struct pci_conf *match_buf)
11069953Smsmith{
11169953Smsmith	int i;
11269953Smsmith
11369953Smsmith	if ((matches == NULL) || (match_buf == NULL) || (num_matches <= 0))
11469953Smsmith		return(1);
11569953Smsmith
11669953Smsmith	for (i = 0; i < num_matches; i++) {
11769953Smsmith		/*
11869953Smsmith		 * I'm not sure why someone would do this...but...
11969953Smsmith		 */
12069953Smsmith		if (matches[i].flags == PCI_GETCONF_NO_MATCH)
12169953Smsmith			continue;
12269953Smsmith
12369953Smsmith		/*
12469953Smsmith		 * Look at each of the match flags.  If it's set, do the
12569953Smsmith		 * comparison.  If the comparison fails, we don't have a
12669953Smsmith		 * match, go on to the next item if there is one.
12769953Smsmith		 */
128172394Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_DOMAIN) != 0)
129172394Smarius		 && (match_buf->pc_sel.pc_domain !=
130172394Smarius		 matches[i].pc_sel.pc_domain))
131172394Smarius			continue;
132172394Smarius
13369953Smsmith		if (((matches[i].flags & PCI_GETCONF_MATCH_BUS) != 0)
13469953Smsmith		 && (match_buf->pc_sel.pc_bus != matches[i].pc_sel.pc_bus))
13569953Smsmith			continue;
13669953Smsmith
13769953Smsmith		if (((matches[i].flags & PCI_GETCONF_MATCH_DEV) != 0)
13869953Smsmith		 && (match_buf->pc_sel.pc_dev != matches[i].pc_sel.pc_dev))
13969953Smsmith			continue;
14069953Smsmith
14169953Smsmith		if (((matches[i].flags & PCI_GETCONF_MATCH_FUNC) != 0)
14269953Smsmith		 && (match_buf->pc_sel.pc_func != matches[i].pc_sel.pc_func))
14369953Smsmith			continue;
14469953Smsmith
14569953Smsmith		if (((matches[i].flags & PCI_GETCONF_MATCH_VENDOR) != 0)
14669953Smsmith		 && (match_buf->pc_vendor != matches[i].pc_vendor))
14769953Smsmith			continue;
14869953Smsmith
14969953Smsmith		if (((matches[i].flags & PCI_GETCONF_MATCH_DEVICE) != 0)
15069953Smsmith		 && (match_buf->pc_device != matches[i].pc_device))
15169953Smsmith			continue;
15269953Smsmith
15369953Smsmith		if (((matches[i].flags & PCI_GETCONF_MATCH_CLASS) != 0)
15469953Smsmith		 && (match_buf->pc_class != matches[i].pc_class))
15569953Smsmith			continue;
15669953Smsmith
15769953Smsmith		if (((matches[i].flags & PCI_GETCONF_MATCH_UNIT) != 0)
15869953Smsmith		 && (match_buf->pd_unit != matches[i].pd_unit))
15969953Smsmith			continue;
16069953Smsmith
16169953Smsmith		if (((matches[i].flags & PCI_GETCONF_MATCH_NAME) != 0)
16269953Smsmith		 && (strncmp(matches[i].pd_name, match_buf->pd_name,
16369953Smsmith			     sizeof(match_buf->pd_name)) != 0))
16469953Smsmith			continue;
16569953Smsmith
16669953Smsmith		return(0);
16769953Smsmith	}
16869953Smsmith
16969953Smsmith	return(1);
17069953Smsmith}
17169953Smsmith
172172932Smarius#if defined(COMPAT_FREEBSD4) || defined(COMPAT_FREEBSD5) || \
173172932Smarius    defined(COMPAT_FREEBSD6)
174172999Simp#define PRE7_COMPAT
175172932Smarius
176172932Smariustypedef enum {
177172932Smarius	PCI_GETCONF_NO_MATCH_OLD	= 0x00,
178172932Smarius	PCI_GETCONF_MATCH_BUS_OLD	= 0x01,
179172932Smarius	PCI_GETCONF_MATCH_DEV_OLD	= 0x02,
180172932Smarius	PCI_GETCONF_MATCH_FUNC_OLD	= 0x04,
181172932Smarius	PCI_GETCONF_MATCH_NAME_OLD	= 0x08,
182172932Smarius	PCI_GETCONF_MATCH_UNIT_OLD	= 0x10,
183172932Smarius	PCI_GETCONF_MATCH_VENDOR_OLD	= 0x20,
184172932Smarius	PCI_GETCONF_MATCH_DEVICE_OLD	= 0x40,
185172932Smarius	PCI_GETCONF_MATCH_CLASS_OLD	= 0x80
186172932Smarius} pci_getconf_flags_old;
187172932Smarius
188172932Smariusstruct pcisel_old {
189172932Smarius	u_int8_t	pc_bus;		/* bus number */
190172932Smarius	u_int8_t	pc_dev;		/* device on this bus */
191172932Smarius	u_int8_t	pc_func;	/* function on this device */
192172932Smarius};
193172932Smarius
194172932Smariusstruct pci_conf_old {
195172932Smarius	struct pcisel_old pc_sel;	/* bus+slot+function */
196172932Smarius	u_int8_t	pc_hdr;		/* PCI header type */
197172932Smarius	u_int16_t	pc_subvendor;	/* card vendor ID */
198172932Smarius	u_int16_t	pc_subdevice;	/* card device ID, assigned by
199172932Smarius					   card vendor */
200172932Smarius	u_int16_t	pc_vendor;	/* chip vendor ID */
201172932Smarius	u_int16_t	pc_device;	/* chip device ID, assigned by
202172932Smarius					   chip vendor */
203172932Smarius	u_int8_t	pc_class;	/* chip PCI class */
204172932Smarius	u_int8_t	pc_subclass;	/* chip PCI subclass */
205172932Smarius	u_int8_t	pc_progif;	/* chip PCI programming interface */
206172932Smarius	u_int8_t	pc_revid;	/* chip revision ID */
207172932Smarius	char		pd_name[PCI_MAXNAMELEN + 1];  /* device name */
208172932Smarius	u_long		pd_unit;	/* device unit number */
209172932Smarius};
210172932Smarius
211172932Smariusstruct pci_match_conf_old {
212172932Smarius	struct pcisel_old	pc_sel;		/* bus+slot+function */
213172932Smarius	char			pd_name[PCI_MAXNAMELEN + 1];  /* device name */
214172932Smarius	u_long			pd_unit;	/* Unit number */
215172932Smarius	u_int16_t		pc_vendor;	/* PCI Vendor ID */
216172932Smarius	u_int16_t		pc_device;	/* PCI Device ID */
217172932Smarius	u_int8_t		pc_class;	/* PCI class */
218172932Smarius	pci_getconf_flags	flags;		/* Matching expression */
219172932Smarius};
220172932Smarius
221172932Smariusstruct pci_io_old {
222172932Smarius	struct pcisel_old pi_sel;	/* device to operate on */
223172932Smarius	int		pi_reg;		/* configuration register to examine */
224172932Smarius	int		pi_width;	/* width (in bytes) of read or write */
225172932Smarius	u_int32_t	pi_data;	/* data to write or result of read */
226172932Smarius};
227172932Smarius
228172932Smarius#define	PCIOCGETCONF_OLD	_IOWR('p', 1, struct pci_conf_io)
229172932Smarius#define	PCIOCREAD_OLD		_IOWR('p', 2, struct pci_io_old)
230172932Smarius#define	PCIOCWRITE_OLD		_IOWR('p', 3, struct pci_io_old)
231172932Smarius
232172932Smariusstatic int	pci_conf_match_old(struct pci_match_conf_old *matches,
233172932Smarius		    int num_matches, struct pci_conf *match_buf);
234172932Smarius
23569953Smsmithstatic int
236172932Smariuspci_conf_match_old(struct pci_match_conf_old *matches, int num_matches,
237172932Smarius    struct pci_conf *match_buf)
238172932Smarius{
239172932Smarius	int i;
240172932Smarius
241172932Smarius	if ((matches == NULL) || (match_buf == NULL) || (num_matches <= 0))
242172932Smarius		return(1);
243172932Smarius
244172932Smarius	for (i = 0; i < num_matches; i++) {
245172932Smarius		if (match_buf->pc_sel.pc_domain != 0)
246172932Smarius			continue;
247172932Smarius
248172932Smarius		/*
249172932Smarius		 * I'm not sure why someone would do this...but...
250172932Smarius		 */
251172932Smarius		if (matches[i].flags == PCI_GETCONF_NO_MATCH_OLD)
252172932Smarius			continue;
253172932Smarius
254172932Smarius		/*
255172932Smarius		 * Look at each of the match flags.  If it's set, do the
256172932Smarius		 * comparison.  If the comparison fails, we don't have a
257172932Smarius		 * match, go on to the next item if there is one.
258172932Smarius		 */
259172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_BUS_OLD) != 0)
260172932Smarius		 && (match_buf->pc_sel.pc_bus != matches[i].pc_sel.pc_bus))
261172932Smarius			continue;
262172932Smarius
263172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_DEV_OLD) != 0)
264172932Smarius		 && (match_buf->pc_sel.pc_dev != matches[i].pc_sel.pc_dev))
265172932Smarius			continue;
266172932Smarius
267172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_FUNC_OLD) != 0)
268172932Smarius		 && (match_buf->pc_sel.pc_func != matches[i].pc_sel.pc_func))
269172932Smarius			continue;
270172932Smarius
271172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_VENDOR_OLD) != 0)
272172932Smarius		 && (match_buf->pc_vendor != matches[i].pc_vendor))
273172932Smarius			continue;
274172932Smarius
275172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_DEVICE_OLD) != 0)
276172932Smarius		 && (match_buf->pc_device != matches[i].pc_device))
277172932Smarius			continue;
278172932Smarius
279172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_CLASS_OLD) != 0)
280172932Smarius		 && (match_buf->pc_class != matches[i].pc_class))
281172932Smarius			continue;
282172932Smarius
283172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_UNIT_OLD) != 0)
284172932Smarius		 && (match_buf->pd_unit != matches[i].pd_unit))
285172932Smarius			continue;
286172932Smarius
287172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_NAME_OLD) != 0)
288172932Smarius		 && (strncmp(matches[i].pd_name, match_buf->pd_name,
289172932Smarius			     sizeof(match_buf->pd_name)) != 0))
290172932Smarius			continue;
291172932Smarius
292172932Smarius		return(0);
293172932Smarius	}
294172932Smarius
295172932Smarius	return(1);
296172932Smarius}
297172932Smarius
298172932Smarius#endif
299172932Smarius
300172932Smariusstatic int
301130585Sphkpci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
30269953Smsmith{
303172932Smarius	device_t pcidev, brdev;
304172932Smarius	void *confdata;
305172932Smarius	const char *name;
306172932Smarius	struct devlist *devlist_head;
307172932Smarius	struct pci_conf_io *cio;
308172932Smarius	struct pci_devinfo *dinfo;
30969953Smsmith	struct pci_io *io;
310172932Smarius	struct pci_match_conf *pattern_buf;
311172932Smarius	size_t confsz, iolen, pbufsz;
312172932Smarius	int error, ionum, i, num_patterns;
313172999Simp#ifdef PRE7_COMPAT
314172932Smarius	struct pci_conf_old conf_old;
315172932Smarius	struct pci_io iodata;
316172932Smarius	struct pci_io_old *io_old;
317172932Smarius	struct pci_match_conf_old *pattern_buf_old;
31869953Smsmith
319172932Smarius	io_old = NULL;
320172932Smarius	pattern_buf_old = NULL;
321172932Smarius
322172932Smarius	if (!(flag & FWRITE) &&
323172932Smarius	    (cmd != PCIOCGETCONF && cmd != PCIOCGETCONF_OLD))
324172932Smarius		return EPERM;
325172932Smarius#else
326116702Sjmg	if (!(flag & FWRITE) && cmd != PCIOCGETCONF)
32769953Smsmith		return EPERM;
328172932Smarius#endif
32969953Smsmith
33069953Smsmith	switch(cmd) {
331172999Simp#ifdef PRE7_COMPAT
332172932Smarius	case PCIOCGETCONF_OLD:
333172932Smarius		/* FALLTHROUGH */
334172932Smarius#endif
33569953Smsmith	case PCIOCGETCONF:
33669953Smsmith		cio = (struct pci_conf_io *)data;
33769953Smsmith
338172932Smarius		pattern_buf = NULL;
33969953Smsmith		num_patterns = 0;
34069953Smsmith		dinfo = NULL;
34169953Smsmith
342172932Smarius		cio->num_matches = 0;
343172932Smarius
34469953Smsmith		/*
34569953Smsmith		 * If the user specified an offset into the device list,
34669953Smsmith		 * but the list has changed since they last called this
34769953Smsmith		 * ioctl, tell them that the list has changed.  They will
34869953Smsmith		 * have to get the list from the beginning.
34969953Smsmith		 */
35069953Smsmith		if ((cio->offset != 0)
35169953Smsmith		 && (cio->generation != pci_generation)){
35269953Smsmith			cio->status = PCI_GETCONF_LIST_CHANGED;
35369953Smsmith			error = 0;
35469953Smsmith			break;
35569953Smsmith		}
35669953Smsmith
35769953Smsmith		/*
35869953Smsmith		 * Check to see whether the user has asked for an offset
35969953Smsmith		 * past the end of our list.
36069953Smsmith		 */
36169953Smsmith		if (cio->offset >= pci_numdevs) {
36269953Smsmith			cio->status = PCI_GETCONF_LAST_DEVICE;
36369953Smsmith			error = 0;
36469953Smsmith			break;
36569953Smsmith		}
36669953Smsmith
36769953Smsmith		/* get the head of the device queue */
36869953Smsmith		devlist_head = &pci_devq;
36969953Smsmith
37069953Smsmith		/*
37169953Smsmith		 * Determine how much room we have for pci_conf structures.
37269953Smsmith		 * Round the user's buffer size down to the nearest
37369953Smsmith		 * multiple of sizeof(struct pci_conf) in case the user
37469953Smsmith		 * didn't specify a multiple of that size.
37569953Smsmith		 */
376172999Simp#ifdef PRE7_COMPAT
377172932Smarius		if (cmd == PCIOCGETCONF_OLD)
378172932Smarius			confsz = sizeof(struct pci_conf_old);
379172932Smarius		else
380172932Smarius#endif
381172932Smarius			confsz = sizeof(struct pci_conf);
382172932Smarius		iolen = min(cio->match_buf_len - (cio->match_buf_len % confsz),
383172932Smarius		    pci_numdevs * confsz);
38469953Smsmith
38569953Smsmith		/*
38669953Smsmith		 * Since we know that iolen is a multiple of the size of
38769953Smsmith		 * the pciconf union, it's okay to do this.
38869953Smsmith		 */
389172932Smarius		ionum = iolen / confsz;
39069953Smsmith
39169953Smsmith		/*
39269953Smsmith		 * If this test is true, the user wants the pci_conf
39369953Smsmith		 * structures returned to match the supplied entries.
39469953Smsmith		 */
395116704Sjmg		if ((cio->num_patterns > 0) && (cio->num_patterns < pci_numdevs)
39669953Smsmith		 && (cio->pat_buf_len > 0)) {
39769953Smsmith			/*
39869953Smsmith			 * pat_buf_len needs to be:
39969953Smsmith			 * num_patterns * sizeof(struct pci_match_conf)
40069953Smsmith			 * While it is certainly possible the user just
40169953Smsmith			 * allocated a large buffer, but set the number of
40269953Smsmith			 * matches correctly, it is far more likely that
40369953Smsmith			 * their kernel doesn't match the userland utility
40469953Smsmith			 * they're using.  It's also possible that the user
40569953Smsmith			 * forgot to initialize some variables.  Yes, this
40669953Smsmith			 * may be overly picky, but I hazard to guess that
40769953Smsmith			 * it's far more likely to just catch folks that
40869953Smsmith			 * updated their kernel but not their userland.
40969953Smsmith			 */
410172999Simp#ifdef PRE7_COMPAT
411172932Smarius			if (cmd == PCIOCGETCONF_OLD)
412172932Smarius				pbufsz = sizeof(struct pci_match_conf_old);
413172932Smarius			else
414172932Smarius#endif
415172932Smarius				pbufsz = sizeof(struct pci_match_conf);
416172932Smarius			if (cio->num_patterns * pbufsz != cio->pat_buf_len) {
417172932Smarius				/* The user made a mistake, return an error. */
41869953Smsmith				cio->status = PCI_GETCONF_ERROR;
41969953Smsmith				error = EINVAL;
42069953Smsmith				break;
42169953Smsmith			}
42269953Smsmith
42369953Smsmith			/*
42469953Smsmith			 * Allocate a buffer to hold the patterns.
42569953Smsmith			 */
426172999Simp#ifdef PRE7_COMPAT
427172932Smarius			if (cmd == PCIOCGETCONF_OLD) {
428172932Smarius				pattern_buf_old = malloc(cio->pat_buf_len,
429172932Smarius				    M_TEMP, M_WAITOK);
430172932Smarius				error = copyin(cio->patterns,
431172932Smarius				    pattern_buf_old, cio->pat_buf_len);
432172932Smarius			} else
433172932Smarius#endif
434172932Smarius			{
435172932Smarius				pattern_buf = malloc(cio->pat_buf_len, M_TEMP,
436172932Smarius				    M_WAITOK);
437172932Smarius				error = copyin(cio->patterns, pattern_buf,
438172932Smarius				    cio->pat_buf_len);
439172932Smarius			}
440116702Sjmg			if (error != 0) {
441116702Sjmg				error = EINVAL;
442116702Sjmg				goto getconfexit;
443116702Sjmg			}
44469953Smsmith			num_patterns = cio->num_patterns;
44569953Smsmith		} else if ((cio->num_patterns > 0)
44669953Smsmith			|| (cio->pat_buf_len > 0)) {
44769953Smsmith			/*
44869953Smsmith			 * The user made a mistake, spit out an error.
44969953Smsmith			 */
45069953Smsmith			cio->status = PCI_GETCONF_ERROR;
45169953Smsmith			error = EINVAL;
45269953Smsmith			break;
453172932Smarius		}
45469953Smsmith
45569953Smsmith		/*
45669953Smsmith		 * Go through the list of devices and copy out the devices
45769953Smsmith		 * that match the user's criteria.
45869953Smsmith		 */
45969953Smsmith		for (cio->num_matches = 0, error = 0, i = 0,
46069953Smsmith		     dinfo = STAILQ_FIRST(devlist_head);
46169953Smsmith		     (dinfo != NULL) && (cio->num_matches < ionum)
462116702Sjmg		     && (error == 0) && (i < pci_numdevs) && (dinfo != NULL);
46369953Smsmith		     dinfo = STAILQ_NEXT(dinfo, pci_links), i++) {
46469953Smsmith
46569953Smsmith			if (i < cio->offset)
46669953Smsmith				continue;
46769953Smsmith
46869953Smsmith			/* Populate pd_name and pd_unit */
46969953Smsmith			name = NULL;
47069953Smsmith			if (dinfo->cfg.dev && dinfo->conf.pd_name[0] == '\0')
47169953Smsmith				name = device_get_name(dinfo->cfg.dev);
47269953Smsmith			if (name) {
47369953Smsmith				strncpy(dinfo->conf.pd_name, name,
47469953Smsmith					sizeof(dinfo->conf.pd_name));
47569953Smsmith				dinfo->conf.pd_name[PCI_MAXNAMELEN] = 0;
47669953Smsmith				dinfo->conf.pd_unit =
47769953Smsmith					device_get_unit(dinfo->cfg.dev);
47869953Smsmith			}
47969953Smsmith
480172999Simp#ifdef PRE7_COMPAT
481172932Smarius			if ((cmd == PCIOCGETCONF_OLD &&
482172932Smarius			    (pattern_buf_old == NULL ||
483172932Smarius			    pci_conf_match_old(pattern_buf_old, num_patterns,
484172932Smarius			    &dinfo->conf) == 0)) ||
485172932Smarius			    (cmd == PCIOCGETCONF &&
486172932Smarius			    (pattern_buf == NULL ||
487172932Smarius			    pci_conf_match(pattern_buf, num_patterns,
488172932Smarius			    &dinfo->conf) == 0))) {
489172932Smarius#else
490172932Smarius			if (pattern_buf == NULL ||
491172932Smarius			    pci_conf_match(pattern_buf, num_patterns,
492172932Smarius			    &dinfo->conf) == 0) {
493172932Smarius#endif
49469953Smsmith				/*
49569953Smsmith				 * If we've filled up the user's buffer,
49669953Smsmith				 * break out at this point.  Since we've
49769953Smsmith				 * got a match here, we'll pick right back
49869953Smsmith				 * up at the matching entry.  We can also
49969953Smsmith				 * tell the user that there are more matches
50069953Smsmith				 * left.
50169953Smsmith				 */
50269953Smsmith				if (cio->num_matches >= ionum)
50369953Smsmith					break;
50469953Smsmith
505172999Simp#ifdef PRE7_COMPAT
506172932Smarius				if (cmd == PCIOCGETCONF_OLD) {
507172932Smarius					conf_old.pc_sel.pc_bus =
508172932Smarius					    dinfo->conf.pc_sel.pc_bus;
509172932Smarius					conf_old.pc_sel.pc_dev =
510172932Smarius					    dinfo->conf.pc_sel.pc_dev;
511172932Smarius					conf_old.pc_sel.pc_func =
512172932Smarius					    dinfo->conf.pc_sel.pc_func;
513172932Smarius					conf_old.pc_hdr = dinfo->conf.pc_hdr;
514172932Smarius					conf_old.pc_subvendor =
515172932Smarius					    dinfo->conf.pc_subvendor;
516172932Smarius					conf_old.pc_subdevice =
517172932Smarius					    dinfo->conf.pc_subdevice;
518172932Smarius					conf_old.pc_vendor =
519172932Smarius					    dinfo->conf.pc_vendor;
520172932Smarius					conf_old.pc_device =
521172932Smarius					    dinfo->conf.pc_device;
522172932Smarius					conf_old.pc_class =
523172932Smarius					    dinfo->conf.pc_class;
524172932Smarius					conf_old.pc_subclass =
525172932Smarius					    dinfo->conf.pc_subclass;
526172932Smarius					conf_old.pc_progif =
527172932Smarius					    dinfo->conf.pc_progif;
528172932Smarius					conf_old.pc_revid =
529172932Smarius					    dinfo->conf.pc_revid;
530172932Smarius					conf_old.pd_name[0] = '\0';
531172932Smarius					conf_old.pd_unit = 0;
532172932Smarius					if (name) {
533172932Smarius						strncpy(conf_old.pd_name, name,
534172932Smarius						    sizeof(conf_old.pd_name));
535172932Smarius						conf_old.
536172932Smarius						    pd_name[PCI_MAXNAMELEN] = 0;
537172932Smarius						conf_old.pd_unit =
538172932Smarius						    dinfo->conf.pd_unit;
539172932Smarius					}
540172932Smarius					confdata = &conf_old;
541172932Smarius				} else
542172932Smarius#endif
543172932Smarius					confdata = &dinfo->conf;
544172932Smarius				/* Only if we can copy it out do we count it. */
545172932Smarius				if (!(error = copyout(confdata,
546172932Smarius				    &cio->matches[cio->num_matches], confsz)))
547116702Sjmg					cio->num_matches++;
54869953Smsmith			}
54969953Smsmith		}
55069953Smsmith
55169953Smsmith		/*
55269953Smsmith		 * Set the pointer into the list, so if the user is getting
55369953Smsmith		 * n records at a time, where n < pci_numdevs,
55469953Smsmith		 */
55569953Smsmith		cio->offset = i;
55669953Smsmith
55769953Smsmith		/*
55869953Smsmith		 * Set the generation, the user will need this if they make
55969953Smsmith		 * another ioctl call with offset != 0.
56069953Smsmith		 */
56169953Smsmith		cio->generation = pci_generation;
562172932Smarius
56369953Smsmith		/*
56469953Smsmith		 * If this is the last device, inform the user so he won't
56569953Smsmith		 * bother asking for more devices.  If dinfo isn't NULL, we
56669953Smsmith		 * know that there are more matches in the list because of
56769953Smsmith		 * the way the traversal is done.
56869953Smsmith		 */
56969953Smsmith		if (dinfo == NULL)
57069953Smsmith			cio->status = PCI_GETCONF_LAST_DEVICE;
57169953Smsmith		else
57269953Smsmith			cio->status = PCI_GETCONF_MORE_DEVS;
57369953Smsmith
574116702Sjmggetconfexit:
57569953Smsmith		if (pattern_buf != NULL)
57669953Smsmith			free(pattern_buf, M_TEMP);
577172999Simp#ifdef PRE7_COMPAT
578172932Smarius		if (pattern_buf_old != NULL)
579172932Smarius			free(pattern_buf_old, M_TEMP);
580172932Smarius#endif
58169953Smsmith
58269953Smsmith		break;
583121013Sse
584172999Simp#ifdef PRE7_COMPAT
585172932Smarius	case PCIOCREAD_OLD:
586172932Smarius	case PCIOCWRITE_OLD:
587172932Smarius		io_old = (struct pci_io_old *)data;
588172932Smarius		iodata.pi_sel.pc_domain = 0;
589172932Smarius		iodata.pi_sel.pc_bus = io_old->pi_sel.pc_bus;
590172932Smarius		iodata.pi_sel.pc_dev = io_old->pi_sel.pc_dev;
591172932Smarius		iodata.pi_sel.pc_func = io_old->pi_sel.pc_func;
592172932Smarius		iodata.pi_reg = io_old->pi_reg;
593172932Smarius		iodata.pi_width = io_old->pi_width;
594172932Smarius		iodata.pi_data = io_old->pi_data;
595172932Smarius		data = (caddr_t)&iodata;
596172932Smarius		/* FALLTHROUGH */
597172932Smarius#endif
59869953Smsmith	case PCIOCREAD:
599121013Sse	case PCIOCWRITE:
60069953Smsmith		io = (struct pci_io *)data;
60169953Smsmith		switch(io->pi_width) {
60269953Smsmith		case 4:
60369953Smsmith		case 2:
60469953Smsmith		case 1:
605163055Sru			/* Make sure register is in bounds and aligned. */
606172932Smarius			if (io->pi_reg < 0 ||
607163055Sru			    io->pi_reg + io->pi_width > PCI_REGMAX + 1 ||
608172932Smarius			    io->pi_reg & (io->pi_width - 1)) {
609163055Sru				error = EINVAL;
610163055Sru				break;
611163055Sru			}
61269953Smsmith			/*
61369953Smsmith			 * Assume that the user-level bus number is
614145022Sbms			 * in fact the physical PCI bus number.
615145022Sbms			 * Look up the grandparent, i.e. the bridge device,
616145022Sbms			 * so that we can issue configuration space cycles.
61769953Smsmith			 */
618172394Smarius			pcidev = pci_find_dbsf(io->pi_sel.pc_domain,
619172394Smarius			    io->pi_sel.pc_bus, io->pi_sel.pc_dev,
620172394Smarius			    io->pi_sel.pc_func);
621145022Sbms			if (pcidev) {
622172932Smarius				brdev = device_get_parent(
623172932Smarius				    device_get_parent(pcidev));
624145022Sbms
625172999Simp#ifdef PRE7_COMPAT
626172932Smarius				if (cmd == PCIOCWRITE || cmd == PCIOCWRITE_OLD)
627172932Smarius#else
628121013Sse				if (cmd == PCIOCWRITE)
629172932Smarius#endif
630145022Sbms					PCIB_WRITE_CONFIG(brdev,
631145022Sbms							  io->pi_sel.pc_bus,
632121013Sse							  io->pi_sel.pc_dev,
633121013Sse							  io->pi_sel.pc_func,
634121013Sse							  io->pi_reg,
635121013Sse							  io->pi_data,
636121013Sse							  io->pi_width);
637172999Simp#ifdef PRE7_COMPAT
638172932Smarius				else if (cmd == PCIOCREAD_OLD)
639172932Smarius					io_old->pi_data =
640172932Smarius						PCIB_READ_CONFIG(brdev,
641172932Smarius							  io->pi_sel.pc_bus,
642172932Smarius							  io->pi_sel.pc_dev,
643172932Smarius							  io->pi_sel.pc_func,
644172932Smarius							  io->pi_reg,
645172932Smarius							  io->pi_width);
646172932Smarius#endif
647121013Sse				else
648121013Sse					io->pi_data =
649145022Sbms						PCIB_READ_CONFIG(brdev,
650145022Sbms							  io->pi_sel.pc_bus,
651121013Sse							  io->pi_sel.pc_dev,
652121013Sse							  io->pi_sel.pc_func,
653121013Sse							  io->pi_reg,
654121013Sse							  io->pi_width);
65569953Smsmith				error = 0;
65669953Smsmith			} else {
657149478Sps#ifdef COMPAT_FREEBSD4
658172932Smarius				if (cmd == PCIOCREAD_OLD) {
659172932Smarius					io_old->pi_data = -1;
660149478Sps					error = 0;
661149478Sps				} else
662149478Sps#endif
663149478Sps					error = ENODEV;
66469953Smsmith			}
66569953Smsmith			break;
66669953Smsmith		default:
667116702Sjmg			error = EINVAL;
66869953Smsmith			break;
66969953Smsmith		}
67069953Smsmith		break;
67169953Smsmith
67269953Smsmith	default:
67369953Smsmith		error = ENOTTY;
67469953Smsmith		break;
67569953Smsmith	}
67669953Smsmith
67769953Smsmith	return (error);
67869953Smsmith}
679