[PATCH v2] dm: pci: fix uninitialized fdt_pci_addr fall-through in pci_get_devfn

Scott Moser <[email protected]>
Newsgroups gmane.comp.boot-loaders.u-boot
Message-ID <20260806181818.197191-1-smoser__39261.1177683406$1786067444$gmane$org@brickies.net>
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) 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 <[email protected]>
Cc: Scott Moser <[email protected]>
---
 drivers/core/util.c | 8 ++++----
 1 file changed, 4 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,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;
 }
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.