* [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts
@ 2025-08-29 0:28 Brian Norris
2025-08-29 0:28 ` [PATCH 2/3] PM: runtime: Make put{,_sync}() return 1 when already suspended Brian Norris
` (2 more replies)
0 siblings, 3 replies; 15+ messages in thread
From: Brian Norris @ 2025-08-29 0:28 UTC (permalink / raw)
To: Rafael J. Wysocki, Pavel Machek
Cc: Rafael J . Wysocki, kunit-dev, Len Brown, Sakari Ailus, linux-pm,
linux-kernel, Brian Norris
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.
Signed-off-by: Brian Norris <briannorris@chromium.org>
---
drivers/base/Kconfig | 6 +
drivers/base/power/Makefile | 1 +
drivers/base/power/runtime-test.c | 259 ++++++++++++++++++++++++++++++
3 files changed, 266 insertions(+)
create mode 100644 drivers/base/power/runtime-test.c
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index 064eb52ff7e2..1786d87b29e2 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -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
diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
index 01f11629d241..2989e42d0161 100644
--- a/drivers/base/power/Makefile
+++ b/drivers/base/power/Makefile
@@ -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
diff --git a/drivers/base/power/runtime-test.c b/drivers/base/power/runtime-test.c
new file mode 100644
index 000000000000..263c28d5fc50
--- /dev/null
+++ b/drivers/base/power/runtime-test.c
@@ -0,0 +1,259 @@
+// 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);
+
+ /* Flush, in case the above (non-sync) triggered any work. */
+ KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
+
+ KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
+ /*
+ * We never actually left RPM_SUSPENDED, but rpm_idle() still treats
+ * this as -EAGAIN / "runtime PM status change ongoing".
+ */
+ KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put(dev));
+
+ pm_runtime_get_noresume(dev);
+ KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(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_TRUE(test, pm_runtime_suspended(dev));
+ KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync_autosuspend(dev));
+
+ pm_runtime_get_noresume(dev);
+ KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
+ KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_autosuspend(dev));
+
+ /* Grab 2 refcounts */
+ pm_runtime_get_noresume(dev);
+ pm_runtime_get_noresume(dev);
+ KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
+ /* The first put() sees usage_count 1 */
+ KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_autosuspend(dev));
+ /* The second put() sees usage_count 0 but tells us "already suspended". */
+ KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_autosuspend(dev));
+
+ 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));
+ KUNIT_EXPECT_EQ(test, -EACCES, 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));
+ KUNIT_EXPECT_EQ(test, -EACCES, 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 active */
+ KUNIT_EXPECT_TRUE(test, pm_runtime_active(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));
+ KUNIT_EXPECT_EQ(test, -EINVAL, 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));
+ KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_put_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));
+
+ /* Still suspended */
+ KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
+ /* 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 */
+ KUNIT_EXPECT_EQ(test, 0, 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));
+ KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_autosuspend(dev));
+
+ KUNIT_EXPECT_EQ(test, 0, pm_runtime_resume_and_get(dev));
+
+ /*
+ * The following should all "fail" with -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));
+
+ /* Flush, and ensure we stayed 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");
--
2.51.0.318.gd7df087d1a-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 2/3] PM: runtime: Make put{,_sync}() return 1 when already suspended
2025-08-29 0:28 [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts Brian Norris
@ 2025-08-29 0:28 ` Brian Norris
2025-09-02 7:25 ` Sakari Ailus
2025-09-24 9:48 ` Dhruva Gole
2025-08-29 0:28 ` [PATCH 3/3] PM: runtime: Update kerneldoc return codes Brian Norris
2025-09-05 17:37 ` [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts Rafael J. Wysocki
2 siblings, 2 replies; 15+ messages in thread
From: Brian Norris @ 2025-08-29 0:28 UTC (permalink / raw)
To: Rafael J. Wysocki, Pavel Machek
Cc: Rafael J . Wysocki, kunit-dev, Len Brown, Sakari Ailus, linux-pm,
linux-kernel, Brian Norris
The pm_runtime.h docs say pm_runtime_put() and pm_runtime_put_sync()
return 1 when already suspended, but this is not true -- they return
-EAGAIN. On the other hand, pm_runtime_put_sync_suspend() and
pm_runtime_put_sync_autosuspend() *do* return 1.
This is an artifact of the fact that the former are built on rpm_idle(),
whereas the latter are built on rpm_suspend().
There are precious few pm_runtime_put()/pm_runtime_put_sync() callers
that check the return code at all, but most of them only log errors, and
usually only for negative error codes. None of them should be treating
this as an error, so:
* at best, this may fix some case where a driver treats this condition
as an error, when it shouldn't;
* at worst, this should make no effect; and
* somewhere in between, we could potentially clear up non-fatal log
messages.
Fix the pm_runtime_already_suspended_test() while tweaking the behavior.
The test makes a lot more sense when these all return 1 when the device
is already suspended:
pm_runtime_put(dev);
pm_runtime_put_sync(dev);
pm_runtime_suspend(dev);
pm_runtime_autosuspend(dev);
pm_request_autosuspend(dev);
pm_runtime_put_sync_autosuspend(dev);
pm_runtime_put_autosuspend(dev);
Signed-off-by: Brian Norris <briannorris@chromium.org>
---
drivers/base/power/runtime-test.c | 8 ++------
drivers/base/power/runtime.c | 3 +++
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/base/power/runtime-test.c b/drivers/base/power/runtime-test.c
index 263c28d5fc50..1be18e871f1d 100644
--- a/drivers/base/power/runtime-test.c
+++ b/drivers/base/power/runtime-test.c
@@ -43,15 +43,11 @@ static void pm_runtime_already_suspended_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
- /*
- * We never actually left RPM_SUSPENDED, but rpm_idle() still treats
- * this as -EAGAIN / "runtime PM status change ongoing".
- */
- KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put(dev));
+ KUNIT_EXPECT_EQ(test, 1, pm_runtime_put(dev));
pm_runtime_get_noresume(dev);
KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
- KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put_sync(dev));
+ KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync(dev));
KUNIT_EXPECT_EQ(test, 1, pm_runtime_suspend(dev));
KUNIT_EXPECT_EQ(test, 1, pm_runtime_autosuspend(dev));
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
index 3e84dc4122de..17cf111d16aa 100644
--- a/drivers/base/power/runtime.c
+++ b/drivers/base/power/runtime.c
@@ -498,6 +498,9 @@ static int rpm_idle(struct device *dev, int rpmflags)
if (retval < 0)
; /* Conditions are wrong. */
+ else if ((rpmflags & RPM_GET_PUT) && (retval == 1))
+ ; /* put() is allowed in RPM_SUSPENDED */
+
/* Idle notifications are allowed only in the RPM_ACTIVE state. */
else if (dev->power.runtime_status != RPM_ACTIVE)
retval = -EAGAIN;
--
2.51.0.318.gd7df087d1a-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 3/3] PM: runtime: Update kerneldoc return codes
2025-08-29 0:28 [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts Brian Norris
2025-08-29 0:28 ` [PATCH 2/3] PM: runtime: Make put{,_sync}() return 1 when already suspended Brian Norris
@ 2025-08-29 0:28 ` Brian Norris
2025-09-02 7:18 ` Sakari Ailus
2025-09-05 17:37 ` [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts Rafael J. Wysocki
2 siblings, 1 reply; 15+ messages in thread
From: Brian Norris @ 2025-08-29 0:28 UTC (permalink / raw)
To: Rafael J. Wysocki, Pavel Machek
Cc: Rafael J . Wysocki, kunit-dev, Len Brown, Sakari Ailus, linux-pm,
linux-kernel, Brian Norris
APIs based on __pm_runtime_idle() (pm_runtime_idle(), pm_request_idle())
do not return 1 when already suspended. They return -EAGAIN. This is
already covered in the docs, so the entry for "1" is redundant and
conflicting.
(pm_runtime_put() and pm_runtime_put_sync() were previously incorrect,
but that's fixed in "PM: runtime: pm_runtime_put{,_sync}() returns 1
when already suspended", to ensure consistency with APIs like
pm_runtime_put_autosuspend().)
RPM_GET_PUT APIs based on __pm_runtime_suspend() do return 1 when
already suspended, but the language is a little unclear -- it's not
really an "error", so it seems better to list as a clarification before
the 0/success case. Additionally, they only actually return 1 when the
refcount makes it to 0; if the usage_count is still non-zero, we return
0.
pm_runtime_put(), etc., also don't appear at first like they can ever
see "-EAGAIN: Runtime PM usage_count non-zero", because in non-racy
conditions, pm_runtime_put() would drop its reference count, see it's
non-zero, and return early (in __pm_runtime_idle()). However, it's
possible to race with another actor that increments the usage_count
afterward, since rpm_idle() is protected by a separate lock; in such a
case, we may see -EAGAIN.
Because this case is only seen in the presence of concurrent actors, it
makes sense to clarify that this is when "usage_count **became**
non-zero", by way of some racing actor.
Lastly, pm_runtime_put_sync_suspend() duplicated some -EAGAIN language.
Fix that.
Fixes: 271ff96d6066 ("PM: runtime: Document return values of suspend-related API functions")
Link: https://lore.kernel.org/linux-pm/aJ5pkEJuixTaybV4@google.com/
Signed-off-by: Brian Norris <briannorris@chromium.org>
---
include/linux/pm_runtime.h | 33 ++++++++++++++++++---------------
1 file changed, 18 insertions(+), 15 deletions(-)
diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h
index d88d6b6ccf5b..fd17ffe1bc79 100644
--- a/include/linux/pm_runtime.h
+++ b/include/linux/pm_runtime.h
@@ -356,7 +356,6 @@ static inline int pm_runtime_force_resume(struct device *dev) { return -ENXIO; }
* * -EPERM: Device PM QoS resume latency 0.
* * -EINPROGRESS: Suspend already in progress.
* * -ENOSYS: CONFIG_PM not enabled.
- * * 1: Device already suspended.
* Other values and conditions for the above values are possible as returned by
* Runtime PM idle and suspend callbacks.
*/
@@ -439,7 +438,6 @@ static inline int pm_runtime_resume(struct device *dev)
* * -EPERM: Device PM QoS resume latency 0.
* * -EINPROGRESS: Suspend already in progress.
* * -ENOSYS: CONFIG_PM not enabled.
- * * 1: Device already suspended.
*/
static inline int pm_request_idle(struct device *dev)
{
@@ -540,15 +538,16 @@ static inline int pm_runtime_resume_and_get(struct device *dev)
* equal to 0, queue up a work item for @dev like in pm_request_idle().
*
* Return:
+ * * 1: Usage counts dropped to zero, but device was already suspended.
* * 0: Success.
* * -EINVAL: Runtime PM error.
* * -EACCES: Runtime PM disabled.
- * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
+ * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
+ * change ongoing.
* * -EBUSY: Runtime PM child_count non-zero.
* * -EPERM: Device PM QoS resume latency 0.
* * -EINPROGRESS: Suspend already in progress.
* * -ENOSYS: CONFIG_PM not enabled.
- * * 1: Device already suspended.
*/
static inline int pm_runtime_put(struct device *dev)
{
@@ -565,15 +564,16 @@ DEFINE_FREE(pm_runtime_put, struct device *, if (_T) pm_runtime_put(_T))
* equal to 0, queue up a work item for @dev like in pm_request_autosuspend().
*
* Return:
+ * * 1: Usage counts dropped to zero, but device was already suspended.
* * 0: Success.
* * -EINVAL: Runtime PM error.
* * -EACCES: Runtime PM disabled.
- * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
+ * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
+ * change ongoing.
* * -EBUSY: Runtime PM child_count non-zero.
* * -EPERM: Device PM QoS resume latency 0.
* * -EINPROGRESS: Suspend already in progress.
* * -ENOSYS: CONFIG_PM not enabled.
- * * 1: Device already suspended.
*/
static inline int __pm_runtime_put_autosuspend(struct device *dev)
{
@@ -590,15 +590,16 @@ static inline int __pm_runtime_put_autosuspend(struct device *dev)
* in pm_request_autosuspend().
*
* Return:
+ * * 1: Usage counts dropped to zero, but device was already suspended.
* * 0: Success.
* * -EINVAL: Runtime PM error.
* * -EACCES: Runtime PM disabled.
- * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
+ * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
+ * change ongoing.
* * -EBUSY: Runtime PM child_count non-zero.
* * -EPERM: Device PM QoS resume latency 0.
* * -EINPROGRESS: Suspend already in progress.
* * -ENOSYS: CONFIG_PM not enabled.
- * * 1: Device already suspended.
*/
static inline int pm_runtime_put_autosuspend(struct device *dev)
{
@@ -619,14 +620,15 @@ static inline int pm_runtime_put_autosuspend(struct device *dev)
* if it returns an error code.
*
* Return:
+ * * 1: Usage counts dropped to zero, but device was already suspended.
* * 0: Success.
* * -EINVAL: Runtime PM error.
* * -EACCES: Runtime PM disabled.
- * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
+ * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
+ * change ongoing.
* * -EBUSY: Runtime PM child_count non-zero.
* * -EPERM: Device PM QoS resume latency 0.
* * -ENOSYS: CONFIG_PM not enabled.
- * * 1: Device already suspended.
* Other values and conditions for the above values are possible as returned by
* Runtime PM suspend callbacks.
*/
@@ -646,15 +648,15 @@ static inline int pm_runtime_put_sync(struct device *dev)
* if it returns an error code.
*
* Return:
+ * * 1: Usage counts dropped to zero, but device was already suspended.
* * 0: Success.
* * -EINVAL: Runtime PM error.
* * -EACCES: Runtime PM disabled.
- * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
- * * -EAGAIN: usage_count non-zero or Runtime PM status change ongoing.
+ * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
+ * change ongoing.
* * -EBUSY: Runtime PM child_count non-zero.
* * -EPERM: Device PM QoS resume latency 0.
* * -ENOSYS: CONFIG_PM not enabled.
- * * 1: Device already suspended.
* Other values and conditions for the above values are possible as returned by
* Runtime PM suspend callbacks.
*/
@@ -677,15 +679,16 @@ static inline int pm_runtime_put_sync_suspend(struct device *dev)
* if it returns an error code.
*
* Return:
+ * * 1: Usage counts dropped to zero, but device was already suspended.
* * 0: Success.
* * -EINVAL: Runtime PM error.
* * -EACCES: Runtime PM disabled.
- * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
+ * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
+ * change ongoing.
* * -EBUSY: Runtime PM child_count non-zero.
* * -EPERM: Device PM QoS resume latency 0.
* * -EINPROGRESS: Suspend already in progress.
* * -ENOSYS: CONFIG_PM not enabled.
- * * 1: Device already suspended.
* Other values and conditions for the above values are possible as returned by
* Runtime PM suspend callbacks.
*/
--
2.51.0.318.gd7df087d1a-goog
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH 3/3] PM: runtime: Update kerneldoc return codes
2025-08-29 0:28 ` [PATCH 3/3] PM: runtime: Update kerneldoc return codes Brian Norris
@ 2025-09-02 7:18 ` Sakari Ailus
2025-09-05 17:54 ` Rafael J. Wysocki
0 siblings, 1 reply; 15+ messages in thread
From: Sakari Ailus @ 2025-09-02 7:18 UTC (permalink / raw)
To: Brian Norris
Cc: Rafael J. Wysocki, Pavel Machek, Rafael J . Wysocki, kunit-dev,
Len Brown, linux-pm, linux-kernel
Hi Brian,
Thanks for posting this. A few comments below.
On Thu, Aug 28, 2025 at 05:28:28PM -0700, Brian Norris wrote:
> APIs based on __pm_runtime_idle() (pm_runtime_idle(), pm_request_idle())
> do not return 1 when already suspended. They return -EAGAIN. This is
> already covered in the docs, so the entry for "1" is redundant and
> conflicting.
>
> (pm_runtime_put() and pm_runtime_put_sync() were previously incorrect,
> but that's fixed in "PM: runtime: pm_runtime_put{,_sync}() returns 1
> when already suspended", to ensure consistency with APIs like
> pm_runtime_put_autosuspend().)
>
> RPM_GET_PUT APIs based on __pm_runtime_suspend() do return 1 when
> already suspended, but the language is a little unclear -- it's not
> really an "error", so it seems better to list as a clarification before
> the 0/success case. Additionally, they only actually return 1 when the
> refcount makes it to 0; if the usage_count is still non-zero, we return
> 0.
>
> pm_runtime_put(), etc., also don't appear at first like they can ever
> see "-EAGAIN: Runtime PM usage_count non-zero", because in non-racy
> conditions, pm_runtime_put() would drop its reference count, see it's
> non-zero, and return early (in __pm_runtime_idle()). However, it's
> possible to race with another actor that increments the usage_count
> afterward, since rpm_idle() is protected by a separate lock; in such a
> case, we may see -EAGAIN.
>
> Because this case is only seen in the presence of concurrent actors, it
> makes sense to clarify that this is when "usage_count **became**
> non-zero", by way of some racing actor.
>
> Lastly, pm_runtime_put_sync_suspend() duplicated some -EAGAIN language.
> Fix that.
>
> Fixes: 271ff96d6066 ("PM: runtime: Document return values of suspend-related API functions")
> Link: https://lore.kernel.org/linux-pm/aJ5pkEJuixTaybV4@google.com/
> Signed-off-by: Brian Norris <briannorris@chromium.org>
> ---
>
> include/linux/pm_runtime.h | 33 ++++++++++++++++++---------------
> 1 file changed, 18 insertions(+), 15 deletions(-)
>
> diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h
> index d88d6b6ccf5b..fd17ffe1bc79 100644
> --- a/include/linux/pm_runtime.h
> +++ b/include/linux/pm_runtime.h
> @@ -356,7 +356,6 @@ static inline int pm_runtime_force_resume(struct device *dev) { return -ENXIO; }
> * * -EPERM: Device PM QoS resume latency 0.
> * * -EINPROGRESS: Suspend already in progress.
> * * -ENOSYS: CONFIG_PM not enabled.
> - * * 1: Device already suspended.
> * Other values and conditions for the above values are possible as returned by
> * Runtime PM idle and suspend callbacks.
> */
> @@ -439,7 +438,6 @@ static inline int pm_runtime_resume(struct device *dev)
> * * -EPERM: Device PM QoS resume latency 0.
> * * -EINPROGRESS: Suspend already in progress.
> * * -ENOSYS: CONFIG_PM not enabled.
> - * * 1: Device already suspended.
> */
> static inline int pm_request_idle(struct device *dev)
> {
> @@ -540,15 +538,16 @@ static inline int pm_runtime_resume_and_get(struct device *dev)
> * equal to 0, queue up a work item for @dev like in pm_request_idle().
> *
> * Return:
> + * * 1: Usage counts dropped to zero, but device was already suspended.
Does this actually happen? pm_runtime_put() calls __pm_runtime_idle() that
doesn't appear to return 1 in any case.
> * * 0: Success.
> * * -EINVAL: Runtime PM error.
> * * -EACCES: Runtime PM disabled.
> - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> + * change ongoing.
> * * -EBUSY: Runtime PM child_count non-zero.
> * * -EPERM: Device PM QoS resume latency 0.
> * * -EINPROGRESS: Suspend already in progress.
> * * -ENOSYS: CONFIG_PM not enabled.
> - * * 1: Device already suspended.
> */
> static inline int pm_runtime_put(struct device *dev)
> {
> @@ -565,15 +564,16 @@ DEFINE_FREE(pm_runtime_put, struct device *, if (_T) pm_runtime_put(_T))
> * equal to 0, queue up a work item for @dev like in pm_request_autosuspend().
> *
> * Return:
> + * * 1: Usage counts dropped to zero, but device was already suspended.
> * * 0: Success.
"usage_count" and "usage counter" is being used in kernel-doc already, I'd
use either of the two. "usage_count" refers directly to the field in struct
dev_pm_info (and is used a few lines below). Same elsewhere.
> * * -EINVAL: Runtime PM error.
> * * -EACCES: Runtime PM disabled.
> - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> + * change ongoing.
> * * -EBUSY: Runtime PM child_count non-zero.
> * * -EPERM: Device PM QoS resume latency 0.
> * * -EINPROGRESS: Suspend already in progress.
> * * -ENOSYS: CONFIG_PM not enabled.
> - * * 1: Device already suspended.
> */
> static inline int __pm_runtime_put_autosuspend(struct device *dev)
> {
> @@ -590,15 +590,16 @@ static inline int __pm_runtime_put_autosuspend(struct device *dev)
> * in pm_request_autosuspend().
> *
> * Return:
> + * * 1: Usage counts dropped to zero, but device was already suspended.
> * * 0: Success.
> * * -EINVAL: Runtime PM error.
> * * -EACCES: Runtime PM disabled.
> - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> + * change ongoing.
> * * -EBUSY: Runtime PM child_count non-zero.
> * * -EPERM: Device PM QoS resume latency 0.
> * * -EINPROGRESS: Suspend already in progress.
> * * -ENOSYS: CONFIG_PM not enabled.
> - * * 1: Device already suspended.
> */
> static inline int pm_runtime_put_autosuspend(struct device *dev)
> {
> @@ -619,14 +620,15 @@ static inline int pm_runtime_put_autosuspend(struct device *dev)
> * if it returns an error code.
> *
> * Return:
> + * * 1: Usage counts dropped to zero, but device was already suspended.
> * * 0: Success.
Does this happen (pm_runtime_put_sync() calls __pm_runtime_idle())?
> * * -EINVAL: Runtime PM error.
> * * -EACCES: Runtime PM disabled.
> - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> + * change ongoing.
> * * -EBUSY: Runtime PM child_count non-zero.
> * * -EPERM: Device PM QoS resume latency 0.
> * * -ENOSYS: CONFIG_PM not enabled.
> - * * 1: Device already suspended.
> * Other values and conditions for the above values are possible as returned by
> * Runtime PM suspend callbacks.
> */
> @@ -646,15 +648,15 @@ static inline int pm_runtime_put_sync(struct device *dev)
> * if it returns an error code.
> *
> * Return:
> + * * 1: Usage counts dropped to zero, but device was already suspended.
> * * 0: Success.
> * * -EINVAL: Runtime PM error.
> * * -EACCES: Runtime PM disabled.
> - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> - * * -EAGAIN: usage_count non-zero or Runtime PM status change ongoing.
> + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> + * change ongoing.
> * * -EBUSY: Runtime PM child_count non-zero.
> * * -EPERM: Device PM QoS resume latency 0.
> * * -ENOSYS: CONFIG_PM not enabled.
> - * * 1: Device already suspended.
> * Other values and conditions for the above values are possible as returned by
> * Runtime PM suspend callbacks.
> */
> @@ -677,15 +679,16 @@ static inline int pm_runtime_put_sync_suspend(struct device *dev)
> * if it returns an error code.
> *
> * Return:
> + * * 1: Usage counts dropped to zero, but device was already suspended.
> * * 0: Success.
> * * -EINVAL: Runtime PM error.
> * * -EACCES: Runtime PM disabled.
> - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> + * change ongoing.
> * * -EBUSY: Runtime PM child_count non-zero.
> * * -EPERM: Device PM QoS resume latency 0.
> * * -EINPROGRESS: Suspend already in progress.
> * * -ENOSYS: CONFIG_PM not enabled.
> - * * 1: Device already suspended.
> * Other values and conditions for the above values are possible as returned by
> * Runtime PM suspend callbacks.
> */
--
Kind regards,
Sakari Ailus
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] PM: runtime: Make put{,_sync}() return 1 when already suspended
2025-08-29 0:28 ` [PATCH 2/3] PM: runtime: Make put{,_sync}() return 1 when already suspended Brian Norris
@ 2025-09-02 7:25 ` Sakari Ailus
2025-09-05 17:42 ` Rafael J. Wysocki
2025-09-24 9:48 ` Dhruva Gole
1 sibling, 1 reply; 15+ messages in thread
From: Sakari Ailus @ 2025-09-02 7:25 UTC (permalink / raw)
To: Brian Norris
Cc: Rafael J. Wysocki, Pavel Machek, Rafael J . Wysocki, kunit-dev,
Len Brown, linux-pm, linux-kernel
Hi Brian,
On Thu, Aug 28, 2025 at 05:28:27PM -0700, Brian Norris wrote:
> The pm_runtime.h docs say pm_runtime_put() and pm_runtime_put_sync()
> return 1 when already suspended, but this is not true -- they return
> -EAGAIN. On the other hand, pm_runtime_put_sync_suspend() and
> pm_runtime_put_sync_autosuspend() *do* return 1.
>
> This is an artifact of the fact that the former are built on rpm_idle(),
> whereas the latter are built on rpm_suspend().
>
> There are precious few pm_runtime_put()/pm_runtime_put_sync() callers
> that check the return code at all, but most of them only log errors, and
> usually only for negative error codes. None of them should be treating
> this as an error, so:
>
> * at best, this may fix some case where a driver treats this condition
> as an error, when it shouldn't;
>
> * at worst, this should make no effect; and
>
> * somewhere in between, we could potentially clear up non-fatal log
> messages.
>
> Fix the pm_runtime_already_suspended_test() while tweaking the behavior.
> The test makes a lot more sense when these all return 1 when the device
> is already suspended:
>
> pm_runtime_put(dev);
> pm_runtime_put_sync(dev);
> pm_runtime_suspend(dev);
> pm_runtime_autosuspend(dev);
> pm_request_autosuspend(dev);
> pm_runtime_put_sync_autosuspend(dev);
> pm_runtime_put_autosuspend(dev);
>
> Signed-off-by: Brian Norris <briannorris@chromium.org>
> ---
>
> drivers/base/power/runtime-test.c | 8 ++------
> drivers/base/power/runtime.c | 3 +++
> 2 files changed, 5 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/base/power/runtime-test.c b/drivers/base/power/runtime-test.c
> index 263c28d5fc50..1be18e871f1d 100644
> --- a/drivers/base/power/runtime-test.c
> +++ b/drivers/base/power/runtime-test.c
> @@ -43,15 +43,11 @@ static void pm_runtime_already_suspended_test(struct kunit *test)
> KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
>
> KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> - /*
> - * We never actually left RPM_SUSPENDED, but rpm_idle() still treats
> - * this as -EAGAIN / "runtime PM status change ongoing".
> - */
> - KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put(dev));
> + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put(dev));
>
> pm_runtime_get_noresume(dev);
> KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> - KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put_sync(dev));
> + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync(dev));
>
> KUNIT_EXPECT_EQ(test, 1, pm_runtime_suspend(dev));
> KUNIT_EXPECT_EQ(test, 1, pm_runtime_autosuspend(dev));
> diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
> index 3e84dc4122de..17cf111d16aa 100644
> --- a/drivers/base/power/runtime.c
> +++ b/drivers/base/power/runtime.c
> @@ -498,6 +498,9 @@ static int rpm_idle(struct device *dev, int rpmflags)
> if (retval < 0)
> ; /* Conditions are wrong. */
>
> + else if ((rpmflags & RPM_GET_PUT) && (retval == 1))
> + ; /* put() is allowed in RPM_SUSPENDED */
Ah, I missed this while reviewing the 3rd patch. Makes sense. Please ignore
my comments regarding the 3rd patch on whether the return value 1 is
applicable.
The latter parentheses are redundant (the former, too, actually, but the
compiler warns so let them be).
> +
> /* Idle notifications are allowed only in the RPM_ACTIVE state. */
> else if (dev->power.runtime_status != RPM_ACTIVE)
> retval = -EAGAIN;
--
Kind regards,
Sakari Ailus
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts
2025-08-29 0:28 [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts Brian Norris
2025-08-29 0:28 ` [PATCH 2/3] PM: runtime: Make put{,_sync}() return 1 when already suspended Brian Norris
2025-08-29 0:28 ` [PATCH 3/3] PM: runtime: Update kerneldoc return codes Brian Norris
@ 2025-09-05 17:37 ` Rafael J. Wysocki
2025-09-10 20:44 ` Brian Norris
2 siblings, 1 reply; 15+ messages in thread
From: Rafael J. Wysocki @ 2025-09-05 17:37 UTC (permalink / raw)
To: Brian Norris
Cc: Rafael J. Wysocki, Pavel Machek, Rafael J . Wysocki, kunit-dev,
Len Brown, Sakari Ailus, linux-pm, linux-kernel
On Fri, Aug 29, 2025 at 2:33 AM Brian Norris <briannorris@chromium.org> wrote:
>
> 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.
>
> Signed-off-by: Brian Norris <briannorris@chromium.org>
This is nice in general, but I have a couple of questions/comments (see below).
> ---
>
> drivers/base/Kconfig | 6 +
> drivers/base/power/Makefile | 1 +
> drivers/base/power/runtime-test.c | 259 ++++++++++++++++++++++++++++++
> 3 files changed, 266 insertions(+)
> create mode 100644 drivers/base/power/runtime-test.c
>
> diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
> index 064eb52ff7e2..1786d87b29e2 100644
> --- a/drivers/base/Kconfig
> +++ b/drivers/base/Kconfig
> @@ -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
> diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
> index 01f11629d241..2989e42d0161 100644
> --- a/drivers/base/power/Makefile
> +++ b/drivers/base/power/Makefile
> @@ -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
> diff --git a/drivers/base/power/runtime-test.c b/drivers/base/power/runtime-test.c
> new file mode 100644
> index 000000000000..263c28d5fc50
> --- /dev/null
> +++ b/drivers/base/power/runtime-test.c
> @@ -0,0 +1,259 @@
> +// 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);
> +
> + /* Flush, in case the above (non-sync) triggered any work. */
> + KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
Why do you run pm_runtime_barrier(dev) here? It is guaranteed that no
requests are pending at this point.
> +
> + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
This has already been tested above.
> + /*
> + * We never actually left RPM_SUSPENDED, but rpm_idle() still treats
> + * this as -EAGAIN / "runtime PM status change ongoing".
No, this means "Conditions are not suitable, but may change".
> + */
> + KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put(dev));
> +
> + pm_runtime_get_noresume(dev);
> + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
This has been tested already twice and why would it change?
> + 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_TRUE(test, pm_runtime_suspended(dev));
There's no way by which it could change above.
> + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync_autosuspend(dev));
> +
> + pm_runtime_get_noresume(dev);
> + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_autosuspend(dev));
> +
> + /* Grab 2 refcounts */
> + pm_runtime_get_noresume(dev);
> + pm_runtime_get_noresume(dev);
> + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> + /* The first put() sees usage_count 1 */
> + KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_autosuspend(dev));
> + /* The second put() sees usage_count 0 but tells us "already suspended". */
> + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_autosuspend(dev));
> +
> + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
Again, there is no way this can change in the whole test.
> +}
> +
> +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));
> + KUNIT_EXPECT_EQ(test, -EACCES, 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));
> + KUNIT_EXPECT_EQ(test, -EACCES, 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 active */
Still disabled rather.
> + KUNIT_EXPECT_TRUE(test, pm_runtime_active(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));
> + KUNIT_EXPECT_EQ(test, -EINVAL, 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));
> + KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_put_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));
> +
> + /* Still suspended */
Error is still pending.
> + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> + /* 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 */
> + KUNIT_EXPECT_EQ(test, 0, 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));
> + KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_autosuspend(dev));
> +
> + KUNIT_EXPECT_EQ(test, 0, pm_runtime_resume_and_get(dev));
> +
> + /*
> + * The following should all "fail" with -EAGAIN (usage is non-zero) or
> + * 1 (already resumed).
The return value of 1 doesn't count as a failure.
> + */
> + 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));
> +
> + /* Flush, and ensure we stayed active. */
There's nothing to flush though.
> + 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");
> --
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] PM: runtime: Make put{,_sync}() return 1 when already suspended
2025-09-02 7:25 ` Sakari Ailus
@ 2025-09-05 17:42 ` Rafael J. Wysocki
0 siblings, 0 replies; 15+ messages in thread
From: Rafael J. Wysocki @ 2025-09-05 17:42 UTC (permalink / raw)
To: Sakari Ailus
Cc: Brian Norris, Rafael J. Wysocki, Pavel Machek, Rafael J . Wysocki,
kunit-dev, Len Brown, linux-pm, linux-kernel
On Tue, Sep 2, 2025 at 9:25 AM Sakari Ailus
<sakari.ailus@linux.intel.com> wrote:
>
> Hi Brian,
>
> On Thu, Aug 28, 2025 at 05:28:27PM -0700, Brian Norris wrote:
> > The pm_runtime.h docs say pm_runtime_put() and pm_runtime_put_sync()
> > return 1 when already suspended, but this is not true -- they return
> > -EAGAIN. On the other hand, pm_runtime_put_sync_suspend() and
> > pm_runtime_put_sync_autosuspend() *do* return 1.
> >
> > This is an artifact of the fact that the former are built on rpm_idle(),
> > whereas the latter are built on rpm_suspend().
> >
> > There are precious few pm_runtime_put()/pm_runtime_put_sync() callers
> > that check the return code at all, but most of them only log errors, and
> > usually only for negative error codes. None of them should be treating
> > this as an error, so:
> >
> > * at best, this may fix some case where a driver treats this condition
> > as an error, when it shouldn't;
> >
> > * at worst, this should make no effect; and
> >
> > * somewhere in between, we could potentially clear up non-fatal log
> > messages.
> >
> > Fix the pm_runtime_already_suspended_test() while tweaking the behavior.
> > The test makes a lot more sense when these all return 1 when the device
> > is already suspended:
> >
> > pm_runtime_put(dev);
> > pm_runtime_put_sync(dev);
> > pm_runtime_suspend(dev);
> > pm_runtime_autosuspend(dev);
> > pm_request_autosuspend(dev);
> > pm_runtime_put_sync_autosuspend(dev);
> > pm_runtime_put_autosuspend(dev);
> >
> > Signed-off-by: Brian Norris <briannorris@chromium.org>
> > ---
> >
> > drivers/base/power/runtime-test.c | 8 ++------
> > drivers/base/power/runtime.c | 3 +++
> > 2 files changed, 5 insertions(+), 6 deletions(-)
> >
> > diff --git a/drivers/base/power/runtime-test.c b/drivers/base/power/runtime-test.c
> > index 263c28d5fc50..1be18e871f1d 100644
> > --- a/drivers/base/power/runtime-test.c
> > +++ b/drivers/base/power/runtime-test.c
> > @@ -43,15 +43,11 @@ static void pm_runtime_already_suspended_test(struct kunit *test)
> > KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
> >
> > KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> > - /*
> > - * We never actually left RPM_SUSPENDED, but rpm_idle() still treats
> > - * this as -EAGAIN / "runtime PM status change ongoing".
> > - */
> > - KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put(dev));
> > + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put(dev));
> >
> > pm_runtime_get_noresume(dev);
> > KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> > - KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put_sync(dev));
> > + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync(dev));
> >
> > KUNIT_EXPECT_EQ(test, 1, pm_runtime_suspend(dev));
> > KUNIT_EXPECT_EQ(test, 1, pm_runtime_autosuspend(dev));
> > diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
> > index 3e84dc4122de..17cf111d16aa 100644
> > --- a/drivers/base/power/runtime.c
> > +++ b/drivers/base/power/runtime.c
> > @@ -498,6 +498,9 @@ static int rpm_idle(struct device *dev, int rpmflags)
> > if (retval < 0)
> > ; /* Conditions are wrong. */
> >
> > + else if ((rpmflags & RPM_GET_PUT) && (retval == 1))
> > + ; /* put() is allowed in RPM_SUSPENDED */
>
> Ah, I missed this while reviewing the 3rd patch. Makes sense. Please ignore
> my comments regarding the 3rd patch on whether the return value 1 is
> applicable.
>
> The latter parentheses are redundant (the former, too, actually, but the
> compiler warns so let them be).
Right.
But overall this should work.
> > +
> > /* Idle notifications are allowed only in the RPM_ACTIVE state. */
> > else if (dev->power.runtime_status != RPM_ACTIVE)
> > retval = -EAGAIN;
>
> --
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 3/3] PM: runtime: Update kerneldoc return codes
2025-09-02 7:18 ` Sakari Ailus
@ 2025-09-05 17:54 ` Rafael J. Wysocki
0 siblings, 0 replies; 15+ messages in thread
From: Rafael J. Wysocki @ 2025-09-05 17:54 UTC (permalink / raw)
To: Sakari Ailus
Cc: Brian Norris, Rafael J. Wysocki, Pavel Machek, Rafael J . Wysocki,
kunit-dev, Len Brown, linux-pm, linux-kernel
On Tue, Sep 2, 2025 at 9:18 AM Sakari Ailus
<sakari.ailus@linux.intel.com> wrote:
>
> Hi Brian,
>
> Thanks for posting this. A few comments below.
>
> On Thu, Aug 28, 2025 at 05:28:28PM -0700, Brian Norris wrote:
> > APIs based on __pm_runtime_idle() (pm_runtime_idle(), pm_request_idle())
> > do not return 1 when already suspended. They return -EAGAIN. This is
> > already covered in the docs, so the entry for "1" is redundant and
> > conflicting.
> >
> > (pm_runtime_put() and pm_runtime_put_sync() were previously incorrect,
> > but that's fixed in "PM: runtime: pm_runtime_put{,_sync}() returns 1
> > when already suspended", to ensure consistency with APIs like
> > pm_runtime_put_autosuspend().)
> >
> > RPM_GET_PUT APIs based on __pm_runtime_suspend() do return 1 when
> > already suspended, but the language is a little unclear -- it's not
> > really an "error", so it seems better to list as a clarification before
> > the 0/success case. Additionally, they only actually return 1 when the
> > refcount makes it to 0; if the usage_count is still non-zero, we return
> > 0.
> >
> > pm_runtime_put(), etc., also don't appear at first like they can ever
> > see "-EAGAIN: Runtime PM usage_count non-zero", because in non-racy
> > conditions, pm_runtime_put() would drop its reference count, see it's
> > non-zero, and return early (in __pm_runtime_idle()). However, it's
> > possible to race with another actor that increments the usage_count
> > afterward, since rpm_idle() is protected by a separate lock; in such a
> > case, we may see -EAGAIN.
> >
> > Because this case is only seen in the presence of concurrent actors, it
> > makes sense to clarify that this is when "usage_count **became**
> > non-zero", by way of some racing actor.
> >
> > Lastly, pm_runtime_put_sync_suspend() duplicated some -EAGAIN language.
> > Fix that.
> >
> > Fixes: 271ff96d6066 ("PM: runtime: Document return values of suspend-related API functions")
> > Link: https://lore.kernel.org/linux-pm/aJ5pkEJuixTaybV4@google.com/
> > Signed-off-by: Brian Norris <briannorris@chromium.org>
> > ---
> >
> > include/linux/pm_runtime.h | 33 ++++++++++++++++++---------------
> > 1 file changed, 18 insertions(+), 15 deletions(-)
> >
> > diff --git a/include/linux/pm_runtime.h b/include/linux/pm_runtime.h
> > index d88d6b6ccf5b..fd17ffe1bc79 100644
> > --- a/include/linux/pm_runtime.h
> > +++ b/include/linux/pm_runtime.h
> > @@ -356,7 +356,6 @@ static inline int pm_runtime_force_resume(struct device *dev) { return -ENXIO; }
> > * * -EPERM: Device PM QoS resume latency 0.
> > * * -EINPROGRESS: Suspend already in progress.
> > * * -ENOSYS: CONFIG_PM not enabled.
> > - * * 1: Device already suspended.
> > * Other values and conditions for the above values are possible as returned by
> > * Runtime PM idle and suspend callbacks.
> > */
> > @@ -439,7 +438,6 @@ static inline int pm_runtime_resume(struct device *dev)
> > * * -EPERM: Device PM QoS resume latency 0.
> > * * -EINPROGRESS: Suspend already in progress.
> > * * -ENOSYS: CONFIG_PM not enabled.
> > - * * 1: Device already suspended.
> > */
> > static inline int pm_request_idle(struct device *dev)
> > {
> > @@ -540,15 +538,16 @@ static inline int pm_runtime_resume_and_get(struct device *dev)
> > * equal to 0, queue up a work item for @dev like in pm_request_idle().
> > *
> > * Return:
> > + * * 1: Usage counts dropped to zero, but device was already suspended.
>
> Does this actually happen? pm_runtime_put() calls __pm_runtime_idle() that
> doesn't appear to return 1 in any case.
Except when it calls rpm_suspend() that may return 1.
> > * * 0: Success.
> > * * -EINVAL: Runtime PM error.
> > * * -EACCES: Runtime PM disabled.
> > - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> > + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> > + * change ongoing.
> > * * -EBUSY: Runtime PM child_count non-zero.
> > * * -EPERM: Device PM QoS resume latency 0.
> > * * -EINPROGRESS: Suspend already in progress.
> > * * -ENOSYS: CONFIG_PM not enabled.
> > - * * 1: Device already suspended.
> > */
> > static inline int pm_runtime_put(struct device *dev)
> > {
> > @@ -565,15 +564,16 @@ DEFINE_FREE(pm_runtime_put, struct device *, if (_T) pm_runtime_put(_T))
> > * equal to 0, queue up a work item for @dev like in pm_request_autosuspend().
> > *
> > * Return:
> > + * * 1: Usage counts dropped to zero, but device was already suspended.
> > * * 0: Success.
>
> "usage_count" and "usage counter" is being used in kernel-doc already, I'd
> use either of the two. "usage_count" refers directly to the field in struct
> dev_pm_info (and is used a few lines below). Same elsewhere.
"Usage counter", please!
> > * * -EINVAL: Runtime PM error.
> > * * -EACCES: Runtime PM disabled.
> > - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> > + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> > + * change ongoing.
> > * * -EBUSY: Runtime PM child_count non-zero.
> > * * -EPERM: Device PM QoS resume latency 0.
> > * * -EINPROGRESS: Suspend already in progress.
> > * * -ENOSYS: CONFIG_PM not enabled.
> > - * * 1: Device already suspended.
> > */
> > static inline int __pm_runtime_put_autosuspend(struct device *dev)
> > {
> > @@ -590,15 +590,16 @@ static inline int __pm_runtime_put_autosuspend(struct device *dev)
> > * in pm_request_autosuspend().
> > *
> > * Return:
> > + * * 1: Usage counts dropped to zero, but device was already suspended.
> > * * 0: Success.
> > * * -EINVAL: Runtime PM error.
> > * * -EACCES: Runtime PM disabled.
> > - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> > + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> > + * change ongoing.
> > * * -EBUSY: Runtime PM child_count non-zero.
> > * * -EPERM: Device PM QoS resume latency 0.
> > * * -EINPROGRESS: Suspend already in progress.
> > * * -ENOSYS: CONFIG_PM not enabled.
> > - * * 1: Device already suspended.
> > */
> > static inline int pm_runtime_put_autosuspend(struct device *dev)
> > {
> > @@ -619,14 +620,15 @@ static inline int pm_runtime_put_autosuspend(struct device *dev)
> > * if it returns an error code.
> > *
> > * Return:
> > + * * 1: Usage counts dropped to zero, but device was already suspended.
> > * * 0: Success.
>
> Does this happen (pm_runtime_put_sync() calls __pm_runtime_idle())?
It does (see above).
> > * * -EINVAL: Runtime PM error.
> > * * -EACCES: Runtime PM disabled.
> > - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> > + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> > + * change ongoing.
> > * * -EBUSY: Runtime PM child_count non-zero.
> > * * -EPERM: Device PM QoS resume latency 0.
> > * * -ENOSYS: CONFIG_PM not enabled.
> > - * * 1: Device already suspended.
> > * Other values and conditions for the above values are possible as returned by
> > * Runtime PM suspend callbacks.
> > */
> > @@ -646,15 +648,15 @@ static inline int pm_runtime_put_sync(struct device *dev)
> > * if it returns an error code.
> > *
> > * Return:
> > + * * 1: Usage counts dropped to zero, but device was already suspended.
> > * * 0: Success.
> > * * -EINVAL: Runtime PM error.
> > * * -EACCES: Runtime PM disabled.
> > - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> > - * * -EAGAIN: usage_count non-zero or Runtime PM status change ongoing.
> > + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> > + * change ongoing.
> > * * -EBUSY: Runtime PM child_count non-zero.
> > * * -EPERM: Device PM QoS resume latency 0.
> > * * -ENOSYS: CONFIG_PM not enabled.
> > - * * 1: Device already suspended.
> > * Other values and conditions for the above values are possible as returned by
> > * Runtime PM suspend callbacks.
> > */
> > @@ -677,15 +679,16 @@ static inline int pm_runtime_put_sync_suspend(struct device *dev)
> > * if it returns an error code.
> > *
> > * Return:
> > + * * 1: Usage counts dropped to zero, but device was already suspended.
> > * * 0: Success.
> > * * -EINVAL: Runtime PM error.
> > * * -EACCES: Runtime PM disabled.
> > - * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> > + * * -EAGAIN: Runtime PM usage_count became non-zero or Runtime PM status
> > + * change ongoing.
> > * * -EBUSY: Runtime PM child_count non-zero.
> > * * -EPERM: Device PM QoS resume latency 0.
> > * * -EINPROGRESS: Suspend already in progress.
> > * * -ENOSYS: CONFIG_PM not enabled.
> > - * * 1: Device already suspended.
> > * Other values and conditions for the above values are possible as returned by
> > * Runtime PM suspend callbacks.
> > */
>
> --
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts
2025-09-05 17:37 ` [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts Rafael J. Wysocki
@ 2025-09-10 20:44 ` Brian Norris
2025-09-19 16:58 ` Rafael J. Wysocki
0 siblings, 1 reply; 15+ messages in thread
From: Brian Norris @ 2025-09-10 20:44 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Pavel Machek, Rafael J . Wysocki, kunit-dev, Len Brown,
Sakari Ailus, linux-pm, linux-kernel
Hi Rafael,
On Fri, Sep 05, 2025 at 07:37:38PM +0200, Rafael J. Wysocki wrote:
> On Fri, Aug 29, 2025 at 2:33 AM Brian Norris <briannorris@chromium.org> wrote:
> >
> > 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.
> >
> > Signed-off-by: Brian Norris <briannorris@chromium.org>
>
> This is nice in general, but I have a couple of questions/comments (see below).
Thanks for looking. There's certainly some matter of opinion on how
exactly to test things, and I'm still getting up to speed on some of the
runtime PM API details, so I appreciate the care you've given.
Replies inline.
> > ---
> >
> > drivers/base/Kconfig | 6 +
> > drivers/base/power/Makefile | 1 +
> > drivers/base/power/runtime-test.c | 259 ++++++++++++++++++++++++++++++
> > 3 files changed, 266 insertions(+)
> > create mode 100644 drivers/base/power/runtime-test.c
> >
> > diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
> > index 064eb52ff7e2..1786d87b29e2 100644
> > --- a/drivers/base/Kconfig
> > +++ b/drivers/base/Kconfig
> > @@ -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
> > diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
> > index 01f11629d241..2989e42d0161 100644
> > --- a/drivers/base/power/Makefile
> > +++ b/drivers/base/power/Makefile
> > @@ -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
> > diff --git a/drivers/base/power/runtime-test.c b/drivers/base/power/runtime-test.c
> > new file mode 100644
> > index 000000000000..263c28d5fc50
> > --- /dev/null
> > +++ b/drivers/base/power/runtime-test.c
> > @@ -0,0 +1,259 @@
> > +// 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);
> > +
> > + /* Flush, in case the above (non-sync) triggered any work. */
> > + KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
>
> Why do you run pm_runtime_barrier(dev) here? It is guaranteed that no
> requests are pending at this point.
I suppose my thought is as somewhat of an outsider, that's not really
familiar with exactly how each API is supposed to work. So without
looking into the details of the implementation, it's not clear to me
that a "get_noresume()" will never queue any work. Admittedly, that's a
pretty weak reason.
OTOH, it does serve to test the 0 side of the API contract:
"""
* 1, if there was a resume request pending and the device had to be woken up,
* 0, otherwise
"""
So IMO, it's a reasonable thing to run in this test, although I probably
should drop the "Flush" comment.
> > +
> > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
>
> This has already been tested above.
I'm not really an expert on unit testing and style, but the whole point
of this test (named "already_suspended") is that we're testing each
operation when the device is suspended. So it's many series of:
1. set up some precondition
2. assert that the device is (still) suspended
3. test that an API returns the expected value for "already suspended"
Even if #1/#3 aren't likely to affect #2 for a later sequence, it seems
like a good pattern to actually test that this continues to remain true
each time. If the test changes in the future such that we perform
something different in #1, we might find ourselves not testing "already
suspended" behavior in #3.
Alternatively, I could split each #1/#2/#3 sequence into its own
subtest, but that might get a little excessive.
Anyway, like I said, it's probably some matter of opinion/style. I can
drop some of these checks if you still think they have no place here.
> > + /*
> > + * We never actually left RPM_SUSPENDED, but rpm_idle() still treats
> > + * this as -EAGAIN / "runtime PM status change ongoing".
>
> No, this means "Conditions are not suitable, but may change".
I'm just quoting the API docs for put():
"""
* * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
"""
If that's the wrong language, then we should update the API doc. At any
rate, I'm not sure what's "unsuitable" about a suspended device when we
call put(). It's not unsuitable -- it's already in the target state!
Notably, I'm also changing this behavior in patch 2, since I think it's
an API bug. And the comment then goes away.
> > + */
> > + KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put(dev));
> > +
> > + pm_runtime_get_noresume(dev);
> > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
>
> This has been tested already twice and why would it change?
Addressed above. I can drop it if you think it's excessive.
> > + 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_TRUE(test, pm_runtime_suspended(dev));
>
> There's no way by which it could change above.
Same.
> > + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync_autosuspend(dev));
> > +
> > + pm_runtime_get_noresume(dev);
> > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> > + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_autosuspend(dev));
> > +
> > + /* Grab 2 refcounts */
> > + pm_runtime_get_noresume(dev);
> > + pm_runtime_get_noresume(dev);
> > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> > + /* The first put() sees usage_count 1 */
> > + KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_autosuspend(dev));
> > + /* The second put() sees usage_count 0 but tells us "already suspended". */
> > + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_autosuspend(dev));
> > +
> > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
>
> Again, there is no way this can change in the whole test.
Same.
> > +}
> > +
> > +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));
> > + KUNIT_EXPECT_EQ(test, -EACCES, 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));
> > + KUNIT_EXPECT_EQ(test, -EACCES, 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 active */
>
> Still disabled rather.
Ack, will change.
> > + KUNIT_EXPECT_TRUE(test, pm_runtime_active(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));
> > + KUNIT_EXPECT_EQ(test, -EINVAL, 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));
> > + KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_put_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));
> > +
> > + /* Still suspended */
>
> Error is still pending.
Your statement is true, but I'm not quite sure what you're suggesting.
Are you suggesting I should
KUNIT_EXPECT_EQ(test, -EIO, dev->power.runtime_error);
?
Or are you suggesting I change the comment?
I'm thinking I'll do both.
> > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> > + /* 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 */
> > + KUNIT_EXPECT_EQ(test, 0, 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));
> > + KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_autosuspend(dev));
> > +
> > + KUNIT_EXPECT_EQ(test, 0, pm_runtime_resume_and_get(dev));
> > +
> > + /*
> > + * The following should all "fail" with -EAGAIN (usage is non-zero) or
> > + * 1 (already resumed).
>
> The return value of 1 doesn't count as a failure.
Hehe, sure. I suppose that's also a matter of unclear docs, because for
some of these, the kerneldoc specifically says 0 is success, while 1
doesn't really say whether it's success or failure. One has to infer
that "already resumed" is essentially "success" when requesting a
resume.
I'll try to tweak the language.
> > + */
> > + 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));
> > +
> > + /* Flush, and ensure we stayed active. */
>
> There's nothing to flush though.
Ack. I'll reword.
Brian
> > + 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");
> > --
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts
2025-09-10 20:44 ` Brian Norris
@ 2025-09-19 16:58 ` Rafael J. Wysocki
2025-09-23 21:51 ` Brian Norris
0 siblings, 1 reply; 15+ messages in thread
From: Rafael J. Wysocki @ 2025-09-19 16:58 UTC (permalink / raw)
To: Brian Norris
Cc: Rafael J. Wysocki, Pavel Machek, Rafael J . Wysocki, kunit-dev,
Len Brown, Sakari Ailus, linux-pm, linux-kernel
Hi Brian,
On Wed, Sep 10, 2025 at 10:44 PM Brian Norris <briannorris@chromium.org> wrote:
>
> Hi Rafael,
>
> On Fri, Sep 05, 2025 at 07:37:38PM +0200, Rafael J. Wysocki wrote:
> > On Fri, Aug 29, 2025 at 2:33 AM Brian Norris <briannorris@chromium.org> wrote:
> > >
> > > 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.
> > >
> > > Signed-off-by: Brian Norris <briannorris@chromium.org>
> >
> > This is nice in general, but I have a couple of questions/comments (see below).
>
> Thanks for looking. There's certainly some matter of opinion on how
> exactly to test things, and I'm still getting up to speed on some of the
> runtime PM API details, so I appreciate the care you've given.
>
> Replies inline.
>
> > > ---
> > >
> > > drivers/base/Kconfig | 6 +
> > > drivers/base/power/Makefile | 1 +
> > > drivers/base/power/runtime-test.c | 259 ++++++++++++++++++++++++++++++
> > > 3 files changed, 266 insertions(+)
> > > create mode 100644 drivers/base/power/runtime-test.c
> > >
> > > diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
> > > index 064eb52ff7e2..1786d87b29e2 100644
> > > --- a/drivers/base/Kconfig
> > > +++ b/drivers/base/Kconfig
> > > @@ -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
> > > diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
> > > index 01f11629d241..2989e42d0161 100644
> > > --- a/drivers/base/power/Makefile
> > > +++ b/drivers/base/power/Makefile
> > > @@ -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
> > > diff --git a/drivers/base/power/runtime-test.c b/drivers/base/power/runtime-test.c
> > > new file mode 100644
> > > index 000000000000..263c28d5fc50
> > > --- /dev/null
> > > +++ b/drivers/base/power/runtime-test.c
> > > @@ -0,0 +1,259 @@
> > > +// 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);
> > > +
> > > + /* Flush, in case the above (non-sync) triggered any work. */
> > > + KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
> >
> > Why do you run pm_runtime_barrier(dev) here? It is guaranteed that no
> > requests are pending at this point.
>
> I suppose my thought is as somewhat of an outsider, that's not really
> familiar with exactly how each API is supposed to work. So without
> looking into the details of the implementation, it's not clear to me
> that a "get_noresume()" will never queue any work. Admittedly, that's a
> pretty weak reason.
>
> OTOH, it does serve to test the 0 side of the API contract:
>
> """
> * 1, if there was a resume request pending and the device had to be woken up,
> * 0, otherwise
> """
>
> So IMO, it's a reasonable thing to run in this test, although I probably
> should drop the "Flush" comment.
Yeah, changing the comment would help.
> > > +
> > > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> >
> > This has already been tested above.
>
> I'm not really an expert on unit testing and style, but the whole point
> of this test (named "already_suspended") is that we're testing each
> operation when the device is suspended. So it's many series of:
>
> 1. set up some precondition
> 2. assert that the device is (still) suspended
> 3. test that an API returns the expected value for "already suspended"
>
> Even if #1/#3 aren't likely to affect #2 for a later sequence, it seems
> like a good pattern to actually test that this continues to remain true
> each time. If the test changes in the future such that we perform
> something different in #1, we might find ourselves not testing "already
> suspended" behavior in #3.
>
> Alternatively, I could split each #1/#2/#3 sequence into its own
> subtest, but that might get a little excessive.
>
> Anyway, like I said, it's probably some matter of opinion/style. I can
> drop some of these checks if you still think they have no place here.
I would do just two of them, one at the beginning and one at the end.
It should be an invariant for everything in between.
> > > + /*
> > > + * We never actually left RPM_SUSPENDED, but rpm_idle() still treats
> > > + * this as -EAGAIN / "runtime PM status change ongoing".
> >
> > No, this means "Conditions are not suitable, but may change".
>
> I'm just quoting the API docs for put():
>
> """
> * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> """
>
> If that's the wrong language, then we should update the API doc. At any
> rate, I'm not sure what's "unsuitable" about a suspended device when we
> call put(). It's not unsuitable -- it's already in the target state!
>
> Notably, I'm also changing this behavior in patch 2, since I think it's
> an API bug. And the comment then goes away.
Yeah, so I'd prefer to change this particular thing entirely,
especially in the face of
https://lore.kernel.org/linux-pm/5049058.31r3eYUQgx@rafael.j.wysocki/
which quite obviously doesn't take the return value of
pm_runtime_put() and pm_runtime_put_sutosuspend() into account.
I would like these two functions to be void.
Of course, there are existing users that check their return values,
but I'm not sure how much of a real advantage from doing that is. At
least some of those users appear to not exactly know what they are
doing.
> > > + */
> > > + KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put(dev));
> > > +
> > > + pm_runtime_get_noresume(dev);
> > > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> >
> > This has been tested already twice and why would it change?
>
> Addressed above. I can drop it if you think it's excessive.
>
> > > + 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_TRUE(test, pm_runtime_suspended(dev));
> >
> > There's no way by which it could change above.
>
> Same.
>
> > > + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync_autosuspend(dev));
> > > +
> > > + pm_runtime_get_noresume(dev);
> > > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> > > + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_autosuspend(dev));
> > > +
> > > + /* Grab 2 refcounts */
> > > + pm_runtime_get_noresume(dev);
> > > + pm_runtime_get_noresume(dev);
> > > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> > > + /* The first put() sees usage_count 1 */
> > > + KUNIT_EXPECT_EQ(test, 0, pm_runtime_put_autosuspend(dev));
> > > + /* The second put() sees usage_count 0 but tells us "already suspended". */
> > > + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_autosuspend(dev));
> > > +
> > > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> >
> > Again, there is no way this can change in the whole test.
>
> Same.
>
> > > +}
> > > +
> > > +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));
> > > + KUNIT_EXPECT_EQ(test, -EACCES, 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));
> > > + KUNIT_EXPECT_EQ(test, -EACCES, 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 active */
> >
> > Still disabled rather.
>
> Ack, will change.
>
> > > + KUNIT_EXPECT_TRUE(test, pm_runtime_active(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));
> > > + KUNIT_EXPECT_EQ(test, -EINVAL, 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));
> > > + KUNIT_EXPECT_EQ(test, -EINVAL, pm_runtime_put_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));
> > > +
> > > + /* Still suspended */
> >
> > Error is still pending.
>
> Your statement is true, but I'm not quite sure what you're suggesting.
> Are you suggesting I should
>
> KUNIT_EXPECT_EQ(test, -EIO, dev->power.runtime_error);
>
> ?
>
> Or are you suggesting I change the comment?
Change the comment.
> I'm thinking I'll do both.
That will work too.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts
2025-09-19 16:58 ` Rafael J. Wysocki
@ 2025-09-23 21:51 ` Brian Norris
2025-09-24 17:32 ` Rafael J. Wysocki
0 siblings, 1 reply; 15+ messages in thread
From: Brian Norris @ 2025-09-23 21:51 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Pavel Machek, Rafael J . Wysocki, kunit-dev, Len Brown,
Sakari Ailus, linux-pm, linux-kernel
Hi Rafael,
On Fri, Sep 19, 2025 at 06:58:50PM +0200, Rafael J. Wysocki wrote:
> On Wed, Sep 10, 2025 at 10:44 PM Brian Norris <briannorris@chromium.org> wrote:
> > On Fri, Sep 05, 2025 at 07:37:38PM +0200, Rafael J. Wysocki wrote:
> > > On Fri, Aug 29, 2025 at 2:33 AM Brian Norris <briannorris@chromium.org> wrote:
> > > > + /* Flush, in case the above (non-sync) triggered any work. */
> > > > + KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
> > >
> > > Why do you run pm_runtime_barrier(dev) here? It is guaranteed that no
> > > requests are pending at this point.
> >
...
> > So IMO, it's a reasonable thing to run in this test, although I probably
> > should drop the "Flush" comment.
>
> Yeah, changing the comment would help.
Will do.
> > > > +
> > > > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> > >
> > > This has already been tested above.
...
> > Anyway, like I said, it's probably some matter of opinion/style. I can
> > drop some of these checks if you still think they have no place here.
>
> I would do just two of them, one at the beginning and one at the end.
> It should be an invariant for everything in between.
Ack.
> > > > + /*
> > > > + * We never actually left RPM_SUSPENDED, but rpm_idle() still treats
> > > > + * this as -EAGAIN / "runtime PM status change ongoing".
> > >
> > > No, this means "Conditions are not suitable, but may change".
> >
> > I'm just quoting the API docs for put():
> >
> > """
> > * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> > """
> >
> > If that's the wrong language, then we should update the API doc. At any
> > rate, I'm not sure what's "unsuitable" about a suspended device when we
> > call put(). It's not unsuitable -- it's already in the target state!
> >
> > Notably, I'm also changing this behavior in patch 2, since I think it's
> > an API bug. And the comment then goes away.
>
> Yeah, so I'd prefer to change this particular thing entirely,
> especially in the face of
>
> https://lore.kernel.org/linux-pm/5049058.31r3eYUQgx@rafael.j.wysocki/
>
> which quite obviously doesn't take the return value of
> pm_runtime_put() and pm_runtime_put_sutosuspend() into account.
>
> I would like these two functions to be void.
Sure, I think inspecting put() return codes is generally a bad idea.
'void' would be cool with me, although maybe a bit impractical now,
considering how many users look at the current return code. So at a
minimum, I'd separate "make 'em void" from my "document and test the
API" work.
Really, I'm mostly looking at this area because I have to support driver
developers trying to learn how to use the runtime PM API, and they
wonder about the return codes. So if they exist, I'd at least like them
to make sense.
Anyway, for the particulars of this test: I can try to adapt the comment
language a bit. But are you suggesting I shouldn't even try patch 2,
which fixes the pm_runtime_put() return codes?
> Of course, there are existing users that check their return values,
> but I'm not sure how much of a real advantage from doing that is. At
> least some of those users appear to not exactly know what they are
> doing.
>
...
> > > > +static void pm_runtime_error_test(struct kunit *test)
> > > > +{
...
> > > > + /* Still suspended */
> > >
> > > Error is still pending.
> >
> > Your statement is true, but I'm not quite sure what you're suggesting.
> > Are you suggesting I should
> >
> > KUNIT_EXPECT_EQ(test, -EIO, dev->power.runtime_error);
> >
> > ?
> >
> > Or are you suggesting I change the comment?
>
> Change the comment.
>
> > I'm thinking I'll do both.
>
> That will work too.
Ack.
Brian
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] PM: runtime: Make put{,_sync}() return 1 when already suspended
2025-08-29 0:28 ` [PATCH 2/3] PM: runtime: Make put{,_sync}() return 1 when already suspended Brian Norris
2025-09-02 7:25 ` Sakari Ailus
@ 2025-09-24 9:48 ` Dhruva Gole
1 sibling, 0 replies; 15+ messages in thread
From: Dhruva Gole @ 2025-09-24 9:48 UTC (permalink / raw)
To: Brian Norris
Cc: Rafael J. Wysocki, Pavel Machek, Rafael J . Wysocki, kunit-dev,
Len Brown, Sakari Ailus, linux-pm, linux-kernel
On Aug 28, 2025 at 17:28:27 -0700, Brian Norris wrote:
> The pm_runtime.h docs say pm_runtime_put() and pm_runtime_put_sync()
> return 1 when already suspended, but this is not true -- they return
> -EAGAIN. On the other hand, pm_runtime_put_sync_suspend() and
> pm_runtime_put_sync_autosuspend() *do* return 1.
>
> This is an artifact of the fact that the former are built on rpm_idle(),
> whereas the latter are built on rpm_suspend().
>
> There are precious few pm_runtime_put()/pm_runtime_put_sync() callers
> that check the return code at all, but most of them only log errors, and
> usually only for negative error codes. None of them should be treating
> this as an error, so:
>
> * at best, this may fix some case where a driver treats this condition
> as an error, when it shouldn't;
>
> * at worst, this should make no effect; and
>
> * somewhere in between, we could potentially clear up non-fatal log
> messages.
Right, just doing a $> git grep -A5 "= pm_runtime_put" gave me quite a few
callers who actually do check the return codes, and in some cases even
directly backpropagate them! So like you say with this patch best case even
might fix a few cases where it's unnecessary.
>
> Fix the pm_runtime_already_suspended_test() while tweaking the behavior.
> The test makes a lot more sense when these all return 1 when the device
> is already suspended:
>
> pm_runtime_put(dev);
> pm_runtime_put_sync(dev);
> pm_runtime_suspend(dev);
> pm_runtime_autosuspend(dev);
> pm_request_autosuspend(dev);
> pm_runtime_put_sync_autosuspend(dev);
> pm_runtime_put_autosuspend(dev);
>
> Signed-off-by: Brian Norris <briannorris@chromium.org>
> ---
>
> drivers/base/power/runtime-test.c | 8 ++------
> drivers/base/power/runtime.c | 3 +++
> 2 files changed, 5 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/base/power/runtime-test.c b/drivers/base/power/runtime-test.c
> index 263c28d5fc50..1be18e871f1d 100644
> --- a/drivers/base/power/runtime-test.c
> +++ b/drivers/base/power/runtime-test.c
> @@ -43,15 +43,11 @@ static void pm_runtime_already_suspended_test(struct kunit *test)
> KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
>
> KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> - /*
> - * We never actually left RPM_SUSPENDED, but rpm_idle() still treats
> - * this as -EAGAIN / "runtime PM status change ongoing".
> - */
> - KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put(dev));
> + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put(dev));
>
> pm_runtime_get_noresume(dev);
> KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> - KUNIT_EXPECT_EQ(test, -EAGAIN, pm_runtime_put_sync(dev));
> + KUNIT_EXPECT_EQ(test, 1, pm_runtime_put_sync(dev));
>
> KUNIT_EXPECT_EQ(test, 1, pm_runtime_suspend(dev));
> KUNIT_EXPECT_EQ(test, 1, pm_runtime_autosuspend(dev));
> diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
> index 3e84dc4122de..17cf111d16aa 100644
> --- a/drivers/base/power/runtime.c
> +++ b/drivers/base/power/runtime.c
> @@ -498,6 +498,9 @@ static int rpm_idle(struct device *dev, int rpmflags)
> if (retval < 0)
> ; /* Conditions are wrong. */
>
> + else if ((rpmflags & RPM_GET_PUT) && (retval == 1))
> + ; /* put() is allowed in RPM_SUSPENDED */
> +
No objections from my side,
Reviewed-by: Dhruva Gole <d-gole@ti.com>
--
Best regards,
Dhruva Gole
Texas Instruments Incorporated
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts
2025-09-23 21:51 ` Brian Norris
@ 2025-09-24 17:32 ` Rafael J. Wysocki
2025-09-24 17:34 ` Rafael J. Wysocki
0 siblings, 1 reply; 15+ messages in thread
From: Rafael J. Wysocki @ 2025-09-24 17:32 UTC (permalink / raw)
To: Brian Norris
Cc: Rafael J. Wysocki, Pavel Machek, Rafael J . Wysocki, kunit-dev,
Len Brown, Sakari Ailus, linux-pm, linux-kernel
Hi Brian,
On Tue, Sep 23, 2025 at 11:51 PM Brian Norris <briannorris@chromium.org> wrote:
>
> Hi Rafael,
>
> On Fri, Sep 19, 2025 at 06:58:50PM +0200, Rafael J. Wysocki wrote:
> > On Wed, Sep 10, 2025 at 10:44 PM Brian Norris <briannorris@chromium.org> wrote:
> > > On Fri, Sep 05, 2025 at 07:37:38PM +0200, Rafael J. Wysocki wrote:
> > > > On Fri, Aug 29, 2025 at 2:33 AM Brian Norris <briannorris@chromium.org> wrote:
> > > > > + /* Flush, in case the above (non-sync) triggered any work. */
> > > > > + KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
> > > >
> > > > Why do you run pm_runtime_barrier(dev) here? It is guaranteed that no
> > > > requests are pending at this point.
> > >
> ...
> > > So IMO, it's a reasonable thing to run in this test, although I probably
> > > should drop the "Flush" comment.
> >
> > Yeah, changing the comment would help.
>
> Will do.
>
> > > > > +
> > > > > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> > > >
> > > > This has already been tested above.
> ...
> > > Anyway, like I said, it's probably some matter of opinion/style. I can
> > > drop some of these checks if you still think they have no place here.
> >
> > I would do just two of them, one at the beginning and one at the end.
> > It should be an invariant for everything in between.
>
> Ack.
>
> > > > > + /*
> > > > > + * We never actually left RPM_SUSPENDED, but rpm_idle() still treats
> > > > > + * this as -EAGAIN / "runtime PM status change ongoing".
> > > >
> > > > No, this means "Conditions are not suitable, but may change".
> > >
> > > I'm just quoting the API docs for put():
> > >
> > > """
> > > * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> > > """
> > >
> > > If that's the wrong language, then we should update the API doc. At any
> > > rate, I'm not sure what's "unsuitable" about a suspended device when we
> > > call put(). It's not unsuitable -- it's already in the target state!
> > >
> > > Notably, I'm also changing this behavior in patch 2, since I think it's
> > > an API bug. And the comment then goes away.
> >
> > Yeah, so I'd prefer to change this particular thing entirely,
> > especially in the face of
> >
> > https://lore.kernel.org/linux-pm/5049058.31r3eYUQgx@rafael.j.wysocki/
> >
> > which quite obviously doesn't take the return value of
> > pm_runtime_put() and pm_runtime_put_sutosuspend() into account.
> >
> > I would like these two functions to be void.
>
> Sure, I think inspecting put() return codes is generally a bad idea.
> 'void' would be cool with me, although maybe a bit impractical now,
> considering how many users look at the current return code.
For pm_runtime_put() it's not that bad. I have ~20 patches changing
all of the code looking at its return value to stop doing that.
Interestingly enough, there's only one piece of that code (USB core)
doing anything remotely useful with that return value. Everything
else is just garbage IMV.
> So at a minimum, I'd separate "make 'em void" from my "document and test the
> API" work.
But you can just skip them.
> Really, I'm mostly looking at this area because I have to support driver
> developers trying to learn how to use the runtime PM API, and they
> wonder about the return codes. So if they exist, I'd at least like them
> to make sense.
Sure.
That said, as far as pm_runtime_put() and pm_runtime_put_autosuspend()
are concerned, you may as well just say "discard their return values,
you don't want to have to deal with them, and never ever pass them
verbatim to the callers of your code".
> Anyway, for the particulars of this test: I can try to adapt the comment
> language a bit. But are you suggesting I shouldn't even try patch 2,
> which fixes the pm_runtime_put() return codes?
Not really.
> > Of course, there are existing users that check their return values,
> > but I'm not sure how much of a real advantage from doing that is.
Well, see above. :-)
> At least some of those users appear to not exactly know what they are
> doing.
Almost none of them do nonsense.
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts
2025-09-24 17:32 ` Rafael J. Wysocki
@ 2025-09-24 17:34 ` Rafael J. Wysocki
2025-09-25 18:36 ` Brian Norris
0 siblings, 1 reply; 15+ messages in thread
From: Rafael J. Wysocki @ 2025-09-24 17:34 UTC (permalink / raw)
To: Brian Norris
Cc: Pavel Machek, Rafael J . Wysocki, kunit-dev, Len Brown,
Sakari Ailus, linux-pm, linux-kernel
On Wed, Sep 24, 2025 at 7:32 PM Rafael J. Wysocki <rafael@kernel.org> wrote:
>
> Hi Brian,
>
> On Tue, Sep 23, 2025 at 11:51 PM Brian Norris <briannorris@chromium.org> wrote:
> >
> > Hi Rafael,
> >
> > On Fri, Sep 19, 2025 at 06:58:50PM +0200, Rafael J. Wysocki wrote:
> > > On Wed, Sep 10, 2025 at 10:44 PM Brian Norris <briannorris@chromium.org> wrote:
> > > > On Fri, Sep 05, 2025 at 07:37:38PM +0200, Rafael J. Wysocki wrote:
> > > > > On Fri, Aug 29, 2025 at 2:33 AM Brian Norris <briannorris@chromium.org> wrote:
> > > > > > + /* Flush, in case the above (non-sync) triggered any work. */
> > > > > > + KUNIT_EXPECT_EQ(test, 0, pm_runtime_barrier(dev)); /* no wakeup needed */
> > > > >
> > > > > Why do you run pm_runtime_barrier(dev) here? It is guaranteed that no
> > > > > requests are pending at this point.
> > > >
> > ...
> > > > So IMO, it's a reasonable thing to run in this test, although I probably
> > > > should drop the "Flush" comment.
> > >
> > > Yeah, changing the comment would help.
> >
> > Will do.
> >
> > > > > > +
> > > > > > + KUNIT_EXPECT_TRUE(test, pm_runtime_suspended(dev));
> > > > >
> > > > > This has already been tested above.
> > ...
> > > > Anyway, like I said, it's probably some matter of opinion/style. I can
> > > > drop some of these checks if you still think they have no place here.
> > >
> > > I would do just two of them, one at the beginning and one at the end.
> > > It should be an invariant for everything in between.
> >
> > Ack.
> >
> > > > > > + /*
> > > > > > + * We never actually left RPM_SUSPENDED, but rpm_idle() still treats
> > > > > > + * this as -EAGAIN / "runtime PM status change ongoing".
> > > > >
> > > > > No, this means "Conditions are not suitable, but may change".
> > > >
> > > > I'm just quoting the API docs for put():
> > > >
> > > > """
> > > > * * -EAGAIN: Runtime PM usage_count non-zero or Runtime PM status change ongoing.
> > > > """
> > > >
> > > > If that's the wrong language, then we should update the API doc. At any
> > > > rate, I'm not sure what's "unsuitable" about a suspended device when we
> > > > call put(). It's not unsuitable -- it's already in the target state!
> > > >
> > > > Notably, I'm also changing this behavior in patch 2, since I think it's
> > > > an API bug. And the comment then goes away.
> > >
> > > Yeah, so I'd prefer to change this particular thing entirely,
> > > especially in the face of
> > >
> > > https://lore.kernel.org/linux-pm/5049058.31r3eYUQgx@rafael.j.wysocki/
> > >
> > > which quite obviously doesn't take the return value of
> > > pm_runtime_put() and pm_runtime_put_sutosuspend() into account.
> > >
> > > I would like these two functions to be void.
> >
> > Sure, I think inspecting put() return codes is generally a bad idea.
> > 'void' would be cool with me, although maybe a bit impractical now,
> > considering how many users look at the current return code.
>
> For pm_runtime_put() it's not that bad. I have ~20 patches changing
> all of the code looking at its return value to stop doing that.
>
> Interestingly enough, there's only one piece of that code (USB core)
> doing anything remotely useful with that return value. Everything
> else is just garbage IMV.
>
> > So at a minimum, I'd separate "make 'em void" from my "document and test the
> > API" work.
>
> But you can just skip them.
>
> > Really, I'm mostly looking at this area because I have to support driver
> > developers trying to learn how to use the runtime PM API, and they
> > wonder about the return codes. So if they exist, I'd at least like them
> > to make sense.
>
> Sure.
>
> That said, as far as pm_runtime_put() and pm_runtime_put_autosuspend()
> are concerned, you may as well just say "discard their return values,
> you don't want to have to deal with them, and never ever pass them
> verbatim to the callers of your code".
>
> > Anyway, for the particulars of this test: I can try to adapt the comment
> > language a bit. But are you suggesting I shouldn't even try patch 2,
> > which fixes the pm_runtime_put() return codes?
>
> Not really.
>
> > > Of course, there are existing users that check their return values,
> > > but I'm not sure how much of a real advantage from doing that is.
>
> Well, see above. :-)
>
> > At least some of those users appear to not exactly know what they are
> > doing.
>
> Almost none of them do nonsense.
s/none/all/ (sorry)
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts
2025-09-24 17:34 ` Rafael J. Wysocki
@ 2025-09-25 18:36 ` Brian Norris
0 siblings, 0 replies; 15+ messages in thread
From: Brian Norris @ 2025-09-25 18:36 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Pavel Machek, Rafael J . Wysocki, kunit-dev, Len Brown,
Sakari Ailus, linux-pm, linux-kernel
On Wed, Sep 24, 2025 at 07:34:31PM +0200, Rafael J. Wysocki wrote:
> On Wed, Sep 24, 2025 at 7:32 PM Rafael J. Wysocki <rafael@kernel.org> wrote:
> > On Tue, Sep 23, 2025 at 11:51 PM Brian Norris <briannorris@chromium.org> wrote:
> > > On Fri, Sep 19, 2025 at 06:58:50PM +0200, Rafael J. Wysocki wrote:
> > > > Yeah, so I'd prefer to change this particular thing entirely,
> > > > especially in the face of
> > > >
> > > > https://lore.kernel.org/linux-pm/5049058.31r3eYUQgx@rafael.j.wysocki/
> > > >
> > > > which quite obviously doesn't take the return value of
> > > > pm_runtime_put() and pm_runtime_put_sutosuspend() into account.
> > > >
> > > > I would like these two functions to be void.
> > >
> > > Sure, I think inspecting put() return codes is generally a bad idea.
> > > 'void' would be cool with me, although maybe a bit impractical now,
> > > considering how many users look at the current return code.
> >
> > For pm_runtime_put() it's not that bad. I have ~20 patches changing
> > all of the code looking at its return value to stop doing that.
> >
> > Interestingly enough, there's only one piece of that code (USB core)
> > doing anything remotely useful with that return value. Everything
> > else is just garbage IMV.
> >
> > > So at a minimum, I'd separate "make 'em void" from my "document and test the
> > > API" work.
> >
> > But you can just skip them.
> >
> > > Really, I'm mostly looking at this area because I have to support driver
> > > developers trying to learn how to use the runtime PM API, and they
> > > wonder about the return codes. So if they exist, I'd at least like them
> > > to make sense.
> >
> > Sure.
> >
> > That said, as far as pm_runtime_put() and pm_runtime_put_autosuspend()
> > are concerned, you may as well just say "discard their return values,
> > you don't want to have to deal with them, and never ever pass them
> > verbatim to the callers of your code".
Sounds reasonable.
I'll drop any unit test expectations for pm_runtime_put() and
pm_runtime_put_autosuspend() return codes. But I'll leave
pm_runtime_put_sync().
> > > Anyway, for the particulars of this test: I can try to adapt the comment
> > > language a bit. But are you suggesting I shouldn't even try patch 2,
> > > which fixes the pm_runtime_put() return codes?
> >
> > Not really.
> >
> > > > Of course, there are existing users that check their return values,
> > > > but I'm not sure how much of a real advantage from doing that is.
> >
> > Well, see above. :-)
> >
> > > At least some of those users appear to not exactly know what they are
> > > doing.
> >
> > Almost none of them do nonsense.
>
> s/none/all/ (sorry)
Ha, thanks for the clarification :)
Brian
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2025-09-25 18:36 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-29 0:28 [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts Brian Norris
2025-08-29 0:28 ` [PATCH 2/3] PM: runtime: Make put{,_sync}() return 1 when already suspended Brian Norris
2025-09-02 7:25 ` Sakari Ailus
2025-09-05 17:42 ` Rafael J. Wysocki
2025-09-24 9:48 ` Dhruva Gole
2025-08-29 0:28 ` [PATCH 3/3] PM: runtime: Update kerneldoc return codes Brian Norris
2025-09-02 7:18 ` Sakari Ailus
2025-09-05 17:54 ` Rafael J. Wysocki
2025-09-05 17:37 ` [PATCH 1/3] PM: runtime: Add basic kunit tests for API contracts Rafael J. Wysocki
2025-09-10 20:44 ` Brian Norris
2025-09-19 16:58 ` Rafael J. Wysocki
2025-09-23 21:51 ` Brian Norris
2025-09-24 17:32 ` Rafael J. Wysocki
2025-09-24 17:34 ` Rafael J. Wysocki
2025-09-25 18:36 ` Brian Norris
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).