All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] hw/arm/bcm2838_pcie: make PCI device enumeration work
@ 2026-07-25 12:42 Marcelo Manzo
  2026-07-25 12:42 ` [PATCH 1/2] hw/arm/bcm2838_pcie: make PCI device enumeration actually work Marcelo Manzo
  2026-07-25 12:42 ` [PATCH 2/2] tests/functional/aarch64: add raspi4b PCIe enumeration test Marcelo Manzo
  0 siblings, 2 replies; 3+ messages in thread
From: Marcelo Manzo @ 2026-07-25 12:42 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé,
	Marcelo Manzo

Follow-up to "hw/arm/raspi4b: working PCIe and GENET (real
networking)" [1]. That series got the PCIe Root Complex link up and
GENET (a plain sysbus device, not PCI) working, but nothing in it
actually required a guest to enumerate a device *on* the PCIe bus --
so a real gap went unnoticed until now: attach anything to
raspi4b's PCIe port and "lspci"/"/sys/bus/pci/devices/" come back
completely empty, even though the link itself is up.

Root causes, all on the same host-bridge config-space path in
hw/arm/bcm2838_pcie.c:

 1. bcm2838_pcie_host_read/write computed
    "offset - PCIE_CONFIG_SPACE_SIZE" on an unsigned hwaddr, which
    underflows for every offset below 4KB -- i.e. the root port's own
    config space, including vendor/device ID at offset 0. Every such
    access fell into the "out of range" branch and read back as
    all-ones ("no device present"), so the guest's PCI core gave up
    immediately.

 2. pcie_host_mmcfg_init() was never called, even though both
    accessors dereference the MemoryRegion it sets up to service
    EXT_CFG_DATA. Fixing (1) let the guest reach that path for the
    first time, which then dereferenced an uninitialised region.

 3. The root port overrode PCIDeviceClass::config_read/config_write
    with plain pci_default_*_config(), bypassing
    pci_bridge_write_config(). Bridge memory windows were therefore
    never actually programmed, so BARs behind the root port stayed
    unreachable however the guest wrote them.

 4. PCIE_MMIO_OFFSET (0xc0000000) was defined in
    hw/arm/bcm2838_peripherals.c for exactly this purpose and never
    used -- grep count of 1, the definition itself. The PCI MMIO
    window was mapped straight onto the ARM address with no
    translation, even though the DTB declares CPU 0x600000000 as
    mapping to PCI 0xc0000000. The guest ended up reading PCI address
    0 where a device's BAR should have been.

Patch 1 fixes all four. I want to flag why they're one patch rather
than four: I tried splitting them during development, and every
subset short of all four either changes nothing (dormant bug) or
produces a kernel panic that doesn't happen on unfixed master (wrong
BAR content, alignment fault in a driver's MMIO access) -- worse
than the current silent "no devices enumerated". None of the four
are a coherent, independently-safe change on their own.

With all four, a PCIe device is enumerated and its driver binds:

  xhci_hcd 0000:01:00.0: xHCI Host Controller
  xhci_hcd 0000:01:00.0: new USB bus registered, assigned bus number 1
  xhci_hcd 0000:01:00.0: Host supports USB 3.0 SuperSpeed

This is not a complete PCIe implementation. The BCM2711's own MSI
controller is not implemented -- the guest driver programs it
correctly (visible as BRCM STB PCIe MSI vectors in
/proc/interrupts, permanently at 0 count) but QEMU never delivers
anything, so a device that only signals via MSI/MSI-X never actually
gets its interrupt. INTx works. Patch 1 documents this in
docs/system/arm/raspi.rst; patch 2's functional test attaches its
xHCI controller with "msi=off,msix=off" for the same reason. I
plan to look at the MSI controller separately; wanted this
enumeration fix to stand on its own rather than block on it, since it
already turns "no PCI device is usable at all" into "PCI devices
using INTx work".

Tested against real hardware, not just the emulated xHCI: with a USB
serial device passed through to the guest via QEMU's usb-serial
device pointed at the real /dev/cu.usbmodem* port, a real serial
peripheral connected to my Mac was reachable from inside the guest,
byte-for-byte identical to reading it directly on the host.

