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

Sascha Hauer <[email protected]>
Newsgroups org.infradead.lists.barebox
Message-ID <[email protected]>
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", &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
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.