[PATCH] PM: sleep: Add DPM watchdog to late/early/noirq phases
From: Mayank Rungta
Date: Fri Aug 21 2026 - 13:13:23 EST
Extend the DPM watchdog to wrap device_suspend_late, device_suspend_noirq,
device_resume_early, and device_resume_noirq callbacks. If a driver hangs
during these transitions, the watchdog will fire and dump a stack trace
to help identify the offending driver.
To prevent false-positive timeouts, the watchdog is set only after
waiting for subordinate (during suspend) and superior (during resume)
devices.
Signed-off-by: Mayank Rungta <mrungta@xxxxxxxxxx>
---
Testing:
- Tested on an ARM64 SoC (Pixel platform running a 6.18-based kernel with
DPM watchdog enabled; Android 6.18 kernel has latest DPM changes
backported).
drivers/base/power/main.c | 25 ++++++++++++++++++++++---
1 file changed, 22 insertions(+), 3 deletions(-)
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 184dc4b3b938..b6778700af28 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -802,6 +802,7 @@ static void device_resume_noirq(struct device *dev, pm_message_t state, bool asy
const char *info = NULL;
bool skip_resume;
int error = 0;
+ DECLARE_DPM_WATCHDOG_ON_STACK(wd);
TRACE_DEVICE(dev);
TRACE_RESUME(0);
@@ -827,6 +828,7 @@ static void device_resume_noirq(struct device *dev, pm_message_t state, bool asy
if (!dpm_wait_for_superior(dev, async))
goto Out;
+ dpm_watchdog_set(&wd, dev);
skip_resume = dev_pm_skip_resume(dev);
/*
* If the driver callback is skipped below or by the middle layer
@@ -871,6 +873,7 @@ static void device_resume_noirq(struct device *dev, pm_message_t state, bool asy
error = dpm_run_callback(callback, dev, state, info);
Skip:
+ dpm_watchdog_clear(&wd);
dev->power.is_noirq_suspended = false;
Out:
@@ -971,6 +974,7 @@ static void device_resume_early(struct device *dev, pm_message_t state, bool asy
pm_callback_t callback = NULL;
const char *info = NULL;
int error = 0;
+ DECLARE_DPM_WATCHDOG_ON_STACK(wd);
TRACE_DEVICE(dev);
TRACE_RESUME(0);
@@ -987,6 +991,7 @@ static void device_resume_early(struct device *dev, pm_message_t state, bool asy
if (!dpm_wait_for_superior(dev, async))
goto Out;
+ dpm_watchdog_set(&wd, dev);
if (dev->pm_domain) {
info = "early power domain ";
callback = pm_late_early_op(&dev->pm_domain->ops, state);
@@ -1004,7 +1009,7 @@ static void device_resume_early(struct device *dev, pm_message_t state, bool asy
goto Run;
if (dev_pm_skip_resume(dev))
- goto Skip;
+ goto End;
if (dev->driver && dev->driver->pm) {
info = "early driver ";
@@ -1014,6 +1019,9 @@ static void device_resume_early(struct device *dev, pm_message_t state, bool asy
Run:
error = dpm_run_callback(callback, dev, state, info);
+End:
+ dpm_watchdog_clear(&wd);
+
Skip:
dev->power.is_late_suspended = false;
pm_runtime_enable(dev);
@@ -1508,6 +1516,7 @@ static void device_suspend_noirq(struct device *dev, pm_message_t state, bool as
pm_callback_t callback = NULL;
const char *info = NULL;
int error = 0;
+ DECLARE_DPM_WATCHDOG_ON_STACK(wd);
TRACE_DEVICE(dev);
TRACE_SUSPEND(0);
@@ -1520,6 +1529,7 @@ static void device_suspend_noirq(struct device *dev, pm_message_t state, bool as
if (dev->power.syscore || dev->power.direct_complete)
goto Complete;
+ dpm_watchdog_set(&wd, dev);
if (dev->pm_domain) {
info = "noirq power domain ";
callback = pm_noirq_op(&dev->pm_domain->ops, state);
@@ -1550,7 +1560,7 @@ static void device_suspend_noirq(struct device *dev, pm_message_t state, bool as
WRITE_ONCE(async_error, error);
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, state, async ? " async noirq" : " noirq", error);
- goto Complete;
+ goto End;
}
Skip:
@@ -1569,6 +1579,9 @@ static void device_suspend_noirq(struct device *dev, pm_message_t state, bool as
if (dev->power.must_resume)
dpm_superior_set_must_resume(dev);
+End:
+ dpm_watchdog_clear(&wd);
+
Complete:
complete_all(&dev->power.completion);
TRACE_SUSPEND(error);
@@ -1703,6 +1716,7 @@ static void device_suspend_late(struct device *dev, pm_message_t state, bool asy
pm_callback_t callback = NULL;
const char *info = NULL;
int error = 0;
+ DECLARE_DPM_WATCHDOG_ON_STACK(wd);
TRACE_DEVICE(dev);
TRACE_SUSPEND(0);
@@ -1720,6 +1734,8 @@ static void device_suspend_late(struct device *dev, pm_message_t state, bool asy
if (dev->power.direct_complete)
goto Complete;
+ dpm_watchdog_set(&wd, dev);
+
/*
* After this point, any runtime PM operations targeting the device
* will fail until the corresponding pm_runtime_enable() call in
@@ -1761,13 +1777,16 @@ static void device_suspend_late(struct device *dev, pm_message_t state, bool asy
dpm_save_failed_dev(dev_name(dev));
pm_dev_err(dev, state, async ? " async late" : " late", error);
pm_runtime_enable(dev);
- goto Complete;
+ goto End;
}
dpm_propagate_wakeup_to_parent(dev);
Skip:
dev->power.is_late_suspended = true;
+End:
+ dpm_watchdog_clear(&wd);
+
Complete:
TRACE_SUSPEND(error);
complete_all(&dev->power.completion);
base-commit: 77ae27fd98f3b548797c9f22c10ab5cf1c4ada53
--
2.55.0.766.g2966f0265a-goog