[PATCH 4/4] pci: synthesize devicetree nodes for enumerated devices

Sascha Hauer s.hauer at pengutronix.de
Thu May 21 01:36:58 PDT 2026


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 at D,F / dev at D,F hierarchy in the source DTS.

Assisted-by: Claude Opus 4.7
Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
---
 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 at D,F / dev at 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", &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




More information about the barebox mailing list