]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
PM: hibernate: Allow ACPI hardware signature to be honoured
authorDavid Woodhouse <dwmw@amazon.co.uk>
Mon, 8 Nov 2021 16:09:41 +0000 (16:09 +0000)
committerDavid Woodhouse <dwmw@amazon.co.uk>
Sat, 27 Apr 2024 09:32:08 +0000 (09:32 +0000)
Theoretically, when the hardware signature in FACS changes, the OS
is supposed to gracefully decline to attempt to resume from S4:

 "If the signature has changed, OSPM will not restore the system
  context and can boot from scratch"

In practice, Windows doesn't do this and many laptop vendors do allow
the signature to change especially when docking/undocking, so it would
be a bad idea to simply comply with the specification by default in the
general case.

However, there are use cases where we do want the compliant behaviour
and we know it's safe. Specifically, when resuming virtual machines where
we know the hypervisor has changed sufficiently that resume will fail.
We really want to be able to *tell* the guest kernel not to try, so it
boots cleanly and doesn't just crash. This patch provides a way to opt
in to the spec-compliant behaviour on the command line.

A follow-up patch may do this automatically for certain "known good"
machines based on a DMI match, or perhaps just for all hypervisor
guests since there's no good reason a hypervisor would change the
hardware_signature that it exposes to guests *unless* it wants them
to obey the ACPI specification.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
(cherry picked from commit 74d9555580c48a04b2c3b742dfb0c80777aa0b26)

Documentation/admin-guide/kernel-parameters.txt
arch/x86/kernel/acpi/sleep.c
drivers/acpi/sleep.c
include/linux/acpi.h
include/linux/suspend.h
kernel/power/power.h
kernel/power/swap.c

index e61f0d038c6d769fe24be5d1aa19f4b724b43153..b27f2043f810ad207380f599e739eb1c6b2e8c46 100644 (file)
                        For broken nForce2 BIOS resulting in XT-PIC timer.
 
        acpi_sleep=     [HW,ACPI] Sleep options
-                       Format: { s3_bios, s3_mode, s3_beep, s4_nohwsig,
-                                 old_ordering, nonvs, sci_force_enable, nobl }
+                       Format: { s3_bios, s3_mode, s3_beep, s4_hwsig,
+                                 s4_nohwsig, old_ordering, nonvs,
+                                 sci_force_enable, nobl }
                        See Documentation/power/video.rst for information on
                        s3_bios and s3_mode.
                        s3_beep is for debugging; it makes the PC's speaker beep
                        as soon as the kernel's real-mode entry point is called.
+                       s4_hwsig causes the kernel to check the ACPI hardware
+                       signature during resume from hibernation, and gracefully
+                       refuse to resume if it has changed. This complies with
+                       the ACPI specification but not with reality, since
+                       Windows does not do this and many laptops do change it
+                       on docking. So the default behaviour is to allow resume
+                       and simply warn when the signature changes, unless the
+                       s4_hwsig option is enabled.
                        s4_nohwsig prevents ACPI hardware signature from being
-                       used during resume from hibernation.
+                       used (or even warned about) during resume.
                        old_ordering causes the ACPI 1.0 ordering of the _PTS
                        control method, with respect to putting devices into
                        low power states, to be enforced (the ACPI 2.0 ordering
index 3f85fcae450ce2fb89b8c466d6872815491f493f..1e97f944b47d7c4de906f26c02a21b6eb2cee142 100644 (file)
@@ -139,8 +139,10 @@ static int __init acpi_sleep_setup(char *str)
                if (strncmp(str, "s3_beep", 7) == 0)
                        acpi_realmode_flags |= 4;
 #ifdef CONFIG_HIBERNATION
+               if (strncmp(str, "s4_hwsig", 8) == 0)
+                       acpi_check_s4_hw_signature(1);
                if (strncmp(str, "s4_nohwsig", 10) == 0)
-                       acpi_no_s4_hw_signature();
+                       acpi_check_s4_hw_signature(0);
 #endif
                if (strncmp(str, "nonvs", 5) == 0)
                        acpi_nvs_nosave();
index 95deb55fb9a8c036f85aa6e01a2590b698c2536f..d39f5a9fcae6dd1af730233c392af0fb64f11e08 100644 (file)
@@ -890,11 +890,11 @@ static inline void acpi_sleep_syscore_init(void) {}
 #ifdef CONFIG_HIBERNATION
 static unsigned long s4_hardware_signature;
 static struct acpi_table_facs *facs;
-static bool nosigcheck;
+static int sigcheck = -1; /* Default behaviour is just to warn */
 
-void __init acpi_no_s4_hw_signature(void)
+void __init acpi_check_s4_hw_signature(int check)
 {
-       nosigcheck = true;
+       sigcheck = check;
 }
 
 static int acpi_hibernation_begin(pm_message_t stage)
@@ -1022,12 +1022,28 @@ static void acpi_sleep_hibernate_setup(void)
        hibernation_set_ops(old_suspend_ordering ?
                        &acpi_hibernation_ops_old : &acpi_hibernation_ops);
        sleep_states[ACPI_STATE_S4] = 1;
-       if (nosigcheck)
+       if (!sigcheck)
                return;
 
        acpi_get_table(ACPI_SIG_FACS, 1, (struct acpi_table_header **)&facs);
-       if (facs)
+       if (facs) {
+               /*
+                * s4_hardware_signature is the local variable which is just
+                * used to warn about mismatch after we're attempting to
+                * resume (in violation of the ACPI specification.)
+                */
                s4_hardware_signature = facs->hardware_signature;
+
+               if (sigcheck > 0) {
+                       /*
+                        * If we're actually obeying the ACPI specification
+                        * then the signature is written out as part of the
+                        * swsusp header, in order to allow the boot kernel
+                        * to gracefully decline to resume.
+                        */
+                       swsusp_hardware_signature = facs->hardware_signature;
+               }
+       }
 }
 #else /* !CONFIG_HIBERNATION */
 static inline void acpi_sleep_hibernate_setup(void) {}
