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 <[email protected]> --- 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