checkpatch.pl is clean (0 errors, 0 warnings) across both patches.
All 4 tests in tests/functional/aarch64/test_raspi4.py pass,
including the 3 pre-existing ones (no regression) and the new one
added here.

[1] https://patchwork.kernel.org/project/qemu-devel/cover/20260725004219.66222-1-marcelomanzo@gmail.com/

Marcelo Manzo (2):
  hw/arm/bcm2838_pcie: make PCI device enumeration actually work
  tests/functional/aarch64: add raspi4b PCIe enumeration test

 docs/system/arm/raspi.rst               |  2 ++
 hw/arm/bcm2838_pcie.c                   | 45 +++++++++++++++----------
 hw/arm/bcm2838_peripherals.c            | 16 +++++++--
 include/hw/arm/bcm2838_peripherals.h    |  1 +
 tests/functional/aarch64/test_raspi4.py | 37 ++++++++++++++++++++
 5 files changed, 82 insertions(+), 19 deletions(-)

-- 
2.47.1



^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH 1/2] hw/arm/bcm2838_pcie: make PCI device enumeration actually work
  2026-07-25 12:42 [PATCH 0/2] hw/arm/bcm2838_pcie: make PCI device enumeration work Marcelo Manzo
@ 2026-07-25 12:42 ` Marcelo Manzo
  2026-07-25 12:42 ` [PATCH 2/2] tests/functional/aarch64: add raspi4b PCIe enumeration test Marcelo Manzo
  1 sibling, 0 replies; 3+ messages in thread
From: Marcelo Manzo @ 2026-07-25 12:42 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé,
	Marcelo Manzo, Pierrick Bouvier

raspi4b's PCIe root complex brought the link up but no device was ever
enumerated: "lspci" was empty and /sys/bus/pci/devices/ had no entries.
Nothing exercised this before, since GENET is a sysbus device rather
than a PCI one.

Four separate defects, all on the same path:

1. bcm2838_pcie_host_read/write computed
   "offset - PCIE_CONFIG_SPACE_SIZE" on an unsigned hwaddr, which
   underflows for every offset below 4KB. The root port's own config
   space -- including vendor/device ID at offset 0 -- therefore always
   fell into the out-of-range branch and read back as all-ones, i.e.
   "no device present", so the guest stopped scanning immediately.

2. pcie_host_mmcfg_init() was never called, yet both accessors
   dereference pcie_hb->mmio.ops to service EXT_CFG_DATA. Once (1) was
   fixed and the guest actually reached that path, it touched an
   uninitialised MemoryRegion.

3. The root port overrode PCIDeviceClass::config_read/config_write with
   plain pci_default_*_config(). That bypasses rp_write_config() ->
   pci_bridge_write_config(), so programming the bridge's memory window
   never updated the bridge's address space and BARs behind the root
   port stayed unreachable. Drop the overrides and dispatch through
   pci_host_config_{read,write}_common() so the device's real handlers
   run.

4. The PCI MMIO window was mapped at the ARM base address with no
   address translation, even though PCIE_MMIO_OFFSET was defined for
   exactly that purpose (and otherwise unused). The DTB declares CPU
   0x600000000 as mapping to PCI 0xc0000000, so the guest was reading
   PCI address 0 where it expected a device BAR. Map an alias at the
   correct PCI offset instead.

With these, a PCIe device is enumerated and its driver binds:

  xhci_hcd 0000:01:00.0: xHCI Host Controller
  xhci_hcd 0000:01:00.0: new USB bus registered, assigned bus number 1
  xhci_hcd 0000:01:00.0: Host supports USB 3.0 SuperSpeed

Note this is not yet complete: devices attached behind the xHCI are
still not enumerated by the guest, which looks like a separate
interrupt-delivery problem rather than an enumeration one.

Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
 docs/system/arm/raspi.rst            |  2 ++
 hw/arm/bcm2838_pcie.c                | 45 +++++++++++++++++-----------
 hw/arm/bcm2838_peripherals.c         | 16 ++++++++--
 include/hw/arm/bcm2838_peripherals.h |  1 +
 4 files changed, 45 insertions(+), 19 deletions(-)

