Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4b4f7280 authored by H. Peter Anvin's avatar H. Peter Anvin Committed by Ingo Molnar
Browse files

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: default avatarH. 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: default avatarIngo Molnar <mingo@elte.hu>
Tested-by: default avatarKirill A. Shutemov <kirill@shutemov.name>
Signed-off-by: default avatarRafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: default avatarIngo Molnar <mingo@elte.hu>
parent b8a0b6cc
Loading
Loading
Loading
Loading
+37 −1
Original line number Diff line number Diff line
@@ -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"
@@ -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
@@ -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

@@ -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
+5 −0
Original line number Diff line number Diff line
@@ -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__));

+15 −1
Original line number Diff line number Diff line
@@ -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);

@@ -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);
}


+2 −3
Original line number Diff line number Diff line
@@ -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();