x86 ACPI: normalize segment descriptor register on resume
Some Dell laptops enter resume with apparent garbage in the segment descriptor registers (almost certainly the result of a botched transition from protected to real mode.) The only way to clean that up is to enter protected mode ourselves and clean out the descriptor registers. This fixes resume on Dell XPS M1210 and Dell D620. Reference: http://bugzilla.kernel.org/show_bug.cgi?id=10927 Signed-off-by: H. Peter Anvin <hpa@zytor.com> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Pavel Machek <pavel@ucw.cz> Cc: pm list <linux-pm@lists.linux-foundation.org> Cc: Len Brown <lenb@kernel.org> Signed-off-by: Ingo Molnar <mingo@elte.hu> Tested-by: Kirill A. Shutemov <kirill@shutemov.name> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl> Signed-off-by: Ingo Molnar <mingo@elte.hu>
This commit is contained in:
parent
b8a0b6ccf2
commit
4b4f7280d7
|
@ -5,6 +5,7 @@
|
||||||
#include <asm/msr-index.h>
|
#include <asm/msr-index.h>
|
||||||
#include <asm/page.h>
|
#include <asm/page.h>
|
||||||
#include <asm/pgtable.h>
|
#include <asm/pgtable.h>
|
||||||
|
#include <asm/processor-flags.h>
|
||||||
|
|
||||||
.code16
|
.code16
|
||||||
.section ".header", "a"
|
.section ".header", "a"
|
||||||
|
@ -24,6 +25,11 @@ pmode_gdt: .quad 0
|
||||||
realmode_flags: .long 0
|
realmode_flags: .long 0
|
||||||
real_magic: .long 0
|
real_magic: .long 0
|
||||||
trampoline_segment: .word 0
|
trampoline_segment: .word 0
|
||||||
|
_pad1: .byte 0
|
||||||
|
wakeup_jmp: .byte 0xea /* ljmpw */
|
||||||
|
wakeup_jmp_off: .word 3f
|
||||||
|
wakeup_jmp_seg: .word 0
|
||||||
|
wakeup_gdt: .quad 0, 0, 0
|
||||||
signature: .long 0x51ee1111
|
signature: .long 0x51ee1111
|
||||||
|
|
||||||
.text
|
.text
|
||||||
|
@ -34,11 +40,34 @@ _start:
|
||||||
cli
|
cli
|
||||||
cld
|
cld
|
||||||
|
|
||||||
|
/* Apparently some dimwit BIOS programmers don't know how to
|
||||||
|
program a PM to RM transition, and we might end up here with
|
||||||
|
junk in the data segment descriptor registers. The only way
|
||||||
|
to repair that is to go into PM and fix it ourselves... */
|
||||||
|
movw $16, %cx
|
||||||
|
lgdtl %cs:wakeup_gdt
|
||||||
|
movl %cr0, %eax
|
||||||
|
orb $X86_CR0_PE, %al
|
||||||
|
movl %eax, %cr0
|
||||||
|
jmp 1f
|
||||||
|
1: ljmpw $8, $2f
|
||||||
|
2:
|
||||||
|
movw %cx, %ds
|
||||||
|
movw %cx, %es
|
||||||
|
movw %cx, %ss
|
||||||
|
movw %cx, %fs
|
||||||
|
movw %cx, %gs
|
||||||
|
|
||||||
|
andb $~X86_CR0_PE, %al
|
||||||
|
movl %eax, %cr0
|
||||||
|
jmp wakeup_jmp
|
||||||
|
3:
|
||||||
/* Set up segments */
|
/* Set up segments */
|
||||||
movw %cs, %ax
|
movw %cs, %ax
|
||||||
movw %ax, %ds
|
movw %ax, %ds
|
||||||
movw %ax, %es
|
movw %ax, %es
|
||||||
movw %ax, %ss
|
movw %ax, %ss
|
||||||
|
lidtl wakeup_idt
|
||||||
|
|
||||||
movl $wakeup_stack_end, %esp
|
movl $wakeup_stack_end, %esp
|
||||||
|
|
||||||
|
@ -98,7 +127,14 @@ bogus_real_magic:
|
||||||
jmp 1b
|
jmp 1b
|
||||||
|
|
||||||
.data
|
.data
|
||||||
.balign 4
|
.balign 8
|
||||||
|
|
||||||
|
/* This is the standard real-mode IDT */
|
||||||
|
wakeup_idt:
|
||||||
|
.word 0xffff /* limit */
|
||||||
|
.long 0 /* address */
|
||||||
|
.word 0
|
||||||
|
|
||||||
.globl HEAP, heap_end
|
.globl HEAP, heap_end
|
||||||
HEAP:
|
HEAP:
|
||||||
.long wakeup_heap
|
.long wakeup_heap
|
||||||
|
|
|
@ -24,6 +24,11 @@ struct wakeup_header {
|
||||||
u32 realmode_flags;
|
u32 realmode_flags;
|
||||||
u32 real_magic;
|
u32 real_magic;
|
||||||
u16 trampoline_segment; /* segment with trampoline code, 64-bit only */
|
u16 trampoline_segment; /* segment with trampoline code, 64-bit only */
|
||||||
|
u8 _pad1;
|
||||||
|
u8 wakeup_jmp;
|
||||||
|
u16 wakeup_jmp_off;
|
||||||
|
u16 wakeup_jmp_seg;
|
||||||
|
u64 wakeup_gdt[3];
|
||||||
u32 signature; /* To check we have correct structure */
|
u32 signature; /* To check we have correct structure */
|
||||||
} __attribute__((__packed__));
|
} __attribute__((__packed__));
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,20 @@ int acpi_save_state_mem(void)
|
||||||
|
|
||||||
header->video_mode = saved_video_mode;
|
header->video_mode = saved_video_mode;
|
||||||
|
|
||||||
|
header->wakeup_jmp_seg = acpi_wakeup_address >> 4;
|
||||||
|
/* GDT[0]: GDT self-pointer */
|
||||||
|
header->wakeup_gdt[0] =
|
||||||
|
(u64)(sizeof(header->wakeup_gdt) - 1) +
|
||||||
|
((u64)(acpi_wakeup_address +
|
||||||
|
((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;
|
||||||
|
/* GDT[2]: real-mode-like data segment */
|
||||||
|
header->wakeup_gdt[2] = (0x0093ULL << 40) +
|
||||||
|
((u64)acpi_wakeup_address << 16) + 0xffff;
|
||||||
|
|
||||||
#ifndef CONFIG_64BIT
|
#ifndef CONFIG_64BIT
|
||||||
store_gdt((struct desc_ptr *)&header->pmode_gdt);
|
store_gdt((struct desc_ptr *)&header->pmode_gdt);
|
||||||
|
|
||||||
|
@ -111,7 +125,7 @@ void __init acpi_reserve_bootmem(void)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
acpi_wakeup_address = acpi_realmode;
|
acpi_wakeup_address = virt_to_phys((void *)acpi_realmode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,8 @@ static int acpi_sleep_prepare(u32 acpi_state)
|
||||||
if (!acpi_wakeup_address) {
|
if (!acpi_wakeup_address) {
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
}
|
}
|
||||||
acpi_set_firmware_waking_vector((acpi_physical_address)
|
acpi_set_firmware_waking_vector(
|
||||||
virt_to_phys((void *)
|
(acpi_physical_address)acpi_wakeup_address);
|
||||||
acpi_wakeup_address));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
ACPI_FLUSH_CPU_CACHE();
|
ACPI_FLUSH_CPU_CACHE();
|
||||||
|
|
Loading…
Reference in a new issue