[PATCH 0/2] PM: hibernate: add helper to detect image-write phase

Haowen Tu posted 2 patches 1 month, 2 weeks ago
drivers/media/usb/uvc/uvc_video.c | 10 ++++++++++
include/linux/suspend.h           |  2 ++
kernel/power/hibernate.c          | 19 +++++++++++++++++++
3 files changed, 31 insertions(+)
[PATCH 0/2] PM: hibernate: add helper to detect image-write phase
Posted by Haowen Tu 1 month, 2 weeks ago
During S4 hibernation, after create_image() saves the memory snapshot,
the kernel resumes devices with PMSG_THAW solely to write the hibernation
image to storage before final powerdown.  Drivers unrelated to storage I/O
have no reason to reinitialize during this transient phase.

Drivers using dev_pm_ops already have separate .thaw and .restore
callbacks and can handle this natively.  However, usb_driver.resume
takes no pm_message_t argument, so a USB driver cannot distinguish
PMSG_THAW from PMSG_RESTORE without an out-of-band query.

This series adds pm_hibernation_storing_image(), a small helper that
returns true during the image-write window.  As a first user, the UVC
driver uses it to skip resume in uvc_video_resume() until the real
PMSG_RESTORE wake-up.

Tested with hibernation image written to local storage and resumed from
disk on a system with a USB UVC camera attached.

Haowen Tu (2):
  pm/hibernate: add pm_hibernation_storing_image() helper
  media: uvcvideo: skip resume when writing hibernation image

 drivers/media/usb/uvc/uvc_video.c | 10 ++++++++++
 include/linux/suspend.h           |  2 ++
 kernel/power/hibernate.c          | 19 +++++++++++++++++++
 3 files changed, 31 insertions(+)

-- 
2.20.1
[PATCH v2 0/2] PM: hibernate: skip UVC resume after snapshot
Posted by Haowen Tu 2 weeks, 5 days ago
During hibernation, after the memory snapshot has been created, the
kernel briefly resumes devices with PMSG_THAW to write the snapshot to
storage before powering off.  USB driver resume callbacks do not receive
the hibernation PM message, so uvcvideo cannot distinguish this transient
image-write phase from the final restore path.

This series adds pm_hibernation_snapshot_done() and uses it in uvcvideo
to avoid reinitializing the camera during the image-write phase.

This is not intended to make every leaf driver special-case hibernation
THAW.  A generic USB-core skip would not be safe because some USB
interfaces may be part of the storage path, or otherwise be required by
dependencies during image writeout.  The helper is an opt-in mechanism
for drivers with a concrete reason to avoid reinitializing hardware in
this transient phase.  In this case, uvcvideo has a user-visible side
effect, the camera indicator LED, while the camera is not part of the
image writeout path.

Changes in v2:
- Rename pm_hibernation_storing_image() to
  pm_hibernation_snapshot_done().
- Clear in_suspend before releasing snapshot memory on the hibernation
  failure paths and after swsusp_write() returns.
- Update the uvcvideo call site for the new helper name.

Haowen Tu (2):
  PM: hibernate: add pm_hibernation_snapshot_done() helper
  media: uvcvideo: skip resume after hibernation snapshot

 drivers/media/usb/uvc/uvc_video.c | 12 ++++++++++++
 include/linux/suspend.h           |  2 ++
 kernel/power/hibernate.c          | 31 +++++++++++++++++++++++++++----
 3 files changed, 41 insertions(+), 4 deletions(-)

-- 
2.20.1
[PATCH v2 1/2] PM: hibernate: add pm_hibernation_snapshot_done() helper
Posted by Haowen Tu 2 weeks, 5 days ago
During hibernation, after create_image() saves the memory snapshot, the
kernel resumes devices with PMSG_THAW solely to write the hibernation
image to storage, then powers off.  Drivers for hardware not involved in
storage I/O have no reason to reinitialize during this transient phase.

Some subsystems, such as USB, do not expose the hibernation PM message
to driver resume callbacks, so drivers there need an explicit query to
distinguish the image-write phase from the final restore path.  Export
pm_hibernation_snapshot_done() for this purpose.

