diff options
author | H. Peter Anvin <hpa@zytor.com> | 2008-07-13 21:18:02 -0700 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2008-07-14 18:16:09 +0200 |
commit | 3bf2e77453a87c22eb57ed4926760ac131c84459 (patch) | |
tree | 645721e2be5c1e14f8849a941c4c23feeb782217 /arch/x86/kernel/acpi | |
parent | 5ac37f87ff18843aabab84cf75b2f8504c2d81fe (diff) |
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>
Diffstat (limited to 'arch/x86/kernel/acpi')
-rw-r--r-- | arch/x86/kernel/acpi/sleep.c | 17 |
1 files changed, 13 insertions, 4 deletions
diff --git a/arch/x86/kernel/acpi/sleep.c b/arch/x86/kernel/acpi/sleep.c index 36af01f029ed..130711f1454b 100644 --- a/arch/x86/kernel/acpi/sleep.c +++ b/arch/x86/kernel/acpi/sleep.c @@ -23,6 +23,15 @@ static unsigned long acpi_realmode; static char temp_stack[10240]; #endif +/* XXX: this macro should move to asm-x86/segment.h and be shared with the + boot code... */ +#define GDT_ENTRY(flags, base, limit) \ + (((u64)(base & 0xff000000) << 32) | \ + ((u64)flags << 40) | \ + ((u64)(limit & 0x00ff0000) << 32) | \ + ((u64)(base & 0x00ffffff) << 16) | \ + ((u64)(limit & 0x0000ffff))) + /** * acpi_save_state_mem - save kernel state * @@ -58,11 +67,11 @@ int acpi_save_state_mem(void) ((char *)&header->wakeup_gdt - (char *)acpi_realmode)) << 16); /* GDT[1]: real-mode-like code segment */ - header->wakeup_gdt[1] = (0x009bULL << 40) + - ((u64)acpi_wakeup_address << 16) + 0xffff; + header->wakeup_gdt[1] = + GDT_ENTRY(0x809b, acpi_wakeup_address, 0xfffff); /* GDT[2]: real-mode-like data segment */ - header->wakeup_gdt[2] = (0x0093ULL << 40) + - ((u64)acpi_wakeup_address << 16) + 0xffff; + header->wakeup_gdt[2] = + GDT_ENTRY(0x8093, acpi_wakeup_address, 0xfffff); #ifndef CONFIG_64BIT store_gdt((struct desc_ptr *)&header->pmode_gdt); |