[PATCH 3/5] PM: runtime: Propagate last_busy from dependent to dependency
From: Brian Norris
Date: Fri Oct 02 2026 - 19:08:47 EST
When a child device suspends, it does not update the last_busy timestamp
for its parent. If that parent configured autosuspend and didn't
otherwise maintain its last_busy timestamp, it may now be immediately
eligible to suspend. This is probably not expected -- the parent should
wait for its autosuspend delay before suspending.
The effect of this behavior is that a parent device may suspend sooner
than its autosuspend delay, simply because its usage was accounted by
its children, and not by direct references to the parent device.
This was noticed in several cases, and some have implemented
workarounds, such as in commit c537d3457542 ("iio: adc: stm32-adc: fix
runtime autosuspend delay when slow polling"). At the same time, Ulf
suggested these problems "should be solved in the runtime PM core".
Instead of working around the problem in drivers, we propagate last_busy
timestamps from a dependent device to its dependencies any time it may
allow a dependency to suspend -- i.e., when releasing a refcount for its
parent or suppliers. We take care to only propagate the timestamp if it
is larger than the existing busy timestamp.
Note that this works best if the dependent device is using autosuspend
(and therefore updates its last_busy timestamps appropriately), but even
for a non-autosuspend child, this is still somewhat useful --
non-autosuspend devices still automatically update their last_busy every
time they resume.
Link: https://lore.kernel.org/all/CAPDyKFp=KTf8=zGBSzPYqhjnZpY8xwvjCeM1e-WTKT1QLSxaDA@xxxxxxxxxxxxxx/
Cc: Ulf Hansson <ulfh@xxxxxxxxxx>
Signed-off-by: Brian Norris <briannorris@xxxxxxxxxxxx>
---
drivers/base/power/runtime.c | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
index 9eed1f79429d..ee7253d154e2 100644
--- a/drivers/base/power/runtime.c
+++ b/drivers/base/power/runtime.c
@@ -318,6 +318,21 @@ static int rpm_get_suppliers(struct device *dev)
return 0;
}
+/*
+ * Propagate last_busy timestamp from one device to another. This can, for
+ * example, prevent overactive suspend when a dependency's usage is primarily
+ * driven by one of its dependents.
+ */
+static void rpm_propagate_last_busy(struct device *dev, struct device *target)
+{
+ s64 busy = atomic64_read(&dev->power.last_busy);
+ s64 target_busy = atomic64_read(&target->power.last_busy);
+
+ while (target_busy < busy)
+ if (atomic64_try_cmpxchg(&target->power.last_busy, &target_busy, busy))
+ return;
+}
+
/**
* pm_runtime_release_supplier - Drop references to device link's supplier.
* @link: Target device link.
@@ -348,6 +363,7 @@ static void __rpm_put_suppliers(struct device *dev, bool try_to_suspend)
if (!device_link_test(link, DL_FLAG_PM_RUNTIME))
continue;
+ rpm_propagate_last_busy(dev, link->supplier);
pm_runtime_release_supplier(link);
if (try_to_suspend)
pm_request_idle(link->supplier);
@@ -710,6 +726,8 @@ static int rpm_suspend(struct device *dev, int rpmflags)
if (dev->parent) {
parent = dev->parent;
+ if (!parent->power.ignore_children)
+ rpm_propagate_last_busy(dev, parent);
atomic_add_unless(&parent->power.child_count, -1, 0);
}
wake_up_all(&dev->power.wait_queue);
@@ -1369,8 +1387,10 @@ int __pm_runtime_set_status(struct device *dev, unsigned int status)
goto out_set;
if (status == RPM_SUSPENDED) {
- atomic_add_unless(&parent->power.child_count, -1, 0);
notify_parent = !parent->power.ignore_children;
+ if (notify_parent)
+ rpm_propagate_last_busy(dev, parent);
+ atomic_add_unless(&parent->power.child_count, -1, 0);
} else {
spin_lock_nested(&parent->power.lock, SINGLE_DEPTH_NESTING);
--
2.56.0.rc1.315.gc6ed9934b7-goog