diff options
Diffstat (limited to 'sys/dev')
| -rw-r--r-- | sys/dev/acpi/acpi_mcfg.c | 161 | ||||
| -rw-r--r-- | sys/dev/acpi/acpi_mcfg.h | 5 | ||||
| -rw-r--r-- | sys/dev/acpi/acpi_pci.c | 69 | ||||
| -rw-r--r-- | sys/dev/acpi/acpi_pci.h | 3 | ||||
| -rw-r--r-- | sys/dev/pci/files.pci | 4 | ||||
| -rw-r--r-- | sys/dev/pci/pci_resource.c | 1001 | ||||
| -rw-r--r-- | sys/dev/pci/pci_resource.h | 50 |
7 files changed, 1149 insertions, 144 deletions
diff --git a/sys/dev/acpi/acpi_mcfg.c b/sys/dev/acpi/acpi_mcfg.c index 4e10446f2ab..91841dc3102 100644 --- a/sys/dev/acpi/acpi_mcfg.c +++ b/sys/dev/acpi/acpi_mcfg.c @@ -1,4 +1,4 @@ -/* $NetBSD: acpi_mcfg.c,v 1.25 2022/02/27 14:19:07 riastradh Exp $ */ +/* $NetBSD: acpi_mcfg.c,v 1.26 2022/10/14 22:10:15 jmcneill Exp $ */ /*- * Copyright (C) 2015 NONAKA Kimihiro <nonaka@NetBSD.org> @@ -28,7 +28,7 @@ #include "opt_pci.h" #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: acpi_mcfg.c,v 1.25 2022/02/27 14:19:07 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: acpi_mcfg.c,v 1.26 2022/10/14 22:10:15 jmcneill Exp $"); #include <sys/param.h> #include <sys/device.h> @@ -37,7 +37,7 @@ __KERNEL_RCSID(0, "$NetBSD: acpi_mcfg.c,v 1.25 2022/02/27 14:19:07 riastradh Exp #include <dev/pci/pcireg.h> #include <dev/pci/pcivar.h> -#include <dev/pci/pciconf.h> +#include <dev/pci/pci_resource.h> #include <dev/pci/pcidevs.h> #include <dev/acpi/acpireg.h> @@ -687,16 +687,14 @@ out: return error; } -#ifdef PCI_NETBSD_CONFIGURE +#ifdef PCI_RESOURCE ACPI_STATUS acpimcfg_configure_bus_cb(ACPI_RESOURCE *res, void *ctx) { - struct pciconf_resources *pcires = ctx; - int type; + struct pci_resource_info *pciinfo = ctx; bus_addr_t addr; bus_size_t size; - const char *s; - int error; + int type; if (res->Type != ACPI_RESOURCE_TYPE_ADDRESS16 && res->Type != ACPI_RESOURCE_TYPE_ADDRESS32 && @@ -707,21 +705,25 @@ acpimcfg_configure_bus_cb(ACPI_RESOURCE *res, void *ctx) return AE_OK; if (res->Data.Address.ResourceType != ACPI_MEMORY_RANGE && - res->Data.Address.ResourceType != ACPI_IO_RANGE) + res->Data.Address.ResourceType != ACPI_IO_RANGE && + res->Data.Address.ResourceType != ACPI_BUS_NUMBER_RANGE) return AE_OK; if (res->Data.Address.ResourceType == ACPI_MEMORY_RANGE && res->Data.Address.Info.Mem.Caching == ACPI_PREFETCHABLE_MEMORY) { - type = PCICONF_RESOURCE_PREFETCHABLE_MEM; - s = "prefetchable"; + type = PCI_RANGE_PMEM; } else if (res->Data.Address.ResourceType == ACPI_MEMORY_RANGE && res->Data.Address.Info.Mem.Caching != ACPI_PREFETCHABLE_MEMORY) { - type = PCICONF_RESOURCE_MEM; - s = "non-prefetchable"; + if (res->Type == ACPI_RESOURCE_TYPE_ADDRESS64) { + type = PCI_RANGE_PMEM; + } else { + type = PCI_RANGE_MEM; + } + } else if (res->Data.Address.ResourceType == ACPI_BUS_NUMBER_RANGE) { + type = PCI_RANGE_BUS; } else { KASSERT(res->Data.Address.ResourceType == ACPI_IO_RANGE); - type = PCICONF_RESOURCE_IO; - s = "i/o"; + type = PCI_RANGE_IO; } switch (res->Type) { @@ -730,7 +732,7 @@ acpimcfg_configure_bus_cb(ACPI_RESOURCE *res, void *ctx) "MCFG: range 0x%04" PRIx16 " size %#" PRIx16 " (16-bit %s)\n", res->Data.Address16.Address.Minimum, res->Data.Address16.Address.AddressLength, - s); + pci_resource_typename(type)); addr = res->Data.Address16.Address.Minimum; size = res->Data.Address16.Address.AddressLength; break; @@ -739,7 +741,7 @@ acpimcfg_configure_bus_cb(ACPI_RESOURCE *res, void *ctx) "MCFG: range 0x%08" PRIx32 " size %#" PRIx32 " (32-bit %s)\n", res->Data.Address32.Address.Minimum, res->Data.Address32.Address.AddressLength, - s); + pci_resource_typename(type)); addr = res->Data.Address32.Address.Minimum; size = res->Data.Address32.Address.AddressLength; break; @@ -748,7 +750,7 @@ acpimcfg_configure_bus_cb(ACPI_RESOURCE *res, void *ctx) "MCFG: range 0x%016" PRIx64 " size %#" PRIx64 " (64-bit %s)\n", res->Data.Address64.Address.Minimum, res->Data.Address64.Address.AddressLength, - s); + pci_resource_typename(type)); addr = res->Data.Address64.Address.Minimum; size = res->Data.Address64.Address.AddressLength; break; @@ -757,93 +759,108 @@ acpimcfg_configure_bus_cb(ACPI_RESOURCE *res, void *ctx) return AE_OK; } - error = pciconf_resource_add(pcires, type, addr, size); + pciinfo->ranges[type].start = addr; + pciinfo->ranges[type].end = addr + size - 1; - return error == 0 ? AE_OK : AE_NO_MEMORY; + return AE_OK; } int acpimcfg_configure_bus(device_t self, pci_chipset_tag_t pc, ACPI_HANDLE handle, - int bus, int cacheline_size) + int bus, bool mapcfgspace) { - struct pciconf_resources *pcires; + struct pci_resource_info pciinfo; struct mcfg_segment *seg; struct mcfg_bus *mb; bus_space_handle_t bsh[256]; bool bsh_mapped[256]; - int error, boff, b, d, f; + int error, boff, b, d, f, endbus; bus_addr_t baddr; ACPI_STATUS rv; - seg = acpimcfg_get_segment(pc, bus); - if (seg == NULL) - return ENOENT; - - pcires = pciconf_resource_init(); + if (mapcfgspace) { + seg = acpimcfg_get_segment(pc, bus); + if (seg == NULL) { + return ENOENT; + } + endbus = seg->ms_bus_end; - /* - * Map config space for all possible busses and mark them valid during - * configuration so pci_configure_bus can access them through our chipset - * tag with acpimcfg_conf_read/write below. - */ - memset(bsh_mapped, 0, sizeof(bsh_mapped)); - for (b = seg->ms_bus_start; b <= seg->ms_bus_end; b++) { - boff = b - seg->ms_bus_start; - mb = &seg->ms_bus[boff]; - baddr = seg->ms_address + (b * ACPIMCFG_SIZE_PER_BUS); - - /* Map extended configuration space of all dev/func. */ - error = bus_space_map(seg->ms_bst, baddr, ACPIMCFG_SIZE_PER_BUS, 0, - &bsh[b]); - if (error != 0) - goto cleanup; - bsh_mapped[b] = true; - for (d = 0; d < 32; d++) { - for (f = 0; f < 8; f++) { - error = bus_space_subregion(seg->ms_bst, bsh[b], - EXTCONF_OFFSET(d, f, 0), PCI_EXTCONF_SIZE, - &mb->bsh[d][f]); - if (error != 0) - break; + /* + * Map config space for all possible busses and mark them valid + * during configuration so pci_configure_bus can access them + * through our chipset tag with acpimcfg_conf_read/write below. + */ + memset(bsh_mapped, 0, sizeof(bsh_mapped)); + for (b = seg->ms_bus_start; b <= seg->ms_bus_end; b++) { + boff = b - seg->ms_bus_start; + mb = &seg->ms_bus[boff]; + baddr = seg->ms_address + (b * ACPIMCFG_SIZE_PER_BUS); + + /* Map extended configuration space of all dev/func. */ + error = bus_space_map(seg->ms_bst, baddr, + ACPIMCFG_SIZE_PER_BUS, 0, &bsh[b]); + if (error != 0) { + goto cleanup; + } + bsh_mapped[b] = true; + for (d = 0; d < 32; d++) { + for (f = 0; f < 8; f++) { + error = bus_space_subregion(seg->ms_bst, + bsh[b], EXTCONF_OFFSET(d, f, 0), + PCI_EXTCONF_SIZE, &mb->bsh[d][f]); + if (error != 0) { + break; + } + } + } + if (error != 0) { + goto cleanup; } - } - if (error != 0) - goto cleanup; - memset(mb->valid_devs, 0xff, sizeof(mb->valid_devs)); + memset(mb->valid_devs, 0xff, sizeof(mb->valid_devs)); + } + } else { + endbus = 255; } + memset(&pciinfo, 0, sizeof(pciinfo)); + pciinfo.pc = pc; + pciinfo.ranges[PCI_RANGE_BUS].start = bus; + pciinfo.ranges[PCI_RANGE_BUS].end = endbus; rv = AcpiWalkResources(handle, "_CRS", acpimcfg_configure_bus_cb, - pcires); + &pciinfo); if (ACPI_FAILURE(rv)) { error = ENXIO; goto cleanup; } + error = 0; - error = pci_configure_bus(pc, pcires, bus, cacheline_size); + pci_resource_init(&pciinfo); cleanup: - /* - * Unmap config space for the segment's busses. Valid devices will be - * re-mapped later on by acpimcfg_map_bus. - */ - for (b = seg->ms_bus_start; b <= seg->ms_bus_end; b++) { - boff = b - seg->ms_bus_start; - mb = &seg->ms_bus[boff]; - memset(mb->valid_devs, 0, sizeof(mb->valid_devs)); - - if (bsh_mapped[b]) - bus_space_unmap(seg->ms_bst, bsh[b], ACPIMCFG_SIZE_PER_BUS); + if (mapcfgspace) { + /* + * Unmap config space for the segment's busses. Valid devices + * will be re-mapped later on by acpimcfg_map_bus. + */ + for (b = seg->ms_bus_start; b <= seg->ms_bus_end; b++) { + boff = b - seg->ms_bus_start; + mb = &seg->ms_bus[boff]; + memset(mb->valid_devs, 0, sizeof(mb->valid_devs)); + + if (bsh_mapped[b]) { + bus_space_unmap(seg->ms_bst, bsh[b], + ACPIMCFG_SIZE_PER_BUS); + } + } } - pciconf_resource_fini(pcires); - return error; } #else int acpimcfg_configure_bus(device_t self, pci_chipset_tag_t pc, ACPI_HANDLE handle, - int bus, int cacheline_size) + int bus, bool mapcfgspace) { return ENXIO; } diff --git a/sys/dev/acpi/acpi_mcfg.h b/sys/dev/acpi/acpi_mcfg.h index 33c4cda303e..cb02353963c 100644 --- a/sys/dev/acpi/acpi_mcfg.h +++ b/sys/dev/acpi/acpi_mcfg.h @@ -1,4 +1,4 @@ -/* $NetBSD: acpi_mcfg.h,v 1.4 2021/08/07 21:19:15 jmcneill Exp $ */ +/* $NetBSD: acpi_mcfg.h,v 1.5 2022/10/14 22:10:15 jmcneill Exp $ */ /*- * Copyright (C) 2015 NONAKA Kimihiro <nonaka@NetBSD.org> @@ -32,7 +32,8 @@ struct acpimcfg_ops; void acpimcfg_probe(struct acpi_softc *); int acpimcfg_init(bus_space_tag_t, const struct acpimcfg_ops *); int acpimcfg_map_bus(device_t, pci_chipset_tag_t, int); -int acpimcfg_configure_bus(device_t, pci_chipset_tag_t, ACPI_HANDLE, int, int); +int acpimcfg_configure_bus(device_t, pci_chipset_tag_t, ACPI_HANDLE, int, + bool); ACPI_STATUS acpimcfg_configure_bus_cb(ACPI_RESOURCE *, void *); int acpimcfg_conf_read(pci_chipset_tag_t, pcitag_t, int, pcireg_t *); diff --git a/sys/dev/acpi/acpi_pci.c b/sys/dev/acpi/acpi_pci.c index c9382549aab..c3ede190ce0 100644 --- a/sys/dev/acpi/acpi_pci.c +++ b/sys/dev/acpi/acpi_pci.c @@ -1,4 +1,4 @@ -/* $NetBSD: acpi_pci.c,v 1.36 2022/02/27 14:19:07 riastradh Exp $ */ +/* $NetBSD: acpi_pci.c,v 1.37 2022/10/14 22:10:15 jmcneill Exp $ */ /* * Copyright (c) 2009, 2010 The NetBSD Foundation, Inc. @@ -29,7 +29,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: acpi_pci.c,v 1.36 2022/02/27 14:19:07 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: acpi_pci.c,v 1.37 2022/10/14 22:10:15 jmcneill Exp $"); #include <sys/param.h> #include <sys/device.h> @@ -59,14 +59,6 @@ static ACPI_STATUS acpi_pcidev_pciroot_bus_callback(ACPI_RESOURCE *, void *); /* - * UUID for _DSM control method, from PCI Firmware Specification. - */ -static UINT8 acpi_pci_dsm_uuid[ACPI_UUID_LENGTH] = { - 0xd0, 0x37, 0xc9, 0xe5, 0x53, 0x35, 0x7a, 0x4d, - 0x91, 0x17, 0xea, 0x4d, 0x19, 0xc3, 0x43, 0x4d -}; - -/* * Regarding PCI Segment Groups (ACPI 4.0, p. 277): * * "The optional _SEG object is located under a PCI host bridge and @@ -486,63 +478,6 @@ acpi_pcidev_find_dev(struct acpi_devnode *ad) } /* - * acpi_pci_ignore_boot_config: - * - * Returns 1 if the operating system may ignore the boot configuration - * of PCI resources. - */ -ACPI_INTEGER -acpi_pci_ignore_boot_config(ACPI_HANDLE handle) -{ - ACPI_OBJECT *pobj = NULL; - ACPI_INTEGER ret; - - /* - * This one is a little confusing, but the result of - * evaluating _DSM #5 is: - * - * 0: The operating system may not ignore the boot configuration - * of PCI resources. - * - * 1: The operating system may ignore the boot configuration of - * PCI resources, and reconfigure or rebalance these resources - * in the hierarchy as required. - */ - - if (ACPI_FAILURE(acpi_dsm(handle, acpi_pci_dsm_uuid, - 1, 5, NULL, &pobj))) { - /* - * In the absence of _DSM #5, we may assume that the - * boot config can be ignored. - */ - return 1; - } - - /* - * ...and we default to "may ignore" in the event that the - * method returns nonsense. - */ - ret = 1; - - if (pobj != NULL) { - switch (pobj->Type) { - case ACPI_TYPE_INTEGER: - ret = pobj->Integer.Value; - break; - - case ACPI_TYPE_PACKAGE: - if (pobj->Package.Count == 1 && - pobj->Package.Elements[0].Type == ACPI_TYPE_INTEGER) - ret = pobj->Package.Elements[0].Integer.Value; - break; - } - ACPI_FREE(pobj); - } - - return ret; -} - -/* * acpi_pci_bus_get_child_devhandle: * * Implements the "pci-bus-get-child-devhandle" device call for diff --git a/sys/dev/acpi/acpi_pci.h b/sys/dev/acpi/acpi_pci.h index c5c7bc7e827..b0f4c5c552b 100644 --- a/sys/dev/acpi/acpi_pci.h +++ b/sys/dev/acpi/acpi_pci.h @@ -1,4 +1,4 @@ -/* $NetBSD: acpi_pci.h,v 1.12 2020/05/08 14:42:38 jmcneill Exp $ */ +/* $NetBSD: acpi_pci.h,v 1.13 2022/10/14 22:10:15 jmcneill Exp $ */ /* * Copyright (c) 2009 The NetBSD Foundation, Inc. @@ -42,6 +42,5 @@ pci_chipset_tag_t acpi_pcidev_get_tag(uint16_t, uint16_t, uint16_t, uint16_t); device_t acpi_pcidev_find_dev(struct acpi_devnode *); struct acpi_devnode *acpi_pciroot_find(uint16_t, uint16_t); -ACPI_INTEGER acpi_pci_ignore_boot_config(ACPI_HANDLE); #endif /* !_SYS_DEV_ACPI_ACPI_PCI_H */ diff --git a/sys/dev/pci/files.pci b/sys/dev/pci/files.pci index f9a97bc43b5..e8d3048828b 100644 --- a/sys/dev/pci/files.pci +++ b/sys/dev/pci/files.pci @@ -1,10 +1,11 @@ -# $NetBSD: files.pci,v 1.444 2022/09/16 03:12:03 knakahara Exp $ +# $NetBSD: files.pci,v 1.445 2022/10/14 22:10:15 jmcneill Exp $ # # Config file and device description for machine-independent PCI code. # Included by ports that need it. Requires that the SCSI files be # defined first. defflag opt_pci.h PCIVERBOSE PCI_CONFIG_DUMP PCI_NETBSD_CONFIGURE + PCI_RESOURCE defparam opt_pci.h PCI_NETBSD_ENABLE_IDE defflag opt_bktr.h BKTR_430_FX_MODE BKTR_GPIO_ACCESS BKTR_NO_MSP_RESET @@ -20,6 +21,7 @@ attach pci at pcibus file dev/pci/pci.c pci needs-flag file dev/pci/pci_map.c pci file dev/pci/pci_quirks.c pci +file dev/pci/pci_resource.c pci & pci_resource file dev/pci/pci_subr.c pci file dev/pci/pci_stub.c pci file dev/pci/pci_usrreq.c pci diff --git a/sys/dev/pci/pci_resource.c b/sys/dev/pci/pci_resource.c new file mode 100644 index 00000000000..f63b172d8fa --- /dev/null +++ b/sys/dev/pci/pci_resource.c @@ -0,0 +1,1001 @@ +/* $NetBSD: pci_resource.c,v 1.1 2022/10/14 22:10:15 jmcneill Exp $ */ + +/*- + * Copyright (c) 2022 Jared McNeill <jmcneill@invisible.ca> + * 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. + * + * 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. + */ + +/* + * pci_resource.c -- + * + * Scan current PCI resource allocations and attempt to assign resources + * to devices that are not configured WITHOUT changing any configuration + * performed by system firmware. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: pci_resource.c,v 1.1 2022/10/14 22:10:15 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/systm.h> +#include <sys/kmem.h> +#include <sys/vmem.h> + +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pcidevs.h> +#include <dev/pci/pci_resource.h> + +#define DPRINT aprint_debug + +#if defined(PCI_RESOURCE_TEST_VENDOR_ID) && \ + defined(PCI_RESOURCE_TEST_PRODUCT_ID) +#define IS_TEST_DEVICE(_pd) \ + (PCI_VENDOR(pd->pd_id) == PCI_RESOURCE_TEST_VENDOR_ID && \ + PCI_PRODUCT(pd->pd_id) == PCI_RESOURCE_TEST_PRODUCT_ID) +#else +#define IS_TEST_DEVICE(_pd) 0 +#endif + +#define PCI_MAX_DEVICE 32 +#define PCI_MAX_FUNC 8 + +#define PCI_MAX_IORES 6 + +#define PCI_RANGE_FOREACH(_type) \ + for (u_int _type = PCI_RANGE_BUS; _type < NUM_PCI_RANGES; _type++) + +static const char *pci_range_typenames[NUM_PCI_RANGES] = { + [PCI_RANGE_BUS] = "bus", + [PCI_RANGE_IO] = "io", + [PCI_RANGE_MEM] = "mem", + [PCI_RANGE_PMEM] = "pmem", +}; + +struct pci_bus; + +struct pci_iores { + uint64_t pi_base; /* Base address */ + uint64_t pi_size; /* Resource size */ + uint8_t pi_type; /* PCI_MAPREG_TYPE_* */ + u_int pi_bar; /* PCI bar number */ + union { + struct { + uint8_t memtype; + bool prefetch; + } pi_mem; + }; +}; + +struct pci_device { + bool pd_present; /* Device is present */ + bool pd_configured; /* Device is configured */ + struct pci_bus *pd_bus; /* Parent bus */ + uint8_t pd_devno; /* Device number */ + uint8_t pd_funcno; /* Function number */ + pcitag_t pd_tag; /* PCI tag */ + + pcireg_t pd_id; /* Vendor ID, Device ID */ + pcireg_t pd_class; /* Revision ID, Class Code */ + pcireg_t pd_bhlc; /* BIST, Header Type, Primary Latency + * Timer, Cache Line Size */ + + struct pci_iores pd_iores[PCI_MAX_IORES]; + u_int pd_niores; + + bool pd_ppb; /* PCI-PCI bridge */ + union { + struct { + pcireg_t bridge_bus; + struct pci_resource_range ranges[NUM_PCI_RANGES]; + } pd_bridge; + }; +}; + +struct pci_bus { + uint8_t pb_busno; /* Bus number */ + struct pci_device *pb_bridge; /* Parent bridge, or NULL */ + + struct pci_device pb_device[PCI_MAX_DEVICE * PCI_MAX_FUNC]; + /* Devices on bus */ + u_int pb_lastdevno; /* Last device found */ + + struct pci_resource_range pb_ranges[NUM_PCI_RANGES]; + vmem_t *pb_res[NUM_PCI_RANGES]; +}; + +struct pci_resources { + struct pci_bus **pr_bus; /* Bus list */ + pci_chipset_tag_t pr_pc; /* Chipset tag */ + uint8_t pr_startbus; /* First bus number */ + uint8_t pr_endbus; /* Last bus number */ + + struct pci_resource_range pr_ranges[NUM_PCI_RANGES]; + vmem_t *pr_res[NUM_PCI_RANGES]; +}; + +static void pci_resource_scan_bus(struct pci_resources *, + struct pci_device *, uint8_t); + +#define PCI_SBDF_FMT "%04x:%02x:%02x.%u" +#define PCI_SBDF_FMT_ARGS(_pr, _pd) \ + pci_get_segment((_pr)->pr_pc), \ + (_pd)->pd_bus->pb_busno, \ + (_pd)->pd_devno, \ + (_pd)->pd_funcno + +#define PCICONF_RES_BUS(_pr, _busno) \ + ((_pr)->pr_bus[(_busno) - (_pr)->pr_startbus]) +#define PCICONF_BUS_DEVICE(_pb, _devno, _funcno) \ + (&(_pb)->pb_device[(_devno) * PCI_MAX_FUNC + (_funcno)]) + +/* + * pci_create_vmem -- + * + * Create a vmem arena covering the specified range, used for tracking + * PCI resources. + */ +static vmem_t * +pci_create_vmem(const char *name, bus_addr_t start, bus_addr_t end) +{ + vmem_t *arena; + + arena = vmem_create(name, 0, 0, 1, NULL, NULL, NULL, 0, VM_SLEEP, + IPL_NONE); + if (arena == NULL) { + return NULL; + } + + if (vmem_add(arena, start, end - start + 1, VM_SLEEP) != 0) { + vmem_destroy(arena); + arena = NULL; + } + + return arena; +} + +/* + * pci_new_bus -- + * + * Create a new PCI bus and initialize its resource ranges. + */ +static struct pci_bus * +pci_new_bus(struct pci_resources *pr, uint8_t busno, struct pci_device *bridge) +{ + struct pci_bus *pb; + struct pci_resource_range *ranges; + + pb = kmem_zalloc(sizeof(*pb), KM_SLEEP); + pb->pb_busno = busno; + pb->pb_bridge = bridge; + if (bridge == NULL) { + /* + * No additional constraints on resource allocations for + * the root bus. + */ + ranges = pr->pr_ranges; + } else { + /* + * Resource allocations for this bus are constrained by the + * bridge forwarding settings. + */ + ranges = bridge->pd_bridge.ranges; + } + memcpy(pb->pb_ranges, ranges, sizeof(pb->pb_ranges)); + + return pb; +} + +/* + * pci_resource_device_functions -- + * + * Returns the number of PCI functions for a a given bus and device. + */ +static uint8_t +pci_resource_device_functions(struct pci_resources *pr, + uint8_t busno, uint8_t devno) +{ + struct pci_bus *pb; + struct pci_device *pd; + + pb = PCICONF_RES_BUS(pr, busno); + pd = PCICONF_BUS_DEVICE(pb, devno, 0); + if (!pd->pd_present) { + return 0; + } + + return PCI_HDRTYPE_MULTIFN(pd->pd_bhlc) ? 8 : 1; +} + +/* + * pci_resource_device_print -- + * + * Log details about a device. + */ +static void +pci_resource_device_print(struct pci_resources *pr, + struct pci_device *pd) +{ + struct pci_iores *pi; + u_int res; + + DPRINT("PCI: " PCI_SBDF_FMT " %04x:%04x %02x 0x%06x", + PCI_SBDF_FMT_ARGS(pr, pd), + PCI_VENDOR(pd->pd_id), PCI_PRODUCT(pd->pd_id), + PCI_REVISION(pd->pd_class), (pd->pd_class >> 8) & 0xffffff); + + switch (PCI_HDRTYPE_TYPE(pd->pd_bhlc)) { + case PCI_HDRTYPE_DEVICE: + DPRINT(" (device)\n"); + break; + case PCI_HDRTYPE_PPB: + DPRINT(" (bridge %u -> %u-%u)\n", + PCI_BRIDGE_BUS_NUM_PRIMARY(pd->pd_bridge.bridge_bus), + PCI_BRIDGE_BUS_NUM_SECONDARY(pd->pd_bridge.bridge_bus), + PCI_BRIDGE_BUS_NUM_SUBORDINATE(pd->pd_bridge.bridge_bus)); + + if (pd->pd_bridge.ranges[PCI_RANGE_IO].end) { + DPRINT("PCI: " PCI_SBDF_FMT + " [bridge] window io %#" PRIx64 "-%#" PRIx64 + "\n", + PCI_SBDF_FMT_ARGS(pr, pd), + pd->pd_bridge.ranges[PCI_RANGE_IO].start, + pd->pd_bridge.ranges[PCI_RANGE_IO].end); + } + if (pd->pd_bridge.ranges[PCI_RANGE_MEM].end) { + DPRINT("PCI: " PCI_SBDF_FMT + " [bridge] window mem %#" PRIx64 "-%#" PRIx64 + " (non-prefetchable)\n", + PCI_SBDF_FMT_ARGS(pr, pd), + pd->pd_bridge.ranges[PCI_RANGE_MEM].start, + pd->pd_bridge.ranges[PCI_RANGE_MEM].end); + } + if (pd->pd_bridge.ranges[PCI_RANGE_PMEM].end) { + DPRINT("PCI: " PCI_SBDF_FMT + " [bridge] window mem %#" PRIx64 "-%#" PRIx64 + " (prefetchable)\n", + PCI_SBDF_FMT_ARGS(pr, pd), + pd->pd_bridge.ranges[PCI_RANGE_PMEM].start, + pd->pd_bridge.ranges[PCI_RANGE_PMEM].end); + } + + break; + default: + DPRINT(" (0x%02x)\n", PCI_HDRTYPE_TYPE(pd->pd_bhlc)); + } + + for (res = 0; res < pd->pd_niores; res++) { + pi = &pd->pd_iores[res]; + + DPRINT("PCI: " PCI_SBDF_FMT + " [device] resource BAR%u: %s @ %#" PRIx64 " size %#" + PRIx64, + PCI_SBDF_FMT_ARGS(pr, pd), pi->pi_bar, + pi->pi_type == PCI_MAPREG_TYPE_MEM ? "mem" : "io ", + pi->pi_base, pi->pi_size); + + if (pi->pi_type == PCI_MAPREG_TYPE_MEM) { + switch (pi->pi_mem.memtype) { + case PCI_MAPREG_MEM_TYPE_32BIT: + DPRINT(", 32-bit"); + break; + case PCI_MAPREG_MEM_TYPE_32BIT_1M: + DPRINT(", 32-bit (1M)"); + break; + case PCI_MAPREG_MEM_TYPE_64BIT: + DPRINT(", 64-bit"); + break; + } + DPRINT(" %sprefetchable", + pi->pi_mem.prefetch ? "" : "non-"); + } + DPRINT("\n"); + } +} + +/* + * pci_resource_scan_bar -- + * + * Determine the current BAR configuration for a given device. + */ +static void +pci_resource_scan_bar(struct pci_resources *pr, + struct pci_device *pd, pcireg_t mapreg_start, pcireg_t mapreg_end, + bool is_ppb) +{ + pci_chipset_tag_t pc = pr->pr_pc; + pcitag_t tag = pd->pd_tag; + pcireg_t mapreg = mapreg_start; + pcireg_t ocmd, cmd, bar[2], mask[2]; + uint64_t addr, size; + struct pci_iores *pi; + + if (!is_ppb) { + ocmd = cmd = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG); + cmd &= ~(PCI_COMMAND_MASTER_ENABLE | + PCI_COMMAND_MEM_ENABLE | + PCI_COMMAND_IO_ENABLE); + pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, cmd); + } + + while (mapreg < mapreg_end) { + u_int width = 4; + + bar[0] = pci_conf_read(pc, tag, mapreg); + pci_conf_write(pc, tag, mapreg, 0xffffffff); + mask[0] = pci_conf_read(pc, tag, mapreg); + pci_conf_write(pc, tag, mapreg, bar[0]); + + switch (PCI_MAPREG_TYPE(mask[0])) { + case PCI_MAPREG_TYPE_MEM: + switch (PCI_MAPREG_MEM_TYPE(mask[0])) { + case PCI_MAPREG_MEM_TYPE_32BIT: + case PCI_MAPREG_MEM_TYPE_32BIT_1M: + size = PCI_MAPREG_MEM_SIZE(mask[0]); + addr = PCI_MAPREG_MEM_ADDR(bar[0]); + break; + case PCI_MAPREG_MEM_TYPE_64BIT: + bar[1] = pci_conf_read(pc, tag, mapreg + 4); + pci_conf_write(pc, tag, mapreg + 4, 0xffffffff); + mask[1] = pci_conf_read(pc, tag, mapreg + 4); + pci_conf_write(pc, tag, mapreg + 4, bar[1]); + + size = PCI_MAPREG_MEM64_SIZE( + ((uint64_t)mask[1] << 32) | mask[0]); + addr = PCI_MAPREG_MEM64_ADDR( + ((uint64_t)bar[1] << 32) | bar[0]); + width = 8; + break; + default: + size = 0; + } + if (size > 0) { + pi = &pd->pd_iores[pd->pd_niores++]; + pi->pi_type = PCI_MAPREG_TYPE_MEM; + pi->pi_base = addr; + pi->pi_size = size; + pi->pi_bar = (mapreg - mapreg_start) / 4; + pi->pi_mem.memtype = + PCI_MAPREG_MEM_TYPE(mask[0]); + pi->pi_mem.prefetch = + PCI_MAPREG_MEM_PREFETCHABLE(mask[0]); + } + break; + case PCI_MAPREG_TYPE_IO: + size = PCI_MAPREG_IO_SIZE(mask[0] | 0xffff0000); + addr = PCI_MAPREG_IO_ADDR(bar[0]); + if (size > 0) { + pi = &pd->pd_iores[pd->pd_niores++]; + pi->pi_type = PCI_MAPREG_TYPE_IO; + pi->pi_base = addr; + pi->pi_size = size; + pi->pi_bar = (mapreg - mapreg_start) / 4; + } + break; + } + + KASSERT(pd->pd_niores <= PCI_MAX_IORES); + + mapreg += width; + } + + if (!is_ppb) { + pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, ocmd); + } +} + +/* + * pci_resource_scan_bridge -- + * + * Determine the current configuration of a PCI-PCI bridge. + */ +static void +pci_resource_scan_bridge(struct pci_resources *pr, + struct pci_device *pd) +{ + pci_chipset_tag_t pc = pr->pr_pc; + pcitag_t tag = pd->pd_tag; + pcireg_t res, reshigh; + + pd->pd_ppb = true; + + res = pci_conf_read(pc, tag, PCI_BRIDGE_BUS_REG); + pd->pd_bridge.bridge_bus = res; + pd->pd_bridge.ranges[PCI_RANGE_BUS].start = + PCI_BRIDGE_BUS_NUM_SECONDARY(res); + pd->pd_bridge.ranges[PCI_RANGE_BUS].end = + PCI_BRIDGE_BUS_NUM_SUBORDINATE(res); + + res = pci_conf_read(pc, tag, PCI_BRIDGE_STATIO_REG); + pd->pd_bridge.ranges[PCI_RANGE_IO].start = + PCI_BRIDGE_STATIO_IOBASE_ADDR(res); + pd->pd_bridge.ranges[PCI_RANGE_IO].end = + PCI_BRIDGE_STATIO_IOLIMIT_ADDR(res); + if (PCI_BRIDGE_IO_32BITS(res)) { + reshigh = pci_conf_read(pc, tag, PCI_BRIDGE_IOHIGH_REG); + pd->pd_bridge.ranges[PCI_RANGE_IO].start |= + __SHIFTOUT(reshigh, PCI_BRIDGE_IOHIGH_BASE) << 16; + pd->pd_bridge.ranges[PCI_RANGE_IO].end |= + __SHIFTOUT(reshigh, PCI_BRIDGE_IOHIGH_LIMIT) << 16; + } + if (pd->pd_bridge.ranges[PCI_RANGE_IO].start >= + pd->pd_bridge.ranges[PCI_RANGE_IO].end) { + pd->pd_bridge.ranges[PCI_RANGE_IO].start = 0; + pd->pd_bridge.ranges[PCI_RANGE_IO].end = 0; + } + + res = pci_conf_read(pc, tag, PCI_BRIDGE_MEMORY_REG); + pd->pd_bridge.ranges[PCI_RANGE_MEM].start = + PCI_BRIDGE_MEMORY_BASE_ADDR(res); + pd->pd_bridge.ranges[PCI_RANGE_MEM].end = + PCI_BRIDGE_MEMORY_LIMIT_ADDR(res); + if (pd->pd_bridge.ranges[PCI_RANGE_MEM].start >= + pd->pd_bridge.ranges[PCI_RANGE_MEM].end) { + pd->pd_bridge.ranges[PCI_RANGE_MEM].start = 0; + pd->pd_bridge.ranges[PCI_RANGE_MEM].end = 0; + } + + res = pci_conf_read(pc, tag, PCI_BRIDGE_PREFETCHMEM_REG); + pd->pd_bridge.ranges[PCI_RANGE_PMEM].start = + PCI_BRIDGE_PREFETCHMEM_BASE_ADDR(res); + pd->pd_bridge.ranges[PCI_RANGE_PMEM].end = + PCI_BRIDGE_PREFETCHMEM_LIMIT_ADDR(res); + if (PCI_BRIDGE_PREFETCHMEM_64BITS(res)) { + reshigh = pci_conf_read(pc, tag, + PCI_BRIDGE_PREFETCHBASEUP32_REG); + pd->pd_bridge.ranges[PCI_RANGE_PMEM].start |= + (uint64_t)reshigh << 32; + reshigh = pci_conf_read(pc, tag, + PCI_BRIDGE_PREFETCHLIMITUP32_REG); + pd->pd_bridge.ranges[PCI_RANGE_PMEM].end |= + (uint64_t)reshigh << 32; + } + if (pd->pd_bridge.ranges[PCI_RANGE_PMEM].start >= + pd->pd_bridge.ranges[PCI_RANGE_PMEM].end) { + pd->pd_bridge.ranges[PCI_RANGE_PMEM].start = 0; + pd->pd_bridge.ranges[PCI_RANGE_PMEM].end = 0; + } +} + +/* + * pci_resource_scan_device -- + * + * Determine the current configuration of a PCI device. + */ +static bool +pci_resource_scan_device(struct pci_resources *pr, + struct pci_bus *parent_bus, uint8_t devno, uint8_t funcno) +{ + struct pci_device *pd; + pcitag_t tag; + pcireg_t id, bridge_bus; + uint8_t sec_bus; + + tag = pci_make_tag(pr->pr_pc, parent_bus->pb_busno, devno, funcno); + id = pci_conf_read(pr->pr_pc, tag, PCI_ID_REG); + if (PCI_VENDOR(id) == PCI_VENDOR_INVALID) { + return false; + } + + pd = PCICONF_BUS_DEVICE(parent_bus, devno, funcno); + pd->pd_present = true; + pd->pd_bus = parent_bus; + pd->pd_tag = tag; + pd->pd_devno = devno; + pd->pd_funcno = funcno; + pd->pd_id = id; + pd->pd_class = pci_conf_read(pr->pr_pc, tag, PCI_CLASS_REG); + pd->pd_bhlc = pci_conf_read(pr->pr_pc, tag, PCI_BHLC_REG); + + switch (PCI_HDRTYPE_TYPE(pd->pd_bhlc)) { + case PCI_HDRTYPE_DEVICE: + pci_resource_scan_bar(pr, pd, PCI_MAPREG_START, + PCI_MAPREG_END, false); + break; + case PCI_HDRTYPE_PPB: + pci_resource_scan_bar(pr, pd, PCI_MAPREG_START, + PCI_MAPREG_PPB_END, true); + pci_resource_scan_bridge(pr, pd); + break; + } + + pci_resource_device_print(pr, pd); + + if (PCI_HDRTYPE_TYPE(pd->pd_bhlc) == PCI_HDRTYPE_PPB && + PCI_CLASS(pd->pd_class) == PCI_CLASS_BRIDGE && + PCI_SUBCLASS(pd->pd_class) == PCI_SUBCLASS_BRIDGE_PCI) { + bridge_bus = pci_conf_read(pr->pr_pc, tag, PCI_BRIDGE_BUS_REG); + sec_bus = PCI_BRIDGE_BUS_NUM_SECONDARY(bridge_bus); + if (sec_bus <= pr->pr_endbus) { + pci_resource_scan_bus(pr, pd, sec_bus); + } + } + + return true; +} + +/* + * pci_resource_scan_bus -- + * + * Enumerate devices on a bus, recursively. + */ +static void +pci_resource_scan_bus(struct pci_resources *pr, + struct pci_device *bridge_dev, uint8_t busno) +{ + struct pci_bus *pb; + uint8_t devno, funcno; + uint8_t nfunc; + + KASSERT(busno >= pr->pr_startbus); + KASSERT(busno <= pr->pr_endbus); + + if (PCICONF_RES_BUS(pr, busno) != NULL) { + /* + * Firmware has configured more than one bridge with the + * same secondary bus number. + */ + panic("Bus %u already scanned (firmware bug!)", busno); + return; + } + + pb = pci_new_bus(pr, busno, bridge_dev); + PCICONF_RES_BUS(pr, busno) = pb; + + for (devno = 0; devno < PCI_MAX_DEVICE; devno++) { + if (!pci_resource_scan_device(pr, pb, devno, 0)) { + continue; + } + pb->pb_lastdevno = devno; + + nfunc = pci_resource_device_functions(pr, busno, devno); + for (funcno = 1; funcno < nfunc; funcno++) { + pci_resource_scan_device(pr, pb, devno, funcno); + } + } +} + +/* + * pci_resource_claim -- + * + * Claim a resource from a vmem arena. This is called to inform the + * resource manager about resources already configured by system firmware. + */ +static int +pci_resource_claim(vmem_t *arena, vmem_addr_t start, vmem_addr_t end) +{ + KASSERT(end >= start); + + return vmem_xalloc(arena, end - start + 1, 0, 0, 0, start, end, + VM_BESTFIT | VM_NOSLEEP, NULL); +} + +/* + * pci_resource_alloc -- + * + * Allocate a resource from a vmem arena. This is called when configuring + * devices that were not already configured by system firmware. + */ +static int +pci_resource_alloc(vmem_t *arena, vmem_size_t size, vmem_size_t align, + uint64_t *base) +{ + vmem_addr_t addr; + int error; + + KASSERT(size != 0); + + error = vmem_xalloc(arena, size, align, 0, 0, VMEM_ADDR_MIN, + VMEM_ADDR_MAX, VM_BESTFIT | VM_NOSLEEP, &addr); + if (error == 0) { + *base = (uint64_t)addr; + } + + return error; +} + +/* + * pci_resource_init_device -- + * + * Discover resources assigned by system firmware, notify the resource + * manager of these ranges, and determine if the device has additional + * resources that need to be allocated. + */ +static void +pci_resource_init_device(struct pci_resources *pr, + struct pci_device *pd) +{ + struct pci_iores *pi; + struct pci_bus *pb = pd->pd_bus; + vmem_t *res_io = pb->pb_res[PCI_RANGE_IO]; + vmem_t *res_mem = pb->pb_res[PCI_RANGE_MEM]; + vmem_t *res_pmem = pb->pb_res[PCI_RANGE_PMEM]; + pcireg_t cmd; + u_int enabled, required; + u_int iores; + int error; + + KASSERT(pd->pd_present); + + if (IS_TEST_DEVICE(pd)) { + cmd = pci_conf_read(pr->pr_pc, pd->pd_tag, + PCI_COMMAND_STATUS_REG); + cmd &= ~(PCI_COMMAND_MEM_ENABLE|PCI_COMMAND_IO_ENABLE| + PCI_COMMAND_MASTER_ENABLE); + pci_conf_write(pr->pr_pc, pd->pd_tag, PCI_COMMAND_STATUS_REG, + cmd); + } + + enabled = required = 0; + cmd = pci_conf_read(pr->pr_pc, pd->pd_tag, PCI_COMMAND_STATUS_REG); + if ((cmd & PCI_COMMAND_MEM_ENABLE) != 0) { + enabled |= __BIT(PCI_MAPREG_TYPE_MEM); + } + if ((cmd & PCI_COMMAND_IO_ENABLE) != 0) { + enabled |= __BIT(PCI_MAPREG_TYPE_IO); + } + + for (iores = 0; iores < pd->pd_niores; iores++) { + pi = &pd->pd_iores[iores]; + + required |= __BIT(pi->pi_type); + + if (IS_TEST_DEVICE(pd)) { + pci_conf_write(pr->pr_pc, pd->pd_tag, + PCI_BAR(pi->pi_bar), 0); + continue; + } + if ((enabled & __BIT(pi->pi_type)) == 0) { + continue; + } + + if (pi->pi_type == PCI_MAPREG_TYPE_IO) { + error = res_io == NULL ? ERANGE : + pci_resource_claim(res_io, pi->pi_base, + pi->pi_base + pi->pi_size - 1); + if (error) { + DPRINT("PCI: " PCI_SBDF_FMT " [device] io " + " %#" PRIx64 "-%#" PRIx64 + " invalid (%d)\n", + PCI_SBDF_FMT_ARGS(pr, pd), + pi->pi_base, + pi->pi_base + pi->pi_size - 1, + error); + } + continue; + } + + KASSERT(pi->pi_type == PCI_MAPREG_TYPE_MEM); + error = ERANGE; + if (pi->pi_mem.prefetch && res_pmem != NULL) { + error = pci_resource_claim(res_pmem, pi->pi_base, + pi->pi_base + pi->pi_size - 1); + } + if (error && res_mem != NULL) { + error = pci_resource_claim(res_mem, pi->pi_base, + pi->pi_base + pi->pi_size - 1); + } + if (error) { + DPRINT("PCI: " PCI_SBDF_FMT " [device] mem" + " (%sprefetchable)" + " %#" PRIx64 "-%#" PRIx64 + " invalid (%d)\n", + PCI_SBDF_FMT_ARGS(pr, pd), + pi->pi_mem.prefetch ? "" : "non-", + pi->pi_base, + pi->pi_base + pi->pi_size - 1, + error); + } + } + + pd->pd_configured = (enabled & required) == required; + + if (!pd->pd_configured) { + DPRINT("PCI: " PCI_SBDF_FMT " [device] " + "not configured by firmware\n", + PCI_SBDF_FMT_ARGS(pr, pd)); + } +} + +/* + * pci_resource_init_bus -- + * + * Discover resources in use on a given bus, recursively. + */ +static void +pci_resource_init_bus(struct pci_resources *pr, uint8_t busno) +{ + struct pci_bus *pb, *parent_bus; + struct pci_device *pd, *bridge; + uint8_t devno, funcno; + uint8_t nfunc; + int error; + + KASSERT(busno >= pr->pr_startbus); + KASSERT(busno <= pr->pr_endbus); + + pb = PCICONF_RES_BUS(pr, busno); + bridge = pb->pb_bridge; + + KASSERT(pb != NULL); + KASSERT((busno == pr->pr_startbus) == (bridge == NULL)); + + if (bridge == NULL) { + /* Use resources provided by firmware. */ + PCI_RANGE_FOREACH(prtype) { + pb->pb_res[prtype] = pr->pr_res[prtype]; + pr->pr_res[prtype] = NULL; + } + } else { + /* + * Using the resources configured in to the bridge by + * firmware, claim the resources on the parent bus and + * create a new vmem arena for the secondary bus. + */ + KASSERT(bridge->pd_bus != NULL); + parent_bus = bridge->pd_bus; + PCI_RANGE_FOREACH(prtype) { + if (parent_bus->pb_res[prtype] == NULL || + !bridge->pd_bridge.ranges[prtype].end) { + continue; + } + error = pci_resource_claim( + parent_bus->pb_res[prtype], + bridge->pd_bridge.ranges[prtype].start, + bridge->pd_bridge.ranges[prtype].end); + if (error == 0) { + pb->pb_res[prtype] = pci_create_vmem( + pci_resource_typename(prtype), + bridge->pd_bridge.ranges[prtype].start, + bridge->pd_bridge.ranges[prtype].end); + KASSERT(pb->pb_res[prtype] != NULL); + } else { + DPRINT("PCI: " PCI_SBDF_FMT " bridge (bus %u)" + " %-4s %#" PRIx64 "-%#" PRIx64 + " invalid\n", + PCI_SBDF_FMT_ARGS(pr, bridge), busno, + pci_resource_typename(prtype), + bridge->pd_bridge.ranges[prtype].start, + bridge->pd_bridge.ranges[prtype].end); + } + } + } + + for (devno = 0; devno <= pb->pb_lastdevno; devno++) { + KASSERT(devno < PCI_MAX_DEVICE); + nfunc = pci_resource_device_functions(pr, busno, devno); + for (funcno = 0; funcno < nfunc; funcno++) { + pd = PCICONF_BUS_DEVICE(pb, devno, funcno); + if (!pd->pd_present) { + continue; + } + if (pd->pd_ppb) { + uint8_t sec_bus = PCI_BRIDGE_BUS_NUM_SECONDARY( + pd->pd_bridge.bridge_bus); + pci_resource_init_bus(pr, sec_bus); + } + pci_resource_init_device(pr, pd); + } + } +} + +/* + * pci_resource_probe -- + * + * Scan for PCI devices and initialize the resource manager. + */ +static void +pci_resource_probe(struct pci_resources *pr, + const struct pci_resource_info *info) +{ + uint8_t startbus = (uint8_t)info->ranges[PCI_RANGE_BUS].start; + uint8_t endbus = (uint8_t)info->ranges[PCI_RANGE_BUS].end; + u_int nbus; + + KASSERT(startbus <= endbus); + KASSERT(pr->pr_bus == NULL); + + nbus = endbus - startbus + 1; + + pr->pr_pc = info->pc; + pr->pr_startbus = startbus; + pr->pr_endbus = endbus; + pr->pr_bus = kmem_zalloc(nbus * sizeof(struct pci_bus *), KM_SLEEP); + memcpy(pr->pr_ranges, info->ranges, sizeof(pr->pr_ranges)); + PCI_RANGE_FOREACH(prtype) { + if (prtype == PCI_RANGE_BUS || info->ranges[prtype].end) { + pr->pr_res[prtype] = pci_create_vmem( + pci_resource_typename(prtype), + info->ranges[prtype].start, + info->ranges[prtype].end); + KASSERT(pr->pr_res[prtype] != NULL); + } + } + + /* Scan devices */ + pci_resource_scan_bus(pr, NULL, pr->pr_startbus); + + /* + * Create per-bus resource pools and remove ranges that are already + * in use by devices and downstream bridges. + */ + pci_resource_init_bus(pr, pr->pr_startbus); +} + +/* + * pci_resource_alloc_device -- + * + * Attempt to allocate resources for a given device. + */ +static void +pci_resource_alloc_device(struct pci_resources *pr, struct pci_device *pd) +{ + struct pci_iores *pi; + vmem_t *arena; + pcireg_t cmd, ocmd, base; + uint64_t addr; + u_int enabled; + u_int res; + u_int align; + int error; + + enabled = 0; + ocmd = cmd = pci_conf_read(pr->pr_pc, pd->pd_tag, + PCI_COMMAND_STATUS_REG); + if ((cmd & PCI_COMMAND_MEM_ENABLE) != 0) { + enabled |= __BIT(PCI_MAPREG_TYPE_MEM); + } + if ((cmd & PCI_COMMAND_IO_ENABLE) != 0) { + enabled |= __BIT(PCI_MAPREG_TYPE_IO); + } + + for (res = 0; res < pd->pd_niores; res++) { + pi = &pd->pd_iores[res]; + + if ((enabled & __BIT(pi->pi_type)) != 0) { + continue; + } + + if (pi->pi_type == PCI_MAPREG_TYPE_IO) { + arena = pd->pd_bus->pb_res[PCI_RANGE_IO]; + align = uimax(pi->pi_size, 4); + } else { + KASSERT(pi->pi_type == PCI_MAPREG_TYPE_MEM); + arena = NULL; + align = uimax(pi->pi_size, 16); + if (pi->pi_mem.prefetch) { + arena = pd->pd_bus->pb_res[PCI_RANGE_PMEM]; + } + if (arena == NULL) { + arena = pd->pd_bus->pb_res[PCI_RANGE_MEM]; + } + } + if (arena == NULL) { + DPRINT("PCI: " PCI_SBDF_FMT " BAR%u failed to" + " allocate %#" PRIx64 " bytes (no arena)\n", + PCI_SBDF_FMT_ARGS(pr, pd), + pi->pi_bar, pi->pi_size); + return; + } + error = pci_resource_alloc(arena, pi->pi_size, align, &addr); + if (error != 0) { + DPRINT("PCI: " PCI_SBDF_FMT " BAR%u failed to" + " allocate %#" PRIx64 " bytes (no space)\n", + PCI_SBDF_FMT_ARGS(pr, pd), + pi->pi_bar, pi->pi_size); + return; + } + DPRINT("PCI: " PCI_SBDF_FMT " BAR%u assigned range" + " 0x%#" PRIx64 "-0x%#" PRIx64 "\n", + PCI_SBDF_FMT_ARGS(pr, pd), + pi->pi_bar, addr, addr + pi->pi_size - 1); + + if (pi->pi_type == PCI_MAPREG_TYPE_IO) { + cmd |= PCI_COMMAND_IO_ENABLE; + pci_conf_write(pr->pr_pc, pd->pd_tag, + PCI_BAR(pi->pi_bar), + PCI_MAPREG_IO_ADDR(addr) | PCI_MAPREG_TYPE_IO); + } else { + cmd |= PCI_COMMAND_MEM_ENABLE; + base = pci_conf_read(pr->pr_pc, pd->pd_tag, + PCI_BAR(pi->pi_bar)); + base = PCI_MAPREG_MEM_ADDR(addr) | + PCI_MAPREG_MEM_TYPE(base); + pci_conf_write(pr->pr_pc, pd->pd_tag, + PCI_BAR(pi->pi_bar), base); + if (pi->pi_mem.memtype == PCI_MAPREG_MEM_TYPE_64BIT) { + base = (pcireg_t) + (PCI_MAPREG_MEM64_ADDR(addr) >> 32); + pci_conf_write(pr->pr_pc, pd->pd_tag, + PCI_BAR(pi->pi_bar + 1), base); + } + } + } + + if (ocmd != cmd) { + pci_conf_write(pr->pr_pc, pd->pd_tag, + PCI_COMMAND_STATUS_REG, cmd); + } +} + +/* + * pci_resource_alloc_bus -- + * + * Attempt to assign resources to all devices on a given bus, recursively. + */ +static void +pci_resource_alloc_bus(struct pci_resources *pr, uint8_t busno) +{ + struct pci_bus *pb = PCICONF_RES_BUS(pr, busno); + struct pci_device *pd; + uint8_t devno, funcno; + + for (devno = 0; devno <= pb->pb_lastdevno; devno++) { + for (funcno = 0; funcno < 8; funcno++) { + pd = PCICONF_BUS_DEVICE(pb, devno, funcno); + if (!pd->pd_present) { + if (funcno == 0) { + break; + } + continue; + } + if (!pd->pd_configured) { + pci_resource_alloc_device(pr, pd); + } + if (pd->pd_ppb) { + uint8_t sec_bus = PCI_BRIDGE_BUS_NUM_SECONDARY( + pd->pd_bridge.bridge_bus); + pci_resource_alloc_bus(pr, sec_bus); + } + } + } +} + +/* + * pci_resource_init -- + * + * Public interface to PCI resource manager. Scans for available devices + * and assigns resources. + */ +void +pci_resource_init(const struct pci_resource_info *info) +{ + struct pci_resources pr = {}; + + pci_resource_probe(&pr, info); + pci_resource_alloc_bus(&pr, pr.pr_startbus); +} + +/* + * pci_resource_typename -- + * + * Return a string description of a PCI range type. + */ +const char * +pci_resource_typename(enum pci_range_type prtype) +{ + KASSERT(prtype < NUM_PCI_RANGES); + return pci_range_typenames[prtype]; +} diff --git a/sys/dev/pci/pci_resource.h b/sys/dev/pci/pci_resource.h new file mode 100644 index 00000000000..3e7b7883dc2 --- /dev/null +++ b/sys/dev/pci/pci_resource.h @@ -0,0 +1,50 @@ +/* $NetBSD: pci_resource.h,v 1.1 2022/10/14 22:10:15 jmcneill Exp $ */ + +/*- + * Copyright (c) 2022 Jared McNeill <jmcneill@invisible.ca> + * 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. + * + * 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. + */ + +#pragma once + +enum pci_range_type { + PCI_RANGE_BUS, + PCI_RANGE_IO, + PCI_RANGE_MEM, + PCI_RANGE_PMEM, + NUM_PCI_RANGES +}; + +struct pci_resource_range { + uint64_t start; + uint64_t end; +}; + +struct pci_resource_info { + pci_chipset_tag_t pc; + struct pci_resource_range ranges[NUM_PCI_RANGES]; +}; + +void pci_resource_init(const struct pci_resource_info *); +const char * pci_resource_typename(enum pci_range_type); |
