PM: runtime: Add basic kunit tests for API contracts

In exploring the various return codes and failure modes of runtime PM
APIs, I found it helpful to verify and codify many of them in unit
tests, especially given that even the kerneldoc can be rather complex to
reason through, and it also has had subtle errors of its own.

Notably, I avoid testing the return codes for pm_runtime_put() and
pm_runtime_put_autosuspend(), since code that checks them is probably
wrong, and we're considering making them return 'void' altogether. I
still test the sync() variants, since those have a bit more meaning to
them.

Signed-off-by: Brian Norris <briannorris@chromium.org>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
This commit is contained in:
Brian Norris 2025-09-25 12:42:14 -07:00 committed by Rafael J. Wysocki
parent 927f3e8501
commit 7f7acd193b
3 changed files with 260 additions and 0 deletions

View file

@ -167,6 +167,12 @@ config PM_QOS_KUNIT_TEST
depends on KUNIT=y
default KUNIT_ALL_TESTS
config PM_RUNTIME_KUNIT_TEST
tristate "KUnit Tests for runtime PM" if !KUNIT_ALL_TESTS
depends on KUNIT
depends on PM
default KUNIT_ALL_TESTS
config HMEM_REPORTING
bool
default n

View file

@ -4,5 +4,6 @@ obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o wakeup_stats.o
obj-$(CONFIG_PM_TRACE_RTC) += trace.o
obj-$(CONFIG_HAVE_CLK) += clock_ops.o
obj-$(CONFIG_PM_QOS_KUNIT_TEST) += qos-test.o
obj-$(CONFIG_PM_RUNTIME_KUNIT_TEST) += runtime-test.o
ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG

View file

