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