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

Reply via email to