[PATCH v5 3/4] PM: hibernate: allow devices to skip post-snapshot THAW

From: Haowen Tu

Date: Wed Sep 02 2026 - 22:25:22 EST


After a hibernation snapshot has been created, the PM core resumes
devices with PMSG_THAW so that the image can be written out. Some
devices are not involved in image writeout and do not need to be resumed
during that transient phase.

Add DPM_FLAG_SKIP_HIBERNATION_THAW to let drivers opt a device out of the
post-snapshot THAW resume. Skipped devices remain suspended while the
image is written. If the original kernel continues running instead of
powering down, resume the skipped devices before userspace is thawed.

The skip is limited to the original kernel's post-snapshot THAW path by
checking pm_hibernation_snapshot_done(). Other THAW paths, including
recovery from failed image restore in the restore kernel, continue to
resume devices normally.

Signed-off-by: Haowen Tu <tuhaowen@xxxxxxxxxxxxx>
---
Documentation/driver-api/pm/devices.rst | 17 +++++++
drivers/base/power/main.c | 65 +++++++++++++++++++++++++
include/linux/pm.h | 4 ++
kernel/power/hibernate.c | 14 +++++-
4 files changed, 98 insertions(+), 2 deletions(-)

diff --git a/Documentation/driver-api/pm/devices.rst b/Documentation/driver-api/pm/devices.rst
index 36d5c9c9fd11..c2933931ca33 100644
--- a/Documentation/driver-api/pm/devices.rst
+++ b/Documentation/driver-api/pm/devices.rst
@@ -878,3 +878,20 @@ callback back-to-back with its "late" and "noirq" suspend ones. [For instance,
that is not a concern if the driver sets both ``DPM_FLAG_SMART_SUSPEND`` and
``DPM_FLAG_MAY_SKIP_RESUME`` and uses the same pair of suspend/resume callback
functions for runtime PM and system-wide suspend/resume.]
+
+
+The ``DPM_FLAG_SKIP_HIBERNATION_THAW`` Driver Flag
+--------------------------------------------------
+
+During hibernation, the PM core resumes devices with ``PMSG_THAW`` after the
+hibernation snapshot has been created so that the image can be written out.
+Some devices do not need to be resumed during that image-write phase.
+
+Drivers for such devices can set ``DPM_FLAG_SKIP_HIBERNATION_THAW`` to allow
+the PM core to leave the device suspended during the post-snapshot ``THAW``.
+If the original kernel continues running instead of powering down, the PM core
+will resume the skipped devices before userspace is thawed.
+
+Drivers must only set this flag for devices that are not needed for writing the
+hibernation image and can remain suspended until the system either powers off or
+the original kernel continues running.
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index e1b550664bab..e68b97608eb2 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -55,11 +55,13 @@ typedef int (*pm_callback_t)(struct device *);
LIST_HEAD(dpm_list);
static LIST_HEAD(dpm_prepared_list);
static LIST_HEAD(dpm_suspended_list);
+static LIST_HEAD(dpm_hibernation_skipped_list);
static LIST_HEAD(dpm_late_early_list);
static LIST_HEAD(dpm_noirq_list);

static DEFINE_MUTEX(dpm_list_mtx);
static pm_message_t pm_transition;
+static bool dpm_in_hibernation_thaw;

static DEFINE_MUTEX(async_wip_mtx);
static int async_error;
@@ -1033,6 +1035,20 @@ EXPORT_SYMBOL_GPL(dpm_resume_start);

static void async_resume(void *data, async_cookie_t cookie);

+static bool dpm_skip_hibernation_thaw(struct device *dev, pm_message_t state)
+{
+ if (!dpm_in_hibernation_thaw || state.event != PM_EVENT_THAW ||
+ !pm_hibernation_snapshot_done())
+ return false;
+
+ return dev_pm_test_driver_flags(dev, DPM_FLAG_SKIP_HIBERNATION_THAW);
+}
+
+static void dpm_set_hibernation_thaw(bool enable)
+{
+ dpm_in_hibernation_thaw = enable;
+}
+
/**
* device_resume - Execute "resume" callbacks for given device.
* @dev: Device to handle.
@@ -1141,7 +1157,11 @@ static void async_resume(void *data, async_cookie_t cookie)
{
struct device *dev = data;

+ if (dpm_skip_hibernation_thaw(dev, pm_transition))
+ goto out;
+
device_resume(dev, pm_transition, true);
+out:
put_device(dev);
}

@@ -1170,12 +1190,22 @@ void dpm_resume(pm_message_t state)
*/
list_for_each_entry(dev, &dpm_suspended_list, power.entry) {
dpm_clear_async_state(dev);
+ if (dpm_skip_hibernation_thaw(dev, state))
+ continue;
+
if (dpm_root_device(dev))
dpm_async_with_cleanup(dev, async_resume);
}

