summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDustin Kirkland <dustin.kirkland@chainguard.dev>2026-08-06 11:18:18 -0700
committerNeil Armstrong <neil.armstrong@linaro.org>2026-09-11 12:05:26 +0200
commit85a7290d4bc976a57c7ecbcfcc3d6aa67665ffaf (patch)
tree4c149197ee66f83a107d8618d6b596f10a8ff6e2
parent0cc7799c0cd87b103e0efdf1b816fc14f046b250 (diff)
dm: pci: fix uninitialized fdt_pci_addr fall-through in pci_get_devfn
pci_get_devfn() reads addr.phys_hi and returns the low bits of it as the requested devfn even on the -ENOENT branch, where ofnode_read_pci_addr() has NOT written *addr. The C standard leaves that read undefined; in practice the returned value depends on the compilers stack layout and on -ftrivial-auto-var-init. That value ends up in pplat->devfn (set by pci_uclass_child_post_bind()) and is later compared for equality in pci_bus_find_devfn() during PCI enumeration, so an under-determined value produces an under-determined driver-binding outcome. Concretely, this fires on Raspberry Pi 5 (BCM2712) when built with GCC -ftrivial-auto-var-init=zero. In that build addr.phys_hi is zeroed rather than left as stack junk, so pci_get_devfn() returns 0 for every DT-declared non-PCI child of a PCI bus. The Pi 5 device tree includes one such child under the second root complex -- the rp1 simple-bus node representing the on-SoC RP1 south bridge as seen from the OS side. Under zero-init: * pci_uclass_child_post_bind(rp1) sets pplat->devfn = 0 * pci_bind_bus_devices() of the second root complex reads vendor at bdf 02:00.0, calls pci_bus_find_devfn(bus, 0x0000, &dev) * pci_bus_find_devfn() finds rp1 with pplat->devfn == 0x0000 == the requested devfn, returns it as the pre-bound match * pci_find_and_bind_driver() is skipped; the RP1 root port is never bound as pci_bridge_drv and its downstream bus is never enumerated * dm_pciauto_postscan_setup_bridge() then writes PCI_SUBORDINATE_BUS = 0 and PCI_MEMORY_LIMIT = 0 on the root port; Linux flags the bridge as "bridge configuration invalid ([bus 01-00])", the rp1 driver fails to enable the endpoint with -EINVAL, and every RP1-hosted peripheral (USB, onboard Ethernet, ttyAMA10) is non-functional for the rest of boot. Under the default -ftrivial-auto-var-init=uninitialized, addr.phys_hi happens to be non-zero stack residue that does not collide with the requested devfn, so the same code path just returns -ENODEV from pci_bus_find_devfn() and pci_find_and_bind_driver() correctly runs the fallback. The bug has been latent since introduction; -zero exposes it deterministically. A "reg" property that is simply absent now consistently returns -ENODEV, matching the documented contract in include/dm/pci.h, instead of a value that depends on stack state. A malformed "reg" property still returns -EINVAL, as it did before this fix. Fixes: b52142004fbd ("pci: Add pci_get_devfn() to extract devfn from the fdt_pci_addr") Signed-off-by: Dustin Kirkland <dustin.kirkland@chainguard.dev> Cc: Scott Moser <smoser@brickies.net> Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org> Reviewed-by: Simon Glass <sjg@chromium.org> Link: https://patch.msgid.link/20260806181818.197191-1-smoser@brickies.net Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
-rw-r--r--drivers/core/util.c8
1 files changed, 4 insertions, 4 deletions
diff --git a/drivers/core/util.c b/drivers/core/util.c
index fa893485a09..f6c7bf7eda6 100644
--- a/drivers/core/util.c
+++ b/drivers/core/util.c
@@ -20,10 +20,10 @@ int pci_get_devfn(struct udevice *dev)
/* Extract the devfn from fdt_pci_addr */
ret = ofnode_read_pci_addr(dev_ofnode(dev), FDT_PCI_SPACE_CONFIG,
"reg", &addr, NULL);
- if (ret) {
- if (ret != -ENOENT)
- return -EINVAL;
- }
+ if (ret == -ENOENT)
+ return -ENODEV;
+ if (ret)
+ return -EINVAL;
return addr.phys_hi & 0xff00;
}