1 /* SPDX-License-Identifier: GPL-2.0 */
2 /*
3  * ACPI wakeup real mode startup stub
4  */
5 #include <linux/linkage.h>
6 #include <asm/segment.h>
7 #include <asm/msr-index.h>
8 #include <asm/page_types.h>
9 #include <asm/pgtable_types.h>
10 #include <asm/processor-flags.h>
11 #include "realmode.h"
12 #include "wakeup.h"
13 
14 	.code16
15 
16 /* This should match the structure in wakeup.h */
17 	.section ".data", "aw"
18 
19 	.balign	16
20 SYM_DATA_START(wakeup_header)
21 	video_mode:	.short	0	/* Video mode number */
22 	pmode_entry:	.long	0
23 	pmode_cs:	.short	__KERNEL_CS
24 	pmode_cr0:	.long	0	/* Saved %cr0 */
25 	pmode_cr3:	.long	0	/* Saved %cr3 */
26 	pmode_cr4:	.long	0	/* Saved %cr4 */
27 	pmode_efer:	.quad	0	/* Saved EFER */
28 	pmode_gdt:	.quad	0
29 	pmode_misc_en:	.quad	0	/* Saved MISC_ENABLE MSR */
30 	pmode_behavior:	.long	0	/* Wakeup behavior flags */
31 	realmode_flags:	.long	0
32 	real_magic:	.long	0
33 	signature:	.long	WAKEUP_HEADER_SIGNATURE
34 SYM_DATA_END(wakeup_header)
35 
36 	.text
37 	.code16
38 
39 	.balign	16
40 SYM_CODE_START(wakeup_start)
41 	cli
42 	cld
43 
44 	LJMPW_RM(3f)
45 3:
46 	/* Apparently some dimwit BIOS programmers don't know how to
47 	   program a PM to RM transition, and we might end up here with
48 	   junk in the data segment descriptor registers.  The only way
49 	   to repair that is to go into PM and fix it ourselves... */
50 	movw	$16, %cx
51 	lgdtl	%cs:wakeup_gdt
52 	movl	%cr0, %eax
53 	orb	$X86_CR0_PE, %al
54 	movl	%eax, %cr0
55 	ljmpw	$8, $2f
56 2:
57 	movw	%cx, %ds
58 	movw	%cx, %es
59 	movw	%cx, %ss
60 	movw	%cx, %fs
61 	movw	%cx, %gs
62 
63 	andb	$~X86_CR0_PE, %al
64 	movl	%eax, %cr0
65 	LJMPW_RM(3f)
66 3:
67 	/* Set up segments */
68 	movw	%cs, %ax
69 	movw	%ax, %ss
70 	movl	$rm_stack_end, %esp
71 	movw	%ax, %ds
72 	movw	%ax, %es
73 	movw	%ax, %fs
74 	movw	%ax, %gs
75 
76 	lidtl	.Lwakeup_idt
77 
78 	/* Clear the EFLAGS */
79 	pushl $0
80 	popfl
81 
82 	/* Check header signature... */
83 	movl	signature, %eax
84 	cmpl	$WAKEUP_HEADER_SIGNATURE, %eax
85 	jne	bogus_real_magic
86 
87 	/* Check we really have everything... */
88 	movl	end_signature, %eax
89 	cmpl	$REALMODE_END_SIGNATURE, %eax
90 	jne	bogus_real_magic
91 
92 	/* Call the C code */
93 	calll	main
94 
95 	/* Restore MISC_ENABLE before entering protected mode, in case
96 	   BIOS decided to clear XD_DISABLE during S3. */
97 	movl	pmode_behavior, %edi
98 	btl	$WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE, %edi
99 	jnc	1f
100 
101 	movl	pmode_misc_en, %eax
102 	movl	pmode_misc_en + 4, %edx
103 	movl	$MSR_IA32_MISC_ENABLE, %ecx
104 	wrmsr
105 1:
106 
107 	/* Do any other stuff... */
108 
109 #ifndef CONFIG_64BIT
110 	/* This could also be done in C code... */
111 	movl	pmode_cr3, %eax
112 	movl	%eax, %cr3
113 
114 	btl	$WAKEUP_BEHAVIOR_RESTORE_CR4, %edi
115 	jnc	1f
116 	movl	pmode_cr4, %eax
117 	movl	%eax, %cr4
118 1:
119 	btl	$WAKEUP_BEHAVIOR_RESTORE_EFER, %edi
120 	jnc	1f
121 	movl	pmode_efer, %eax
122 	movl	pmode_efer + 4, %edx
123 	movl	$MSR_EFER, %ecx
124 	wrmsr
125 1:
126 
127 	lgdtl	pmode_gdt
128 
129 	/* This really couldn't... */
130 	movl	pmode_entry, %eax
131 	movl	pmode_cr0, %ecx
132 	movl	%ecx, %cr0
133 	ljmpl	$__KERNEL_CS, $pa_startup_32
134 	/* -> jmp *%eax in trampoline_32.S */
135 #else
136 	jmp	trampoline_start
137 #endif
138 SYM_CODE_END(wakeup_start)
139 
140 bogus_real_magic:
141 1:
142 	hlt
143 	jmp	1b
144 
145 	.section ".rodata","a"
146 
147 	/*
148 	 * Set up the wakeup GDT.  We set these up as Big Real Mode,
149 	 * that is, with limits set to 4 GB.  At least the Lenovo
150 	 * Thinkpad X61 is known to need this for the video BIOS
151 	 * initialization quirk to work; this is likely to also
152 	 * be the case for other laptops or integrated video devices.
153 	 */
154 
155 	.balign	16
156 SYM_DATA_START(wakeup_gdt)
157 	.word	3*8-1		/* Self-descriptor */
158 	.long	pa_wakeup_gdt
159 	.word	0
160 
161 	.word	0xffff		/* 16-bit code segment @ real_mode_base */
162 	.long	0x9b000000 + pa_real_mode_base
163 	.word	0x008f		/* big real mode */
164 
165 	.word	0xffff		/* 16-bit data segment @ real_mode_base */
166 	.long	0x93000000 + pa_real_mode_base
167 	.word	0x008f		/* big real mode */
168 SYM_DATA_END(wakeup_gdt)
169 
170 	.section ".rodata","a"
171 	.balign	8
172 
173 	/* This is the standard real-mode IDT */
174 	.balign	16
175 SYM_DATA_START_LOCAL(.Lwakeup_idt)
176 	.word	0xffff		/* limit */
177 	.long	0		/* address */
178 	.word	0
179 SYM_DATA_END(.Lwakeup_idt)
180