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


Reply via email to