diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2013-10-09 22:42:09 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2013-10-10 09:06:58 +0200 |
commit | bceabc95c1c85d793200446fa85f1ddc6313ea29 (patch) | |
tree | 973c8bd8deca9fd69913f2895cc91e0e6114d46c /freebsd/sys/dev/pci | |
parent | Add FreeBSD sources as a submodule (diff) | |
download | rtems-libbsd-bceabc95c1c85d793200446fa85f1ddc6313ea29.tar.bz2 |
Move files to match FreeBSD layout
Diffstat (limited to 'freebsd/sys/dev/pci')
-rw-r--r-- | freebsd/sys/dev/pci/pci.c | 4119 | ||||
-rw-r--r-- | freebsd/sys/dev/pci/pci_pci.c | 740 | ||||
-rw-r--r-- | freebsd/sys/dev/pci/pci_private.h | 116 | ||||
-rw-r--r-- | freebsd/sys/dev/pci/pci_user.c | 748 | ||||
-rw-r--r-- | freebsd/sys/dev/pci/pcib_private.h | 86 | ||||
-rw-r--r-- | freebsd/sys/dev/pci/pcireg.h | 750 | ||||
-rw-r--r-- | freebsd/sys/dev/pci/pcivar.h | 478 |
7 files changed, 7037 insertions, 0 deletions
diff --git a/freebsd/sys/dev/pci/pci.c b/freebsd/sys/dev/pci/pci.c new file mode 100644 index 00000000..ebff0fff --- /dev/null +++ b/freebsd/sys/dev/pci/pci.c @@ -0,0 +1,4119 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/*- + * Copyright (c) 1997, Stefan Esser <se@freebsd.org> + * Copyright (c) 2000, Michael Smith <msmith@freebsd.org> + * Copyright (c) 2000, BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <freebsd/sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <freebsd/local/opt_bus.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/linker.h> +#include <freebsd/sys/fcntl.h> +#include <freebsd/sys/conf.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/sysctl.h> +#include <freebsd/sys/endian.h> + +#include <freebsd/vm/vm.h> +#include <freebsd/vm/pmap.h> +#ifndef __rtems__ +#include <freebsd/vm/vm_extern.h> +#endif /* __rtems__ */ + +#include <freebsd/sys/bus.h> +#include <freebsd/machine/bus.h> +#include <freebsd/sys/rman.h> +#include <freebsd/machine/resource.h> +#include <freebsd/machine/stdarg.h> + +#if defined(__i386__) || defined(__amd64__) || defined(__powerpc__) +#include <freebsd/machine/intr_machdep.h> +#endif + +#include <freebsd/sys/pciio.h> +#include <freebsd/dev/pci/pcireg.h> +#include <freebsd/dev/pci/pcivar.h> +#include <freebsd/dev/pci/pci_private.h> + +#include <freebsd/dev/usb/controller/ehcireg.h> +#include <freebsd/dev/usb/controller/ohcireg.h> +#ifndef __rtems__ +#include <freebsd/dev/usb/controller/uhcireg.h> +#endif /* __rtems__ */ + +#include <freebsd/local/pcib_if.h> +#include <freebsd/local/pci_if.h> + +#ifdef __HAVE_ACPI +#include <freebsd/contrib/dev/acpica/include/acpi.h> +#include <freebsd/local/acpi_if.h> +#else +#define ACPI_PWR_FOR_SLEEP(x, y, z) +#endif + +static pci_addr_t pci_mapbase(uint64_t mapreg); +static const char *pci_maptype(uint64_t mapreg); +static int pci_mapsize(uint64_t testval); +static int pci_maprange(uint64_t mapreg); +static void pci_fixancient(pcicfgregs *cfg); +static int pci_printf(pcicfgregs *cfg, const char *fmt, ...); + +static int pci_porten(device_t dev); +static int pci_memen(device_t dev); +static void pci_assign_interrupt(device_t bus, device_t dev, + int force_route); +static int pci_add_map(device_t bus, device_t dev, int reg, + struct resource_list *rl, int force, int prefetch); +static int pci_probe(device_t dev); +static int pci_attach(device_t dev); +static void pci_load_vendor_data(void); +static int pci_describe_parse_line(char **ptr, int *vendor, + int *device, char **desc); +static char *pci_describe_device(device_t dev); +static int pci_modevent(module_t mod, int what, void *arg); +static void pci_hdrtypedata(device_t pcib, int b, int s, int f, + pcicfgregs *cfg); +static void pci_read_extcap(device_t pcib, pcicfgregs *cfg); +static int pci_read_vpd_reg(device_t pcib, pcicfgregs *cfg, + int reg, uint32_t *data); +#if 0 +static int pci_write_vpd_reg(device_t pcib, pcicfgregs *cfg, + int reg, uint32_t data); +#endif +static void pci_read_vpd(device_t pcib, pcicfgregs *cfg); +static void pci_disable_msi(device_t dev); +static void pci_enable_msi(device_t dev, uint64_t address, + uint16_t data); +static void pci_enable_msix(device_t dev, u_int index, + uint64_t address, uint32_t data); +static void pci_mask_msix(device_t dev, u_int index); +static void pci_unmask_msix(device_t dev, u_int index); +static int pci_msi_blacklisted(void); +static void pci_resume_msi(device_t dev); +static void pci_resume_msix(device_t dev); +static int pci_remap_intr_method(device_t bus, device_t dev, + u_int irq); + +static device_method_t pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, pci_probe), + DEVMETHOD(device_attach, pci_attach), + DEVMETHOD(device_detach, bus_generic_detach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(device_suspend, pci_suspend), + DEVMETHOD(device_resume, pci_resume), + + /* Bus interface */ + DEVMETHOD(bus_print_child, pci_print_child), + DEVMETHOD(bus_probe_nomatch, pci_probe_nomatch), + DEVMETHOD(bus_read_ivar, pci_read_ivar), + DEVMETHOD(bus_write_ivar, pci_write_ivar), + DEVMETHOD(bus_driver_added, pci_driver_added), + DEVMETHOD(bus_setup_intr, pci_setup_intr), + DEVMETHOD(bus_teardown_intr, pci_teardown_intr), + + DEVMETHOD(bus_get_resource_list,pci_get_resource_list), + DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), + DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), + DEVMETHOD(bus_delete_resource, pci_delete_resource), + DEVMETHOD(bus_alloc_resource, pci_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), + DEVMETHOD(bus_activate_resource, pci_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_child_pnpinfo_str, pci_child_pnpinfo_str_method), + DEVMETHOD(bus_child_location_str, pci_child_location_str_method), + DEVMETHOD(bus_remap_intr, pci_remap_intr_method), + + /* PCI interface */ + DEVMETHOD(pci_read_config, pci_read_config_method), + DEVMETHOD(pci_write_config, pci_write_config_method), + DEVMETHOD(pci_enable_busmaster, pci_enable_busmaster_method), + DEVMETHOD(pci_disable_busmaster, pci_disable_busmaster_method), + DEVMETHOD(pci_enable_io, pci_enable_io_method), + DEVMETHOD(pci_disable_io, pci_disable_io_method), + DEVMETHOD(pci_get_vpd_ident, pci_get_vpd_ident_method), + DEVMETHOD(pci_get_vpd_readonly, pci_get_vpd_readonly_method), + DEVMETHOD(pci_get_powerstate, pci_get_powerstate_method), + DEVMETHOD(pci_set_powerstate, pci_set_powerstate_method), + DEVMETHOD(pci_assign_interrupt, pci_assign_interrupt_method), + DEVMETHOD(pci_find_extcap, pci_find_extcap_method), + DEVMETHOD(pci_alloc_msi, pci_alloc_msi_method), + DEVMETHOD(pci_alloc_msix, pci_alloc_msix_method), + DEVMETHOD(pci_remap_msix, pci_remap_msix_method), + DEVMETHOD(pci_release_msi, pci_release_msi_method), + DEVMETHOD(pci_msi_count, pci_msi_count_method), + DEVMETHOD(pci_msix_count, pci_msix_count_method), + + { 0, 0 } +}; + +DEFINE_CLASS_0(pci, pci_driver, pci_methods, 0); + +static devclass_t pci_devclass; +DRIVER_MODULE(pci, pcib, pci_driver, pci_devclass, pci_modevent, 0); +MODULE_VERSION(pci, 1); + +static char *pci_vendordata; +static size_t pci_vendordata_size; + + +struct pci_quirk { + uint32_t devid; /* Vendor/device of the card */ + int type; +#define PCI_QUIRK_MAP_REG 1 /* PCI map register in weird place */ +#define PCI_QUIRK_DISABLE_MSI 2 /* MSI/MSI-X doesn't work */ +#define PCI_QUIRK_ENABLE_MSI_VM 3 /* Older chipset in VM where MSI works */ + int arg1; + int arg2; +}; + +struct pci_quirk pci_quirks[] = { + /* The Intel 82371AB and 82443MX has a map register at offset 0x90. */ + { 0x71138086, PCI_QUIRK_MAP_REG, 0x90, 0 }, + { 0x719b8086, PCI_QUIRK_MAP_REG, 0x90, 0 }, + /* As does the Serverworks OSB4 (the SMBus mapping register) */ + { 0x02001166, PCI_QUIRK_MAP_REG, 0x90, 0 }, + + /* + * MSI doesn't work with the ServerWorks CNB20-HE Host Bridge + * or the CMIC-SL (AKA ServerWorks GC_LE). + */ + { 0x00141166, PCI_QUIRK_DISABLE_MSI, 0, 0 }, + { 0x00171166, PCI_QUIRK_DISABLE_MSI, 0, 0 }, + + /* + * MSI doesn't work on earlier Intel chipsets including + * E7500, E7501, E7505, 845, 865, 875/E7210, and 855. + */ + { 0x25408086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, + { 0x254c8086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, + { 0x25508086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, + { 0x25608086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, + { 0x25708086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, + { 0x25788086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, + { 0x35808086, PCI_QUIRK_DISABLE_MSI, 0, 0 }, + + /* + * MSI doesn't work with devices behind the AMD 8131 HT-PCIX + * bridge. + */ + { 0x74501022, PCI_QUIRK_DISABLE_MSI, 0, 0 }, + + /* + * Some virtualization environments emulate an older chipset + * but support MSI just fine. QEMU uses the Intel 82440. + */ + { 0x12378086, PCI_QUIRK_ENABLE_MSI_VM, 0, 0 }, + + { 0 } +}; + +/* map register information */ +#define PCI_MAPMEM 0x01 /* memory map */ +#define PCI_MAPMEMP 0x02 /* prefetchable memory map */ +#define PCI_MAPPORT 0x04 /* port map */ + +struct devlist pci_devq; +uint32_t pci_generation; +uint32_t pci_numdevs = 0; +static int pcie_chipset, pcix_chipset; + +/* sysctl vars */ +SYSCTL_NODE(_hw, OID_AUTO, pci, CTLFLAG_RD, 0, "PCI bus tuning parameters"); + +static int pci_enable_io_modes = 1; +TUNABLE_INT("hw.pci.enable_io_modes", &pci_enable_io_modes); +SYSCTL_INT(_hw_pci, OID_AUTO, enable_io_modes, CTLFLAG_RW, + &pci_enable_io_modes, 1, + "Enable I/O and memory bits in the config register. Some BIOSes do not\n\ +enable these bits correctly. We'd like to do this all the time, but there\n\ +are some peripherals that this causes problems with."); + +static int pci_do_power_nodriver = 0; +TUNABLE_INT("hw.pci.do_power_nodriver", &pci_do_power_nodriver); +SYSCTL_INT(_hw_pci, OID_AUTO, do_power_nodriver, CTLFLAG_RW, + &pci_do_power_nodriver, 0, + "Place a function into D3 state when no driver attaches to it. 0 means\n\ +disable. 1 means conservatively place devices into D3 state. 2 means\n\ +agressively place devices into D3 state. 3 means put absolutely everything\n\ +in D3 state."); + +static int pci_do_power_resume = 1; +TUNABLE_INT("hw.pci.do_power_resume", &pci_do_power_resume); +SYSCTL_INT(_hw_pci, OID_AUTO, do_power_resume, CTLFLAG_RW, + &pci_do_power_resume, 1, + "Transition from D3 -> D0 on resume."); + +static int pci_do_msi = 1; +TUNABLE_INT("hw.pci.enable_msi", &pci_do_msi); +SYSCTL_INT(_hw_pci, OID_AUTO, enable_msi, CTLFLAG_RW, &pci_do_msi, 1, + "Enable support for MSI interrupts"); + +static int pci_do_msix = 1; +TUNABLE_INT("hw.pci.enable_msix", &pci_do_msix); +SYSCTL_INT(_hw_pci, OID_AUTO, enable_msix, CTLFLAG_RW, &pci_do_msix, 1, + "Enable support for MSI-X interrupts"); + +static int pci_honor_msi_blacklist = 1; +TUNABLE_INT("hw.pci.honor_msi_blacklist", &pci_honor_msi_blacklist); +SYSCTL_INT(_hw_pci, OID_AUTO, honor_msi_blacklist, CTLFLAG_RD, + &pci_honor_msi_blacklist, 1, "Honor chipset blacklist for MSI"); + +#if defined(__i386__) || defined(__amd64__) +static int pci_usb_takeover = 1; +#else +static int pci_usb_takeover = 0; +#endif +TUNABLE_INT("hw.pci.usb_early_takeover", &pci_usb_takeover); +SYSCTL_INT(_hw_pci, OID_AUTO, usb_early_takeover, CTLFLAG_RD | CTLFLAG_TUN, + &pci_usb_takeover, 1, "Enable early takeover of USB controllers.\n\ +Disable this if you depend on BIOS emulation of USB devices, that is\n\ +you use USB devices (like keyboard or mouse) but do not load USB drivers"); + +/* Find a device_t by bus/slot/function in domain 0 */ + +device_t +pci_find_bsf(uint8_t bus, uint8_t slot, uint8_t func) +{ + + return (pci_find_dbsf(0, bus, slot, func)); +} + +/* Find a device_t by domain/bus/slot/function */ + +device_t +pci_find_dbsf(uint32_t domain, uint8_t bus, uint8_t slot, uint8_t func) +{ + struct pci_devinfo *dinfo; + + STAILQ_FOREACH(dinfo, &pci_devq, pci_links) { + if ((dinfo->cfg.domain == domain) && + (dinfo->cfg.bus == bus) && + (dinfo->cfg.slot == slot) && + (dinfo->cfg.func == func)) { + return (dinfo->cfg.dev); + } + } + + return (NULL); +} + +#ifndef __rtems__ +/* Find a device_t by vendor/device ID */ + +device_t +pci_find_device(uint16_t vendor, uint16_t device) +{ + struct pci_devinfo *dinfo; + + STAILQ_FOREACH(dinfo, &pci_devq, pci_links) { + if ((dinfo->cfg.vendor == vendor) && + (dinfo->cfg.device == device)) { + return (dinfo->cfg.dev); + } + } + + return (NULL); +} +#endif /* __rtems__ */ + +static int +pci_printf(pcicfgregs *cfg, const char *fmt, ...) +{ + va_list ap; + int retval; + + retval = printf("pci%d:%d:%d:%d: ", cfg->domain, cfg->bus, cfg->slot, + cfg->func); + va_start(ap, fmt); + retval += vprintf(fmt, ap); + va_end(ap); + return (retval); +} + +/* return base address of memory or port map */ + +static pci_addr_t +pci_mapbase(uint64_t mapreg) +{ + + if (PCI_BAR_MEM(mapreg)) + return (mapreg & PCIM_BAR_MEM_BASE); + else + return (mapreg & PCIM_BAR_IO_BASE); +} + +/* return map type of memory or port map */ + +static const char * +pci_maptype(uint64_t mapreg) +{ + + if (PCI_BAR_IO(mapreg)) + return ("I/O Port"); + if (mapreg & PCIM_BAR_MEM_PREFETCH) + return ("Prefetchable Memory"); + return ("Memory"); +} + +/* return log2 of map size decoded for memory or port map */ + +static int +pci_mapsize(uint64_t testval) +{ + int ln2size; + + testval = pci_mapbase(testval); + ln2size = 0; + if (testval != 0) { + while ((testval & 1) == 0) + { + ln2size++; + testval >>= 1; + } + } + return (ln2size); +} + +/* return log2 of address range supported by map register */ + +static int +pci_maprange(uint64_t mapreg) +{ + int ln2range = 0; + + if (PCI_BAR_IO(mapreg)) + ln2range = 32; + else + switch (mapreg & PCIM_BAR_MEM_TYPE) { + case PCIM_BAR_MEM_32: + ln2range = 32; + break; + case PCIM_BAR_MEM_1MB: + ln2range = 20; + break; + case PCIM_BAR_MEM_64: + ln2range = 64; + break; + } + return (ln2range); +} + +/* adjust some values from PCI 1.0 devices to match 2.0 standards ... */ + +static void +pci_fixancient(pcicfgregs *cfg) +{ + if (cfg->hdrtype != 0) + return; + + /* PCI to PCI bridges use header type 1 */ + if (cfg->baseclass == PCIC_BRIDGE && cfg->subclass == PCIS_BRIDGE_PCI) + cfg->hdrtype = 1; +} + +/* extract header type specific config data */ + +static void +pci_hdrtypedata(device_t pcib, int b, int s, int f, pcicfgregs *cfg) +{ +#define REG(n, w) PCIB_READ_CONFIG(pcib, b, s, f, n, w) + switch (cfg->hdrtype) { + case 0: + cfg->subvendor = REG(PCIR_SUBVEND_0, 2); + cfg->subdevice = REG(PCIR_SUBDEV_0, 2); + cfg->nummaps = PCI_MAXMAPS_0; + break; + case 1: + cfg->nummaps = PCI_MAXMAPS_1; + break; + case 2: + cfg->subvendor = REG(PCIR_SUBVEND_2, 2); + cfg->subdevice = REG(PCIR_SUBDEV_2, 2); + cfg->nummaps = PCI_MAXMAPS_2; + break; + } +#undef REG +} + +/* read configuration header into pcicfgregs structure */ +struct pci_devinfo * +pci_read_device(device_t pcib, int d, int b, int s, int f, size_t size) +{ +#define REG(n, w) PCIB_READ_CONFIG(pcib, b, s, f, n, w) + pcicfgregs *cfg = NULL; + struct pci_devinfo *devlist_entry; + struct devlist *devlist_head; + + devlist_head = &pci_devq; + + devlist_entry = NULL; + + if (REG(PCIR_DEVVENDOR, 4) != 0xfffffffful) { + devlist_entry = malloc(size, M_DEVBUF, M_WAITOK | M_ZERO); + if (devlist_entry == NULL) + return (NULL); + + cfg = &devlist_entry->cfg; + + cfg->domain = d; + cfg->bus = b; + cfg->slot = s; + cfg->func = f; + cfg->vendor = REG(PCIR_VENDOR, 2); + cfg->device = REG(PCIR_DEVICE, 2); + cfg->cmdreg = REG(PCIR_COMMAND, 2); + cfg->statreg = REG(PCIR_STATUS, 2); + cfg->baseclass = REG(PCIR_CLASS, 1); + cfg->subclass = REG(PCIR_SUBCLASS, 1); + cfg->progif = REG(PCIR_PROGIF, 1); + cfg->revid = REG(PCIR_REVID, 1); + cfg->hdrtype = REG(PCIR_HDRTYPE, 1); + cfg->cachelnsz = REG(PCIR_CACHELNSZ, 1); + cfg->lattimer = REG(PCIR_LATTIMER, 1); + cfg->intpin = REG(PCIR_INTPIN, 1); + cfg->intline = REG(PCIR_INTLINE, 1); + + cfg->mingnt = REG(PCIR_MINGNT, 1); + cfg->maxlat = REG(PCIR_MAXLAT, 1); + + cfg->mfdev = (cfg->hdrtype & PCIM_MFDEV) != 0; + cfg->hdrtype &= ~PCIM_MFDEV; + + pci_fixancient(cfg); + pci_hdrtypedata(pcib, b, s, f, cfg); + + if (REG(PCIR_STATUS, 2) & PCIM_STATUS_CAPPRESENT) + pci_read_extcap(pcib, cfg); + + STAILQ_INSERT_TAIL(devlist_head, devlist_entry, pci_links); + + devlist_entry->conf.pc_sel.pc_domain = cfg->domain; + devlist_entry->conf.pc_sel.pc_bus = cfg->bus; + devlist_entry->conf.pc_sel.pc_dev = cfg->slot; + devlist_entry->conf.pc_sel.pc_func = cfg->func; + devlist_entry->conf.pc_hdr = cfg->hdrtype; + + devlist_entry->conf.pc_subvendor = cfg->subvendor; + devlist_entry->conf.pc_subdevice = cfg->subdevice; + devlist_entry->conf.pc_vendor = cfg->vendor; + devlist_entry->conf.pc_device = cfg->device; + + devlist_entry->conf.pc_class = cfg->baseclass; + devlist_entry->conf.pc_subclass = cfg->subclass; + devlist_entry->conf.pc_progif = cfg->progif; + devlist_entry->conf.pc_revid = cfg->revid; + + pci_numdevs++; + pci_generation++; + } + return (devlist_entry); +#undef REG +} + +static void +pci_read_extcap(device_t pcib, pcicfgregs *cfg) +{ +#define REG(n, w) PCIB_READ_CONFIG(pcib, cfg->bus, cfg->slot, cfg->func, n, w) +#define WREG(n, v, w) PCIB_WRITE_CONFIG(pcib, cfg->bus, cfg->slot, cfg->func, n, v, w) +#if defined(__i386__) || defined(__amd64__) || defined(__powerpc__) + uint64_t addr; +#endif + uint32_t val; + int ptr, nextptr, ptrptr; + + switch (cfg->hdrtype & PCIM_HDRTYPE) { + case 0: + case 1: + ptrptr = PCIR_CAP_PTR; + break; + case 2: + ptrptr = PCIR_CAP_PTR_2; /* cardbus capabilities ptr */ + break; + default: + return; /* no extended capabilities support */ + } + nextptr = REG(ptrptr, 1); /* sanity check? */ + + /* + * Read capability entries. + */ + while (nextptr != 0) { + /* Sanity check */ + if (nextptr > 255) { + printf("illegal PCI extended capability offset %d\n", + nextptr); + return; + } + /* Find the next entry */ + ptr = nextptr; + nextptr = REG(ptr + PCICAP_NEXTPTR, 1); + + /* Process this entry */ + switch (REG(ptr + PCICAP_ID, 1)) { + case PCIY_PMG: /* PCI power management */ + if (cfg->pp.pp_cap == 0) { + cfg->pp.pp_cap = REG(ptr + PCIR_POWER_CAP, 2); + cfg->pp.pp_status = ptr + PCIR_POWER_STATUS; + cfg->pp.pp_pmcsr = ptr + PCIR_POWER_PMCSR; + if ((nextptr - ptr) > PCIR_POWER_DATA) + cfg->pp.pp_data = ptr + PCIR_POWER_DATA; + } + break; +#if defined(__i386__) || defined(__amd64__) || defined(__powerpc__) + case PCIY_HT: /* HyperTransport */ + /* Determine HT-specific capability type. */ + val = REG(ptr + PCIR_HT_COMMAND, 2); + switch (val & PCIM_HTCMD_CAP_MASK) { + case PCIM_HTCAP_MSI_MAPPING: + if (!(val & PCIM_HTCMD_MSI_FIXED)) { + /* Sanity check the mapping window. */ + addr = REG(ptr + PCIR_HTMSI_ADDRESS_HI, + 4); + addr <<= 32; + addr |= REG(ptr + PCIR_HTMSI_ADDRESS_LO, + 4); + if (addr != MSI_INTEL_ADDR_BASE) + device_printf(pcib, + "HT Bridge at pci%d:%d:%d:%d has non-default MSI window 0x%llx\n", + cfg->domain, cfg->bus, + cfg->slot, cfg->func, + (long long)addr); + } else + addr = MSI_INTEL_ADDR_BASE; + + cfg->ht.ht_msimap = ptr; + cfg->ht.ht_msictrl = val; + cfg->ht.ht_msiaddr = addr; + break; + } + break; +#endif + case PCIY_MSI: /* PCI MSI */ + cfg->msi.msi_location = ptr; + cfg->msi.msi_ctrl = REG(ptr + PCIR_MSI_CTRL, 2); + cfg->msi.msi_msgnum = 1 << ((cfg->msi.msi_ctrl & + PCIM_MSICTRL_MMC_MASK)>>1); + break; + case PCIY_MSIX: /* PCI MSI-X */ + cfg->msix.msix_location = ptr; + cfg->msix.msix_ctrl = REG(ptr + PCIR_MSIX_CTRL, 2); + cfg->msix.msix_msgnum = (cfg->msix.msix_ctrl & + PCIM_MSIXCTRL_TABLE_SIZE) + 1; + val = REG(ptr + PCIR_MSIX_TABLE, 4); + cfg->msix.msix_table_bar = PCIR_BAR(val & + PCIM_MSIX_BIR_MASK); + cfg->msix.msix_table_offset = val & ~PCIM_MSIX_BIR_MASK; + val = REG(ptr + PCIR_MSIX_PBA, 4); + cfg->msix.msix_pba_bar = PCIR_BAR(val & + PCIM_MSIX_BIR_MASK); + cfg->msix.msix_pba_offset = val & ~PCIM_MSIX_BIR_MASK; + break; + case PCIY_VPD: /* PCI Vital Product Data */ + cfg->vpd.vpd_reg = ptr; + break; + case PCIY_SUBVENDOR: + /* Should always be true. */ + if ((cfg->hdrtype & PCIM_HDRTYPE) == 1) { + val = REG(ptr + PCIR_SUBVENDCAP_ID, 4); + cfg->subvendor = val & 0xffff; + cfg->subdevice = val >> 16; + } + break; + case PCIY_PCIX: /* PCI-X */ + /* + * Assume we have a PCI-X chipset if we have + * at least one PCI-PCI bridge with a PCI-X + * capability. Note that some systems with + * PCI-express or HT chipsets might match on + * this check as well. + */ + if ((cfg->hdrtype & PCIM_HDRTYPE) == 1) + pcix_chipset = 1; + break; + case PCIY_EXPRESS: /* PCI-express */ + /* + * Assume we have a PCI-express chipset if we have + * at least one PCI-express device. + */ + pcie_chipset = 1; + break; + default: + break; + } + } +/* REG and WREG use carry through to next functions */ +} + +/* + * PCI Vital Product Data + */ + +#define PCI_VPD_TIMEOUT 1000000 + +static int +pci_read_vpd_reg(device_t pcib, pcicfgregs *cfg, int reg, uint32_t *data) +{ + int count = PCI_VPD_TIMEOUT; + + KASSERT((reg & 3) == 0, ("VPD register must by 4 byte aligned")); + + WREG(cfg->vpd.vpd_reg + PCIR_VPD_ADDR, reg, 2); + + while ((REG(cfg->vpd.vpd_reg + PCIR_VPD_ADDR, 2) & 0x8000) != 0x8000) { + if (--count < 0) + return (ENXIO); + DELAY(1); /* limit looping */ + } + *data = (REG(cfg->vpd.vpd_reg + PCIR_VPD_DATA, 4)); + + return (0); +} + +#if 0 +static int +pci_write_vpd_reg(device_t pcib, pcicfgregs *cfg, int reg, uint32_t data) +{ + int count = PCI_VPD_TIMEOUT; + + KASSERT((reg & 3) == 0, ("VPD register must by 4 byte aligned")); + + WREG(cfg->vpd.vpd_reg + PCIR_VPD_DATA, data, 4); + WREG(cfg->vpd.vpd_reg + PCIR_VPD_ADDR, reg | 0x8000, 2); + while ((REG(cfg->vpd.vpd_reg + PCIR_VPD_ADDR, 2) & 0x8000) == 0x8000) { + if (--count < 0) + return (ENXIO); + DELAY(1); /* limit looping */ + } + + return (0); +} +#endif + +#undef PCI_VPD_TIMEOUT + +struct vpd_readstate { + device_t pcib; + pcicfgregs *cfg; + uint32_t val; + int bytesinval; + int off; + uint8_t cksum; +}; + +static int +vpd_nextbyte(struct vpd_readstate *vrs, uint8_t *data) +{ + uint32_t reg; + uint8_t byte; + + if (vrs->bytesinval == 0) { + if (pci_read_vpd_reg(vrs->pcib, vrs->cfg, vrs->off, ®)) + return (ENXIO); + vrs->val = le32toh(reg); + vrs->off += 4; + byte = vrs->val & 0xff; + vrs->bytesinval = 3; + } else { + vrs->val = vrs->val >> 8; + byte = vrs->val & 0xff; + vrs->bytesinval--; + } + + vrs->cksum += byte; + *data = byte; + return (0); +} + +static void +pci_read_vpd(device_t pcib, pcicfgregs *cfg) +{ + struct vpd_readstate vrs; + int state; + int name; + int remain; + int i; + int alloc, off; /* alloc/off for RO/W arrays */ + int cksumvalid; + int dflen; + uint8_t byte; + uint8_t byte2; + + /* init vpd reader */ + vrs.bytesinval = 0; + vrs.off = 0; + vrs.pcib = pcib; + vrs.cfg = cfg; + vrs.cksum = 0; + + state = 0; + name = remain = i = 0; /* shut up stupid gcc */ + alloc = off = 0; /* shut up stupid gcc */ + dflen = 0; /* shut up stupid gcc */ + cksumvalid = -1; + while (state >= 0) { + if (vpd_nextbyte(&vrs, &byte)) { + state = -2; + break; + } +#if 0 + printf("vpd: val: %#x, off: %d, bytesinval: %d, byte: %#hhx, " \ + "state: %d, remain: %d, name: %#x, i: %d\n", vrs.val, + vrs.off, vrs.bytesinval, byte, state, remain, name, i); +#endif + switch (state) { + case 0: /* item name */ + if (byte & 0x80) { + if (vpd_nextbyte(&vrs, &byte2)) { + state = -2; + break; + } + remain = byte2; + if (vpd_nextbyte(&vrs, &byte2)) { + state = -2; + break; + } + remain |= byte2 << 8; + if (remain > (0x7f*4 - vrs.off)) { + state = -1; + printf( + "pci%d:%d:%d:%d: invalid VPD data, remain %#x\n", + cfg->domain, cfg->bus, cfg->slot, + cfg->func, remain); + } + name = byte & 0x7f; + } else { + remain = byte & 0x7; + name = (byte >> 3) & 0xf; + } + switch (name) { + case 0x2: /* String */ + cfg->vpd.vpd_ident = malloc(remain + 1, + M_DEVBUF, M_WAITOK); + i = 0; + state = 1; + break; + case 0xf: /* End */ + state = -1; + break; + case 0x10: /* VPD-R */ + alloc = 8; + off = 0; + cfg->vpd.vpd_ros = malloc(alloc * + sizeof(*cfg->vpd.vpd_ros), M_DEVBUF, + M_WAITOK | M_ZERO); + state = 2; + break; + case 0x11: /* VPD-W */ + alloc = 8; + off = 0; + cfg->vpd.vpd_w = malloc(alloc * + sizeof(*cfg->vpd.vpd_w), M_DEVBUF, + M_WAITOK | M_ZERO); + state = 5; + break; + default: /* Invalid data, abort */ + state = -1; + break; + } + break; + + case 1: /* Identifier String */ + cfg->vpd.vpd_ident[i++] = byte; + remain--; + if (remain == 0) { + cfg->vpd.vpd_ident[i] = '\0'; + state = 0; + } + break; + + case 2: /* VPD-R Keyword Header */ + if (off == alloc) { + cfg->vpd.vpd_ros = reallocf(cfg->vpd.vpd_ros, + (alloc *= 2) * sizeof(*cfg->vpd.vpd_ros), + M_DEVBUF, M_WAITOK | M_ZERO); + } + cfg->vpd.vpd_ros[off].keyword[0] = byte; + if (vpd_nextbyte(&vrs, &byte2)) { + state = -2; + break; + } + cfg->vpd.vpd_ros[off].keyword[1] = byte2; + if (vpd_nextbyte(&vrs, &byte2)) { + state = -2; + break; + } + dflen = byte2; + if (dflen == 0 && + strncmp(cfg->vpd.vpd_ros[off].keyword, "RV", + 2) == 0) { + /* + * if this happens, we can't trust the rest + * of the VPD. + */ + printf( + "pci%d:%d:%d:%d: bad keyword length: %d\n", + cfg->domain, cfg->bus, cfg->slot, + cfg->func, dflen); + cksumvalid = 0; + state = -1; + break; + } else if (dflen == 0) { + cfg->vpd.vpd_ros[off].value = malloc(1 * + sizeof(*cfg->vpd.vpd_ros[off].value), + M_DEVBUF, M_WAITOK); + cfg->vpd.vpd_ros[off].value[0] = '\x00'; + } else + cfg->vpd.vpd_ros[off].value = malloc( + (dflen + 1) * + sizeof(*cfg->vpd.vpd_ros[off].value), + M_DEVBUF, M_WAITOK); + remain -= 3; + i = 0; + /* keep in sync w/ state 3's transistions */ + if (dflen == 0 && remain == 0) + state = 0; + else if (dflen == 0) + state = 2; + else + state = 3; + break; + + case 3: /* VPD-R Keyword Value */ + cfg->vpd.vpd_ros[off].value[i++] = byte; + if (strncmp(cfg->vpd.vpd_ros[off].keyword, + "RV", 2) == 0 && cksumvalid == -1) { + if (vrs.cksum == 0) + cksumvalid = 1; + else { + if (bootverbose) + printf( + "pci%d:%d:%d:%d: bad VPD cksum, remain %hhu\n", + cfg->domain, cfg->bus, + cfg->slot, cfg->func, + vrs.cksum); + cksumvalid = 0; + state = -1; + break; + } + } + dflen--; + remain--; + /* keep in sync w/ state 2's transistions */ + if (dflen == 0) + cfg->vpd.vpd_ros[off++].value[i++] = '\0'; + if (dflen == 0 && remain == 0) { + cfg->vpd.vpd_rocnt = off; + cfg->vpd.vpd_ros = reallocf(cfg->vpd.vpd_ros, + off * sizeof(*cfg->vpd.vpd_ros), + M_DEVBUF, M_WAITOK | M_ZERO); + state = 0; + } else if (dflen == 0) + state = 2; + break; + + case 4: + remain--; + if (remain == 0) + state = 0; + break; + + case 5: /* VPD-W Keyword Header */ + if (off == alloc) { + cfg->vpd.vpd_w = reallocf(cfg->vpd.vpd_w, + (alloc *= 2) * sizeof(*cfg->vpd.vpd_w), + M_DEVBUF, M_WAITOK | M_ZERO); + } + cfg->vpd.vpd_w[off].keyword[0] = byte; + if (vpd_nextbyte(&vrs, &byte2)) { + state = -2; + break; + } + cfg->vpd.vpd_w[off].keyword[1] = byte2; + if (vpd_nextbyte(&vrs, &byte2)) { + state = -2; + break; + } + cfg->vpd.vpd_w[off].len = dflen = byte2; + cfg->vpd.vpd_w[off].start = vrs.off - vrs.bytesinval; + cfg->vpd.vpd_w[off].value = malloc((dflen + 1) * + sizeof(*cfg->vpd.vpd_w[off].value), + M_DEVBUF, M_WAITOK); + remain -= 3; + i = 0; + /* keep in sync w/ state 6's transistions */ + if (dflen == 0 && remain == 0) + state = 0; + else if (dflen == 0) + state = 5; + else + state = 6; + break; + + case 6: /* VPD-W Keyword Value */ + cfg->vpd.vpd_w[off].value[i++] = byte; + dflen--; + remain--; + /* keep in sync w/ state 5's transistions */ + if (dflen == 0) + cfg->vpd.vpd_w[off++].value[i++] = '\0'; + if (dflen == 0 && remain == 0) { + cfg->vpd.vpd_wcnt = off; + cfg->vpd.vpd_w = reallocf(cfg->vpd.vpd_w, + off * sizeof(*cfg->vpd.vpd_w), + M_DEVBUF, M_WAITOK | M_ZERO); + state = 0; + } else if (dflen == 0) + state = 5; + break; + + default: + printf("pci%d:%d:%d:%d: invalid state: %d\n", + cfg->domain, cfg->bus, cfg->slot, cfg->func, + state); + state = -1; + break; + } + } + + if (cksumvalid == 0 || state < -1) { + /* read-only data bad, clean up */ + if (cfg->vpd.vpd_ros != NULL) { + for (off = 0; cfg->vpd.vpd_ros[off].value; off++) + free(cfg->vpd.vpd_ros[off].value, M_DEVBUF); + free(cfg->vpd.vpd_ros, M_DEVBUF); + cfg->vpd.vpd_ros = NULL; + } + } + if (state < -1) { + /* I/O error, clean up */ + printf("pci%d:%d:%d:%d: failed to read VPD data.\n", + cfg->domain, cfg->bus, cfg->slot, cfg->func); + if (cfg->vpd.vpd_ident != NULL) { + free(cfg->vpd.vpd_ident, M_DEVBUF); + cfg->vpd.vpd_ident = NULL; + } + if (cfg->vpd.vpd_w != NULL) { + for (off = 0; cfg->vpd.vpd_w[off].value; off++) + free(cfg->vpd.vpd_w[off].value, M_DEVBUF); + free(cfg->vpd.vpd_w, M_DEVBUF); + cfg->vpd.vpd_w = NULL; + } + } + cfg->vpd.vpd_cached = 1; +#undef REG +#undef WREG +} + +int +pci_get_vpd_ident_method(device_t dev, device_t child, const char **identptr) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + + if (!cfg->vpd.vpd_cached && cfg->vpd.vpd_reg != 0) + pci_read_vpd(device_get_parent(dev), cfg); + + *identptr = cfg->vpd.vpd_ident; + + if (*identptr == NULL) + return (ENXIO); + + return (0); +} + +int +pci_get_vpd_readonly_method(device_t dev, device_t child, const char *kw, + const char **vptr) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + int i; + + if (!cfg->vpd.vpd_cached && cfg->vpd.vpd_reg != 0) + pci_read_vpd(device_get_parent(dev), cfg); + + for (i = 0; i < cfg->vpd.vpd_rocnt; i++) + if (memcmp(kw, cfg->vpd.vpd_ros[i].keyword, + sizeof(cfg->vpd.vpd_ros[i].keyword)) == 0) { + *vptr = cfg->vpd.vpd_ros[i].value; + } + + if (i != cfg->vpd.vpd_rocnt) + return (0); + + *vptr = NULL; + return (ENXIO); +} + +/* + * Find the requested extended capability and return the offset in + * configuration space via the pointer provided. The function returns + * 0 on success and error code otherwise. + */ +int +pci_find_extcap_method(device_t dev, device_t child, int capability, + int *capreg) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + u_int32_t status; + u_int8_t ptr; + + /* + * Check the CAP_LIST bit of the PCI status register first. + */ + status = pci_read_config(child, PCIR_STATUS, 2); + if (!(status & PCIM_STATUS_CAPPRESENT)) + return (ENXIO); + + /* + * Determine the start pointer of the capabilities list. + */ + switch (cfg->hdrtype & PCIM_HDRTYPE) { + case 0: + case 1: + ptr = PCIR_CAP_PTR; + break; + case 2: + ptr = PCIR_CAP_PTR_2; + break; + default: + /* XXX: panic? */ + return (ENXIO); /* no extended capabilities support */ + } + ptr = pci_read_config(child, ptr, 1); + + /* + * Traverse the capabilities list. + */ + while (ptr != 0) { + if (pci_read_config(child, ptr + PCICAP_ID, 1) == capability) { + if (capreg != NULL) + *capreg = ptr; + return (0); + } + ptr = pci_read_config(child, ptr + PCICAP_NEXTPTR, 1); + } + + return (ENOENT); +} + +/* + * Support for MSI-X message interrupts. + */ +void +pci_enable_msix(device_t dev, u_int index, uint64_t address, uint32_t data) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + struct pcicfg_msix *msix = &dinfo->cfg.msix; + uint32_t offset; + + KASSERT(msix->msix_table_len > index, ("bogus index")); + offset = msix->msix_table_offset + index * 16; + bus_write_4(msix->msix_table_res, offset, address & 0xffffffff); + bus_write_4(msix->msix_table_res, offset + 4, address >> 32); + bus_write_4(msix->msix_table_res, offset + 8, data); + + /* Enable MSI -> HT mapping. */ + pci_ht_map_msi(dev, address); +} + +void +pci_mask_msix(device_t dev, u_int index) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + struct pcicfg_msix *msix = &dinfo->cfg.msix; + uint32_t offset, val; + + KASSERT(msix->msix_msgnum > index, ("bogus index")); + offset = msix->msix_table_offset + index * 16 + 12; + val = bus_read_4(msix->msix_table_res, offset); + if (!(val & PCIM_MSIX_VCTRL_MASK)) { + val |= PCIM_MSIX_VCTRL_MASK; + bus_write_4(msix->msix_table_res, offset, val); + } +} + +void +pci_unmask_msix(device_t dev, u_int index) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + struct pcicfg_msix *msix = &dinfo->cfg.msix; + uint32_t offset, val; + + KASSERT(msix->msix_table_len > index, ("bogus index")); + offset = msix->msix_table_offset + index * 16 + 12; + val = bus_read_4(msix->msix_table_res, offset); + if (val & PCIM_MSIX_VCTRL_MASK) { + val &= ~PCIM_MSIX_VCTRL_MASK; + bus_write_4(msix->msix_table_res, offset, val); + } +} + +int +pci_pending_msix(device_t dev, u_int index) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + struct pcicfg_msix *msix = &dinfo->cfg.msix; + uint32_t offset, bit; + + KASSERT(msix->msix_table_len > index, ("bogus index")); + offset = msix->msix_pba_offset + (index / 32) * 4; + bit = 1 << index % 32; + return (bus_read_4(msix->msix_pba_res, offset) & bit); +} + +/* + * Restore MSI-X registers and table during resume. If MSI-X is + * enabled then walk the virtual table to restore the actual MSI-X + * table. + */ +static void +pci_resume_msix(device_t dev) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + struct pcicfg_msix *msix = &dinfo->cfg.msix; + struct msix_table_entry *mte; + struct msix_vector *mv; + int i; + + if (msix->msix_alloc > 0) { + /* First, mask all vectors. */ + for (i = 0; i < msix->msix_msgnum; i++) + pci_mask_msix(dev, i); + + /* Second, program any messages with at least one handler. */ + for (i = 0; i < msix->msix_table_len; i++) { + mte = &msix->msix_table[i]; + if (mte->mte_vector == 0 || mte->mte_handlers == 0) + continue; + mv = &msix->msix_vectors[mte->mte_vector - 1]; + pci_enable_msix(dev, i, mv->mv_address, mv->mv_data); + pci_unmask_msix(dev, i); + } + } + pci_write_config(dev, msix->msix_location + PCIR_MSIX_CTRL, + msix->msix_ctrl, 2); +} + +/* + * Attempt to allocate *count MSI-X messages. The actual number allocated is + * returned in *count. After this function returns, each message will be + * available to the driver as SYS_RES_IRQ resources starting at rid 1. + */ +int +pci_alloc_msix_method(device_t dev, device_t child, int *count) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + struct resource_list_entry *rle; + int actual, error, i, irq, max; + + /* Don't let count == 0 get us into trouble. */ + if (*count == 0) + return (EINVAL); + + /* If rid 0 is allocated, then fail. */ + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, 0); + if (rle != NULL && rle->res != NULL) + return (ENXIO); + + /* Already have allocated messages? */ + if (cfg->msi.msi_alloc != 0 || cfg->msix.msix_alloc != 0) + return (ENXIO); + + /* If MSI is blacklisted for this system, fail. */ + if (pci_msi_blacklisted()) + return (ENXIO); + + /* MSI-X capability present? */ + if (cfg->msix.msix_location == 0 || !pci_do_msix) + return (ENODEV); + + /* Make sure the appropriate BARs are mapped. */ + rle = resource_list_find(&dinfo->resources, SYS_RES_MEMORY, + cfg->msix.msix_table_bar); + if (rle == NULL || rle->res == NULL || + !(rman_get_flags(rle->res) & RF_ACTIVE)) + return (ENXIO); + cfg->msix.msix_table_res = rle->res; + if (cfg->msix.msix_pba_bar != cfg->msix.msix_table_bar) { + rle = resource_list_find(&dinfo->resources, SYS_RES_MEMORY, + cfg->msix.msix_pba_bar); + if (rle == NULL || rle->res == NULL || + !(rman_get_flags(rle->res) & RF_ACTIVE)) + return (ENXIO); + } + cfg->msix.msix_pba_res = rle->res; + + if (bootverbose) + device_printf(child, + "attempting to allocate %d MSI-X vectors (%d supported)\n", + *count, cfg->msix.msix_msgnum); + max = min(*count, cfg->msix.msix_msgnum); + for (i = 0; i < max; i++) { + /* Allocate a message. */ + error = PCIB_ALLOC_MSIX(device_get_parent(dev), child, &irq); + if (error) + break; + resource_list_add(&dinfo->resources, SYS_RES_IRQ, i + 1, irq, + irq, 1); + } + actual = i; + + if (bootverbose) { + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, 1); + if (actual == 1) + device_printf(child, "using IRQ %lu for MSI-X\n", + rle->start); + else { + int run; + + /* + * Be fancy and try to print contiguous runs of + * IRQ values as ranges. 'irq' is the previous IRQ. + * 'run' is true if we are in a range. + */ + device_printf(child, "using IRQs %lu", rle->start); + irq = rle->start; + run = 0; + for (i = 1; i < actual; i++) { + rle = resource_list_find(&dinfo->resources, + SYS_RES_IRQ, i + 1); + + /* Still in a run? */ + if (rle->start == irq + 1) { + run = 1; + irq++; + continue; + } + + /* Finish previous range. */ + if (run) { + printf("-%d", irq); + run = 0; + } + + /* Start new range. */ + printf(",%lu", rle->start); + irq = rle->start; + } + + /* Unfinished range? */ + if (run) + printf("-%d", irq); + printf(" for MSI-X\n"); + } + } + + /* Mask all vectors. */ + for (i = 0; i < cfg->msix.msix_msgnum; i++) + pci_mask_msix(child, i); + + /* Allocate and initialize vector data and virtual table. */ + cfg->msix.msix_vectors = malloc(sizeof(struct msix_vector) * actual, + M_DEVBUF, M_WAITOK | M_ZERO); + cfg->msix.msix_table = malloc(sizeof(struct msix_table_entry) * actual, + M_DEVBUF, M_WAITOK | M_ZERO); + for (i = 0; i < actual; i++) { + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); + cfg->msix.msix_vectors[i].mv_irq = rle->start; + cfg->msix.msix_table[i].mte_vector = i + 1; + } + + /* Update control register to enable MSI-X. */ + cfg->msix.msix_ctrl |= PCIM_MSIXCTRL_MSIX_ENABLE; + pci_write_config(child, cfg->msix.msix_location + PCIR_MSIX_CTRL, + cfg->msix.msix_ctrl, 2); + + /* Update counts of alloc'd messages. */ + cfg->msix.msix_alloc = actual; + cfg->msix.msix_table_len = actual; + *count = actual; + return (0); +} + +/* + * By default, pci_alloc_msix() will assign the allocated IRQ + * resources consecutively to the first N messages in the MSI-X table. + * However, device drivers may want to use different layouts if they + * either receive fewer messages than they asked for, or they wish to + * populate the MSI-X table sparsely. This method allows the driver + * to specify what layout it wants. It must be called after a + * successful pci_alloc_msix() but before any of the associated + * SYS_RES_IRQ resources are allocated via bus_alloc_resource(). + * + * The 'vectors' array contains 'count' message vectors. The array + * maps directly to the MSI-X table in that index 0 in the array + * specifies the vector for the first message in the MSI-X table, etc. + * The vector value in each array index can either be 0 to indicate + * that no vector should be assigned to a message slot, or it can be a + * number from 1 to N (where N is the count returned from a + * succcessful call to pci_alloc_msix()) to indicate which message + * vector (IRQ) to be used for the corresponding message. + * + * On successful return, each message with a non-zero vector will have + * an associated SYS_RES_IRQ whose rid is equal to the array index + + * 1. Additionally, if any of the IRQs allocated via the previous + * call to pci_alloc_msix() are not used in the mapping, those IRQs + * will be freed back to the system automatically. + * + * For example, suppose a driver has a MSI-X table with 6 messages and + * asks for 6 messages, but pci_alloc_msix() only returns a count of + * 3. Call the three vectors allocated by pci_alloc_msix() A, B, and + * C. After the call to pci_alloc_msix(), the device will be setup to + * have an MSI-X table of ABC--- (where - means no vector assigned). + * If the driver ten passes a vector array of { 1, 0, 1, 2, 0, 2 }, + * then the MSI-X table will look like A-AB-B, and the 'C' vector will + * be freed back to the system. This device will also have valid + * SYS_RES_IRQ rids of 1, 3, 4, and 6. + * + * In any case, the SYS_RES_IRQ rid X will always map to the message + * at MSI-X table index X - 1 and will only be valid if a vector is + * assigned to that table entry. + */ +int +pci_remap_msix_method(device_t dev, device_t child, int count, + const u_int *vectors) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + struct pcicfg_msix *msix = &dinfo->cfg.msix; + struct resource_list_entry *rle; + int i, irq, j, *used; + + /* + * Have to have at least one message in the table but the + * table can't be bigger than the actual MSI-X table in the + * device. + */ + if (count == 0 || count > msix->msix_msgnum) + return (EINVAL); + + /* Sanity check the vectors. */ + for (i = 0; i < count; i++) + if (vectors[i] > msix->msix_alloc) + return (EINVAL); + + /* + * Make sure there aren't any holes in the vectors to be used. + * It's a big pain to support it, and it doesn't really make + * sense anyway. Also, at least one vector must be used. + */ + used = malloc(sizeof(int) * msix->msix_alloc, M_DEVBUF, M_WAITOK | + M_ZERO); + for (i = 0; i < count; i++) + if (vectors[i] != 0) + used[vectors[i] - 1] = 1; + for (i = 0; i < msix->msix_alloc - 1; i++) + if (used[i] == 0 && used[i + 1] == 1) { + free(used, M_DEVBUF); + return (EINVAL); + } + if (used[0] != 1) { + free(used, M_DEVBUF); + return (EINVAL); + } + + /* Make sure none of the resources are allocated. */ + for (i = 0; i < msix->msix_table_len; i++) { + if (msix->msix_table[i].mte_vector == 0) + continue; + if (msix->msix_table[i].mte_handlers > 0) + return (EBUSY); + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); + KASSERT(rle != NULL, ("missing resource")); + if (rle->res != NULL) + return (EBUSY); + } + + /* Free the existing resource list entries. */ + for (i = 0; i < msix->msix_table_len; i++) { + if (msix->msix_table[i].mte_vector == 0) + continue; + resource_list_delete(&dinfo->resources, SYS_RES_IRQ, i + 1); + } + + /* + * Build the new virtual table keeping track of which vectors are + * used. + */ + free(msix->msix_table, M_DEVBUF); + msix->msix_table = malloc(sizeof(struct msix_table_entry) * count, + M_DEVBUF, M_WAITOK | M_ZERO); + for (i = 0; i < count; i++) + msix->msix_table[i].mte_vector = vectors[i]; + msix->msix_table_len = count; + + /* Free any unused IRQs and resize the vectors array if necessary. */ + j = msix->msix_alloc - 1; + if (used[j] == 0) { + struct msix_vector *vec; + + while (used[j] == 0) { + PCIB_RELEASE_MSIX(device_get_parent(dev), child, + msix->msix_vectors[j].mv_irq); + j--; + } + vec = malloc(sizeof(struct msix_vector) * (j + 1), M_DEVBUF, + M_WAITOK); + bcopy(msix->msix_vectors, vec, sizeof(struct msix_vector) * + (j + 1)); + free(msix->msix_vectors, M_DEVBUF); + msix->msix_vectors = vec; + msix->msix_alloc = j + 1; + } + free(used, M_DEVBUF); + + /* Map the IRQs onto the rids. */ + for (i = 0; i < count; i++) { + if (vectors[i] == 0) + continue; + irq = msix->msix_vectors[vectors[i]].mv_irq; + resource_list_add(&dinfo->resources, SYS_RES_IRQ, i + 1, irq, + irq, 1); + } + + if (bootverbose) { + device_printf(child, "Remapped MSI-X IRQs as: "); + for (i = 0; i < count; i++) { + if (i != 0) + printf(", "); + if (vectors[i] == 0) + printf("---"); + else + printf("%d", + msix->msix_vectors[vectors[i]].mv_irq); + } + printf("\n"); + } + + return (0); +} + +static int +pci_release_msix(device_t dev, device_t child) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + struct pcicfg_msix *msix = &dinfo->cfg.msix; + struct resource_list_entry *rle; + int i; + + /* Do we have any messages to release? */ + if (msix->msix_alloc == 0) + return (ENODEV); + + /* Make sure none of the resources are allocated. */ + for (i = 0; i < msix->msix_table_len; i++) { + if (msix->msix_table[i].mte_vector == 0) + continue; + if (msix->msix_table[i].mte_handlers > 0) + return (EBUSY); + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); + KASSERT(rle != NULL, ("missing resource")); + if (rle->res != NULL) + return (EBUSY); + } + + /* Update control register to disable MSI-X. */ + msix->msix_ctrl &= ~PCIM_MSIXCTRL_MSIX_ENABLE; + pci_write_config(child, msix->msix_location + PCIR_MSIX_CTRL, + msix->msix_ctrl, 2); + + /* Free the resource list entries. */ + for (i = 0; i < msix->msix_table_len; i++) { + if (msix->msix_table[i].mte_vector == 0) + continue; + resource_list_delete(&dinfo->resources, SYS_RES_IRQ, i + 1); + } + free(msix->msix_table, M_DEVBUF); + msix->msix_table_len = 0; + + /* Release the IRQs. */ + for (i = 0; i < msix->msix_alloc; i++) + PCIB_RELEASE_MSIX(device_get_parent(dev), child, + msix->msix_vectors[i].mv_irq); + free(msix->msix_vectors, M_DEVBUF); + msix->msix_alloc = 0; + return (0); +} + +/* + * Return the max supported MSI-X messages this device supports. + * Basically, assuming the MD code can alloc messages, this function + * should return the maximum value that pci_alloc_msix() can return. + * Thus, it is subject to the tunables, etc. + */ +int +pci_msix_count_method(device_t dev, device_t child) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + struct pcicfg_msix *msix = &dinfo->cfg.msix; + + if (pci_do_msix && msix->msix_location != 0) + return (msix->msix_msgnum); + return (0); +} + +/* + * HyperTransport MSI mapping control + */ +void +pci_ht_map_msi(device_t dev, uint64_t addr) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + struct pcicfg_ht *ht = &dinfo->cfg.ht; + + if (!ht->ht_msimap) + return; + + if (addr && !(ht->ht_msictrl & PCIM_HTCMD_MSI_ENABLE) && + ht->ht_msiaddr >> 20 == addr >> 20) { + /* Enable MSI -> HT mapping. */ + ht->ht_msictrl |= PCIM_HTCMD_MSI_ENABLE; + pci_write_config(dev, ht->ht_msimap + PCIR_HT_COMMAND, + ht->ht_msictrl, 2); + } + + if (!addr && ht->ht_msictrl & PCIM_HTCMD_MSI_ENABLE) { + /* Disable MSI -> HT mapping. */ + ht->ht_msictrl &= ~PCIM_HTCMD_MSI_ENABLE; + pci_write_config(dev, ht->ht_msimap + PCIR_HT_COMMAND, + ht->ht_msictrl, 2); + } +} + +int +pci_get_max_read_req(device_t dev) +{ + int cap; + uint16_t val; + + if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0) + return (0); + val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2); + val &= PCIM_EXP_CTL_MAX_READ_REQUEST; + val >>= 12; + return (1 << (val + 7)); +} + +int +pci_set_max_read_req(device_t dev, int size) +{ + int cap; + uint16_t val; + + if (pci_find_extcap(dev, PCIY_EXPRESS, &cap) != 0) + return (0); + if (size < 128) + size = 128; + if (size > 4096) + size = 4096; + size = (1 << (fls(size) - 1)); + val = pci_read_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, 2); + val &= ~PCIM_EXP_CTL_MAX_READ_REQUEST; + val |= (fls(size) - 8) << 12; + pci_write_config(dev, cap + PCIR_EXPRESS_DEVICE_CTL, val, 2); + return (size); +} + +/* + * Support for MSI message signalled interrupts. + */ +void +pci_enable_msi(device_t dev, uint64_t address, uint16_t data) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + struct pcicfg_msi *msi = &dinfo->cfg.msi; + + /* Write data and address values. */ + pci_write_config(dev, msi->msi_location + PCIR_MSI_ADDR, + address & 0xffffffff, 4); + if (msi->msi_ctrl & PCIM_MSICTRL_64BIT) { + pci_write_config(dev, msi->msi_location + PCIR_MSI_ADDR_HIGH, + address >> 32, 4); + pci_write_config(dev, msi->msi_location + PCIR_MSI_DATA_64BIT, + data, 2); + } else + pci_write_config(dev, msi->msi_location + PCIR_MSI_DATA, data, + 2); + + /* Enable MSI in the control register. */ + msi->msi_ctrl |= PCIM_MSICTRL_MSI_ENABLE; + pci_write_config(dev, msi->msi_location + PCIR_MSI_CTRL, msi->msi_ctrl, + 2); + + /* Enable MSI -> HT mapping. */ + pci_ht_map_msi(dev, address); +} + +void +pci_disable_msi(device_t dev) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + struct pcicfg_msi *msi = &dinfo->cfg.msi; + + /* Disable MSI -> HT mapping. */ + pci_ht_map_msi(dev, 0); + + /* Disable MSI in the control register. */ + msi->msi_ctrl &= ~PCIM_MSICTRL_MSI_ENABLE; + pci_write_config(dev, msi->msi_location + PCIR_MSI_CTRL, msi->msi_ctrl, + 2); +} + +/* + * Restore MSI registers during resume. If MSI is enabled then + * restore the data and address registers in addition to the control + * register. + */ +static void +pci_resume_msi(device_t dev) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + struct pcicfg_msi *msi = &dinfo->cfg.msi; + uint64_t address; + uint16_t data; + + if (msi->msi_ctrl & PCIM_MSICTRL_MSI_ENABLE) { + address = msi->msi_addr; + data = msi->msi_data; + pci_write_config(dev, msi->msi_location + PCIR_MSI_ADDR, + address & 0xffffffff, 4); + if (msi->msi_ctrl & PCIM_MSICTRL_64BIT) { + pci_write_config(dev, msi->msi_location + + PCIR_MSI_ADDR_HIGH, address >> 32, 4); + pci_write_config(dev, msi->msi_location + + PCIR_MSI_DATA_64BIT, data, 2); + } else + pci_write_config(dev, msi->msi_location + PCIR_MSI_DATA, + data, 2); + } + pci_write_config(dev, msi->msi_location + PCIR_MSI_CTRL, msi->msi_ctrl, + 2); +} + +static int +pci_remap_intr_method(device_t bus, device_t dev, u_int irq) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + pcicfgregs *cfg = &dinfo->cfg; + struct resource_list_entry *rle; + struct msix_table_entry *mte; + struct msix_vector *mv; + uint64_t addr; + uint32_t data; + int error, i, j; + + /* + * Handle MSI first. We try to find this IRQ among our list + * of MSI IRQs. If we find it, we request updated address and + * data registers and apply the results. + */ + if (cfg->msi.msi_alloc > 0) { + + /* If we don't have any active handlers, nothing to do. */ + if (cfg->msi.msi_handlers == 0) + return (0); + for (i = 0; i < cfg->msi.msi_alloc; i++) { + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, + i + 1); + if (rle->start == irq) { + error = PCIB_MAP_MSI(device_get_parent(bus), + dev, irq, &addr, &data); + if (error) + return (error); + pci_disable_msi(dev); + dinfo->cfg.msi.msi_addr = addr; + dinfo->cfg.msi.msi_data = data; + pci_enable_msi(dev, addr, data); + return (0); + } + } + return (ENOENT); + } + + /* + * For MSI-X, we check to see if we have this IRQ. If we do, + * we request the updated mapping info. If that works, we go + * through all the slots that use this IRQ and update them. + */ + if (cfg->msix.msix_alloc > 0) { + for (i = 0; i < cfg->msix.msix_alloc; i++) { + mv = &cfg->msix.msix_vectors[i]; + if (mv->mv_irq == irq) { + error = PCIB_MAP_MSI(device_get_parent(bus), + dev, irq, &addr, &data); + if (error) + return (error); + mv->mv_address = addr; + mv->mv_data = data; + for (j = 0; j < cfg->msix.msix_table_len; j++) { + mte = &cfg->msix.msix_table[j]; + if (mte->mte_vector != i + 1) + continue; + if (mte->mte_handlers == 0) + continue; + pci_mask_msix(dev, j); + pci_enable_msix(dev, j, addr, data); + pci_unmask_msix(dev, j); + } + } + } + return (ENOENT); + } + + return (ENOENT); +} + +/* + * Returns true if the specified device is blacklisted because MSI + * doesn't work. + */ +int +pci_msi_device_blacklisted(device_t dev) +{ + struct pci_quirk *q; + + if (!pci_honor_msi_blacklist) + return (0); + + for (q = &pci_quirks[0]; q->devid; q++) { + if (q->devid == pci_get_devid(dev) && + q->type == PCI_QUIRK_DISABLE_MSI) + return (1); + } + return (0); +} + +/* + * Returns true if a specified chipset supports MSI when it is + * emulated hardware in a virtual machine. + */ +static int +pci_msi_vm_chipset(device_t dev) +{ + struct pci_quirk *q; + + for (q = &pci_quirks[0]; q->devid; q++) { + if (q->devid == pci_get_devid(dev) && + q->type == PCI_QUIRK_ENABLE_MSI_VM) + return (1); + } + return (0); +} + +/* + * Determine if MSI is blacklisted globally on this sytem. Currently, + * we just check for blacklisted chipsets as represented by the + * host-PCI bridge at device 0:0:0. In the future, it may become + * necessary to check other system attributes, such as the kenv values + * that give the motherboard manufacturer and model number. + */ +static int +pci_msi_blacklisted(void) +{ + device_t dev; + + if (!pci_honor_msi_blacklist) + return (0); + + /* Blacklist all non-PCI-express and non-PCI-X chipsets. */ + if (!(pcie_chipset || pcix_chipset)) { + if (vm_guest != VM_GUEST_NO) { + dev = pci_find_bsf(0, 0, 0); + if (dev != NULL) + return (pci_msi_vm_chipset(dev) == 0); + } + return (1); + } + + dev = pci_find_bsf(0, 0, 0); + if (dev != NULL) + return (pci_msi_device_blacklisted(dev)); + return (0); +} + +/* + * Attempt to allocate *count MSI messages. The actual number allocated is + * returned in *count. After this function returns, each message will be + * available to the driver as SYS_RES_IRQ resources starting at a rid 1. + */ +int +pci_alloc_msi_method(device_t dev, device_t child, int *count) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + struct resource_list_entry *rle; + int actual, error, i, irqs[32]; + uint16_t ctrl; + + /* Don't let count == 0 get us into trouble. */ + if (*count == 0) + return (EINVAL); + + /* If rid 0 is allocated, then fail. */ + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, 0); + if (rle != NULL && rle->res != NULL) + return (ENXIO); + + /* Already have allocated messages? */ + if (cfg->msi.msi_alloc != 0 || cfg->msix.msix_alloc != 0) + return (ENXIO); + + /* If MSI is blacklisted for this system, fail. */ + if (pci_msi_blacklisted()) + return (ENXIO); + + /* MSI capability present? */ + if (cfg->msi.msi_location == 0 || !pci_do_msi) + return (ENODEV); + + if (bootverbose) + device_printf(child, + "attempting to allocate %d MSI vectors (%d supported)\n", + *count, cfg->msi.msi_msgnum); + + /* Don't ask for more than the device supports. */ + actual = min(*count, cfg->msi.msi_msgnum); + + /* Don't ask for more than 32 messages. */ + actual = min(actual, 32); + + /* MSI requires power of 2 number of messages. */ + if (!powerof2(actual)) + return (EINVAL); + + for (;;) { + /* Try to allocate N messages. */ + error = PCIB_ALLOC_MSI(device_get_parent(dev), child, actual, + cfg->msi.msi_msgnum, irqs); + if (error == 0) + break; + if (actual == 1) + return (error); + + /* Try N / 2. */ + actual >>= 1; + } + + /* + * We now have N actual messages mapped onto SYS_RES_IRQ + * resources in the irqs[] array, so add new resources + * starting at rid 1. + */ + for (i = 0; i < actual; i++) + resource_list_add(&dinfo->resources, SYS_RES_IRQ, i + 1, + irqs[i], irqs[i], 1); + + if (bootverbose) { + if (actual == 1) + device_printf(child, "using IRQ %d for MSI\n", irqs[0]); + else { + int run; + + /* + * Be fancy and try to print contiguous runs + * of IRQ values as ranges. 'run' is true if + * we are in a range. + */ + device_printf(child, "using IRQs %d", irqs[0]); + run = 0; + for (i = 1; i < actual; i++) { + + /* Still in a run? */ + if (irqs[i] == irqs[i - 1] + 1) { + run = 1; + continue; + } + + /* Finish previous range. */ + if (run) { + printf("-%d", irqs[i - 1]); + run = 0; + } + + /* Start new range. */ + printf(",%d", irqs[i]); + } + + /* Unfinished range? */ + if (run) + printf("-%d", irqs[actual - 1]); + printf(" for MSI\n"); + } + } + + /* Update control register with actual count. */ + ctrl = cfg->msi.msi_ctrl; + ctrl &= ~PCIM_MSICTRL_MME_MASK; + ctrl |= (ffs(actual) - 1) << 4; + cfg->msi.msi_ctrl = ctrl; + pci_write_config(child, cfg->msi.msi_location + PCIR_MSI_CTRL, ctrl, 2); + + /* Update counts of alloc'd messages. */ + cfg->msi.msi_alloc = actual; + cfg->msi.msi_handlers = 0; + *count = actual; + return (0); +} + +/* Release the MSI messages associated with this device. */ +int +pci_release_msi_method(device_t dev, device_t child) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + struct pcicfg_msi *msi = &dinfo->cfg.msi; + struct resource_list_entry *rle; + int error, i, irqs[32]; + + /* Try MSI-X first. */ + error = pci_release_msix(dev, child); + if (error != ENODEV) + return (error); + + /* Do we have any messages to release? */ + if (msi->msi_alloc == 0) + return (ENODEV); + KASSERT(msi->msi_alloc <= 32, ("more than 32 alloc'd messages")); + + /* Make sure none of the resources are allocated. */ + if (msi->msi_handlers > 0) + return (EBUSY); + for (i = 0; i < msi->msi_alloc; i++) { + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, i + 1); + KASSERT(rle != NULL, ("missing MSI resource")); + if (rle->res != NULL) + return (EBUSY); + irqs[i] = rle->start; + } + + /* Update control register with 0 count. */ + KASSERT(!(msi->msi_ctrl & PCIM_MSICTRL_MSI_ENABLE), + ("%s: MSI still enabled", __func__)); + msi->msi_ctrl &= ~PCIM_MSICTRL_MME_MASK; + pci_write_config(child, msi->msi_location + PCIR_MSI_CTRL, + msi->msi_ctrl, 2); + + /* Release the messages. */ + PCIB_RELEASE_MSI(device_get_parent(dev), child, msi->msi_alloc, irqs); + for (i = 0; i < msi->msi_alloc; i++) + resource_list_delete(&dinfo->resources, SYS_RES_IRQ, i + 1); + + /* Update alloc count. */ + msi->msi_alloc = 0; + msi->msi_addr = 0; + msi->msi_data = 0; + return (0); +} + +/* + * Return the max supported MSI messages this device supports. + * Basically, assuming the MD code can alloc messages, this function + * should return the maximum value that pci_alloc_msi() can return. + * Thus, it is subject to the tunables, etc. + */ +int +pci_msi_count_method(device_t dev, device_t child) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + struct pcicfg_msi *msi = &dinfo->cfg.msi; + + if (pci_do_msi && msi->msi_location != 0) + return (msi->msi_msgnum); + return (0); +} + +/* free pcicfgregs structure and all depending data structures */ + +int +pci_freecfg(struct pci_devinfo *dinfo) +{ + struct devlist *devlist_head; + int i; + + devlist_head = &pci_devq; + + if (dinfo->cfg.vpd.vpd_reg) { + free(dinfo->cfg.vpd.vpd_ident, M_DEVBUF); + for (i = 0; i < dinfo->cfg.vpd.vpd_rocnt; i++) + free(dinfo->cfg.vpd.vpd_ros[i].value, M_DEVBUF); + free(dinfo->cfg.vpd.vpd_ros, M_DEVBUF); + for (i = 0; i < dinfo->cfg.vpd.vpd_wcnt; i++) + free(dinfo->cfg.vpd.vpd_w[i].value, M_DEVBUF); + free(dinfo->cfg.vpd.vpd_w, M_DEVBUF); + } + STAILQ_REMOVE(devlist_head, dinfo, pci_devinfo, pci_links); + free(dinfo, M_DEVBUF); + + /* increment the generation count */ + pci_generation++; + + /* we're losing one device */ + pci_numdevs--; + return (0); +} + +/* + * PCI power manangement + */ +int +pci_set_powerstate_method(device_t dev, device_t child, int state) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + uint16_t status; + int result, oldstate, highest, delay; + + if (cfg->pp.pp_cap == 0) + return (EOPNOTSUPP); + + /* + * Optimize a no state change request away. While it would be OK to + * write to the hardware in theory, some devices have shown odd + * behavior when going from D3 -> D3. + */ + oldstate = pci_get_powerstate(child); + if (oldstate == state) + return (0); + + /* + * The PCI power management specification states that after a state + * transition between PCI power states, system software must + * guarantee a minimal delay before the function accesses the device. + * Compute the worst case delay that we need to guarantee before we + * access the device. Many devices will be responsive much more + * quickly than this delay, but there are some that don't respond + * instantly to state changes. Transitions to/from D3 state require + * 10ms, while D2 requires 200us, and D0/1 require none. The delay + * is done below with DELAY rather than a sleeper function because + * this function can be called from contexts where we cannot sleep. + */ + highest = (oldstate > state) ? oldstate : state; + if (highest == PCI_POWERSTATE_D3) + delay = 10000; + else if (highest == PCI_POWERSTATE_D2) + delay = 200; + else + delay = 0; + status = PCI_READ_CONFIG(dev, child, cfg->pp.pp_status, 2) + & ~PCIM_PSTAT_DMASK; + result = 0; + switch (state) { + case PCI_POWERSTATE_D0: + status |= PCIM_PSTAT_D0; + break; + case PCI_POWERSTATE_D1: + if ((cfg->pp.pp_cap & PCIM_PCAP_D1SUPP) == 0) + return (EOPNOTSUPP); + status |= PCIM_PSTAT_D1; + break; + case PCI_POWERSTATE_D2: + if ((cfg->pp.pp_cap & PCIM_PCAP_D2SUPP) == 0) + return (EOPNOTSUPP); + status |= PCIM_PSTAT_D2; + break; + case PCI_POWERSTATE_D3: + status |= PCIM_PSTAT_D3; + break; + default: + return (EINVAL); + } + + if (bootverbose) + pci_printf(cfg, "Transition from D%d to D%d\n", oldstate, + state); + + PCI_WRITE_CONFIG(dev, child, cfg->pp.pp_status, status, 2); + if (delay) + DELAY(delay); + return (0); +} + +int +pci_get_powerstate_method(device_t dev, device_t child) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + uint16_t status; + int result; + + if (cfg->pp.pp_cap != 0) { + status = PCI_READ_CONFIG(dev, child, cfg->pp.pp_status, 2); + switch (status & PCIM_PSTAT_DMASK) { + case PCIM_PSTAT_D0: + result = PCI_POWERSTATE_D0; + break; + case PCIM_PSTAT_D1: + result = PCI_POWERSTATE_D1; + break; + case PCIM_PSTAT_D2: + result = PCI_POWERSTATE_D2; + break; + case PCIM_PSTAT_D3: + result = PCI_POWERSTATE_D3; + break; + default: + result = PCI_POWERSTATE_UNKNOWN; + break; + } + } else { + /* No support, device is always at D0 */ + result = PCI_POWERSTATE_D0; + } + return (result); +} + +/* + * Some convenience functions for PCI device drivers. + */ + +static __inline void +pci_set_command_bit(device_t dev, device_t child, uint16_t bit) +{ + uint16_t command; + + command = PCI_READ_CONFIG(dev, child, PCIR_COMMAND, 2); + command |= bit; + PCI_WRITE_CONFIG(dev, child, PCIR_COMMAND, command, 2); +} + +static __inline void +pci_clear_command_bit(device_t dev, device_t child, uint16_t bit) +{ + uint16_t command; + + command = PCI_READ_CONFIG(dev, child, PCIR_COMMAND, 2); + command &= ~bit; + PCI_WRITE_CONFIG(dev, child, PCIR_COMMAND, command, 2); +} + +int +pci_enable_busmaster_method(device_t dev, device_t child) +{ + pci_set_command_bit(dev, child, PCIM_CMD_BUSMASTEREN); + return (0); +} + +int +pci_disable_busmaster_method(device_t dev, device_t child) +{ + pci_clear_command_bit(dev, child, PCIM_CMD_BUSMASTEREN); + return (0); +} + +int +pci_enable_io_method(device_t dev, device_t child, int space) +{ + uint16_t bit; + + switch(space) { + case SYS_RES_IOPORT: + bit = PCIM_CMD_PORTEN; + break; + case SYS_RES_MEMORY: + bit = PCIM_CMD_MEMEN; + break; + default: + return (EINVAL); + } + pci_set_command_bit(dev, child, bit); + return (0); +} + +int +pci_disable_io_method(device_t dev, device_t child, int space) +{ + uint16_t bit; + + switch(space) { + case SYS_RES_IOPORT: + bit = PCIM_CMD_PORTEN; + break; + case SYS_RES_MEMORY: + bit = PCIM_CMD_MEMEN; + break; + default: + return (EINVAL); + } + pci_clear_command_bit(dev, child, bit); + return (0); +} + +/* + * New style pci driver. Parent device is either a pci-host-bridge or a + * pci-pci-bridge. Both kinds are represented by instances of pcib. + */ + +void +pci_print_verbose(struct pci_devinfo *dinfo) +{ + + if (bootverbose) { + pcicfgregs *cfg = &dinfo->cfg; + + printf("found->\tvendor=0x%04x, dev=0x%04x, revid=0x%02x\n", + cfg->vendor, cfg->device, cfg->revid); + printf("\tdomain=%d, bus=%d, slot=%d, func=%d\n", + cfg->domain, cfg->bus, cfg->slot, cfg->func); + printf("\tclass=%02x-%02x-%02x, hdrtype=0x%02x, mfdev=%d\n", + cfg->baseclass, cfg->subclass, cfg->progif, cfg->hdrtype, + cfg->mfdev); + printf("\tcmdreg=0x%04x, statreg=0x%04x, cachelnsz=%d (dwords)\n", + cfg->cmdreg, cfg->statreg, cfg->cachelnsz); + printf("\tlattimer=0x%02x (%d ns), mingnt=0x%02x (%d ns), maxlat=0x%02x (%d ns)\n", + cfg->lattimer, cfg->lattimer * 30, cfg->mingnt, + cfg->mingnt * 250, cfg->maxlat, cfg->maxlat * 250); + if (cfg->intpin > 0) + printf("\tintpin=%c, irq=%d\n", + cfg->intpin +'a' -1, cfg->intline); + if (cfg->pp.pp_cap) { + uint16_t status; + + status = pci_read_config(cfg->dev, cfg->pp.pp_status, 2); + printf("\tpowerspec %d supports D0%s%s D3 current D%d\n", + cfg->pp.pp_cap & PCIM_PCAP_SPEC, + cfg->pp.pp_cap & PCIM_PCAP_D1SUPP ? " D1" : "", + cfg->pp.pp_cap & PCIM_PCAP_D2SUPP ? " D2" : "", + status & PCIM_PSTAT_DMASK); + } + if (cfg->msi.msi_location) { + int ctrl; + + ctrl = cfg->msi.msi_ctrl; + printf("\tMSI supports %d message%s%s%s\n", + cfg->msi.msi_msgnum, + (cfg->msi.msi_msgnum == 1) ? "" : "s", + (ctrl & PCIM_MSICTRL_64BIT) ? ", 64 bit" : "", + (ctrl & PCIM_MSICTRL_VECTOR) ? ", vector masks":""); + } + if (cfg->msix.msix_location) { + printf("\tMSI-X supports %d message%s ", + cfg->msix.msix_msgnum, + (cfg->msix.msix_msgnum == 1) ? "" : "s"); + if (cfg->msix.msix_table_bar == cfg->msix.msix_pba_bar) + printf("in map 0x%x\n", + cfg->msix.msix_table_bar); + else + printf("in maps 0x%x and 0x%x\n", + cfg->msix.msix_table_bar, + cfg->msix.msix_pba_bar); + } + } +} + +static int +pci_porten(device_t dev) +{ + return (pci_read_config(dev, PCIR_COMMAND, 2) & PCIM_CMD_PORTEN) != 0; +} + +static int +pci_memen(device_t dev) +{ + return (pci_read_config(dev, PCIR_COMMAND, 2) & PCIM_CMD_MEMEN) != 0; +} + +static void +pci_read_bar(device_t dev, int reg, pci_addr_t *mapp, pci_addr_t *testvalp) +{ + pci_addr_t map, testval; + int ln2range; + uint16_t cmd; + + map = pci_read_config(dev, reg, 4); + ln2range = pci_maprange(map); + if (ln2range == 64) + map |= (pci_addr_t)pci_read_config(dev, reg + 4, 4) << 32; + + /* + * Disable decoding via the command register before + * determining the BAR's length since we will be placing it in + * a weird state. + */ + cmd = pci_read_config(dev, PCIR_COMMAND, 2); + pci_write_config(dev, PCIR_COMMAND, + cmd & ~(PCI_BAR_MEM(map) ? PCIM_CMD_MEMEN : PCIM_CMD_PORTEN), 2); + + /* + * Determine the BAR's length by writing all 1's. The bottom + * log_2(size) bits of the BAR will stick as 0 when we read + * the value back. + */ + pci_write_config(dev, reg, 0xffffffff, 4); + testval = pci_read_config(dev, reg, 4); + if (ln2range == 64) { + pci_write_config(dev, reg + 4, 0xffffffff, 4); + testval |= (pci_addr_t)pci_read_config(dev, reg + 4, 4) << 32; + } + + /* + * Restore the original value of the BAR. We may have reprogrammed + * the BAR of the low-level console device and when booting verbose, + * we need the console device addressable. + */ + pci_write_config(dev, reg, map, 4); + if (ln2range == 64) + pci_write_config(dev, reg + 4, map >> 32, 4); + pci_write_config(dev, PCIR_COMMAND, cmd, 2); + + *mapp = map; + *testvalp = testval; +} + +static void +pci_write_bar(device_t dev, int reg, pci_addr_t base) +{ + pci_addr_t map; + int ln2range; + + map = pci_read_config(dev, reg, 4); + ln2range = pci_maprange(map); + pci_write_config(dev, reg, base, 4); + if (ln2range == 64) + pci_write_config(dev, reg + 4, base >> 32, 4); +} + +/* + * Add a resource based on a pci map register. Return 1 if the map + * register is a 32bit map register or 2 if it is a 64bit register. + */ +static int +pci_add_map(device_t bus, device_t dev, int reg, struct resource_list *rl, + int force, int prefetch) +{ + pci_addr_t base, map, testval; + pci_addr_t start, end, count; + int barlen, basezero, maprange, mapsize, type; + uint16_t cmd; + struct resource *res; + + pci_read_bar(dev, reg, &map, &testval); + if (PCI_BAR_MEM(map)) { + type = SYS_RES_MEMORY; + if (map & PCIM_BAR_MEM_PREFETCH) + prefetch = 1; + } else + type = SYS_RES_IOPORT; + mapsize = pci_mapsize(testval); + base = pci_mapbase(map); +#ifdef __PCI_BAR_ZERO_VALID + basezero = 0; +#else + basezero = base == 0; +#endif + maprange = pci_maprange(map); + barlen = maprange == 64 ? 2 : 1; + + /* + * For I/O registers, if bottom bit is set, and the next bit up + * isn't clear, we know we have a BAR that doesn't conform to the + * spec, so ignore it. Also, sanity check the size of the data + * areas to the type of memory involved. Memory must be at least + * 16 bytes in size, while I/O ranges must be at least 4. + */ + if (PCI_BAR_IO(testval) && (testval & PCIM_BAR_IO_RESERVED) != 0) + return (barlen); + if ((type == SYS_RES_MEMORY && mapsize < 4) || + (type == SYS_RES_IOPORT && mapsize < 2)) + return (barlen); + + if (bootverbose) { + printf("\tmap[%02x]: type %s, range %2d, base %#jx, size %2d", + reg, pci_maptype(map), maprange, (uintmax_t)base, mapsize); + if (type == SYS_RES_IOPORT && !pci_porten(dev)) + printf(", port disabled\n"); + else if (type == SYS_RES_MEMORY && !pci_memen(dev)) + printf(", memory disabled\n"); + else + printf(", enabled\n"); + } + + /* + * If base is 0, then we have problems if this architecture does + * not allow that. It is best to ignore such entries for the + * moment. These will be allocated later if the driver specifically + * requests them. However, some removable busses look better when + * all resources are allocated, so allow '0' to be overriden. + * + * Similarly treat maps whose values is the same as the test value + * read back. These maps have had all f's written to them by the + * BIOS in an attempt to disable the resources. + */ + if (!force && (basezero || map == testval)) + return (barlen); + if ((u_long)base != base) { + device_printf(bus, + "pci%d:%d:%d:%d bar %#x too many address bits", + pci_get_domain(dev), pci_get_bus(dev), pci_get_slot(dev), + pci_get_function(dev), reg); + return (barlen); + } + + /* + * This code theoretically does the right thing, but has + * undesirable side effects in some cases where peripherals + * respond oddly to having these bits enabled. Let the user + * be able to turn them off (since pci_enable_io_modes is 1 by + * default). + */ + if (pci_enable_io_modes) { + /* Turn on resources that have been left off by a lazy BIOS */ + if (type == SYS_RES_IOPORT && !pci_porten(dev)) { + cmd = pci_read_config(dev, PCIR_COMMAND, 2); + cmd |= PCIM_CMD_PORTEN; + pci_write_config(dev, PCIR_COMMAND, cmd, 2); + } + if (type == SYS_RES_MEMORY && !pci_memen(dev)) { + cmd = pci_read_config(dev, PCIR_COMMAND, 2); + cmd |= PCIM_CMD_MEMEN; + pci_write_config(dev, PCIR_COMMAND, cmd, 2); + } + } else { + if (type == SYS_RES_IOPORT && !pci_porten(dev)) + return (barlen); + if (type == SYS_RES_MEMORY && !pci_memen(dev)) + return (barlen); + } + + count = 1 << mapsize; + if (basezero || base == pci_mapbase(testval)) { + start = 0; /* Let the parent decide. */ + end = ~0ULL; + } else { + start = base; + end = base + (1 << mapsize) - 1; + } + resource_list_add(rl, type, reg, start, end, count); + + /* + * Try to allocate the resource for this BAR from our parent + * so that this resource range is already reserved. The + * driver for this device will later inherit this resource in + * pci_alloc_resource(). + */ + res = resource_list_alloc(rl, bus, dev, type, ®, start, end, count, + prefetch ? RF_PREFETCHABLE : 0); + if (res == NULL) { + /* + * If the allocation fails, clear the BAR and delete + * the resource list entry to force + * pci_alloc_resource() to allocate resources from the + * parent. + */ + resource_list_delete(rl, type, reg); + start = 0; + } else { + start = rman_get_start(res); + rman_set_device(res, bus); + } + pci_write_bar(dev, reg, start); + return (barlen); +} + +/* + * For ATA devices we need to decide early what addressing mode to use. + * Legacy demands that the primary and secondary ATA ports sits on the + * same addresses that old ISA hardware did. This dictates that we use + * those addresses and ignore the BAR's if we cannot set PCI native + * addressing mode. + */ +static void +pci_ata_maps(device_t bus, device_t dev, struct resource_list *rl, int force, + uint32_t prefetchmask) +{ + struct resource *r; + int rid, type, progif; +#if 0 + /* if this device supports PCI native addressing use it */ + progif = pci_read_config(dev, PCIR_PROGIF, 1); + if ((progif & 0x8a) == 0x8a) { + if (pci_mapbase(pci_read_config(dev, PCIR_BAR(0), 4)) && + pci_mapbase(pci_read_config(dev, PCIR_BAR(2), 4))) { + printf("Trying ATA native PCI addressing mode\n"); + pci_write_config(dev, PCIR_PROGIF, progif | 0x05, 1); + } + } +#endif + progif = pci_read_config(dev, PCIR_PROGIF, 1); + type = SYS_RES_IOPORT; + if (progif & PCIP_STORAGE_IDE_MODEPRIM) { + pci_add_map(bus, dev, PCIR_BAR(0), rl, force, + prefetchmask & (1 << 0)); + pci_add_map(bus, dev, PCIR_BAR(1), rl, force, + prefetchmask & (1 << 1)); + } else { + rid = PCIR_BAR(0); + resource_list_add(rl, type, rid, 0x1f0, 0x1f7, 8); + r = resource_list_alloc(rl, bus, dev, type, &rid, 0x1f0, 0x1f7, + 8, 0); + rman_set_device(r, bus); + rid = PCIR_BAR(1); + resource_list_add(rl, type, rid, 0x3f6, 0x3f6, 1); + r = resource_list_alloc(rl, bus, dev, type, &rid, 0x3f6, 0x3f6, + 1, 0); + rman_set_device(r, bus); + } + if (progif & PCIP_STORAGE_IDE_MODESEC) { + pci_add_map(bus, dev, PCIR_BAR(2), rl, force, + prefetchmask & (1 << 2)); + pci_add_map(bus, dev, PCIR_BAR(3), rl, force, + prefetchmask & (1 << 3)); + } else { + rid = PCIR_BAR(2); + resource_list_add(rl, type, rid, 0x170, 0x177, 8); + r = resource_list_alloc(rl, bus, dev, type, &rid, 0x170, 0x177, + 8, 0); + rman_set_device(r, bus); + rid = PCIR_BAR(3); + resource_list_add(rl, type, rid, 0x376, 0x376, 1); + r = resource_list_alloc(rl, bus, dev, type, &rid, 0x376, 0x376, + 1, 0); + rman_set_device(r, bus); + } + pci_add_map(bus, dev, PCIR_BAR(4), rl, force, + prefetchmask & (1 << 4)); + pci_add_map(bus, dev, PCIR_BAR(5), rl, force, + prefetchmask & (1 << 5)); +} + +static void +pci_assign_interrupt(device_t bus, device_t dev, int force_route) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + pcicfgregs *cfg = &dinfo->cfg; + char tunable_name[64]; + int irq; + + /* Has to have an intpin to have an interrupt. */ + if (cfg->intpin == 0) + return; + + /* Let the user override the IRQ with a tunable. */ + irq = PCI_INVALID_IRQ; +#ifndef __rtems__ + snprintf(tunable_name, sizeof(tunable_name), + "hw.pci%d.%d.%d.INT%c.irq", + cfg->domain, cfg->bus, cfg->slot, cfg->intpin + 'A' - 1); + if (TUNABLE_INT_FETCH(tunable_name, &irq) && (irq >= 255 || irq <= 0)) + irq = PCI_INVALID_IRQ; +#endif /* __rtems__ */ + + /* + * If we didn't get an IRQ via the tunable, then we either use the + * IRQ value in the intline register or we ask the bus to route an + * interrupt for us. If force_route is true, then we only use the + * value in the intline register if the bus was unable to assign an + * IRQ. + */ + if (!PCI_INTERRUPT_VALID(irq)) { + if (!PCI_INTERRUPT_VALID(cfg->intline) || force_route) + irq = PCI_ASSIGN_INTERRUPT(bus, dev); + if (!PCI_INTERRUPT_VALID(irq)) + irq = cfg->intline; + } + + /* If after all that we don't have an IRQ, just bail. */ + if (!PCI_INTERRUPT_VALID(irq)) + return; + + /* Update the config register if it changed. */ + if (irq != cfg->intline) { + cfg->intline = irq; + pci_write_config(dev, PCIR_INTLINE, irq, 1); + } + + /* Add this IRQ as rid 0 interrupt resource. */ + resource_list_add(&dinfo->resources, SYS_RES_IRQ, 0, irq, irq, 1); +} + +/* Perform early OHCI takeover from SMM. */ +static void +ohci_early_takeover(device_t self) +{ + struct resource *res; + uint32_t ctl; + int rid; + int i; + + rid = PCIR_BAR(0); + res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); + if (res == NULL) + return; + + ctl = bus_read_4(res, OHCI_CONTROL); + if (ctl & OHCI_IR) { + if (bootverbose) + printf("ohci early: " + "SMM active, request owner change\n"); + bus_write_4(res, OHCI_COMMAND_STATUS, OHCI_OCR); + for (i = 0; (i < 100) && (ctl & OHCI_IR); i++) { + DELAY(1000); + ctl = bus_read_4(res, OHCI_CONTROL); + } + if (ctl & OHCI_IR) { + if (bootverbose) + printf("ohci early: " + "SMM does not respond, resetting\n"); + bus_write_4(res, OHCI_CONTROL, OHCI_HCFS_RESET); + } + /* Disable interrupts */ + bus_write_4(res, OHCI_INTERRUPT_DISABLE, OHCI_ALL_INTRS); + } + + bus_release_resource(self, SYS_RES_MEMORY, rid, res); +} + +#ifndef __rtems__ +/* Perform early UHCI takeover from SMM. */ +static void +uhci_early_takeover(device_t self) +{ + struct resource *res; + int rid; + + /* + * Set the PIRQD enable bit and switch off all the others. We don't + * want legacy support to interfere with us XXX Does this also mean + * that the BIOS won't touch the keyboard anymore if it is connected + * to the ports of the root hub? + */ + pci_write_config(self, PCI_LEGSUP, PCI_LEGSUP_USBPIRQDEN, 2); + + /* Disable interrupts */ + rid = PCI_UHCI_BASE_REG; + res = bus_alloc_resource_any(self, SYS_RES_IOPORT, &rid, RF_ACTIVE); + if (res != NULL) { + bus_write_2(res, UHCI_INTR, 0); + bus_release_resource(self, SYS_RES_IOPORT, rid, res); + } +} +#endif /* __rtems__ */ + +/* Perform early EHCI takeover from SMM. */ +static void +ehci_early_takeover(device_t self) +{ + struct resource *res; + uint32_t cparams; + uint32_t eec; + uint8_t eecp; + uint8_t bios_sem; + uint8_t offs; + int rid; + int i; + + rid = PCIR_BAR(0); + res = bus_alloc_resource_any(self, SYS_RES_MEMORY, &rid, RF_ACTIVE); + if (res == NULL) + return; + + cparams = bus_read_4(res, EHCI_HCCPARAMS); + + /* Synchronise with the BIOS if it owns the controller. */ + for (eecp = EHCI_HCC_EECP(cparams); eecp != 0; + eecp = EHCI_EECP_NEXT(eec)) { + eec = pci_read_config(self, eecp, 4); + if (EHCI_EECP_ID(eec) != EHCI_EC_LEGSUP) { + continue; + } + bios_sem = pci_read_config(self, eecp + + EHCI_LEGSUP_BIOS_SEM, 1); + if (bios_sem == 0) { + continue; + } + if (bootverbose) + printf("ehci early: " + "SMM active, request owner change\n"); + + pci_write_config(self, eecp + EHCI_LEGSUP_OS_SEM, 1, 1); + + for (i = 0; (i < 100) && (bios_sem != 0); i++) { + DELAY(1000); + bios_sem = pci_read_config(self, eecp + + EHCI_LEGSUP_BIOS_SEM, 1); + } + + if (bios_sem != 0) { + if (bootverbose) + printf("ehci early: " + "SMM does not respond\n"); + } + /* Disable interrupts */ + offs = EHCI_CAPLENGTH(bus_read_4(res, EHCI_CAPLEN_HCIVERSION)); + bus_write_4(res, offs + EHCI_USBINTR, 0); + } + bus_release_resource(self, SYS_RES_MEMORY, rid, res); +} + +void +pci_add_resources(device_t bus, device_t dev, int force, uint32_t prefetchmask) +{ + struct pci_devinfo *dinfo = device_get_ivars(dev); + pcicfgregs *cfg = &dinfo->cfg; + struct resource_list *rl = &dinfo->resources; + struct pci_quirk *q; + int i; + + /* ATA devices needs special map treatment */ + if ((pci_get_class(dev) == PCIC_STORAGE) && + (pci_get_subclass(dev) == PCIS_STORAGE_IDE) && + ((pci_get_progif(dev) & PCIP_STORAGE_IDE_MASTERDEV) || + (!pci_read_config(dev, PCIR_BAR(0), 4) && + !pci_read_config(dev, PCIR_BAR(2), 4))) ) + pci_ata_maps(bus, dev, rl, force, prefetchmask); + else + for (i = 0; i < cfg->nummaps;) + i += pci_add_map(bus, dev, PCIR_BAR(i), rl, force, + prefetchmask & (1 << i)); + + /* + * Add additional, quirked resources. + */ + for (q = &pci_quirks[0]; q->devid; q++) { + if (q->devid == ((cfg->device << 16) | cfg->vendor) + && q->type == PCI_QUIRK_MAP_REG) + pci_add_map(bus, dev, q->arg1, rl, force, 0); + } + + if (cfg->intpin > 0 && PCI_INTERRUPT_VALID(cfg->intline)) { +#ifdef __PCI_REROUTE_INTERRUPT + /* + * Try to re-route interrupts. Sometimes the BIOS or + * firmware may leave bogus values in these registers. + * If the re-route fails, then just stick with what we + * have. + */ + pci_assign_interrupt(bus, dev, 1); +#else + pci_assign_interrupt(bus, dev, 0); +#endif + } + + if (pci_usb_takeover && pci_get_class(dev) == PCIC_SERIALBUS && + pci_get_subclass(dev) == PCIS_SERIALBUS_USB) { + if (pci_get_progif(dev) == PCIP_SERIALBUS_USB_EHCI) + ehci_early_takeover(dev); + else if (pci_get_progif(dev) == PCIP_SERIALBUS_USB_OHCI) + ohci_early_takeover(dev); +#ifndef __rtems__ + else if (pci_get_progif(dev) == PCIP_SERIALBUS_USB_UHCI) + uhci_early_takeover(dev); +#endif /* __rtems__ */ + } +} + +void +pci_add_children(device_t dev, int domain, int busno, size_t dinfo_size) +{ +#define REG(n, w) PCIB_READ_CONFIG(pcib, busno, s, f, n, w) + device_t pcib = device_get_parent(dev); + struct pci_devinfo *dinfo; + int maxslots; + int s, f, pcifunchigh; + uint8_t hdrtype; + + KASSERT(dinfo_size >= sizeof(struct pci_devinfo), + ("dinfo_size too small")); + maxslots = PCIB_MAXSLOTS(pcib); + for (s = 0; s <= maxslots; s++) { + pcifunchigh = 0; + f = 0; + DELAY(1); + hdrtype = REG(PCIR_HDRTYPE, 1); + if ((hdrtype & PCIM_HDRTYPE) > PCI_MAXHDRTYPE) + continue; + if (hdrtype & PCIM_MFDEV) + pcifunchigh = PCI_FUNCMAX; + for (f = 0; f <= pcifunchigh; f++) { + dinfo = pci_read_device(pcib, domain, busno, s, f, + dinfo_size); + if (dinfo != NULL) { + pci_add_child(dev, dinfo); + } + } + } +#undef REG +} + +void +pci_add_child(device_t bus, struct pci_devinfo *dinfo) +{ + dinfo->cfg.dev = device_add_child(bus, NULL, -1); + device_set_ivars(dinfo->cfg.dev, dinfo); + resource_list_init(&dinfo->resources); + pci_cfg_save(dinfo->cfg.dev, dinfo, 0); + pci_cfg_restore(dinfo->cfg.dev, dinfo); + pci_print_verbose(dinfo); + pci_add_resources(bus, dinfo->cfg.dev, 0, 0); +} + +static int +pci_probe(device_t dev) +{ + + device_set_desc(dev, "PCI bus"); + + /* Allow other subclasses to override this driver. */ + return (BUS_PROBE_GENERIC); +} + +static int +pci_attach(device_t dev) +{ + int busno, domain; + + /* + * Since there can be multiple independantly numbered PCI + * busses on systems with multiple PCI domains, we can't use + * the unit number to decide which bus we are probing. We ask + * the parent pcib what our domain and bus numbers are. + */ + domain = pcib_get_domain(dev); + busno = pcib_get_bus(dev); + if (bootverbose) + device_printf(dev, "domain=%d, physical bus=%d\n", + domain, busno); + pci_add_children(dev, domain, busno, sizeof(struct pci_devinfo)); + return (bus_generic_attach(dev)); +} + +int +pci_suspend(device_t dev) +{ + int dstate, error, i, numdevs; + device_t acpi_dev, child, *devlist; + struct pci_devinfo *dinfo; + + /* + * Save the PCI configuration space for each child and set the + * device in the appropriate power state for this sleep state. + */ + acpi_dev = NULL; + if (pci_do_power_resume) + acpi_dev = devclass_get_device(devclass_find("acpi"), 0); + if ((error = device_get_children(dev, &devlist, &numdevs)) != 0) + return (error); + for (i = 0; i < numdevs; i++) { + child = devlist[i]; + dinfo = (struct pci_devinfo *) device_get_ivars(child); + pci_cfg_save(child, dinfo, 0); + } + + /* Suspend devices before potentially powering them down. */ + error = bus_generic_suspend(dev); + if (error) { + free(devlist, M_TEMP); + return (error); + } + + /* + * Always set the device to D3. If ACPI suggests a different + * power state, use it instead. If ACPI is not present, the + * firmware is responsible for managing device power. Skip + * children who aren't attached since they are powered down + * separately. Only manage type 0 devices for now. + */ + for (i = 0; acpi_dev && i < numdevs; i++) { + child = devlist[i]; + dinfo = (struct pci_devinfo *) device_get_ivars(child); + if (device_is_attached(child) && dinfo->cfg.hdrtype == 0) { + dstate = PCI_POWERSTATE_D3; + ACPI_PWR_FOR_SLEEP(acpi_dev, child, &dstate); + pci_set_powerstate(child, dstate); + } + } + free(devlist, M_TEMP); + return (0); +} + +int +pci_resume(device_t dev) +{ + int i, numdevs, error; + device_t acpi_dev, child, *devlist; + struct pci_devinfo *dinfo; + + /* + * Set each child to D0 and restore its PCI configuration space. + */ + acpi_dev = NULL; + if (pci_do_power_resume) + acpi_dev = devclass_get_device(devclass_find("acpi"), 0); + if ((error = device_get_children(dev, &devlist, &numdevs)) != 0) + return (error); + for (i = 0; i < numdevs; i++) { + /* + * Notify ACPI we're going to D0 but ignore the result. If + * ACPI is not present, the firmware is responsible for + * managing device power. Only manage type 0 devices for now. + */ + child = devlist[i]; + dinfo = (struct pci_devinfo *) device_get_ivars(child); + if (acpi_dev && device_is_attached(child) && + dinfo->cfg.hdrtype == 0) { + ACPI_PWR_FOR_SLEEP(acpi_dev, child, NULL); + pci_set_powerstate(child, PCI_POWERSTATE_D0); + } + + /* Now the device is powered up, restore its config space. */ + pci_cfg_restore(child, dinfo); + } + free(devlist, M_TEMP); + return (bus_generic_resume(dev)); +} + +static void +pci_load_vendor_data(void) +{ + caddr_t vendordata, info; + + if ((vendordata = preload_search_by_type("pci_vendor_data")) != NULL) { + info = preload_search_info(vendordata, MODINFO_ADDR); + pci_vendordata = *(char **)info; + info = preload_search_info(vendordata, MODINFO_SIZE); + pci_vendordata_size = *(size_t *)info; + /* terminate the database */ + pci_vendordata[pci_vendordata_size] = '\n'; + } +} + +void +pci_driver_added(device_t dev, driver_t *driver) +{ + int numdevs; + device_t *devlist; + device_t child; + struct pci_devinfo *dinfo; + int i; + + if (bootverbose) + device_printf(dev, "driver added\n"); + DEVICE_IDENTIFY(driver, dev); + if (device_get_children(dev, &devlist, &numdevs) != 0) + return; + for (i = 0; i < numdevs; i++) { + child = devlist[i]; + if (device_get_state(child) != DS_NOTPRESENT) + continue; + dinfo = device_get_ivars(child); + pci_print_verbose(dinfo); + if (bootverbose) + pci_printf(&dinfo->cfg, "reprobing on driver added\n"); + pci_cfg_restore(child, dinfo); + if (device_probe_and_attach(child) != 0) + pci_cfg_save(child, dinfo, 1); + } + free(devlist, M_TEMP); +} + +int +pci_setup_intr(device_t dev, device_t child, struct resource *irq, int flags, + driver_filter_t *filter, driver_intr_t *intr, void *arg, void **cookiep) +{ + struct pci_devinfo *dinfo; + struct msix_table_entry *mte; + struct msix_vector *mv; + uint64_t addr; + uint32_t data; + void *cookie; + int error, rid; + + error = bus_generic_setup_intr(dev, child, irq, flags, filter, intr, + arg, &cookie); + if (error) + return (error); + + /* If this is not a direct child, just bail out. */ + if (device_get_parent(child) != dev) { + *cookiep = cookie; + return(0); + } + + rid = rman_get_rid(irq); + if (rid == 0) { + /* Make sure that INTx is enabled */ + pci_clear_command_bit(dev, child, PCIM_CMD_INTxDIS); + } else { + /* + * Check to see if the interrupt is MSI or MSI-X. + * Ask our parent to map the MSI and give + * us the address and data register values. + * If we fail for some reason, teardown the + * interrupt handler. + */ + dinfo = device_get_ivars(child); + if (dinfo->cfg.msi.msi_alloc > 0) { + if (dinfo->cfg.msi.msi_addr == 0) { + KASSERT(dinfo->cfg.msi.msi_handlers == 0, + ("MSI has handlers, but vectors not mapped")); + error = PCIB_MAP_MSI(device_get_parent(dev), + child, rman_get_start(irq), &addr, &data); + if (error) + goto bad; + dinfo->cfg.msi.msi_addr = addr; + dinfo->cfg.msi.msi_data = data; + } + if (dinfo->cfg.msi.msi_handlers == 0) + pci_enable_msi(child, dinfo->cfg.msi.msi_addr, + dinfo->cfg.msi.msi_data); + dinfo->cfg.msi.msi_handlers++; + } else { + KASSERT(dinfo->cfg.msix.msix_alloc > 0, + ("No MSI or MSI-X interrupts allocated")); + KASSERT(rid <= dinfo->cfg.msix.msix_table_len, + ("MSI-X index too high")); + mte = &dinfo->cfg.msix.msix_table[rid - 1]; + KASSERT(mte->mte_vector != 0, ("no message vector")); + mv = &dinfo->cfg.msix.msix_vectors[mte->mte_vector - 1]; + KASSERT(mv->mv_irq == rman_get_start(irq), + ("IRQ mismatch")); + if (mv->mv_address == 0) { + KASSERT(mte->mte_handlers == 0, + ("MSI-X table entry has handlers, but vector not mapped")); + error = PCIB_MAP_MSI(device_get_parent(dev), + child, rman_get_start(irq), &addr, &data); + if (error) + goto bad; + mv->mv_address = addr; + mv->mv_data = data; + } + if (mte->mte_handlers == 0) { + pci_enable_msix(child, rid - 1, mv->mv_address, + mv->mv_data); + pci_unmask_msix(child, rid - 1); + } + mte->mte_handlers++; + } + + /* Make sure that INTx is disabled if we are using MSI/MSIX */ + pci_set_command_bit(dev, child, PCIM_CMD_INTxDIS); + bad: + if (error) { + (void)bus_generic_teardown_intr(dev, child, irq, + cookie); + return (error); + } + } + *cookiep = cookie; + return (0); +} + +int +pci_teardown_intr(device_t dev, device_t child, struct resource *irq, + void *cookie) +{ + struct msix_table_entry *mte; + struct resource_list_entry *rle; + struct pci_devinfo *dinfo; + int error, rid; + + if (irq == NULL || !(rman_get_flags(irq) & RF_ACTIVE)) + return (EINVAL); + + /* If this isn't a direct child, just bail out */ + if (device_get_parent(child) != dev) + return(bus_generic_teardown_intr(dev, child, irq, cookie)); + + rid = rman_get_rid(irq); + if (rid == 0) { + /* Mask INTx */ + pci_set_command_bit(dev, child, PCIM_CMD_INTxDIS); + } else { + /* + * Check to see if the interrupt is MSI or MSI-X. If so, + * decrement the appropriate handlers count and mask the + * MSI-X message, or disable MSI messages if the count + * drops to 0. + */ + dinfo = device_get_ivars(child); + rle = resource_list_find(&dinfo->resources, SYS_RES_IRQ, rid); + if (rle->res != irq) + return (EINVAL); + if (dinfo->cfg.msi.msi_alloc > 0) { + KASSERT(rid <= dinfo->cfg.msi.msi_alloc, + ("MSI-X index too high")); + if (dinfo->cfg.msi.msi_handlers == 0) + return (EINVAL); + dinfo->cfg.msi.msi_handlers--; + if (dinfo->cfg.msi.msi_handlers == 0) + pci_disable_msi(child); + } else { + KASSERT(dinfo->cfg.msix.msix_alloc > 0, + ("No MSI or MSI-X interrupts allocated")); + KASSERT(rid <= dinfo->cfg.msix.msix_table_len, + ("MSI-X index too high")); + mte = &dinfo->cfg.msix.msix_table[rid - 1]; + if (mte->mte_handlers == 0) + return (EINVAL); + mte->mte_handlers--; + if (mte->mte_handlers == 0) + pci_mask_msix(child, rid - 1); + } + } + error = bus_generic_teardown_intr(dev, child, irq, cookie); + if (rid > 0) + KASSERT(error == 0, + ("%s: generic teardown failed for MSI/MSI-X", __func__)); + return (error); +} + +int +pci_print_child(device_t dev, device_t child) +{ + struct pci_devinfo *dinfo; + struct resource_list *rl; + int retval = 0; + + dinfo = device_get_ivars(child); + rl = &dinfo->resources; + + retval += bus_print_child_header(dev, child); + + retval += resource_list_print_type(rl, "port", SYS_RES_IOPORT, "%#lx"); + retval += resource_list_print_type(rl, "mem", SYS_RES_MEMORY, "%#lx"); + retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%ld"); + if (device_get_flags(dev)) + retval += printf(" flags %#x", device_get_flags(dev)); + + retval += printf(" at device %d.%d", pci_get_slot(child), + pci_get_function(child)); + + retval += bus_print_child_footer(dev, child); + + return (retval); +} + +static struct +{ + int class; + int subclass; + char *desc; +} pci_nomatch_tab[] = { + {PCIC_OLD, -1, "old"}, + {PCIC_OLD, PCIS_OLD_NONVGA, "non-VGA display device"}, + {PCIC_OLD, PCIS_OLD_VGA, "VGA-compatible display device"}, + {PCIC_STORAGE, -1, "mass storage"}, + {PCIC_STORAGE, PCIS_STORAGE_SCSI, "SCSI"}, + {PCIC_STORAGE, PCIS_STORAGE_IDE, "ATA"}, + {PCIC_STORAGE, PCIS_STORAGE_FLOPPY, "floppy disk"}, + {PCIC_STORAGE, PCIS_STORAGE_IPI, "IPI"}, + {PCIC_STORAGE, PCIS_STORAGE_RAID, "RAID"}, + {PCIC_STORAGE, PCIS_STORAGE_ATA_ADMA, "ATA (ADMA)"}, + {PCIC_STORAGE, PCIS_STORAGE_SATA, "SATA"}, + {PCIC_STORAGE, PCIS_STORAGE_SAS, "SAS"}, + {PCIC_NETWORK, -1, "network"}, + {PCIC_NETWORK, PCIS_NETWORK_ETHERNET, "ethernet"}, + {PCIC_NETWORK, PCIS_NETWORK_TOKENRING, "token ring"}, + {PCIC_NETWORK, PCIS_NETWORK_FDDI, "fddi"}, + {PCIC_NETWORK, PCIS_NETWORK_ATM, "ATM"}, + {PCIC_NETWORK, PCIS_NETWORK_ISDN, "ISDN"}, + {PCIC_DISPLAY, -1, "display"}, + {PCIC_DISPLAY, PCIS_DISPLAY_VGA, "VGA"}, + {PCIC_DISPLAY, PCIS_DISPLAY_XGA, "XGA"}, + {PCIC_DISPLAY, PCIS_DISPLAY_3D, "3D"}, + {PCIC_MULTIMEDIA, -1, "multimedia"}, + {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_VIDEO, "video"}, + {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_AUDIO, "audio"}, + {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_TELE, "telephony"}, + {PCIC_MULTIMEDIA, PCIS_MULTIMEDIA_HDA, "HDA"}, + {PCIC_MEMORY, -1, "memory"}, + {PCIC_MEMORY, PCIS_MEMORY_RAM, "RAM"}, + {PCIC_MEMORY, PCIS_MEMORY_FLASH, "flash"}, + {PCIC_BRIDGE, -1, "bridge"}, + {PCIC_BRIDGE, PCIS_BRIDGE_HOST, "HOST-PCI"}, + {PCIC_BRIDGE, PCIS_BRIDGE_ISA, "PCI-ISA"}, + {PCIC_BRIDGE, PCIS_BRIDGE_EISA, "PCI-EISA"}, + {PCIC_BRIDGE, PCIS_BRIDGE_MCA, "PCI-MCA"}, + {PCIC_BRIDGE, PCIS_BRIDGE_PCI, "PCI-PCI"}, + {PCIC_BRIDGE, PCIS_BRIDGE_PCMCIA, "PCI-PCMCIA"}, + {PCIC_BRIDGE, PCIS_BRIDGE_NUBUS, "PCI-NuBus"}, + {PCIC_BRIDGE, PCIS_BRIDGE_CARDBUS, "PCI-CardBus"}, + {PCIC_BRIDGE, PCIS_BRIDGE_RACEWAY, "PCI-RACEway"}, + {PCIC_SIMPLECOMM, -1, "simple comms"}, + {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_UART, "UART"}, /* could detect 16550 */ + {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_PAR, "parallel port"}, + {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_MULSER, "multiport serial"}, + {PCIC_SIMPLECOMM, PCIS_SIMPLECOMM_MODEM, "generic modem"}, + {PCIC_BASEPERIPH, -1, "base peripheral"}, + {PCIC_BASEPERIPH, PCIS_BASEPERIPH_PIC, "interrupt controller"}, + {PCIC_BASEPERIPH, PCIS_BASEPERIPH_DMA, "DMA controller"}, + {PCIC_BASEPERIPH, PCIS_BASEPERIPH_TIMER, "timer"}, + {PCIC_BASEPERIPH, PCIS_BASEPERIPH_RTC, "realtime clock"}, + {PCIC_BASEPERIPH, PCIS_BASEPERIPH_PCIHOT, "PCI hot-plug controller"}, + {PCIC_BASEPERIPH, PCIS_BASEPERIPH_SDHC, "SD host controller"}, + {PCIC_INPUTDEV, -1, "input device"}, + {PCIC_INPUTDEV, PCIS_INPUTDEV_KEYBOARD, "keyboard"}, + {PCIC_INPUTDEV, PCIS_INPUTDEV_DIGITIZER,"digitizer"}, + {PCIC_INPUTDEV, PCIS_INPUTDEV_MOUSE, "mouse"}, + {PCIC_INPUTDEV, PCIS_INPUTDEV_SCANNER, "scanner"}, + {PCIC_INPUTDEV, PCIS_INPUTDEV_GAMEPORT, "gameport"}, + {PCIC_DOCKING, -1, "docking station"}, + {PCIC_PROCESSOR, -1, "processor"}, + {PCIC_SERIALBUS, -1, "serial bus"}, + {PCIC_SERIALBUS, PCIS_SERIALBUS_FW, "FireWire"}, + {PCIC_SERIALBUS, PCIS_SERIALBUS_ACCESS, "AccessBus"}, + {PCIC_SERIALBUS, PCIS_SERIALBUS_SSA, "SSA"}, + {PCIC_SERIALBUS, PCIS_SERIALBUS_USB, "USB"}, + {PCIC_SERIALBUS, PCIS_SERIALBUS_FC, "Fibre Channel"}, + {PCIC_SERIALBUS, PCIS_SERIALBUS_SMBUS, "SMBus"}, + {PCIC_WIRELESS, -1, "wireless controller"}, + {PCIC_WIRELESS, PCIS_WIRELESS_IRDA, "iRDA"}, + {PCIC_WIRELESS, PCIS_WIRELESS_IR, "IR"}, + {PCIC_WIRELESS, PCIS_WIRELESS_RF, "RF"}, + {PCIC_INTELLIIO, -1, "intelligent I/O controller"}, + {PCIC_INTELLIIO, PCIS_INTELLIIO_I2O, "I2O"}, + {PCIC_SATCOM, -1, "satellite communication"}, + {PCIC_SATCOM, PCIS_SATCOM_TV, "sat TV"}, + {PCIC_SATCOM, PCIS_SATCOM_AUDIO, "sat audio"}, + {PCIC_SATCOM, PCIS_SATCOM_VOICE, "sat voice"}, + {PCIC_SATCOM, PCIS_SATCOM_DATA, "sat data"}, + {PCIC_CRYPTO, -1, "encrypt/decrypt"}, + {PCIC_CRYPTO, PCIS_CRYPTO_NETCOMP, "network/computer crypto"}, + {PCIC_CRYPTO, PCIS_CRYPTO_ENTERTAIN, "entertainment crypto"}, + {PCIC_DASP, -1, "dasp"}, + {PCIC_DASP, PCIS_DASP_DPIO, "DPIO module"}, + {0, 0, NULL} +}; + +void +pci_probe_nomatch(device_t dev, device_t child) +{ + int i; + char *cp, *scp, *device; + + /* + * Look for a listing for this device in a loaded device database. + */ + if ((device = pci_describe_device(child)) != NULL) { + device_printf(dev, "<%s>", device); + free(device, M_DEVBUF); + } else { + /* + * Scan the class/subclass descriptions for a general + * description. + */ + cp = "unknown"; + scp = NULL; + for (i = 0; pci_nomatch_tab[i].desc != NULL; i++) { + if (pci_nomatch_tab[i].class == pci_get_class(child)) { + if (pci_nomatch_tab[i].subclass == -1) { + cp = pci_nomatch_tab[i].desc; + } else if (pci_nomatch_tab[i].subclass == + pci_get_subclass(child)) { + scp = pci_nomatch_tab[i].desc; + } + } + } + device_printf(dev, "<%s%s%s>", + cp ? cp : "", + ((cp != NULL) && (scp != NULL)) ? ", " : "", + scp ? scp : ""); + } + printf(" at device %d.%d (no driver attached)\n", + pci_get_slot(child), pci_get_function(child)); + pci_cfg_save(child, (struct pci_devinfo *)device_get_ivars(child), 1); + return; +} + +/* + * Parse the PCI device database, if loaded, and return a pointer to a + * description of the device. + * + * The database is flat text formatted as follows: + * + * Any line not in a valid format is ignored. + * Lines are terminated with newline '\n' characters. + * + * A VENDOR line consists of the 4 digit (hex) vendor code, a TAB, then + * the vendor name. + * + * A DEVICE line is entered immediately below the corresponding VENDOR ID. + * - devices cannot be listed without a corresponding VENDOR line. + * A DEVICE line consists of a TAB, the 4 digit (hex) device code, + * another TAB, then the device name. + */ + +/* + * Assuming (ptr) points to the beginning of a line in the database, + * return the vendor or device and description of the next entry. + * The value of (vendor) or (device) inappropriate for the entry type + * is set to -1. Returns nonzero at the end of the database. + * + * Note that this is slightly unrobust in the face of corrupt data; + * we attempt to safeguard against this by spamming the end of the + * database with a newline when we initialise. + */ +static int +pci_describe_parse_line(char **ptr, int *vendor, int *device, char **desc) +{ + char *cp = *ptr; + int left; + + *device = -1; + *vendor = -1; + **desc = '\0'; + for (;;) { + left = pci_vendordata_size - (cp - pci_vendordata); + if (left <= 0) { + *ptr = cp; + return(1); + } + + /* vendor entry? */ + if (*cp != '\t' && + sscanf(cp, "%x\t%80[^\n]", vendor, *desc) == 2) + break; + /* device entry? */ + if (*cp == '\t' && + sscanf(cp, "%x\t%80[^\n]", device, *desc) == 2) + break; + + /* skip to next line */ + while (*cp != '\n' && left > 0) { + cp++; + left--; + } + if (*cp == '\n') { + cp++; + left--; + } + } + /* skip to next line */ + while (*cp != '\n' && left > 0) { + cp++; + left--; + } + if (*cp == '\n' && left > 0) + cp++; + *ptr = cp; + return(0); +} + +static char * +pci_describe_device(device_t dev) +{ + int vendor, device; + char *desc, *vp, *dp, *line; + + desc = vp = dp = NULL; + + /* + * If we have no vendor data, we can't do anything. + */ + if (pci_vendordata == NULL) + goto out; + + /* + * Scan the vendor data looking for this device + */ + line = pci_vendordata; + if ((vp = malloc(80, M_DEVBUF, M_NOWAIT)) == NULL) + goto out; + for (;;) { + if (pci_describe_parse_line(&line, &vendor, &device, &vp)) + goto out; + if (vendor == pci_get_vendor(dev)) + break; + } + if ((dp = malloc(80, M_DEVBUF, M_NOWAIT)) == NULL) + goto out; + for (;;) { + if (pci_describe_parse_line(&line, &vendor, &device, &dp)) { + *dp = 0; + break; + } + if (vendor != -1) { + *dp = 0; + break; + } + if (device == pci_get_device(dev)) + break; + } + if (dp[0] == '\0') + snprintf(dp, 80, "0x%x", pci_get_device(dev)); + if ((desc = malloc(strlen(vp) + strlen(dp) + 3, M_DEVBUF, M_NOWAIT)) != + NULL) + sprintf(desc, "%s, %s", vp, dp); + out: + if (vp != NULL) + free(vp, M_DEVBUF); + if (dp != NULL) + free(dp, M_DEVBUF); + return(desc); +} + +int +pci_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) +{ + struct pci_devinfo *dinfo; + pcicfgregs *cfg; + + dinfo = device_get_ivars(child); + cfg = &dinfo->cfg; + + switch (which) { + case PCI_IVAR_ETHADDR: + /* + * The generic accessor doesn't deal with failure, so + * we set the return value, then return an error. + */ + *((uint8_t **) result) = NULL; + return (EINVAL); + case PCI_IVAR_SUBVENDOR: + *result = cfg->subvendor; + break; + case PCI_IVAR_SUBDEVICE: + *result = cfg->subdevice; + break; + case PCI_IVAR_VENDOR: + *result = cfg->vendor; + break; + case PCI_IVAR_DEVICE: + *result = cfg->device; + break; + case PCI_IVAR_DEVID: + *result = (cfg->device << 16) | cfg->vendor; + break; + case PCI_IVAR_CLASS: + *result = cfg->baseclass; + break; + case PCI_IVAR_SUBCLASS: + *result = cfg->subclass; + break; + case PCI_IVAR_PROGIF: + *result = cfg->progif; + break; + case PCI_IVAR_REVID: + *result = cfg->revid; + break; + case PCI_IVAR_INTPIN: + *result = cfg->intpin; + break; + case PCI_IVAR_IRQ: + *result = cfg->intline; + break; + case PCI_IVAR_DOMAIN: + *result = cfg->domain; + break; + case PCI_IVAR_BUS: + *result = cfg->bus; + break; + case PCI_IVAR_SLOT: + *result = cfg->slot; + break; + case PCI_IVAR_FUNCTION: + *result = cfg->func; + break; + case PCI_IVAR_CMDREG: + *result = cfg->cmdreg; + break; + case PCI_IVAR_CACHELNSZ: + *result = cfg->cachelnsz; + break; + case PCI_IVAR_MINGNT: + *result = cfg->mingnt; + break; + case PCI_IVAR_MAXLAT: + *result = cfg->maxlat; + break; + case PCI_IVAR_LATTIMER: + *result = cfg->lattimer; + break; + default: + return (ENOENT); + } + return (0); +} + +int +pci_write_ivar(device_t dev, device_t child, int which, uintptr_t value) +{ + struct pci_devinfo *dinfo; + + dinfo = device_get_ivars(child); + + switch (which) { + case PCI_IVAR_INTPIN: + dinfo->cfg.intpin = value; + return (0); + case PCI_IVAR_ETHADDR: + case PCI_IVAR_SUBVENDOR: + case PCI_IVAR_SUBDEVICE: + case PCI_IVAR_VENDOR: + case PCI_IVAR_DEVICE: + case PCI_IVAR_DEVID: + case PCI_IVAR_CLASS: + case PCI_IVAR_SUBCLASS: + case PCI_IVAR_PROGIF: + case PCI_IVAR_REVID: + case PCI_IVAR_IRQ: + case PCI_IVAR_DOMAIN: + case PCI_IVAR_BUS: + case PCI_IVAR_SLOT: + case PCI_IVAR_FUNCTION: + return (EINVAL); /* disallow for now */ + + default: + return (ENOENT); + } +} + + +#include <freebsd/local/opt_ddb.h> +#ifdef DDB +#include <freebsd/ddb/ddb.h> +#include <freebsd/sys/cons.h> + +/* + * List resources based on pci map registers, used for within ddb + */ + +DB_SHOW_COMMAND(pciregs, db_pci_dump) +{ + struct pci_devinfo *dinfo; + struct devlist *devlist_head; + struct pci_conf *p; + const char *name; + int i, error, none_count; + + none_count = 0; + /* get the head of the device queue */ + devlist_head = &pci_devq; + + /* + * Go through the list of devices and print out devices + */ + for (error = 0, i = 0, + dinfo = STAILQ_FIRST(devlist_head); + (dinfo != NULL) && (error == 0) && (i < pci_numdevs) && !db_pager_quit; + dinfo = STAILQ_NEXT(dinfo, pci_links), i++) { + + /* Populate pd_name and pd_unit */ + name = NULL; + if (dinfo->cfg.dev) + name = device_get_name(dinfo->cfg.dev); + + p = &dinfo->conf; + db_printf("%s%d@pci%d:%d:%d:%d:\tclass=0x%06x card=0x%08x " + "chip=0x%08x rev=0x%02x hdr=0x%02x\n", + (name && *name) ? name : "none", + (name && *name) ? (int)device_get_unit(dinfo->cfg.dev) : + none_count++, + p->pc_sel.pc_domain, p->pc_sel.pc_bus, p->pc_sel.pc_dev, + p->pc_sel.pc_func, (p->pc_class << 16) | + (p->pc_subclass << 8) | p->pc_progif, + (p->pc_subdevice << 16) | p->pc_subvendor, + (p->pc_device << 16) | p->pc_vendor, + p->pc_revid, p->pc_hdr); + } +} +#endif /* DDB */ + +static struct resource * +pci_alloc_map(device_t dev, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + struct resource_list *rl = &dinfo->resources; + struct resource_list_entry *rle; + struct resource *res; + pci_addr_t map, testval; + int mapsize; + + /* + * Weed out the bogons, and figure out how large the BAR/map + * is. Bars that read back 0 here are bogus and unimplemented. + * Note: atapci in legacy mode are special and handled elsewhere + * in the code. If you have a atapci device in legacy mode and + * it fails here, that other code is broken. + */ + res = NULL; + pci_read_bar(child, *rid, &map, &testval); + + /* Ignore a BAR with a base of 0. */ + if (pci_mapbase(testval) == 0) + goto out; + + if (PCI_BAR_MEM(testval)) { + if (type != SYS_RES_MEMORY) { + if (bootverbose) + device_printf(dev, + "child %s requested type %d for rid %#x," + " but the BAR says it is an memio\n", + device_get_nameunit(child), type, *rid); + goto out; + } + } else { + if (type != SYS_RES_IOPORT) { + if (bootverbose) + device_printf(dev, + "child %s requested type %d for rid %#x," + " but the BAR says it is an ioport\n", + device_get_nameunit(child), type, *rid); + goto out; + } + } + + /* + * For real BARs, we need to override the size that + * the driver requests, because that's what the BAR + * actually uses and we would otherwise have a + * situation where we might allocate the excess to + * another driver, which won't work. + */ + mapsize = pci_mapsize(testval); + count = 1UL << mapsize; + if (RF_ALIGNMENT(flags) < mapsize) + flags = (flags & ~RF_ALIGNMENT_MASK) | RF_ALIGNMENT_LOG2(mapsize); + if (PCI_BAR_MEM(testval) && (testval & PCIM_BAR_MEM_PREFETCH)) + flags |= RF_PREFETCHABLE; + + /* + * Allocate enough resource, and then write back the + * appropriate bar for that resource. + */ + res = BUS_ALLOC_RESOURCE(device_get_parent(dev), child, type, rid, + start, end, count, flags & ~RF_ACTIVE); + if (res == NULL) { + device_printf(child, + "%#lx bytes of rid %#x res %d failed (%#lx, %#lx).\n", + count, *rid, type, start, end); + goto out; + } + rman_set_device(res, dev); + resource_list_add(rl, type, *rid, start, end, count); + rle = resource_list_find(rl, type, *rid); + if (rle == NULL) + panic("pci_alloc_map: unexpectedly can't find resource."); + rle->res = res; + rle->start = rman_get_start(res); + rle->end = rman_get_end(res); + rle->count = count; + if (bootverbose) + device_printf(child, + "Lazy allocation of %#lx bytes rid %#x type %d at %#lx\n", + count, *rid, type, rman_get_start(res)); + map = rman_get_start(res); + pci_write_bar(child, *rid, map); +out:; + return (res); +} + + +struct resource * +pci_alloc_resource(device_t dev, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + struct resource_list *rl = &dinfo->resources; + struct resource_list_entry *rle; + struct resource *res; + pcicfgregs *cfg = &dinfo->cfg; + + if (device_get_parent(child) != dev) + return (BUS_ALLOC_RESOURCE(device_get_parent(dev), child, + type, rid, start, end, count, flags)); + + /* + * Perform lazy resource allocation + */ + switch (type) { + case SYS_RES_IRQ: + /* + * Can't alloc legacy interrupt once MSI messages have + * been allocated. + */ + if (*rid == 0 && (cfg->msi.msi_alloc > 0 || + cfg->msix.msix_alloc > 0)) + return (NULL); + + /* + * If the child device doesn't have an interrupt + * routed and is deserving of an interrupt, try to + * assign it one. + */ + if (*rid == 0 && !PCI_INTERRUPT_VALID(cfg->intline) && + (cfg->intpin != 0)) + pci_assign_interrupt(dev, child, 0); + break; + case SYS_RES_IOPORT: + case SYS_RES_MEMORY: + /* Allocate resources for this BAR if needed. */ + rle = resource_list_find(rl, type, *rid); + if (rle == NULL) { + res = pci_alloc_map(dev, child, type, rid, start, end, + count, flags); + if (res == NULL) + return (NULL); + rle = resource_list_find(rl, type, *rid); + } + + /* + * If the resource belongs to the bus, then give it to + * the child. We need to activate it if requested + * since the bus always allocates inactive resources. + */ + if (rle != NULL && rle->res != NULL && + rman_get_device(rle->res) == dev) { + if (bootverbose) + device_printf(child, + "Reserved %#lx bytes for rid %#x type %d at %#lx\n", + rman_get_size(rle->res), *rid, type, + rman_get_start(rle->res)); + rman_set_device(rle->res, child); + if ((flags & RF_ACTIVE) && + bus_activate_resource(child, type, *rid, + rle->res) != 0) + return (NULL); + return (rle->res); + } + } + return (resource_list_alloc(rl, dev, child, type, rid, + start, end, count, flags)); +} + +int +pci_release_resource(device_t dev, device_t child, int type, int rid, + struct resource *r) +{ + int error; + + if (device_get_parent(child) != dev) + return (BUS_RELEASE_RESOURCE(device_get_parent(dev), child, + type, rid, r)); + + /* + * For BARs we don't actually want to release the resource. + * Instead, we deactivate the resource if needed and then give + * ownership of the BAR back to the bus. + */ + switch (type) { + case SYS_RES_IOPORT: + case SYS_RES_MEMORY: + if (rman_get_device(r) != child) + return (EINVAL); + if (rman_get_flags(r) & RF_ACTIVE) { + error = bus_deactivate_resource(child, type, rid, r); + if (error) + return (error); + } + rman_set_device(r, dev); + return (0); + } + return (bus_generic_rl_release_resource(dev, child, type, rid, r)); +} + +int +pci_activate_resource(device_t dev, device_t child, int type, int rid, + struct resource *r) +{ + int error; + + error = bus_generic_activate_resource(dev, child, type, rid, r); + if (error) + return (error); + + /* Enable decoding in the command register when activating BARs. */ + if (device_get_parent(child) == dev) { + switch (type) { + case SYS_RES_IOPORT: + case SYS_RES_MEMORY: + error = PCI_ENABLE_IO(dev, child, type); + break; + } + } + return (error); +} + +void +pci_delete_resource(device_t dev, device_t child, int type, int rid) +{ + struct pci_devinfo *dinfo; + struct resource_list *rl; + struct resource_list_entry *rle; + + if (device_get_parent(child) != dev) + return; + + dinfo = device_get_ivars(child); + rl = &dinfo->resources; + rle = resource_list_find(rl, type, rid); + if (rle == NULL) + return; + + if (rle->res) { + if (rman_get_device(rle->res) != dev || + rman_get_flags(rle->res) & RF_ACTIVE) { + device_printf(dev, "delete_resource: " + "Resource still owned by child, oops. " + "(type=%d, rid=%d, addr=%lx)\n", + rle->type, rle->rid, + rman_get_start(rle->res)); + return; + } + +#ifndef __PCI_BAR_ZERO_VALID + /* + * If this is a BAR, clear the BAR so it stops + * decoding before releasing the resource. + */ + switch (type) { + case SYS_RES_IOPORT: + case SYS_RES_MEMORY: + pci_write_bar(child, rid, 0); + break; + } +#endif + bus_release_resource(dev, type, rid, rle->res); + } + resource_list_delete(rl, type, rid); +} + +struct resource_list * +pci_get_resource_list (device_t dev, device_t child) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + + return (&dinfo->resources); +} + +uint32_t +pci_read_config_method(device_t dev, device_t child, int reg, int width) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + + return (PCIB_READ_CONFIG(device_get_parent(dev), + cfg->bus, cfg->slot, cfg->func, reg, width)); +} + +void +pci_write_config_method(device_t dev, device_t child, int reg, + uint32_t val, int width) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + + PCIB_WRITE_CONFIG(device_get_parent(dev), + cfg->bus, cfg->slot, cfg->func, reg, val, width); +} + +int +pci_child_location_str_method(device_t dev, device_t child, char *buf, + size_t buflen) +{ + + snprintf(buf, buflen, "slot=%d function=%d", pci_get_slot(child), + pci_get_function(child)); + return (0); +} + +int +pci_child_pnpinfo_str_method(device_t dev, device_t child, char *buf, + size_t buflen) +{ + struct pci_devinfo *dinfo; + pcicfgregs *cfg; + + dinfo = device_get_ivars(child); + cfg = &dinfo->cfg; + snprintf(buf, buflen, "vendor=0x%04x device=0x%04x subvendor=0x%04x " + "subdevice=0x%04x class=0x%02x%02x%02x", cfg->vendor, cfg->device, + cfg->subvendor, cfg->subdevice, cfg->baseclass, cfg->subclass, + cfg->progif); + return (0); +} + +int +pci_assign_interrupt_method(device_t dev, device_t child) +{ + struct pci_devinfo *dinfo = device_get_ivars(child); + pcicfgregs *cfg = &dinfo->cfg; + + return (PCIB_ROUTE_INTERRUPT(device_get_parent(dev), child, + cfg->intpin)); +} + +static int +pci_modevent(module_t mod, int what, void *arg) +{ + static struct cdev *pci_cdev; + + switch (what) { + case MOD_LOAD: + STAILQ_INIT(&pci_devq); + pci_generation = 0; + pci_cdev = make_dev(&pcicdev, 0, UID_ROOT, GID_WHEEL, 0644, + "pci"); + pci_load_vendor_data(); + break; + + case MOD_UNLOAD: + destroy_dev(pci_cdev); + break; + } + + return (0); +} + +void +pci_cfg_restore(device_t dev, struct pci_devinfo *dinfo) +{ + int i; + + /* + * Only do header type 0 devices. Type 1 devices are bridges, + * which we know need special treatment. Type 2 devices are + * cardbus bridges which also require special treatment. + * Other types are unknown, and we err on the side of safety + * by ignoring them. + */ + if (dinfo->cfg.hdrtype != 0) + return; + + /* + * Restore the device to full power mode. We must do this + * before we restore the registers because moving from D3 to + * D0 will cause the chip's BARs and some other registers to + * be reset to some unknown power on reset values. Cut down + * the noise on boot by doing nothing if we are already in + * state D0. + */ + if (pci_get_powerstate(dev) != PCI_POWERSTATE_D0) { + pci_set_powerstate(dev, PCI_POWERSTATE_D0); + } + for (i = 0; i < dinfo->cfg.nummaps; i++) + pci_write_config(dev, PCIR_BAR(i), dinfo->cfg.bar[i], 4); + pci_write_config(dev, PCIR_BIOS, dinfo->cfg.bios, 4); + pci_write_config(dev, PCIR_COMMAND, dinfo->cfg.cmdreg, 2); + pci_write_config(dev, PCIR_INTLINE, dinfo->cfg.intline, 1); + pci_write_config(dev, PCIR_INTPIN, dinfo->cfg.intpin, 1); + pci_write_config(dev, PCIR_MINGNT, dinfo->cfg.mingnt, 1); + pci_write_config(dev, PCIR_MAXLAT, dinfo->cfg.maxlat, 1); + pci_write_config(dev, PCIR_CACHELNSZ, dinfo->cfg.cachelnsz, 1); + pci_write_config(dev, PCIR_LATTIMER, dinfo->cfg.lattimer, 1); + pci_write_config(dev, PCIR_PROGIF, dinfo->cfg.progif, 1); + pci_write_config(dev, PCIR_REVID, dinfo->cfg.revid, 1); + + /* Restore MSI and MSI-X configurations if they are present. */ + if (dinfo->cfg.msi.msi_location != 0) + pci_resume_msi(dev); + if (dinfo->cfg.msix.msix_location != 0) + pci_resume_msix(dev); +} + +void +pci_cfg_save(device_t dev, struct pci_devinfo *dinfo, int setstate) +{ + int i; + uint32_t cls; + int ps; + + /* + * Only do header type 0 devices. Type 1 devices are bridges, which + * we know need special treatment. Type 2 devices are cardbus bridges + * which also require special treatment. Other types are unknown, and + * we err on the side of safety by ignoring them. Powering down + * bridges should not be undertaken lightly. + */ + if (dinfo->cfg.hdrtype != 0) + return; + for (i = 0; i < dinfo->cfg.nummaps; i++) + dinfo->cfg.bar[i] = pci_read_config(dev, PCIR_BAR(i), 4); + dinfo->cfg.bios = pci_read_config(dev, PCIR_BIOS, 4); + + /* + * Some drivers apparently write to these registers w/o updating our + * cached copy. No harm happens if we update the copy, so do so here + * so we can restore them. The COMMAND register is modified by the + * bus w/o updating the cache. This should represent the normally + * writable portion of the 'defined' part of type 0 headers. In + * theory we also need to save/restore the PCI capability structures + * we know about, but apart from power we don't know any that are + * writable. + */ + dinfo->cfg.subvendor = pci_read_config(dev, PCIR_SUBVEND_0, 2); + dinfo->cfg.subdevice = pci_read_config(dev, PCIR_SUBDEV_0, 2); + dinfo->cfg.vendor = pci_read_config(dev, PCIR_VENDOR, 2); + dinfo->cfg.device = pci_read_config(dev, PCIR_DEVICE, 2); + dinfo->cfg.cmdreg = pci_read_config(dev, PCIR_COMMAND, 2); + dinfo->cfg.intline = pci_read_config(dev, PCIR_INTLINE, 1); + dinfo->cfg.intpin = pci_read_config(dev, PCIR_INTPIN, 1); + dinfo->cfg.mingnt = pci_read_config(dev, PCIR_MINGNT, 1); + dinfo->cfg.maxlat = pci_read_config(dev, PCIR_MAXLAT, 1); + dinfo->cfg.cachelnsz = pci_read_config(dev, PCIR_CACHELNSZ, 1); + dinfo->cfg.lattimer = pci_read_config(dev, PCIR_LATTIMER, 1); + dinfo->cfg.baseclass = pci_read_config(dev, PCIR_CLASS, 1); + dinfo->cfg.subclass = pci_read_config(dev, PCIR_SUBCLASS, 1); + dinfo->cfg.progif = pci_read_config(dev, PCIR_PROGIF, 1); + dinfo->cfg.revid = pci_read_config(dev, PCIR_REVID, 1); + + /* + * don't set the state for display devices, base peripherals and + * memory devices since bad things happen when they are powered down. + * We should (a) have drivers that can easily detach and (b) use + * generic drivers for these devices so that some device actually + * attaches. We need to make sure that when we implement (a) we don't + * power the device down on a reattach. + */ + cls = pci_get_class(dev); + if (!setstate) + return; + switch (pci_do_power_nodriver) + { + case 0: /* NO powerdown at all */ + return; + case 1: /* Conservative about what to power down */ + if (cls == PCIC_STORAGE) + return; + /*FALLTHROUGH*/ + case 2: /* Agressive about what to power down */ + if (cls == PCIC_DISPLAY || cls == PCIC_MEMORY || + cls == PCIC_BASEPERIPH) + return; + /*FALLTHROUGH*/ + case 3: /* Power down everything */ + break; + } + /* + * PCI spec says we can only go into D3 state from D0 state. + * Transition from D[12] into D0 before going to D3 state. + */ + ps = pci_get_powerstate(dev); + if (ps != PCI_POWERSTATE_D0 && ps != PCI_POWERSTATE_D3) + pci_set_powerstate(dev, PCI_POWERSTATE_D0); + if (pci_get_powerstate(dev) != PCI_POWERSTATE_D3) + pci_set_powerstate(dev, PCI_POWERSTATE_D3); +} diff --git a/freebsd/sys/dev/pci/pci_pci.c b/freebsd/sys/dev/pci/pci_pci.c new file mode 100644 index 00000000..c2a829c8 --- /dev/null +++ b/freebsd/sys/dev/pci/pci_pci.c @@ -0,0 +1,740 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/*- + * Copyright (c) 1994,1995 Stefan Esser, Wolfgang StanglMeier + * Copyright (c) 2000 Michael Smith <msmith@freebsd.org> + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <freebsd/sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * PCI:PCI bridge support. + */ + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/bus.h> +#include <freebsd/machine/bus.h> +#include <freebsd/sys/rman.h> +#include <freebsd/sys/sysctl.h> + +#include <freebsd/machine/resource.h> + +#include <freebsd/dev/pci/pcivar.h> +#include <freebsd/dev/pci/pcireg.h> +#include <freebsd/dev/pci/pcib_private.h> + +#include <freebsd/local/pcib_if.h> + +static int pcib_probe(device_t dev); + +static device_method_t pcib_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, pcib_probe), + DEVMETHOD(device_attach, pcib_attach), + DEVMETHOD(device_detach, bus_generic_detach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_read_ivar, pcib_read_ivar), + DEVMETHOD(bus_write_ivar, pcib_write_ivar), + DEVMETHOD(bus_alloc_resource, pcib_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + + /* pcib interface */ + DEVMETHOD(pcib_maxslots, pcib_maxslots), + DEVMETHOD(pcib_read_config, pcib_read_config), + DEVMETHOD(pcib_write_config, pcib_write_config), + DEVMETHOD(pcib_route_interrupt, pcib_route_interrupt), + DEVMETHOD(pcib_alloc_msi, pcib_alloc_msi), + DEVMETHOD(pcib_release_msi, pcib_release_msi), + DEVMETHOD(pcib_alloc_msix, pcib_alloc_msix), + DEVMETHOD(pcib_release_msix, pcib_release_msix), + DEVMETHOD(pcib_map_msi, pcib_map_msi), + + { 0, 0 } +}; + +static devclass_t pcib_devclass; + +DEFINE_CLASS_0(pcib, pcib_driver, pcib_methods, sizeof(struct pcib_softc)); +DRIVER_MODULE(pcib, pci, pcib_driver, pcib_devclass, 0, 0); + +/* + * Is the prefetch window open (eg, can we allocate memory in it?) + */ +static int +pcib_is_prefetch_open(struct pcib_softc *sc) +{ + return (sc->pmembase > 0 && sc->pmembase < sc->pmemlimit); +} + +/* + * Is the nonprefetch window open (eg, can we allocate memory in it?) + */ +static int +pcib_is_nonprefetch_open(struct pcib_softc *sc) +{ + return (sc->membase > 0 && sc->membase < sc->memlimit); +} + +/* + * Is the io window open (eg, can we allocate ports in it?) + */ +static int +pcib_is_io_open(struct pcib_softc *sc) +{ + return (sc->iobase > 0 && sc->iobase < sc->iolimit); +} + +/* + * Generic device interface + */ +static int +pcib_probe(device_t dev) +{ + if ((pci_get_class(dev) == PCIC_BRIDGE) && + (pci_get_subclass(dev) == PCIS_BRIDGE_PCI)) { + device_set_desc(dev, "PCI-PCI bridge"); + return(-10000); + } + return(ENXIO); +} + +void +pcib_attach_common(device_t dev) +{ + struct pcib_softc *sc; + uint8_t iolow; + struct sysctl_ctx_list *sctx; + struct sysctl_oid *soid; + + sc = device_get_softc(dev); + sc->dev = dev; + + /* + * Get current bridge configuration. + */ + sc->command = pci_read_config(dev, PCIR_COMMAND, 1); + sc->domain = pci_get_domain(dev); + sc->pribus = pci_read_config(dev, PCIR_PRIBUS_1, 1); + sc->secbus = pci_read_config(dev, PCIR_SECBUS_1, 1); + sc->subbus = pci_read_config(dev, PCIR_SUBBUS_1, 1); + sc->secstat = pci_read_config(dev, PCIR_SECSTAT_1, 2); + sc->bridgectl = pci_read_config(dev, PCIR_BRIDGECTL_1, 2); + sc->seclat = pci_read_config(dev, PCIR_SECLAT_1, 1); + + /* + * Setup sysctl reporting nodes + */ + sctx = device_get_sysctl_ctx(dev); + soid = device_get_sysctl_tree(dev); + SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "domain", + CTLFLAG_RD, &sc->domain, 0, "Domain number"); + SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "pribus", + CTLFLAG_RD, &sc->pribus, 0, "Primary bus number"); + SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "secbus", + CTLFLAG_RD, &sc->secbus, 0, "Secondary bus number"); + SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "subbus", + CTLFLAG_RD, &sc->subbus, 0, "Subordinate bus number"); + + /* + * Determine current I/O decode. + */ + if (sc->command & PCIM_CMD_PORTEN) { + iolow = pci_read_config(dev, PCIR_IOBASEL_1, 1); + if ((iolow & PCIM_BRIO_MASK) == PCIM_BRIO_32) { + sc->iobase = PCI_PPBIOBASE(pci_read_config(dev, PCIR_IOBASEH_1, 2), + pci_read_config(dev, PCIR_IOBASEL_1, 1)); + } else { + sc->iobase = PCI_PPBIOBASE(0, pci_read_config(dev, PCIR_IOBASEL_1, 1)); + } + + iolow = pci_read_config(dev, PCIR_IOLIMITL_1, 1); + if ((iolow & PCIM_BRIO_MASK) == PCIM_BRIO_32) { + sc->iolimit = PCI_PPBIOLIMIT(pci_read_config(dev, PCIR_IOLIMITH_1, 2), + pci_read_config(dev, PCIR_IOLIMITL_1, 1)); + } else { + sc->iolimit = PCI_PPBIOLIMIT(0, pci_read_config(dev, PCIR_IOLIMITL_1, 1)); + } + } + + /* + * Determine current memory decode. + */ + if (sc->command & PCIM_CMD_MEMEN) { + sc->membase = PCI_PPBMEMBASE(0, pci_read_config(dev, PCIR_MEMBASE_1, 2)); + sc->memlimit = PCI_PPBMEMLIMIT(0, pci_read_config(dev, PCIR_MEMLIMIT_1, 2)); + iolow = pci_read_config(dev, PCIR_PMBASEL_1, 1); + if ((iolow & PCIM_BRPM_MASK) == PCIM_BRPM_64) + sc->pmembase = PCI_PPBMEMBASE( + pci_read_config(dev, PCIR_PMBASEH_1, 4), + pci_read_config(dev, PCIR_PMBASEL_1, 2)); + else + sc->pmembase = PCI_PPBMEMBASE(0, + pci_read_config(dev, PCIR_PMBASEL_1, 2)); + iolow = pci_read_config(dev, PCIR_PMLIMITL_1, 1); + if ((iolow & PCIM_BRPM_MASK) == PCIM_BRPM_64) + sc->pmemlimit = PCI_PPBMEMLIMIT( + pci_read_config(dev, PCIR_PMLIMITH_1, 4), + pci_read_config(dev, PCIR_PMLIMITL_1, 2)); + else + sc->pmemlimit = PCI_PPBMEMLIMIT(0, + pci_read_config(dev, PCIR_PMLIMITL_1, 2)); + } + + /* + * Quirk handling. + */ + switch (pci_get_devid(dev)) { + case 0x12258086: /* Intel 82454KX/GX (Orion) */ + { + uint8_t supbus; + + supbus = pci_read_config(dev, 0x41, 1); + if (supbus != 0xff) { + sc->secbus = supbus + 1; + sc->subbus = supbus + 1; + } + break; + } + + /* + * The i82380FB mobile docking controller is a PCI-PCI bridge, + * and it is a subtractive bridge. However, the ProgIf is wrong + * so the normal setting of PCIB_SUBTRACTIVE bit doesn't + * happen. There's also a Toshiba bridge that behaves this + * way. + */ + case 0x124b8086: /* Intel 82380FB Mobile */ + case 0x060513d7: /* Toshiba ???? */ + sc->flags |= PCIB_SUBTRACTIVE; + break; + +#ifndef __rtems__ + /* Compaq R3000 BIOS sets wrong subordinate bus number. */ + case 0x00dd10de: + { + char *cp; + + if ((cp = getenv("smbios.planar.maker")) == NULL) + break; + if (strncmp(cp, "Compal", 6) != 0) { + freeenv(cp); + break; + } + freeenv(cp); + if ((cp = getenv("smbios.planar.product")) == NULL) + break; + if (strncmp(cp, "08A0", 4) != 0) { + freeenv(cp); + break; + } + freeenv(cp); + if (sc->subbus < 0xa) { + pci_write_config(dev, PCIR_SUBBUS_1, 0xa, 1); + sc->subbus = pci_read_config(dev, PCIR_SUBBUS_1, 1); + } + break; + } +#endif /* __rtems__ */ + } + + if (pci_msi_device_blacklisted(dev)) + sc->flags |= PCIB_DISABLE_MSI; + + /* + * Intel 815, 845 and other chipsets say they are PCI-PCI bridges, + * but have a ProgIF of 0x80. The 82801 family (AA, AB, BAM/CAM, + * BA/CA/DB and E) PCI bridges are HUB-PCI bridges, in Intelese. + * This means they act as if they were subtractively decoding + * bridges and pass all transactions. Mark them and real ProgIf 1 + * parts as subtractive. + */ + if ((pci_get_devid(dev) & 0xff00ffff) == 0x24008086 || + pci_read_config(dev, PCIR_PROGIF, 1) == PCIP_BRIDGE_PCI_SUBTRACTIVE) + sc->flags |= PCIB_SUBTRACTIVE; + + if (bootverbose) { + device_printf(dev, " domain %d\n", sc->domain); + device_printf(dev, " secondary bus %d\n", sc->secbus); + device_printf(dev, " subordinate bus %d\n", sc->subbus); + device_printf(dev, " I/O decode 0x%x-0x%x\n", sc->iobase, sc->iolimit); + if (pcib_is_nonprefetch_open(sc)) + device_printf(dev, " memory decode 0x%jx-0x%jx\n", + (uintmax_t)sc->membase, (uintmax_t)sc->memlimit); + if (pcib_is_prefetch_open(sc)) + device_printf(dev, " prefetched decode 0x%jx-0x%jx\n", + (uintmax_t)sc->pmembase, (uintmax_t)sc->pmemlimit); + else + device_printf(dev, " no prefetched decode\n"); + if (sc->flags & PCIB_SUBTRACTIVE) + device_printf(dev, " Subtractively decoded bridge.\n"); + } + + /* + * XXX If the secondary bus number is zero, we should assign a bus number + * since the BIOS hasn't, then initialise the bridge. A simple + * bus_alloc_resource with the a couple of busses seems like the right + * approach, but we don't know what busses the BIOS might have already + * assigned to other bridges on this bus that probe later than we do. + * + * If the subordinate bus number is less than the secondary bus number, + * we should pick a better value. One sensible alternative would be to + * pick 255; the only tradeoff here is that configuration transactions + * would be more widely routed than absolutely necessary. We could + * then do a walk of the tree later and fix it. + */ +} + +int +pcib_attach(device_t dev) +{ + struct pcib_softc *sc; + device_t child; + + pcib_attach_common(dev); + sc = device_get_softc(dev); + if (sc->secbus != 0) { + child = device_add_child(dev, "pci", sc->secbus); + if (child != NULL) + return(bus_generic_attach(dev)); + } + + /* no secondary bus; we should have fixed this */ + return(0); +} + +int +pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) +{ + struct pcib_softc *sc = device_get_softc(dev); + + switch (which) { + case PCIB_IVAR_DOMAIN: + *result = sc->domain; + return(0); + case PCIB_IVAR_BUS: + *result = sc->secbus; + return(0); + } + return(ENOENT); +} + +int +pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value) +{ + struct pcib_softc *sc = device_get_softc(dev); + + switch (which) { + case PCIB_IVAR_DOMAIN: + return(EINVAL); + case PCIB_IVAR_BUS: + sc->secbus = value; + return(0); + } + return(ENOENT); +} + +/* + * We have to trap resource allocation requests and ensure that the bridge + * is set up to, or capable of handling them. + */ +struct resource * +pcib_alloc_resource(device_t dev, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + struct pcib_softc *sc = device_get_softc(dev); + const char *name, *suffix; + int ok; + + /* + * Fail the allocation for this range if it's not supported. + */ + name = device_get_nameunit(child); + if (name == NULL) { + name = ""; + suffix = ""; + } else + suffix = " "; + switch (type) { + case SYS_RES_IOPORT: + ok = 0; + if (!pcib_is_io_open(sc)) + break; + ok = (start >= sc->iobase && end <= sc->iolimit); + + /* + * Make sure we allow access to VGA I/O addresses when the + * bridge has the "VGA Enable" bit set. + */ + if (!ok && pci_is_vga_ioport_range(start, end)) + ok = (sc->bridgectl & PCIB_BCR_VGA_ENABLE) ? 1 : 0; + + if ((sc->flags & PCIB_SUBTRACTIVE) == 0) { + if (!ok) { + if (start < sc->iobase) + start = sc->iobase; + if (end > sc->iolimit) + end = sc->iolimit; + if (start < end) + ok = 1; + } + } else { + ok = 1; +#if 0 + /* + * If we overlap with the subtractive range, then + * pick the upper range to use. + */ + if (start < sc->iolimit && end > sc->iobase) + start = sc->iolimit + 1; +#endif + } + if (end < start) { + device_printf(dev, "ioport: end (%lx) < start (%lx)\n", + end, start); + start = 0; + end = 0; + ok = 0; + } + if (!ok) { + device_printf(dev, "%s%srequested unsupported I/O " + "range 0x%lx-0x%lx (decoding 0x%x-0x%x)\n", + name, suffix, start, end, sc->iobase, sc->iolimit); + return (NULL); + } + if (bootverbose) + device_printf(dev, + "%s%srequested I/O range 0x%lx-0x%lx: in range\n", + name, suffix, start, end); + break; + + case SYS_RES_MEMORY: + ok = 0; + if (pcib_is_nonprefetch_open(sc)) + ok = ok || (start >= sc->membase && end <= sc->memlimit); + if (pcib_is_prefetch_open(sc)) + ok = ok || (start >= sc->pmembase && end <= sc->pmemlimit); + + /* + * Make sure we allow access to VGA memory addresses when the + * bridge has the "VGA Enable" bit set. + */ + if (!ok && pci_is_vga_memory_range(start, end)) + ok = (sc->bridgectl & PCIB_BCR_VGA_ENABLE) ? 1 : 0; + + if ((sc->flags & PCIB_SUBTRACTIVE) == 0) { + if (!ok) { + ok = 1; + if (flags & RF_PREFETCHABLE) { + if (pcib_is_prefetch_open(sc)) { + if (start < sc->pmembase) + start = sc->pmembase; + if (end > sc->pmemlimit) + end = sc->pmemlimit; + } else { + ok = 0; + } + } else { /* non-prefetchable */ + if (pcib_is_nonprefetch_open(sc)) { + if (start < sc->membase) + start = sc->membase; + if (end > sc->memlimit) + end = sc->memlimit; + } else { + ok = 0; + } + } + } + } else if (!ok) { + ok = 1; /* subtractive bridge: always ok */ +#if 0 + if (pcib_is_nonprefetch_open(sc)) { + if (start < sc->memlimit && end > sc->membase) + start = sc->memlimit + 1; + } + if (pcib_is_prefetch_open(sc)) { + if (start < sc->pmemlimit && end > sc->pmembase) + start = sc->pmemlimit + 1; + } +#endif + } + if (end < start) { + device_printf(dev, "memory: end (%lx) < start (%lx)\n", + end, start); + start = 0; + end = 0; + ok = 0; + } + if (!ok && bootverbose) + device_printf(dev, + "%s%srequested unsupported memory range %#lx-%#lx " + "(decoding %#jx-%#jx, %#jx-%#jx)\n", + name, suffix, start, end, + (uintmax_t)sc->membase, (uintmax_t)sc->memlimit, + (uintmax_t)sc->pmembase, (uintmax_t)sc->pmemlimit); + if (!ok) + return (NULL); + if (bootverbose) + device_printf(dev,"%s%srequested memory range " + "0x%lx-0x%lx: good\n", + name, suffix, start, end); + break; + + default: + break; + } + /* + * Bridge is OK decoding this resource, so pass it up. + */ + return (bus_generic_alloc_resource(dev, child, type, rid, start, end, + count, flags)); +} + +/* + * PCIB interface. + */ +int +pcib_maxslots(device_t dev) +{ + return(PCI_SLOTMAX); +} + +/* + * Since we are a child of a PCI bus, its parent must support the pcib interface. + */ +uint32_t +pcib_read_config(device_t dev, u_int b, u_int s, u_int f, u_int reg, int width) +{ + return(PCIB_READ_CONFIG(device_get_parent(device_get_parent(dev)), b, s, f, reg, width)); +} + +void +pcib_write_config(device_t dev, u_int b, u_int s, u_int f, u_int reg, uint32_t val, int width) +{ + PCIB_WRITE_CONFIG(device_get_parent(device_get_parent(dev)), b, s, f, reg, val, width); +} + +/* + * Route an interrupt across a PCI bridge. + */ +int +pcib_route_interrupt(device_t pcib, device_t dev, int pin) +{ + device_t bus; + int parent_intpin; + int intnum; + + /* + * + * The PCI standard defines a swizzle of the child-side device/intpin to + * the parent-side intpin as follows. + * + * device = device on child bus + * child_intpin = intpin on child bus slot (0-3) + * parent_intpin = intpin on parent bus slot (0-3) + * + * parent_intpin = (device + child_intpin) % 4 + */ + parent_intpin = (pci_get_slot(dev) + (pin - 1)) % 4; + + /* + * Our parent is a PCI bus. Its parent must export the pcib interface + * which includes the ability to route interrupts. + */ + bus = device_get_parent(pcib); + intnum = PCIB_ROUTE_INTERRUPT(device_get_parent(bus), pcib, parent_intpin + 1); + if (PCI_INTERRUPT_VALID(intnum) && bootverbose) { + device_printf(pcib, "slot %d INT%c is routed to irq %d\n", + pci_get_slot(dev), 'A' + pin - 1, intnum); + } + return(intnum); +} + +/* Pass request to alloc MSI/MSI-X messages up to the parent bridge. */ +int +pcib_alloc_msi(device_t pcib, device_t dev, int count, int maxcount, int *irqs) +{ + struct pcib_softc *sc = device_get_softc(pcib); + device_t bus; + + if (sc->flags & PCIB_DISABLE_MSI) + return (ENXIO); + bus = device_get_parent(pcib); + return (PCIB_ALLOC_MSI(device_get_parent(bus), dev, count, maxcount, + irqs)); +} + +/* Pass request to release MSI/MSI-X messages up to the parent bridge. */ +int +pcib_release_msi(device_t pcib, device_t dev, int count, int *irqs) +{ + device_t bus; + + bus = device_get_parent(pcib); + return (PCIB_RELEASE_MSI(device_get_parent(bus), dev, count, irqs)); +} + +/* Pass request to alloc an MSI-X message up to the parent bridge. */ +int +pcib_alloc_msix(device_t pcib, device_t dev, int *irq) +{ + struct pcib_softc *sc = device_get_softc(pcib); + device_t bus; + + if (sc->flags & PCIB_DISABLE_MSI) + return (ENXIO); + bus = device_get_parent(pcib); + return (PCIB_ALLOC_MSIX(device_get_parent(bus), dev, irq)); +} + +/* Pass request to release an MSI-X message up to the parent bridge. */ +int +pcib_release_msix(device_t pcib, device_t dev, int irq) +{ + device_t bus; + + bus = device_get_parent(pcib); + return (PCIB_RELEASE_MSIX(device_get_parent(bus), dev, irq)); +} + +/* Pass request to map MSI/MSI-X message up to parent bridge. */ +int +pcib_map_msi(device_t pcib, device_t dev, int irq, uint64_t *addr, + uint32_t *data) +{ + device_t bus; + int error; + + bus = device_get_parent(pcib); + error = PCIB_MAP_MSI(device_get_parent(bus), dev, irq, addr, data); + if (error) + return (error); + + pci_ht_map_msi(pcib, *addr); + return (0); +} + +/* + * Try to read the bus number of a host-PCI bridge using appropriate config + * registers. + */ +int +host_pcib_get_busno(pci_read_config_fn read_config, int bus, int slot, int func, + uint8_t *busnum) +{ + uint32_t id; + + id = read_config(bus, slot, func, PCIR_DEVVENDOR, 4); + if (id == 0xffffffff) + return (0); + + switch (id) { + case 0x12258086: + /* Intel 824?? */ + /* XXX This is a guess */ + /* *busnum = read_config(bus, slot, func, 0x41, 1); */ + *busnum = bus; + break; + case 0x84c48086: + /* Intel 82454KX/GX (Orion) */ + *busnum = read_config(bus, slot, func, 0x4a, 1); + break; + case 0x84ca8086: + /* + * For the 450nx chipset, there is a whole bundle of + * things pretending to be host bridges. The MIOC will + * be seen first and isn't really a pci bridge (the + * actual busses are attached to the PXB's). We need to + * read the registers of the MIOC to figure out the + * bus numbers for the PXB channels. + * + * Since the MIOC doesn't have a pci bus attached, we + * pretend it wasn't there. + */ + return (0); + case 0x84cb8086: + switch (slot) { + case 0x12: + /* Intel 82454NX PXB#0, Bus#A */ + *busnum = read_config(bus, 0x10, func, 0xd0, 1); + break; + case 0x13: + /* Intel 82454NX PXB#0, Bus#B */ + *busnum = read_config(bus, 0x10, func, 0xd1, 1) + 1; + break; + case 0x14: + /* Intel 82454NX PXB#1, Bus#A */ + *busnum = read_config(bus, 0x10, func, 0xd3, 1); + break; + case 0x15: + /* Intel 82454NX PXB#1, Bus#B */ + *busnum = read_config(bus, 0x10, func, 0xd4, 1) + 1; + break; + } + break; + + /* ServerWorks -- vendor 0x1166 */ + case 0x00051166: + case 0x00061166: + case 0x00081166: + case 0x00091166: + case 0x00101166: + case 0x00111166: + case 0x00171166: + case 0x01011166: + case 0x010f1014: + case 0x01101166: + case 0x02011166: + case 0x02251166: + case 0x03021014: + *busnum = read_config(bus, slot, func, 0x44, 1); + break; + + /* Compaq/HP -- vendor 0x0e11 */ + case 0x60100e11: + *busnum = read_config(bus, slot, func, 0xc8, 1); + break; + default: + /* Don't know how to read bus number. */ + return 0; + } + + return 1; +} diff --git a/freebsd/sys/dev/pci/pci_private.h b/freebsd/sys/dev/pci/pci_private.h new file mode 100644 index 00000000..93da2538 --- /dev/null +++ b/freebsd/sys/dev/pci/pci_private.h @@ -0,0 +1,116 @@ +/*- + * Copyright (c) 1997, Stefan Esser <se@freebsd.org> + * Copyright (c) 2000, Michael Smith <msmith@freebsd.org> + * Copyright (c) 2000, BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#ifndef _PCI_PRIVATE_HH_ +#define _PCI_PRIVATE_HH_ + +/* + * Export definitions of the pci bus so that we can more easily share + * it with "subclass" busses. + */ +DECLARE_CLASS(pci_driver); + +void pci_add_children(device_t dev, int domain, int busno, + size_t dinfo_size); +void pci_add_child(device_t bus, struct pci_devinfo *dinfo); +void pci_add_resources(device_t bus, device_t dev, int force, + uint32_t prefetchmask); +void pci_driver_added(device_t dev, driver_t *driver); +int pci_print_child(device_t dev, device_t child); +void pci_probe_nomatch(device_t dev, device_t child); +int pci_read_ivar(device_t dev, device_t child, int which, + uintptr_t *result); +int pci_write_ivar(device_t dev, device_t child, int which, + uintptr_t value); +int pci_setup_intr(device_t dev, device_t child, + struct resource *irq, int flags, driver_filter_t *filter, + driver_intr_t *intr, void *arg, void **cookiep); +int pci_teardown_intr(device_t dev, device_t child, + struct resource *irq, void *cookie); +int pci_get_vpd_ident_method(device_t dev, device_t child, + const char **identptr); +int pci_get_vpd_readonly_method(device_t dev, device_t child, + const char *kw, const char **vptr); +int pci_set_powerstate_method(device_t dev, device_t child, + int state); +int pci_get_powerstate_method(device_t dev, device_t child); +uint32_t pci_read_config_method(device_t dev, device_t child, + int reg, int width); +void pci_write_config_method(device_t dev, device_t child, + int reg, uint32_t val, int width); +int pci_enable_busmaster_method(device_t dev, device_t child); +int pci_disable_busmaster_method(device_t dev, device_t child); +int pci_enable_io_method(device_t dev, device_t child, int space); +int pci_disable_io_method(device_t dev, device_t child, int space); +int pci_find_extcap_method(device_t dev, device_t child, + int capability, int *capreg); +int pci_alloc_msi_method(device_t dev, device_t child, int *count); +int pci_alloc_msix_method(device_t dev, device_t child, int *count); +int pci_remap_msix_method(device_t dev, device_t child, + int count, const u_int *vectors); +int pci_release_msi_method(device_t dev, device_t child); +int pci_msi_count_method(device_t dev, device_t child); +int pci_msix_count_method(device_t dev, device_t child); +struct resource *pci_alloc_resource(device_t dev, device_t child, + int type, int *rid, u_long start, u_long end, u_long count, + u_int flags); +int pci_release_resource(device_t dev, device_t child, int type, + int rid, struct resource *r); +int pci_activate_resource(device_t dev, device_t child, int type, + int rid, struct resource *r); +void pci_delete_resource(device_t dev, device_t child, + int type, int rid); +struct resource_list *pci_get_resource_list (device_t dev, device_t child); +struct pci_devinfo *pci_read_device(device_t pcib, int d, int b, int s, int f, + size_t size); +void pci_print_verbose(struct pci_devinfo *dinfo); +int pci_freecfg(struct pci_devinfo *dinfo); +int pci_child_location_str_method(device_t cbdev, device_t child, + char *buf, size_t buflen); +int pci_child_pnpinfo_str_method(device_t cbdev, device_t child, + char *buf, size_t buflen); +int pci_assign_interrupt_method(device_t dev, device_t child); +int pci_resume(device_t dev); +int pci_suspend(device_t dev); + +/** Restore the config register state. The state must be previously + * saved with pci_cfg_save. However, the pci bus driver takes care of + * that. This function will also return the device to PCI_POWERSTATE_D0 + * if it is currently in a lower power mode. + */ +void pci_cfg_restore(device_t, struct pci_devinfo *); + +/** Save the config register state. Optionally set the power state to D3 + * if the third argument is non-zero. + */ +void pci_cfg_save(device_t, struct pci_devinfo *, int); + +#endif /* _PCI_PRIVATE_HH_ */ diff --git a/freebsd/sys/dev/pci/pci_user.c b/freebsd/sys/dev/pci/pci_user.c new file mode 100644 index 00000000..c0b1eda0 --- /dev/null +++ b/freebsd/sys/dev/pci/pci_user.c @@ -0,0 +1,748 @@ +#include <freebsd/machine/rtems-bsd-config.h> + +/*- + * Copyright (c) 1997, Stefan Esser <se@freebsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <freebsd/sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <freebsd/local/opt_bus.h> /* XXX trim includes */ +#include <freebsd/local/opt_compat.h> + +#include <freebsd/sys/param.h> +#include <freebsd/sys/systm.h> +#include <freebsd/sys/malloc.h> +#include <freebsd/sys/module.h> +#include <freebsd/sys/linker.h> +#include <freebsd/sys/fcntl.h> +#include <freebsd/sys/conf.h> +#include <freebsd/sys/kernel.h> +#include <freebsd/sys/proc.h> +#include <freebsd/sys/queue.h> +#include <freebsd/sys/types.h> + +#include <freebsd/vm/vm.h> +#include <freebsd/vm/pmap.h> +#ifndef __rtems__ +#include <freebsd/vm/vm_extern.h> +#endif /* __rtems__ */ + +#include <freebsd/sys/bus.h> +#include <freebsd/machine/bus.h> +#include <freebsd/sys/rman.h> +#include <freebsd/machine/resource.h> + +#include <freebsd/sys/pciio.h> +#include <freebsd/dev/pci/pcireg.h> +#include <freebsd/dev/pci/pcivar.h> + +#include <freebsd/local/pcib_if.h> +#include <freebsd/local/pci_if.h> + +/* + * This is the user interface to PCI configuration space. + */ + +static d_open_t pci_open; +static d_close_t pci_close; +static int pci_conf_match(struct pci_match_conf *matches, int num_matches, + struct pci_conf *match_buf); +static d_ioctl_t pci_ioctl; + +struct cdevsw pcicdev = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = pci_open, + .d_close = pci_close, + .d_ioctl = pci_ioctl, + .d_name = "pci", +}; + +static int +pci_open(struct cdev *dev, int oflags, int devtype, struct thread *td) +{ + int error; + + if (oflags & FWRITE) { + error = securelevel_gt(td->td_ucred, 0); + if (error) + return (error); + } + + return (0); +} + +static int +pci_close(struct cdev *dev, int flag, int devtype, struct thread *td) +{ + return 0; +} + +/* + * Match a single pci_conf structure against an array of pci_match_conf + * structures. The first argument, 'matches', is an array of num_matches + * pci_match_conf structures. match_buf is a pointer to the pci_conf + * structure that will be compared to every entry in the matches array. + * This function returns 1 on failure, 0 on success. + */ +static int +pci_conf_match(struct pci_match_conf *matches, int num_matches, + struct pci_conf *match_buf) +{ + int i; + + if ((matches == NULL) || (match_buf == NULL) || (num_matches <= 0)) + return(1); + + for (i = 0; i < num_matches; i++) { + /* + * I'm not sure why someone would do this...but... + */ + if (matches[i].flags == PCI_GETCONF_NO_MATCH) + continue; + + /* + * Look at each of the match flags. If it's set, do the + * comparison. If the comparison fails, we don't have a + * match, go on to the next item if there is one. + */ + if (((matches[i].flags & PCI_GETCONF_MATCH_DOMAIN) != 0) + && (match_buf->pc_sel.pc_domain != + matches[i].pc_sel.pc_domain)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_BUS) != 0) + && (match_buf->pc_sel.pc_bus != matches[i].pc_sel.pc_bus)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_DEV) != 0) + && (match_buf->pc_sel.pc_dev != matches[i].pc_sel.pc_dev)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_FUNC) != 0) + && (match_buf->pc_sel.pc_func != matches[i].pc_sel.pc_func)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_VENDOR) != 0) + && (match_buf->pc_vendor != matches[i].pc_vendor)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_DEVICE) != 0) + && (match_buf->pc_device != matches[i].pc_device)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_CLASS) != 0) + && (match_buf->pc_class != matches[i].pc_class)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_UNIT) != 0) + && (match_buf->pd_unit != matches[i].pd_unit)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_NAME) != 0) + && (strncmp(matches[i].pd_name, match_buf->pd_name, + sizeof(match_buf->pd_name)) != 0)) + continue; + + return(0); + } + + return(1); +} + +#if defined(COMPAT_FREEBSD4) || defined(COMPAT_FREEBSD5) || \ + defined(COMPAT_FREEBSD6) +#define PRE7_COMPAT + +typedef enum { + PCI_GETCONF_NO_MATCH_OLD = 0x00, + PCI_GETCONF_MATCH_BUS_OLD = 0x01, + PCI_GETCONF_MATCH_DEV_OLD = 0x02, + PCI_GETCONF_MATCH_FUNC_OLD = 0x04, + PCI_GETCONF_MATCH_NAME_OLD = 0x08, + PCI_GETCONF_MATCH_UNIT_OLD = 0x10, + PCI_GETCONF_MATCH_VENDOR_OLD = 0x20, + PCI_GETCONF_MATCH_DEVICE_OLD = 0x40, + PCI_GETCONF_MATCH_CLASS_OLD = 0x80 +} pci_getconf_flags_old; + +struct pcisel_old { + u_int8_t pc_bus; /* bus number */ + u_int8_t pc_dev; /* device on this bus */ + u_int8_t pc_func; /* function on this device */ +}; + +struct pci_conf_old { + struct pcisel_old pc_sel; /* bus+slot+function */ + u_int8_t pc_hdr; /* PCI header type */ + u_int16_t pc_subvendor; /* card vendor ID */ + u_int16_t pc_subdevice; /* card device ID, assigned by + card vendor */ + u_int16_t pc_vendor; /* chip vendor ID */ + u_int16_t pc_device; /* chip device ID, assigned by + chip vendor */ + u_int8_t pc_class; /* chip PCI class */ + u_int8_t pc_subclass; /* chip PCI subclass */ + u_int8_t pc_progif; /* chip PCI programming interface */ + u_int8_t pc_revid; /* chip revision ID */ + char pd_name[PCI_MAXNAMELEN + 1]; /* device name */ + u_long pd_unit; /* device unit number */ +}; + +struct pci_match_conf_old { + struct pcisel_old pc_sel; /* bus+slot+function */ + char pd_name[PCI_MAXNAMELEN + 1]; /* device name */ + u_long pd_unit; /* Unit number */ + u_int16_t pc_vendor; /* PCI Vendor ID */ + u_int16_t pc_device; /* PCI Device ID */ + u_int8_t pc_class; /* PCI class */ + pci_getconf_flags_old flags; /* Matching expression */ +}; + +struct pci_io_old { + struct pcisel_old pi_sel; /* device to operate on */ + int pi_reg; /* configuration register to examine */ + int pi_width; /* width (in bytes) of read or write */ + u_int32_t pi_data; /* data to write or result of read */ +}; + +#define PCIOCGETCONF_OLD _IOWR('p', 1, struct pci_conf_io) +#define PCIOCREAD_OLD _IOWR('p', 2, struct pci_io_old) +#define PCIOCWRITE_OLD _IOWR('p', 3, struct pci_io_old) + +static int pci_conf_match_old(struct pci_match_conf_old *matches, + int num_matches, struct pci_conf *match_buf); + +static int +pci_conf_match_old(struct pci_match_conf_old *matches, int num_matches, + struct pci_conf *match_buf) +{ + int i; + + if ((matches == NULL) || (match_buf == NULL) || (num_matches <= 0)) + return(1); + + for (i = 0; i < num_matches; i++) { + if (match_buf->pc_sel.pc_domain != 0) + continue; + + /* + * I'm not sure why someone would do this...but... + */ + if (matches[i].flags == PCI_GETCONF_NO_MATCH_OLD) + continue; + + /* + * Look at each of the match flags. If it's set, do the + * comparison. If the comparison fails, we don't have a + * match, go on to the next item if there is one. + */ + if (((matches[i].flags & PCI_GETCONF_MATCH_BUS_OLD) != 0) + && (match_buf->pc_sel.pc_bus != matches[i].pc_sel.pc_bus)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_DEV_OLD) != 0) + && (match_buf->pc_sel.pc_dev != matches[i].pc_sel.pc_dev)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_FUNC_OLD) != 0) + && (match_buf->pc_sel.pc_func != matches[i].pc_sel.pc_func)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_VENDOR_OLD) != 0) + && (match_buf->pc_vendor != matches[i].pc_vendor)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_DEVICE_OLD) != 0) + && (match_buf->pc_device != matches[i].pc_device)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_CLASS_OLD) != 0) + && (match_buf->pc_class != matches[i].pc_class)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_UNIT_OLD) != 0) + && (match_buf->pd_unit != matches[i].pd_unit)) + continue; + + if (((matches[i].flags & PCI_GETCONF_MATCH_NAME_OLD) != 0) + && (strncmp(matches[i].pd_name, match_buf->pd_name, + sizeof(match_buf->pd_name)) != 0)) + continue; + + return(0); + } + + return(1); +} + +#endif + +static int +pci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) +{ + device_t pcidev, brdev; + void *confdata; + const char *name; + struct devlist *devlist_head; + struct pci_conf_io *cio; + struct pci_devinfo *dinfo; + struct pci_io *io; + struct pci_bar_io *bio; + struct pci_match_conf *pattern_buf; + struct resource_list_entry *rle; + uint32_t value; + size_t confsz, iolen, pbufsz; + int error, ionum, i, num_patterns; +#ifdef PRE7_COMPAT + struct pci_conf_old conf_old; + struct pci_io iodata; + struct pci_io_old *io_old; + struct pci_match_conf_old *pattern_buf_old; + + io_old = NULL; + pattern_buf_old = NULL; + + if (!(flag & FWRITE) && cmd != PCIOCGETBAR && + cmd != PCIOCGETCONF && cmd != PCIOCGETCONF_OLD) + return EPERM; +#else + if (!(flag & FWRITE) && cmd != PCIOCGETBAR && cmd != PCIOCGETCONF) + return EPERM; +#endif + + switch(cmd) { +#ifdef PRE7_COMPAT + case PCIOCGETCONF_OLD: + /* FALLTHROUGH */ +#endif + case PCIOCGETCONF: + cio = (struct pci_conf_io *)data; + + pattern_buf = NULL; + num_patterns = 0; + dinfo = NULL; + + cio->num_matches = 0; + + /* + * If the user specified an offset into the device list, + * but the list has changed since they last called this + * ioctl, tell them that the list has changed. They will + * have to get the list from the beginning. + */ + if ((cio->offset != 0) + && (cio->generation != pci_generation)){ + cio->status = PCI_GETCONF_LIST_CHANGED; + error = 0; + break; + } + + /* + * Check to see whether the user has asked for an offset + * past the end of our list. + */ + if (cio->offset >= pci_numdevs) { + cio->status = PCI_GETCONF_LAST_DEVICE; + error = 0; + break; + } + + /* get the head of the device queue */ + devlist_head = &pci_devq; + + /* + * Determine how much room we have for pci_conf structures. + * Round the user's buffer size down to the nearest + * multiple of sizeof(struct pci_conf) in case the user + * didn't specify a multiple of that size. + */ +#ifdef PRE7_COMPAT + if (cmd == PCIOCGETCONF_OLD) + confsz = sizeof(struct pci_conf_old); + else +#endif + confsz = sizeof(struct pci_conf); + iolen = min(cio->match_buf_len - (cio->match_buf_len % confsz), + pci_numdevs * confsz); + + /* + * Since we know that iolen is a multiple of the size of + * the pciconf union, it's okay to do this. + */ + ionum = iolen / confsz; + + /* + * If this test is true, the user wants the pci_conf + * structures returned to match the supplied entries. + */ + if ((cio->num_patterns > 0) && (cio->num_patterns < pci_numdevs) + && (cio->pat_buf_len > 0)) { + /* + * pat_buf_len needs to be: + * num_patterns * sizeof(struct pci_match_conf) + * While it is certainly possible the user just + * allocated a large buffer, but set the number of + * matches correctly, it is far more likely that + * their kernel doesn't match the userland utility + * they're using. It's also possible that the user + * forgot to initialize some variables. Yes, this + * may be overly picky, but I hazard to guess that + * it's far more likely to just catch folks that + * updated their kernel but not their userland. + */ +#ifdef PRE7_COMPAT + if (cmd == PCIOCGETCONF_OLD) + pbufsz = sizeof(struct pci_match_conf_old); + else +#endif + pbufsz = sizeof(struct pci_match_conf); + if (cio->num_patterns * pbufsz != cio->pat_buf_len) { + /* The user made a mistake, return an error. */ + cio->status = PCI_GETCONF_ERROR; + error = EINVAL; + break; + } + + /* + * Allocate a buffer to hold the patterns. + */ +#ifdef PRE7_COMPAT + if (cmd == PCIOCGETCONF_OLD) { + pattern_buf_old = malloc(cio->pat_buf_len, + M_TEMP, M_WAITOK); + error = copyin(cio->patterns, + pattern_buf_old, cio->pat_buf_len); + } else +#endif + { + pattern_buf = malloc(cio->pat_buf_len, M_TEMP, + M_WAITOK); + error = copyin(cio->patterns, pattern_buf, + cio->pat_buf_len); + } + if (error != 0) { + error = EINVAL; + goto getconfexit; + } + num_patterns = cio->num_patterns; + } else if ((cio->num_patterns > 0) + || (cio->pat_buf_len > 0)) { + /* + * The user made a mistake, spit out an error. + */ + cio->status = PCI_GETCONF_ERROR; + error = EINVAL; + break; + } + + /* + * Go through the list of devices and copy out the devices + * that match the user's criteria. + */ + for (cio->num_matches = 0, error = 0, i = 0, + dinfo = STAILQ_FIRST(devlist_head); + (dinfo != NULL) && (cio->num_matches < ionum) + && (error == 0) && (i < pci_numdevs) && (dinfo != NULL); + dinfo = STAILQ_NEXT(dinfo, pci_links), i++) { + + if (i < cio->offset) + continue; + + /* Populate pd_name and pd_unit */ + name = NULL; + if (dinfo->cfg.dev) + name = device_get_name(dinfo->cfg.dev); + if (name) { + strncpy(dinfo->conf.pd_name, name, + sizeof(dinfo->conf.pd_name)); + dinfo->conf.pd_name[PCI_MAXNAMELEN] = 0; + dinfo->conf.pd_unit = + device_get_unit(dinfo->cfg.dev); + } else { + dinfo->conf.pd_name[0] = '\0'; + dinfo->conf.pd_unit = 0; + } + +#ifdef PRE7_COMPAT + if ((cmd == PCIOCGETCONF_OLD && + (pattern_buf_old == NULL || + pci_conf_match_old(pattern_buf_old, num_patterns, + &dinfo->conf) == 0)) || + (cmd == PCIOCGETCONF && + (pattern_buf == NULL || + pci_conf_match(pattern_buf, num_patterns, + &dinfo->conf) == 0))) { +#else + if (pattern_buf == NULL || + pci_conf_match(pattern_buf, num_patterns, + &dinfo->conf) == 0) { +#endif + /* + * If we've filled up the user's buffer, + * break out at this point. Since we've + * got a match here, we'll pick right back + * up at the matching entry. We can also + * tell the user that there are more matches + * left. + */ + if (cio->num_matches >= ionum) + break; + +#ifdef PRE7_COMPAT + if (cmd == PCIOCGETCONF_OLD) { + conf_old.pc_sel.pc_bus = + dinfo->conf.pc_sel.pc_bus; + conf_old.pc_sel.pc_dev = + dinfo->conf.pc_sel.pc_dev; + conf_old.pc_sel.pc_func = + dinfo->conf.pc_sel.pc_func; + conf_old.pc_hdr = dinfo->conf.pc_hdr; + conf_old.pc_subvendor = + dinfo->conf.pc_subvendor; + conf_old.pc_subdevice = + dinfo->conf.pc_subdevice; + conf_old.pc_vendor = + dinfo->conf.pc_vendor; + conf_old.pc_device = + dinfo->conf.pc_device; + conf_old.pc_class = + dinfo->conf.pc_class; + conf_old.pc_subclass = + dinfo->conf.pc_subclass; + conf_old.pc_progif = + dinfo->conf.pc_progif; + conf_old.pc_revid = + dinfo->conf.pc_revid; + strncpy(conf_old.pd_name, + dinfo->conf.pd_name, + sizeof(conf_old.pd_name)); + conf_old.pd_name[PCI_MAXNAMELEN] = 0; + conf_old.pd_unit = + dinfo->conf.pd_unit; + confdata = &conf_old; + } else +#endif + confdata = &dinfo->conf; + /* Only if we can copy it out do we count it. */ + if (!(error = copyout(confdata, + (caddr_t)cio->matches + + confsz * cio->num_matches, confsz))) + cio->num_matches++; + } + } + + /* + * Set the pointer into the list, so if the user is getting + * n records at a time, where n < pci_numdevs, + */ + cio->offset = i; + + /* + * Set the generation, the user will need this if they make + * another ioctl call with offset != 0. + */ + cio->generation = pci_generation; + + /* + * If this is the last device, inform the user so he won't + * bother asking for more devices. If dinfo isn't NULL, we + * know that there are more matches in the list because of + * the way the traversal is done. + */ + if (dinfo == NULL) + cio->status = PCI_GETCONF_LAST_DEVICE; + else + cio->status = PCI_GETCONF_MORE_DEVS; + +getconfexit: + if (pattern_buf != NULL) + free(pattern_buf, M_TEMP); +#ifdef PRE7_COMPAT + if (pattern_buf_old != NULL) + free(pattern_buf_old, M_TEMP); +#endif + + break; + +#ifdef PRE7_COMPAT + case PCIOCREAD_OLD: + case PCIOCWRITE_OLD: + io_old = (struct pci_io_old *)data; + iodata.pi_sel.pc_domain = 0; + iodata.pi_sel.pc_bus = io_old->pi_sel.pc_bus; + iodata.pi_sel.pc_dev = io_old->pi_sel.pc_dev; + iodata.pi_sel.pc_func = io_old->pi_sel.pc_func; + iodata.pi_reg = io_old->pi_reg; + iodata.pi_width = io_old->pi_width; + iodata.pi_data = io_old->pi_data; + data = (caddr_t)&iodata; + /* FALLTHROUGH */ +#endif + case PCIOCREAD: + case PCIOCWRITE: + io = (struct pci_io *)data; + switch(io->pi_width) { + case 4: + case 2: + case 1: + /* Make sure register is not negative and aligned. */ + if (io->pi_reg < 0 || + io->pi_reg & (io->pi_width - 1)) { + error = EINVAL; + break; + } + /* + * Assume that the user-level bus number is + * in fact the physical PCI bus number. + * Look up the grandparent, i.e. the bridge device, + * so that we can issue configuration space cycles. + */ + pcidev = pci_find_dbsf(io->pi_sel.pc_domain, + io->pi_sel.pc_bus, io->pi_sel.pc_dev, + io->pi_sel.pc_func); + if (pcidev) { + brdev = device_get_parent( + device_get_parent(pcidev)); + +#ifdef PRE7_COMPAT + if (cmd == PCIOCWRITE || cmd == PCIOCWRITE_OLD) +#else + if (cmd == PCIOCWRITE) +#endif + PCIB_WRITE_CONFIG(brdev, + io->pi_sel.pc_bus, + io->pi_sel.pc_dev, + io->pi_sel.pc_func, + io->pi_reg, + io->pi_data, + io->pi_width); +#ifdef PRE7_COMPAT + else if (cmd == PCIOCREAD_OLD) + io_old->pi_data = + PCIB_READ_CONFIG(brdev, + io->pi_sel.pc_bus, + io->pi_sel.pc_dev, + io->pi_sel.pc_func, + io->pi_reg, + io->pi_width); +#endif + else + io->pi_data = + PCIB_READ_CONFIG(brdev, + io->pi_sel.pc_bus, + io->pi_sel.pc_dev, + io->pi_sel.pc_func, + io->pi_reg, + io->pi_width); + error = 0; + } else { +#ifdef COMPAT_FREEBSD4 + if (cmd == PCIOCREAD_OLD) { + io_old->pi_data = -1; + error = 0; + } else +#endif + error = ENODEV; + } + break; + default: + error = EINVAL; + break; + } + break; + + case PCIOCGETBAR: + bio = (struct pci_bar_io *)data; + + /* + * Assume that the user-level bus number is + * in fact the physical PCI bus number. + */ + pcidev = pci_find_dbsf(bio->pbi_sel.pc_domain, + bio->pbi_sel.pc_bus, bio->pbi_sel.pc_dev, + bio->pbi_sel.pc_func); + if (pcidev == NULL) { + error = ENODEV; + break; + } + dinfo = device_get_ivars(pcidev); + + /* + * Look for a resource list entry matching the requested BAR. + * + * XXX: This will not find BARs that are not initialized, but + * maybe that is ok? + */ + rle = resource_list_find(&dinfo->resources, SYS_RES_MEMORY, + bio->pbi_reg); + if (rle == NULL) + rle = resource_list_find(&dinfo->resources, + SYS_RES_IOPORT, bio->pbi_reg); + if (rle == NULL || rle->res == NULL) { + error = EINVAL; + break; + } + + /* + * Ok, we have a resource for this BAR. Read the lower + * 32 bits to get any flags. + */ + value = pci_read_config(pcidev, bio->pbi_reg, 4); + if (PCI_BAR_MEM(value)) { + if (rle->type != SYS_RES_MEMORY) { + error = EINVAL; + break; + } + value &= ~PCIM_BAR_MEM_BASE; + } else { + if (rle->type != SYS_RES_IOPORT) { + error = EINVAL; + break; + } + value &= ~PCIM_BAR_IO_BASE; + } + bio->pbi_base = rman_get_start(rle->res) | value; + bio->pbi_length = rman_get_size(rle->res); + + /* + * Check the command register to determine if this BAR + * is enabled. + */ + value = pci_read_config(pcidev, PCIR_COMMAND, 2); + if (rle->type == SYS_RES_MEMORY) + bio->pbi_enabled = (value & PCIM_CMD_MEMEN) != 0; + else + bio->pbi_enabled = (value & PCIM_CMD_PORTEN) != 0; + error = 0; + break; + default: + error = ENOTTY; + break; + } + + return (error); +} diff --git a/freebsd/sys/dev/pci/pcib_private.h b/freebsd/sys/dev/pci/pcib_private.h new file mode 100644 index 00000000..20f8da92 --- /dev/null +++ b/freebsd/sys/dev/pci/pcib_private.h @@ -0,0 +1,86 @@ +/*- + * Copyright (c) 1994,1995 Stefan Esser, Wolfgang StanglMeier + * Copyright (c) 2000 Michael Smith <msmith@freebsd.org> + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef __PCIB_PRIVATE_HH__ +#define __PCIB_PRIVATE_HH__ + +/* + * Export portions of generic PCI:PCI bridge support so that it can be + * used by subclasses. + */ + +/* + * Bridge-specific data. + */ +struct pcib_softc +{ + device_t dev; + uint32_t flags; /* flags */ +#define PCIB_SUBTRACTIVE 0x1 +#define PCIB_DISABLE_MSI 0x2 + uint16_t command; /* command register */ + u_int domain; /* domain number */ + u_int pribus; /* primary bus number */ + u_int secbus; /* secondary bus number */ + u_int subbus; /* subordinate bus number */ + pci_addr_t pmembase; /* base address of prefetchable memory */ + pci_addr_t pmemlimit; /* topmost address of prefetchable memory */ + pci_addr_t membase; /* base address of memory window */ + pci_addr_t memlimit; /* topmost address of memory window */ + uint32_t iobase; /* base address of port window */ + uint32_t iolimit; /* topmost address of port window */ + uint16_t secstat; /* secondary bus status register */ + uint16_t bridgectl; /* bridge control register */ + uint8_t seclat; /* secondary bus latency timer */ +}; + +typedef uint32_t pci_read_config_fn(int b, int s, int f, int reg, int width); + +int host_pcib_get_busno(pci_read_config_fn read_config, int bus, + int slot, int func, uint8_t *busnum); +int pcib_attach(device_t dev); +void pcib_attach_common(device_t dev); +int pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result); +int pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value); +struct resource *pcib_alloc_resource(device_t dev, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags); +int pcib_maxslots(device_t dev); +uint32_t pcib_read_config(device_t dev, u_int b, u_int s, u_int f, u_int reg, int width); +void pcib_write_config(device_t dev, u_int b, u_int s, u_int f, u_int reg, uint32_t val, int width); +int pcib_route_interrupt(device_t pcib, device_t dev, int pin); +int pcib_alloc_msi(device_t pcib, device_t dev, int count, int maxcount, int *irqs); +int pcib_release_msi(device_t pcib, device_t dev, int count, int *irqs); +int pcib_alloc_msix(device_t pcib, device_t dev, int *irq); +int pcib_release_msix(device_t pcib, device_t dev, int irq); +int pcib_map_msi(device_t pcib, device_t dev, int irq, uint64_t *addr, uint32_t *data); + +#endif diff --git a/freebsd/sys/dev/pci/pcireg.h b/freebsd/sys/dev/pci/pcireg.h new file mode 100644 index 00000000..9b4ad87b --- /dev/null +++ b/freebsd/sys/dev/pci/pcireg.h @@ -0,0 +1,750 @@ +/*- + * Copyright (c) 1997, Stefan Esser <se@freebsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +/* + * PCIM_xxx: mask to locate subfield in register + * PCIR_xxx: config register offset + * PCIC_xxx: device class + * PCIS_xxx: device subclass + * PCIP_xxx: device programming interface + * PCIV_xxx: PCI vendor ID (only required to fixup ancient devices) + * PCID_xxx: device ID + * PCIY_xxx: capability identification number + * PCIZ_xxx: extended capability identification number + */ + +/* some PCI bus constants */ +#define PCI_DOMAINMAX 65535 /* highest supported domain number */ +#define PCI_BUSMAX 255 /* highest supported bus number */ +#define PCI_SLOTMAX 31 /* highest supported slot number */ +#define PCI_FUNCMAX 7 /* highest supported function number */ +#define PCI_REGMAX 255 /* highest supported config register addr. */ +#define PCIE_REGMAX 4095 /* highest supported config register addr. */ +#define PCI_MAXHDRTYPE 2 + +/* PCI config header registers for all devices */ + +#define PCIR_DEVVENDOR 0x00 +#define PCIR_VENDOR 0x00 +#define PCIR_DEVICE 0x02 +#define PCIR_COMMAND 0x04 +#define PCIM_CMD_PORTEN 0x0001 +#define PCIM_CMD_MEMEN 0x0002 +#define PCIM_CMD_BUSMASTEREN 0x0004 +#define PCIM_CMD_SPECIALEN 0x0008 +#define PCIM_CMD_MWRICEN 0x0010 +#define PCIM_CMD_PERRESPEN 0x0040 +#define PCIM_CMD_SERRESPEN 0x0100 +#define PCIM_CMD_BACKTOBACK 0x0200 +#define PCIM_CMD_INTxDIS 0x0400 +#define PCIR_STATUS 0x06 +#define PCIM_STATUS_CAPPRESENT 0x0010 +#define PCIM_STATUS_66CAPABLE 0x0020 +#define PCIM_STATUS_BACKTOBACK 0x0080 +#define PCIM_STATUS_MDPERR 0x0100 +#define PCIM_STATUS_PERRREPORT PCIM_STATUS_MDPERR +#define PCIM_STATUS_SEL_FAST 0x0000 +#define PCIM_STATUS_SEL_MEDIMUM 0x0200 +#define PCIM_STATUS_SEL_SLOW 0x0400 +#define PCIM_STATUS_SEL_MASK 0x0600 +#define PCIM_STATUS_STABORT 0x0800 +#define PCIM_STATUS_RTABORT 0x1000 +#define PCIM_STATUS_RMABORT 0x2000 +#define PCIM_STATUS_SERR 0x4000 +#define PCIM_STATUS_PERR 0x8000 +#define PCIR_REVID 0x08 +#define PCIR_PROGIF 0x09 +#define PCIR_SUBCLASS 0x0a +#define PCIR_CLASS 0x0b +#define PCIR_CACHELNSZ 0x0c +#define PCIR_LATTIMER 0x0d +#define PCIR_HDRTYPE 0x0e +#define PCIM_HDRTYPE 0x7f +#define PCIM_HDRTYPE_NORMAL 0x00 +#define PCIM_HDRTYPE_BRIDGE 0x01 +#define PCIM_HDRTYPE_CARDBUS 0x02 +#define PCIM_MFDEV 0x80 +#define PCIR_BIST 0x0f + +/* Capability Register Offsets */ + +#define PCICAP_ID 0x0 +#define PCICAP_NEXTPTR 0x1 + +/* Capability Identification Numbers */ + +#define PCIY_PMG 0x01 /* PCI Power Management */ +#define PCIY_AGP 0x02 /* AGP */ +#define PCIY_VPD 0x03 /* Vital Product Data */ +#define PCIY_SLOTID 0x04 /* Slot Identification */ +#define PCIY_MSI 0x05 /* Message Signaled Interrupts */ +#define PCIY_CHSWP 0x06 /* CompactPCI Hot Swap */ +#define PCIY_PCIX 0x07 /* PCI-X */ +#define PCIY_HT 0x08 /* HyperTransport */ +#define PCIY_VENDOR 0x09 /* Vendor Unique */ +#define PCIY_DEBUG 0x0a /* Debug port */ +#define PCIY_CRES 0x0b /* CompactPCI central resource control */ +#define PCIY_HOTPLUG 0x0c /* PCI Hot-Plug */ +#define PCIY_SUBVENDOR 0x0d /* PCI-PCI bridge subvendor ID */ +#define PCIY_AGP8X 0x0e /* AGP 8x */ +#define PCIY_SECDEV 0x0f /* Secure Device */ +#define PCIY_EXPRESS 0x10 /* PCI Express */ +#define PCIY_MSIX 0x11 /* MSI-X */ +#define PCIY_SATA 0x12 /* SATA */ +#define PCIY_PCIAF 0x13 /* PCI Advanced Features */ + +/* Extended Capability Register Fields */ + +#define PCIR_EXTCAP 0x100 +#define PCIM_EXTCAP_ID 0x0000ffff +#define PCIM_EXTCAP_VER 0x000f0000 +#define PCIM_EXTCAP_NEXTPTR 0xfff00000 +#define PCI_EXTCAP_ID(ecap) ((ecap) & PCIM_EXTCAP_ID) +#define PCI_EXTCAP_VER(ecap) (((ecap) & PCIM_EXTCAP_VER) >> 16) +#define PCI_EXTCAP_NEXTPTR(ecap) (((ecap) & PCIM_EXTCAP_NEXTPTR) >> 20) + +/* Extended Capability Identification Numbers */ + +#define PCIZ_AER 0x0001 /* Advanced Error Reporting */ +#define PCIZ_VC 0x0002 /* Virtual Channel */ +#define PCIZ_SERNUM 0x0003 /* Device Serial Number */ +#define PCIZ_PWRBDGT 0x0004 /* Power Budgeting */ +#define PCIZ_VENDOR 0x000b /* Vendor Unique */ +#define PCIZ_ACS 0x000d /* Access Control Services */ +#define PCIZ_ARI 0x000e /* Alternative Routing-ID Interpretation */ +#define PCIZ_ATS 0x000f /* Address Translation Services */ +#define PCIZ_SRIOV 0x0010 /* Single Root IO Virtualization */ + +/* config registers for header type 0 devices */ + +#define PCIR_BARS 0x10 +#define PCIR_BAR(x) (PCIR_BARS + (x) * 4) +#define PCIR_MAX_BAR_0 5 +#define PCI_RID2BAR(rid) (((rid) - PCIR_BARS) / 4) +#define PCI_BAR_IO(x) (((x) & PCIM_BAR_SPACE) == PCIM_BAR_IO_SPACE) +#define PCI_BAR_MEM(x) (((x) & PCIM_BAR_SPACE) == PCIM_BAR_MEM_SPACE) +#define PCIM_BAR_SPACE 0x00000001 +#define PCIM_BAR_MEM_SPACE 0 +#define PCIM_BAR_IO_SPACE 1 +#define PCIM_BAR_MEM_TYPE 0x00000006 +#define PCIM_BAR_MEM_32 0 +#define PCIM_BAR_MEM_1MB 2 /* Locate below 1MB in PCI <= 2.1 */ +#define PCIM_BAR_MEM_64 4 +#define PCIM_BAR_MEM_PREFETCH 0x00000008 +#define PCIM_BAR_MEM_BASE 0xfffffffffffffff0ULL +#define PCIM_BAR_IO_RESERVED 0x00000002 +#define PCIM_BAR_IO_BASE 0xfffffffc +#define PCIR_CIS 0x28 +#define PCIM_CIS_ASI_MASK 0x00000007 +#define PCIM_CIS_ASI_CONFIG 0 +#define PCIM_CIS_ASI_BAR0 1 +#define PCIM_CIS_ASI_BAR1 2 +#define PCIM_CIS_ASI_BAR2 3 +#define PCIM_CIS_ASI_BAR3 4 +#define PCIM_CIS_ASI_BAR4 5 +#define PCIM_CIS_ASI_BAR5 6 +#define PCIM_CIS_ASI_ROM 7 +#define PCIM_CIS_ADDR_MASK 0x0ffffff8 +#define PCIM_CIS_ROM_MASK 0xf0000000 +#define PCIM_CIS_CONFIG_MASK 0xff +#define PCIR_SUBVEND_0 0x2c +#define PCIR_SUBDEV_0 0x2e +#define PCIR_BIOS 0x30 +#define PCIM_BIOS_ENABLE 0x01 +#define PCIM_BIOS_ADDR_MASK 0xfffff800 +#define PCIR_CAP_PTR 0x34 +#define PCIR_INTLINE 0x3c +#define PCIR_INTPIN 0x3d +#define PCIR_MINGNT 0x3e +#define PCIR_MAXLAT 0x3f + +/* config registers for header type 1 (PCI-to-PCI bridge) devices */ + +#define PCIR_MAX_BAR_1 1 +#define PCIR_SECSTAT_1 0x1e + +#define PCIR_PRIBUS_1 0x18 +#define PCIR_SECBUS_1 0x19 +#define PCIR_SUBBUS_1 0x1a +#define PCIR_SECLAT_1 0x1b + +#define PCIR_IOBASEL_1 0x1c +#define PCIR_IOLIMITL_1 0x1d +#define PCIR_IOBASEH_1 0x30 +#define PCIR_IOLIMITH_1 0x32 +#define PCIM_BRIO_16 0x0 +#define PCIM_BRIO_32 0x1 +#define PCIM_BRIO_MASK 0xf + +#define PCIR_MEMBASE_1 0x20 +#define PCIR_MEMLIMIT_1 0x22 + +#define PCIR_PMBASEL_1 0x24 +#define PCIR_PMLIMITL_1 0x26 +#define PCIR_PMBASEH_1 0x28 +#define PCIR_PMLIMITH_1 0x2c +#define PCIM_BRPM_32 0x0 +#define PCIM_BRPM_64 0x1 +#define PCIM_BRPM_MASK 0xf + +#define PCIR_BRIDGECTL_1 0x3e + +/* config registers for header type 2 (CardBus) devices */ + +#define PCIR_MAX_BAR_2 0 +#define PCIR_CAP_PTR_2 0x14 +#define PCIR_SECSTAT_2 0x16 + +#define PCIR_PRIBUS_2 0x18 +#define PCIR_SECBUS_2 0x19 +#define PCIR_SUBBUS_2 0x1a +#define PCIR_SECLAT_2 0x1b + +#define PCIR_MEMBASE0_2 0x1c +#define PCIR_MEMLIMIT0_2 0x20 +#define PCIR_MEMBASE1_2 0x24 +#define PCIR_MEMLIMIT1_2 0x28 +#define PCIR_IOBASE0_2 0x2c +#define PCIR_IOLIMIT0_2 0x30 +#define PCIR_IOBASE1_2 0x34 +#define PCIR_IOLIMIT1_2 0x38 + +#define PCIR_BRIDGECTL_2 0x3e + +#define PCIR_SUBVEND_2 0x40 +#define PCIR_SUBDEV_2 0x42 + +#define PCIR_PCCARDIF_2 0x44 + +/* PCI device class, subclass and programming interface definitions */ + +#define PCIC_OLD 0x00 +#define PCIS_OLD_NONVGA 0x00 +#define PCIS_OLD_VGA 0x01 + +#define PCIC_STORAGE 0x01 +#define PCIS_STORAGE_SCSI 0x00 +#define PCIS_STORAGE_IDE 0x01 +#define PCIP_STORAGE_IDE_MODEPRIM 0x01 +#define PCIP_STORAGE_IDE_PROGINDPRIM 0x02 +#define PCIP_STORAGE_IDE_MODESEC 0x04 +#define PCIP_STORAGE_IDE_PROGINDSEC 0x08 +#define PCIP_STORAGE_IDE_MASTERDEV 0x80 +#define PCIS_STORAGE_FLOPPY 0x02 +#define PCIS_STORAGE_IPI 0x03 +#define PCIS_STORAGE_RAID 0x04 +#define PCIS_STORAGE_ATA_ADMA 0x05 +#define PCIS_STORAGE_SATA 0x06 +#define PCIP_STORAGE_SATA_AHCI_1_0 0x01 +#define PCIS_STORAGE_SAS 0x07 +#define PCIS_STORAGE_OTHER 0x80 + +#define PCIC_NETWORK 0x02 +#define PCIS_NETWORK_ETHERNET 0x00 +#define PCIS_NETWORK_TOKENRING 0x01 +#define PCIS_NETWORK_FDDI 0x02 +#define PCIS_NETWORK_ATM 0x03 +#define PCIS_NETWORK_ISDN 0x04 +#define PCIS_NETWORK_WORLDFIP 0x05 +#define PCIS_NETWORK_PICMG 0x06 +#define PCIS_NETWORK_OTHER 0x80 + +#define PCIC_DISPLAY 0x03 +#define PCIS_DISPLAY_VGA 0x00 +#define PCIS_DISPLAY_XGA 0x01 +#define PCIS_DISPLAY_3D 0x02 +#define PCIS_DISPLAY_OTHER 0x80 + +#define PCIC_MULTIMEDIA 0x04 +#define PCIS_MULTIMEDIA_VIDEO 0x00 +#define PCIS_MULTIMEDIA_AUDIO 0x01 +#define PCIS_MULTIMEDIA_TELE 0x02 +#define PCIS_MULTIMEDIA_HDA 0x03 +#define PCIS_MULTIMEDIA_OTHER 0x80 + +#define PCIC_MEMORY 0x05 +#define PCIS_MEMORY_RAM 0x00 +#define PCIS_MEMORY_FLASH 0x01 +#define PCIS_MEMORY_OTHER 0x80 + +#define PCIC_BRIDGE 0x06 +#define PCIS_BRIDGE_HOST 0x00 +#define PCIS_BRIDGE_ISA 0x01 +#define PCIS_BRIDGE_EISA 0x02 +#define PCIS_BRIDGE_MCA 0x03 +#define PCIS_BRIDGE_PCI 0x04 +#define PCIP_BRIDGE_PCI_SUBTRACTIVE 0x01 +#define PCIS_BRIDGE_PCMCIA 0x05 +#define PCIS_BRIDGE_NUBUS 0x06 +#define PCIS_BRIDGE_CARDBUS 0x07 +#define PCIS_BRIDGE_RACEWAY 0x08 +#define PCIS_BRIDGE_PCI_TRANSPARENT 0x09 +#define PCIS_BRIDGE_INFINIBAND 0x0a +#define PCIS_BRIDGE_OTHER 0x80 + +#define PCIC_SIMPLECOMM 0x07 +#define PCIS_SIMPLECOMM_UART 0x00 +#define PCIP_SIMPLECOMM_UART_8250 0x00 +#define PCIP_SIMPLECOMM_UART_16450A 0x01 +#define PCIP_SIMPLECOMM_UART_16550A 0x02 +#define PCIP_SIMPLECOMM_UART_16650A 0x03 +#define PCIP_SIMPLECOMM_UART_16750A 0x04 +#define PCIP_SIMPLECOMM_UART_16850A 0x05 +#define PCIP_SIMPLECOMM_UART_16950A 0x06 +#define PCIS_SIMPLECOMM_PAR 0x01 +#define PCIS_SIMPLECOMM_MULSER 0x02 +#define PCIS_SIMPLECOMM_MODEM 0x03 +#define PCIS_SIMPLECOMM_GPIB 0x04 +#define PCIS_SIMPLECOMM_SMART_CARD 0x05 +#define PCIS_SIMPLECOMM_OTHER 0x80 + +#define PCIC_BASEPERIPH 0x08 +#define PCIS_BASEPERIPH_PIC 0x00 +#define PCIP_BASEPERIPH_PIC_8259A 0x00 +#define PCIP_BASEPERIPH_PIC_ISA 0x01 +#define PCIP_BASEPERIPH_PIC_EISA 0x02 +#define PCIP_BASEPERIPH_PIC_IO_APIC 0x10 +#define PCIP_BASEPERIPH_PIC_IOX_APIC 0x20 +#define PCIS_BASEPERIPH_DMA 0x01 +#define PCIS_BASEPERIPH_TIMER 0x02 +#define PCIS_BASEPERIPH_RTC 0x03 +#define PCIS_BASEPERIPH_PCIHOT 0x04 +#define PCIS_BASEPERIPH_SDHC 0x05 +#define PCIS_BASEPERIPH_OTHER 0x80 + +#define PCIC_INPUTDEV 0x09 +#define PCIS_INPUTDEV_KEYBOARD 0x00 +#define PCIS_INPUTDEV_DIGITIZER 0x01 +#define PCIS_INPUTDEV_MOUSE 0x02 +#define PCIS_INPUTDEV_SCANNER 0x03 +#define PCIS_INPUTDEV_GAMEPORT 0x04 +#define PCIS_INPUTDEV_OTHER 0x80 + +#define PCIC_DOCKING 0x0a +#define PCIS_DOCKING_GENERIC 0x00 +#define PCIS_DOCKING_OTHER 0x80 + +#define PCIC_PROCESSOR 0x0b +#define PCIS_PROCESSOR_386 0x00 +#define PCIS_PROCESSOR_486 0x01 +#define PCIS_PROCESSOR_PENTIUM 0x02 +#define PCIS_PROCESSOR_ALPHA 0x10 +#define PCIS_PROCESSOR_POWERPC 0x20 +#define PCIS_PROCESSOR_MIPS 0x30 +#define PCIS_PROCESSOR_COPROC 0x40 + +#define PCIC_SERIALBUS 0x0c +#define PCIS_SERIALBUS_FW 0x00 +#define PCIS_SERIALBUS_ACCESS 0x01 +#define PCIS_SERIALBUS_SSA 0x02 +#define PCIS_SERIALBUS_USB 0x03 +#define PCIP_SERIALBUS_USB_UHCI 0x00 +#define PCIP_SERIALBUS_USB_OHCI 0x10 +#define PCIP_SERIALBUS_USB_EHCI 0x20 +#define PCIP_SERIALBUS_USB_DEVICE 0xfe +#define PCIS_SERIALBUS_FC 0x04 +#define PCIS_SERIALBUS_SMBUS 0x05 +#define PCIS_SERIALBUS_INFINIBAND 0x06 +#define PCIS_SERIALBUS_IPMI 0x07 +#define PCIP_SERIALBUS_IPMI_SMIC 0x00 +#define PCIP_SERIALBUS_IPMI_KCS 0x01 +#define PCIP_SERIALBUS_IPMI_BT 0x02 +#define PCIS_SERIALBUS_SERCOS 0x08 +#define PCIS_SERIALBUS_CANBUS 0x09 + +#define PCIC_WIRELESS 0x0d +#define PCIS_WIRELESS_IRDA 0x00 +#define PCIS_WIRELESS_IR 0x01 +#define PCIS_WIRELESS_RF 0x10 +#define PCIS_WIRELESS_BLUETOOTH 0x11 +#define PCIS_WIRELESS_BROADBAND 0x12 +#define PCIS_WIRELESS_80211A 0x20 +#define PCIS_WIRELESS_80211B 0x21 +#define PCIS_WIRELESS_OTHER 0x80 + +#define PCIC_INTELLIIO 0x0e +#define PCIS_INTELLIIO_I2O 0x00 + +#define PCIC_SATCOM 0x0f +#define PCIS_SATCOM_TV 0x01 +#define PCIS_SATCOM_AUDIO 0x02 +#define PCIS_SATCOM_VOICE 0x03 +#define PCIS_SATCOM_DATA 0x04 + +#define PCIC_CRYPTO 0x10 +#define PCIS_CRYPTO_NETCOMP 0x00 +#define PCIS_CRYPTO_ENTERTAIN 0x10 +#define PCIS_CRYPTO_OTHER 0x80 + +#define PCIC_DASP 0x11 +#define PCIS_DASP_DPIO 0x00 +#define PCIS_DASP_PERFCNTRS 0x01 +#define PCIS_DASP_COMM_SYNC 0x10 +#define PCIS_DASP_MGMT_CARD 0x20 +#define PCIS_DASP_OTHER 0x80 + +#define PCIC_OTHER 0xff + +/* Bridge Control Values. */ +#define PCIB_BCR_PERR_ENABLE 0x0001 +#define PCIB_BCR_SERR_ENABLE 0x0002 +#define PCIB_BCR_ISA_ENABLE 0x0004 +#define PCIB_BCR_VGA_ENABLE 0x0008 +#define PCIB_BCR_MASTER_ABORT_MODE 0x0020 +#define PCIB_BCR_SECBUS_RESET 0x0040 +#define PCIB_BCR_SECBUS_BACKTOBACK 0x0080 +#define PCIB_BCR_PRI_DISCARD_TIMEOUT 0x0100 +#define PCIB_BCR_SEC_DISCARD_TIMEOUT 0x0200 +#define PCIB_BCR_DISCARD_TIMER_STATUS 0x0400 +#define PCIB_BCR_DISCARD_TIMER_SERREN 0x0800 + +/* PCI power manangement */ +#define PCIR_POWER_CAP 0x2 +#define PCIM_PCAP_SPEC 0x0007 +#define PCIM_PCAP_PMEREQCLK 0x0008 +#define PCIM_PCAP_PMEREQPWR 0x0010 +#define PCIM_PCAP_DEVSPECINIT 0x0020 +#define PCIM_PCAP_DYNCLOCK 0x0040 +#define PCIM_PCAP_SECCLOCK 0x00c0 +#define PCIM_PCAP_CLOCKMASK 0x00c0 +#define PCIM_PCAP_REQFULLCLOCK 0x0100 +#define PCIM_PCAP_D1SUPP 0x0200 +#define PCIM_PCAP_D2SUPP 0x0400 +#define PCIM_PCAP_D0PME 0x0800 +#define PCIM_PCAP_D1PME 0x1000 +#define PCIM_PCAP_D2PME 0x2000 +#define PCIM_PCAP_D3PME_HOT 0x4000 +#define PCIM_PCAP_D3PME_COLD 0x8000 + +#define PCIR_POWER_STATUS 0x4 +#define PCIM_PSTAT_D0 0x0000 +#define PCIM_PSTAT_D1 0x0001 +#define PCIM_PSTAT_D2 0x0002 +#define PCIM_PSTAT_D3 0x0003 +#define PCIM_PSTAT_DMASK 0x0003 +#define PCIM_PSTAT_REPENABLE 0x0010 +#define PCIM_PSTAT_PMEENABLE 0x0100 +#define PCIM_PSTAT_D0POWER 0x0000 +#define PCIM_PSTAT_D1POWER 0x0200 +#define PCIM_PSTAT_D2POWER 0x0400 +#define PCIM_PSTAT_D3POWER 0x0600 +#define PCIM_PSTAT_D0HEAT 0x0800 +#define PCIM_PSTAT_D1HEAT 0x1000 +#define PCIM_PSTAT_D2HEAT 0x1200 +#define PCIM_PSTAT_D3HEAT 0x1400 +#define PCIM_PSTAT_DATAUNKN 0x0000 +#define PCIM_PSTAT_DATADIV10 0x2000 +#define PCIM_PSTAT_DATADIV100 0x4000 +#define PCIM_PSTAT_DATADIV1000 0x6000 +#define PCIM_PSTAT_DATADIVMASK 0x6000 +#define PCIM_PSTAT_PME 0x8000 + +#define PCIR_POWER_PMCSR 0x6 +#define PCIM_PMCSR_DCLOCK 0x10 +#define PCIM_PMCSR_B2SUPP 0x20 +#define PCIM_BMCSR_B3SUPP 0x40 +#define PCIM_BMCSR_BPCE 0x80 + +#define PCIR_POWER_DATA 0x7 + +/* VPD capability registers */ +#define PCIR_VPD_ADDR 0x2 +#define PCIR_VPD_DATA 0x4 + +/* PCI Message Signalled Interrupts (MSI) */ +#define PCIR_MSI_CTRL 0x2 +#define PCIM_MSICTRL_VECTOR 0x0100 +#define PCIM_MSICTRL_64BIT 0x0080 +#define PCIM_MSICTRL_MME_MASK 0x0070 +#define PCIM_MSICTRL_MME_1 0x0000 +#define PCIM_MSICTRL_MME_2 0x0010 +#define PCIM_MSICTRL_MME_4 0x0020 +#define PCIM_MSICTRL_MME_8 0x0030 +#define PCIM_MSICTRL_MME_16 0x0040 +#define PCIM_MSICTRL_MME_32 0x0050 +#define PCIM_MSICTRL_MMC_MASK 0x000E +#define PCIM_MSICTRL_MMC_1 0x0000 +#define PCIM_MSICTRL_MMC_2 0x0002 +#define PCIM_MSICTRL_MMC_4 0x0004 +#define PCIM_MSICTRL_MMC_8 0x0006 +#define PCIM_MSICTRL_MMC_16 0x0008 +#define PCIM_MSICTRL_MMC_32 0x000A +#define PCIM_MSICTRL_MSI_ENABLE 0x0001 +#define PCIR_MSI_ADDR 0x4 +#define PCIR_MSI_ADDR_HIGH 0x8 +#define PCIR_MSI_DATA 0x8 +#define PCIR_MSI_DATA_64BIT 0xc +#define PCIR_MSI_MASK 0x10 +#define PCIR_MSI_PENDING 0x14 + +/* PCI-X definitions */ + +/* For header type 0 devices */ +#define PCIXR_COMMAND 0x2 +#define PCIXM_COMMAND_DPERR_E 0x0001 /* Data Parity Error Recovery */ +#define PCIXM_COMMAND_ERO 0x0002 /* Enable Relaxed Ordering */ +#define PCIXM_COMMAND_MAX_READ 0x000c /* Maximum Burst Read Count */ +#define PCIXM_COMMAND_MAX_READ_512 0x0000 +#define PCIXM_COMMAND_MAX_READ_1024 0x0004 +#define PCIXM_COMMAND_MAX_READ_2048 0x0008 +#define PCIXM_COMMAND_MAX_READ_4096 0x000c +#define PCIXM_COMMAND_MAX_SPLITS 0x0070 /* Maximum Split Transactions */ +#define PCIXM_COMMAND_MAX_SPLITS_1 0x0000 +#define PCIXM_COMMAND_MAX_SPLITS_2 0x0010 +#define PCIXM_COMMAND_MAX_SPLITS_3 0x0020 +#define PCIXM_COMMAND_MAX_SPLITS_4 0x0030 +#define PCIXM_COMMAND_MAX_SPLITS_8 0x0040 +#define PCIXM_COMMAND_MAX_SPLITS_12 0x0050 +#define PCIXM_COMMAND_MAX_SPLITS_16 0x0060 +#define PCIXM_COMMAND_MAX_SPLITS_32 0x0070 +#define PCIXM_COMMAND_VERSION 0x3000 +#define PCIXR_STATUS 0x4 +#define PCIXM_STATUS_DEVFN 0x000000FF +#define PCIXM_STATUS_BUS 0x0000FF00 +#define PCIXM_STATUS_64BIT 0x00010000 +#define PCIXM_STATUS_133CAP 0x00020000 +#define PCIXM_STATUS_SC_DISCARDED 0x00040000 +#define PCIXM_STATUS_UNEXP_SC 0x00080000 +#define PCIXM_STATUS_COMPLEX_DEV 0x00100000 +#define PCIXM_STATUS_MAX_READ 0x00600000 +#define PCIXM_STATUS_MAX_READ_512 0x00000000 +#define PCIXM_STATUS_MAX_READ_1024 0x00200000 +#define PCIXM_STATUS_MAX_READ_2048 0x00400000 +#define PCIXM_STATUS_MAX_READ_4096 0x00600000 +#define PCIXM_STATUS_MAX_SPLITS 0x03800000 +#define PCIXM_STATUS_MAX_SPLITS_1 0x00000000 +#define PCIXM_STATUS_MAX_SPLITS_2 0x00800000 +#define PCIXM_STATUS_MAX_SPLITS_3 0x01000000 +#define PCIXM_STATUS_MAX_SPLITS_4 0x01800000 +#define PCIXM_STATUS_MAX_SPLITS_8 0x02000000 +#define PCIXM_STATUS_MAX_SPLITS_12 0x02800000 +#define PCIXM_STATUS_MAX_SPLITS_16 0x03000000 +#define PCIXM_STATUS_MAX_SPLITS_32 0x03800000 +#define PCIXM_STATUS_MAX_CUM_READ 0x1C000000 +#define PCIXM_STATUS_RCVD_SC_ERR 0x20000000 +#define PCIXM_STATUS_266CAP 0x40000000 +#define PCIXM_STATUS_533CAP 0x80000000 + +/* For header type 1 devices (PCI-X bridges) */ +#define PCIXR_SEC_STATUS 0x2 +#define PCIXM_SEC_STATUS_64BIT 0x0001 +#define PCIXM_SEC_STATUS_133CAP 0x0002 +#define PCIXM_SEC_STATUS_SC_DISC 0x0004 +#define PCIXM_SEC_STATUS_UNEXP_SC 0x0008 +#define PCIXM_SEC_STATUS_SC_OVERRUN 0x0010 +#define PCIXM_SEC_STATUS_SR_DELAYED 0x0020 +#define PCIXM_SEC_STATUS_BUS_MODE 0x03c0 +#define PCIXM_SEC_STATUS_VERSION 0x3000 +#define PCIXM_SEC_STATUS_266CAP 0x4000 +#define PCIXM_SEC_STATUS_533CAP 0x8000 +#define PCIXR_BRIDGE_STATUS 0x4 +#define PCIXM_BRIDGE_STATUS_DEVFN 0x000000FF +#define PCIXM_BRIDGE_STATUS_BUS 0x0000FF00 +#define PCIXM_BRIDGE_STATUS_64BIT 0x00010000 +#define PCIXM_BRIDGE_STATUS_133CAP 0x00020000 +#define PCIXM_BRIDGE_STATUS_SC_DISCARDED 0x00040000 +#define PCIXM_BRIDGE_STATUS_UNEXP_SC 0x00080000 +#define PCIXM_BRIDGE_STATUS_SC_OVERRUN 0x00100000 +#define PCIXM_BRIDGE_STATUS_SR_DELAYED 0x00200000 +#define PCIXM_BRIDGE_STATUS_DEVID_MSGCAP 0x20000000 +#define PCIXM_BRIDGE_STATUS_266CAP 0x40000000 +#define PCIXM_BRIDGE_STATUS_533CAP 0x80000000 + +/* HT (HyperTransport) Capability definitions */ +#define PCIR_HT_COMMAND 0x2 +#define PCIM_HTCMD_CAP_MASK 0xf800 /* Capability type. */ +#define PCIM_HTCAP_SLAVE 0x0000 /* 000xx */ +#define PCIM_HTCAP_HOST 0x2000 /* 001xx */ +#define PCIM_HTCAP_SWITCH 0x4000 /* 01000 */ +#define PCIM_HTCAP_INTERRUPT 0x8000 /* 10000 */ +#define PCIM_HTCAP_REVISION_ID 0x8800 /* 10001 */ +#define PCIM_HTCAP_UNITID_CLUMPING 0x9000 /* 10010 */ +#define PCIM_HTCAP_EXT_CONFIG_SPACE 0x9800 /* 10011 */ +#define PCIM_HTCAP_ADDRESS_MAPPING 0xa000 /* 10100 */ +#define PCIM_HTCAP_MSI_MAPPING 0xa800 /* 10101 */ +#define PCIM_HTCAP_DIRECT_ROUTE 0xb000 /* 10110 */ +#define PCIM_HTCAP_VCSET 0xb800 /* 10111 */ +#define PCIM_HTCAP_RETRY_MODE 0xc000 /* 11000 */ +#define PCIM_HTCAP_X86_ENCODING 0xc800 /* 11001 */ + +/* HT MSI Mapping Capability definitions. */ +#define PCIM_HTCMD_MSI_ENABLE 0x0001 +#define PCIM_HTCMD_MSI_FIXED 0x0002 +#define PCIR_HTMSI_ADDRESS_LO 0x4 +#define PCIR_HTMSI_ADDRESS_HI 0x8 + +/* PCI Vendor capability definitions */ +#define PCIR_VENDOR_LENGTH 0x2 +#define PCIR_VENDOR_DATA 0x3 + +/* PCI EHCI Debug Port definitions */ +#define PCIR_DEBUG_PORT 0x2 +#define PCIM_DEBUG_PORT_OFFSET 0x1FFF +#define PCIM_DEBUG_PORT_BAR 0xe000 + +/* PCI-PCI Bridge Subvendor definitions */ +#define PCIR_SUBVENDCAP_ID 0x4 + +/* PCI Express definitions */ +#define PCIR_EXPRESS_FLAGS 0x2 +#define PCIM_EXP_FLAGS_VERSION 0x000F +#define PCIM_EXP_FLAGS_TYPE 0x00F0 +#define PCIM_EXP_TYPE_ENDPOINT 0x0000 +#define PCIM_EXP_TYPE_LEGACY_ENDPOINT 0x0010 +#define PCIM_EXP_TYPE_ROOT_PORT 0x0040 +#define PCIM_EXP_TYPE_UPSTREAM_PORT 0x0050 +#define PCIM_EXP_TYPE_DOWNSTREAM_PORT 0x0060 +#define PCIM_EXP_TYPE_PCI_BRIDGE 0x0070 +#define PCIM_EXP_TYPE_PCIE_BRIDGE 0x0080 +#define PCIM_EXP_TYPE_ROOT_INT_EP 0x0090 +#define PCIM_EXP_TYPE_ROOT_EC 0x00a0 +#define PCIM_EXP_FLAGS_SLOT 0x0100 +#define PCIM_EXP_FLAGS_IRQ 0x3e00 +#define PCIR_EXPRESS_DEVICE_CAP 0x4 +#define PCIM_EXP_CAP_MAX_PAYLOAD 0x0007 +#define PCIR_EXPRESS_DEVICE_CTL 0x8 +#define PCIM_EXP_CTL_NFER_ENABLE 0x0002 +#define PCIM_EXP_CTL_FER_ENABLE 0x0004 +#define PCIM_EXP_CTL_URR_ENABLE 0x0008 +#define PCIM_EXP_CTL_RELAXED_ORD_ENABLE 0x0010 +#define PCIM_EXP_CTL_MAX_PAYLOAD 0x00e0 +#define PCIM_EXP_CTL_NOSNOOP_ENABLE 0x0800 +#define PCIM_EXP_CTL_MAX_READ_REQUEST 0x7000 +#define PCIR_EXPRESS_DEVICE_STA 0xa +#define PCIM_EXP_STA_CORRECTABLE_ERROR 0x0001 +#define PCIM_EXP_STA_NON_FATAL_ERROR 0x0002 +#define PCIM_EXP_STA_FATAL_ERROR 0x0004 +#define PCIM_EXP_STA_UNSUPPORTED_REQ 0x0008 +#define PCIM_EXP_STA_AUX_POWER 0x0010 +#define PCIM_EXP_STA_TRANSACTION_PND 0x0020 +#define PCIR_EXPRESS_LINK_CAP 0xc +#define PCIM_LINK_CAP_MAX_SPEED 0x0000000f +#define PCIM_LINK_CAP_MAX_WIDTH 0x000003f0 +#define PCIM_LINK_CAP_ASPM 0x00000c00 +#define PCIM_LINK_CAP_L0S_EXIT 0x00007000 +#define PCIM_LINK_CAP_L1_EXIT 0x00038000 +#define PCIM_LINK_CAP_PORT 0xff000000 +#define PCIR_EXPRESS_LINK_CTL 0x10 +#define PCIR_EXPRESS_LINK_STA 0x12 +#define PCIM_LINK_STA_SPEED 0x000f +#define PCIM_LINK_STA_WIDTH 0x03f0 +#define PCIM_LINK_STA_TRAINING_ERROR 0x0400 +#define PCIM_LINK_STA_TRAINING 0x0800 +#define PCIM_LINK_STA_SLOT_CLOCK 0x1000 +#define PCIR_EXPRESS_SLOT_CAP 0x14 +#define PCIR_EXPRESS_SLOT_CTL 0x18 +#define PCIR_EXPRESS_SLOT_STA 0x1a +#define PCIR_EXPRESS_ROOT_CTL 0x1c +#define PCIR_EXPRESS_ROOT_STA 0x20 + +/* MSI-X definitions */ +#define PCIR_MSIX_CTRL 0x2 +#define PCIM_MSIXCTRL_MSIX_ENABLE 0x8000 +#define PCIM_MSIXCTRL_FUNCTION_MASK 0x4000 +#define PCIM_MSIXCTRL_TABLE_SIZE 0x07FF +#define PCIR_MSIX_TABLE 0x4 +#define PCIR_MSIX_PBA 0x8 +#define PCIM_MSIX_BIR_MASK 0x7 +#define PCIM_MSIX_BIR_BAR_10 0 +#define PCIM_MSIX_BIR_BAR_14 1 +#define PCIM_MSIX_BIR_BAR_18 2 +#define PCIM_MSIX_BIR_BAR_1C 3 +#define PCIM_MSIX_BIR_BAR_20 4 +#define PCIM_MSIX_BIR_BAR_24 5 +#define PCIM_MSIX_VCTRL_MASK 0x1 + +/* PCI Advanced Features definitions */ +#define PCIR_PCIAF_CAP 0x3 +#define PCIM_PCIAFCAP_TP 0x01 +#define PCIM_PCIAFCAP_FLR 0x02 +#define PCIR_PCIAF_CTRL 0x4 +#define PCIR_PCIAFCTRL_FLR 0x01 +#define PCIR_PCIAF_STATUS 0x5 +#define PCIR_PCIAFSTATUS_TP 0x01 + +/* Advanced Error Reporting */ +#define PCIR_AER_UC_STATUS 0x04 +#define PCIM_AER_UC_TRAINING_ERROR 0x00000001 +#define PCIM_AER_UC_DL_PROTOCOL_ERROR 0x00000010 +#define PCIM_AER_UC_POISONED_TLP 0x00001000 +#define PCIM_AER_UC_FC_PROTOCOL_ERROR 0x00002000 +#define PCIM_AER_UC_COMPLETION_TIMEOUT 0x00004000 +#define PCIM_AER_UC_COMPLETER_ABORT 0x00008000 +#define PCIM_AER_UC_UNEXPECTED_COMPLETION 0x00010000 +#define PCIM_AER_UC_RECEIVER_OVERFLOW 0x00020000 +#define PCIM_AER_UC_MALFORMED_TLP 0x00040000 +#define PCIM_AER_UC_ECRC_ERROR 0x00080000 +#define PCIM_AER_UC_UNSUPPORTED_REQUEST 0x00100000 +#define PCIM_AER_UC_ACS_VIOLATION 0x00200000 +#define PCIR_AER_UC_MASK 0x08 /* Shares bits with UC_STATUS */ +#define PCIR_AER_UC_SEVERITY 0x0c /* Shares bits with UC_STATUS */ +#define PCIR_AER_COR_STATUS 0x10 +#define PCIM_AER_COR_RECEIVER_ERROR 0x00000001 +#define PCIM_AER_COR_BAD_TLP 0x00000040 +#define PCIM_AER_COR_BAD_DLLP 0x00000080 +#define PCIM_AER_COR_REPLAY_ROLLOVER 0x00000100 +#define PCIM_AER_COR_REPLAY_TIMEOUT 0x00001000 +#define PCIR_AER_COR_MASK 0x14 /* Shares bits with COR_STATUS */ +#define PCIR_AER_CAP_CONTROL 0x18 +#define PCIM_AER_FIRST_ERROR_PTR 0x0000001f +#define PCIM_AER_ECRC_GEN_CAPABLE 0x00000020 +#define PCIM_AER_ECRC_GEN_ENABLE 0x00000040 +#define PCIM_AER_ECRC_CHECK_CAPABLE 0x00000080 +#define PCIM_AER_ECRC_CHECK_ENABLE 0x00000100 +#define PCIR_AER_HEADER_LOG 0x1c +#define PCIR_AER_ROOTERR_CMD 0x2c /* Only for root complex ports */ +#define PCIM_AER_ROOTERR_COR_ENABLE 0x00000001 +#define PCIM_AER_ROOTERR_NF_ENABLE 0x00000002 +#define PCIM_AER_ROOTERR_F_ENABLE 0x00000004 +#define PCIR_AER_ROOTERR_STATUS 0x30 /* Only for root complex ports */ +#define PCIM_AER_ROOTERR_COR_ERR 0x00000001 +#define PCIM_AER_ROOTERR_MULTI_COR_ERR 0x00000002 +#define PCIM_AER_ROOTERR_UC_ERR 0x00000004 +#define PCIM_AER_ROOTERR_MULTI_UC_ERR 0x00000008 +#define PCIM_AER_ROOTERR_FIRST_UC_FATAL 0x00000010 +#define PCIM_AER_ROOTERR_NF_ERR 0x00000020 +#define PCIM_AER_ROOTERR_F_ERR 0x00000040 +#define PCIM_AER_ROOTERR_INT_MESSAGE 0xf8000000 +#define PCIR_AER_COR_SOURCE_ID 0x34 /* Only for root complex ports */ +#define PCIR_AER_ERR_SOURCE_ID 0x36 /* Only for root complex ports */ + +/* Virtual Channel definitions */ +#define PCIR_VC_CAP1 0x04 +#define PCIM_VC_CAP1_EXT_COUNT 0x00000007 +#define PCIM_VC_CAP1_LOWPRI_EXT_COUNT 0x00000070 +#define PCIR_VC_CAP2 0x08 +#define PCIR_VC_CONTROL 0x0C +#define PCIR_VC_STATUS 0x0E +#define PCIR_VC_RESOURCE_CAP(n) (0x10 + (n) * 0x0C) +#define PCIR_VC_RESOURCE_CTL(n) (0x14 + (n) * 0x0C) +#define PCIR_VC_RESOURCE_STA(n) (0x18 + (n) * 0x0C) + +/* Serial Number definitions */ +#define PCIR_SERIAL_LOW 0x04 +#define PCIR_SERIAL_HIGH 0x08 diff --git a/freebsd/sys/dev/pci/pcivar.h b/freebsd/sys/dev/pci/pcivar.h new file mode 100644 index 00000000..a095db6d --- /dev/null +++ b/freebsd/sys/dev/pci/pcivar.h @@ -0,0 +1,478 @@ +/*- + * Copyright (c) 1997, Stefan Esser <se@freebsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#ifndef _PCIVAR_HH_ +#define _PCIVAR_HH_ + +#include <freebsd/sys/queue.h> + +/* some PCI bus constants */ +#define PCI_MAXMAPS_0 6 /* max. no. of memory/port maps */ +#define PCI_MAXMAPS_1 2 /* max. no. of maps for PCI to PCI bridge */ +#define PCI_MAXMAPS_2 1 /* max. no. of maps for CardBus bridge */ + +typedef uint64_t pci_addr_t; + +/* Interesting values for PCI power management */ +struct pcicfg_pp { + uint16_t pp_cap; /* PCI power management capabilities */ + uint8_t pp_status; /* config space address of PCI power status reg */ + uint8_t pp_pmcsr; /* config space address of PMCSR reg */ + uint8_t pp_data; /* config space address of PCI power data reg */ +}; + +struct vpd_readonly { + char keyword[2]; + char *value; +}; + +struct vpd_write { + char keyword[2]; + char *value; + int start; + int len; +}; + +struct pcicfg_vpd { + uint8_t vpd_reg; /* base register, + 2 for addr, + 4 data */ + char vpd_cached; + char *vpd_ident; /* string identifier */ + int vpd_rocnt; + struct vpd_readonly *vpd_ros; + int vpd_wcnt; + struct vpd_write *vpd_w; +}; + +/* Interesting values for PCI MSI */ +struct pcicfg_msi { + uint16_t msi_ctrl; /* Message Control */ + uint8_t msi_location; /* Offset of MSI capability registers. */ + uint8_t msi_msgnum; /* Number of messages */ + int msi_alloc; /* Number of allocated messages. */ + uint64_t msi_addr; /* Contents of address register. */ + uint16_t msi_data; /* Contents of data register. */ + u_int msi_handlers; +}; + +/* Interesting values for PCI MSI-X */ +struct msix_vector { + uint64_t mv_address; /* Contents of address register. */ + uint32_t mv_data; /* Contents of data register. */ + int mv_irq; +}; + +struct msix_table_entry { + u_int mte_vector; /* 1-based index into msix_vectors array. */ + u_int mte_handlers; +}; + +struct pcicfg_msix { + uint16_t msix_ctrl; /* Message Control */ + uint16_t msix_msgnum; /* Number of messages */ + uint8_t msix_location; /* Offset of MSI-X capability registers. */ + uint8_t msix_table_bar; /* BAR containing vector table. */ + uint8_t msix_pba_bar; /* BAR containing PBA. */ + uint32_t msix_table_offset; + uint32_t msix_pba_offset; + int msix_alloc; /* Number of allocated vectors. */ + int msix_table_len; /* Length of virtual table. */ + struct msix_table_entry *msix_table; /* Virtual table. */ + struct msix_vector *msix_vectors; /* Array of allocated vectors. */ + struct resource *msix_table_res; /* Resource containing vector table. */ + struct resource *msix_pba_res; /* Resource containing PBA. */ +}; + +/* Interesting values for HyperTransport */ +struct pcicfg_ht { + uint8_t ht_msimap; /* Offset of MSI mapping cap registers. */ + uint16_t ht_msictrl; /* MSI mapping control */ + uint64_t ht_msiaddr; /* MSI mapping base address */ +}; + +/* config header information common to all header types */ +typedef struct pcicfg { + struct device *dev; /* device which owns this */ + + uint32_t bar[PCI_MAXMAPS_0]; /* BARs */ + uint32_t bios; /* BIOS mapping */ + + uint16_t subvendor; /* card vendor ID */ + uint16_t subdevice; /* card device ID, assigned by card vendor */ + uint16_t vendor; /* chip vendor ID */ + uint16_t device; /* chip device ID, assigned by chip vendor */ + + uint16_t cmdreg; /* disable/enable chip and PCI options */ + uint16_t statreg; /* supported PCI features and error state */ + + uint8_t baseclass; /* chip PCI class */ + uint8_t subclass; /* chip PCI subclass */ + uint8_t progif; /* chip PCI programming interface */ + uint8_t revid; /* chip revision ID */ + + uint8_t hdrtype; /* chip config header type */ + uint8_t cachelnsz; /* cache line size in 4byte units */ + uint8_t intpin; /* PCI interrupt pin */ + uint8_t intline; /* interrupt line (IRQ for PC arch) */ + + uint8_t mingnt; /* min. useful bus grant time in 250ns units */ + uint8_t maxlat; /* max. tolerated bus grant latency in 250ns */ + uint8_t lattimer; /* latency timer in units of 30ns bus cycles */ + + uint8_t mfdev; /* multi-function device (from hdrtype reg) */ + uint8_t nummaps; /* actual number of PCI maps used */ + + uint32_t domain; /* PCI domain */ + uint8_t bus; /* config space bus address */ + uint8_t slot; /* config space slot address */ + uint8_t func; /* config space function number */ + + struct pcicfg_pp pp; /* Power management */ + struct pcicfg_vpd vpd; /* Vital product data */ + struct pcicfg_msi msi; /* PCI MSI */ + struct pcicfg_msix msix; /* PCI MSI-X */ + struct pcicfg_ht ht; /* HyperTransport */ +} pcicfgregs; + +/* additional type 1 device config header information (PCI to PCI bridge) */ + +#define PCI_PPBMEMBASE(h,l) ((((pci_addr_t)(h) << 32) + ((l)<<16)) & ~0xfffff) +#define PCI_PPBMEMLIMIT(h,l) ((((pci_addr_t)(h) << 32) + ((l)<<16)) | 0xfffff) +#define PCI_PPBIOBASE(h,l) ((((h)<<16) + ((l)<<8)) & ~0xfff) +#define PCI_PPBIOLIMIT(h,l) ((((h)<<16) + ((l)<<8)) | 0xfff) + +typedef struct { + pci_addr_t pmembase; /* base address of prefetchable memory */ + pci_addr_t pmemlimit; /* topmost address of prefetchable memory */ + uint32_t membase; /* base address of memory window */ + uint32_t memlimit; /* topmost address of memory window */ + uint32_t iobase; /* base address of port window */ + uint32_t iolimit; /* topmost address of port window */ + uint16_t secstat; /* secondary bus status register */ + uint16_t bridgectl; /* bridge control register */ + uint8_t seclat; /* CardBus latency timer */ +} pcih1cfgregs; + +/* additional type 2 device config header information (CardBus bridge) */ + +typedef struct { + uint32_t membase0; /* base address of memory window */ + uint32_t memlimit0; /* topmost address of memory window */ + uint32_t membase1; /* base address of memory window */ + uint32_t memlimit1; /* topmost address of memory window */ + uint32_t iobase0; /* base address of port window */ + uint32_t iolimit0; /* topmost address of port window */ + uint32_t iobase1; /* base address of port window */ + uint32_t iolimit1; /* topmost address of port window */ + uint32_t pccardif; /* PC Card 16bit IF legacy more base addr. */ + uint16_t secstat; /* secondary bus status register */ + uint16_t bridgectl; /* bridge control register */ + uint8_t seclat; /* CardBus latency timer */ +} pcih2cfgregs; + +extern uint32_t pci_numdevs; + +/* Only if the prerequisites are present */ +#if defined(_SYS_BUS_HH_) && defined(_SYS_PCIIO_HH_) +struct pci_devinfo { + STAILQ_ENTRY(pci_devinfo) pci_links; + struct resource_list resources; + pcicfgregs cfg; + struct pci_conf conf; +}; +#endif + +#ifdef _SYS_BUS_HH_ + +#include <freebsd/local/pci_if.h> + +enum pci_device_ivars { + PCI_IVAR_SUBVENDOR, + PCI_IVAR_SUBDEVICE, + PCI_IVAR_VENDOR, + PCI_IVAR_DEVICE, + PCI_IVAR_DEVID, + PCI_IVAR_CLASS, + PCI_IVAR_SUBCLASS, + PCI_IVAR_PROGIF, + PCI_IVAR_REVID, + PCI_IVAR_INTPIN, + PCI_IVAR_IRQ, + PCI_IVAR_DOMAIN, + PCI_IVAR_BUS, + PCI_IVAR_SLOT, + PCI_IVAR_FUNCTION, + PCI_IVAR_ETHADDR, + PCI_IVAR_CMDREG, + PCI_IVAR_CACHELNSZ, + PCI_IVAR_MINGNT, + PCI_IVAR_MAXLAT, + PCI_IVAR_LATTIMER +}; + +/* + * Simplified accessors for pci devices + */ +#define PCI_ACCESSOR(var, ivar, type) \ + __BUS_ACCESSOR(pci, var, PCI, ivar, type) + +PCI_ACCESSOR(subvendor, SUBVENDOR, uint16_t) +PCI_ACCESSOR(subdevice, SUBDEVICE, uint16_t) +PCI_ACCESSOR(vendor, VENDOR, uint16_t) +PCI_ACCESSOR(device, DEVICE, uint16_t) +PCI_ACCESSOR(devid, DEVID, uint32_t) +PCI_ACCESSOR(class, CLASS, uint8_t) +PCI_ACCESSOR(subclass, SUBCLASS, uint8_t) +PCI_ACCESSOR(progif, PROGIF, uint8_t) +PCI_ACCESSOR(revid, REVID, uint8_t) +PCI_ACCESSOR(intpin, INTPIN, uint8_t) +PCI_ACCESSOR(irq, IRQ, uint8_t) +PCI_ACCESSOR(domain, DOMAIN, uint32_t) +PCI_ACCESSOR(bus, BUS, uint8_t) +PCI_ACCESSOR(slot, SLOT, uint8_t) +PCI_ACCESSOR(function, FUNCTION, uint8_t) +PCI_ACCESSOR(ether, ETHADDR, uint8_t *) +PCI_ACCESSOR(cmdreg, CMDREG, uint8_t) +PCI_ACCESSOR(cachelnsz, CACHELNSZ, uint8_t) +PCI_ACCESSOR(mingnt, MINGNT, uint8_t) +PCI_ACCESSOR(maxlat, MAXLAT, uint8_t) +PCI_ACCESSOR(lattimer, LATTIMER, uint8_t) + +#undef PCI_ACCESSOR + +/* + * Operations on configuration space. + */ +static __inline uint32_t +pci_read_config(device_t dev, int reg, int width) +{ + return PCI_READ_CONFIG(device_get_parent(dev), dev, reg, width); +} + +static __inline void +pci_write_config(device_t dev, int reg, uint32_t val, int width) +{ + PCI_WRITE_CONFIG(device_get_parent(dev), dev, reg, val, width); +} + +/* + * Ivars for pci bridges. + */ + +/*typedef enum pci_device_ivars pcib_device_ivars;*/ +enum pcib_device_ivars { + PCIB_IVAR_DOMAIN, + PCIB_IVAR_BUS +}; + +#define PCIB_ACCESSOR(var, ivar, type) \ + __BUS_ACCESSOR(pcib, var, PCIB, ivar, type) + +PCIB_ACCESSOR(domain, DOMAIN, uint32_t) +PCIB_ACCESSOR(bus, BUS, uint32_t) + +#undef PCIB_ACCESSOR + +/* + * PCI interrupt validation. Invalid interrupt values such as 0 or 128 + * on i386 or other platforms should be mapped out in the MD pcireadconf + * code and not here, since the only MI invalid IRQ is 255. + */ +#define PCI_INVALID_IRQ 255 +#define PCI_INTERRUPT_VALID(x) ((x) != PCI_INVALID_IRQ) + + +/* + * Convenience functions. + * + * These should be used in preference to manually manipulating + * configuration space. + */ +static __inline int +pci_enable_busmaster(device_t dev) +{ + return(PCI_ENABLE_BUSMASTER(device_get_parent(dev), dev)); +} + +#ifndef __rtems__ +static __inline int +pci_disable_busmaster(device_t dev) +{ + return(PCI_DISABLE_BUSMASTER(device_get_parent(dev), dev)); +} + +static __inline int +pci_enable_io(device_t dev, int space) +{ + return(PCI_ENABLE_IO(device_get_parent(dev), dev, space)); +} + +static __inline int +pci_disable_io(device_t dev, int space) +{ + return(PCI_DISABLE_IO(device_get_parent(dev), dev, space)); +} + +static __inline int +pci_get_vpd_ident(device_t dev, const char **identptr) +{ + return(PCI_GET_VPD_IDENT(device_get_parent(dev), dev, identptr)); +} + +static __inline int +pci_get_vpd_readonly(device_t dev, const char *kw, const char **identptr) +{ + return(PCI_GET_VPD_READONLY(device_get_parent(dev), dev, kw, identptr)); +} +#endif /* __rtems__ */ + +/* + * Check if the address range falls within the VGA defined address range(s) + */ +static __inline int +pci_is_vga_ioport_range(u_long start, u_long end) +{ + + return (((start >= 0x3b0 && end <= 0x3bb) || + (start >= 0x3c0 && end <= 0x3df)) ? 1 : 0); +} + +static __inline int +pci_is_vga_memory_range(u_long start, u_long end) +{ + + return ((start >= 0xa0000 && end <= 0xbffff) ? 1 : 0); +} + +/* + * PCI power states are as defined by ACPI: + * + * D0 State in which device is on and running. It is receiving full + * power from the system and delivering full functionality to the user. + * D1 Class-specific low-power state in which device context may or may not + * be lost. Buses in D1 cannot do anything to the bus that would force + * devices on that bus to lose context. + * D2 Class-specific low-power state in which device context may or may + * not be lost. Attains greater power savings than D1. Buses in D2 + * can cause devices on that bus to lose some context. Devices in D2 + * must be prepared for the bus to be in D2 or higher. + * D3 State in which the device is off and not running. Device context is + * lost. Power can be removed from the device. + */ +#define PCI_POWERSTATE_D0 0 +#define PCI_POWERSTATE_D1 1 +#define PCI_POWERSTATE_D2 2 +#define PCI_POWERSTATE_D3 3 +#define PCI_POWERSTATE_UNKNOWN -1 + +static __inline int +pci_set_powerstate(device_t dev, int state) +{ + return PCI_SET_POWERSTATE(device_get_parent(dev), dev, state); +} + +static __inline int +pci_get_powerstate(device_t dev) +{ + return PCI_GET_POWERSTATE(device_get_parent(dev), dev); +} + +static __inline int +pci_find_extcap(device_t dev, int capability, int *capreg) +{ + return PCI_FIND_EXTCAP(device_get_parent(dev), dev, capability, capreg); +} + +static __inline int +pci_alloc_msi(device_t dev, int *count) +{ + return (PCI_ALLOC_MSI(device_get_parent(dev), dev, count)); +} + +static __inline int +pci_alloc_msix(device_t dev, int *count) +{ + return (PCI_ALLOC_MSIX(device_get_parent(dev), dev, count)); +} + +static __inline int +pci_remap_msix(device_t dev, int count, const u_int *vectors) +{ + return (PCI_REMAP_MSIX(device_get_parent(dev), dev, count, vectors)); +} + +static __inline int +pci_release_msi(device_t dev) +{ + return (PCI_RELEASE_MSI(device_get_parent(dev), dev)); +} + +static __inline int +pci_msi_count(device_t dev) +{ + return (PCI_MSI_COUNT(device_get_parent(dev), dev)); +} + +static __inline int +pci_msix_count(device_t dev) +{ + return (PCI_MSIX_COUNT(device_get_parent(dev), dev)); +} + +device_t pci_find_bsf(uint8_t, uint8_t, uint8_t); +device_t pci_find_dbsf(uint32_t, uint8_t, uint8_t, uint8_t); +#ifndef __rtems__ +device_t pci_find_device(uint16_t, uint16_t); +#endif /* __rtems__ */ + +/* Can be used by drivers to manage the MSI-X table. */ +int pci_pending_msix(device_t dev, u_int index); + +int pci_msi_device_blacklisted(device_t dev); + +void pci_ht_map_msi(device_t dev, uint64_t addr); + +int pci_get_max_read_req(device_t dev); +int pci_set_max_read_req(device_t dev, int size); + +#endif /* _SYS_BUS_HH_ */ + +/* + * cdev switch for control device, initialised in generic PCI code + */ +extern struct cdevsw pcicdev; + +/* + * List of all PCI devices, generation count for the list. + */ +STAILQ_HEAD(devlist, pci_devinfo); + +extern struct devlist pci_devq; +extern uint32_t pci_generation; + +#endif /* _PCIVAR_HH_ */ |