2008-01-30 05:32:54 -07:00
|
|
|
/*
|
|
|
|
* sleep.c - x86-specific ACPI sleep support.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2001-2003 Patrick Mochel
|
2010-07-18 06:27:13 -06:00
|
|
|
* Copyright (C) 2001-2003 Pavel Machek <pavel@ucw.cz>
|
2008-01-30 05:32:54 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/acpi.h>
|
|
|
|
#include <linux/bootmem.h>
|
2010-08-25 14:39:17 -06:00
|
|
|
#include <linux/memblock.h>
|
2008-01-30 05:32:54 -07:00
|
|
|
#include <linux/dmi.h>
|
|
|
|
#include <linux/cpumask.h>
|
2008-07-17 12:29:24 -06:00
|
|
|
#include <asm/segment.h>
|
2008-10-16 17:26:27 -06:00
|
|
|
#include <asm/desc.h>
|
2010-08-28 07:58:33 -06:00
|
|
|
#include <asm/pgtable.h>
|
2011-02-06 22:16:09 -07:00
|
|
|
#include <asm/cacheflush.h>
|
2012-05-08 12:22:29 -06:00
|
|
|
#include <asm/realmode.h>
|
2010-08-28 07:58:33 -06:00
|
|
|
|
2012-05-08 12:22:42 -06:00
|
|
|
#include "../../realmode/rm/wakeup.h"
|
2008-04-10 15:28:10 -06:00
|
|
|
#include "sleep.h"
|
2008-01-30 05:32:54 -07:00
|
|
|
|
|
|
|
unsigned long acpi_realmode_flags;
|
|
|
|
|
2008-08-03 11:25:48 -06:00
|
|
|
#if defined(CONFIG_SMP) && defined(CONFIG_64BIT)
|
2008-10-09 10:56:21 -06:00
|
|
|
static char temp_stack[4096];
|
2008-04-10 15:28:10 -06:00
|
|
|
#endif
|
2008-01-30 05:32:54 -07:00
|
|
|
|
|
|
|
/**
|
2011-02-08 15:42:22 -07:00
|
|
|
* acpi_suspend_lowlevel - save kernel state
|
2008-01-30 05:32:54 -07:00
|
|
|
*
|
|
|
|
* Create an identity mapped page table and copy the wakeup routine to
|
|
|
|
* low memory.
|
|
|
|
*/
|
2011-02-08 15:42:22 -07:00
|
|
|
int acpi_suspend_lowlevel(void)
|
2008-01-30 05:32:54 -07:00
|
|
|
{
|
2012-05-08 12:22:29 -06:00
|
|
|
struct wakeup_header *header =
|
2012-05-08 12:22:41 -06:00
|
|
|
(struct wakeup_header *) __va(real_mode_header->wakeup_header);
|
2008-04-10 15:28:10 -06:00
|
|
|
|
2011-02-14 16:42:46 -07:00
|
|
|
if (header->signature != WAKEUP_HEADER_SIGNATURE) {
|
2008-04-10 15:28:10 -06:00
|
|
|
printk(KERN_ERR "wakeup header does not match\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
header->video_mode = saved_video_mode;
|
|
|
|
|
2012-09-26 16:02:34 -06:00
|
|
|
header->pmode_behavior = 0;
|
|
|
|
|
2008-04-10 15:28:10 -06:00
|
|
|
#ifndef CONFIG_64BIT
|
|
|
|
store_gdt((struct desc_ptr *)&header->pmode_gdt);
|
|
|
|
|
2012-09-26 16:02:34 -06:00
|
|
|
if (!rdmsr_safe(MSR_EFER,
|
|
|
|
&header->pmode_efer_low,
|
|
|
|
&header->pmode_efer_high))
|
|
|
|
header->pmode_behavior |= (1 << WAKEUP_BEHAVIOR_RESTORE_EFER);
|
2008-04-10 15:28:10 -06:00
|
|
|
#endif /* !CONFIG_64BIT */
|
|
|
|
|
|
|
|
header->pmode_cr0 = read_cr0();
|
2012-09-26 16:02:34 -06: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-06 19:10:34 -06:00
|
|
|
if (!rdmsr_safe(MSR_IA32_MISC_ENABLE,
|
|
|
|
&header->pmode_misc_en_low,
|
|
|
|
&header->pmode_misc_en_high))
|
|
|
|
header->pmode_behavior |=
|
|
|
|
(1 << WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE);
|
2008-04-10 15:28:10 -06:00
|
|
|
header->realmode_flags = acpi_realmode_flags;
|
|
|
|
header->real_magic = 0x12345678;
|
|
|
|
|
|
|
|
#ifndef CONFIG_64BIT
|
|
|
|
header->pmode_entry = (u32)&wakeup_pmode_return;
|
2010-08-28 07:58:33 -06:00
|
|
|
header->pmode_cr3 = (u32)__pa(&initial_page_table);
|
2008-04-10 15:28:10 -06:00
|
|
|
saved_magic = 0x12345678;
|
|
|
|
#else /* CONFIG_64BIT */
|
2008-06-13 12:31:54 -06:00
|
|
|
#ifdef CONFIG_SMP
|
2011-02-04 17:14:11 -07:00
|
|
|
stack_start = (unsigned long)temp_stack + sizeof(temp_stack);
|
2008-10-16 17:26:27 -06:00
|
|
|
early_gdt_descr.address =
|
|
|
|
(unsigned long)get_cpu_gdt_table(smp_processor_id());
|
2009-01-13 04:41:35 -07:00
|
|
|
initial_gs = per_cpu_offset(smp_processor_id());
|
2008-06-13 12:31:54 -06:00
|
|
|
#endif
|
2008-04-10 15:28:10 -06:00
|
|
|
initial_code = (unsigned long)wakeup_long64;
|
2009-04-18 05:44:57 -06:00
|
|
|
saved_magic = 0x123456789abcdef0L;
|
2008-04-10 15:28:10 -06:00
|
|
|
#endif /* CONFIG_64BIT */
|
2008-01-30 05:32:54 -07:00
|
|
|
|
2011-02-08 15:42:22 -07:00
|
|
|
do_suspend_lowlevel();
|
2008-01-30 05:32:54 -07: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-23 22:28:41 -06:00
|
|
|
#ifdef CONFIG_HIBERNATION
|
|
|
|
if (strncmp(str, "s4_nohwsig", 10) == 0)
|
|
|
|
acpi_no_s4_hw_signature();
|
|
|
|
#endif
|
2010-07-23 14:59:09 -06:00
|
|
|
if (strncmp(str, "nonvs", 5) == 0)
|
|
|
|
acpi_nvs_nosave();
|
2008-06-12 15:24:06 -06:00
|
|
|
if (strncmp(str, "old_ordering", 12) == 0)
|
|
|
|
acpi_old_suspend_ordering();
|
2008-01-30 05:32:54 -07:00
|
|
|
str = strchr(str, ',');
|
|
|
|
if (str != NULL)
|
|
|
|
str += strspn(str, ", \t");
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
__setup("acpi_sleep=", acpi_sleep_setup);
|