]> git.ipfire.org Git - thirdparty/u-boot.git/commitdiff
pinctrl: single: parse gpio-range as a raw array (O(N^2) -> O(N))
authorJordi Trepat Mur <yordy1902@gmail.com>
Thu, 25 Jun 2026 17:41:21 +0000 (19:41 +0200)
committerTom Rini <trini@konsulko.com>
Tue, 28 Jul 2026 18:30:55 +0000 (12:30 -0600)
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@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@gmail.com>
Reviewed-by: Anshul Dalal <anshuld@ti.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
drivers/pinctrl/pinctrl-single.c

index 42980e097e0def158ab8d6779cb18a57a2fdb23c..0029a7e1a2790f074df7f2ba381e63c83dabe418 100644 (file)
@@ -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)