@ -0,0 +1,253 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2025 Google, Inc.
*/
#include <linux/cleanup.h>
#include <linux/pm_runtime.h>
#include <kunit/device.h>
#include <kunit/test.h>
#define DEVICE_NAME "pm_runtime_test_device"
static void pm_runtime_depth_test(struct kunit *test)
{
struct device *dev = kunit_device_register(test, DEVICE_NAME);
KUNIT_ASSERT_PTR_NE(test, NULL, dev);
pm_runtime_enable(dev);
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
KUNIT_EXPECT_EQ(test, 0, pm_runtime_get_sync(dev));
KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
KUNIT_EXPECT_EQ(test, 1, pm_runtime_get_sync(dev)); /* "already active" */
KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_sync(dev));
KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_sync(dev));
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
}
/* Test pm_runtime_put() and friends when already suspended. */
static void pm_runtime_already_suspended_test(struct kunit *test)
{
struct device *dev = kunit_device_register(test, DEVICE_NAME);
KUNIT_ASSERT_PTR_NE(test, NULL, dev);
pm_runtime_enable(dev);
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
pm_runtime_get_noresume(dev);
KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
pm_runtime_put(dev);
pm_runtime_get_noresume(dev);
KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put_sync(dev));
KUNIT_EXPECT_EQ(test, 1, pm_runtime_suspend(dev));
KUNIT_EXPECT_EQ(test, 1, pm_runtime_autosuspend(dev));
KUNIT_EXPECT_EQ(test, 1, pm_request_autosuspend(dev));
pm_runtime_get_noresume(dev);
KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync_autosuspend(dev));
pm_runtime_get_noresume(dev);
pm_runtime_put_autosuspend(dev);
/* Grab 2 refcounts */
pm_runtime_get_noresume(dev);
pm_runtime_get_noresume(dev);
/* The first put() sees usage_count 1 */
KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_sync_autosuspend(dev));
/* The second put() sees usage_count 0 but tells us "already suspended". */
KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync_autosuspend(dev));
/* Should have remained suspended the whole time. */
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
}
static void pm_runtime_idle_test(struct kunit *test)
{
struct device *dev = kunit_device_register(test, DEVICE_NAME);
KUNIT_ASSERT_PTR_NE(test, NULL, dev);
pm_runtime_enable(dev);
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
KUNIT_EXPECT_EQ(test, 0, pm_runtime_get_sync(dev));
KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_idle(dev));
KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
pm_runtime_put_noidle(dev);
KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
KUNIT_EXPECT_EQ(test, 0, pm_runtime_idle(dev));
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_idle(dev));
KUNIT_EXPECT_EQ(test, -EAGAIN, pm_request_idle(dev));
}
static void pm_runtime_disabled_test(struct kunit *test)
{
struct device *dev = kunit_device_register(test, DEVICE_NAME);
KUNIT_ASSERT_PTR_NE(test, NULL, dev);
/* Never called pm_runtime_enable() */
KUNIT_EXPECT_FALSE(test, pm_runtime_enabled(dev));
/* "disabled" is treated as "active" */
KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
KUNIT_EXPECT_FALSE(test, pm_runtime_suspended(dev));
/*
* Note: these "fail", but they still acquire/release refcounts, so
* keep them balanced.
*/
KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_get(dev));
pm_runtime_put(dev);
KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_get_sync(dev));
KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_put_sync(dev));
KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_get(dev));
pm_runtime_put_autosuspend(dev);
KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_resume_and_get(dev));
KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_idle(dev));
KUNIT_EXPECT_EQ(test, -EACCES, pm_request_idle(dev));
KUNIT_EXPECT_EQ(test, -EACCES, pm_request_resume(dev));
KUNIT_EXPECT_EQ(test, -EACCES, pm_request_autosuspend(dev));
KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_suspend(dev));
KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_resume(dev));
KUNIT_EXPECT_EQ(test, -EACCES, pm_runtime_autosuspend(dev));
/* Still disabled */
KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
KUNIT_EXPECT_FALSE(test, pm_runtime_enabled(dev));
}
static void pm_runtime_error_test(struct kunit *test)
{
struct device *dev = kunit_device_register(test, DEVICE_NAME);
KUNIT_ASSERT_PTR_NE(test, NULL, dev);
pm_runtime_enable(dev);
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
/* Fake a .runtime_resume() error */
dev->power.runtime_error = -EIO;
/*
* Note: these "fail", but they still acquire/release refcounts, so
* keep them balanced.
*/
KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_get(dev));
pm_runtime_put(dev);
KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_get_sync(dev));
KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_put_sync(dev));
KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_get(dev));
pm_runtime_put_autosuspend(dev);
KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_get(dev));
KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_put_sync_autosuspend(dev));
KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_resume_and_get(dev));
KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_idle(dev));
KUNIT_EXPECT_EQ(test, -EINVAL, pm_request_idle(dev));
KUNIT_EXPECT_EQ(test, -EINVAL, pm_request_resume(dev));
KUNIT_EXPECT_EQ(test, -EINVAL, pm_request_autosuspend(dev));
KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_suspend(dev));
KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_resume(dev));
KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_autosuspend(dev));
/* Error is still pending */
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
KUNIT_EXPECT_EQ(test, -EIO, dev->power.runtime_error);
/* Clear error */
KUNIT_EXPECT_EQ(test, 0, pm_runtime_set_suspended(dev));
KUNIT_EXPECT_EQ(test, 0, dev->power.runtime_error);
/* Still suspended */
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
KUNIT_EXPECT_EQ(test, 0, pm_runtime_get(dev));
KUNIT_EXPECT_EQ(test, 1, pm_runtime_barrier(dev)); /* resume was pending */
pm_runtime_put(dev);
pm_runtime_suspend(dev); /* flush the put(), to suspend */
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
KUNIT_EXPECT_EQ(test, 0, pm_runtime_get_sync(dev));
KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_sync(dev));
KUNIT_EXPECT_EQ(test, 0, pm_runtime_get_sync(dev));
pm_runtime_put_autosuspend(dev);
KUNIT_EXPECT_EQ(test, 0, pm_runtime_resume_and_get(dev));
/*
* The following should all return -EAGAIN (usage is non-zero) or 1
* (already resumed).
*/
KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_idle(dev));
KUNIT_EXPECT_EQ(test, -EAGAIN, pm_request_idle(dev));
KUNIT_EXPECT_EQ(test, 1, pm_request_resume(dev));
KUNIT_EXPECT_EQ(test, -EAGAIN, pm_request_autosuspend(dev));
KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_suspend(dev));
KUNIT_EXPECT_EQ(test, 1, pm_runtime_resume(dev));
KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_autosuspend(dev));
KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_sync(dev));
/* Suspended again */
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
}
/*
* Explore a typical probe() sequence in which a device marks itself powered,
* but doesn't hold any runtime PM reference, so it suspends as soon as it goes
* idle.
*/
static void pm_runtime_probe_active_test(struct kunit *test)
{
struct device *dev = kunit_device_register(test, DEVICE_NAME);
KUNIT_ASSERT_PTR_NE(test, NULL, dev);
KUNIT_EXPECT_TRUE(test, pm_runtime_status_suspended(dev));
KUNIT_EXPECT_EQ(test, 0, pm_runtime_set_active(dev));
KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
pm_runtime_enable(dev);
KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
/* Nothing to flush. We stay active. */
KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev));
KUNIT_EXPECT_TRUE(test, pm_runtime_active(dev));
/* Ask for idle? Now we suspend. */
KUNIT_EXPECT_EQ(test, 0, pm_runtime_idle(dev));
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
}
static struct kunit_case pm_runtime_test_cases[] = {
KUNIT_CASE(pm_runtime_depth_test),
KUNIT_CASE(pm_runtime_already_suspended_test),
KUNIT_CASE(pm_runtime_idle_test),
KUNIT_CASE(pm_runtime_disabled_test),
KUNIT_CASE(pm_runtime_error_test),
KUNIT_CASE(pm_runtime_probe_active_test),
{}
};
static struct kunit_suite pm_runtime_test_suite = {
.name = "pm_runtime_test_cases",
.test_cases = pm_runtime_test_cases,
};
kunit_test_suite(pm_runtime_test_suite);
MODULE_DESCRIPTION("Runtime power management unit test suite");
MODULE_LICENSE("GPL");