[PATCH v3 4/8] of: unittest: Test fw_devlink with GPIO nexus maps

From: Ernest Van Hoecke

Date: Tue Oct 20 2026 - 09:12:36 EST


From: Ernest Van Hoecke <ernest.vanhoecke@xxxxxxxxxxx>

Add OF unit tests that call the fw_devlink add_links operation for GPIOs
routed through nexus maps. Verify that each mapped consumer has exactly
one link to the final GPIO provider rather than to the nexus.

Also cover direct references and all GPIO property spellings recognized
by fw_devlink, including the deprecated singular and unprefixed forms.

The gpio-map routes its entries to different providers so that selecting
the wrong entry is caught as well.

Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@xxxxxxxxxxxxxxxx>
Reviewed-by: Linus Walleij <linusw@xxxxxxxxxx>
Signed-off-by: Ernest Van Hoecke <ernest.vanhoecke@xxxxxxxxxxx>
---
v3: Route gpio-map entries to two providers, use a middle entry (Herve)
v2: https://lore.kernel.org/r/20260910-fw-devlink-nexus-ready-v2-3-17ac82dbe226@xxxxxxxxxxx
---
drivers/of/unittest-data/tests-phandle.dtsi | 39 +++++++++++++++
drivers/of/unittest.c | 75 +++++++++++++++++++++++++++++
2 files changed, 114 insertions(+)

diff --git a/drivers/of/unittest-data/tests-phandle.dtsi b/drivers/of/unittest-data/tests-phandle.dtsi
index 554a996b2ef1..f5d7ee072c90 100644
--- a/drivers/of/unittest-data/tests-phandle.dtsi
+++ b/drivers/of/unittest-data/tests-phandle.dtsi
@@ -79,6 +79,45 @@ consumer-b {
phandle-list-bad-args = <&provider2 1 0>,
<&provider4 0>;
};
+
+ fw-devlink-tests {
+ gpio_provider_a: gpio-controller-a {
+ #gpio-cells = <2>;
+ };
+
+ gpio_provider_b: gpio-controller-b {
+ #gpio-cells = <2>;
+ };
+
+ fwdevlink_nexus: nexus {
+ #gpio-cells = <2>;
+ gpio-map = <1 0 &gpio_provider_a 3 0>,
+ <2 0 &gpio_provider_b 5 0>,
+ <3 0 &gpio_provider_a 7 0>;
+ gpio-map-mask = <0xffffffff 0>;
+ gpio-map-pass-thru = <0 0xffffffff>;
+ };
+
+ gpio-compat-consumer {
+ gpios = <&fwdevlink_nexus 2 1>;
+ };
+
+ gpio-compat-singular-consumer {
+ gpio = <&fwdevlink_nexus 2 1>;
+ };
+
+ gpio-consumer {
+ test-gpios = <&fwdevlink_nexus 2 1>;
+ };
+
+ gpio-direct-consumer {
+ test-gpios = <&gpio_provider_a 1 0>;
+ };
+
+ gpio-singular-consumer {
+ test-gpio = <&fwdevlink_nexus 2 1>;
+ };
+ };
};
};
};
diff --git a/drivers/of/unittest.c b/drivers/of/unittest.c
index e255f54f4d76..f15c110dbf4d 100644
--- a/drivers/of/unittest.c
+++ b/drivers/of/unittest.c
@@ -10,6 +10,7 @@
#include <linux/dma-direct.h> /* to test phys_to_dma/dma_to_phys */
#include <linux/err.h>
#include <linux/errno.h>
+#include <linux/fwnode.h>
#include <linux/hashtable.h>
#include <linux/libfdt.h>
#include <linux/of.h>
@@ -710,6 +711,79 @@ static void __init of_unittest_parse_phandle_with_args_map(void)
}
}

+static void __init of_unittest_fw_devlink_supplier(struct device_node *tests,
+ const char *consumer_name,
+ const char *supplier_name)
+{
+ struct device_node *consumer, *supplier;
+ struct fwnode_handle *consumer_fwnode;
+ struct fwnode_link *link;
+ unsigned int link_count = 0;
+ bool found = false;
+ int rc;
+
+ consumer = of_get_child_by_name(tests, consumer_name);
+ supplier = of_get_child_by_name(tests, supplier_name);
+ if (unittest(consumer && supplier, "missing consumer %s or supplier %s\n",
+ consumer_name, supplier_name))
+ goto put_nodes;
+
+ consumer_fwnode = of_fwnode_handle(consumer);
+ fwnode_links_purge(consumer_fwnode);
+ rc = fwnode_call_int_op(consumer_fwnode, add_links);
+ if (rc == -EOPNOTSUPP) {
+ pr_info_once("fw_devlink tests skipped: OF supplier links are not supported\n");
+ goto purge_links;
+ }
+ if (unittest(!rc, "failed to add links for %pOF: %d\n", consumer, rc))
+ goto purge_links;
+
+ /*
+ * The suppliers list should normally be accessed with fwnode_link_lock
+ * held, but that lock is private to the driver core. These test nodes
+ * are isolated and have no concurrent link updates.
+ */
+ list_for_each_entry(link, &consumer_fwnode->suppliers, c_hook) {
+ link_count++;
+ if (link->supplier == of_fwnode_handle(supplier))
+ found = true;
+ }
+
+ unittest(link_count == 1, "%pOF has %u suppliers, expected 1\n", consumer, link_count);
+ unittest(found, "%pOF is not linked to supplier %pOF\n", consumer, supplier);
+
+purge_links:
+ fwnode_links_purge(consumer_fwnode);
+put_nodes:
+ of_node_put(consumer);
+ of_node_put(supplier);
+}
+
+static void __init of_unittest_fw_devlink(void)
+{
+ static const struct {
+ const char *consumer;
+ const char *supplier;
+ } cases[] = {
+ { "gpio-compat-consumer", "gpio-controller-b" },
+ { "gpio-compat-singular-consumer", "gpio-controller-b" },
+ { "gpio-consumer", "gpio-controller-b" },
+ { "gpio-direct-consumer", "gpio-controller-a" },
+ { "gpio-singular-consumer", "gpio-controller-b" },
+ };
+ struct device_node *tests;
+ int i;
+
+ tests = of_find_node_by_path("/testcase-data/phandle-tests/fw-devlink-tests");
+ if (unittest(tests, "missing fw_devlink test data\n"))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(cases); i++)
+ of_unittest_fw_devlink_supplier(tests, cases[i].consumer, cases[i].supplier);
+
+ of_node_put(tests);
+}
+
static void __init of_unittest_property_string(void)
{
const char *strings[4];
@@ -4533,6 +4607,7 @@ static int __init of_unittest(void)
of_unittest_dynamic();
of_unittest_parse_phandle_with_args();
of_unittest_parse_phandle_with_args_map();
+ of_unittest_fw_devlink();
of_unittest_printf();
of_unittest_property_string();
of_unittest_property_copy();

--
2.43.0