The software node fw_devlink support already has its own kunit suite, but that verifies the fwnode links in isolation. Add GPIO tests that prove the ordering works in a real-life use-case: a GPIO consumer that references its provider via a software node.
The first suite registers the provider's software node, adds the consumer device first and checks that fw_devlink defers its probe until the provider has been added and bound. The second covers the fallback: with the provider's software node not yet registered no supplier link is created, so the consumer probes, devm_gpiod_get() returns -EPROBE_DEFER and the consumer only binds once the provider shows up. While at it: the existing gpio_unbind_with_consumers() test keeps the consumer bound while the provider goes away and then operates the orphaned descriptor. With software nodes now being covered by fw_devlink that would instead force-unbind the consumer along with the provider, so opt it out by setting FWNODE_FLAG_LINKS_ADDED. Acked-by: Andy Shevchenko <[email protected]> Tested-by: David Gow <[email protected]> Signed-off-by: Bartosz Golaszewski <[email protected]> --- Currently only devicetree systems have their devices' probe ordered against their suppliers automatically by fw_devlink. Software nodes have lately been used extensively treewide to describe references to resource suppliers: most notably, the GPIO subsystem uses it in many places. Now that the conversion of "dangling" GPIO chip software nodes to using real links is almost done, it makes sense to ensure no needless probe deferrals by implementing the add_links() callback from the fwnode interface. This series extends software node support to use fw_devlink and adds test coverage for the new behaviour. This is the remaining GPIO patch (originally patch 5/5) that had some conflicts with my gpio/for-next branch. It's rebased on top of the changes Danilo queued in the driver core tree. --- Changes in v5: - Drop patches that went into linux-next and rebase the remaining GPIO patch on top of the already queued kunit test cases - Link to v4: https://patch.msgid.link/[email protected] Changes in v4: - Extend patch 1 with an additional kunit helper: kunit_device_add_software_node() and use it in patch 3/5 to fix the release ordering - Fix a memory leak in GPIO test cases pointed out by Danilo - Fix more possible device_link_mq races: extend the coverage of device_link_wait_removal() - Link to v3: https://patch.msgid.link/[email protected] Changes in v3: - Use device_match_fwnode() where applicable - Fix a race between the actions on the device_link_mq and the GPIO test cases' swnode management - Link to v2: https://patch.msgid.link/[email protected] Changes in v2: - Use loop-local variables where applicable - Reformat a long comment - Keep trailing commas on array initializations which may potentially be extended in the future - Add missing headers - Update MAINTAINERS in commit adding a new file - Initialize arrays at declaration - Fix signedness of ints where needed - Remove unneeded DISABLE_STRUCTLEAK_PLUGIN switch for swnode tests - Link to v1: https://patch.msgid.link/[email protected] --- drivers/gpio/gpiolib-kunit.c | 291 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 284 insertions(+), 7 deletions(-) diff --git a/drivers/gpio/gpiolib-kunit.c b/drivers/gpio/gpiolib-kunit.c index c9c5b437482012cf9dbf8d593fa686e248ea1c88..e6cb43a8df5b8ea3f1904ecfca4d0bc4fe2d0e83 100644 --- a/drivers/gpio/gpiolib-kunit.c +++ b/drivers/gpio/gpiolib-kunit.c @@ -3,6 +3,8 @@ * Copyright (C) Qualcomm Technologies, Inc. and/or its subsidiaries */ +#include <linux/cleanup.h> +#include <linux/err.h> #include <linux/fwnode.h> #include <linux/gpio/consumer.h> #include <linux/gpio/driver.h> @@ -11,12 +13,16 @@ #include <linux/notifier.h> #include <linux/platform_device.h> #include <linux/property.h> +#include <linux/types.h> +#include <kunit/fwnode.h> #include <kunit/platform_device.h> #include <kunit/test.h> #define GPIO_TEST_PROVIDER "gpio-test-provider" #define GPIO_SWNODE_TEST_CONSUMER "gpio-swnode-test-consumer" +#define GPIO_PROBE_ORDER_TEST_CONSUMER "gpio-probe-order-test-consumer" +#define GPIO_PROBE_DEFER_TEST_CONSUMER "gpio-probe-defer-test-consumer" #define GPIO_UNBIND_TEST_CONSUMER "gpio-unbind-test-consumer" #define GPIO_CONSUMER_NAME "gpio-swnode-consumer-test-device" @@ -275,6 +281,265 @@ static struct kunit_suite gpio_swnode_lookup_test_suite = { .init = gpio_swnode_register_drivers, }; +static void gpio_swnode_unregister_swnode(void *data) +{ + software_node_unregister(data); +} + +struct gpio_probe_order_pdata { + unsigned int probe_count; + bool gpio_ok; +}; + +static const struct gpio_probe_order_pdata gpio_probe_order_pdata_template = { + .probe_count = 0, + .gpio_ok = false, +}; + +static int gpio_probe_order_consumer_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_probe_order_pdata *pdata = dev_get_platdata(dev); + struct gpio_desc *desc; + + pdata->probe_count++; + + desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + pdata->gpio_ok = true; + + return 0; +} + +static struct platform_driver gpio_probe_order_consumer_driver = { + .probe = gpio_probe_order_consumer_probe, + .driver = { + .name = GPIO_PROBE_ORDER_TEST_CONSUMER, + }, +}; + +/* + * Verify that fw_devlink orders the probe of a GPIO consumer after its + * provider. The consumer references the provider through a software node and + * is registered first. fw_devlink must defer it before its driver's probe() + * is ever entered, so the consumer probes exactly once - only after the + * provider is added and bound. + */ +static void gpio_swnode_probe_order(struct kunit *test) +{ + struct property_entry properties[2] = { }; + struct gpio_probe_order_pdata *pdata; + struct platform_device_info pdevinfo; + struct platform_device *prvd, *cons; + bool bound = false; + int ret; + + ret = kunit_platform_driver_register(test, &gpio_test_provider_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_platform_driver_register(test, &gpio_probe_order_consumer_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = software_node_register(&gpio_test_provider_swnode); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_add_action_or_reset(test, gpio_swnode_unregister_swnode, + (void *)&gpio_test_provider_swnode); + KUNIT_ASSERT_EQ(test, ret, 0); + + properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios", + &gpio_test_provider_swnode, + 0, GPIO_ACTIVE_HIGH); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_PROBE_ORDER_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + .data = &gpio_probe_order_pdata_template, + .size_data = sizeof(gpio_probe_order_pdata_template), + .properties = properties, + }; + + cons = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + + wait_for_device_probe(); + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_ASSERT_EQ(test, pdata->probe_count, 0); + KUNIT_ASSERT_FALSE(test, pdata->gpio_ok); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_TEST_PROVIDER, + .id = PLATFORM_DEVID_NONE, + .swnode = &gpio_test_provider_swnode, + }; + + prvd = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd); + + wait_for_device_probe(); + + scoped_guard(device, &prvd->dev) + bound = device_is_bound(&prvd->dev); + KUNIT_ASSERT_TRUE(test, bound); + + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + KUNIT_ASSERT_TRUE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_ASSERT_EQ(test, pdata->probe_count, 1); + KUNIT_ASSERT_TRUE(test, pdata->gpio_ok); +} + +struct gpio_probe_defer_pdata { + unsigned int probe_count; + int gpio_err; +}; + +static const struct gpio_probe_defer_pdata gpio_probe_defer_pdata_template = { + .probe_count = 0, + .gpio_err = 0, +}; + +static int gpio_probe_defer_consumer_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct gpio_probe_defer_pdata *pdata = dev_get_platdata(dev); + struct gpio_desc *desc; + + pdata->probe_count++; + + desc = devm_gpiod_get(dev, "foo", GPIOD_OUT_HIGH); + if (IS_ERR(desc)) { + pdata->gpio_err = PTR_ERR(desc); + return pdata->gpio_err; + } + + pdata->gpio_err = 0; + + return 0; +} + +static struct platform_driver gpio_probe_defer_consumer_driver = { + .probe = gpio_probe_defer_consumer_probe, + .driver = { + .name = GPIO_PROBE_DEFER_TEST_CONSUMER, + }, +}; + +/* + * Verify that a GPIO consumer referencing a provider whose software node is + * not registered yet, defers its probe instead of failing. + * + * The provider software node is deliberately left unregistered when the + * consumer is added. fw_devlink cannot resolve the reference, so it creates no + * supplier link and does not order the consumer - the consumer's probe() runs + * and reaches devm_gpiod_get(). The swnode GPIO lookup returns -ENOTCONN for a + * reference to an unregistered node, which gpiolib maps to -EPROBE_DEFER. Once + * the provider software node and device appear, the deferred consumer probes + * again and binds. + */ +static void gpio_swnode_probe_defer_on_unregistered(struct kunit *test) +{ + struct property_entry properties[2] = { }; + struct gpio_probe_defer_pdata *pdata; + struct platform_device_info pdevinfo; + struct platform_device *prvd, *cons; + struct fwnode_handle *fwnode; + bool bound = false; + int ret; + + ret = kunit_platform_driver_register(test, &gpio_test_provider_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kunit_platform_driver_register(test, &gpio_probe_defer_consumer_driver); + KUNIT_ASSERT_EQ(test, ret, 0); + + properties[0] = PROPERTY_ENTRY_GPIO("foo-gpios", + &gpio_test_provider_swnode, + 0, GPIO_ACTIVE_HIGH); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_PROBE_DEFER_TEST_CONSUMER, + .id = PLATFORM_DEVID_NONE, + .data = &gpio_probe_defer_pdata_template, + .size_data = sizeof(gpio_probe_defer_pdata_template), + .properties = properties, + }; + + cons = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + + wait_for_device_probe(); + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + + KUNIT_ASSERT_FALSE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_ASSERT_GT(test, pdata->probe_count, 0); + KUNIT_ASSERT_EQ(test, pdata->gpio_err, -EPROBE_DEFER); + + fwnode = kunit_software_node_register(test, &gpio_test_provider_swnode); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode); + + pdevinfo = (struct platform_device_info){ + .name = GPIO_TEST_PROVIDER, + .id = PLATFORM_DEVID_NONE, + .swnode = &gpio_test_provider_swnode, + }; + + prvd = kunit_platform_device_register_full(test, &pdevinfo); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, prvd); + + wait_for_device_probe(); + + scoped_guard(device, &prvd->dev) + bound = device_is_bound(&prvd->dev); + KUNIT_ASSERT_TRUE(test, bound); + + scoped_guard(device, &cons->dev) + bound = device_is_bound(&cons->dev); + KUNIT_ASSERT_TRUE(test, bound); + + pdata = dev_get_platdata(&cons->dev); + KUNIT_ASSERT_EQ(test, pdata->gpio_err, 0); + + /* Tear down the consumer before the provider to free the GPIO. */ + kunit_platform_device_unregister(test, cons); +} + +static int gpio_swnode_probe_order_test_init(struct kunit *test) +{ + /* + * A prior test may have left a managed device link teardown queued on + * the device_link_mq. Flush it so that software_node_register() + * doesn't spuriously see the node as registered and fail with -EEXIST. + */ + device_link_wait_removal(); + + return 0; +} + +static struct kunit_case gpio_swnode_probe_order_tests[] = { + KUNIT_CASE(gpio_swnode_probe_order), + KUNIT_CASE(gpio_swnode_probe_defer_on_unregistered), + { } +}; + +static struct kunit_suite gpio_swnode_probe_order_test_suite = { + .name = "gpio-swnode-probe-order", + .test_cases = gpio_swnode_probe_order_tests, + .init = gpio_swnode_probe_order_test_init, +}; + static BLOCKING_NOTIFIER_HEAD(gpio_unbind_notifier); struct gpio_unbind_consumer_drvdata { @@ -372,15 +637,24 @@ static void gpio_unbind_with_consumers(struct kunit *test) 0, GPIO_ACTIVE_HIGH); properties[1] = (struct property_entry){ }; - pdevinfo = (struct platform_device_info){ - .name = GPIO_UNBIND_TEST_CONSUMER, - .id = PLATFORM_DEVID_NONE, - .properties = properties, - }; - - cons = kunit_platform_device_register_full(test, &pdevinfo); + /* + * This test deliberately keeps the consumer bound while the provider + * is unregistered. fw_devlink would force-unbind the consumer before + * the provider so use the FWNODE_FLAG_LINKS_ADDED flag to opt out of + * it as a workaround. + */ + cons = kunit_platform_device_alloc(test, GPIO_UNBIND_TEST_CONSUMER, + PLATFORM_DEVID_NONE); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cons); + ret = device_create_managed_software_node(&cons->dev, properties, NULL); + KUNIT_ASSERT_EQ(test, ret, 0); + + fwnode_set_flag(dev_fwnode(&cons->dev), FWNODE_FLAG_LINKS_ADDED); + + ret = kunit_platform_device_add(test, cons); + KUNIT_ASSERT_EQ(test, ret, 0); + wait_for_device_probe(); scoped_guard(device, &cons->dev) bound = device_is_bound(&cons->dev); @@ -408,6 +682,8 @@ static struct kunit_case gpio_unbind_with_consumers_tests[] = { static struct kunit_suite gpio_unbind_with_consumers_test_suite = { .name = "gpio-unbind-with-consumers", .test_cases = gpio_unbind_with_consumers_tests, + /* We need this here too to clean any left over links. */ + .init = gpio_swnode_probe_order_test_init, }; /* @@ -593,6 +869,7 @@ static struct kunit_suite gpio_swnode_hog_test_suite = { kunit_test_suites( &gpio_swnode_lookup_test_suite, + &gpio_swnode_probe_order_test_suite, &gpio_unbind_with_consumers_test_suite, &gpio_swnode_hog_test_suite, ); --- base-commit: bc289224b9a1b0a7045c81272172342811b5c0fd change-id: 20260609-swnode-fw-devlink-cdc8dc4e5cb4 Best regards, -- Bartosz Golaszewski <[email protected]>