while (!list_empty(&dpm_suspended_list)) {
dev = to_device(dpm_suspended_list.next);
+ if (dpm_skip_hibernation_thaw(dev, state)) {
+ list_move_tail(&dev->power.entry,
+ &dpm_hibernation_skipped_list);
+ complete_all(&dev->power.completion);
+ continue;
+ }
+
list_move_tail(&dev->power.entry, &dpm_prepared_list);

if (!dpm_async_fn(dev, async_resume)) {
@@ -1201,6 +1231,41 @@ void dpm_resume(pm_message_t state)
trace_suspend_resume(TPS("dpm_resume"), state.event, false);
}

+/**
+ * dpm_resume_hibernation_thaw - Execute the post-snapshot THAW callbacks.
+ *
+ * Execute THAW callbacks for devices required during hibernation image writeout.
+ * Devices with DPM_FLAG_SKIP_HIBERNATION_THAW set are left suspended.
+ */
+void dpm_resume_hibernation_thaw(void)
+{
+ dpm_set_hibernation_thaw(true);
+ dpm_resume(PMSG_THAW);
+ dpm_set_hibernation_thaw(false);
+}
+
+/**
+ * dpm_resume_skipped_hibernation_devices - Resume devices skipped in THAW.
+ *
+ * Resume devices whose post-snapshot THAW callbacks were skipped because they
+ * are not required during hibernation image writeout.
+ */
+void dpm_resume_skipped_hibernation_devices(void)
+{
+ mutex_lock(&dpm_list_mtx);
+ if (list_empty(&dpm_hibernation_skipped_list)) {
+ mutex_unlock(&dpm_list_mtx);
+ return;
+ }
+
+ list_splice_tail_init(&dpm_hibernation_skipped_list,
+ &dpm_suspended_list);
+ mutex_unlock(&dpm_list_mtx);
+
+ dpm_resume(PMSG_THAW);
+ dpm_complete(PMSG_THAW);
+}
+
/**
* device_complete - Complete a PM transition for given device.
* @dev: Device to handle.
diff --git a/include/linux/pm.h b/include/linux/pm.h
index afcaaa37a812..3d7f52c63f7e 100644
--- a/include/linux/pm.h
+++ b/include/linux/pm.h
@@ -655,6 +655,7 @@ struct pm_subsys_data {
* SMART_PREPARE: Take the driver ->prepare callback return value into account.
* SMART_SUSPEND: Avoid resuming the device from runtime suspend.
* MAY_SKIP_RESUME: Allow driver "noirq" and "early" callbacks to be skipped.
+ * SKIP_HIBERNATION_THAW: Do not resume the device during post-snapshot THAW.
*
* See Documentation/driver-api/pm/devices.rst for details.
*/
@@ -662,6 +663,7 @@ struct pm_subsys_data {
#define DPM_FLAG_SMART_PREPARE BIT(1)
#define DPM_FLAG_SMART_SUSPEND BIT(2)
#define DPM_FLAG_MAY_SKIP_RESUME BIT(3)
+#define DPM_FLAG_SKIP_HIBERNATION_THAW BIT(4)

struct dev_pm_info {
pm_message_t power_state;
@@ -819,6 +821,8 @@ extern void dpm_resume_end(pm_message_t state);
extern void dpm_resume_noirq(pm_message_t state);
extern void dpm_resume_early(pm_message_t state);
extern void dpm_resume(pm_message_t state);
+void dpm_resume_hibernation_thaw(void);
+void dpm_resume_skipped_hibernation_devices(void);
extern void dpm_complete(pm_message_t state);

extern void device_pm_unlock(void);
diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c
index 045d29f55011..da3ce0382f6d 100644
--- a/kernel/power/hibernate.c
+++ b/kernel/power/hibernate.c
@@ -496,7 +496,10 @@ int hibernation_snapshot(int platform_mode)
}

msg = snapshot_done ? (error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE;
- dpm_resume(msg);
+ if (msg.event == PM_EVENT_THAW)
+ dpm_resume_hibernation_thaw();
+ else
+ dpm_resume(msg);

if (error || !snapshot_done)
pm_restore_gfp_mask();
@@ -804,6 +807,7 @@ static int load_image_and_restore(void)
int hibernate(void)
{
bool snapshot_test = false;
+ bool resume_skipped = false;
unsigned int sleep_flags;
int error;

@@ -883,11 +887,14 @@ int hibernate(void)
error = swsusp_write(flags);
in_suspend = 0;
swsusp_free();
- if (!error) {
+ if (error) {
+ resume_skipped = true;
+ } else {
if (hibernation_mode == HIBERNATION_TEST_RESUME)
snapshot_test = true;
else
power_down();
+ resume_skipped = true;
}
pm_restore_gfp_mask();
} else {
@@ -903,7 +910,10 @@ int hibernate(void)
error = swsusp_check(false);
if (!error)
error = load_image_and_restore();
+ resume_skipped = true;
}
+ if (resume_skipped)
+ dpm_resume_skipped_hibernation_devices();
thaw_processes();

/* Don't bother checking whether freezer_test_done is true */
--
2.20.1