index a23a5aea9c817798f4ca1f6d23b8a5c062bd18da..eb64c9410d39844ceae8ec38df5f983e7adcc086 100644 (file)
@@ -507,7 +507,7 @@ acpi_status acpi_release_memory(acpi_handle handle, struct resource *res,
 int acpi_resources_are_enforced(void);
 
 #ifdef CONFIG_HIBERNATION
-void __init acpi_no_s4_hw_signature(void);
+void __init acpi_check_s4_hw_signature(int check);
 #endif
 
 #ifdef CONFIG_PM_SLEEP
index 4bcd65679cee0c46d48040330572d40d26715903..300273ff40cc1c8f39d6f44472314a6c8e4229d9 100644 (file)
@@ -438,6 +438,7 @@ extern unsigned long get_safe_page(gfp_t gfp_mask);
 extern asmlinkage int swsusp_arch_suspend(void);
 extern asmlinkage int swsusp_arch_resume(void);
 
+extern u32 swsusp_hardware_signature;
 extern void hibernation_set_ops(const struct platform_hibernation_ops *ops);
 extern int hibernate(void);
 extern bool system_entering_hibernation(void);
index 778bf431ec0243e6cebe5cdac5321b6a99805ab9..bb41a1a1c0d3babc4d96d49ecb55ef571e8b8764 100644 (file)
@@ -168,6 +168,7 @@ extern int swsusp_swap_in_use(void);
 #define SF_PLATFORM_MODE       1
 #define SF_NOCOMPRESS_MODE     2
 #define SF_CRC32_MODE          4
+#define SF_HW_SIG              8
 
 /* kernel/power/hibernate.c */
 extern int swsusp_check(void);
index 6146c746371caaa251fe63f8fa101ab79b85c506..5af8253e34b3be255e312da1a7b3d22da6439346 100644 (file)
@@ -36,6 +36,8 @@
 
 #define HIBERNATE_SIG  "S1SUSPEND"
 
+u32 swsusp_hardware_signature;
+
 /*
  * When reading an {un,}compressed image, we may restore pages in place,
  * in which case some architectures need these pages cleaning before they
@@ -104,7 +106,8 @@ struct swap_map_handle {
 
 struct swsusp_header {
        char reserved[PAGE_SIZE - 20 - sizeof(sector_t) - sizeof(int) -
-                     sizeof(u32)];
+                     sizeof(u32) - sizeof(u32)];
+       u32     hw_sig;
        u32     crc32;
        sector_t image;
        unsigned int flags;     /* Flags to pass to the "boot" kernel */
@@ -312,7 +315,6 @@ static int hib_wait_io(struct hib_bio_batch *hb)
 /*
  * Saving part
  */
-
 static int mark_swapfiles(struct swap_map_handle *handle, unsigned int flags)
 {
        int error;
@@ -324,6 +326,10 @@ static int mark_swapfiles(struct swap_map_handle *handle, unsigned int flags)
                memcpy(swsusp_header->orig_sig,swsusp_header->sig, 10);
                memcpy(swsusp_header->sig, HIBERNATE_SIG, 10);
                swsusp_header->image = handle->first_sector;
+               if (swsusp_hardware_signature) {
+                       swsusp_header->hw_sig = swsusp_hardware_signature;
+                       flags |= SF_HW_SIG;
+               }
                swsusp_header->flags = flags;
                if (flags & SF_CRC32_MODE)
                        swsusp_header->crc32 = handle->crc32;
@@ -1543,6 +1549,12 @@ int swsusp_check(void)
                } else {
                        error = -EINVAL;
                }
+               if (!error && swsusp_header->flags & SF_HW_SIG &&
+                   swsusp_header->hw_sig != swsusp_hardware_signature) {
+                       pr_info("Suspend image hardware signature mismatch (%08x now %08x); aborting resume.\n",
+                               swsusp_header->hw_sig, swsusp_hardware_signature);
+                       error = -EINVAL;
+               }
 
 put:
                if (error)