diff --git a/docs/system/arm/raspi.rst b/docs/system/arm/raspi.rst
index a4f83d0a..a8fc37e9 100644
--- a/docs/system/arm/raspi.rst
+++ b/docs/system/arm/raspi.rst
@@ -43,3 +43,5 @@ Missing devices
 ---------------
 
  * Pulse Width Modulation (PWM)
+ * PCIe MSI/MSI-X interrupt delivery (raspi4b) -- INTx works; a PCIe
+   device that only signals via MSI/MSI-X needs ``msi=off,msix=off``
diff --git a/hw/arm/bcm2838_pcie.c b/hw/arm/bcm2838_pcie.c
index 1596145b..88166d1f 100644
--- a/hw/arm/bcm2838_pcie.c
+++ b/hw/arm/bcm2838_pcie.c
@@ -11,24 +11,13 @@
 #include "qapi/error.h"
 #include "hw/core/irq.h"
 #include "hw/pci-host/gpex.h"
+#include "hw/pci/pci_host.h"
 #include "hw/core/qdev-properties.h"
 #include "migration/vmstate.h"
 #include "qemu/module.h"
 #include "hw/arm/bcm2838_pcie.h"
 #include "trace.h"
 
-static uint32_t bcm2838_pcie_config_read(PCIDevice *d,
-                                         uint32_t address, int len)
-{
-    return pci_default_read_config(d, address, len);
-}
-
-static void bcm2838_pcie_config_write(PCIDevice *d, uint32_t addr, uint32_t val,
-                                      int len)
-{
-    return pci_default_write_config(d, addr, val, len);
-}
-
 static uint64_t bcm2838_pcie_host_read(void *opaque, hwaddr offset,
                                        unsigned size) {
     hwaddr mmcfg_addr;
@@ -39,7 +28,18 @@ static uint64_t bcm2838_pcie_host_read(void *opaque, hwaddr offset,
     uint32_t *cfg_idx = (uint32_t *)(root_regs + BCM2838_PCIE_EXT_CFG_INDEX
                                      - PCIE_CONFIG_SPACE_SIZE);
 
-    if (offset - PCIE_CONFIG_SPACE_SIZE + size <= sizeof(s->root_port.regs)) {
+    if (offset < PCIE_CONFIG_SPACE_SIZE) {
+        /*
+         * The first 4KB of the window is the root port's own PCI config
+         * space (vendor/device ID, BARs, capabilities). Serve it from the
+         * real PCIDevice, not from the raw regs[] shadow buffer, which is
+         * only backing store for the controller registers above 4KB.
+         */
+        value = pci_host_config_read_common(PCI_DEVICE(&s->root_port),
+                                            offset, PCIE_CONFIG_SPACE_SIZE,
+                                            size);
+    } else if (offset - PCIE_CONFIG_SPACE_SIZE + size
+               <= sizeof(s->root_port.regs)) {
         switch (offset) {
         case BCM2838_PCIE_EXT_CFG_DATA
             ... BCM2838_PCIE_EXT_CFG_DATA + PCIE_CONFIG_SPACE_SIZE - 1:
@@ -72,7 +72,12 @@ static void bcm2838_pcie_host_write(void *opaque, hwaddr offset,
 
     trace_bcm2838_pcie_host_write(size, offset, value);
 
-    if (offset - PCIE_CONFIG_SPACE_SIZE + size <= sizeof(s->root_port.regs)) {
+    if (offset < PCIE_CONFIG_SPACE_SIZE) {
+        /* Root port's own PCI config space -- see read path above */
+        pci_host_config_write_common(PCI_DEVICE(&s->root_port), offset,
+                                     PCIE_CONFIG_SPACE_SIZE, value, size);
+    } else if (offset - PCIE_CONFIG_SPACE_SIZE + size
+               <= sizeof(s->root_port.regs)) {
         switch (offset) {
         case BCM2838_PCIE_EXT_CFG_DATA
             ... BCM2838_PCIE_EXT_CFG_DATA + PCIE_CONFIG_SPACE_SIZE - 1:
@@ -137,9 +142,18 @@ static void bcm2838_pcie_host_realize(DeviceState *dev, Error **errp)
     PCIHostState *pci = PCI_HOST_BRIDGE(dev);
     BCM2838PcieHostState *s = BCM2838_PCIE_HOST(dev);
     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    PCIExpressHost *pex = PCIE_HOST_BRIDGE(dev);
 
     int i;
 
+    /*
+     * Initialise the ECAM config-space window. The BCM2838 does not expose
+     * it to the guest directly; config accesses are made indirectly through
+     * the EXT_CFG_INDEX/EXT_CFG_DATA register pair, which dispatch into
+     * pex->mmio. Without this the ops pointer is never set up.
+     */
+    pcie_host_mmcfg_init(pex, PCIE_MMCFG_SIZE_MAX);
+
     memory_region_init_io(&s->cfg_regs, OBJECT(s), &bcm2838_pcie_host_ops, s,
                           "bcm2838_pcie_cfg_regs", BCM2838_PCIE_REGS_SIZE);
     sysbus_init_mmio(sbd, &s->cfg_regs);
@@ -273,9 +287,6 @@ static void bcm2838_pcie_root_class_init(ObjectClass *class, const void *data)
     k->device_id = BCM2838_PCIE_DEVICE_ID;
     k->revision = BCM2838_PCIE_REVISION;
 
-    k->config_read = bcm2838_pcie_config_read;
-    k->config_write = bcm2838_pcie_config_write;
-
     rpc->exp_offset = BCM2838_PCIE_EXP_CAP_OFFSET;
     rpc->aer_offset = BCM2838_PCIE_AER_CAP_OFFSET;
 }
diff --git a/hw/arm/bcm2838_peripherals.c b/hw/arm/bcm2838_peripherals.c
index de49f48b..012a4567 100644
--- a/hw/arm/bcm2838_peripherals.c
+++ b/hw/arm/bcm2838_peripherals.c
@@ -204,10 +204,22 @@ static void bcm2838_peripherals_realize(DeviceState *dev, Error **errp)
     /* RC registers region */
     regs_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->pcie_host), 0);
     memory_region_add_subregion(&s->peri_low_mr, PCIE_RC_OFFSET, regs_mr);
-    /* MMIO region */
+    /*
+     * MMIO region.
+     *
+     * The BCM2711 PCIe controller translates addresses between the ARM and
+     * PCI address spaces: the DTB declares CPU 0x600000000 as mapping to PCI
+     * 0xc0000000. Map an alias of the PCI window starting at that PCI offset
+     * so accesses land on the right addresses; mapping the window directly
+     * would expose PCI address 0 at the ARM base instead, and every BAR
+     * behind the root port would be read at the wrong address.
+     */
     mmio_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->pcie_host), 1);
+    memory_region_init_alias(&s->pcie_mmio_alias, OBJECT(s),
+                             "bcm2838_pcie_mmio_alias", mmio_mr,
+                             PCIE_MMIO_OFFSET, PCIE_MMIO_SIZE);
     memory_region_add_subregion(get_system_memory(), PCIE_MMIO_ARM_OFFSET,
-                                mmio_mr);
+                                &s->pcie_mmio_alias);
 
     /* Gigabit Ethernet */
     if (!sysbus_realize(SYS_BUS_DEVICE(&s->genet), errp)) {
diff --git a/include/hw/arm/bcm2838_peripherals.h b/include/hw/arm/bcm2838_peripherals.h
index 5da340f2..c05709f4 100644
--- a/include/hw/arm/bcm2838_peripherals.h
+++ b/include/hw/arm/bcm2838_peripherals.h
@@ -70,6 +70,7 @@ struct BCM2838PeripheralState {
 
     SDHCIState emmc2;
     BCM2838PcieHostState pcie_host;
+    MemoryRegion pcie_mmio_alias;
     BCM2838GenetState genet;
     BCM2838GpioState gpio;
 
-- 
2.47.1



^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [PATCH 2/2] tests/functional/aarch64: add raspi4b PCIe enumeration test
  2026-07-25 12:42 [PATCH 0/2] hw/arm/bcm2838_pcie: make PCI device enumeration work Marcelo Manzo
  2026-07-25 12:42 ` [PATCH 1/2] hw/arm/bcm2838_pcie: make PCI device enumeration actually work Marcelo Manzo
@ 2026-07-25 12:42 ` Marcelo Manzo
  1 sibling, 0 replies; 3+ messages in thread
From: Marcelo Manzo @ 2026-07-25 12:42 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé,
	Marcelo Manzo

Add test_arm_raspi4_pcie, exercising the fix in the previous patch:
attach a qemu-xhci controller to the PCIe root port, confirm the
guest's driver binds it, and confirm both the root port (00:00.0)
and the xHCI controller (01:00.0) show up in
/sys/bus/pci/devices/. Before the previous patch this would find no
devices at all.

Attaches with msi=off,msix=off: the BCM2838 PCIe root complex's own
MSI controller is not implemented, so a device relying on MSI/MSI-X
for interrupts never signals its driver. INTx works and is what this
test (and any real device behind this root port, for now) relies on.

Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
 tests/functional/aarch64/test_raspi4.py | 37 +++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/tests/functional/aarch64/test_raspi4.py b/tests/functional/aarch64/test_raspi4.py
index 0e254a20..32542b84 100755
--- a/tests/functional/aarch64/test_raspi4.py
+++ b/tests/functional/aarch64/test_raspi4.py
@@ -122,5 +122,42 @@ def test_arm_raspi4_genet(self):
         exec_command_and_wait_for_pattern(self, 'halt', 'reboot: System halted')
 
 
+    def test_arm_raspi4_pcie(self):
+        kernel_path = self.archive_extract(self.ASSET_KERNEL_20190215,
+                                           member='boot/kernel8.img')
+        dtb_path = self.archive_extract(self.ASSET_KERNEL_20190215,
+                                        member='boot/bcm2711-rpi-4-b.dtb')
+        initrd_path = self.uncompress(self.ASSET_INITRD)
+
+        self.set_machine('raspi4b')
+        self.vm.set_console()
+        kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
+                               'earlycon=pl011,mmio32,0xfe201000 ' +
+                               'console=ttyAMA0,115200 ' +
+                               'panic=-1 noreboot ' +
+                               'dwc_otg.fiq_fsm_enable=0')
+        self.vm.add_args('-kernel', kernel_path,
+                         '-dtb', dtb_path,
+                         '-initrd', initrd_path,
+                         '-append', kernel_command_line,
+                         '-no-reboot',
+                         # msi=off,msix=off: the BCM2838 PCIe root complex's
+                         # MSI controller is not implemented, so a device
+                         # relying on MSI/MSI-X for interrupts never signals
+                         # and its driver never probes. INTx works.
+                         '-device',
+                         'qemu-xhci,bus=pcie.1,id=xhci,msi=off,msix=off')
+        self.vm.launch()
+        self.wait_for_console_pattern(
+            'xhci_hcd 0000:01:00.0: xHCI Host Controller')
+        self.wait_for_console_pattern('Boot successful.')
+
+        exec_command_and_wait_for_pattern(self, 'ls /sys/bus/pci/devices/',
+                                                '0000:00:00.0')
+        exec_command_and_wait_for_pattern(self, 'ls /sys/bus/pci/devices/',
+                                                '0000:01:00.0')
+        exec_command_and_wait_for_pattern(self, 'halt', 'reboot: System halted')
+
+
 if __name__ == '__main__':
     LinuxKernelTest.main()
-- 
2.47.1



^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-07-25 12:43 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-25 12:42 [PATCH 0/2] hw/arm/bcm2838_pcie: make PCI device enumeration work Marcelo Manzo
2026-07-25 12:42 ` [PATCH 1/2] hw/arm/bcm2838_pcie: make PCI device enumeration actually work Marcelo Manzo
2026-07-25 12:42 ` [PATCH 2/2] tests/functional/aarch64: add raspi4b PCIe enumeration test Marcelo Manzo

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.