Skip to content

Commit 74d9555

Browse files
dwmw2rafaeljw
authored andcommitted
PM: hibernate: Allow ACPI hardware signature to be honoured
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>
1 parent 0fcfb00 commit 74d9555

File tree

7 files changed

+53
-12
lines changed

7 files changed

+53
-12
lines changed

Documentation/admin-guide/kernel-parameters.txt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,23 @@
225225
For broken nForce2 BIOS resulting in XT-PIC timer.
226226

227227
acpi_sleep= [HW,ACPI] Sleep options
228-
Format: { s3_bios, s3_mode, s3_beep, s4_nohwsig,
229-
old_ordering, nonvs, sci_force_enable, nobl }
228+
Format: { s3_bios, s3_mode, s3_beep, s4_hwsig,
229+
s4_nohwsig, old_ordering, nonvs,
230+
sci_force_enable, nobl }
230231
See Documentation/power/video.rst for information on
231232
s3_bios and s3_mode.
232233
s3_beep is for debugging; it makes the PC's speaker beep
233234
as soon as the kernel's real-mode entry point is called.
235+
s4_hwsig causes the kernel to check the ACPI hardware
236+
signature during resume from hibernation, and gracefully
237+
refuse to resume if it has changed. This complies with
238+
the ACPI specification but not with reality, since
239+
Windows does not do this and many laptops do change it
240+
on docking. So the default behaviour is to allow resume
241+
and simply warn when the signature changes, unless the
242+
s4_hwsig option is enabled.
234243
s4_nohwsig prevents ACPI hardware signature from being
235-
used during resume from hibernation.
244+
used (or even warned about) during resume.
236245
old_ordering causes the ACPI 1.0 ordering of the _PTS
237246
control method, with respect to putting devices into
238247
low power states, to be enforced (the ACPI 2.0 ordering

arch/x86/kernel/acpi/sleep.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,10 @@ static int __init acpi_sleep_setup(char *str)
139139
if (strncmp(str, "s3_beep", 7) == 0)
140140
acpi_realmode_flags |= 4;
141141
#ifdef CONFIG_HIBERNATION
142+
if (strncmp(str, "s4_hwsig", 8) == 0)
143+
acpi_check_s4_hw_signature(1);
142144
if (strncmp(str, "s4_nohwsig", 10) == 0)
143-
acpi_no_s4_hw_signature();
145+
acpi_check_s4_hw_signature(0);
144146
#endif
145147
if (strncmp(str, "nonvs", 5) == 0)
146148
acpi_nvs_nosave();

drivers/acpi/sleep.c

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -877,11 +877,11 @@ static inline void acpi_sleep_syscore_init(void) {}
877877
#ifdef CONFIG_HIBERNATION
878878
static unsigned long s4_hardware_signature;
879879
static struct acpi_table_facs *facs;
880-
static bool nosigcheck;
880+
static int sigcheck = -1; /* Default behaviour is just to warn */
881881

882-
void __init acpi_no_s4_hw_signature(void)
882+
void __init acpi_check_s4_hw_signature(int check)
883883
{
884-
nosigcheck = true;
884+
sigcheck = check;
885885
}
886886

887887
static int acpi_hibernation_begin(pm_message_t stage)
@@ -1009,12 +1009,28 @@ static void acpi_sleep_hibernate_setup(void)
10091009
hibernation_set_ops(old_suspend_ordering ?
10101010
&acpi_hibernation_ops_old : &acpi_hibernation_ops);
10111011
sleep_states[ACPI_STATE_S4] = 1;
1012-
if (nosigcheck)
1012+
if (!sigcheck)
10131013
return;
10141014

10151015
acpi_get_table(ACPI_SIG_FACS, 1, (struct acpi_table_header **)&facs);
1016-
if (facs)
1016+
if (facs) {
1017+
/*
1018+
* s4_hardware_signature is the local variable which is just
1019+
* used to warn about mismatch after we're attempting to
1020+
* resume (in violation of the ACPI specification.)
1021+
*/
10171022
s4_hardware_signature = facs->hardware_signature;
1023+
1024+
if (sigcheck > 0) {
1025+
/*
1026+
* If we're actually obeying the ACPI specification
1027+
* then the signature is written out as part of the
1028+
* swsusp header, in order to allow the boot kernel
1029+
* to gracefully decline to resume.
1030+
*/
1031+
swsusp_hardware_signature = facs->hardware_signature;
1032+
}
1033+
}
10181034
}
10191035
#else /* !CONFIG_HIBERNATION */
10201036
static inline void acpi_sleep_hibernate_setup(void) {}

