From 422330d4ab2d7c4df0692f7206279ff4de5de806 Mon Sep 17 00:00:00 2001 From: Sainath Grandhi Date: Fri, 1 Nov 2019 00:22:46 -0700 Subject: [PATCH] HV: reimplement PCI device discovery Major changes: 1. Correct handling of device multi-function capability We only check function zero for this feature. If it has it, we continue looking at all remaining functions, ignoring those with invalid vendors. The PCI spec says we are not to probe beyond function zero if it does not exist or indicates it is not a multi-function device. 2a. Walk *ALL* buses in the PCI space, however, Before walking the PCI hierarchy, post-processed ACPI DMAR info is parsed and a map is created between all device-scopes across all DRHDs and the corresponding IOMMU index. This map is used at the time of walking the PCI hierarchy. If a BDF that ACRN is currently working on, is found in the above-mentioned map, the BDF device is mapped to the corresponding DRHD in the map. If the BDF were a bridge type, realized with "Header Type" in config space, the BDF device along with all its downstream devices are mapped to the corresponding DRHD in the map. To avoid walking previously visited buses, we maintain a bitmap that stores which bus is walked when we handle Bridge type devices. Once ACPI information is included into ACRN about the PCI-Express Root Complexes / PCI Host Bridges, we can avoid the final loop which probes all remainder buses, and instead jump to the next Host Bridge bus. From prior patches, init_pdev returns the pdev structure it created to the caller. This allows us to complete initialization by updating its drhd_idx to the correct DRHD. Tracked-On: #4134 Signed-off-by: Alexander Merritt Signed-off-by: Sainath Grandhi Reviewed-by: Eddie Dong Reviewed-by: Jason Chen CJ --- hypervisor/hw/pci.c | 247 +++++++++++++++++++++++++++++------- hypervisor/include/hw/pci.h | 6 + 2 files changed, 210 insertions(+), 43 deletions(-) diff --git a/hypervisor/hw/pci.c b/hypervisor/hw/pci.c index e76800fa6..af395f00f 100644 --- a/hypervisor/hw/pci.c +++ b/hypervisor/hw/pci.c @@ -38,16 +38,19 @@ #include #include #include +#include +#include +#include static spinlock_t pci_device_lock; static uint32_t num_pci_pdev; static struct pci_pdev pci_pdev_array[CONFIG_MAX_PCI_DEV_NUM]; -static void init_pdev(uint16_t pbdf); +static void init_pdev(uint16_t pbdf, uint32_t drhd_index); /* @brief: Find the DRHD index corresponding to a PCI device * Runs through the pci_pdev_array and returns the value in drhd_idx - * member from pdev strucutre that matches matches B:D.F + * member from pdev structure that matches matches B:D.F * * @pbdf[in] B:D.F of a PCI device * @@ -151,69 +154,225 @@ void enable_disable_pci_intx(union pci_bdf bdf, bool enable) } } -#define BUS_SCAN_SKIP 0U -#define BUS_SCAN_PENDING 1U -#define BUS_SCAN_COMPLETE 2U -void init_pci_pdev_list(void) +static bool is_hidden_pdev(union pci_bdf pbdf) { + bool hidden = false; + /* if it is debug uart, hide it*/ + if (is_pci_dbg_uart(pbdf)) { + pr_info("hide pci uart dev: (%x:%x:%x)", pbdf.bits.b, pbdf.bits.d, pbdf.bits.f); + hidden = true; + } + + return hidden; +} + +static void pci_init_pdev(union pci_bdf pbdf, uint32_t drhd_index) +{ + if (!is_hidden_pdev(pbdf)) { + init_pdev(pbdf.value, drhd_index); + } +} + +/* + * quantity of uint64_t to encode a bitmap of all bus values + * TODO: PCI_BUSMAX is a good candidate to move to + * generated platform files. + */ +#define BUSES_BITMAP_LEN ((PCI_BUSMAX + 1U) >> 6U) + +/* + * must be >= total Endpoints in all DRDH devscope + * TODO: BDF_SET_LEN is a good candidate to move to + * generated platform files. + */ +#define BDF_SET_LEN 32U + +struct pci_bdf_to_iommu { + union pci_bdf dev_scope_bdf; + uint32_t dev_scope_drhd_index; +}; + +struct pci_bdf_set { + uint32_t pci_bdf_map_count; + struct pci_bdf_to_iommu bdf_map[BDF_SET_LEN]; +}; + +struct pci_bus_set { + uint8_t bus_under_scan; + uint32_t bus_drhd_index; +}; + +static uint32_t pci_check_override_drhd_index(union pci_bdf pbdf, + const struct pci_bdf_set *const bdfs_from_drhds, + uint32_t current_drhd_index) +{ + uint16_t bdfi; + uint32_t bdf_drhd_index = current_drhd_index; + + for (bdfi = 0U; bdfi < bdfs_from_drhds->pci_bdf_map_count; bdfi++) { + if (bdfs_from_drhds->bdf_map[bdfi].dev_scope_bdf.value == pbdf.value) { + /* + * Override current_drhd_index + */ + bdf_drhd_index = + bdfs_from_drhds->bdf_map[bdfi].dev_scope_drhd_index; + } + } + + return bdf_drhd_index; +} + +/* Scan part of PCI hierarchy, starting with the given bus. */ +static void init_pci_hierarchy(uint8_t bus, uint64_t buses_visited[BUSES_BITMAP_LEN], + const struct pci_bdf_set *const bdfs_from_drhds, uint32_t drhd_index) +{ + bool is_mfdev; + uint32_t vendor; + uint8_t hdr_type, dev, func; union pci_bdf pbdf; - uint8_t hdr_type, secondary_bus, dev, func; - uint32_t bus, val; - uint8_t bus_to_scan[PCI_BUSMAX + 1] = { BUS_SCAN_SKIP }; + uint8_t current_bus_index; + uint32_t current_drhd_index, bdf_drhd_index; - /* start from bus 0 */ - bus_to_scan[0U] = BUS_SCAN_PENDING; + struct pci_bus_set bus_map[PCI_BUSMAX + 1U]; /* FIFO queue of buses to walk */ + uint32_t s = 0U, e = 0U; /* start and end index into queue */ - for (bus = 0U; bus <= PCI_BUSMAX; bus++) { - if (bus_to_scan[bus] != BUS_SCAN_PENDING) { - continue; - } + bus_map[e].bus_under_scan = bus; + bus_map[e].bus_drhd_index = drhd_index; + e = e + 1U; + while (s < e) { + current_bus_index = bus_map[s].bus_under_scan; + current_drhd_index = bus_map[s].bus_drhd_index; + s = s + 1U; - bus_to_scan[bus] = BUS_SCAN_COMPLETE; - pbdf.bits.b = (uint8_t)bus; + bitmap_set_nolock(current_bus_index, + &buses_visited[current_bus_index >> 6U]); + pbdf.bits.b = current_bus_index; for (dev = 0U; dev <= PCI_SLOTMAX; dev++) { pbdf.bits.d = dev; + is_mfdev = false; for (func = 0U; func <= PCI_FUNCMAX; func++) { + pbdf.bits.f = func; - val = pci_pdev_read_cfg(pbdf, PCIR_VENDOR, 4U); - if ((val == 0xFFFFFFFFU) || (val == 0U) || (val == 0xFFFF0000U) || (val == 0xFFFFU)) { - /* If function 0 is not implemented, skip to next device */ - if (func == 0U) { - break; - } + vendor = read_pci_pdev_cfg_vendor(pbdf); + hdr_type = read_pci_pdev_cfg_headertype(pbdf); - /* continue scan next function */ + if (func == 0U) { + is_mfdev = is_pci_cfg_multifunction(hdr_type); + } + + /* Do not probe beyond function 0 if not a multi-function device + * TODO unless device supports ARI or SR-IOV + * (PCIe spec r5.0 ยง7.5.1.1.9) + */ + if (((func == 0U) && !is_pci_vendor_valid(vendor)) || + ((func > 0U) && (!is_mfdev))) { + break; + } + + if (!is_pci_vendor_valid(vendor)) { continue; } - /* if it is debug uart, hide it from SOS */ - if (is_pci_dbg_uart(pbdf)) { - pr_info("hide pci uart dev: (%x:%x:%x)", pbdf.bits.b, pbdf.bits.d, pbdf.bits.f); - continue; - } + bdf_drhd_index = pci_check_override_drhd_index(pbdf, bdfs_from_drhds, + current_drhd_index); + pci_init_pdev(pbdf, bdf_drhd_index); - init_pdev(pbdf.value); - - hdr_type = (uint8_t)pci_pdev_read_cfg(pbdf, PCIR_HDRTYPE, 1U); - if ((hdr_type & PCIM_HDRTYPE) == PCIM_HDRTYPE_BRIDGE) { - - /* Secondary bus to be scanned */ - secondary_bus = (uint8_t)pci_pdev_read_cfg(pbdf, PCIR_SECBUS_1, 1U); - if (bus_to_scan[secondary_bus] != BUS_SCAN_SKIP) { - pr_err("%s, bus %d may be downstream of different PCI bridges", - __func__, secondary_bus); - } else { - bus_to_scan[secondary_bus] = BUS_SCAN_PENDING; - } + if (is_pci_cfg_bridge(hdr_type)) { + bus_map[e].bus_under_scan = read_pci_pdev_cfg_secbus(pbdf); + bus_map[e].bus_drhd_index = bdf_drhd_index; + e = e + 1U; } } } } } +/* + * @brief: Setup bdfs_from_drhds data structure as the DMAR tables are walked searching + * for PCI device scopes. bdfs_from_drhds is used later in init_pci_hierarchy + * to map the right DRHD unit to the PCI device + */ +static void pci_add_bdf_from_drhd(union pci_bdf bdf, struct pci_bdf_set *const bdfs_from_drhds, + uint32_t drhd_index) +{ + if (bdfs_from_drhds->pci_bdf_map_count < BDF_SET_LEN) { + bdfs_from_drhds->bdf_map[bdfs_from_drhds->pci_bdf_map_count].dev_scope_bdf = bdf; + bdfs_from_drhds->bdf_map[bdfs_from_drhds->pci_bdf_map_count].dev_scope_drhd_index = drhd_index; + bdfs_from_drhds->pci_bdf_map_count++; + } else { + ASSERT(bdfs_from_drhds->pci_bdf_map_count < BDF_SET_LEN, + "Compare value in BDF_SET_LEN against those in ACPI DMAR tables"); + } +} + + +/* + * @brief: Setup bdfs_from_drhds data structure as the DMAR tables are walked searching + * for PCI device scopes. bdfs_from_drhds is used later in init_pci_hierarchy + * to map the right DRHD unit to the PCI device. + * TODO: bdfs_from_drhds is a good candidate to be part of generated platform + * info. + */ +static void pci_parse_iommu_devscopes(struct pci_bdf_set *const bdfs_from_drhds, + uint32_t *drhd_idx_pci_all) +{ + union pci_bdf bdf; + uint32_t drhd_index, devscope_index; + + for (drhd_index = 0U; drhd_index < plat_dmar_info.drhd_count; drhd_index++) { + for (devscope_index = 0U; devscope_index < plat_dmar_info.drhd_units[drhd_index].dev_cnt; + devscope_index++) { + bdf.fields.bus = plat_dmar_info.drhd_units[drhd_index].devices[devscope_index].bus; + bdf.fields.devfun = plat_dmar_info.drhd_units[drhd_index].devices[devscope_index].devfun; + + if ((plat_dmar_info.drhd_units[drhd_index].devices[devscope_index].type == + ACPI_DMAR_SCOPE_TYPE_ENDPOINT) || + (plat_dmar_info.drhd_units[drhd_index].devices[devscope_index].type == + ACPI_DMAR_SCOPE_TYPE_BRIDGE)) { + pci_add_bdf_from_drhd(bdf, bdfs_from_drhds, drhd_index); + } else { + /* + * Do nothing for IOAPIC, ACPI namespace and + * MSI Capable HPET device scope + */ + } + } + } + + if ((plat_dmar_info.drhd_units[plat_dmar_info.drhd_count - 1U].flags & DRHD_FLAG_INCLUDE_PCI_ALL_MASK) + == DRHD_FLAG_INCLUDE_PCI_ALL_MASK) { + *drhd_idx_pci_all = plat_dmar_info.drhd_count - 1U; + } +} + +/* + * @brief Walks the PCI heirarchy and initializes array of pci_pdev structs + * Uses DRHD info from ACPI DMAR tables to cover the endpoints and + * bridges along with their hierarchy captured in the device scope entries + * Walks through rest of the devices starting at bus 0 and thru PCI_BUSMAX + */ +void init_pci_pdev_list(void) +{ + uint64_t buses_visited[BUSES_BITMAP_LEN] = {0UL}; + struct pci_bdf_set bdfs_from_drhds; + uint32_t drhd_idx_pci_all = INVALID_DRHD_INDEX; + uint16_t bus; + bool was_visited = false; + + pci_parse_iommu_devscopes(&bdfs_from_drhds, &drhd_idx_pci_all); + + /* TODO: iterate over list of PCI Host Bridges found in ACPI namespace */ + for (bus = 0U; bus <= PCI_BUSMAX; bus++) { + was_visited = bitmap_test((bus & 0x3FU), &buses_visited[bus >> 6U]); + if (!was_visited) { + init_pci_hierarchy((uint8_t)bus, buses_visited, &bdfs_from_drhds, drhd_idx_pci_all); + } + } +} + static inline uint32_t pci_pdev_get_nr_bars(uint8_t hdr_type) { uint32_t nr_bars = 0U; @@ -283,7 +442,7 @@ static void pci_read_cap(struct pci_pdev *pdev) } } -static void init_pdev(uint16_t pbdf) +static void init_pdev(uint16_t pbdf, uint32_t drhd_index) { uint8_t hdr_type; union pci_bdf bdf; @@ -303,6 +462,8 @@ static void init_pdev(uint16_t pbdf) pci_read_cap(pdev); } + pdev->drhd_index = drhd_index; + fill_pci_dev_config(pdev); num_pci_pdev++; diff --git a/hypervisor/include/hw/pci.h b/hypervisor/include/hw/pci.h index b856ef7e7..17d0991cb 100644 --- a/hypervisor/include/hw/pci.h +++ b/hypervisor/include/hw/pci.h @@ -237,6 +237,12 @@ uint32_t pci_pdev_read_cfg(union pci_bdf bdf, uint32_t offset, uint32_t bytes); void pci_pdev_write_cfg(union pci_bdf bdf, uint32_t offset, uint32_t bytes, uint32_t val); void enable_disable_pci_intx(union pci_bdf bdf, bool enable); +/* + * @brief Walks the PCI heirarchy and initializes array of pci_pdev structs + * Uses DRHD info from ACPI DMAR tables to cover the endpoints and + * bridges along with their hierarchy captured in the device scope entries + * Walks through rest of the devices starting at bus 0 and thru PCI_BUSMAX + */ void init_pci_pdev_list(void); /* @brief: Find the DRHD index corresponding to a PCI device