[PATCH v2] pinctrl: single: parse gpio-range as a raw array (O(N^2) -> O(N))
Jordi Trepat Mur
yordy1902 at gmail.com
Thu Jun 25 19:41:21 CEST 2026
single_add_gpio_func() calls ofnode_parse_phandle_with_args() once per
gpio-range entry. With a flat device tree, every call re-iterates the
property from index 0 and, because cellname is set, performs an
fdt_node_offset_by_phandle() (a full-FDT scan) at every step. The total
cost is therefore quadratic in the number of entries and proportional to
the size of the device tree.
The impact depends on when the pinctrl is probed. On the J722S EVM
defconfig, pinctrl at f4000 (main_pmx0, the SoC's stock 7-entry gpio-range)
is probed after relocation (caches on): single_add_gpio_func() takes
~18 ms there and ~1 ms with this change. When the node is probed before
relocation (caches off) the same parse takes hundreds of ms; on a board
that probes it pre-relocation and runs LPDDR4 clocked down it reached
723 ms, cut to 22 ms here. Any pinctrl-single user with a populated
gpio-range property pays this cost.
The phandle target is never dereferenced by this function (only the
argument cells are stored), so resolve it only for the first entry to
learn the per-entry cell count, then read the argument cells directly
with ofnode_read_u32_index() and allocate the ranges in one block. A
zero phandle still terminates the list, as in the original loop. This
follows the existing pinctrl-single,gpio-range usage observed in current
DTs, where entries use the same provider and therefore a uniform
per-entry cell count.
Signed-off-by: Jordi Trepat Mur <yordy1902 at gmail.com>
---
Changes in v2:
- Restore terminate-on-zero-phandle: the original breaks on the first
ofnode_parse_phandle_with_args() error and a zero phandle is one such
error, so v1's "skip" was a behaviour change (Simon Glass).
- Drop the args_count and size-multiple -EINVAL paths; malformed or
partial data no longer turns into a hard probe failure and is handled
leniently, as before (Simon Glass).
- Read the argument cells with ofnode_read_u32_index() instead of an
open-coded be32_to_cpup() walk; works for OF_LIVE too (Simon Glass).
- Coalesce the per-entry devm_kzalloc() into a single devm_kcalloc()
(Anshul Dalal, Simon Glass).
- Drop the unused ret_total; reuse the single ret (Anshul, Simon).
- Use single-space field assignment, matching the file (Anshul, Simon).
drivers/pinctrl/pinctrl-single.c | 79 +++++++++++++++++++++++++-------
1 file changed, 62 insertions(+), 17 deletions(-)
diff --git a/drivers/pinctrl/pinctrl-single.c b/drivers/pinctrl/pinctrl-single.c
index 42980e09..0029a7e1 100644
--- a/drivers/pinctrl/pinctrl-single.c
+++ b/drivers/pinctrl/pinctrl-single.c
@@ -509,29 +509,74 @@ static int single_add_gpio_func(struct udevice *dev)
struct single_priv *priv = dev_get_priv(dev);
const char *propname = "pinctrl-single,gpio-range";
const char *cellname = "#pinctrl-single,gpio-range-cells";
+ ofnode node = dev_ofnode(dev);
struct single_gpiofunc_range *range;
struct ofnode_phandle_args gpiospec;
- int ret, i;
-
- for (i = 0; ; i++) {
- ret = ofnode_parse_phandle_with_args(dev_ofnode(dev), propname,
- cellname, 0, i, &gpiospec);
- /* Do not treat it as error. Only treat it as end condition. */
- if (ret) {
- ret = 0;
+ int size, total_cells, cells_per_entry, entries, ret, i;
+
+ /*
+ * The original loop called ofnode_parse_phandle_with_args() once per
+ * entry. On a flat DT each call resolves the phandle, which scans the
+ * whole tree, so the repeated parse is quadratic in the number of
+ * entries and proportional to the tree size. This is very slow
+ * pre-relocation with caches off.
+ *
+ * The phandle target is not used here (only the argument cells are
+ * stored), so resolve it only for the first entry to learn the
+ * per-entry cell count, then read the argument cells directly. This
+ * follows the existing pinctrl-single,gpio-range usage observed in
+ * current DTs, where entries use the same provider and therefore a
+ * uniform per-entry cell count.
+ */
+ ret = ofnode_parse_phandle_with_args(node, propname, cellname, 0, 0,
+ &gpiospec);
+ if (ret)
+ return 0;
+ if (gpiospec.args_count != 3)
+ return 0;
+
+ cells_per_entry = 1 + gpiospec.args_count;
+ size = ofnode_read_size(node, propname);
+ if (size < (int)sizeof(u32))
+ return 0;
+ total_cells = size / sizeof(u32);
+ entries = total_cells / cells_per_entry;
+ if (!entries)
+ return 0;
+
+ range = devm_kcalloc(dev, entries, sizeof(*range), GFP_KERNEL);
+ if (!range)
+ return -ENOMEM;
+
+ for (i = 0; i < entries; i++) {
+ int base = i * cells_per_entry;
+ u32 phandle;
+
+ ret = ofnode_read_u32_index(node, propname, base, &phandle);
+ if (ret)
break;
- }
- range = devm_kzalloc(dev, sizeof(*range), GFP_KERNEL);
- if (!range) {
- ret = -ENOMEM;
+
+ /* A zero phandle terminates the list, as in the original loop. */
+ if (!phandle)
break;
- }
- range->offset = gpiospec.args[0];
- range->npins = gpiospec.args[1];
- range->gpiofunc = gpiospec.args[2];
+
+ ret = ofnode_read_u32_index(node, propname, base + 1, &range->offset);
+ if (ret)
+ break;
+
+ ret = ofnode_read_u32_index(node, propname, base + 2, &range->npins);
+ if (ret)
+ break;
+
+ ret = ofnode_read_u32_index(node, propname, base + 3, &range->gpiofunc);
+ if (ret)
+ break;
+
list_add_tail(&range->node, &priv->gpiofuncs);
+ range++;
}
- return ret;
+
+ return 0;
}
static int single_probe(struct udevice *dev)
--
2.34.1
More information about the U-Boot
mailing list