Add an opt-in PCI_DYNAMIC_OF_NODES that creates a devicetree node for every PCI device discovered during bus scan that doesn't already have one in the static tree. Nodes are attached to the live tree under the host bridge / parent bridge, so they're visible both to barebox's own pci_of_match_device() and to the kernel devicetree handed off at boot.
Inspired by Linux's CONFIG_PCI_DYNAMIC_OF_NODES, but trimmed to the bare minimum Linux actually consumes: a node with a "reg" property encoding bus/devfn so of_pci_find_child_device() can attach the node to the device it rediscovers from hardware. Everything else Linux would otherwise look at (compatible, bus-range, ranges, interrupts) is recomputed from PCI config space at probe time, so duplicating it here would just be state with no consumer and more surface for bugs. Linux's changeset wrapper is dropped: barebox's live tree is freely mutable via of_new_node() / of_set_property(). The primary motivation is letting board code stamp MAC addresses onto PCI Ethernet endpoints via of_eth_register_ethaddr() without having to hand-write the pci@D,F / dev@D,F hierarchy in the source DTS. Assisted-by: Claude Opus 4.7 Signed-off-by: Sascha Hauer <[email protected]> --- drivers/pci/Kconfig | 23 +++++++ drivers/pci/Makefile | 1 + drivers/pci/of-dynamic.c | 173 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/pci/pci.c | 8 +++ include/of_pci.h | 6 ++ 5 files changed, 211 insertions(+) diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig index f8e60c4ea5..a2ebf348d9 100644 --- a/drivers/pci/Kconfig +++ b/drivers/pci/Kconfig @@ -25,6 +25,29 @@ config PCI_DEBUG When in doubt, say N. +config PCI_DYNAMIC_OF_NODES + bool "Create devicetree nodes for runtime-enumerated PCI devices" + depends on PCI && OFDEVICE + help + Synthesize a devicetree node for every PCI device discovered during + bus scan that does not already have one in the static devicetree. + Nodes are attached to the live tree under the host-bridge / parent + bridge node, so they are visible to barebox itself and flow into the + devicetree passed to the kernel at boot. + + This lets consumers (e.g. of_eth_register_ethaddr() for stamping a + MAC address) reference PCI endpoints by node path without + hand-writing the pci@D,F / dev@D,F hierarchy in the source DTS. + + Only the bare minimum is generated: a node with a "reg" property + encoding the device/function so Linux's of_pci_find_child_device() + can attach the node to the device it rediscovers from hardware. + Linux recomputes everything else (compatible from VID/DID, bus-range + and ranges from config space, etc.) so adding it here would just be + duplicated state with no consumer. + + When in doubt, say N. + config PCIE_DW bool diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 69649fbcd2..803ff09f89 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -4,6 +4,7 @@ # obj-y += pci.o bus.o pci_iomap.o host-bridge.o obj-$(CONFIG_OFDEVICE) += of.o +obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of-dynamic.o ccflags-$(CONFIG_PCI_DEBUG) := -DDEBUG diff --git a/drivers/pci/of-dynamic.c b/drivers/pci/of-dynamic.c new file mode 100644 index 0000000000..2f6d7fb84e --- /dev/null +++ b/drivers/pci/of-dynamic.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Synthesize the minimal devicetree nodes Linux needs to attach properties + * (most notably mac-address) to runtime-enumerated PCI devices. + * + * Loosely modelled on Linux's CONFIG_PCI_DYNAMIC_OF_NODES, but trimmed to + * just node creation + a "reg" property. Linux discovers PCI topology from + * hardware, so other properties it would otherwise read (compatible, + * bus-range, ranges, #address-cells/#size-cells, interrupts, ...) are + * either redundant or recomputed from config space at probe time. Keeping + * the generator small keeps the bug surface small. + * + * The only thing barebox must put in DT for Linux to find the node is: + * - a child of the parent bus' DT node, with + * - reg cell[0] = (bus << 16) | (devfn << 8) so of_pci_get_devfn() works. + * + * Linux uses of_changeset because its live kernel devicetree is treated + * as immutable; barebox is free to mutate the live tree directly, so the + * changeset layer is dropped. + */ + +#define pr_fmt(fmt) "PCI: OF: " fmt + +#include <common.h> +#include <init.h> +#include <of.h> +#include <of_pci.h> +#include <malloc.h> +#include <linux/pci.h> + +static int of_pci_fill_node(struct device_node *np, struct pci_dev *pdev) +{ + u32 reg[5] = { 0 }; + int ret; + + /* Config-space tag (space = 0); only bus/devfn matter for matching. */ + reg[0] = (pdev->bus->number << 16) | (pdev->devfn << 8); + ret = of_property_write_u32_array(np, "reg", reg, ARRAY_SIZE(reg)); + if (ret) + return ret; + + if (pci_is_bridge(pdev)) { + ret = of_property_write_u32(np, "#address-cells", 3); + if (ret) + return ret; + ret = of_property_write_u32(np, "#size-cells", 2); + if (ret) + return ret; + } + + return 0; +} + +void of_pci_make_dev_node(struct pci_dev *pdev) +{ + struct device_node *parent_np, *np; + struct device *parent_dev; + char *name; + + if (pdev->dev.of_node) + return; + + parent_dev = pdev->bus->self ? &pdev->bus->self->dev + : pdev->bus->host->parent; + if (!parent_dev || !parent_dev->of_node) + return; + parent_np = parent_dev->of_node; + + name = xasprintf("%s@%x,%x", + pci_is_bridge(pdev) ? "pci" : "dev", + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)); + np = of_new_node(parent_np, name); + free(name); + if (!np) + return; + + if (of_pci_fill_node(np, pdev)) { + of_delete_node(np); + return; + } + + pdev->dev.of_node = np; + + dev_dbg(&pdev->dev, "created OF node %pOF\n", np); +} + +static struct device_node * +of_pci_fixup_dev_node(struct device_node *parent_np, struct pci_dev *pdev) +{ + struct device_node *np; + char *name; + u32 reg; + + /* + * Match by devfn before creating a new node: any existing child + * whose reg[0] devfn byte matches refers to the same hardware, + * regardless of node name. + */ + for_each_child_of_node(parent_np, np) { + if (of_property_read_u32_array(np, "reg", ®, 1)) + continue; + if (((reg >> 8) & 0xff) == pdev->devfn) + return np; + } + + name = xasprintf("%s@%x,%x", + pci_is_bridge(pdev) ? "pci" : "dev", + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)); + np = of_new_node(parent_np, name); + free(name); + if (!np) + return NULL; + + if (of_pci_fill_node(np, pdev)) { + of_delete_node(np); + return NULL; + } + + return np; +} + +static void of_pci_fixup_bus(struct device_node *parent_np, struct pci_bus *bus) +{ + struct pci_dev *pdev; + struct device_node *np; + + list_for_each_entry(pdev, &bus->devices, bus_list) { + np = of_pci_fixup_dev_node(parent_np, pdev); + if (np && pdev->subordinate) + of_pci_fixup_bus(np, pdev->subordinate); + } +} + +static int of_pci_fixup(struct device_node *root, void *ctx) +{ + struct pci_bus *bus; + + list_for_each_entry(bus, &pci_root_buses, node) { + struct device_node *bb_np, *host_np; + char *name; + + if (!bus->host || !bus->host->parent) + continue; + bb_np = bus->host->parent->of_node; + if (!bb_np) + continue; + + name = of_get_reproducible_name(bb_np); + host_np = of_find_node_by_reproducible_name(root, name); + free(name); + if (!host_np) { + pr_debug("no kernel-DT mirror of host bridge %pOF\n", bb_np); + continue; + } + + of_pci_fixup_bus(host_np, bus); + } + + return 0; +} + +static int of_pci_register_fixup(void) +{ + return of_register_fixup(of_pci_fixup, NULL); +} + +/* + * The PCI device OF fixup must run before other fixups want to modify the nodes + * we are creating here. As OF fixups are running in the order they are registered, + * register this one early. This might be the first sign that we need a dependency + * tracking or -EPROBE_DEFERRED mechanism for OF fixups. Keep an eye on it. + */ +core_initcall(of_pci_register_fixup); diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index fc00ec2249..ca19a03c20 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -5,6 +5,7 @@ #include <linux/sizes.h> #include <linux/pci.h> #include <linux/bitfield.h> +#include <of_pci.h> static unsigned int pci_scan_bus(struct pci_bus *bus); @@ -672,6 +673,7 @@ static unsigned int pci_scan_bus(struct pci_bus *bus) pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device); pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor); + of_pci_make_dev_node(dev); break; case PCI_HEADER_TYPE_BRIDGE: child_bus = pci_alloc_bus(); @@ -690,6 +692,12 @@ static unsigned int pci_scan_bus(struct pci_bus *bus) /* scan pci hierarchy behind bridge */ prescan_setup_bridge(dev); + /* + * Materialize the bridge's OF node before recursing so + * children below it can find their parent during their + * own of_pci_make_dev_node() call. + */ + of_pci_make_dev_node(dev); pci_scan_bus(child_bus); postscan_setup_bridge(dev); diff --git a/include/of_pci.h b/include/of_pci.h index c787150936..c44cc625f5 100644 --- a/include/of_pci.h +++ b/include/of_pci.h @@ -15,4 +15,10 @@ static inline int of_pci_get_devfn(struct device_node *np) #endif +#ifdef CONFIG_PCI_DYNAMIC_OF_NODES +void of_pci_make_dev_node(struct pci_dev *pdev); +#else +static inline void of_pci_make_dev_node(struct pci_dev *pdev) { } +#endif + #endif -- 2.47.3
