2008-01-30 20:32:54 +08:00
|
|
|
/*
|
|
|
|
* sleep.c - x86-specific ACPI sleep support.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2001-2003 Patrick Mochel
|
2010-07-18 20:27:13 +08:00
|
|
|
* Copyright (C) 2001-2003 Pavel Machek <pavel@ucw.cz>
|
2008-01-30 20:32:54 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/acpi.h>
|
|
|
|
#include <linux/bootmem.h>
|
2010-08-26 04:39:17 +08:00
|
|
|
#include <linux/memblock.h>
|
2008-01-30 20:32:54 +08:00
|
|
|
#include <linux/dmi.h>
|
|
|
|
#include <linux/cpumask.h>
|
2008-07-18 02:29:24 +08:00
|
|
|
#include <asm/segment.h>
|
2008-10-17 07:26:27 +08:00
|
|
|
#include <asm/desc.h>
|
2010-08-28 21:58:33 +08:00
|
|
|
#include <asm/pgtable.h>
|
2011-02-07 13:16:09 +08:00
|
|
|
#include <asm/cacheflush.h>
|
2012-05-09 02:22:29 +08:00
|
|
|
#include <asm/realmode.h>
|
2010-08-28 21:58:33 +08:00
|
|
|
|
2012-05-09 02:22:42 +08:00
|
|
|
#include "../../realmode/rm/wakeup.h"
|
2008-04-11 05:28:10 +08:00
|
|
|
#include "sleep.h"
|
2008-01-30 20:32:54 +08:00
|
|
|
|
|
|
|
unsigned long acpi_realmode_flags;
|
|
|
|
|
2008-08-04 01:25:48 +08:00
|
|
|
#if defined(CONFIG_SMP) && defined(CONFIG_64BIT)
|
2008-10-10 00:56:21 +08:00
|
|
|
static char temp_stack[4096];
|
2008-04-11 05:28:10 +08:00
|
|
|
#endif
|
2008-01-30 20:32:54 +08:00
|
|
|
|
2013-10-31 09:31:18 +08:00
|
|
|
/**
|
|
|
|
* x86_acpi_enter_sleep_state - enter sleep state
|
|
|
|
* @state: Sleep state to enter.
|
|
|
|
*
|
|
|
|
* Wrapper around acpi_enter_sleep_state() to be called by assmebly.
|
|
|
|
*/
|
2014-05-02 06:44:37 +08:00
|
|
|
acpi_status asmlinkage __visible x86_acpi_enter_sleep_state(u8 state)
|
2013-10-31 09:31:18 +08:00
|
|
|
{
|
|
|
|
return acpi_enter_sleep_state(state);
|
|
|
|
}
|
|
|
|
|
2008-01-30 20:32:54 +08:00
|
|
|
/**
|
2013-05-15 01:09:16 +08:00
|
|
|
* x86_acpi_suspend_lowlevel - save kernel state
|
2008-01-30 20:32:54 +08:00
|
|
|
*
|
|
|
|
* Create an identity mapped page table and copy the wakeup routine to
|
|
|
|
* low memory.
|
|
|
|
*/
|
2013-05-15 01:09:16 +08:00
|
|
|
int x86_acpi_suspend_lowlevel(void)
|
2008-01-30 20:32:54 +08:00
|
|
|
{
|
2012-05-09 02:22:29 +08:00
|
|
|
struct wakeup_header *header =
|
2012-05-09 02:22:41 +08:00
|
|
|
(struct wakeup_header *) __va(real_mode_header->wakeup_header);
|
2008-04-11 05:28:10 +08:00
|
|
|
|
2011-02-15 07:42:46 +08:00
|
|
|
if (header->signature != WAKEUP_HEADER_SIGNATURE) {
|
2008-04-11 05:28:10 +08:00
|
|
|
printk(KERN_ERR "wakeup header does not match\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
header->video_mode = saved_video_mode;
|
|
|
|
|
2012-09-27 06:02:34 +08:00
|
|
|
header->pmode_behavior = 0;
|
|
|
|
|
2008-04-11 05:28:10 +08:00
|
|
|
#ifndef CONFIG_64BIT
|
2013-04-06 04:42:23 +08:00
|
|
|
native_store_gdt((struct desc_ptr *)&header->pmode_gdt);
|
2008-04-11 05:28:10 +08:00
|
|
|
|
2013-07-13 07:48:12 +08:00
|
|
|
/*
|
|
|
|
* We have to check that we can write back the value, and not
|
|
|
|
* just read it. At least on 90 nm Pentium M (Family 6, Model
|
|
|
|
* 13), reading an invalid MSR is not guaranteed to trap, see
|
|
|
|
* Erratum X4 in "Intel Pentium M Processor on 90 nm Process
|
|
|
|
* with 2-MB L2 Cache and Intel® Processor A100 and A110 on 90
|
|
|
|
* nm process with 512-KB L2 Cache Specification Update".
|
|
|
|
*/
|
2012-09-27 06:02:34 +08:00
|
|
|
if (!rdmsr_safe(MSR_EFER,
|
|
|
|
&header->pmode_efer_low,
|
2013-07-13 07:48:12 +08:00
|
|
|
&header->pmode_efer_high) &&
|
|
|
|
!wrmsr_safe(MSR_EFER,
|
|
|
|
header->pmode_efer_low,
|
|
|
|
header->pmode_efer_high))
|
2012-09-27 06:02:34 +08:00
|
|
|
header->pmode_behavior |= (1 << WAKEUP_BEHAVIOR_RESTORE_EFER);
|
2008-04-11 05:28:10 +08:00
|
|
|
#endif /* !CONFIG_64BIT */
|
|
|
|
|
|
|
|
header->pmode_cr0 = read_cr0();
|
2012-09-27 06:02:34 +08:00
|
|
|
if (__this_cpu_read(cpu_info.cpuid_level) >= 0) {
|
|
|
|
header->pmode_cr4 = read_cr4();
|
|
|
|
header->pmode_behavior |= (1 << WAKEUP_BEHAVIOR_RESTORE_CR4);
|
|
|
|
}
|
x86, suspend: Restore MISC_ENABLE MSR in realmode wakeup
Some BIOSes will reset the Intel MISC_ENABLE MSR (specifically the
XD_DISABLE bit) when resuming from S3, which can interact poorly with
ebba638ae723d8a8fc2f7abce5ec18b688b791d7. In 32bit PAE mode, this can
lead to a fault when EFER is restored by the kernel wakeup routines,
due to it setting the NX bit for a CPU that (thanks to the BIOS reset)
now incorrectly thinks it lacks the NX feature. (64bit is not affected
because it uses a common CPU bring-up that specifically handles the
XD_DISABLE bit.)
The need for MISC_ENABLE being restored so early is specific to the S3
resume path. Normally, MISC_ENABLE is saved in save_processor_state(),
but this happens after the resume header is created, so just reproduce
the logic here. (acpi_suspend_lowlevel() creates the header, calls
do_suspend_lowlevel, which calls save_processor_state(), so the saved
processor context isn't available during resume header creation.)
[ hpa: Consider for stable if OK in mainline ]
Signed-off-by: Kees Cook <kees.cook@canonical.com>
Link: http://lkml.kernel.org/r/20110707011034.GA8523@outflux.net
Signed-off-by: H. Peter Anvin <hpa@zytor.com>
Cc: Rafael J. Wysocki <rjw@sisk.pl>
Cc: <stable@kernel.org> 2.6.38+
2011-07-07 09:10:34 +08:00
|
|
|
if (!rdmsr_safe(MSR_IA32_MISC_ENABLE,
|
|
|
|
&header->pmode_misc_en_low,
|
2013-07-13 07:48:12 +08:00
|
|
|
&header->pmode_misc_en_high) &&
|
|
|
|
!wrmsr_safe(MSR_IA32_MISC_ENABLE,
|
|
|
|
header->pmode_misc_en_low,
|
|
|
|
header->pmode_misc_en_high))
|
x86, suspend: Restore MISC_ENABLE MSR in realmode wakeup
Some BIOSes will reset the Intel MISC_ENABLE MSR (specifically the
XD_DISABLE bit) when resuming from S3, which can interact poorly with
ebba638ae723d8a8fc2f7abce5ec18b688b791d7. In 32bit PAE mode, this can
lead to a fault when EFER is restored by the kernel wakeup routines,
due to it setting the NX bit for a CPU that (thanks to the BIOS reset)
now incorrectly thinks it lacks the NX feature. (64bit is not affected
because it uses a common CPU bring-up that specifically handles the
XD_DISABLE bit.)
The need for MISC_ENABLE being restored so early is specific to the S3
resume path. Normally, MISC_ENABLE is saved in save_processor_state(),
but this happens after the resume header is created, so just reproduce
the logic here. (acpi_suspend_lowlevel() creates the header, calls
do_suspend_lowlevel, which calls save_processor_state(), so the saved
processor context isn't available during resume header creation.)
[ hpa: Consider for stable if OK in mainline ]
Signed-off-by: Kees Cook <kees.cook@canonical.com>
Link: http://lkml.kernel.org/r/20110707011034.GA8523@outflux.net
Signed-off-by: H. Peter Anvin <hpa@zytor.com>
Cc: Rafael J. Wysocki <rjw@sisk.pl>
Cc: <stable@kernel.org> 2.6.38+
2011-07-07 09:10:34 +08:00
|
|
|
header->pmode_behavior |=
|
|
|
|
(1 << WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE);
|
2008-04-11 05:28:10 +08:00
|
|
|
header->realmode_flags = acpi_realmode_flags;
|
|
|
|
header->real_magic = 0x12345678;
|
|
|
|
|
|
|
|
#ifndef CONFIG_64BIT
|
|
|
|
header->pmode_entry = (u32)&wakeup_pmode_return;
|
2012-11-17 05:57:43 +08:00
|
|
|
header->pmode_cr3 = (u32)__pa_symbol(initial_page_table);
|
2008-04-11 05:28:10 +08:00
|
|
|
saved_magic = 0x12345678;
|
|
|
|
#else /* CONFIG_64BIT */
|
2008-06-14 02:31:54 +08:00
|
|
|
#ifdef CONFIG_SMP
|
2011-02-05 08:14:11 +08:00
|
|
|
stack_start = (unsigned long)temp_stack + sizeof(temp_stack);
|
2008-10-17 07:26:27 +08:00
|
|
|
early_gdt_descr.address =
|
|
|
|
(unsigned long)get_cpu_gdt_table(smp_processor_id());
|
2009-01-13 19:41:35 +08:00
|
|
|
initial_gs = per_cpu_offset(smp_processor_id());
|
2008-06-14 02:31:54 +08:00
|
|
|
#endif
|
2008-04-11 05:28:10 +08:00
|
|
|
initial_code = (unsigned long)wakeup_long64;
|
2009-04-18 19:44:57 +08:00
|
|
|
saved_magic = 0x123456789abcdef0L;
|
2008-04-11 05:28:10 +08:00
|
|
|
#endif /* CONFIG_64BIT */
|
2008-01-30 20:32:54 +08:00
|
|
|
|
2011-02-09 06:42:22 +08:00
|
|
|
do_suspend_lowlevel();
|
2008-01-30 20:32:54 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __init acpi_sleep_setup(char *str)
|
|
|
|
{
|
|
|
|
while ((str != NULL) && (*str != '\0')) {
|
|
|
|
if (strncmp(str, "s3_bios", 7) == 0)
|
|
|
|
acpi_realmode_flags |= 1;
|
|
|
|
if (strncmp(str, "s3_mode", 7) == 0)
|
|
|
|
acpi_realmode_flags |= 2;
|
|
|
|
if (strncmp(str, "s3_beep", 7) == 0)
|
|
|
|
acpi_realmode_flags |= 4;
|
2008-07-24 12:28:41 +08:00
|
|
|
#ifdef CONFIG_HIBERNATION
|
|
|
|
if (strncmp(str, "s4_nohwsig", 10) == 0)
|
|
|
|
acpi_no_s4_hw_signature();
|
|
|
|
#endif
|
2010-07-24 04:59:09 +08:00
|
|
|
if (strncmp(str, "nonvs", 5) == 0)
|
|
|
|
acpi_nvs_nosave();
|
2012-10-26 19:39:15 +08:00
|
|
|
if (strncmp(str, "nonvs_s3", 8) == 0)
|
|
|
|
acpi_nvs_nosave_s3();
|
2008-06-13 05:24:06 +08:00
|
|
|
if (strncmp(str, "old_ordering", 12) == 0)
|
|
|
|
acpi_old_suspend_ordering();
|
2008-01-30 20:32:54 +08:00
|
|
|
str = strchr(str, ',');
|
|
|
|
if (str != NULL)
|
|
|
|
str += strspn(str, ", \t");
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
__setup("acpi_sleep=", acpi_sleep_setup);
|