From: Tushar Dave <tdave@nvidia.com>
To: qemu-devel@nongnu.org
Cc: alwilliamson@nvidia.com, jgg@nvidia.com, skolothumtho@nvidia.com,
qemu-arm@nongnu.org, peter.maydell@linaro.org, mst@redhat.com,
marcel.apfelbaum@gmail.com, devel@edk2.groups.io
Subject: [RFC PATCH 2/8] hw/pci: enumerate PCI bus and program bridge bus numbers
Date: Fri, 8 May 2026 13:37:11 -0500 [thread overview]
Message-ID: <20260508183717.193630-3-tdave@nvidia.com> (raw)
In-Reply-To: <20260508183717.193630-1-tdave@nvidia.com>
When guest firmware is told not to perform bus enumeration, QEMU
must program bridge primary, secondary, and subordinate bus number
registers before handing control to firmware. Walk the hierarchy
under the root bus, assign secondary bus numbers in firmware-like
order (PXB roots first by bus number, then PCI bridges by devfn),
and program those bridge registers.
Note that SR-IOV bus number allocation (VF offset/stride/NumVFs) is not
handled in this commit and requires additional work.
Signed-off-by: Tushar Dave <tdave@nvidia.com>
---
hw/pci/meson.build | 1 +
hw/pci/pci-enumerate.c | 144 +++++++++++++++++++++++++++++++++++++++++
hw/pci/pci-enumerate.h | 15 +++++
3 files changed, 160 insertions(+)
create mode 100644 hw/pci/pci-enumerate.c
create mode 100644 hw/pci/pci-enumerate.h
diff --git a/hw/pci/meson.build b/hw/pci/meson.build
index a6cbd89c0a..7e8f5bb87d 100644
--- a/hw/pci/meson.build
+++ b/hw/pci/meson.build
@@ -5,6 +5,7 @@ pci_ss.add(files(
'pci.c',
'pci_bridge.c',
'pci_host.c',
+ 'pci-enumerate.c',
'pci-hmp-cmds.c',
'pci-qmp-cmds.c',
'pcie_sriov.c',
diff --git a/hw/pci/pci-enumerate.c b/hw/pci/pci-enumerate.c
new file mode 100644
index 0000000000..2c6d25b25d
--- /dev/null
+++ b/hw/pci/pci-enumerate.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2026 NVIDIA
+ * Written by Tushar Dave
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci/pci-enumerate.h"
+
+/* Forward declaration */
+static uint8_t pci_program_bus_numbers(PCIBus *bus, uint8_t current_bus_num,
+ uint8_t *next_bus_num);
+
+static int cmp_bus_by_devfn(gconstpointer a, gconstpointer b)
+{
+ PCIBus *bus_a = *(PCIBus * const *)a;
+ PCIBus *bus_b = *(PCIBus * const *)b;
+ return (int)bus_a->parent_dev->devfn - (int)bus_b->parent_dev->devfn;
+}
+
+static int cmp_bus_by_num(gconstpointer a, gconstpointer b)
+{
+ PCIBus *bus_a = *(PCIBus * const *)a;
+ PCIBus *bus_b = *(PCIBus * const *)b;
+ return pci_bus_num(bus_a) - pci_bus_num(bus_b);
+}
+
+/*
+ * Program one bridge's primary, secondary and subordinate bus numbers
+ * and recurse. Return the max subordinate bus number.
+ */
+static uint8_t pci_program_bridge(PCIDevice *bridge, PCIBus *child_bus,
+ uint8_t current_bus_num,
+ uint8_t *next_bus_num)
+{
+ uint8_t secondary, max_child;
+
+ /* Bus number space exhausted; no bus number to assign. */
+ if (*next_bus_num == 0) {
+ return current_bus_num;
+ }
+ secondary = *next_bus_num;
+ (*next_bus_num)++;
+
+ pci_default_write_config(bridge, PCI_PRIMARY_BUS, current_bus_num, 1);
+ pci_default_write_config(bridge, PCI_SECONDARY_BUS, secondary, 1);
+ /*
+ * Unlike real hardware, QEMU does not require opening a subordinate
+ * aperture before scanning downstream devices. Write secondary as
+ * a placeholder; the final value is set after recursion below.
+ */
+ pci_default_write_config(bridge, PCI_SUBORDINATE_BUS, secondary, 1);
+
+ max_child = pci_program_bus_numbers(child_bus, secondary, next_bus_num);
+ pci_default_write_config(bridge, PCI_SUBORDINATE_BUS, max_child, 1);
+ return max_child;
+}
+
+/*
+ * Program bus numbers for this bus and all subordinates.
+ * - current_bus_num: this bus' number (0 for root, or already set for PXB).
+ * - next_bus_num: next free bus number to assign to a bridge.
+ *
+ * Children come from bus->child only. Two kinds:
+ * 1) PXB (extra root): child has PCI_BUS_IS_ROOT. Has bus number
+ * already set, recurse only.
+ * 2) Normal bridge: parent is IS_PCI_BRIDGE. Assign secondary = *next_bus_num,
+ * program primary, secondary and subordinate bus numbers, and recurse.
+ *
+ * Order matches EDK2 PciBusDxe enumeration: process PXB children first
+ * (sorted by bus number), then bridges (sorted by devfn).
+ */
+static uint8_t pci_program_bus_numbers(PCIBus *bus, uint8_t current_bus_num,
+ uint8_t *next_bus_num)
+{
+ PCIBus *child_bus;
+ GArray *pxb_buses = g_array_new(false, false, sizeof(PCIBus *));
+ GArray *bridges = g_array_new(false, false, sizeof(PCIBus *));
+ uint8_t max_subordinate = current_bus_num;
+ uint8_t child_num;
+ uint8_t one_max;
+ guint i;
+
+ /* Single pass over bus->child: split into PXB vs bridge */
+ QLIST_FOREACH(child_bus, &bus->child, sibling) {
+ if (!child_bus->parent_dev) {
+ continue;
+ }
+ if (pci_bus_is_root(child_bus)) {
+ /* PXB or similar: bus number already set (e.g. bus_nr=1, 9) */
+ g_array_append_val(pxb_buses, child_bus);
+ } else if (IS_PCI_BRIDGE(child_bus->parent_dev)) {
+ g_array_append_val(bridges, child_bus);
+ }
+ }
+
+ /* PXB first, sorted by bus number (e.g. 1 before 9) */
+ if (pxb_buses->len > 1) {
+ g_array_sort(pxb_buses, cmp_bus_by_num);
+ }
+ for (i = 0; i < pxb_buses->len; i++) {
+ child_bus = g_array_index(pxb_buses, PCIBus *, i);
+ child_num = (uint8_t)pci_bus_num(child_bus);
+ if (child_num + 1 > *next_bus_num) {
+ *next_bus_num = child_num + 1;
+ }
+ one_max = pci_program_bus_numbers(child_bus, child_num, next_bus_num);
+ if (one_max > max_subordinate) {
+ max_subordinate = one_max;
+ }
+ }
+ g_array_free(pxb_buses, true);
+
+ /* Bridges second, sorted by devfn */
+ if (bridges->len > 1) {
+ g_array_sort(bridges, cmp_bus_by_devfn);
+ }
+ for (i = 0; i < bridges->len; i++) {
+ child_bus = g_array_index(bridges, PCIBus *, i);
+ one_max = pci_program_bridge(child_bus->parent_dev, child_bus,
+ current_bus_num, next_bus_num);
+ if (one_max > max_subordinate) {
+ max_subordinate = one_max;
+ }
+ }
+ g_array_free(bridges, true);
+
+ return max_subordinate;
+}
+
+void pci_enumerate_bus(PCIBus *root_bus)
+{
+ uint8_t next_bus_num;
+
+ if (!root_bus) {
+ return;
+ }
+ next_bus_num = 1;
+ pci_program_bus_numbers(root_bus, 0, &next_bus_num);
+}
diff --git a/hw/pci/pci-enumerate.h b/hw/pci/pci-enumerate.h
new file mode 100644
index 0000000000..b1e4b989f1
--- /dev/null
+++ b/hw/pci/pci-enumerate.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2026 NVIDIA
+ * Written by Tushar Dave
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_PCI_PCI_ENUMERATE_H
+#define HW_PCI_PCI_ENUMERATE_H
+
+#include "hw/pci/pci_bus.h"
+
+void pci_enumerate_bus(PCIBus *root_bus);
+
+#endif
--
2.34.1
next prev parent reply other threads:[~2026-05-08 20:43 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-08 18:37 [RFC PATCH 0/8] hw/arm/virt, hw/pci: PCI pre-enumeration and fixed BAR allocation Tushar Dave
2026-05-08 18:37 ` [RFC PATCH 1/8] hw/pci: add fixed-bars property to allow fixed BAR addresses Tushar Dave
2026-05-08 18:37 ` Tushar Dave [this message]
2026-05-08 18:37 ` [RFC PATCH 3/8] hw/pci: introduce allocator for fixed BAR placement Tushar Dave
2026-05-08 18:37 ` [RFC PATCH 4/8] hw/pci: pack remaining BARs and update bridge windows Tushar Dave
2026-05-08 18:37 ` [RFC PATCH 5/8] hw/pci: allocate remaining BARs for buses without fixed BARs Tushar Dave
2026-05-08 18:37 ` [RFC PATCH 6/8] hw/pci: finalize bridge prefetch windows after BAR allocation Tushar Dave
2026-05-08 18:37 ` [RFC PATCH 7/8] hw/arm/virt: add pcie-mmio-window machine property Tushar Dave
2026-05-08 18:37 ` [RFC PATCH 8/8] hw/arm/virt: add pci-pre-enum " Tushar Dave
2026-05-11 7:46 ` [RFC PATCH 0/8] hw/arm/virt, hw/pci: PCI pre-enumeration and fixed BAR allocation Peter Maydell
2026-05-11 12:26 ` Jason Gunthorpe
2026-05-11 18:38 ` Mohamed Mediouni
2026-05-11 20:28 ` Jason Gunthorpe
2026-05-11 9:09 ` Michael S. Tsirkin
2026-05-11 18:10 ` Tushar Dave
2026-05-11 22:09 ` Michael S. Tsirkin
2026-05-11 11:43 ` [edk2-devel] " Ard Biesheuvel
2026-05-12 17:25 ` Tushar Dave
2026-05-12 23:06 ` Alex Williamson
2026-05-12 23:12 ` Michael S. Tsirkin
2026-05-12 23:57 ` Alex Williamson
2026-05-13 11:36 ` Jason Gunthorpe
2026-05-13 14:25 ` Ard Biesheuvel
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260508183717.193630-3-tdave@nvidia.com \
--to=tdave@nvidia.com \
--cc=alwilliamson@nvidia.com \
--cc=devel@edk2.groups.io \
--cc=jgg@nvidia.com \
--cc=marcel.apfelbaum@gmail.com \
--cc=mst@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=skolothumtho@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.