Skip to content

Commit

Permalink
x86 ACPI: normalize segment descriptor register on resume
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
H. Peter Anvin authored and Ingo Molnar committed Jul 5, 2008
1 parent b8a0b6c commit 4b4f728
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 5 deletions.
38 changes: 37 additions & 1 deletion arch/x86/kernel/acpi/realmode/wakeup.S
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <asm/msr-index.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <asm/processor-flags.h>

.code16
.section ".header", "a"
Expand All @@ -24,6 +25,11 @@ pmode_gdt: .quad 0
realmode_flags: .long 0
real_magic: .long 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

.text
Expand All @@ -34,11 +40,34 @@ _start:
cli
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 */
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
lidtl wakeup_idt

movl $wakeup_stack_end, %esp

Expand Down Expand Up @@ -98,7 +127,14 @@ bogus_real_magic:
jmp 1b

.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
HEAP:
.long wakeup_heap
Expand Down
5 changes: 5 additions & 0 deletions arch/x86/kernel/acpi/realmode/wakeup.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ struct wakeup_header {
u32 realmode_flags;
u32 real_magic;
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 */
} __attribute__((__packed__));

Expand Down
16 changes: 15 additions & 1 deletion arch/x86/kernel/acpi/sleep.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ int acpi_save_state_mem(void)

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
store_gdt((struct desc_ptr *)&header->pmode_gdt);

Expand Down Expand Up @@ -111,7 +125,7 @@ void __init acpi_reserve_bootmem(void)
return;
}

acpi_wakeup_address = acpi_realmode;
acpi_wakeup_address = virt_to_phys((void *)acpi_realmode);
}


Expand Down
5 changes: 2 additions & 3 deletions drivers/acpi/sleep/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ static int acpi_sleep_prepare(u32 acpi_state)
if (!acpi_wakeup_address) {
return -EFAULT;
}
acpi_set_firmware_waking_vector((acpi_physical_address)
virt_to_phys((void *)
acpi_wakeup_address));
acpi_set_firmware_waking_vector(
(acpi_physical_address)acpi_wakeup_address);

}
ACPI_FLUSH_CPU_CACHE();
Expand Down

0 comments on commit 4b4f728

Please sign in to comment.