Commit 74d95555 authored by David Woodhouse's avatar David Woodhouse Committed by Rafael J. Wysocki
Browse files

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: default avatarDavid Woodhouse <dwmw@amazon.co.uk>
Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
parent 0fcfb00b
Loading
Loading
Loading
Loading
+12 −3
Original line number Diff line number Diff line
@@ -225,14 +225,23 @@
			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
+3 −1
Original line number Diff line number Diff line
@@ -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();
+21 −5
Original line number Diff line number Diff line
@@ -877,11 +877,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)
@@ -1009,12 +1009,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) {}
+1 −1
Original line number Diff line number Diff line
@@ -506,7 +506,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
+1 −0
Original line number Diff line number Diff line
@@ -446,6 +446,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);
Loading