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: stable/11/sys/dev/pci/pci_user.c 339932 2018-10-30 21:31:32Z 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
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
409339932Sjhb/*
410339932Sjhb * Like PVE_NEXT but takes an explicit length since 'pve' is a user
411339932Sjhb * pointer that cannot be dereferenced.
412339932Sjhb */
413339932Sjhb#define	PVE_NEXT_LEN(pve, datalen)					\
414339932Sjhb	((struct pci_vpd_element *)((char *)(pve) +			\
415339932Sjhb	    sizeof(struct pci_vpd_element) + (datalen)))
416339932Sjhb
417172932Smariusstatic int
418260926Sjhbpci_list_vpd(device_t dev, struct pci_list_vpd_io *lvio)
419260926Sjhb{
420260926Sjhb	struct pci_vpd_element vpd_element, *vpd_user;
421260926Sjhb	struct pcicfg_vpd *vpd;
422260926Sjhb	size_t len;
423260926Sjhb	int error, i;
424260926Sjhb
425260926Sjhb	vpd = pci_fetch_vpd_list(dev);
426260926Sjhb	if (vpd->vpd_reg == 0 || vpd->vpd_ident == NULL)
427260926Sjhb		return (ENXIO);
428260926Sjhb
429260926Sjhb	/*
430260926Sjhb	 * Calculate the amount of space needed in the data buffer.  An
431260926Sjhb	 * identifier element is always present followed by the read-only
432260926Sjhb	 * and read-write keywords.
433260926Sjhb	 */
434260926Sjhb	len = sizeof(struct pci_vpd_element) + strlen(vpd->vpd_ident);
435260926Sjhb	for (i = 0; i < vpd->vpd_rocnt; i++)
436260926Sjhb		len += sizeof(struct pci_vpd_element) + vpd->vpd_ros[i].len;
437260926Sjhb	for (i = 0; i < vpd->vpd_wcnt; i++)
438260926Sjhb		len += sizeof(struct pci_vpd_element) + vpd->vpd_w[i].len;
439260926Sjhb
440260926Sjhb	if (lvio->plvi_len == 0) {
441260926Sjhb		lvio->plvi_len = len;
442260926Sjhb		return (0);
443260926Sjhb	}
444260926Sjhb	if (lvio->plvi_len < len) {
445260926Sjhb		lvio->plvi_len = len;
446260926Sjhb		return (ENOMEM);
447260926Sjhb	}
448260926Sjhb
449260926Sjhb	/*
450260926Sjhb	 * Copyout the identifier string followed by each keyword and
451260926Sjhb	 * value.
452260926Sjhb	 */
453260926Sjhb	vpd_user = lvio->plvi_data;
454260926Sjhb	vpd_element.pve_keyword[0] = '\0';
455260926Sjhb	vpd_element.pve_keyword[1] = '\0';
456260926Sjhb	vpd_element.pve_flags = PVE_FLAG_IDENT;
457260926Sjhb	vpd_element.pve_datalen = strlen(vpd->vpd_ident);
458260926Sjhb	error = copyout(&vpd_element, vpd_user, sizeof(vpd_element));
459260926Sjhb	if (error)
460260926Sjhb		return (error);
461260926Sjhb	error = copyout(vpd->vpd_ident, vpd_user->pve_data,
462260926Sjhb	    strlen(vpd->vpd_ident));
463260926Sjhb	if (error)
464260926Sjhb		return (error);
465339932Sjhb	vpd_user = PVE_NEXT_LEN(vpd_user, vpd_element.pve_datalen);
466260926Sjhb	vpd_element.pve_flags = 0;
467260926Sjhb	for (i = 0; i < vpd->vpd_rocnt; i++) {
468260926Sjhb		vpd_element.pve_keyword[0] = vpd->vpd_ros[i].keyword[0];
469260926Sjhb		vpd_element.pve_keyword[1] = vpd->vpd_ros[i].keyword[1];
470260926Sjhb		vpd_element.pve_datalen = vpd->vpd_ros[i].len;
471260926Sjhb		error = copyout(&vpd_element, vpd_user, sizeof(vpd_element));
472260926Sjhb		if (error)
473260926Sjhb			return (error);
474260926Sjhb		error = copyout(vpd->vpd_ros[i].value, vpd_user->pve_data,
475260926Sjhb		    vpd->vpd_ros[i].len);
476260926Sjhb		if (error)
477260926Sjhb			return (error);
478339932Sjhb		vpd_user = PVE_NEXT_LEN(vpd_user, vpd_element.pve_datalen);
479260926Sjhb	}
480260926Sjhb	vpd_element.pve_flags = PVE_FLAG_RW;
481260926Sjhb	for (i = 0; i < vpd->vpd_wcnt; i++) {
482260926Sjhb		vpd_element.pve_keyword[0] = vpd->vpd_w[i].keyword[0];
483260926Sjhb		vpd_element.pve_keyword[1] = vpd->vpd_w[i].keyword[1];
484260926Sjhb		vpd_element.pve_datalen = vpd->vpd_w[i].len;
485260926Sjhb		error = copyout(&vpd_element, vpd_user, sizeof(vpd_element));
486260926Sjhb		if (error)
487260926Sjhb			return (error);
488260926Sjhb		error = copyout(vpd->vpd_w[i].value, vpd_user->pve_data,
489260926Sjhb		    vpd->vpd_w[i].len);
490260926Sjhb		if (error)
491260926Sjhb			return (error);
492339932Sjhb		vpd_user = PVE_NEXT_LEN(vpd_user, vpd_element.pve_datalen);
493260926Sjhb	}
494260926Sjhb	KASSERT((char *)vpd_user - (char *)lvio->plvi_data == len,
495260926Sjhb	    ("length mismatch"));
496260926Sjhb	lvio->plvi_len = len;
497260926Sjhb	return (0);
498260926Sjhb}
499260926Sjhb
500260926Sjhbstatic int
501130585Sphkpci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
50269953Smsmith{
503279448Srstone	device_t pcidev;
504172932Smarius	void *confdata;
505172932Smarius	const char *name;
506172932Smarius	struct devlist *devlist_head;
507240990Sglebius	struct pci_conf_io *cio = NULL;
508172932Smarius	struct pci_devinfo *dinfo;
50969953Smsmith	struct pci_io *io;
510188018Sjhb	struct pci_bar_io *bio;
511260926Sjhb	struct pci_list_vpd_io *lvio;
512172932Smarius	struct pci_match_conf *pattern_buf;
513220195Sjhb	struct pci_map *pm;
514172932Smarius	size_t confsz, iolen, pbufsz;
515172932Smarius	int error, ionum, i, num_patterns;
516172999Simp#ifdef PRE7_COMPAT
517240981Ssobomax#ifdef COMPAT_FREEBSD32
518240990Sglebius	struct pci_conf_io32 *cio32 = NULL;
519240990Sglebius	struct pci_conf_old32 conf_old32;
520244695Sdavidxu	struct pci_match_conf_old32 *pattern_buf_old32 = NULL;
521240981Ssobomax#endif
522172932Smarius	struct pci_conf_old conf_old;
523172932Smarius	struct pci_io iodata;
524172932Smarius	struct pci_io_old *io_old;
525244695Sdavidxu	struct pci_match_conf_old *pattern_buf_old = NULL;
52669953Smsmith
527172932Smarius	io_old = NULL;
528260926Sjhb#endif
529172932Smarius
530260926Sjhb	if (!(flag & FWRITE)) {
531260926Sjhb		switch (cmd) {
532260926Sjhb#ifdef PRE7_COMPAT
533260926Sjhb#ifdef COMPAT_FREEBSD32
534260926Sjhb		case PCIOCGETCONF_OLD32:
535172932Smarius#endif
536260926Sjhb		case PCIOCGETCONF_OLD:
537260926Sjhb#endif
538260926Sjhb		case PCIOCGETCONF:
539260926Sjhb		case PCIOCGETBAR:
540260926Sjhb		case PCIOCLISTVPD:
541260926Sjhb			break;
542260926Sjhb		default:
543260926Sjhb			return (EPERM);
544260926Sjhb		}
545260926Sjhb	}
54669953Smsmith
547260926Sjhb	switch (cmd) {
548172999Simp#ifdef PRE7_COMPAT
549240990Sglebius#ifdef COMPAT_FREEBSD32
550260926Sjhb	case PCIOCGETCONF_OLD32:
551240981Ssobomax               cio32 = (struct pci_conf_io32 *)data;
552240981Ssobomax               cio = malloc(sizeof(struct pci_conf_io), M_TEMP, M_WAITOK);
553240981Ssobomax               cio->pat_buf_len = cio32->pat_buf_len;
554240981Ssobomax               cio->num_patterns = cio32->num_patterns;
555240981Ssobomax               cio->patterns = (void *)(uintptr_t)cio32->patterns;
556240981Ssobomax               cio->match_buf_len = cio32->match_buf_len;
557240981Ssobomax               cio->num_matches = cio32->num_matches;
558240981Ssobomax               cio->matches = (void *)(uintptr_t)cio32->matches;
559240981Ssobomax               cio->offset = cio32->offset;
560240981Ssobomax               cio->generation = cio32->generation;
561240981Ssobomax               cio->status = cio32->status;
562240981Ssobomax               cio32->num_matches = 0;
563240990Sglebius               break;
564240990Sglebius#endif
565240990Sglebius	case PCIOCGETCONF_OLD:
566240990Sglebius#endif
567240990Sglebius	case PCIOCGETCONF:
568240990Sglebius		cio = (struct pci_conf_io *)data;
569240990Sglebius	}
570240981Ssobomax
571260926Sjhb	switch (cmd) {
572240990Sglebius#ifdef PRE7_COMPAT
573240990Sglebius#ifdef COMPAT_FREEBSD32
574240990Sglebius	case PCIOCGETCONF_OLD32:
575240990Sglebius#endif
576172932Smarius	case PCIOCGETCONF_OLD:
577172932Smarius#endif
57869953Smsmith	case PCIOCGETCONF:
57969953Smsmith
580172932Smarius		pattern_buf = NULL;
58169953Smsmith		num_patterns = 0;
58269953Smsmith		dinfo = NULL;
58369953Smsmith
584172932Smarius		cio->num_matches = 0;
585172932Smarius
58669953Smsmith		/*
58769953Smsmith		 * If the user specified an offset into the device list,
58869953Smsmith		 * but the list has changed since they last called this
58969953Smsmith		 * ioctl, tell them that the list has changed.  They will
59069953Smsmith		 * have to get the list from the beginning.
59169953Smsmith		 */
59269953Smsmith		if ((cio->offset != 0)
59369953Smsmith		 && (cio->generation != pci_generation)){
59469953Smsmith			cio->status = PCI_GETCONF_LIST_CHANGED;
59569953Smsmith			error = 0;
596240992Sglebius			goto getconfexit;
59769953Smsmith		}
59869953Smsmith
59969953Smsmith		/*
60069953Smsmith		 * Check to see whether the user has asked for an offset
60169953Smsmith		 * past the end of our list.
60269953Smsmith		 */
60369953Smsmith		if (cio->offset >= pci_numdevs) {
60469953Smsmith			cio->status = PCI_GETCONF_LAST_DEVICE;
60569953Smsmith			error = 0;
606240992Sglebius			goto getconfexit;
60769953Smsmith		}
60869953Smsmith
60969953Smsmith		/* get the head of the device queue */
61069953Smsmith		devlist_head = &pci_devq;
61169953Smsmith
61269953Smsmith		/*
61369953Smsmith		 * Determine how much room we have for pci_conf structures.
61469953Smsmith		 * Round the user's buffer size down to the nearest
61569953Smsmith		 * multiple of sizeof(struct pci_conf) in case the user
61669953Smsmith		 * didn't specify a multiple of that size.
61769953Smsmith		 */
618172999Simp#ifdef PRE7_COMPAT
619240981Ssobomax#ifdef COMPAT_FREEBSD32
620240992Sglebius		if (cmd == PCIOCGETCONF_OLD32)
621240992Sglebius			confsz = sizeof(struct pci_conf_old32);
622240992Sglebius		else
623240981Ssobomax#endif
624172932Smarius		if (cmd == PCIOCGETCONF_OLD)
625172932Smarius			confsz = sizeof(struct pci_conf_old);
626172932Smarius		else
627172932Smarius#endif
628172932Smarius			confsz = sizeof(struct pci_conf);
629172932Smarius		iolen = min(cio->match_buf_len - (cio->match_buf_len % confsz),
630172932Smarius		    pci_numdevs * confsz);
63169953Smsmith
63269953Smsmith		/*
63369953Smsmith		 * Since we know that iolen is a multiple of the size of
63469953Smsmith		 * the pciconf union, it's okay to do this.
63569953Smsmith		 */
636172932Smarius		ionum = iolen / confsz;
63769953Smsmith
63869953Smsmith		/*
63969953Smsmith		 * If this test is true, the user wants the pci_conf
64069953Smsmith		 * structures returned to match the supplied entries.
64169953Smsmith		 */
642116704Sjmg		if ((cio->num_patterns > 0) && (cio->num_patterns < pci_numdevs)
64369953Smsmith		 && (cio->pat_buf_len > 0)) {
64469953Smsmith			/*
64569953Smsmith			 * pat_buf_len needs to be:
64669953Smsmith			 * num_patterns * sizeof(struct pci_match_conf)
64769953Smsmith			 * While it is certainly possible the user just
64869953Smsmith			 * allocated a large buffer, but set the number of
64969953Smsmith			 * matches correctly, it is far more likely that
65069953Smsmith			 * their kernel doesn't match the userland utility
65169953Smsmith			 * they're using.  It's also possible that the user
65269953Smsmith			 * forgot to initialize some variables.  Yes, this
65369953Smsmith			 * may be overly picky, but I hazard to guess that
65469953Smsmith			 * it's far more likely to just catch folks that
65569953Smsmith			 * updated their kernel but not their userland.
65669953Smsmith			 */
657172999Simp#ifdef PRE7_COMPAT
658240981Ssobomax#ifdef COMPAT_FREEBSD32
659240992Sglebius			if (cmd == PCIOCGETCONF_OLD32)
660240992Sglebius				pbufsz = sizeof(struct pci_match_conf_old32);
661240992Sglebius			else
662240981Ssobomax#endif
663172932Smarius			if (cmd == PCIOCGETCONF_OLD)
664172932Smarius				pbufsz = sizeof(struct pci_match_conf_old);
665172932Smarius			else
666172932Smarius#endif
667172932Smarius				pbufsz = sizeof(struct pci_match_conf);
668172932Smarius			if (cio->num_patterns * pbufsz != cio->pat_buf_len) {
669172932Smarius				/* The user made a mistake, return an error. */
67069953Smsmith				cio->status = PCI_GETCONF_ERROR;
67169953Smsmith				error = EINVAL;
672240992Sglebius				goto getconfexit;
67369953Smsmith			}
67469953Smsmith
67569953Smsmith			/*
67669953Smsmith			 * Allocate a buffer to hold the patterns.
67769953Smsmith			 */
678172999Simp#ifdef PRE7_COMPAT
679240990Sglebius#ifdef COMPAT_FREEBSD32
680240992Sglebius			if (cmd == PCIOCGETCONF_OLD32) {
681240992Sglebius				pattern_buf_old32 = malloc(cio->pat_buf_len,
682240992Sglebius				    M_TEMP, M_WAITOK);
683240992Sglebius				error = copyin(cio->patterns,
684240992Sglebius				    pattern_buf_old32, cio->pat_buf_len);
685240992Sglebius			} else
686240990Sglebius#endif /* COMPAT_FREEBSD32 */
687172932Smarius			if (cmd == PCIOCGETCONF_OLD) {
688172932Smarius				pattern_buf_old = malloc(cio->pat_buf_len,
689172932Smarius				    M_TEMP, M_WAITOK);
690172932Smarius				error = copyin(cio->patterns,
691172932Smarius				    pattern_buf_old, cio->pat_buf_len);
692172932Smarius			} else
693240990Sglebius#endif /* PRE7_COMPAT */
694172932Smarius			{
695172932Smarius				pattern_buf = malloc(cio->pat_buf_len, M_TEMP,
696172932Smarius				    M_WAITOK);
697172932Smarius				error = copyin(cio->patterns, pattern_buf,
698172932Smarius				    cio->pat_buf_len);
699172932Smarius			}
700116702Sjmg			if (error != 0) {
701116702Sjmg				error = EINVAL;
702116702Sjmg				goto getconfexit;
703116702Sjmg			}
70469953Smsmith			num_patterns = cio->num_patterns;
70569953Smsmith		} else if ((cio->num_patterns > 0)
70669953Smsmith			|| (cio->pat_buf_len > 0)) {
70769953Smsmith			/*
70869953Smsmith			 * The user made a mistake, spit out an error.
70969953Smsmith			 */
71069953Smsmith			cio->status = PCI_GETCONF_ERROR;
71169953Smsmith			error = EINVAL;
712240981Ssobomax                       goto getconfexit;
713172932Smarius		}
71469953Smsmith
71569953Smsmith		/*
71669953Smsmith		 * Go through the list of devices and copy out the devices
71769953Smsmith		 * that match the user's criteria.
71869953Smsmith		 */
719306469Sjhb		for (cio->num_matches = 0, i = 0,
720295813Sse				 dinfo = STAILQ_FIRST(devlist_head);
721306469Sjhb		     dinfo != NULL;
72269953Smsmith		     dinfo = STAILQ_NEXT(dinfo, pci_links), i++) {
72369953Smsmith
72469953Smsmith			if (i < cio->offset)
72569953Smsmith				continue;
72669953Smsmith
72769953Smsmith			/* Populate pd_name and pd_unit */
72869953Smsmith			name = NULL;
729175368Sjhb			if (dinfo->cfg.dev)
73069953Smsmith				name = device_get_name(dinfo->cfg.dev);
73169953Smsmith			if (name) {
73269953Smsmith				strncpy(dinfo->conf.pd_name, name,
73369953Smsmith					sizeof(dinfo->conf.pd_name));
73469953Smsmith				dinfo->conf.pd_name[PCI_MAXNAMELEN] = 0;
73569953Smsmith				dinfo->conf.pd_unit =
73669953Smsmith					device_get_unit(dinfo->cfg.dev);
737175368Sjhb			} else {
738175368Sjhb				dinfo->conf.pd_name[0] = '\0';
739175368Sjhb				dinfo->conf.pd_unit = 0;
74069953Smsmith			}
74169953Smsmith
742172999Simp#ifdef PRE7_COMPAT
743240990Sglebius			if (
744240990Sglebius#ifdef COMPAT_FREEBSD32
745240990Sglebius			    (cmd == PCIOCGETCONF_OLD32 &&
746240990Sglebius			    (pattern_buf_old32 == NULL ||
747240990Sglebius			    pci_conf_match_old32(pattern_buf_old32,
748240990Sglebius			    num_patterns, &dinfo->conf) == 0)) ||
749240990Sglebius#endif
750240990Sglebius			    (cmd == PCIOCGETCONF_OLD &&
751172932Smarius			    (pattern_buf_old == NULL ||
752172932Smarius			    pci_conf_match_old(pattern_buf_old, num_patterns,
753172932Smarius			    &dinfo->conf) == 0)) ||
754172932Smarius			    (cmd == PCIOCGETCONF &&
755172932Smarius			    (pattern_buf == NULL ||
756172932Smarius			    pci_conf_match(pattern_buf, num_patterns,
757172932Smarius			    &dinfo->conf) == 0))) {
758172932Smarius#else
759172932Smarius			if (pattern_buf == NULL ||
760172932Smarius			    pci_conf_match(pattern_buf, num_patterns,
761172932Smarius			    &dinfo->conf) == 0) {
762172932Smarius#endif
76369953Smsmith				/*
76469953Smsmith				 * If we've filled up the user's buffer,
76569953Smsmith				 * break out at this point.  Since we've
76669953Smsmith				 * got a match here, we'll pick right back
76769953Smsmith				 * up at the matching entry.  We can also
76869953Smsmith				 * tell the user that there are more matches
76969953Smsmith				 * left.
77069953Smsmith				 */
77169953Smsmith				if (cio->num_matches >= ionum)
77269953Smsmith					break;
77369953Smsmith
774172999Simp#ifdef PRE7_COMPAT
775240990Sglebius#ifdef COMPAT_FREEBSD32
776240992Sglebius				if (cmd == PCIOCGETCONF_OLD32) {
777331922Skib					memset(&conf_old32, 0,
778331922Skib					    sizeof(conf_old32));
779240992Sglebius					conf_old32.pc_sel.pc_bus =
780240992Sglebius					    dinfo->conf.pc_sel.pc_bus;
781240992Sglebius					conf_old32.pc_sel.pc_dev =
782240992Sglebius					    dinfo->conf.pc_sel.pc_dev;
783240992Sglebius					conf_old32.pc_sel.pc_func =
784240992Sglebius					    dinfo->conf.pc_sel.pc_func;
785240992Sglebius					conf_old32.pc_hdr = dinfo->conf.pc_hdr;
786240992Sglebius					conf_old32.pc_subvendor =
787240992Sglebius					    dinfo->conf.pc_subvendor;
788240992Sglebius					conf_old32.pc_subdevice =
789240992Sglebius					    dinfo->conf.pc_subdevice;
790240992Sglebius					conf_old32.pc_vendor =
791240992Sglebius					    dinfo->conf.pc_vendor;
792240992Sglebius					conf_old32.pc_device =
793240992Sglebius					    dinfo->conf.pc_device;
794240992Sglebius					conf_old32.pc_class =
795240992Sglebius					    dinfo->conf.pc_class;
796240992Sglebius					conf_old32.pc_subclass =
797240992Sglebius					    dinfo->conf.pc_subclass;
798240992Sglebius					conf_old32.pc_progif =
799240992Sglebius					    dinfo->conf.pc_progif;
800240992Sglebius					conf_old32.pc_revid =
801240992Sglebius					    dinfo->conf.pc_revid;
802240992Sglebius					strncpy(conf_old32.pd_name,
803240992Sglebius					    dinfo->conf.pd_name,
804240992Sglebius					    sizeof(conf_old32.pd_name));
805240992Sglebius					conf_old32.pd_name[PCI_MAXNAMELEN] = 0;
806240992Sglebius					conf_old32.pd_unit =
807240992Sglebius					    (uint32_t)dinfo->conf.pd_unit;
808240992Sglebius					confdata = &conf_old32;
809240992Sglebius				} else
810240990Sglebius#endif /* COMPAT_FREEBSD32 */
811172932Smarius				if (cmd == PCIOCGETCONF_OLD) {
812331922Skib					memset(&conf_old, 0, sizeof(conf_old));
813172932Smarius					conf_old.pc_sel.pc_bus =
814172932Smarius					    dinfo->conf.pc_sel.pc_bus;
815172932Smarius					conf_old.pc_sel.pc_dev =
816172932Smarius					    dinfo->conf.pc_sel.pc_dev;
817172932Smarius					conf_old.pc_sel.pc_func =
818172932Smarius					    dinfo->conf.pc_sel.pc_func;
819172932Smarius					conf_old.pc_hdr = dinfo->conf.pc_hdr;
820172932Smarius					conf_old.pc_subvendor =
821172932Smarius					    dinfo->conf.pc_subvendor;
822172932Smarius					conf_old.pc_subdevice =
823172932Smarius					    dinfo->conf.pc_subdevice;
824172932Smarius					conf_old.pc_vendor =
825172932Smarius					    dinfo->conf.pc_vendor;
826172932Smarius					conf_old.pc_device =
827172932Smarius					    dinfo->conf.pc_device;
828172932Smarius					conf_old.pc_class =
829172932Smarius					    dinfo->conf.pc_class;
830172932Smarius					conf_old.pc_subclass =
831172932Smarius					    dinfo->conf.pc_subclass;
832172932Smarius					conf_old.pc_progif =
833172932Smarius					    dinfo->conf.pc_progif;
834172932Smarius					conf_old.pc_revid =
835172932Smarius					    dinfo->conf.pc_revid;
836174932Smarius					strncpy(conf_old.pd_name,
837174932Smarius					    dinfo->conf.pd_name,
838174932Smarius					    sizeof(conf_old.pd_name));
839174932Smarius					conf_old.pd_name[PCI_MAXNAMELEN] = 0;
840174932Smarius					conf_old.pd_unit =
841174932Smarius					    dinfo->conf.pd_unit;
842172932Smarius					confdata = &conf_old;
843172932Smarius				} else
844240990Sglebius#endif /* PRE7_COMPAT */
845172932Smarius					confdata = &dinfo->conf;
846306469Sjhb				error = copyout(confdata,
847174932Smarius				    (caddr_t)cio->matches +
848306469Sjhb				    confsz * cio->num_matches, confsz);
849306469Sjhb				if (error)
850306469Sjhb					break;
851306469Sjhb				cio->num_matches++;
85269953Smsmith			}
85369953Smsmith		}
85469953Smsmith
85569953Smsmith		/*
85669953Smsmith		 * Set the pointer into the list, so if the user is getting
85769953Smsmith		 * n records at a time, where n < pci_numdevs,
85869953Smsmith		 */
85969953Smsmith		cio->offset = i;
86069953Smsmith
86169953Smsmith		/*
86269953Smsmith		 * Set the generation, the user will need this if they make
86369953Smsmith		 * another ioctl call with offset != 0.
86469953Smsmith		 */
86569953Smsmith		cio->generation = pci_generation;
866172932Smarius
86769953Smsmith		/*
86869953Smsmith		 * If this is the last device, inform the user so he won't
86969953Smsmith		 * bother asking for more devices.  If dinfo isn't NULL, we
87069953Smsmith		 * know that there are more matches in the list because of
87169953Smsmith		 * the way the traversal is done.
87269953Smsmith		 */
87369953Smsmith		if (dinfo == NULL)
87469953Smsmith			cio->status = PCI_GETCONF_LAST_DEVICE;
87569953Smsmith		else
87669953Smsmith			cio->status = PCI_GETCONF_MORE_DEVS;
87769953Smsmith
878116702Sjmggetconfexit:
879240990Sglebius#ifdef PRE7_COMPAT
880240981Ssobomax#ifdef COMPAT_FREEBSD32
881240992Sglebius		if (cmd == PCIOCGETCONF_OLD32) {
882240992Sglebius			cio32->status = cio->status;
883240992Sglebius			cio32->generation = cio->generation;
884240992Sglebius			cio32->offset = cio->offset;
885240992Sglebius			cio32->num_matches = cio->num_matches;
886240990Sglebius			free(cio, M_TEMP);
887240990Sglebius		}
888240990Sglebius		if (pattern_buf_old32 != NULL)
889240990Sglebius			free(pattern_buf_old32, M_TEMP);
890240981Ssobomax#endif
891172932Smarius		if (pattern_buf_old != NULL)
892172932Smarius			free(pattern_buf_old, M_TEMP);
893172932Smarius#endif
894240990Sglebius		if (pattern_buf != NULL)
895240990Sglebius			free(pattern_buf, M_TEMP);
89669953Smsmith
89769953Smsmith		break;
898121013Sse
899172999Simp#ifdef PRE7_COMPAT
900172932Smarius	case PCIOCREAD_OLD:
901172932Smarius	case PCIOCWRITE_OLD:
902172932Smarius		io_old = (struct pci_io_old *)data;
903172932Smarius		iodata.pi_sel.pc_domain = 0;
904172932Smarius		iodata.pi_sel.pc_bus = io_old->pi_sel.pc_bus;
905172932Smarius		iodata.pi_sel.pc_dev = io_old->pi_sel.pc_dev;
906172932Smarius		iodata.pi_sel.pc_func = io_old->pi_sel.pc_func;
907172932Smarius		iodata.pi_reg = io_old->pi_reg;
908172932Smarius		iodata.pi_width = io_old->pi_width;
909172932Smarius		iodata.pi_data = io_old->pi_data;
910172932Smarius		data = (caddr_t)&iodata;
911172932Smarius		/* FALLTHROUGH */
912172932Smarius#endif
91369953Smsmith	case PCIOCREAD:
914121013Sse	case PCIOCWRITE:
91569953Smsmith		io = (struct pci_io *)data;
91669953Smsmith		switch(io->pi_width) {
91769953Smsmith		case 4:
91869953Smsmith		case 2:
91969953Smsmith		case 1:
920197099Savg			/* Make sure register is not negative and aligned. */
921172932Smarius			if (io->pi_reg < 0 ||
922172932Smarius			    io->pi_reg & (io->pi_width - 1)) {
923163055Sru				error = EINVAL;
924163055Sru				break;
925163055Sru			}
92669953Smsmith			/*
92769953Smsmith			 * Assume that the user-level bus number is
928145022Sbms			 * in fact the physical PCI bus number.
929145022Sbms			 * Look up the grandparent, i.e. the bridge device,
930145022Sbms			 * so that we can issue configuration space cycles.
93169953Smsmith			 */
932172394Smarius			pcidev = pci_find_dbsf(io->pi_sel.pc_domain,
933172394Smarius			    io->pi_sel.pc_bus, io->pi_sel.pc_dev,
934172394Smarius			    io->pi_sel.pc_func);
935145022Sbms			if (pcidev) {
936172999Simp#ifdef PRE7_COMPAT
937172932Smarius				if (cmd == PCIOCWRITE || cmd == PCIOCWRITE_OLD)
938172932Smarius#else
939121013Sse				if (cmd == PCIOCWRITE)
940172932Smarius#endif
941279448Srstone					pci_write_config(pcidev,
942121013Sse							  io->pi_reg,
943121013Sse							  io->pi_data,
944121013Sse							  io->pi_width);
945172999Simp#ifdef PRE7_COMPAT
946172932Smarius				else if (cmd == PCIOCREAD_OLD)
947172932Smarius					io_old->pi_data =
948279448Srstone						pci_read_config(pcidev,
949172932Smarius							  io->pi_reg,
950172932Smarius							  io->pi_width);
951172932Smarius#endif
952121013Sse				else
953121013Sse					io->pi_data =
954279448Srstone						pci_read_config(pcidev,
955121013Sse							  io->pi_reg,
956121013Sse							  io->pi_width);
95769953Smsmith				error = 0;
95869953Smsmith			} else {
959149478Sps#ifdef COMPAT_FREEBSD4
960172932Smarius				if (cmd == PCIOCREAD_OLD) {
961172932Smarius					io_old->pi_data = -1;
962149478Sps					error = 0;
963149478Sps				} else
964149478Sps#endif
965149478Sps					error = ENODEV;
96669953Smsmith			}
96769953Smsmith			break;
96869953Smsmith		default:
969116702Sjmg			error = EINVAL;
97069953Smsmith			break;
97169953Smsmith		}
97269953Smsmith		break;
97369953Smsmith
974188018Sjhb	case PCIOCGETBAR:
975188018Sjhb		bio = (struct pci_bar_io *)data;
976188018Sjhb
977188018Sjhb		/*
978188018Sjhb		 * Assume that the user-level bus number is
979188018Sjhb		 * in fact the physical PCI bus number.
980188018Sjhb		 */
981188018Sjhb		pcidev = pci_find_dbsf(bio->pbi_sel.pc_domain,
982188018Sjhb		    bio->pbi_sel.pc_bus, bio->pbi_sel.pc_dev,
983188018Sjhb		    bio->pbi_sel.pc_func);
984188018Sjhb		if (pcidev == NULL) {
985188018Sjhb			error = ENODEV;
986188018Sjhb			break;
987188018Sjhb		}
988220195Sjhb		pm = pci_find_bar(pcidev, bio->pbi_reg);
989220195Sjhb		if (pm == NULL) {
990188018Sjhb			error = EINVAL;
991188018Sjhb			break;
992188018Sjhb		}
993220195Sjhb		bio->pbi_base = pm->pm_value;
994220195Sjhb		bio->pbi_length = (pci_addr_t)1 << pm->pm_size;
995220195Sjhb		bio->pbi_enabled = pci_bar_enabled(pcidev, pm);
996188018Sjhb		error = 0;
997188018Sjhb		break;
998210597Sneel	case PCIOCATTACHED:
999210597Sneel		error = 0;
1000210597Sneel		io = (struct pci_io *)data;
1001210597Sneel		pcidev = pci_find_dbsf(io->pi_sel.pc_domain, io->pi_sel.pc_bus,
1002210597Sneel				       io->pi_sel.pc_dev, io->pi_sel.pc_func);
1003210597Sneel		if (pcidev != NULL)
1004210597Sneel			io->pi_data = device_is_attached(pcidev);
1005210597Sneel		else
1006210597Sneel			error = ENODEV;
1007210597Sneel		break;
1008260926Sjhb	case PCIOCLISTVPD:
1009260926Sjhb		lvio = (struct pci_list_vpd_io *)data;
1010260926Sjhb
1011260926Sjhb		/*
1012260926Sjhb		 * Assume that the user-level bus number is
1013260926Sjhb		 * in fact the physical PCI bus number.
1014260926Sjhb		 */
1015260926Sjhb		pcidev = pci_find_dbsf(lvio->plvi_sel.pc_domain,
1016260926Sjhb		    lvio->plvi_sel.pc_bus, lvio->plvi_sel.pc_dev,
1017260926Sjhb		    lvio->plvi_sel.pc_func);
1018260926Sjhb		if (pcidev == NULL) {
1019260926Sjhb			error = ENODEV;
1020260926Sjhb			break;
1021260926Sjhb		}
1022260926Sjhb		error = pci_list_vpd(pcidev, lvio);
1023260926Sjhb		break;
102469953Smsmith	default:
102569953Smsmith		error = ENOTTY;
102669953Smsmith		break;
102769953Smsmith	}
102869953Smsmith
102969953Smsmith	return (error);
103069953Smsmith}
1031