diff options
| author | Dustin Kirkland <dustin.kirkland@chainguard.dev> | 2026-08-06 11:18:18 -0700 |
|---|---|---|
| committer | Neil Armstrong <neil.armstrong@linaro.org> | 2026-09-11 12:05:26 +0200 |
| commit | 85a7290d4bc976a57c7ecbcfcc3d6aa67665ffaf (patch) | |
| tree | 4c149197ee66f83a107d8618d6b596f10a8ff6e2 | |
| parent | 0cc7799c0cd87b103e0efdf1b816fc14f046b250 (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.c | 8 |
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; } |