The implementation returns !!in_suspend, which is set to 1 in
create_image() just before swsusp_arch_suspend().  Because in_suspend is
marked __nosavedata, it is not saved into the hibernation image; on the
restore path the variable remains 0, so the helper correctly returns
false during PMSG_RESTORE device resume.

Clear in_suspend before releasing snapshot memory on hibernation failure
paths and after swsusp_write() returns, so the helper does not report a
stale snapshot after the snapshot pages have been released.

Signed-off-by: Haowen Tu <tuhaowen@uniontech.com>
---
Changes in v2:
- Rename pm_hibernation_storing_image() to
  pm_hibernation_snapshot_done().
- Clear in_suspend before releasing snapshot memory on failure paths and
  after swsusp_write() returns.

 include/linux/suspend.h  |  2 ++
 kernel/power/hibernate.c | 31 +++++++++++++++++++++++++++----
 2 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/include/linux/suspend.h b/include/linux/suspend.h
index b02876f1ae38..78e7e33c3d19 100644
--- a/include/linux/suspend.h
+++ b/include/linux/suspend.h
@@ -393,6 +393,7 @@ extern void hibernation_set_ops(const struct platform_hibernation_ops *ops);
 extern int hibernate(void);
 extern bool system_entering_hibernation(void);
 extern bool hibernation_available(void);
+extern bool pm_hibernation_snapshot_done(void);
 asmlinkage int swsusp_save(void);
 extern struct pbe *restore_pblist;
 int pfn_is_nosave(unsigned long pfn);
