]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | /* SPDX-License-Identifier: GPL-2.0 */ |
e44b7b75 PM |
2 | /* |
3 | * ACPI wakeup real mode startup stub | |
4 | */ | |
8e029fcd | 5 | #include <linux/linkage.h> |
e44b7b75 PM |
6 | #include <asm/segment.h> |
7 | #include <asm/msr-index.h> | |
0341c14d JF |
8 | #include <asm/page_types.h> |
9 | #include <asm/pgtable_types.h> | |
4b4f7280 | 10 | #include <asm/processor-flags.h> |
c4845474 | 11 | #include "realmode.h" |
d1ee4335 | 12 | #include "wakeup.h" |
e44b7b75 | 13 | |
8e029fcd | 14 | .code16 |
e44b7b75 PM |
15 | |
16 | /* This should match the structure in wakeup.h */ | |
8e029fcd JS |
17 | .section ".data", "aw" |
18 | ||
19 | .balign 16 | |
20 | GLOBAL(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 | END(wakeup_header) | |
e44b7b75 PM |
35 | |
36 | .text | |
e44b7b75 | 37 | .code16 |
8e029fcd JS |
38 | |
39 | .balign 16 | |
40 | ENTRY(wakeup_start) | |
c9b77ccb | 41 | cli |
e44b7b75 PM |
42 | cld |
43 | ||
e5684ec4 | 44 | LJMPW_RM(3f) |
c9b77ccb | 45 | 3: |
4b4f7280 PA |
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 | |
c9b77ccb | 55 | ljmpw $8, $2f |
4b4f7280 PA |
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 | |
e5684ec4 | 65 | LJMPW_RM(3f) |
4b4f7280 | 66 | 3: |
e44b7b75 PM |
67 | /* Set up segments */ |
68 | movw %cs, %ax | |
8e029fcd JS |
69 | movw %ax, %ss |
70 | movl $rm_stack_end, %esp | |
e44b7b75 PM |
71 | movw %ax, %ds |
72 | movw %ax, %es | |
8e029fcd JS |
73 | movw %ax, %fs |
74 | movw %ax, %gs | |
e44b7b75 | 75 | |
8e029fcd | 76 | lidtl wakeup_idt |
e44b7b75 | 77 | |
1396adc3 | 78 | /* Clear the EFLAGS */ |
73201dbe PA |
79 | pushl $0 |
80 | popfl | |
e44b7b75 PM |
81 | |
82 | /* Check header signature... */ | |
83 | movl signature, %eax | |
d1ee4335 | 84 | cmpl $WAKEUP_HEADER_SIGNATURE, %eax |
e44b7b75 PM |
85 | jne bogus_real_magic |
86 | ||
87 | /* Check we really have everything... */ | |
88 | movl end_signature, %eax | |
61f54461 | 89 | cmpl $REALMODE_END_SIGNATURE, %eax |
e44b7b75 PM |
90 | jne bogus_real_magic |
91 | ||
92 | /* Call the C code */ | |
93 | calll main | |
94 | ||
7a313666 KC |
95 | /* Restore MISC_ENABLE before entering protected mode, in case |
96 | BIOS decided to clear XD_DISABLE during S3. */ | |
73201dbe PA |
97 | movl pmode_behavior, %edi |
98 | btl $WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE, %edi | |
7a313666 KC |
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 | ||
e44b7b75 PM |
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 | ||
73201dbe | 114 | btl $WAKEUP_BEHAVIOR_RESTORE_CR4, %edi |
1396adc3 | 115 | jnc 1f |
73201dbe PA |
116 | movl pmode_cr4, %eax |
117 | movl %eax, %cr4 | |
e44b7b75 | 118 | 1: |
73201dbe | 119 | btl $WAKEUP_BEHAVIOR_RESTORE_EFER, %edi |
1396adc3 | 120 | jnc 1f |
e44b7b75 PM |
121 | movl pmode_efer, %eax |
122 | movl pmode_efer + 4, %edx | |
cfaa71ee | 123 | movl $MSR_EFER, %ecx |
e44b7b75 PM |
124 | wrmsr |
125 | 1: | |
126 | ||
127 | lgdtl pmode_gdt | |
128 | ||
129 | /* This really couldn't... */ | |
968ff9ee PA |
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 */ | |
e44b7b75 | 135 | #else |
f37240f1 | 136 | jmp trampoline_start |
e44b7b75 PM |
137 | #endif |
138 | ||
139 | bogus_real_magic: | |
140 | 1: | |
141 | hlt | |
142 | jmp 1b | |
143 | ||
c9b77ccb JS |
144 | .section ".rodata","a" |
145 | ||
146 | /* | |
147 | * Set up the wakeup GDT. We set these up as Big Real Mode, | |
148 | * that is, with limits set to 4 GB. At least the Lenovo | |
149 | * Thinkpad X61 is known to need this for the video BIOS | |
150 | * initialization quirk to work; this is likely to also | |
151 | * be the case for other laptops or integrated video devices. | |
152 | */ | |
153 | ||
c9b77ccb | 154 | .balign 16 |
8e029fcd | 155 | GLOBAL(wakeup_gdt) |
c9b77ccb JS |
156 | .word 3*8-1 /* Self-descriptor */ |
157 | .long pa_wakeup_gdt | |
158 | .word 0 | |
159 | ||
160 | .word 0xffff /* 16-bit code segment @ real_mode_base */ | |
161 | .long 0x9b000000 + pa_real_mode_base | |
162 | .word 0x008f /* big real mode */ | |
163 | ||
164 | .word 0xffff /* 16-bit data segment @ real_mode_base */ | |
165 | .long 0x93000000 + pa_real_mode_base | |
166 | .word 0x008f /* big real mode */ | |
8e029fcd | 167 | END(wakeup_gdt) |
c9b77ccb | 168 | |
8e029fcd | 169 | .section ".rodata","a" |
4b4f7280 PA |
170 | .balign 8 |
171 | ||
172 | /* This is the standard real-mode IDT */ | |
8e029fcd JS |
173 | .balign 16 |
174 | GLOBAL(wakeup_idt) | |
4b4f7280 PA |
175 | .word 0xffff /* limit */ |
176 | .long 0 /* address */ | |
177 | .word 0 | |
8e029fcd | 178 | END(wakeup_idt) |