pci_user.c revision 188018
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 188018 2009-02-02 19:54:16Z jhb $");
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 */
218174932Smarius	pci_getconf_flags_old	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;
310188018Sjhb	struct pci_bar_io *bio;
311172932Smarius	struct pci_match_conf *pattern_buf;
312188018Sjhb	struct resource_list_entry *rle;
313188018Sjhb	uint32_t value;
314172932Smarius	size_t confsz, iolen, pbufsz;
315172932Smarius	int error, ionum, i, num_patterns;
316172999Simp#ifdef PRE7_COMPAT
317172932Smarius	struct pci_conf_old conf_old;
318172932Smarius	struct pci_io iodata;
319172932Smarius	struct pci_io_old *io_old;
320172932Smarius	struct pci_match_conf_old *pattern_buf_old;
32169953Smsmith
322172932Smarius	io_old = NULL;
323172932Smarius	pattern_buf_old = NULL;
324172932Smarius
325188018Sjhb	if (!(flag & FWRITE) && cmd != PCIOCGETBAR &&
326188018Sjhb	    cmd != PCIOCGETCONF && cmd != PCIOCGETCONF_OLD)
327172932Smarius		return EPERM;
328172932Smarius#else
329188018Sjhb	if (!(flag & FWRITE) && cmd != PCIOCGETBAR && cmd != PCIOCGETCONF)
33069953Smsmith		return EPERM;
331172932Smarius#endif
33269953Smsmith
33369953Smsmith	switch(cmd) {
334172999Simp#ifdef PRE7_COMPAT
335172932Smarius	case PCIOCGETCONF_OLD:
336172932Smarius		/* FALLTHROUGH */
337172932Smarius#endif
33869953Smsmith	case PCIOCGETCONF:
33969953Smsmith		cio = (struct pci_conf_io *)data;
34069953Smsmith
341172932Smarius		pattern_buf = NULL;
34269953Smsmith		num_patterns = 0;
34369953Smsmith		dinfo = NULL;
34469953Smsmith
345172932Smarius		cio->num_matches = 0;
346172932Smarius
34769953Smsmith		/*
34869953Smsmith		 * If the user specified an offset into the device list,
34969953Smsmith		 * but the list has changed since they last called this
35069953Smsmith		 * ioctl, tell them that the list has changed.  They will
35169953Smsmith		 * have to get the list from the beginning.
35269953Smsmith		 */
35369953Smsmith		if ((cio->offset != 0)
35469953Smsmith		 && (cio->generation != pci_generation)){
35569953Smsmith			cio->status = PCI_GETCONF_LIST_CHANGED;
35669953Smsmith			error = 0;
35769953Smsmith			break;
35869953Smsmith		}
35969953Smsmith
36069953Smsmith		/*
36169953Smsmith		 * Check to see whether the user has asked for an offset
36269953Smsmith		 * past the end of our list.
36369953Smsmith		 */
36469953Smsmith		if (cio->offset >= pci_numdevs) {
36569953Smsmith			cio->status = PCI_GETCONF_LAST_DEVICE;
36669953Smsmith			error = 0;
36769953Smsmith			break;
36869953Smsmith		}
36969953Smsmith
37069953Smsmith		/* get the head of the device queue */
37169953Smsmith		devlist_head = &pci_devq;
37269953Smsmith
37369953Smsmith		/*
37469953Smsmith		 * Determine how much room we have for pci_conf structures.
37569953Smsmith		 * Round the user's buffer size down to the nearest
37669953Smsmith		 * multiple of sizeof(struct pci_conf) in case the user
37769953Smsmith		 * didn't specify a multiple of that size.
37869953Smsmith		 */
379172999Simp#ifdef PRE7_COMPAT
380172932Smarius		if (cmd == PCIOCGETCONF_OLD)
381172932Smarius			confsz = sizeof(struct pci_conf_old);
382172932Smarius		else
383172932Smarius#endif
384172932Smarius			confsz = sizeof(struct pci_conf);
385172932Smarius		iolen = min(cio->match_buf_len - (cio->match_buf_len % confsz),
386172932Smarius		    pci_numdevs * confsz);
38769953Smsmith
38869953Smsmith		/*
38969953Smsmith		 * Since we know that iolen is a multiple of the size of
39069953Smsmith		 * the pciconf union, it's okay to do this.
39169953Smsmith		 */
392172932Smarius		ionum = iolen / confsz;
39369953Smsmith
39469953Smsmith		/*
39569953Smsmith		 * If this test is true, the user wants the pci_conf
39669953Smsmith		 * structures returned to match the supplied entries.
39769953Smsmith		 */
398116704Sjmg		if ((cio->num_patterns > 0) && (cio->num_patterns < pci_numdevs)
39969953Smsmith		 && (cio->pat_buf_len > 0)) {
40069953Smsmith			/*
40169953Smsmith			 * pat_buf_len needs to be:
40269953Smsmith			 * num_patterns * sizeof(struct pci_match_conf)
40369953Smsmith			 * While it is certainly possible the user just
40469953Smsmith			 * allocated a large buffer, but set the number of
40569953Smsmith			 * matches correctly, it is far more likely that
40669953Smsmith			 * their kernel doesn't match the userland utility
40769953Smsmith			 * they're using.  It's also possible that the user
40869953Smsmith			 * forgot to initialize some variables.  Yes, this
40969953Smsmith			 * may be overly picky, but I hazard to guess that
41069953Smsmith			 * it's far more likely to just catch folks that
41169953Smsmith			 * updated their kernel but not their userland.
41269953Smsmith			 */
413172999Simp#ifdef PRE7_COMPAT
414172932Smarius			if (cmd == PCIOCGETCONF_OLD)
415172932Smarius				pbufsz = sizeof(struct pci_match_conf_old);
416172932Smarius			else
417172932Smarius#endif
418172932Smarius				pbufsz = sizeof(struct pci_match_conf);
419172932Smarius			if (cio->num_patterns * pbufsz != cio->pat_buf_len) {
420172932Smarius				/* The user made a mistake, return an error. */
42169953Smsmith				cio->status = PCI_GETCONF_ERROR;
42269953Smsmith				error = EINVAL;
42369953Smsmith				break;
42469953Smsmith			}
42569953Smsmith
42669953Smsmith			/*
42769953Smsmith			 * Allocate a buffer to hold the patterns.
42869953Smsmith			 */
429172999Simp#ifdef PRE7_COMPAT
430172932Smarius			if (cmd == PCIOCGETCONF_OLD) {
431172932Smarius				pattern_buf_old = malloc(cio->pat_buf_len,
432172932Smarius				    M_TEMP, M_WAITOK);
433172932Smarius				error = copyin(cio->patterns,
434172932Smarius				    pattern_buf_old, cio->pat_buf_len);
435172932Smarius			} else
436172932Smarius#endif
437172932Smarius			{
438172932Smarius				pattern_buf = malloc(cio->pat_buf_len, M_TEMP,
439172932Smarius				    M_WAITOK);
440172932Smarius				error = copyin(cio->patterns, pattern_buf,
441172932Smarius				    cio->pat_buf_len);
442172932Smarius			}
443116702Sjmg			if (error != 0) {
444116702Sjmg				error = EINVAL;
445116702Sjmg				goto getconfexit;
446116702Sjmg			}
44769953Smsmith			num_patterns = cio->num_patterns;
44869953Smsmith		} else if ((cio->num_patterns > 0)
44969953Smsmith			|| (cio->pat_buf_len > 0)) {
45069953Smsmith			/*
45169953Smsmith			 * The user made a mistake, spit out an error.
45269953Smsmith			 */
45369953Smsmith			cio->status = PCI_GETCONF_ERROR;
45469953Smsmith			error = EINVAL;
45569953Smsmith			break;
456172932Smarius		}
45769953Smsmith
45869953Smsmith		/*
45969953Smsmith		 * Go through the list of devices and copy out the devices
46069953Smsmith		 * that match the user's criteria.
46169953Smsmith		 */
46269953Smsmith		for (cio->num_matches = 0, error = 0, i = 0,
46369953Smsmith		     dinfo = STAILQ_FIRST(devlist_head);
46469953Smsmith		     (dinfo != NULL) && (cio->num_matches < ionum)
465116702Sjmg		     && (error == 0) && (i < pci_numdevs) && (dinfo != NULL);
46669953Smsmith		     dinfo = STAILQ_NEXT(dinfo, pci_links), i++) {
46769953Smsmith
46869953Smsmith			if (i < cio->offset)
46969953Smsmith				continue;
47069953Smsmith
47169953Smsmith			/* Populate pd_name and pd_unit */
47269953Smsmith			name = NULL;
473175368Sjhb			if (dinfo->cfg.dev)
47469953Smsmith				name = device_get_name(dinfo->cfg.dev);
47569953Smsmith			if (name) {
47669953Smsmith				strncpy(dinfo->conf.pd_name, name,
47769953Smsmith					sizeof(dinfo->conf.pd_name));
47869953Smsmith				dinfo->conf.pd_name[PCI_MAXNAMELEN] = 0;
47969953Smsmith				dinfo->conf.pd_unit =
48069953Smsmith					device_get_unit(dinfo->cfg.dev);
481175368Sjhb			} else {
482175368Sjhb				dinfo->conf.pd_name[0] = '\0';
483175368Sjhb				dinfo->conf.pd_unit = 0;
48469953Smsmith			}
48569953Smsmith
486172999Simp#ifdef PRE7_COMPAT
487172932Smarius			if ((cmd == PCIOCGETCONF_OLD &&
488172932Smarius			    (pattern_buf_old == NULL ||
489172932Smarius			    pci_conf_match_old(pattern_buf_old, num_patterns,
490172932Smarius			    &dinfo->conf) == 0)) ||
491172932Smarius			    (cmd == PCIOCGETCONF &&
492172932Smarius			    (pattern_buf == NULL ||
493172932Smarius			    pci_conf_match(pattern_buf, num_patterns,
494172932Smarius			    &dinfo->conf) == 0))) {
495172932Smarius#else
496172932Smarius			if (pattern_buf == NULL ||
497172932Smarius			    pci_conf_match(pattern_buf, num_patterns,
498172932Smarius			    &dinfo->conf) == 0) {
499172932Smarius#endif
50069953Smsmith				/*
50169953Smsmith				 * If we've filled up the user's buffer,
50269953Smsmith				 * break out at this point.  Since we've
50369953Smsmith				 * got a match here, we'll pick right back
50469953Smsmith				 * up at the matching entry.  We can also
50569953Smsmith				 * tell the user that there are more matches
50669953Smsmith				 * left.
50769953Smsmith				 */
50869953Smsmith				if (cio->num_matches >= ionum)
50969953Smsmith					break;
51069953Smsmith
511172999Simp#ifdef PRE7_COMPAT
512172932Smarius				if (cmd == PCIOCGETCONF_OLD) {
513172932Smarius					conf_old.pc_sel.pc_bus =
514172932Smarius					    dinfo->conf.pc_sel.pc_bus;
515172932Smarius					conf_old.pc_sel.pc_dev =
516172932Smarius					    dinfo->conf.pc_sel.pc_dev;
517172932Smarius					conf_old.pc_sel.pc_func =
518172932Smarius					    dinfo->conf.pc_sel.pc_func;
519172932Smarius					conf_old.pc_hdr = dinfo->conf.pc_hdr;
520172932Smarius					conf_old.pc_subvendor =
521172932Smarius					    dinfo->conf.pc_subvendor;
522172932Smarius					conf_old.pc_subdevice =
523172932Smarius					    dinfo->conf.pc_subdevice;
524172932Smarius					conf_old.pc_vendor =
525172932Smarius					    dinfo->conf.pc_vendor;
526172932Smarius					conf_old.pc_device =
527172932Smarius					    dinfo->conf.pc_device;
528172932Smarius					conf_old.pc_class =
529172932Smarius					    dinfo->conf.pc_class;
530172932Smarius					conf_old.pc_subclass =
531172932Smarius					    dinfo->conf.pc_subclass;
532172932Smarius					conf_old.pc_progif =
533172932Smarius					    dinfo->conf.pc_progif;
534172932Smarius					conf_old.pc_revid =
535172932Smarius					    dinfo->conf.pc_revid;
536174932Smarius					strncpy(conf_old.pd_name,
537174932Smarius					    dinfo->conf.pd_name,
538174932Smarius					    sizeof(conf_old.pd_name));
539174932Smarius					conf_old.pd_name[PCI_MAXNAMELEN] = 0;
540174932Smarius					conf_old.pd_unit =
541174932Smarius					    dinfo->conf.pd_unit;
542172932Smarius					confdata = &conf_old;
543172932Smarius				} else
544172932Smarius#endif
545172932Smarius					confdata = &dinfo->conf;
546172932Smarius				/* Only if we can copy it out do we count it. */
547172932Smarius				if (!(error = copyout(confdata,
548174932Smarius				    (caddr_t)cio->matches +
549174932Smarius				    confsz * cio->num_matches, confsz)))
550116702Sjmg					cio->num_matches++;
55169953Smsmith			}
55269953Smsmith		}
55369953Smsmith
55469953Smsmith		/*
55569953Smsmith		 * Set the pointer into the list, so if the user is getting
55669953Smsmith		 * n records at a time, where n < pci_numdevs,
55769953Smsmith		 */
55869953Smsmith		cio->offset = i;
55969953Smsmith
56069953Smsmith		/*
56169953Smsmith		 * Set the generation, the user will need this if they make
56269953Smsmith		 * another ioctl call with offset != 0.
56369953Smsmith		 */
56469953Smsmith		cio->generation = pci_generation;
565172932Smarius
56669953Smsmith		/*
56769953Smsmith		 * If this is the last device, inform the user so he won't
56869953Smsmith		 * bother asking for more devices.  If dinfo isn't NULL, we
56969953Smsmith		 * know that there are more matches in the list because of
57069953Smsmith		 * the way the traversal is done.
57169953Smsmith		 */
57269953Smsmith		if (dinfo == NULL)
57369953Smsmith			cio->status = PCI_GETCONF_LAST_DEVICE;
57469953Smsmith		else
57569953Smsmith			cio->status = PCI_GETCONF_MORE_DEVS;
57669953Smsmith
577116702Sjmggetconfexit:
57869953Smsmith		if (pattern_buf != NULL)
57969953Smsmith			free(pattern_buf, M_TEMP);
580172999Simp#ifdef PRE7_COMPAT
581172932Smarius		if (pattern_buf_old != NULL)
582172932Smarius			free(pattern_buf_old, M_TEMP);
583172932Smarius#endif
58469953Smsmith
58569953Smsmith		break;
586121013Sse
587172999Simp#ifdef PRE7_COMPAT
588172932Smarius	case PCIOCREAD_OLD:
589172932Smarius	case PCIOCWRITE_OLD:
590172932Smarius		io_old = (struct pci_io_old *)data;
591172932Smarius		iodata.pi_sel.pc_domain = 0;
592172932Smarius		iodata.pi_sel.pc_bus = io_old->pi_sel.pc_bus;
593172932Smarius		iodata.pi_sel.pc_dev = io_old->pi_sel.pc_dev;
594172932Smarius		iodata.pi_sel.pc_func = io_old->pi_sel.pc_func;
595172932Smarius		iodata.pi_reg = io_old->pi_reg;
596172932Smarius		iodata.pi_width = io_old->pi_width;
597172932Smarius		iodata.pi_data = io_old->pi_data;
598172932Smarius		data = (caddr_t)&iodata;
599172932Smarius		/* FALLTHROUGH */
600172932Smarius#endif
60169953Smsmith	case PCIOCREAD:
602121013Sse	case PCIOCWRITE:
60369953Smsmith		io = (struct pci_io *)data;
60469953Smsmith		switch(io->pi_width) {
60569953Smsmith		case 4:
60669953Smsmith		case 2:
60769953Smsmith		case 1:
608163055Sru			/* Make sure register is in bounds and aligned. */
609172932Smarius			if (io->pi_reg < 0 ||
610163055Sru			    io->pi_reg + io->pi_width > PCI_REGMAX + 1 ||
611172932Smarius			    io->pi_reg & (io->pi_width - 1)) {
612163055Sru				error = EINVAL;
613163055Sru				break;
614163055Sru			}
61569953Smsmith			/*
61669953Smsmith			 * Assume that the user-level bus number is
617145022Sbms			 * in fact the physical PCI bus number.
618145022Sbms			 * Look up the grandparent, i.e. the bridge device,
619145022Sbms			 * so that we can issue configuration space cycles.
62069953Smsmith			 */
621172394Smarius			pcidev = pci_find_dbsf(io->pi_sel.pc_domain,
622172394Smarius			    io->pi_sel.pc_bus, io->pi_sel.pc_dev,
623172394Smarius			    io->pi_sel.pc_func);
624145022Sbms			if (pcidev) {
625172932Smarius				brdev = device_get_parent(
626172932Smarius				    device_get_parent(pcidev));
627145022Sbms
628172999Simp#ifdef PRE7_COMPAT
629172932Smarius				if (cmd == PCIOCWRITE || cmd == PCIOCWRITE_OLD)
630172932Smarius#else
631121013Sse				if (cmd == PCIOCWRITE)
632172932Smarius#endif
633145022Sbms					PCIB_WRITE_CONFIG(brdev,
634145022Sbms							  io->pi_sel.pc_bus,
635121013Sse							  io->pi_sel.pc_dev,
636121013Sse							  io->pi_sel.pc_func,
637121013Sse							  io->pi_reg,
638121013Sse							  io->pi_data,
639121013Sse							  io->pi_width);
640172999Simp#ifdef PRE7_COMPAT
641172932Smarius				else if (cmd == PCIOCREAD_OLD)
642172932Smarius					io_old->pi_data =
643172932Smarius						PCIB_READ_CONFIG(brdev,
644172932Smarius							  io->pi_sel.pc_bus,
645172932Smarius							  io->pi_sel.pc_dev,
646172932Smarius							  io->pi_sel.pc_func,
647172932Smarius							  io->pi_reg,
648172932Smarius							  io->pi_width);
649172932Smarius#endif
650121013Sse				else
651121013Sse					io->pi_data =
652145022Sbms						PCIB_READ_CONFIG(brdev,
653145022Sbms							  io->pi_sel.pc_bus,
654121013Sse							  io->pi_sel.pc_dev,
655121013Sse							  io->pi_sel.pc_func,
656121013Sse							  io->pi_reg,
657121013Sse							  io->pi_width);
65869953Smsmith				error = 0;
65969953Smsmith			} else {
660149478Sps#ifdef COMPAT_FREEBSD4
661172932Smarius				if (cmd == PCIOCREAD_OLD) {
662172932Smarius					io_old->pi_data = -1;
663149478Sps					error = 0;
664149478Sps				} else
665149478Sps#endif
666149478Sps					error = ENODEV;
66769953Smsmith			}
66869953Smsmith			break;
66969953Smsmith		default:
670116702Sjmg			error = EINVAL;
67169953Smsmith			break;
67269953Smsmith		}
67369953Smsmith		break;
67469953Smsmith
675188018Sjhb	case PCIOCGETBAR:
676188018Sjhb		bio = (struct pci_bar_io *)data;
677188018Sjhb
678188018Sjhb		/*
679188018Sjhb		 * Assume that the user-level bus number is
680188018Sjhb		 * in fact the physical PCI bus number.
681188018Sjhb		 */
682188018Sjhb		pcidev = pci_find_dbsf(bio->pbi_sel.pc_domain,
683188018Sjhb		    bio->pbi_sel.pc_bus, bio->pbi_sel.pc_dev,
684188018Sjhb		    bio->pbi_sel.pc_func);
685188018Sjhb		if (pcidev == NULL) {
686188018Sjhb			error = ENODEV;
687188018Sjhb			break;
688188018Sjhb		}
689188018Sjhb		dinfo = device_get_ivars(pcidev);
690188018Sjhb
691188018Sjhb		/*
692188018Sjhb		 * Look for a resource list entry matching the requested BAR.
693188018Sjhb		 *
694188018Sjhb		 * XXX: This will not find BARs that are not initialized, but
695188018Sjhb		 * maybe that is ok?
696188018Sjhb		 */
697188018Sjhb		rle = resource_list_find(&dinfo->resources, SYS_RES_MEMORY,
698188018Sjhb		    bio->pbi_reg);
699188018Sjhb		if (rle == NULL)
700188018Sjhb			rle = resource_list_find(&dinfo->resources,
701188018Sjhb			    SYS_RES_IOPORT, bio->pbi_reg);
702188018Sjhb		if (rle == NULL || rle->res == NULL) {
703188018Sjhb			error = EINVAL;
704188018Sjhb			break;
705188018Sjhb		}
706188018Sjhb
707188018Sjhb		/*
708188018Sjhb		 * Ok, we have a resource for this BAR.  Read the lower
709188018Sjhb		 * 32 bits to get any flags.
710188018Sjhb		 */
711188018Sjhb		value = pci_read_config(pcidev, bio->pbi_reg, 4);
712188018Sjhb		if (PCI_BAR_MEM(value)) {
713188018Sjhb			if (rle->type != SYS_RES_MEMORY) {
714188018Sjhb				error = EINVAL;
715188018Sjhb				break;
716188018Sjhb			}
717188018Sjhb			value &= ~PCIM_BAR_MEM_BASE;
718188018Sjhb		} else {
719188018Sjhb			if (rle->type != SYS_RES_IOPORT) {
720188018Sjhb				error = EINVAL;
721188018Sjhb				break;
722188018Sjhb			}
723188018Sjhb			value &= ~PCIM_BAR_IO_BASE;
724188018Sjhb		}
725188018Sjhb		bio->pbi_base = rman_get_start(rle->res) | value;
726188018Sjhb		bio->pbi_length = rman_get_size(rle->res);
727188018Sjhb
728188018Sjhb		/*
729188018Sjhb		 * Check the command register to determine if this BAR
730188018Sjhb		 * is enabled.
731188018Sjhb		 */
732188018Sjhb		value = pci_read_config(pcidev, PCIR_COMMAND, 2);
733188018Sjhb		if (rle->type == SYS_RES_MEMORY)
734188018Sjhb			bio->pbi_enabled = (value & PCIM_CMD_MEMEN) != 0;
735188018Sjhb		else
736188018Sjhb			bio->pbi_enabled = (value & PCIM_CMD_PORTEN) != 0;
737188018Sjhb		error = 0;
738188018Sjhb		break;
73969953Smsmith	default:
74069953Smsmith		error = ENOTTY;
74169953Smsmith		break;
74269953Smsmith	}
74369953Smsmith
74469953Smsmith	return (error);
74569953Smsmith}
746