[PATCH] pinctrl: single: parse gpio-range as a raw array (O(N^2) -> O(N))

Jordi Trepat Mur yordy1902 at gmail.com
Sun Jun 14 12:32:32 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.

On a TI J722S board this makes the pinctrl probe take 723 ms
pre-relocation (caches off) for only 7 entries; with this change it
takes 22 ms (-97%). Any pinctrl-single user with a populated
pinctrl-single,gpio-range property pays this cost.

Read the property once with ofnode_get_property() and iterate over the
raw cells in memory instead. The phandle target node is never used by
this function (only the three argument cells are stored), so resolve
only the first entry with ofnode_parse_phandle_with_args() to validate
the cell layout. Empty entries (phandle == 0) are skipped, matching the
previous behaviour.

Tested on TI J722S (HS-FS): pinctrl probe 723 ms -> 22 ms, identical
gpio-range list produced.

Signed-off-by: Jordi Trepat Mur <yordy1902 at gmail.com>
---
 drivers/pinctrl/pinctrl-single.c | 62 ++++++++++++++++++++++++--------
 1 file changed, 48 insertions(+), 14 deletions(-)

diff --git a/drivers/pinctrl/pinctrl-single.c b/drivers/pinctrl/pinctrl-single.c
index 42980e09..44297951 100644
--- a/drivers/pinctrl/pinctrl-single.c
+++ b/drivers/pinctrl/pinctrl-single.c
@@ -511,27 +511,61 @@ static int single_add_gpio_func(struct udevice *dev)
 	const char *cellname = "#pinctrl-single,gpio-range-cells";
 	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;
-			break;
+	const __be32 *list;
+	int size, total_cells, cells_per_entry, entries, ret, i;
+	int ret_total = 0;
+
+	/*
+	 * Read the property once and iterate over the raw cell array in
+	 * memory. Calling ofnode_parse_phandle_with_args() once per index
+	 * re-walks the property from the start on every call and resolves
+	 * the phandle (a full-FDT scan) at each step, which is O(N^2) in
+	 * the number of entries and very slow pre-relocation with caches
+	 * off. Resolve only the first entry's phandle to validate the
+	 * cell layout; the target node itself is never used here.
+	 */
+	list = ofnode_get_property(dev_ofnode(dev), propname, &size);
+	if (!list || size <= 0)
+		return 0;
+	total_cells = size / sizeof(*list);
+
+	/* Validate cell layout via the first entry (one phandle lookup total). */
+	ret = ofnode_parse_phandle_with_args(dev_ofnode(dev), propname,
+					     cellname, 0, 0, &gpiospec);
+	if (ret)
+		return 0;
+	if (gpiospec.args_count != 3) {
+		dev_err(dev, "expected 3 gpio-range args/entry, got %d\n",
+			gpiospec.args_count);
+		return -EINVAL;
+	}
+	cells_per_entry = 1 + gpiospec.args_count;	/* phandle + args */
+	if (total_cells % cells_per_entry) {
+		dev_err(dev, "gpio-range size %d cells not multiple of %d\n",
+			total_cells, cells_per_entry);
+		return -EINVAL;
+	}
+	entries = total_cells / cells_per_entry;
+
+	for (i = 0; i < entries; i++) {
+		const __be32 *entry = list + i * cells_per_entry;
+		u32 phandle_i = be32_to_cpup(entry + 0);
+
+		if (phandle_i == 0) {
+			/* empty entry - match original: skip without creating range */
+			continue;
 		}
 		range = devm_kzalloc(dev, sizeof(*range), GFP_KERNEL);
 		if (!range) {
-			ret = -ENOMEM;
+			ret_total = -ENOMEM;
 			break;
 		}
-		range->offset = gpiospec.args[0];
-		range->npins = gpiospec.args[1];
-		range->gpiofunc = gpiospec.args[2];
+		range->offset   = be32_to_cpup(entry + 1);
+		range->npins    = be32_to_cpup(entry + 2);
+		range->gpiofunc = be32_to_cpup(entry + 3);
 		list_add_tail(&range->node, &priv->gpiofuncs);
 	}
-	return ret;
+	return ret_total;
 }
 
 static int single_probe(struct udevice *dev)
-- 
2.34.1



More information about the U-Boot mailing list