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>
|
2010-08-28 21:58:33 +08:00
|
|
|
|
2008-04-11 05:28:10 +08:00
|
|
|
#include "realmode/wakeup.h"
|
|
|
|
#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
|
|
|
|
x86, acpi: Call acpi_enter_sleep_state via an asmlinkage C function from assembler
With commit a2ef5c4fd44ce3922435139393b89f2cce47f576
"ACPI: Move module parameter gts and bfs to sleep.c" the
wake_sleep_flags is required when calling acpi_enter_sleep_state.
The assembler code in wakeup_*.S did not do that. One solution
is to call it from assembler and stick the wake_sleep_flags on
the stack (for 32-bit) or in %esi (for 64-bit). hpa and rafael
both suggested however to create a wrapper function to call
acpi_enter_sleep_state and call said wrapper function
("acpi_enter_s3") from assembler.
For 32-bit, the acpi_enter_s3 ends up looking as so:
push %ebp
mov %esp,%ebp
sub $0x8,%esp
movzbl 0xc1809314,%eax [wake_sleep_flags]
movl $0x3,(%esp)
mov %eax,0x4(%esp)
call 0xc12d1fa0 <acpi_enter_sleep_state>
leave
ret
And 64-bit:
movzbl 0x9afde1(%rip),%esi [wake_sleep_flags]
push %rbp
mov $0x3,%edi
mov %rsp,%rbp
callq 0xffffffff812e9800 <acpi_enter_sleep_state>
leaveq
retq
Reviewed-by: H. Peter Anvin <hpa@zytor.com>
Suggested-by: H. Peter Anvin <hpa@zytor.com>
[v2: Remove extra assembler operations, per hpa review]
Signed-off-by: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
Link: http://lkml.kernel.org/r/1335150198-21899-3-git-send-email-konrad.wilk@oracle.com
Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
2012-04-23 11:03:18 +08:00
|
|
|
asmlinkage void acpi_enter_s3(void)
|
|
|
|
{
|
|
|
|
acpi_enter_sleep_state(3, wake_sleep_flags);
|
|
|
|
}
|
2008-01-30 20:32:54 +08:00
|
|
|
/**
|
2011-02-09 06:42:22 +08:00
|
|
|
* 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.
|
|
|
|
*/
|
2011-02-09 06:42:22 +08:00
|
|
|
int acpi_suspend_lowlevel(void)
|
2008-01-30 20:32:54 +08:00
|
|
|
{
|
2008-04-11 05:28:10 +08:00
|
|
|
struct wakeup_header *header;
|
2011-02-15 07:42:46 +08:00
|
|
|
/* address in low memory of the wakeup routine. */
|
|
|
|
char *acpi_realmode;
|
2008-04-11 05:28:10 +08:00
|
|
|
|
2011-02-15 07:42:46 +08:00
|
|
|
acpi_realmode = TRAMPOLINE_SYM(acpi_wakeup_code);
|
2008-04-11 05:28:10 +08:00
|
|
|
|
2011-02-15 07:42:46 +08:00
|
|
|
header = (struct wakeup_header *)(acpi_realmode + WAKEUP_HEADER_OFFSET);
|
|
|
|
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;
|
|
|
|
|
2008-06-25 05:03:48 +08:00
|
|
|
header->wakeup_jmp_seg = acpi_wakeup_address >> 4;
|
2008-07-15 02:44:26 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the wakeup GDT. We set these up as Big Real Mode,
|
|
|
|
* that is, with limits set to 4 GB. At least the Lenovo
|
|
|
|
* Thinkpad X61 is known to need this for the video BIOS
|
|
|
|
* initialization quirk to work; this is likely to also
|
|
|
|
* be the case for other laptops or integrated video devices.
|
|
|
|
*/
|
|
|
|
|
2008-06-25 05:03:48 +08:00
|
|
|
/* GDT[0]: GDT self-pointer */
|
|
|
|
header->wakeup_gdt[0] =
|
|
|
|
(u64)(sizeof(header->wakeup_gdt) - 1) +
|
2011-02-15 07:42:46 +08:00
|
|
|
((u64)__pa(&header->wakeup_gdt) << 16);
|
2008-07-15 02:44:26 +08:00
|
|
|
/* GDT[1]: big real mode-like code segment */
|
x86, suspend, acpi: enter Big Real Mode
The explanation for recent video BIOS suspend quirk failures is that
the VESA BIOS expects to be entered in Big Real Mode (*.limit = 0xffffffff)
instead of ordinary Real Mode (*.limit = 0xffff).
This patch changes the segment descriptors to Big Real Mode instead.
The segment descriptor registers (what Intel calls "segment cache") is
always active. The only thing that changes based on CR0.PE is how it is
*loaded* and the interpretation of the CS flags.
The segment descriptor registers contain of the following sub-registers:
selector (the "visible" part), base, limit and flags. In protected mode
or long mode, they are loaded from descriptors (or fs.base or gs.base can
be manipulated directly in long mode.) In real mode, the only thing
changed by a segment register load is the selector and the base, where the
base <- selector << 4. In particular, *the limit and the flags are not
changed*.
As far as the handling of the CS flags: a code segment cannot be writable
in protected mode, whereas it is "just another segment" in real mode, so
there is some kind of quirk that kicks in for this when CR0.PE <- 0. I'm
not sure if this is accomplished by actually changing the cs.flags register
or just changing the interpretation; it might be something that is
CPU-specific. In particular, the Transmeta CPUs had an explicit "CS is
writable if you're in real mode" override, so even if you had loaded CS
with an execute-only segment it'd be writable (but not readable!) on return
to real mode. I'm not at all sure if that is how other CPUs behave.
Signed-off-by: "H. Peter Anvin" <hpa@zytor.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2008-07-14 12:18:02 +08:00
|
|
|
header->wakeup_gdt[1] =
|
|
|
|
GDT_ENTRY(0x809b, acpi_wakeup_address, 0xfffff);
|
2008-07-15 02:44:26 +08:00
|
|
|
/* GDT[2]: big real mode-like data segment */
|
x86, suspend, acpi: enter Big Real Mode
The explanation for recent video BIOS suspend quirk failures is that
the VESA BIOS expects to be entered in Big Real Mode (*.limit = 0xffffffff)
instead of ordinary Real Mode (*.limit = 0xffff).
This patch changes the segment descriptors to Big Real Mode instead.
The segment descriptor registers (what Intel calls "segment cache") is
always active. The only thing that changes based on CR0.PE is how it is
*loaded* and the interpretation of the CS flags.
The segment descriptor registers contain of the following sub-registers:
selector (the "visible" part), base, limit and flags. In protected mode
or long mode, they are loaded from descriptors (or fs.base or gs.base can
be manipulated directly in long mode.) In real mode, the only thing
changed by a segment register load is the selector and the base, where the
base <- selector << 4. In particular, *the limit and the flags are not
changed*.
As far as the handling of the CS flags: a code segment cannot be writable
in protected mode, whereas it is "just another segment" in real mode, so
there is some kind of quirk that kicks in for this when CR0.PE <- 0. I'm
not sure if this is accomplished by actually changing the cs.flags register
or just changing the interpretation; it might be something that is
CPU-specific. In particular, the Transmeta CPUs had an explicit "CS is
writable if you're in real mode" override, so even if you had loaded CS
with an execute-only segment it'd be writable (but not readable!) on return
to real mode. I'm not at all sure if that is how other CPUs behave.
Signed-off-by: "H. Peter Anvin" <hpa@zytor.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2008-07-14 12:18:02 +08:00
|
|
|
header->wakeup_gdt[2] =
|
|
|
|
GDT_ENTRY(0x8093, acpi_wakeup_address, 0xfffff);
|
2008-06-25 05:03:48 +08:00
|
|
|
|
2008-04-11 05:28:10 +08:00
|
|
|
#ifndef CONFIG_64BIT
|
|
|
|
store_gdt((struct desc_ptr *)&header->pmode_gdt);
|
|
|
|
|
2009-11-14 07:28:14 +08:00
|
|
|
if (rdmsr_safe(MSR_EFER, &header->pmode_efer_low,
|
|
|
|
&header->pmode_efer_high))
|
|
|
|
header->pmode_efer_low = header->pmode_efer_high = 0;
|
2008-04-11 05:28:10 +08:00
|
|
|
#endif /* !CONFIG_64BIT */
|
|
|
|
|
|
|
|
header->pmode_cr0 = read_cr0();
|
2008-08-18 12:03:40 +08:00
|
|
|
header->pmode_cr4 = read_cr4_safe();
|
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 = 0;
|
|
|
|
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-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;
|
2010-08-28 21:58:33 +08:00
|
|
|
header->pmode_cr3 = (u32)__pa(&initial_page_table);
|
2008-04-11 05:28:10 +08:00
|
|
|
saved_magic = 0x12345678;
|
|
|
|
#else /* CONFIG_64BIT */
|
2011-02-15 07:42:46 +08:00
|
|
|
header->trampoline_segment = trampoline_address() >> 4;
|
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();
|
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);
|