summaryrefslogtreecommitdiffstats
path: root/arch/x86/realmode/rm/wakeup_asm.S
blob: 05ac9c17c8111b9c9672386fcddaa5dcca1dd98e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * ACPI wakeup real mode startup stub
 */
#include <linux/linkage.h>
#include <asm/segment.h>
#include <asm/msr-index.h>
#include <asm/page_types.h>
#include <asm/pgtable_types.h>
#include <asm/processor-flags.h>
#include "realmode.h"
#include "wakeup.h"

	.code16

/* This should match the structure in wakeup.h */
	.section ".data", "aw"

	.balign	16
GLOBAL(wakeup_header)
	video_mode:	.short	0	/* Video mode number */
	pmode_entry:	.long	0
	pmode_cs:	.short	__KERNEL_CS
	pmode_cr0:	.long	0	/* Saved %cr0 */
	pmode_cr3:	.long	0	/* Saved %cr3 */
	pmode_cr4:	.long	0	/* Saved %cr4 */
	pmode_efer:	.quad	0	/* Saved EFER */
	pmode_gdt:	.quad	0
	pmode_misc_en:	.quad	0	/* Saved MISC_ENABLE MSR */
	pmode_behavior:	.long	0	/* Wakeup behavior flags */
	realmode_flags:	.long	0
	real_magic:	.long	0
	signature:	.long	WAKEUP_HEADER_SIGNATURE
END(wakeup_header)

	.text
	.code16

	.balign	16
ENTRY(wakeup_start)
	cli
	cld

	LJMPW_RM(3f)
3:
	/* 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
	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
	LJMPW_RM(3f)
3:
	/* Set up segments */
	movw	%cs, %ax
	movw	%ax, %ss
	movl	$rm_stack_end, %esp
	movw	%ax, %ds
	movw	%ax, %es
	movw	%ax, %fs
	movw	%ax, %gs

	lidtl	wakeup_idt

	/* Clear the EFLAGS */
	pushl $0
	popfl

	/* Check header signature... */
	movl	signature, %eax
	cmpl	$WAKEUP_HEADER_SIGNATURE, %eax
	jne	bogus_real_magic

	/* Check we really have everything... */
	movl	end_signature, %eax
	cmpl	$REALMODE_END_SIGNATURE, %eax
	jne	bogus_real_magic

	/* Call the C code */
	calll	main

	/* Restore MISC_ENABLE before entering protected mode, in case
	   BIOS decided to clear XD_DISABLE during S3. */
	movl	pmode_behavior, %edi
	btl	$WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE, %edi
	jnc	1f

	movl	pmode_misc_en, %eax
	movl	pmode_misc_en + 4, %edx
	movl	$MSR_IA32_MISC_ENABLE, %ecx
	wrmsr
1:

	/* Do any other stuff... */

#ifndef CONFIG_64BIT
	/* This could also be done in C code... */
	movl	pmode_cr3, %eax
	movl	%eax, %cr3

	btl	$WAKEUP_BEHAVIOR_RESTORE_CR4, %edi
	jnc	1f
	movl	pmode_cr4, %eax
	movl	%eax, %cr4
1:
	btl	$WAKEUP_BEHAVIOR_RESTORE_EFER, %edi
	jnc	1f
	movl	pmode_efer, %eax
	movl	pmode_efer + 4, %edx
	movl	$MSR_EFER, %ecx
	wrmsr
1:

	lgdtl	pmode_gdt

	/* This really couldn't... */
	movl	pmode_entry, %eax
	movl	pmode_cr0, %ecx
	movl	%ecx, %cr0
	ljmpl	$__KERNEL_CS, $pa_startup_32
	/* -> jmp *%eax in trampoline_32.S */
#else
	jmp	trampoline_start
#endif

bogus_real_magic:
1:
	hlt
	jmp	1b

	.section ".rodata","a"

	/*
	 * 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.
	 */

	.balign	16
GLOBAL(wakeup_gdt)
	.word	3*8-1		/* Self-descriptor */
	.long	pa_wakeup_gdt
	.word	0

	.word	0xffff		/* 16-bit code segment @ real_mode_base */
	.long	0x9b000000 + pa_real_mode_base
	.word	0x008f		/* big real mode */

	.word	0xffff		/* 16-bit data segment @ real_mode_base */
	.long	0x93000000 + pa_real_mode_base
	.word	0x008f		/* big real mode */
END(wakeup_gdt)

	.section ".rodata","a"
	.balign	8

	/* This is the standard real-mode IDT */
	.balign	16
GLOBAL(wakeup_idt)
	.word	0xffff		/* limit */
	.long	0		/* address */
	.word	0
END(wakeup_idt)