mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
gpio: kunit: add test cases verifying swnode devlink support
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 <andriy.shevchenko@linux.intel.com> Tested-by: David Gow <david@davidgow.net> Link: https://patch.msgid.link/20260720-swnode-fw-devlink-v5-1-ec250ae6af91@oss.qualcomm.com Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
This commit is contained in:
parent
bc289224b9
commit
9672edf75b
|
|
@ -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,
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user