pci_user.c revision 295813
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 295813 2016-02-19 16:43:03Z se $");
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
228240981Ssobomax#ifdef COMPAT_FREEBSD32
229240981Ssobomaxstruct pci_conf_old32 {
230240992Sglebius	struct pcisel_old pc_sel;	/* bus+slot+function */
231240992Sglebius	uint8_t		pc_hdr;		/* PCI header type */
232240992Sglebius	uint16_t	pc_subvendor;	/* card vendor ID */
233240992Sglebius	uint16_t	pc_subdevice;	/* card device ID, assigned by
234240992Sglebius					   card vendor */
235240992Sglebius	uint16_t	pc_vendor;	/* chip vendor ID */
236240992Sglebius	uint16_t	pc_device;	/* chip device ID, assigned by
237240992Sglebius					   chip vendor */
238240992Sglebius	uint8_t		pc_class;	/* chip PCI class */
239240992Sglebius	uint8_t		pc_subclass;	/* chip PCI subclass */
240240992Sglebius	uint8_t		pc_progif;	/* chip PCI programming interface */
241240992Sglebius	uint8_t		pc_revid;	/* chip revision ID */
242240992Sglebius	char		pd_name[PCI_MAXNAMELEN + 1]; /* device name */
243240992Sglebius	uint32_t	pd_unit;	/* device unit number (u_long) */
244240981Ssobomax};
245240981Ssobomax
246240981Ssobomaxstruct pci_match_conf_old32 {
247240992Sglebius	struct pcisel_old pc_sel;	/* bus+slot+function */
248240992Sglebius	char		pd_name[PCI_MAXNAMELEN + 1]; /* device name */
249240992Sglebius	uint32_t	pd_unit;	/* Unit number (u_long) */
250240992Sglebius	uint16_t	pc_vendor;	/* PCI Vendor ID */
251240992Sglebius	uint16_t	pc_device;	/* PCI Device ID */
252240992Sglebius	uint8_t		pc_class;	/* PCI class */
253240992Sglebius	pci_getconf_flags_old flags;	/* Matching expression */
254240981Ssobomax};
255240981Ssobomax
256240981Ssobomaxstruct pci_conf_io32 {
257240992Sglebius	uint32_t	pat_buf_len;	/* pattern buffer length */
258240992Sglebius	uint32_t	num_patterns;	/* number of patterns */
259240992Sglebius	uint32_t	patterns;	/* pattern buffer
260240992Sglebius					   (struct pci_match_conf_old32 *) */
261240992Sglebius	uint32_t	match_buf_len;	/* match buffer length */
262240992Sglebius	uint32_t	num_matches;	/* number of matches returned */
263240992Sglebius	uint32_t	matches;	/* match buffer
264240992Sglebius					   (struct pci_conf_old32 *) */
265240992Sglebius	uint32_t	offset;		/* offset into device list */
266240992Sglebius	uint32_t	generation;	/* device list generation */
267240992Sglebius	pci_getconf_status status;	/* request status */
268240981Ssobomax};
269240981Ssobomax
270240992Sglebius#define	PCIOCGETCONF_OLD32	_IOWR('p', 1, struct pci_conf_io32)
271240990Sglebius#endif	/* COMPAT_FREEBSD32 */
272240981Ssobomax
273172932Smarius#define	PCIOCGETCONF_OLD	_IOWR('p', 1, struct pci_conf_io)
274172932Smarius#define	PCIOCREAD_OLD		_IOWR('p', 2, struct pci_io_old)
275172932Smarius#define	PCIOCWRITE_OLD		_IOWR('p', 3, struct pci_io_old)
276172932Smarius
277172932Smariusstatic int	pci_conf_match_old(struct pci_match_conf_old *matches,
278172932Smarius		    int num_matches, struct pci_conf *match_buf);
279172932Smarius
28069953Smsmithstatic int
281172932Smariuspci_conf_match_old(struct pci_match_conf_old *matches, int num_matches,
282172932Smarius    struct pci_conf *match_buf)
283172932Smarius{
284172932Smarius	int i;
285172932Smarius
286172932Smarius	if ((matches == NULL) || (match_buf == NULL) || (num_matches <= 0))
287172932Smarius		return(1);
288172932Smarius
289172932Smarius	for (i = 0; i < num_matches; i++) {
290172932Smarius		if (match_buf->pc_sel.pc_domain != 0)
291172932Smarius			continue;
292172932Smarius
293172932Smarius		/*
294172932Smarius		 * I'm not sure why someone would do this...but...
295172932Smarius		 */
296172932Smarius		if (matches[i].flags == PCI_GETCONF_NO_MATCH_OLD)
297172932Smarius			continue;
298172932Smarius
299172932Smarius		/*
300172932Smarius		 * Look at each of the match flags.  If it's set, do the
301172932Smarius		 * comparison.  If the comparison fails, we don't have a
302172932Smarius		 * match, go on to the next item if there is one.
303172932Smarius		 */
304172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_BUS_OLD) != 0)
305172932Smarius		 && (match_buf->pc_sel.pc_bus != matches[i].pc_sel.pc_bus))
306172932Smarius			continue;
307172932Smarius
308172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_DEV_OLD) != 0)
309172932Smarius		 && (match_buf->pc_sel.pc_dev != matches[i].pc_sel.pc_dev))
310172932Smarius			continue;
311172932Smarius
312172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_FUNC_OLD) != 0)
313172932Smarius		 && (match_buf->pc_sel.pc_func != matches[i].pc_sel.pc_func))
314172932Smarius			continue;
315172932Smarius
316172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_VENDOR_OLD) != 0)
317172932Smarius		 && (match_buf->pc_vendor != matches[i].pc_vendor))
318172932Smarius			continue;
319172932Smarius
320172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_DEVICE_OLD) != 0)
321172932Smarius		 && (match_buf->pc_device != matches[i].pc_device))
322172932Smarius			continue;
323172932Smarius
324172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_CLASS_OLD) != 0)
325172932Smarius		 && (match_buf->pc_class != matches[i].pc_class))
326172932Smarius			continue;
327172932Smarius
328172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_UNIT_OLD) != 0)
329172932Smarius		 && (match_buf->pd_unit != matches[i].pd_unit))
330172932Smarius			continue;
331172932Smarius
332172932Smarius		if (((matches[i].flags & PCI_GETCONF_MATCH_NAME_OLD) != 0)
333172932Smarius		 && (strncmp(matches[i].pd_name, match_buf->pd_name,
334172932Smarius			     sizeof(match_buf->pd_name)) != 0))
335172932Smarius			continue;
336172932Smarius
337172932Smarius		return(0);
338172932Smarius	}
339172932Smarius
340172932Smarius	return(1);
341172932Smarius}
342172932Smarius
343240990Sglebius#ifdef COMPAT_FREEBSD32
344240981Ssobomaxstatic int
345240981Ssobomaxpci_conf_match_old32(struct pci_match_conf_old32 *matches, int num_matches,
346240981Ssobomax    struct pci_conf *match_buf)
347240981Ssobomax{
348240992Sglebius	int i;
349240981Ssobomax
350240992Sglebius	if ((matches == NULL) || (match_buf == NULL) || (num_matches <= 0))
351240992Sglebius		return(1);
352240981Ssobomax
353240992Sglebius	for (i = 0; i < num_matches; i++) {
354240992Sglebius		if (match_buf->pc_sel.pc_domain != 0)
355240992Sglebius			continue;
356240981Ssobomax
357240992Sglebius		/*
358240992Sglebius		 * I'm not sure why someone would do this...but...
359240992Sglebius		 */
360240992Sglebius		if (matches[i].flags == PCI_GETCONF_NO_MATCH_OLD)
361240992Sglebius			continue;
362240981Ssobomax
363240992Sglebius		/*
364240992Sglebius		 * Look at each of the match flags.  If it's set, do the
365240992Sglebius		 * comparison.  If the comparison fails, we don't have a
366240992Sglebius		 * match, go on to the next item if there is one.
367240992Sglebius		 */
368240992Sglebius		if (((matches[i].flags & PCI_GETCONF_MATCH_BUS_OLD) != 0) &&
369240992Sglebius		    (match_buf->pc_sel.pc_bus != matches[i].pc_sel.pc_bus))
370240992Sglebius			continue;
371240981Ssobomax
372240992Sglebius		if (((matches[i].flags & PCI_GETCONF_MATCH_DEV_OLD) != 0) &&
373240992Sglebius		    (match_buf->pc_sel.pc_dev != matches[i].pc_sel.pc_dev))
374240992Sglebius			continue;
375240981Ssobomax
376240992Sglebius		if (((matches[i].flags & PCI_GETCONF_MATCH_FUNC_OLD) != 0) &&
377240992Sglebius		    (match_buf->pc_sel.pc_func != matches[i].pc_sel.pc_func))
378240992Sglebius			continue;
379240981Ssobomax
380240992Sglebius		if (((matches[i].flags & PCI_GETCONF_MATCH_VENDOR_OLD) != 0) &&
381240992Sglebius		    (match_buf->pc_vendor != matches[i].pc_vendor))
382240992Sglebius			continue;
383240981Ssobomax
384240992Sglebius		if (((matches[i].flags & PCI_GETCONF_MATCH_DEVICE_OLD) != 0) &&
385240992Sglebius		    (match_buf->pc_device != matches[i].pc_device))
386240992Sglebius			continue;
387240981Ssobomax
388240992Sglebius		if (((matches[i].flags & PCI_GETCONF_MATCH_CLASS_OLD) != 0) &&
389240992Sglebius		    (match_buf->pc_class != matches[i].pc_class))
390240992Sglebius			continue;
391240981Ssobomax
392240992Sglebius		if (((matches[i].flags & PCI_GETCONF_MATCH_UNIT_OLD) != 0) &&
393240992Sglebius		    ((u_int32_t)match_buf->pd_unit != matches[i].pd_unit))
394240992Sglebius			continue;
395240981Ssobomax
396240992Sglebius		if (((matches[i].flags & PCI_GETCONF_MATCH_NAME_OLD) != 0) &&
397240992Sglebius		    (strncmp(matches[i].pd_name, match_buf->pd_name,
398240992Sglebius		    sizeof(match_buf->pd_name)) != 0))
399240992Sglebius			continue;
400240981Ssobomax
401240992Sglebius		return (0);
402240992Sglebius	}
403240981Ssobomax
404240992Sglebius	return (1);
405240981Ssobomax}
406240990Sglebius#endif	/* COMPAT_FREEBSD32 */
407240990Sglebius#endif	/* PRE7_COMPAT */
408240981Ssobomax
409172932Smariusstatic int
410260926Sjhbpci_list_vpd(device_t dev, struct pci_list_vpd_io *lvio)
411260926Sjhb{
412260926Sjhb	struct pci_vpd_element vpd_element, *vpd_user;
413260926Sjhb	struct pcicfg_vpd *vpd;
414260926Sjhb	size_t len;
415260926Sjhb	int error, i;
416260926Sjhb
417260926Sjhb	vpd = pci_fetch_vpd_list(dev);
418260926Sjhb	if (vpd->vpd_reg == 0 || vpd->vpd_ident == NULL)
419260926Sjhb		return (ENXIO);
420260926Sjhb
421260926Sjhb	/*
422260926Sjhb	 * Calculate the amount of space needed in the data buffer.  An
423260926Sjhb	 * identifier element is always present followed by the read-only
424260926Sjhb	 * and read-write keywords.
425260926Sjhb	 */
426260926Sjhb	len = sizeof(struct pci_vpd_element) + strlen(vpd->vpd_ident);
427260926Sjhb	for (i = 0; i < vpd->vpd_rocnt; i++)
428260926Sjhb		len += sizeof(struct pci_vpd_element) + vpd->vpd_ros[i].len;
429260926Sjhb	for (i = 0; i < vpd->vpd_wcnt; i++)
430260926Sjhb		len += sizeof(struct pci_vpd_element) + vpd->vpd_w[i].len;
431260926Sjhb
432260926Sjhb	if (lvio->plvi_len == 0) {
433260926Sjhb		lvio->plvi_len = len;
434260926Sjhb		return (0);
435260926Sjhb	}
436260926Sjhb	if (lvio->plvi_len < len) {
437260926Sjhb		lvio->plvi_len = len;
438260926Sjhb		return (ENOMEM);
439260926Sjhb	}
440260926Sjhb
441260926Sjhb	/*
442260926Sjhb	 * Copyout the identifier string followed by each keyword and
443260926Sjhb	 * value.
444260926Sjhb	 */
445260926Sjhb	vpd_user = lvio->plvi_data;
446260926Sjhb	vpd_element.pve_keyword[0] = '\0';
447260926Sjhb	vpd_element.pve_keyword[1] = '\0';
448260926Sjhb	vpd_element.pve_flags = PVE_FLAG_IDENT;
449260926Sjhb	vpd_element.pve_datalen = strlen(vpd->vpd_ident);
450260926Sjhb	error = copyout(&vpd_element, vpd_user, sizeof(vpd_element));
451260926Sjhb	if (error)
452260926Sjhb		return (error);
453260926Sjhb	error = copyout(vpd->vpd_ident, vpd_user->pve_data,
454260926Sjhb	    strlen(vpd->vpd_ident));
455260926Sjhb	if (error)
456260926Sjhb		return (error);
457260926Sjhb	vpd_user = PVE_NEXT(vpd_user);
458260926Sjhb	vpd_element.pve_flags = 0;
459260926Sjhb	for (i = 0; i < vpd->vpd_rocnt; i++) {
460260926Sjhb		vpd_element.pve_keyword[0] = vpd->vpd_ros[i].keyword[0];
461260926Sjhb		vpd_element.pve_keyword[1] = vpd->vpd_ros[i].keyword[1];
462260926Sjhb		vpd_element.pve_datalen = vpd->vpd_ros[i].len;
463260926Sjhb		error = copyout(&vpd_element, vpd_user, sizeof(vpd_element));
464260926Sjhb		if (error)
465260926Sjhb			return (error);
466260926Sjhb		error = copyout(vpd->vpd_ros[i].value, vpd_user->pve_data,
467260926Sjhb		    vpd->vpd_ros[i].len);
468260926Sjhb		if (error)
469260926Sjhb			return (error);
470260926Sjhb		vpd_user = PVE_NEXT(vpd_user);
471260926Sjhb	}
472260926Sjhb	vpd_element.pve_flags = PVE_FLAG_RW;
473260926Sjhb	for (i = 0; i < vpd->vpd_wcnt; i++) {
474260926Sjhb		vpd_element.pve_keyword[0] = vpd->vpd_w[i].keyword[0];
475260926Sjhb		vpd_element.pve_keyword[1] = vpd->vpd_w[i].keyword[1];
476260926Sjhb		vpd_element.pve_datalen = vpd->vpd_w[i].len;
477260926Sjhb		error = copyout(&vpd_element, vpd_user, sizeof(vpd_element));
478260926Sjhb		if (error)
479260926Sjhb			return (error);
480260926Sjhb		error = copyout(vpd->vpd_w[i].value, vpd_user->pve_data,
481260926Sjhb		    vpd->vpd_w[i].len);
482260926Sjhb		if (error)
483260926Sjhb			return (error);
484260926Sjhb		vpd_user = PVE_NEXT(vpd_user);
485260926Sjhb	}
486260926Sjhb	KASSERT((char *)vpd_user - (char *)lvio->plvi_data == len,
487260926Sjhb	    ("length mismatch"));
488260926Sjhb	lvio->plvi_len = len;
489260926Sjhb	return (0);
490260926Sjhb}
491260926Sjhb
492260926Sjhbstatic int
493130585Sphkpci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
49469953Smsmith{
495279448Srstone	device_t pcidev;
496172932Smarius	void *confdata;
497172932Smarius	const char *name;
498172932Smarius	struct devlist *devlist_head;
499240990Sglebius	struct pci_conf_io *cio = NULL;
500172932Smarius	struct pci_devinfo *dinfo;
50169953Smsmith	struct pci_io *io;
502188018Sjhb	struct pci_bar_io *bio;
503260926Sjhb	struct pci_list_vpd_io *lvio;
504172932Smarius	struct pci_match_conf *pattern_buf;
505220195Sjhb	struct pci_map *pm;
506172932Smarius	size_t confsz, iolen, pbufsz;
507172932Smarius	int error, ionum, i, num_patterns;
508172999Simp#ifdef PRE7_COMPAT
509240981Ssobomax#ifdef COMPAT_FREEBSD32
510240990Sglebius	struct pci_conf_io32 *cio32 = NULL;
511240990Sglebius	struct pci_conf_old32 conf_old32;
512244695Sdavidxu	struct pci_match_conf_old32 *pattern_buf_old32 = NULL;
513240981Ssobomax#endif
514172932Smarius	struct pci_conf_old conf_old;
515172932Smarius	struct pci_io iodata;
516172932Smarius	struct pci_io_old *io_old;
517244695Sdavidxu	struct pci_match_conf_old *pattern_buf_old = NULL;
51869953Smsmith
519172932Smarius	io_old = NULL;
520260926Sjhb#endif
521172932Smarius
522260926Sjhb	if (!(flag & FWRITE)) {
523260926Sjhb		switch (cmd) {
524260926Sjhb#ifdef PRE7_COMPAT
525260926Sjhb#ifdef COMPAT_FREEBSD32
526260926Sjhb		case PCIOCGETCONF_OLD32:
527172932Smarius#endif
528260926Sjhb		case PCIOCGETCONF_OLD:
529260926Sjhb#endif
530260926Sjhb		case PCIOCGETCONF:
531260926Sjhb		case PCIOCGETBAR:
532260926Sjhb		case PCIOCLISTVPD:
533260926Sjhb			break;
534260926Sjhb		default:
535260926Sjhb			return (EPERM);
536260926Sjhb		}
537260926Sjhb	}
53869953Smsmith
539260926Sjhb	switch (cmd) {
540172999Simp#ifdef PRE7_COMPAT
541240990Sglebius#ifdef COMPAT_FREEBSD32
542260926Sjhb	case PCIOCGETCONF_OLD32:
543240981Ssobomax               cio32 = (struct pci_conf_io32 *)data;
544240981Ssobomax               cio = malloc(sizeof(struct pci_conf_io), M_TEMP, M_WAITOK);
545240981Ssobomax               cio->pat_buf_len = cio32->pat_buf_len;
546240981Ssobomax               cio->num_patterns = cio32->num_patterns;
547240981Ssobomax               cio->patterns = (void *)(uintptr_t)cio32->patterns;
548240981Ssobomax               cio->match_buf_len = cio32->match_buf_len;
549240981Ssobomax               cio->num_matches = cio32->num_matches;
550240981Ssobomax               cio->matches = (void *)(uintptr_t)cio32->matches;
551240981Ssobomax               cio->offset = cio32->offset;
552240981Ssobomax               cio->generation = cio32->generation;
553240981Ssobomax               cio->status = cio32->status;
554240981Ssobomax               cio32->num_matches = 0;
555240990Sglebius               break;
556240990Sglebius#endif
557240990Sglebius	case PCIOCGETCONF_OLD:
558240990Sglebius#endif
559240990Sglebius	case PCIOCGETCONF:
560240990Sglebius		cio = (struct pci_conf_io *)data;
561240990Sglebius	}
562240981Ssobomax
563260926Sjhb	switch (cmd) {
564240990Sglebius#ifdef PRE7_COMPAT
565240990Sglebius#ifdef COMPAT_FREEBSD32
566240990Sglebius	case PCIOCGETCONF_OLD32:
567240990Sglebius#endif
568172932Smarius	case PCIOCGETCONF_OLD:
569172932Smarius#endif
57069953Smsmith	case PCIOCGETCONF:
57169953Smsmith
572172932Smarius		pattern_buf = NULL;
57369953Smsmith		num_patterns = 0;
57469953Smsmith		dinfo = NULL;
57569953Smsmith
576172932Smarius		cio->num_matches = 0;
577172932Smarius
57869953Smsmith		/*
57969953Smsmith		 * If the user specified an offset into the device list,
58069953Smsmith		 * but the list has changed since they last called this
58169953Smsmith		 * ioctl, tell them that the list has changed.  They will
58269953Smsmith		 * have to get the list from the beginning.
58369953Smsmith		 */
58469953Smsmith		if ((cio->offset != 0)
58569953Smsmith		 && (cio->generation != pci_generation)){
58669953Smsmith			cio->status = PCI_GETCONF_LIST_CHANGED;
58769953Smsmith			error = 0;
588240992Sglebius			goto getconfexit;
58969953Smsmith		}
59069953Smsmith
59169953Smsmith		/*
59269953Smsmith		 * Check to see whether the user has asked for an offset
59369953Smsmith		 * past the end of our list.
59469953Smsmith		 */
59569953Smsmith		if (cio->offset >= pci_numdevs) {
59669953Smsmith			cio->status = PCI_GETCONF_LAST_DEVICE;
59769953Smsmith			error = 0;
598240992Sglebius			goto getconfexit;
59969953Smsmith		}
60069953Smsmith
60169953Smsmith		/* get the head of the device queue */
60269953Smsmith		devlist_head = &pci_devq;
60369953Smsmith
60469953Smsmith		/*
60569953Smsmith		 * Determine how much room we have for pci_conf structures.
60669953Smsmith		 * Round the user's buffer size down to the nearest
60769953Smsmith		 * multiple of sizeof(struct pci_conf) in case the user
60869953Smsmith		 * didn't specify a multiple of that size.
60969953Smsmith		 */
610172999Simp#ifdef PRE7_COMPAT
611240981Ssobomax#ifdef COMPAT_FREEBSD32
612240992Sglebius		if (cmd == PCIOCGETCONF_OLD32)
613240992Sglebius			confsz = sizeof(struct pci_conf_old32);
614240992Sglebius		else
615240981Ssobomax#endif
616172932Smarius		if (cmd == PCIOCGETCONF_OLD)
617172932Smarius			confsz = sizeof(struct pci_conf_old);
618172932Smarius		else
619172932Smarius#endif
620172932Smarius			confsz = sizeof(struct pci_conf);
621172932Smarius		iolen = min(cio->match_buf_len - (cio->match_buf_len % confsz),
622172932Smarius		    pci_numdevs * confsz);
62369953Smsmith
62469953Smsmith		/*
62569953Smsmith		 * Since we know that iolen is a multiple of the size of
62669953Smsmith		 * the pciconf union, it's okay to do this.
62769953Smsmith		 */
628172932Smarius		ionum = iolen / confsz;
62969953Smsmith
63069953Smsmith		/*
63169953Smsmith		 * If this test is true, the user wants the pci_conf
63269953Smsmith		 * structures returned to match the supplied entries.
63369953Smsmith		 */
634116704Sjmg		if ((cio->num_patterns > 0) && (cio->num_patterns < pci_numdevs)
63569953Smsmith		 && (cio->pat_buf_len > 0)) {
63669953Smsmith			/*
63769953Smsmith			 * pat_buf_len needs to be:
63869953Smsmith			 * num_patterns * sizeof(struct pci_match_conf)
63969953Smsmith			 * While it is certainly possible the user just
64069953Smsmith			 * allocated a large buffer, but set the number of
64169953Smsmith			 * matches correctly, it is far more likely that
64269953Smsmith			 * their kernel doesn't match the userland utility
64369953Smsmith			 * they're using.  It's also possible that the user
64469953Smsmith			 * forgot to initialize some variables.  Yes, this
64569953Smsmith			 * may be overly picky, but I hazard to guess that
64669953Smsmith			 * it's far more likely to just catch folks that
64769953Smsmith			 * updated their kernel but not their userland.
64869953Smsmith			 */
649172999Simp#ifdef PRE7_COMPAT
650240981Ssobomax#ifdef COMPAT_FREEBSD32
651240992Sglebius			if (cmd == PCIOCGETCONF_OLD32)
652240992Sglebius				pbufsz = sizeof(struct pci_match_conf_old32);
653240992Sglebius			else
654240981Ssobomax#endif
655172932Smarius			if (cmd == PCIOCGETCONF_OLD)
656172932Smarius				pbufsz = sizeof(struct pci_match_conf_old);
657172932Smarius			else
658172932Smarius#endif
659172932Smarius				pbufsz = sizeof(struct pci_match_conf);
660172932Smarius			if (cio->num_patterns * pbufsz != cio->pat_buf_len) {
661172932Smarius				/* The user made a mistake, return an error. */
66269953Smsmith				cio->status = PCI_GETCONF_ERROR;
66369953Smsmith				error = EINVAL;
664240992Sglebius				goto getconfexit;
66569953Smsmith			}
66669953Smsmith
66769953Smsmith			/*
66869953Smsmith			 * Allocate a buffer to hold the patterns.
66969953Smsmith			 */
670172999Simp#ifdef PRE7_COMPAT
671240990Sglebius#ifdef COMPAT_FREEBSD32
672240992Sglebius			if (cmd == PCIOCGETCONF_OLD32) {
673240992Sglebius				pattern_buf_old32 = malloc(cio->pat_buf_len,
674240992Sglebius				    M_TEMP, M_WAITOK);
675240992Sglebius				error = copyin(cio->patterns,
676240992Sglebius				    pattern_buf_old32, cio->pat_buf_len);
677240992Sglebius			} else
678240990Sglebius#endif /* COMPAT_FREEBSD32 */
679172932Smarius			if (cmd == PCIOCGETCONF_OLD) {
680172932Smarius				pattern_buf_old = malloc(cio->pat_buf_len,
681172932Smarius				    M_TEMP, M_WAITOK);
682172932Smarius				error = copyin(cio->patterns,
683172932Smarius				    pattern_buf_old, cio->pat_buf_len);
684172932Smarius			} else
685240990Sglebius#endif /* PRE7_COMPAT */
686172932Smarius			{
687172932Smarius				pattern_buf = malloc(cio->pat_buf_len, M_TEMP,
688172932Smarius				    M_WAITOK);
689172932Smarius				error = copyin(cio->patterns, pattern_buf,
690172932Smarius				    cio->pat_buf_len);
691172932Smarius			}
692116702Sjmg			if (error != 0) {
693116702Sjmg				error = EINVAL;
694116702Sjmg				goto getconfexit;
695116702Sjmg			}
69669953Smsmith			num_patterns = cio->num_patterns;
69769953Smsmith		} else if ((cio->num_patterns > 0)
69869953Smsmith			|| (cio->pat_buf_len > 0)) {
69969953Smsmith			/*
70069953Smsmith			 * The user made a mistake, spit out an error.
70169953Smsmith			 */
70269953Smsmith			cio->status = PCI_GETCONF_ERROR;
70369953Smsmith			error = EINVAL;
704240981Ssobomax                       goto getconfexit;
705172932Smarius		}
70669953Smsmith
70769953Smsmith		/*
70869953Smsmith		 * Go through the list of devices and copy out the devices
70969953Smsmith		 * that match the user's criteria.
71069953Smsmith		 */
71169953Smsmith		for (cio->num_matches = 0, error = 0, i = 0,
712295813Sse				 dinfo = STAILQ_FIRST(devlist_head);
713295813Sse		     (dinfo != NULL) && (cio->num_matches < ionum) &&
714295813Sse				 (error == 0) && (i < pci_numdevs));
71569953Smsmith		     dinfo = STAILQ_NEXT(dinfo, pci_links), i++) {
71669953Smsmith
71769953Smsmith			if (i < cio->offset)
71869953Smsmith				continue;
71969953Smsmith
72069953Smsmith			/* Populate pd_name and pd_unit */
72169953Smsmith			name = NULL;
722175368Sjhb			if (dinfo->cfg.dev)
72369953Smsmith				name = device_get_name(dinfo->cfg.dev);
72469953Smsmith			if (name) {
72569953Smsmith				strncpy(dinfo->conf.pd_name, name,
72669953Smsmith					sizeof(dinfo->conf.pd_name));
72769953Smsmith				dinfo->conf.pd_name[PCI_MAXNAMELEN] = 0;
72869953Smsmith				dinfo->conf.pd_unit =
72969953Smsmith					device_get_unit(dinfo->cfg.dev);
730175368Sjhb			} else {
731175368Sjhb				dinfo->conf.pd_name[0] = '\0';
732175368Sjhb				dinfo->conf.pd_unit = 0;
73369953Smsmith			}
73469953Smsmith
735172999Simp#ifdef PRE7_COMPAT
736240990Sglebius			if (
737240990Sglebius#ifdef COMPAT_FREEBSD32
738240990Sglebius			    (cmd == PCIOCGETCONF_OLD32 &&
739240990Sglebius			    (pattern_buf_old32 == NULL ||
740240990Sglebius			    pci_conf_match_old32(pattern_buf_old32,
741240990Sglebius			    num_patterns, &dinfo->conf) == 0)) ||
742240990Sglebius#endif
743240990Sglebius			    (cmd == PCIOCGETCONF_OLD &&
744172932Smarius			    (pattern_buf_old == NULL ||
745172932Smarius			    pci_conf_match_old(pattern_buf_old, num_patterns,
746172932Smarius			    &dinfo->conf) == 0)) ||
747172932Smarius			    (cmd == PCIOCGETCONF &&
748172932Smarius			    (pattern_buf == NULL ||
749172932Smarius			    pci_conf_match(pattern_buf, num_patterns,
750172932Smarius			    &dinfo->conf) == 0))) {
751172932Smarius#else
752172932Smarius			if (pattern_buf == NULL ||
753172932Smarius			    pci_conf_match(pattern_buf, num_patterns,
754172932Smarius			    &dinfo->conf) == 0) {
755172932Smarius#endif
75669953Smsmith				/*
75769953Smsmith				 * If we've filled up the user's buffer,
75869953Smsmith				 * break out at this point.  Since we've
75969953Smsmith				 * got a match here, we'll pick right back
76069953Smsmith				 * up at the matching entry.  We can also
76169953Smsmith				 * tell the user that there are more matches
76269953Smsmith				 * left.
76369953Smsmith				 */
76469953Smsmith				if (cio->num_matches >= ionum)
76569953Smsmith					break;
76669953Smsmith
767172999Simp#ifdef PRE7_COMPAT
768240990Sglebius#ifdef COMPAT_FREEBSD32
769240992Sglebius				if (cmd == PCIOCGETCONF_OLD32) {
770240992Sglebius					conf_old32.pc_sel.pc_bus =
771240992Sglebius					    dinfo->conf.pc_sel.pc_bus;
772240992Sglebius					conf_old32.pc_sel.pc_dev =
773240992Sglebius					    dinfo->conf.pc_sel.pc_dev;
774240992Sglebius					conf_old32.pc_sel.pc_func =
775240992Sglebius					    dinfo->conf.pc_sel.pc_func;
776240992Sglebius					conf_old32.pc_hdr = dinfo->conf.pc_hdr;
777240992Sglebius					conf_old32.pc_subvendor =
778240992Sglebius					    dinfo->conf.pc_subvendor;
779240992Sglebius					conf_old32.pc_subdevice =
780240992Sglebius					    dinfo->conf.pc_subdevice;
781240992Sglebius					conf_old32.pc_vendor =
782240992Sglebius					    dinfo->conf.pc_vendor;
783240992Sglebius					conf_old32.pc_device =
784240992Sglebius					    dinfo->conf.pc_device;
785240992Sglebius					conf_old32.pc_class =
786240992Sglebius					    dinfo->conf.pc_class;
787240992Sglebius					conf_old32.pc_subclass =
788240992Sglebius					    dinfo->conf.pc_subclass;
789240992Sglebius					conf_old32.pc_progif =
790240992Sglebius					    dinfo->conf.pc_progif;
791240992Sglebius					conf_old32.pc_revid =
792240992Sglebius					    dinfo->conf.pc_revid;
793240992Sglebius					strncpy(conf_old32.pd_name,
794240992Sglebius					    dinfo->conf.pd_name,
795240992Sglebius					    sizeof(conf_old32.pd_name));
796240992Sglebius					conf_old32.pd_name[PCI_MAXNAMELEN] = 0;
797240992Sglebius					conf_old32.pd_unit =
798240992Sglebius					    (uint32_t)dinfo->conf.pd_unit;
799240992Sglebius					confdata = &conf_old32;
800240992Sglebius				} else
801240990Sglebius#endif /* COMPAT_FREEBSD32 */
802172932Smarius				if (cmd == PCIOCGETCONF_OLD) {
803172932Smarius					conf_old.pc_sel.pc_bus =
804172932Smarius					    dinfo->conf.pc_sel.pc_bus;
805172932Smarius					conf_old.pc_sel.pc_dev =
806172932Smarius					    dinfo->conf.pc_sel.pc_dev;
807172932Smarius					conf_old.pc_sel.pc_func =
808172932Smarius					    dinfo->conf.pc_sel.pc_func;
809172932Smarius					conf_old.pc_hdr = dinfo->conf.pc_hdr;
810172932Smarius					conf_old.pc_subvendor =
811172932Smarius					    dinfo->conf.pc_subvendor;
812172932Smarius					conf_old.pc_subdevice =
813172932Smarius					    dinfo->conf.pc_subdevice;
814172932Smarius					conf_old.pc_vendor =
815172932Smarius					    dinfo->conf.pc_vendor;
816172932Smarius					conf_old.pc_device =
817172932Smarius					    dinfo->conf.pc_device;
818172932Smarius					conf_old.pc_class =
819172932Smarius					    dinfo->conf.pc_class;
820172932Smarius					conf_old.pc_subclass =
821172932Smarius					    dinfo->conf.pc_subclass;
822172932Smarius					conf_old.pc_progif =
823172932Smarius					    dinfo->conf.pc_progif;
824172932Smarius					conf_old.pc_revid =
825172932Smarius					    dinfo->conf.pc_revid;
826174932Smarius					strncpy(conf_old.pd_name,
827174932Smarius					    dinfo->conf.pd_name,
828174932Smarius					    sizeof(conf_old.pd_name));
829174932Smarius					conf_old.pd_name[PCI_MAXNAMELEN] = 0;
830174932Smarius					conf_old.pd_unit =
831174932Smarius					    dinfo->conf.pd_unit;
832172932Smarius					confdata = &conf_old;
833172932Smarius				} else
834240990Sglebius#endif /* PRE7_COMPAT */
835172932Smarius					confdata = &dinfo->conf;
836172932Smarius				/* Only if we can copy it out do we count it. */
837172932Smarius				if (!(error = copyout(confdata,
838174932Smarius				    (caddr_t)cio->matches +
839174932Smarius				    confsz * cio->num_matches, confsz)))
840116702Sjmg					cio->num_matches++;
84169953Smsmith			}
84269953Smsmith		}
84369953Smsmith
84469953Smsmith		/*
84569953Smsmith		 * Set the pointer into the list, so if the user is getting
84669953Smsmith		 * n records at a time, where n < pci_numdevs,
84769953Smsmith		 */
84869953Smsmith		cio->offset = i;
84969953Smsmith
85069953Smsmith		/*
85169953Smsmith		 * Set the generation, the user will need this if they make
85269953Smsmith		 * another ioctl call with offset != 0.
85369953Smsmith		 */
85469953Smsmith		cio->generation = pci_generation;
855172932Smarius
85669953Smsmith		/*
85769953Smsmith		 * If this is the last device, inform the user so he won't
85869953Smsmith		 * bother asking for more devices.  If dinfo isn't NULL, we
85969953Smsmith		 * know that there are more matches in the list because of
86069953Smsmith		 * the way the traversal is done.
86169953Smsmith		 */
86269953Smsmith		if (dinfo == NULL)
86369953Smsmith			cio->status = PCI_GETCONF_LAST_DEVICE;
86469953Smsmith		else
86569953Smsmith			cio->status = PCI_GETCONF_MORE_DEVS;
86669953Smsmith
867116702Sjmggetconfexit:
868240990Sglebius#ifdef PRE7_COMPAT
869240981Ssobomax#ifdef COMPAT_FREEBSD32
870240992Sglebius		if (cmd == PCIOCGETCONF_OLD32) {
871240992Sglebius			cio32->status = cio->status;
872240992Sglebius			cio32->generation = cio->generation;
873240992Sglebius			cio32->offset = cio->offset;
874240992Sglebius			cio32->num_matches = cio->num_matches;
875240990Sglebius			free(cio, M_TEMP);
876240990Sglebius		}
877240990Sglebius		if (pattern_buf_old32 != NULL)
878240990Sglebius			free(pattern_buf_old32, M_TEMP);
879240981Ssobomax#endif
880172932Smarius		if (pattern_buf_old != NULL)
881172932Smarius			free(pattern_buf_old, M_TEMP);
882172932Smarius#endif
883240990Sglebius		if (pattern_buf != NULL)
884240990Sglebius			free(pattern_buf, M_TEMP);
88569953Smsmith
88669953Smsmith		break;
887121013Sse
888172999Simp#ifdef PRE7_COMPAT
889172932Smarius	case PCIOCREAD_OLD:
890172932Smarius	case PCIOCWRITE_OLD:
891172932Smarius		io_old = (struct pci_io_old *)data;
892172932Smarius		iodata.pi_sel.pc_domain = 0;
893172932Smarius		iodata.pi_sel.pc_bus = io_old->pi_sel.pc_bus;
894172932Smarius		iodata.pi_sel.pc_dev = io_old->pi_sel.pc_dev;
895172932Smarius		iodata.pi_sel.pc_func = io_old->pi_sel.pc_func;
896172932Smarius		iodata.pi_reg = io_old->pi_reg;
897172932Smarius		iodata.pi_width = io_old->pi_width;
898172932Smarius		iodata.pi_data = io_old->pi_data;
899172932Smarius		data = (caddr_t)&iodata;
900172932Smarius		/* FALLTHROUGH */
901172932Smarius#endif
90269953Smsmith	case PCIOCREAD:
903121013Sse	case PCIOCWRITE:
90469953Smsmith		io = (struct pci_io *)data;
90569953Smsmith		switch(io->pi_width) {
90669953Smsmith		case 4:
90769953Smsmith		case 2:
90869953Smsmith		case 1:
909197099Savg			/* Make sure register is not negative and aligned. */
910172932Smarius			if (io->pi_reg < 0 ||
911172932Smarius			    io->pi_reg & (io->pi_width - 1)) {
912163055Sru				error = EINVAL;
913163055Sru				break;
914163055Sru			}
91569953Smsmith			/*
91669953Smsmith			 * Assume that the user-level bus number is
917145022Sbms			 * in fact the physical PCI bus number.
918145022Sbms			 * Look up the grandparent, i.e. the bridge device,
919145022Sbms			 * so that we can issue configuration space cycles.
92069953Smsmith			 */
921172394Smarius			pcidev = pci_find_dbsf(io->pi_sel.pc_domain,
922172394Smarius			    io->pi_sel.pc_bus, io->pi_sel.pc_dev,
923172394Smarius			    io->pi_sel.pc_func);
924145022Sbms			if (pcidev) {
925172999Simp#ifdef PRE7_COMPAT
926172932Smarius				if (cmd == PCIOCWRITE || cmd == PCIOCWRITE_OLD)
927172932Smarius#else
928121013Sse				if (cmd == PCIOCWRITE)
929172932Smarius#endif
930279448Srstone					pci_write_config(pcidev,
931121013Sse							  io->pi_reg,
932121013Sse							  io->pi_data,
933121013Sse							  io->pi_width);
934172999Simp#ifdef PRE7_COMPAT
935172932Smarius				else if (cmd == PCIOCREAD_OLD)
936172932Smarius					io_old->pi_data =
937279448Srstone						pci_read_config(pcidev,
938172932Smarius							  io->pi_reg,
939172932Smarius							  io->pi_width);
940172932Smarius#endif
941121013Sse				else
942121013Sse					io->pi_data =
943279448Srstone						pci_read_config(pcidev,
944121013Sse							  io->pi_reg,
945121013Sse							  io->pi_width);
94669953Smsmith				error = 0;
94769953Smsmith			} else {
948149478Sps#ifdef COMPAT_FREEBSD4
949172932Smarius				if (cmd == PCIOCREAD_OLD) {
950172932Smarius					io_old->pi_data = -1;
951149478Sps					error = 0;
952149478Sps				} else
953149478Sps#endif
954149478Sps					error = ENODEV;
95569953Smsmith			}
95669953Smsmith			break;
95769953Smsmith		default:
958116702Sjmg			error = EINVAL;
95969953Smsmith			break;
96069953Smsmith		}
96169953Smsmith		break;
96269953Smsmith
963188018Sjhb	case PCIOCGETBAR:
964188018Sjhb		bio = (struct pci_bar_io *)data;
965188018Sjhb
966188018Sjhb		/*
967188018Sjhb		 * Assume that the user-level bus number is
968188018Sjhb		 * in fact the physical PCI bus number.
969188018Sjhb		 */
970188018Sjhb		pcidev = pci_find_dbsf(bio->pbi_sel.pc_domain,
971188018Sjhb		    bio->pbi_sel.pc_bus, bio->pbi_sel.pc_dev,
972188018Sjhb		    bio->pbi_sel.pc_func);
973188018Sjhb		if (pcidev == NULL) {
974188018Sjhb			error = ENODEV;
975188018Sjhb			break;
976188018Sjhb		}
977220195Sjhb		pm = pci_find_bar(pcidev, bio->pbi_reg);
978220195Sjhb		if (pm == NULL) {
979188018Sjhb			error = EINVAL;
980188018Sjhb			break;
981188018Sjhb		}
982220195Sjhb		bio->pbi_base = pm->pm_value;
983220195Sjhb		bio->pbi_length = (pci_addr_t)1 << pm->pm_size;
984220195Sjhb		bio->pbi_enabled = pci_bar_enabled(pcidev, pm);
985188018Sjhb		error = 0;
986188018Sjhb		break;
987210597Sneel	case PCIOCATTACHED:
988210597Sneel		error = 0;
989210597Sneel		io = (struct pci_io *)data;
990210597Sneel		pcidev = pci_find_dbsf(io->pi_sel.pc_domain, io->pi_sel.pc_bus,
991210597Sneel				       io->pi_sel.pc_dev, io->pi_sel.pc_func);
992210597Sneel		if (pcidev != NULL)
993210597Sneel			io->pi_data = device_is_attached(pcidev);
994210597Sneel		else
995210597Sneel			error = ENODEV;
996210597Sneel		break;
997260926Sjhb	case PCIOCLISTVPD:
998260926Sjhb		lvio = (struct pci_list_vpd_io *)data;
999260926Sjhb
1000260926Sjhb		/*
1001260926Sjhb		 * Assume that the user-level bus number is
1002260926Sjhb		 * in fact the physical PCI bus number.
1003260926Sjhb		 */
1004260926Sjhb		pcidev = pci_find_dbsf(lvio->plvi_sel.pc_domain,
1005260926Sjhb		    lvio->plvi_sel.pc_bus, lvio->plvi_sel.pc_dev,
1006260926Sjhb		    lvio->plvi_sel.pc_func);
1007260926Sjhb		if (pcidev == NULL) {
1008260926Sjhb			error = ENODEV;
1009260926Sjhb			break;
1010260926Sjhb		}
1011260926Sjhb		error = pci_list_vpd(pcidev, lvio);
1012260926Sjhb		break;
101369953Smsmith	default:
101469953Smsmith		error = ENOTTY;
101569953Smsmith		break;
101669953Smsmith	}
101769953Smsmith
101869953Smsmith	return (error);
101969953Smsmith}
1020