include/linux/acpi.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ acpi_status acpi_release_memory(acpi_handle handle, struct resource *res,
506506
int acpi_resources_are_enforced(void);
507507

508508
#ifdef CONFIG_HIBERNATION
509-
void __init acpi_no_s4_hw_signature(void);
509+
void __init acpi_check_s4_hw_signature(int check);
510510
#endif
511511

512512
#ifdef CONFIG_PM_SLEEP

include/linux/suspend.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,7 @@ extern unsigned long get_safe_page(gfp_t gfp_mask);
446446
extern asmlinkage int swsusp_arch_suspend(void);
447447
extern asmlinkage int swsusp_arch_resume(void);
448448

449+
extern u32 swsusp_hardware_signature;
449450
extern void hibernation_set_ops(const struct platform_hibernation_ops *ops);
450451
extern int hibernate(void);
451452
extern bool system_entering_hibernation(void);

kernel/power/power.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ extern int swsusp_swap_in_use(void);
170170
#define SF_PLATFORM_MODE 1
171171
#define SF_NOCOMPRESS_MODE 2
172172
#define SF_CRC32_MODE 4
173+
#define SF_HW_SIG 8
173174

174175
/* kernel/power/hibernate.c */
175176
extern int swsusp_check(void);

kernel/power/swap.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636

3737
#define HIBERNATE_SIG "S1SUSPEND"
3838

39+
u32 swsusp_hardware_signature;
40+
3941
/*
4042
* When reading an {un,}compressed image, we may restore pages in place,
4143
* in which case some architectures need these pages cleaning before they
@@ -104,7 +106,8 @@ struct swap_map_handle {
104106

105107
struct swsusp_header {
106108
char reserved[PAGE_SIZE - 20 - sizeof(sector_t) - sizeof(int) -
107-
sizeof(u32)];
109+
sizeof(u32) - sizeof(u32)];
110+
u32 hw_sig;
108111
u32 crc32;
109112
sector_t image;
110113
unsigned int flags; /* Flags to pass to the "boot" kernel */
@@ -312,7 +315,6 @@ static int hib_wait_io(struct hib_bio_batch *hb)
312315
/*
313316
* Saving part
314317
*/
315-
316318
static int mark_swapfiles(struct swap_map_handle *handle, unsigned int flags)
317319
{
318320
int error;
@@ -324,6 +326,10 @@ static int mark_swapfiles(struct swap_map_handle *handle, unsigned int flags)
324326
memcpy(swsusp_header->orig_sig,swsusp_header->sig, 10);
325327
memcpy(swsusp_header->sig, HIBERNATE_SIG, 10);
326328
swsusp_header->image = handle->first_sector;
329+
if (swsusp_hardware_signature) {
330+
swsusp_header->hw_sig = swsusp_hardware_signature;
331+
flags |= SF_HW_SIG;
332+
}
327333
swsusp_header->flags = flags;
328334
if (flags & SF_CRC32_MODE)
329335
swsusp_header->crc32 = handle->crc32;
@@ -1537,6 +1543,12 @@ int swsusp_check(void)
15371543
} else {
15381544
error = -EINVAL;
15391545
}
1546+
if (!error && swsusp_header->flags & SF_HW_SIG &&
1547+
swsusp_header->hw_sig != swsusp_hardware_signature) {
1548+
pr_info("Suspend image hardware signature mismatch (%08x now %08x); aborting resume.\n",
1549+
swsusp_header->hw_sig, swsusp_hardware_signature);
1550+
error = -EINVAL;
1551+
}
15401552

15411553
put:
15421554
if (error)

0 commit comments

Comments
 (0)