* [PATCH 0/2] PCI: of: update endpoint ranges dynamically
@ 2026-08-13 22:07 Alex Elder
2026-08-13 22:07 ` [PATCH 1/2] PCI: of: introduce of_pci_build_prop_ranges() Alex Elder
2026-08-13 22:07 ` [PATCH 2/2] PCI: of: introduce of_pci_update_endpoint_node_ranges() Alex Elder
0 siblings, 2 replies; 3+ messages in thread
From: Alex Elder @ 2026-08-13 22:07 UTC (permalink / raw)
To: bhelgaas
Cc: lizhi.hou, herve.codina, andrea.porta, daniel, mohdayaa, lbiancon,
mani, robh, linux-pci, linux-kernel
A PCI endpoint bus is a devicetree construct that allows a PCI
endpoint (function) to have sub-devices defined that are accessible
in an SoC via the PCI endpoint's BARs. Such a bus is represented as
a devicetree sub-node for a PCI function having the name "pci-ep-bus".
There can be one or more pci-ep-bus nodes.
A PCI function with a pci-ep-bus devicetree node must also define
"#address-cells", "#size-cells", and "ranges" properties, to specify
how endpoint bus addresses are translated to the PCI parent bus.
An endpoint bus address has three cells; the first indicates which of
the function's BARs the address is associated with, and the other two
specify a 64-bit (2 cell) offset within the BAR's region.
BAR base addresses are determined dynamically by the PCI enumeration
process, so generally it's not possible to include them in a static
devicetree file. When this addressing scheme was introduced, this
was not a problem because the devicetree content was generated
dynamically--after booting--based on the information (including BAR
addresses) available following PCI enumeration.
It is possible (and in some cases, necessary) to define the devicetree
nodes that represent PCI devices ahead of time, in a statically-defined
devicetree file. In order to support the PCI endpoint bus model in this
case it is necessary to dynamically update the static devicetree so that
the BAR base addresses assigned during enumeration are reflected in the
endpoint's "ranges" property.
This series implements that dynamic update, leveraging the same code
used to create the "ranges" property when PCI_DYNAMIC_OF_NODES is
enabled. The first patch separates the code that dynamically builds
the property value into a helper function, and the second arranges for
even statically-defined devicetree nodes to have the "ranges" property
updated.
-Alex
Note: this series is built upon these patches:
https://lore.kernel.org/lkml/20260812172247.276554-5-elder@riscstar.com/
Alex Elder (2):
PCI: of: introduce of_pci_build_prop_ranges()
PCI: of: introduce of_pci_update_endpoint_node_ranges()
drivers/pci/of.c | 59 ++++++++++++++---
drivers/pci/of_property.c | 129 +++++++++++++++++++++++++-------------
drivers/pci/pci.h | 1 +
3 files changed, 139 insertions(+), 50 deletions(-)
base-commit: cb0a459e8259a695f4c2d179534b931399e6847f
--
2.53.0
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 1/2] PCI: of: introduce of_pci_build_prop_ranges()
2026-08-13 22:07 [PATCH 0/2] PCI: of: update endpoint ranges dynamically Alex Elder
@ 2026-08-13 22:07 ` Alex Elder
2026-08-13 22:07 ` [PATCH 2/2] PCI: of: introduce of_pci_update_endpoint_node_ranges() Alex Elder
1 sibling, 0 replies; 3+ messages in thread
From: Alex Elder @ 2026-08-13 22:07 UTC (permalink / raw)
To: bhelgaas
Cc: lizhi.hou, herve.codina, andrea.porta, daniel, mohdayaa, lbiancon,
mani, robh, linux-pci, linux-kernel
Move the bulk of what happens inside of_pci_prop_ranges() into
a helper function that builds up the value to be passed to
of_changeset_add_prop_u32_array().
Signed-off-by: Alex Elder <elder@riscstar.com>
---
drivers/pci/of_property.c | 129 +++++++++++++++++++++++++-------------
1 file changed, 86 insertions(+), 43 deletions(-)
diff --git a/drivers/pci/of_property.c b/drivers/pci/of_property.c
index 5e1820b8bb79b..48ce467e7e966 100644
--- a/drivers/pci/of_property.c
+++ b/drivers/pci/of_property.c
@@ -102,57 +102,100 @@ static int of_pci_prop_bus_range(struct pci_dev *pdev,
ARRAY_SIZE(bus_range));
}
+/*
+ * Build a "ranges" property value that defines the mapping between
+ * child and parent PCI address space for each of the given PCI
+ * device's resources.
+ *
+ * Returns a dynamically allocated array of u32 devicetree cells, or
+ * a null pointer if allocation fails. The cell array is built as a
+ * of_pci_prop_ranges structure consisting of 8 32-bit cells in host
+ * byte order. It is suitable for use as the value of a PCI device
+ * node "ranges" property passed to of_changeset_add_prop_u32_array().
+ * The total number of cells in the array is returned in *count.
+ *
+ * Caller is responsible for ensuring the returned pointer gets freed.
+ */
+static u32 *of_pci_build_prop_ranges(struct pci_dev *pdev, u32 *count)
+{
+ bool bridge_device = pci_is_bridge(pdev);
+ struct of_pci_range_entry *entries;
+ struct of_pci_range_entry *ep;
+ u32 resource_count = 0;
+ struct resource *res;
+ u32 first;
+ u32 num;
+ u32 i;
+
+ if (bridge_device) {
+ first = PCI_BRIDGE_RESOURCES;
+ num = PCI_BRIDGE_RESOURCE_NUM;
+ } else {
+ first = PCI_STD_RESOURCES;
+ num = PCI_STD_NUM_BARS;
+ }
+
+ /* First count how many resources will get a range property */
+ res = &pdev->resource[first];
+ for (i = 0; i < num; i++, res++)
+ if (resource_size(res) && !of_pci_get_addr_flags(res, NULL))
+ resource_count++;
+
+ entries = kzalloc_objs(*entries, resource_count);
+ if (!entries)
+ return NULL;
+
+ ep = entries;
+ res = &pdev->resource[first];
+ for (i = 0; i < num; i++, res++) {
+ u64 size = resource_size(res);
+ u32 flags;
+
+ if (!size || of_pci_get_addr_flags(res, &flags))
+ continue;
+
+ /* Record the size in the range entry */
+ ep->size[0] = upper_32_bits(size);
+ ep->size[1] = lower_32_bits(size);
+
+ /* Record the parent bus address from the resource */
+ of_pci_set_address(pdev, ep->parent_addr,
+ pci_bus_address(pdev, first + i),
+ flags, false);
+
+ /*
+ * For a bridge device, the child address matches the
+ * parent address (including its flags cell). For an
+ * endpoint device, the (flags) cell contains the BAR
+ * number, and the two address cells are zero.
+ */
+ if (bridge_device)
+ memcpy(ep->child_addr, ep->parent_addr,
+ sizeof(ep->child_addr));
+ else
+ ep->child_addr[0] = i;
+
+ ep++;
+ }
+ *count = resource_count * sizeof(*ep) / sizeof(u32);
+
+ return (u32 *)entries;
+}
+
static int of_pci_prop_ranges(struct pci_dev *pdev, struct of_changeset *ocs,
struct device_node *np)
{
struct of_pci_range_entry *rp;
- struct resource *res;
- int i, j, ret;
- u32 flags, num;
- u64 val64;
+ u32 *value;
+ u32 count;
+ int ret;
- if (pci_is_bridge(pdev)) {
- num = PCI_BRIDGE_RESOURCE_NUM;
- res = &pdev->resource[PCI_BRIDGE_RESOURCES];
- } else {
- num = PCI_STD_NUM_BARS;
- res = &pdev->resource[PCI_STD_RESOURCES];
- }
-
- rp = kzalloc_objs(*rp, num);
- if (!rp)
+ value = of_pci_build_prop_ranges(pdev, &count);
+ if (!value)
return -ENOMEM;
- for (i = 0, j = 0; j < num; j++) {
- if (!resource_size(&res[j]))
- continue;
+ ret = of_changeset_add_prop_u32_array(ocs, np, "ranges", value, count);
- if (of_pci_get_addr_flags(&res[j], &flags))
- continue;
-
- val64 = pci_bus_address(pdev, &res[j] - pdev->resource);
- of_pci_set_address(pdev, rp[i].parent_addr, val64, flags,
- false);
- if (pci_is_bridge(pdev)) {
- memcpy(rp[i].child_addr, rp[i].parent_addr,
- sizeof(rp[i].child_addr));
- } else {
- /*
- * For endpoint device, the lower 64-bits of child
- * address is always zero.
- */
- rp[i].child_addr[0] = j;
- }
-
- val64 = resource_size(&res[j]);
- rp[i].size[0] = upper_32_bits(val64);
- rp[i].size[1] = lower_32_bits(val64);
-
- i++;
- }
-
- ret = of_changeset_add_prop_u32_array(ocs, np, "ranges", (u32 *)rp,
- i * sizeof(*rp) / sizeof(u32));
kfree(rp);
return ret;
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH 2/2] PCI: of: introduce of_pci_update_endpoint_node_ranges()
2026-08-13 22:07 [PATCH 0/2] PCI: of: update endpoint ranges dynamically Alex Elder
2026-08-13 22:07 ` [PATCH 1/2] PCI: of: introduce of_pci_build_prop_ranges() Alex Elder
@ 2026-08-13 22:07 ` Alex Elder
1 sibling, 0 replies; 3+ messages in thread
From: Alex Elder @ 2026-08-13 22:07 UTC (permalink / raw)
To: bhelgaas
Cc: lizhi.hou, herve.codina, andrea.porta, daniel, mohdayaa, lbiancon,
mani, robh, linux-pci, linux-kernel
Commit 407d1a51921e9 ("PCI: Create device tree node for bridge")
introduced the PCI_DYNAMIC_OF_NODES Kconfig option, which creates
a devicetree node for a PCI bridge as part of pci_bus_add_device().
Its successor commit ae9813db1dc5a ("PCI: Add quirks to generate
device tree node for Xilinx Alveo U50") shows how to use a PCI final
fixup quirk to also create a devicetree node for a non-bridge PCI
device. In both cases, of_pci_make_dev_node() uses an OF changeset
to dynamically create a node populated with appropriate properties
and apply it to the live devicetree.
The dynamic devicetree node for a PCI device will include a "ranges"
property, and a new type of 3-cell address is introduced for use
within an endpoint. The endpoint's ranges property will contain a
range entry for each of the endpoint's BARs. The "child address"
portion of each range will use the BAR number in the "flags" (first)
cell in the address. This allows addresses within the endpoint to
be expressed relative to whatever address gets assigned to each BAR.
Unfortunately, if a PCI endpoint device had a devicetree node set
up statically, its "ranges" property (if present) will be static,
and it cannot contain the addresses assigned to the endpoint's BARs
during enumeration.
This means that the "BAR number" based addressing scheme doesn't
work for PCI endpoints whose devicetree nodes are created statically.
To remedy this, modify of_pci_make_dev_node() to dynamically create
a "ranges" property just as is done when the endpoint has a null
devicetree node pointer. The device node will be updated to add
the new "ranges" property (or replace it if one exists).
This allows "BAR number" addresses to work correctly even when the
endpoint's devicetree node is created statically.
Signed-off-by: Alex Elder <elder@riscstar.com>
---
drivers/pci/of.c | 59 ++++++++++++++++++++++++++++++++++-----
drivers/pci/of_property.c | 2 +-
drivers/pci/pci.h | 1 +
3 files changed, 54 insertions(+), 8 deletions(-)
diff --git a/drivers/pci/of.c b/drivers/pci/of.c
index 0bbf1a915b7d2..ce14ae01d1f4b 100644
--- a/drivers/pci/of.c
+++ b/drivers/pci/of.c
@@ -663,20 +663,65 @@ void of_pci_remove_node(struct pci_dev *pdev)
of_node_put(np);
}
+/* Returns true if the ranges property was added or updated successfully */
+static bool of_pci_update_endpoint_node_ranges(struct pci_dev *pdev)
+{
+ struct device_node *np = pci_device_to_OF_node(pdev);
+ struct property *prop;
+ u32 *value;
+ u32 size;
+
+ prop = kzalloc_obj(*prop);
+ if (!prop)
+ return false;
+
+ value = of_pci_build_prop_ranges(pdev, &size);
+ if (!value) {
+ kfree(prop);
+ return false;
+ }
+
+ prop->name = "ranges";
+ prop->length = size * sizeof(u32);
+ prop->value = value;
+
+ /* The property value needs to be in big-endian byte order */
+ while (size--)
+ cpu_to_be32s(value++);
+
+ /* of_update_property() consumes the allocated property */
+ of_update_property(np, prop);
+
+ return true;
+}
+
void of_pci_make_dev_node(struct pci_dev *pdev)
{
struct device_node *ppnode, *np = NULL;
+ struct device *dev = &pdev->dev;
+ struct of_changeset *cset;
const char *pci_type;
- struct of_changeset *cset;
const char *name;
int ret;
- /*
- * If there is already a device tree node linked to this device,
- * return immediately.
- */
- if (pci_device_to_OF_node(pdev))
+ /* No need to create a new devicetree node if one already exists */
+ if (pci_device_to_OF_node(pdev)) {
+ if (pci_is_bridge(pdev))
+ return;
+
+ /*
+ * For an endpoint device, we need to add or update its
+ * devicetree "ranges" property. The new property will
+ * include an entry for every BAR, mapping BAR offsets
+ * to the actual assigned PCI bus address space for the
+ * endpoint. The child space in each range uses the flags
+ * cell of the child bus address to encode the BAR number.
+ */
+ if (!of_pci_update_endpoint_node_ranges(pdev))
+ dev_err(dev, "failed updating ranges property\n");
+
return;
+ }
/* Check if there is device tree node for parent device */
if (!pdev->bus->self)
@@ -715,7 +760,7 @@ void of_pci_make_dev_node(struct pci_dev *pdev)
np->data = cset;
- ret = device_add_of_node(&pdev->dev, np);
+ ret = device_add_of_node(dev, np);
if (ret)
goto out_revert_cset;
diff --git a/drivers/pci/of_property.c b/drivers/pci/of_property.c
index 48ce467e7e966..744dee0dd9625 100644
--- a/drivers/pci/of_property.c
+++ b/drivers/pci/of_property.c
@@ -116,7 +116,7 @@ static int of_pci_prop_bus_range(struct pci_dev *pdev,
*
* Caller is responsible for ensuring the returned pointer gets freed.
*/
-static u32 *of_pci_build_prop_ranges(struct pci_dev *pdev, u32 *count)
+u32 *of_pci_build_prop_ranges(struct pci_dev *pdev, u32 *count)
{
bool bridge_device = pci_is_bridge(pdev);
struct of_pci_range_entry *entries;
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index 2e1ccdd6f6418..6ae8adb30fdd0 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -1316,6 +1316,7 @@ struct of_changeset;
#ifdef CONFIG_PCI_DYNAMIC_OF_NODES
void of_pci_make_dev_node(struct pci_dev *pdev);
void of_pci_remove_node(struct pci_dev *pdev);
+u32 *of_pci_build_prop_ranges(struct pci_dev *pdev, u32 *count);
int of_pci_add_properties(struct pci_dev *pdev, struct of_changeset *ocs,
struct device_node *np);
void of_pci_make_host_bridge_node(struct pci_host_bridge *bridge);
--
2.53.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-13 22:07 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-13 22:07 [PATCH 0/2] PCI: of: update endpoint ranges dynamically Alex Elder
2026-08-13 22:07 ` [PATCH 1/2] PCI: of: introduce of_pci_build_prop_ranges() Alex Elder
2026-08-13 22:07 ` [PATCH 2/2] PCI: of: introduce of_pci_update_endpoint_node_ranges() Alex Elder
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox