platform: Add test managed platform_device/driver APIs

Introduce KUnit resource wrappers around platform_driver_register(),
platform_device_alloc(), and platform_device_add() so that test authors
can register platform drivers/devices from their tests and have the
drivers/devices automatically be unregistered when the test is done.

This makes test setup code simpler when a platform driver or platform
device is needed. Add a few test cases at the same time to make sure the
APIs work as intended.

Cc: Brendan Higgins <brendan.higgins@linux.dev>
Reviewed-by: David Gow <davidgow@google.com>
Cc: Rae Moar <rmoar@google.com>
Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: "Rafael J. Wysocki" <rafael@kernel.org>
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
Link: https://lore.kernel.org/r/20240718210513.3801024-6-sboyd@kernel.org
This commit is contained in:
Stephen Boyd 2024-07-18 14:05:04 -07:00
parent 5c9dd72d83
commit 5ac7973032
7 changed files with 565 additions and 1 deletions

View File

@ -10,6 +10,7 @@ API Reference
resource resource
functionredirection functionredirection
of of
platformdevice
This page documents the KUnit kernel testing API. It is divided into the This page documents the KUnit kernel testing API. It is divided into the
@ -36,3 +37,7 @@ Driver KUnit API
Documentation/dev-tools/kunit/api/of.rst Documentation/dev-tools/kunit/api/of.rst
- Documents the KUnit device tree (OF) API - Documents the KUnit device tree (OF) API
Documentation/dev-tools/kunit/api/platformdevice.rst
- Documents the KUnit platform device API

View File

@ -0,0 +1,10 @@
.. SPDX-License-Identifier: GPL-2.0
===================
Platform Device API
===================
The KUnit platform device API is used to test platform devices.
.. kernel-doc:: lib/kunit/platform.c
:export:

View File

@ -393,6 +393,7 @@ bool device_is_bound(struct device *dev)
{ {
return dev->p && klist_node_attached(&dev->p->knode_driver); return dev->p && klist_node_attached(&dev->p->knode_driver);
} }
EXPORT_SYMBOL_GPL(device_is_bound);
static void driver_bound(struct device *dev) static void driver_bound(struct device *dev)
{ {

View File

@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _KUNIT_PLATFORM_DRIVER_H
#define _KUNIT_PLATFORM_DRIVER_H
struct kunit;
struct platform_device;
struct platform_driver;
struct platform_device *
kunit_platform_device_alloc(struct kunit *test, const char *name, int id);
int kunit_platform_device_add(struct kunit *test, struct platform_device *pdev);
int kunit_platform_device_prepare_wait_for_probe(struct kunit *test,
struct platform_device *pdev,
struct completion *x);
int kunit_platform_driver_register(struct kunit *test,
struct platform_driver *drv);
#endif

View File

@ -9,7 +9,8 @@ kunit-objs += test.o \
try-catch.o \ try-catch.o \
executor.o \ executor.o \
attributes.o \ attributes.o \
device.o device.o \
platform.o
ifeq ($(CONFIG_KUNIT_DEBUGFS),y) ifeq ($(CONFIG_KUNIT_DEBUGFS),y)
kunit-objs += debugfs.o kunit-objs += debugfs.o
@ -19,6 +20,7 @@ endif
obj-y += hooks.o obj-y += hooks.o
obj-$(CONFIG_KUNIT_TEST) += kunit-test.o obj-$(CONFIG_KUNIT_TEST) += kunit-test.o
obj-$(CONFIG_KUNIT_TEST) += platform-test.o
# string-stream-test compiles built-in only. # string-stream-test compiles built-in only.
ifeq ($(CONFIG_KUNIT_TEST),y) ifeq ($(CONFIG_KUNIT_TEST),y)

224
lib/kunit/platform-test.c Normal file
View File

@ -0,0 +1,224 @@
// SPDX-License-Identifier: GPL-2.0
/*
* KUnit test for KUnit platform driver infrastructure.
*/
#include <linux/platform_device.h>
#include <kunit/platform_device.h>
#include <kunit/test.h>
/*
* Test that kunit_platform_device_alloc() creates a platform device.
*/
static void kunit_platform_device_alloc_test(struct kunit *test)
{
KUNIT_EXPECT_NOT_ERR_OR_NULL(test,
kunit_platform_device_alloc(test, "kunit-platform", 1));
}
/*
* Test that kunit_platform_device_add() registers a platform device on the
* platform bus with the proper name and id.
*/
static void kunit_platform_device_add_test(struct kunit *test)
{
struct platform_device *pdev;
const char *name = "kunit-platform-add";
const int id = -1;
pdev = kunit_platform_device_alloc(test, name, id);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
KUNIT_EXPECT_EQ(test, 0, kunit_platform_device_add(test, pdev));
KUNIT_EXPECT_TRUE(test, dev_is_platform(&pdev->dev));
KUNIT_EXPECT_STREQ(test, pdev->name, name);
KUNIT_EXPECT_EQ(test, pdev->id, id);
}
/*
* Test that kunit_platform_device_add() called twice with the same device name
* and id fails the second time and properly cleans up.
*/
static void kunit_platform_device_add_twice_fails_test(struct kunit *test)
{
struct platform_device *pdev;
const char *name = "kunit-platform-add-2";
const int id = -1;
pdev = kunit_platform_device_alloc(test, name, id);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_add(test, pdev));
pdev = kunit_platform_device_alloc(test, name, id);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
KUNIT_EXPECT_NE(test, 0, kunit_platform_device_add(test, pdev));
}
static int kunit_platform_device_find_by_name(struct device *dev, const void *data)
{
return strcmp(dev_name(dev), data) == 0;
}
/*
* Test that kunit_platform_device_add() cleans up by removing the platform
* device when the test finishes. */
static void kunit_platform_device_add_cleans_up(struct kunit *test)
{
struct platform_device *pdev;
const char *name = "kunit-platform-clean";
const int id = -1;
struct kunit fake;
struct device *dev;
kunit_init_test(&fake, "kunit_platform_device_add_fake_test", NULL);
KUNIT_ASSERT_EQ(test, fake.status, KUNIT_SUCCESS);
pdev = kunit_platform_device_alloc(&fake, name, id);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_add(&fake, pdev));
dev = bus_find_device(&platform_bus_type, NULL, name,
kunit_platform_device_find_by_name);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
put_device(dev);
/* Remove pdev */
kunit_cleanup(&fake);
/*
* Failing to migrate the kunit_resource would lead to an extra
* put_device() call on the platform device. The best we can do here is
* make sure the device no longer exists on the bus, but if something
* is wrong we'll see a refcount underflow here. We can't test for a
* refcount underflow because the kref matches the lifetime of the
* device which should already be freed and could be used by something
* else.
*/
dev = bus_find_device(&platform_bus_type, NULL, name,
kunit_platform_device_find_by_name);
KUNIT_EXPECT_PTR_EQ(test, NULL, dev);
put_device(dev);
}
/*
* Test suite for struct platform_device kunit APIs
*/
static struct kunit_case kunit_platform_device_test_cases[] = {
KUNIT_CASE(kunit_platform_device_alloc_test),
KUNIT_CASE(kunit_platform_device_add_test),
KUNIT_CASE(kunit_platform_device_add_twice_fails_test),
KUNIT_CASE(kunit_platform_device_add_cleans_up),
{}
};
static struct kunit_suite kunit_platform_device_suite = {
.name = "kunit_platform_device",
.test_cases = kunit_platform_device_test_cases,
};
struct kunit_platform_driver_test_context {
struct platform_driver pdrv;
const char *data;
};
static const char * const test_data = "test data";
static inline struct kunit_platform_driver_test_context *
to_test_context(struct platform_device *pdev)
{
return container_of(to_platform_driver(pdev->dev.driver),
struct kunit_platform_driver_test_context,
pdrv);
}
static int kunit_platform_driver_probe(struct platform_device *pdev)
{
struct kunit_platform_driver_test_context *ctx;
ctx = to_test_context(pdev);
ctx->data = test_data;
return 0;
}
/* Test that kunit_platform_driver_register() registers a driver that probes. */
static void kunit_platform_driver_register_test(struct kunit *test)
{
struct platform_device *pdev;
struct kunit_platform_driver_test_context *ctx;
DECLARE_COMPLETION_ONSTACK(comp);
const char *name = "kunit-platform-register";
ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
pdev = kunit_platform_device_alloc(test, name, -1);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_add(test, pdev));
ctx->pdrv.probe = kunit_platform_driver_probe;
ctx->pdrv.driver.name = name;
ctx->pdrv.driver.owner = THIS_MODULE;
KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_prepare_wait_for_probe(test, pdev, &comp));
KUNIT_EXPECT_EQ(test, 0, kunit_platform_driver_register(test, &ctx->pdrv));
KUNIT_EXPECT_NE(test, 0, wait_for_completion_timeout(&comp, 3 * HZ));
KUNIT_EXPECT_STREQ(test, ctx->data, test_data);
}
/*
* Test that kunit_platform_device_prepare_wait_for_probe() completes the completion
* when the device is already probed.
*/
static void kunit_platform_device_prepare_wait_for_probe_completes_when_already_probed(struct kunit *test)
{
struct platform_device *pdev;
struct kunit_platform_driver_test_context *ctx;
DECLARE_COMPLETION_ONSTACK(comp);
const char *name = "kunit-platform-wait";
ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
pdev = kunit_platform_device_alloc(test, name, -1);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_add(test, pdev));
ctx->pdrv.probe = kunit_platform_driver_probe;
ctx->pdrv.driver.name = name;
ctx->pdrv.driver.owner = THIS_MODULE;
/* Make sure driver has actually probed */
KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_prepare_wait_for_probe(test, pdev, &comp));
KUNIT_ASSERT_EQ(test, 0, kunit_platform_driver_register(test, &ctx->pdrv));
KUNIT_ASSERT_NE(test, 0, wait_for_completion_timeout(&comp, 3 * HZ));
reinit_completion(&comp);
KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_prepare_wait_for_probe(test, pdev, &comp));
KUNIT_EXPECT_NE(test, 0, wait_for_completion_timeout(&comp, HZ));
}
static struct kunit_case kunit_platform_driver_test_cases[] = {
KUNIT_CASE(kunit_platform_driver_register_test),
KUNIT_CASE(kunit_platform_device_prepare_wait_for_probe_completes_when_already_probed),
{}
};
/*
* Test suite for struct platform_driver kunit APIs
*/
static struct kunit_suite kunit_platform_driver_suite = {
.name = "kunit_platform_driver",
.test_cases = kunit_platform_driver_test_cases,
};
kunit_test_suites(
&kunit_platform_device_suite,
&kunit_platform_driver_suite,
);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("KUnit test for KUnit platform driver infrastructure");

302
lib/kunit/platform.c Normal file
View File

@ -0,0 +1,302 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Test managed platform driver
*/
#include <linux/completion.h>
#include <linux/device/bus.h>
#include <linux/device/driver.h>
#include <linux/platform_device.h>
#include <kunit/platform_device.h>
#include <kunit/resource.h>
struct kunit_platform_device_alloc_params {
const char *name;
int id;
};
static int kunit_platform_device_alloc_init(struct kunit_resource *res, void *context)
{
struct kunit_platform_device_alloc_params *params = context;
struct platform_device *pdev;
pdev = platform_device_alloc(params->name, params->id);
if (!pdev)
return -ENOMEM;
res->data = pdev;
return 0;
}
static void kunit_platform_device_alloc_exit(struct kunit_resource *res)
{
struct platform_device *pdev = res->data;
platform_device_put(pdev);
}
/**
* kunit_platform_device_alloc() - Allocate a KUnit test managed platform device
* @test: test context
* @name: device name of platform device to alloc
* @id: identifier of platform device to alloc.
*
* Allocate a test managed platform device. The device is put when the test completes.
*
* Return: Allocated platform device on success, NULL on failure.
*/
struct platform_device *
kunit_platform_device_alloc(struct kunit *test, const char *name, int id)
{
struct kunit_platform_device_alloc_params params = {
.name = name,
.id = id,
};
return kunit_alloc_resource(test,
kunit_platform_device_alloc_init,
kunit_platform_device_alloc_exit,
GFP_KERNEL, &params);
}
EXPORT_SYMBOL_GPL(kunit_platform_device_alloc);
static void kunit_platform_device_add_exit(struct kunit_resource *res)
{
struct platform_device *pdev = res->data;
platform_device_unregister(pdev);
}
static bool
kunit_platform_device_alloc_match(struct kunit *test,
struct kunit_resource *res, void *match_data)
{
struct platform_device *pdev = match_data;
return res->data == pdev && res->free == kunit_platform_device_alloc_exit;
}
KUNIT_DEFINE_ACTION_WRAPPER(platform_device_unregister_wrapper,
platform_device_unregister, struct platform_device *);
/**
* kunit_platform_device_add() - Register a KUnit test managed platform device
* @test: test context
* @pdev: platform device to add
*
* Register a test managed platform device. The device is unregistered when the
* test completes.
*
* Return: 0 on success, negative errno on failure.
*/
int kunit_platform_device_add(struct kunit *test, struct platform_device *pdev)
{
struct kunit_resource *res;
int ret;
ret = platform_device_add(pdev);
if (ret)
return ret;
res = kunit_find_resource(test, kunit_platform_device_alloc_match, pdev);
if (res) {
/*
* Transfer the reference count of the platform device if it
* was allocated with kunit_platform_device_alloc(). In this
* case, calling platform_device_put() when the test exits from
* kunit_platform_device_alloc_exit() would lead to reference
* count underflow because platform_device_unregister_wrapper()
* calls platform_device_unregister() which also calls
* platform_device_put().
*
* Usually callers transfer the refcount initialized in
* platform_device_alloc() to platform_device_add() by calling
* platform_device_unregister() when platform_device_add()
* succeeds or platform_device_put() when it fails. KUnit has to
* keep this straight by redirecting the free routine for the
* resource to the right function. Luckily this only has to
* account for the success scenario.
*/
res->free = kunit_platform_device_add_exit;
kunit_put_resource(res);
} else {
ret = kunit_add_action_or_reset(test, platform_device_unregister_wrapper, pdev);
if (ret)
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(kunit_platform_device_add);
struct kunit_platform_device_probe_nb {
struct completion *x;
struct device *dev;
struct notifier_block nb;
};
static int kunit_platform_device_probe_notify(struct notifier_block *nb,
unsigned long event, void *data)
{
struct kunit_platform_device_probe_nb *knb;
struct device *dev = data;
knb = container_of(nb, struct kunit_platform_device_probe_nb, nb);
if (event != BUS_NOTIFY_BOUND_DRIVER || knb->dev != dev)
return NOTIFY_DONE;
complete(knb->x);
return NOTIFY_OK;
}
static void kunit_platform_device_probe_nb_remove(void *nb)
{
bus_unregister_notifier(&platform_bus_type, nb);
}
/**
* kunit_platform_device_prepare_wait_for_probe() - Prepare a completion
* variable to wait for a platform device to probe
* @test: test context
* @pdev: platform device to prepare to wait for probe of
* @x: completion variable completed when @dev has probed
*
* Prepare a completion variable @x to wait for @pdev to probe. Waiting on the
* completion forces a preemption, allowing the platform driver to probe.
*
* Example
*
* .. code-block:: c
*
* static int kunit_platform_driver_probe(struct platform_device *pdev)
* {
* return 0;
* }
*
* static void kunit_platform_driver_test(struct kunit *test)
* {
* struct platform_device *pdev;
* struct platform_driver *pdrv;
* DECLARE_COMPLETION_ONSTACK(comp);
*
* pdev = kunit_platform_device_alloc(test, "kunit-platform", -1);
* KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdev);
* KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_add(test, pdev));
*
* pdrv = kunit_kzalloc(test, sizeof(*pdrv), GFP_KERNEL);
* KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pdrv);
*
* pdrv->probe = kunit_platform_driver_probe;
* pdrv->driver.name = "kunit-platform";
* pdrv->driver.owner = THIS_MODULE;
*
* KUNIT_ASSERT_EQ(test, 0, kunit_platform_device_prepare_wait_for_probe(test, pdev, &comp));
* KUNIT_ASSERT_EQ(test, 0, kunit_platform_driver_register(test, pdrv));
*
* KUNIT_EXPECT_NE(test, 0, wait_for_completion_timeout(&comp, 3 * HZ));
* }
*
* Return: 0 on success, negative errno on failure.
*/
int kunit_platform_device_prepare_wait_for_probe(struct kunit *test,
struct platform_device *pdev,
struct completion *x)
{
struct device *dev = &pdev->dev;
struct kunit_platform_device_probe_nb *knb;
bool bound;
knb = kunit_kzalloc(test, sizeof(*knb), GFP_KERNEL);
if (!knb)
return -ENOMEM;
knb->nb.notifier_call = kunit_platform_device_probe_notify;
knb->dev = dev;
knb->x = x;
device_lock(dev);
bound = device_is_bound(dev);
if (bound) {
device_unlock(dev);
complete(x);
kunit_kfree(test, knb);
return 0;
}
bus_register_notifier(&platform_bus_type, &knb->nb);
device_unlock(&pdev->dev);
return kunit_add_action_or_reset(test, kunit_platform_device_probe_nb_remove, &knb->nb);
}
EXPORT_SYMBOL_GPL(kunit_platform_device_prepare_wait_for_probe);
KUNIT_DEFINE_ACTION_WRAPPER(platform_driver_unregister_wrapper,
platform_driver_unregister, struct platform_driver *);
/**
* kunit_platform_driver_register() - Register a KUnit test managed platform driver
* @test: test context
* @drv: platform driver to register
*
* Register a test managed platform driver. This allows callers to embed the
* @drv in a container structure and use container_of() in the probe function
* to pass information to KUnit tests.
*
* Example
*
* .. code-block:: c
*
* struct kunit_test_context {
* struct platform_driver pdrv;
* const char *data;
* };
*
* static inline struct kunit_test_context *
* to_test_context(struct platform_device *pdev)
* {
* return container_of(to_platform_driver(pdev->dev.driver),
* struct kunit_test_context,
* pdrv);
* }
*
* static int kunit_platform_driver_probe(struct platform_device *pdev)
* {
* struct kunit_test_context *ctx;
*
* ctx = to_test_context(pdev);
* ctx->data = "test data";
*
* return 0;
* }
*
* static void kunit_platform_driver_test(struct kunit *test)
* {
* struct kunit_test_context *ctx;
*
* ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
* KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
*
* ctx->pdrv.probe = kunit_platform_driver_probe;
* ctx->pdrv.driver.name = "kunit-platform";
* ctx->pdrv.driver.owner = THIS_MODULE;
*
* KUNIT_EXPECT_EQ(test, 0, kunit_platform_driver_register(test, &ctx->pdrv));
* <... wait for driver to probe ...>
* KUNIT_EXPECT_STREQ(test, ctx->data, "test data");
* }
*
* Return: 0 on success, negative errno on failure.
*/
int kunit_platform_driver_register(struct kunit *test,
struct platform_driver *drv)
{
int ret;
ret = platform_driver_register(drv);
if (ret)
return ret;
return kunit_add_action_or_reset(test, platform_driver_unregister_wrapper, drv);
}
EXPORT_SYMBOL_GPL(kunit_platform_driver_register);