@@ -412,6 +413,7 @@ static inline void hibernation_set_ops(const struct platform_hibernation_ops *op
 static inline int hibernate(void) { return -ENOSYS; }
 static inline bool system_entering_hibernation(void) { return false; }
 static inline bool hibernation_available(void) { return false; }
+static inline bool pm_hibernation_snapshot_done(void) { return false; }
 
 static inline int hibernate_quiet_exec(int (*func)(void *data), void *data) {
 	return -ENOTSUPP;
diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c
index af8d07bafe02..47047937e262 100644
--- a/kernel/power/hibernate.c
+++ b/kernel/power/hibernate.c
@@ -113,6 +113,25 @@ bool hibernation_available(void)
 		!secretmem_active() && !cxl_mem_active();
 }
 
+/**
+ * pm_hibernation_snapshot_done - check if a hibernation snapshot is available
+ *
+ * After create_image() saves a memory snapshot, the kernel briefly resumes
+ * devices with PMSG_THAW to write the image to storage before final powerdown.
+ * Drivers that do not need to participate in image writing may call this
+ * helper from their resume callbacks to skip unnecessary hardware
+ * initialization during that transient phase.
+ *
+ * Context: May be called from device PM callbacks.
+ * Return: %true if a hibernation snapshot has been taken and has not been
+ *         released yet.
+ */
+bool pm_hibernation_snapshot_done(void)
+{
+	return !!in_suspend;
+}
+EXPORT_SYMBOL_GPL(pm_hibernation_snapshot_done);
+
 /**
  * hibernation_set_ops - Set the global hibernate operations.
  * @ops: Hibernation operations to use in subsequent hibernation transitions.
@@ -418,6 +437,7 @@ static void shrink_shmem_memory(void)
 int hibernation_snapshot(int platform_mode)
 {
 	pm_message_t msg;
+	bool snapshot_done;
 	int error;
 
 	pm_suspend_clear_flags();
@@ -474,15 +494,18 @@ int hibernation_snapshot(int platform_mode)
 	 * returns here (1) after the image has been created or the
 	 * image creation has failed and (2) after a successful restore.
 	 */
+	snapshot_done = in_suspend;
 
 	/* We may need to release the preallocated image pages here. */
-	if (error || !in_suspend)
+	if (error || !snapshot_done) {
+		in_suspend = 0;
 		swsusp_free();
+	}
 
-	msg = in_suspend ? (error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE;
+	msg = snapshot_done ? (error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE;
 	dpm_resume(msg);
 
-	if (error || !in_suspend)
+	if (error || !snapshot_done)
 		pm_restore_gfp_mask();
 
 	console_resume_all();
@@ -865,6 +888,7 @@ int hibernate(void)
 
 		pm_pr_dbg("Writing hibernation image.\n");
 		error = swsusp_write(flags);
+		in_suspend = 0;
 		swsusp_free();
 		if (!error) {
 			if (hibernation_mode == HIBERNATION_TEST_RESUME)
@@ -872,7 +896,6 @@ int hibernate(void)
 			else
 				power_down();
 		}
-		in_suspend = 0;
 		pm_restore_gfp_mask();
 	} else {
 		pm_pr_dbg("Hibernation image restored successfully.\n");
-- 
2.20.1
Re: [PATCH v2 1/2] PM: hibernate: add pm_hibernation_snapshot_done() helper
Posted by Rafael J. Wysocki 2 weeks, 1 day ago
On Thu, May 28, 2026 at 10:19 AM Haowen Tu <tuhaowen@uniontech.com> wrote:
>
> During hibernation, after create_image() saves the memory snapshot, the
> kernel resumes devices with PMSG_THAW solely to write the hibernation
> image to storage, then powers off.  Drivers for hardware not involved in
> storage I/O have no reason to reinitialize during this transient phase.

They do have a reason for doing it.

Their poweroff (or shutdown) callbacks will be called while preparing
to power off the system subsequently and they need to be ready for
that.  The most straightforward way to achieve this is to resume so
they can "suspend" again.

> Some subsystems, such as USB, do not expose the hibernation PM message
> to driver resume callbacks, so drivers there need an explicit query to
> distinguish the image-write phase from the final restore path.  Export
> pm_hibernation_snapshot_done() for this purpose.
>
> The implementation returns !!in_suspend, which is set to 1 in
> create_image() just before swsusp_arch_suspend().  Because in_suspend is
> marked __nosavedata, it is not saved into the hibernation image; on the
> restore path the variable remains 0, so the helper correctly returns
> false during PMSG_RESTORE device resume.
>
> Clear in_suspend before releasing snapshot memory on hibernation failure
> paths and after swsusp_write() returns, so the helper does not report a
> stale snapshot after the snapshot pages have been released.

This last piece needs to be split off into a separate patch.

> Signed-off-by: Haowen Tu <tuhaowen@uniontech.com>
> ---
> Changes in v2:
> - Rename pm_hibernation_storing_image() to
>   pm_hibernation_snapshot_done().
> - Clear in_suspend before releasing snapshot memory on failure paths and
>   after swsusp_write() returns.
>
>  include/linux/suspend.h  |  2 ++
>  kernel/power/hibernate.c | 31 +++++++++++++++++++++++++++----
>  2 files changed, 29 insertions(+), 4 deletions(-)
>
> diff --git a/include/linux/suspend.h b/include/linux/suspend.h
> index b02876f1ae38..78e7e33c3d19 100644
> --- a/include/linux/suspend.h
> +++ b/include/linux/suspend.h
> @@ -393,6 +393,7 @@ extern void hibernation_set_ops(const struct platform_hibernation_ops *ops);
>  extern int hibernate(void);
>  extern bool system_entering_hibernation(void);
>  extern bool hibernation_available(void);
> +extern bool pm_hibernation_snapshot_done(void);
>  asmlinkage int swsusp_save(void);
>  extern struct pbe *restore_pblist;
>  int pfn_is_nosave(unsigned long pfn);
> @@ -412,6 +413,7 @@ static inline void hibernation_set_ops(const struct platform_hibernation_ops *op
>  static inline int hibernate(void) { return -ENOSYS; }
>  static inline bool system_entering_hibernation(void) { return false; }
>  static inline bool hibernation_available(void) { return false; }
> +static inline bool pm_hibernation_snapshot_done(void) { return false; }
>
>  static inline int hibernate_quiet_exec(int (*func)(void *data), void *data) {
>         return -ENOTSUPP;
> diff --git a/kernel/power/hibernate.c b/kernel/power/hibernate.c
> index af8d07bafe02..47047937e262 100644
> --- a/kernel/power/hibernate.c
> +++ b/kernel/power/hibernate.c
> @@ -113,6 +113,25 @@ bool hibernation_available(void)
>                 !secretmem_active() && !cxl_mem_active();
>  }
>
> +/**
> + * pm_hibernation_snapshot_done - check if a hibernation snapshot is available
> + *
> + * After create_image() saves a memory snapshot, the kernel briefly resumes
> + * devices with PMSG_THAW to write the image to storage before final powerdown.
> + * Drivers that do not need to participate in image writing may call this
> + * helper from their resume callbacks to skip unnecessary hardware
> + * initialization during that transient phase.
> + *
> + * Context: May be called from device PM callbacks.
> + * Return: %true if a hibernation snapshot has been taken and has not been
> + *         released yet.
> + */
> +bool pm_hibernation_snapshot_done(void)
> +{
> +       return !!in_suspend;
> +}
> +EXPORT_SYMBOL_GPL(pm_hibernation_snapshot_done);
> +
>  /**
>   * hibernation_set_ops - Set the global hibernate operations.
>   * @ops: Hibernation operations to use in subsequent hibernation transitions.
> @@ -418,6 +437,7 @@ static void shrink_shmem_memory(void)
>  int hibernation_snapshot(int platform_mode)
>  {
>         pm_message_t msg;
> +       bool snapshot_done;
>         int error;
>
>         pm_suspend_clear_flags();
> @@ -474,15 +494,18 @@ int hibernation_snapshot(int platform_mode)
>          * returns here (1) after the image has been created or the
>          * image creation has failed and (2) after a successful restore.
>          */
> +       snapshot_done = in_suspend;
>
>         /* We may need to release the preallocated image pages here. */
> -       if (error || !in_suspend)
> +       if (error || !snapshot_done) {
> +               in_suspend = 0;
>                 swsusp_free();
> +       }
>
> -       msg = in_suspend ? (error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE;
> +       msg = snapshot_done ? (error ? PMSG_RECOVER : PMSG_THAW) : PMSG_RESTORE;
>         dpm_resume(msg);
>
> -       if (error || !in_suspend)
> +       if (error || !snapshot_done)
>                 pm_restore_gfp_mask();
>
>         console_resume_all();
> @@ -865,6 +888,7 @@ int hibernate(void)
>
>                 pm_pr_dbg("Writing hibernation image.\n");
>                 error = swsusp_write(flags);
> +               in_suspend = 0;
>                 swsusp_free();
>                 if (!error) {
>                         if (hibernation_mode == HIBERNATION_TEST_RESUME)
> @@ -872,7 +896,6 @@ int hibernate(void)
>                         else
>                                 power_down();
>                 }
> -               in_suspend = 0;
>                 pm_restore_gfp_mask();
>         } else {
>                 pm_pr_dbg("Hibernation image restored successfully.\n");
> --
> 2.20.1
Re: [PATCH v2 1/2] PM: hibernate: add pm_hibernation_snapshot_done() helper
Posted by Haowen Tu 2 weeks ago
Hi Rafael,

On Mon, Jun 01, 2026 at 08:22:00PM +0200, Rafael J. Wysocki wrote:
> On Thu, May 28, 2026 at 10:19 AM Haowen Tu <tuhaowen@uniontech.com> wrote:
> >
> > During hibernation, after create_image() saves the memory snapshot, the
> > kernel resumes devices with PMSG_THAW solely to write the hibernation
> > image to storage, then powers off.  Drivers for hardware not involved in
> > storage I/O have no reason to reinitialize during this transient phase.
>
> They do have a reason for doing it.
>
> Their poweroff (or shutdown) callbacks will be called while preparing
> to power off the system subsequently and they need to be ready for
> that.  The most straightforward way to achieve this is to resume so
> they can "suspend" again.

Thanks for pointing this out.

My understanding is that the hibernation image contains the state from
the snapshot point, that is before the subsequent THAW and image-write
phase.  Therefore, on the later restore path, the kernel resumes from the
state captured at the snapshot point and still goes through the normal
PMSG_RESTORE resume path.  The THAW activity after the snapshot is not
part of the restored image state.

That said, I agree that some devices may need to be resumed before the
final poweroff/shutdown callbacks run, so the wording in the changelog is
too broad.  I did not mean to suggest that all non-storage devices can or
should skip THAW resume.

The helper is meant to expose this PM state, not to prescribe that
drivers should skip THAW resume.  Whether a driver uses it would remain a
driver-specific decision, based on whether skipping the hardware
reinitialization is safe for that device, including its subsequent
poweroff/shutdown handling.  Drivers that need the normal THAW resume
before poweroff/shutdown would simply not use it.

In this series the user is uvcvideo.  For USB devices, and specifically
for UVC cameras, the device is hotpluggable and the driver already needs
to tolerate device removal and disconnect-like conditions.  The UVC
driver also does not need the camera streaming engine to be restarted in
order to write the hibernation image, while restarting it has a visible
side effect by turning the camera LED back on.  The check is placed after
the driver's frozen state is cleared, so if image writeout fails, the
driver is not left in the frozen state.

I will reword the changelog in the next version to avoid implying that
non-storage devices generally have no reason to resume during THAW.

> > Clear in_suspend before releasing snapshot memory on hibernation failure
> > paths and after swsusp_write() returns, so the helper does not report a
> > stale snapshot after the snapshot pages have been released.
>
> This last piece needs to be split off into a separate patch.

Sure, I will split the in_suspend cleanup into a separate patch in the
next version.

I will wait for your feedback before sending the next version.

Thanks,
Haowen
[PATCH v2 2/2] media: uvcvideo: skip resume after hibernation snapshot
Posted by Haowen Tu 2 weeks, 5 days ago
When a UVC camera is in active use and the system enters S4 hibernation,
the camera is suspended as part of the normal device freeze sequence.
However, after create_image() saves the memory snapshot, the kernel
briefly resumes all devices with PMSG_THAW to write the hibernation
image to storage.  This causes uvc_video_resume() to run and
reinitialize the camera hardware, which visibly turns on the camera
indicator LED during this intermediate phase even though the system is
about to power off.

The UVC device is not needed during the image-write window, where the
system only needs devices required for writing the hibernation image.
USB .resume callbacks do not receive pm_message_t, unlike .suspend, so
use the PM-layer helper to detect this phase.

This is intentionally handled in uvcvideo rather than in USB core.  USB
core cannot skip all interface resume callbacks during hibernation THAW,
because some USB interfaces may be part of the image writeout path or
otherwise be required by dependencies.  uvcvideo has a concrete
user-visible side effect from reinitializing hardware in this transient
phase, and it is not involved in image writeout.

The check is placed after stream->frozen is cleared and the clock is
reset, so that driver state remains consistent if the image write fails
and the system resumes normally instead of powering off.  In that case
userspace will need to restart the stream, but the driver will not be
left with stale frozen state.

Tested with hibernation image written to local storage and resumed from
disk on a system with a USB UVC camera attached; the camera LED remains
off during image writing and the video stream resumes correctly after
restore.

Signed-off-by: Haowen Tu <tuhaowen@uniontech.com>
---
Changes in v2:
- Use pm_hibernation_snapshot_done() after the PM helper was renamed.

 drivers/media/usb/uvc/uvc_video.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c
index f6c8e3223796..9fa649fd47e0 100644
--- a/drivers/media/usb/uvc/uvc_video.c
+++ b/drivers/media/usb/uvc/uvc_video.c
@@ -12,6 +12,7 @@
 #include <linux/list.h>
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/suspend.h>
 #include <linux/usb.h>
 #include <linux/usb/hcd.h>
 #include <linux/videodev2.h>
@@ -2151,6 +2152,17 @@ int uvc_video_resume(struct uvc_streaming *stream, int reset)
 	if (!uvc_queue_streaming(&stream->queue))
 		return 0;
 
+	/*
+	 * During hibernation image writing (PMSG_THAW), the kernel briefly
+	 * resumes devices after the snapshot has been created.  Skip hardware
+	 * reinitialization to avoid USB traffic and the spurious camera LED
+	 * activation.  stream->frozen has already been cleared, so if the
+	 * image write fails and the system resumes normally, driver state
+	 * remains consistent; userspace will need to restart the stream.
+	 */
+	if (pm_hibernation_snapshot_done())
+		return 0;
+
 	ret = uvc_commit_video(stream, &stream->ctrl);
 	if (ret < 0)
 		return ret;
-- 
2.20.1