diff --git a/devicemodel/hw/pci/pci_util.c b/devicemodel/hw/pci/pci_util.c index 498afb342..4dd588a0f 100644 --- a/devicemodel/hw/pci/pci_util.c +++ b/devicemodel/hw/pci/pci_util.c @@ -16,6 +16,35 @@ #include "pci_util.h" #include "log.h" +#define MAX_LEN (PCI_BUSMAX + 1) + +struct pci_device_info { + bool is_bridge; + int primary_bus; + int secondary_bus; + int subordinate_bus; + uint16_t bdf; + struct pci_device_info *parent; /* pointer to its parent bridge */ + struct pci_device_info *clist; /* children list */ + + /* cache of all pci devices + * FIXME: remove PCI_DEVICE_Q. To cleanup pci device cache: + * remove children, then remove parents + */ + TAILQ_ENTRY(pci_device_info) PCI_DEVICE_Q; +}; + +TAILQ_HEAD(ALL_PCI_DEVICES, pci_device_info); +static struct ALL_PCI_DEVICES pci_device_q; + +static int pci_scanned; + +/* map bus to bridge + * assume only one pci domain in the system so the + * bus # in the pci hierarchy is unique. + */ +static struct pci_device_info *bus_map[MAX_LEN]; + /* find position of specified pci capability register*/ int pci_find_cap(struct pci_device *pdev, const int cap_id) { @@ -101,3 +130,204 @@ bool is_bridge(struct pci_device *pdev) return ((hdr_type & PCIM_HDRTYPE) == PCIM_HDRTYPE_BRIDGE); } + +/* add new_child to parent's child list */ +static inline void +add_child(struct pci_device_info *new_child, + struct pci_device_info *parent) +{ + new_child->clist = parent->clist; + parent->clist = new_child; + + // it is important to reset the new_child's clist to null + new_child->clist = NULL; +} + +/* get number of children on bus */ +int get_device_count_on_bus(int bus) +{ + int count = 0; + struct pci_device_info *bridge = bus_map[bus]; + struct pci_device_info *next; + + if (bridge == NULL) + return 0; + + next = bridge->clist; + + while (next != NULL) { + if (next->is_bridge) + count += get_device_count_on_bridge(bridge); + else + count++; + + next = next->clist; + } + + return count; +} + +/* get number of children on bridge */ +int get_device_count_on_bridge(const struct pci_device_info *bridge_info) +{ + int count = 0; + int i, ret; + + if (bridge_info == NULL) + return 0; + + for (i = bridge_info->secondary_bus; i <= bridge_info->subordinate_bus; i++) { + ret = get_device_count_on_bus(i); + pr_info("%s: device_count on bus[%x]=%d.\n", __func__, i, ret); + count += ret; + } + + return count; +} + +/* build and cache pci hierarchy in one pci domain + * @pre: only one pci domain in the system. Need revisit + * if there is more than one domain in the system. + */ +int scan_pci(void) +{ + int error = 0; + struct pci_device_iterator *iter; + struct pci_device *dev; + int i; + + TAILQ_INIT(&pci_device_q); + + pci_scanned = 1; + + iter = pci_slot_match_iterator_create(NULL); + while ((dev = pci_device_next(iter)) != NULL) { + struct pci_device_info *pci_info = calloc(1, sizeof(struct pci_device_info)); + + if (pci_info == NULL) { + error = -ENOMEM; + pr_err("%s, %s: calloc", __func__, __LINE__); + goto done; + } + + pci_info->parent = NULL; + pci_info->clist = NULL; + + pci_info->bdf = PCI_BDF(dev->bus, dev->dev, dev->func); + + TAILQ_INSERT_TAIL(&pci_device_q, pci_info, PCI_DEVICE_Q); + + if (pci_info->bdf == 0) { /* host bridge */ + bus_map[0] = pci_info; + continue; + } + + if (is_bridge(dev)) { + pci_info->is_bridge = true; + + pci_device_get_bridge_buses(dev, &(pci_info->primary_bus), + &(pci_info->secondary_bus), &(pci_info->subordinate_bus)); + + // put bus and bridge into bus_map + for (i = pci_info->secondary_bus; i <= pci_info->subordinate_bus; i++) + bus_map[i] = pci_info; + } else + pci_info->is_bridge = false; + + // set its parent bridge + pci_info->parent = bus_map[dev->bus]; + + // add itself to its parent bridge's child list + add_child(pci_info, pci_info->parent); + } + +done: + if (error < 0) + clean_pci_cache(); + + pci_iterator_destroy(iter); + + return error; +} + +/* find bdf of pci device pdev's root port */ +struct pci_device * pci_find_root_port(const struct pci_device *pdev) +{ + struct pci_device *bdev; + int bus, dev, func; + + struct pci_device_info *brdg = bus_map[pdev->bus]; + + for (;;) { + if (brdg == NULL) + return NULL; + + bus = (brdg->bdf >> 8) & 0xFF; + dev = (brdg->bdf >> 3) & 0x1F; + func = brdg->bdf & 0x7; + + bdev = pci_device_find_by_slot(0, bus, dev, func); + if (is_root_port(bdev)) + return bdev; + + brdg = brdg->parent; + } + + return NULL; +} + +/* clean cache of pci device: free all allocated memory */ +void clean_pci_cache(void) +{ + if (!pci_scanned) + return; + + while (!TAILQ_EMPTY(&pci_device_q)) { + struct pci_device_info *p = TAILQ_FIRST(&pci_device_q); + + TAILQ_REMOVE(&pci_device_q, p, PCI_DEVICE_Q); + } + + pci_scanned = 0; +} + +#ifdef SCAN_PCI_TEST +static void +scan_pci_test(void) +{ + struct pci_device_info *device_info; + struct pci_device *pdev; + int bdf, parent_bdf; + + printf("%s enter.\n", __func__); + if (!pci_scanned) { + printf("%s: no pci hierarchy to test.\n", __func__); + return; + } + + TAILQ_FOREACH(device_info, &pci_device_q, PCI_DEVICE_Q) { + bdf = device_info->bdf; + + if (bdf == 0) { + printf("%s: host bridge.\n", __func__); + continue; + } + + parent_bdf = device_info->parent->bdf; + + printf("%s: [%x:%x.%x]: [%x:%x.%x]\n", __func__, (bdf >> 8) & 0xFF, (bdf >> 3) & 0x1F, bdf & 0x7, + (parent_bdf >> 8) & 0xFF, (parent_bdf >> 3) & 0x1F, parent_bdf & 0x7); + + pdev = pci_device_find_by_slot(0, (bdf >> 8) & 0xFF, (bdf >> 3) & 0x1F, bdf & 0x7); + + if (is_bridge(pdev)) { + struct pci_device_info *child; + child = device_info->clist; + while(child != NULL) { + printf("\t\t: child: [%x:%x.%x]\n", (child->bdf >> 8) & 0xFF, (child->bdf >> 3) & 0x1F, child->bdf & 0x7); + child = child->clist; + } + } + } +} +#endif diff --git a/devicemodel/include/pci_util.h b/devicemodel/include/pci_util.h index 71cc8a2dc..9be322e45 100644 --- a/devicemodel/include/pci_util.h +++ b/devicemodel/include/pci_util.h @@ -11,10 +11,18 @@ #include #include "pciaccess.h" +struct pci_device_info; + int pci_find_cap(struct pci_device *pdev, const int cap_id); int pci_find_ext_cap(struct pci_device *pdev, int cap_id); int pci_get_pcie_type(struct pci_device *dev); bool is_root_port(struct pci_device *pdev); bool is_bridge(struct pci_device *pdev); +int get_device_count_on_bus(int bus); +int get_device_count_on_bridge(const struct pci_device_info *bridge_info); +int scan_pci(void); +struct pci_device * pci_find_root_port(const struct pci_device *pdev); +void clean_pci_cache(void); + #endif