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() |
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 | |