From: Dustin Kirkland <[email protected]>
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) with vendor U-Boot
v2026.07 when the tree is compiled 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.
The only observable behaviour change is that DT-declared non-PCI
children of PCI buses now consistently report -EINVAL from
pci_get_devfn() rather than a value that depends on stack state.
Reported-by: Dustin Kirkland <[email protected]>
Fixes: b52142004fbd ("pci: Add pci_get_devfn() to extract devfn from the
fdt_pci_addr")
Signed-off-by: Dustin Kirkland <[email protected]>
Investigated-by: Claude Opus 4.7 <[email protected]>
Co-authored-by: Claude Opus 4.7 <[email protected]>
---
drivers/core/util.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/drivers/core/util.c b/drivers/core/util.c
index fa893485..57006554 100644
--- a/drivers/core/util.c
+++ b/drivers/core/util.c
@@ -20,10 +20,8 @@ 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)
+ return -EINVAL;
return addr.phys_hi & 0xff00;
}
--
2.43.0