]>
Commit | Line | Data |
---|---|---|
b2441318 | 1 | /* SPDX-License-Identifier: GPL-2.0 */ |
3d35ac34 | 2 | #include <linux/linkage.h> |
3d35ac34 PA |
3 | #include <asm/segment.h> |
4 | #include <asm/page_types.h> | |
65051397 PA |
5 | #include <asm/processor-flags.h> |
6 | #include <asm/msr-index.h> | |
e5684ec4 | 7 | #include "realmode.h" |
3d35ac34 PA |
8 | |
9 | /* | |
10 | * The following code and data reboots the machine by switching to real | |
11 | * mode and jumping to the BIOS reset entry point, as if the CPU has | |
12 | * really been reset. The previous version asked the keyboard | |
13 | * controller to pulse the CPU reset line, which is more thorough, but | |
14 | * doesn't work with at least one type of 486 motherboard. It is easy | |
15 | * to stop this code working; hence the copious comments. | |
16 | * | |
65051397 PA |
17 | * This code is called with the restart type (0 = BIOS, 1 = APM) in |
18 | * the primary argument register (%eax for 32 bit, %edi for 64 bit). | |
3d35ac34 | 19 | */ |
5a8c9aeb | 20 | .section ".text32", "ax" |
3d35ac34 | 21 | .code32 |
8e029fcd | 22 | ENTRY(machine_real_restart_asm) |
65051397 PA |
23 | |
24 | #ifdef CONFIG_X86_64 | |
9751d762 PA |
25 | /* Switch to trampoline GDT as it is guaranteed < 4 GiB */ |
26 | movl $__KERNEL_DS, %eax | |
27 | movl %eax, %ds | |
28 | lgdtl pa_tr_gdt | |
65051397 PA |
29 | |
30 | /* Disable paging to drop us out of long mode */ | |
31 | movl %cr0, %eax | |
32 | andl $~X86_CR0_PG, %eax | |
33 | movl %eax, %cr0 | |
9751d762 | 34 | ljmpl $__KERNEL32_CS, $pa_machine_real_restart_paging_off |
65051397 | 35 | |
9751d762 | 36 | GLOBAL(machine_real_restart_paging_off) |
65051397 PA |
37 | xorl %eax, %eax |
38 | xorl %edx, %edx | |
39 | movl $MSR_EFER, %ecx | |
40 | wrmsr | |
41 | ||
42 | movl %edi, %eax | |
43 | ||
44 | #endif /* CONFIG_X86_64 */ | |
45 | ||
3d35ac34 | 46 | /* Set up the IDT for real mode. */ |
5a8c9aeb | 47 | lidtl pa_machine_real_restart_idt |
3d35ac34 PA |
48 | |
49 | /* | |
50 | * Set up a GDT from which we can load segment descriptors for real | |
51 | * mode. The GDT is not used in real mode; it is just needed here to | |
52 | * prepare the descriptors. | |
53 | */ | |
5a8c9aeb | 54 | lgdtl pa_machine_real_restart_gdt |
3d35ac34 PA |
55 | |
56 | /* | |
57 | * Load the data segment registers with 16-bit compatible values | |
58 | */ | |
59 | movl $16, %ecx | |
60 | movl %ecx, %ds | |
61 | movl %ecx, %es | |
62 | movl %ecx, %fs | |
63 | movl %ecx, %gs | |
64 | movl %ecx, %ss | |
5a8c9aeb | 65 | ljmpw $8, $1f |
3d35ac34 PA |
66 | |
67 | /* | |
68 | * This is 16-bit protected mode code to disable paging and the cache, | |
69 | * switch to real mode and jump to the BIOS reset code. | |
70 | * | |
71 | * The instruction that switches to real mode by writing to CR0 must be | |
72 | * followed immediately by a far jump instruction, which set CS to a | |
73 | * valid value for real mode, and flushes the prefetch queue to avoid | |
74 | * running instructions that have already been decoded in protected | |
75 | * mode. | |
76 | * | |
77 | * Clears all the flags except ET, especially PG (paging), PE | |
78 | * (protected-mode enable) and TS (task switch for coprocessor state | |
79 | * save). Flushes the TLB after paging has been disabled. Sets CD and | |
80 | * NW, to disable the cache on a 486, and invalidates the cache. This | |
81 | * is more like the state of a 486 after reset. I don't know if | |
82 | * something else should be done for other chips. | |
83 | * | |
84 | * More could be done here to set up the registers as if a CPU reset had | |
85 | * occurred; hopefully real BIOSs don't assume much. This is not the | |
86 | * actual BIOS entry point, anyway (that is at 0xfffffff0). | |
87 | * | |
88 | * Most of this work is probably excessive, but it is what is tested. | |
89 | */ | |
5a8c9aeb | 90 | .text |
3d35ac34 | 91 | .code16 |
5a8c9aeb | 92 | |
8e029fcd | 93 | .balign 16 |
5a8c9aeb | 94 | machine_real_restart_asm16: |
3d35ac34 PA |
95 | 1: |
96 | xorl %ecx, %ecx | |
5a8c9aeb JS |
97 | movl %cr0, %edx |
98 | andl $0x00000011, %edx | |
99 | orl $0x60000000, %edx | |
100 | movl %edx, %cr0 | |
3d35ac34 PA |
101 | movl %ecx, %cr3 |
102 | movl %cr0, %edx | |
34d0b02e | 103 | testl $0x60000000, %edx /* If no cache bits -> no wbinvd */ |
3d35ac34 PA |
104 | jz 2f |
105 | wbinvd | |
106 | 2: | |
5a8c9aeb JS |
107 | andb $0x10, %dl |
108 | movl %edx, %cr0 | |
e5684ec4 | 109 | LJMPW_RM(3f) |
5a8c9aeb | 110 | 3: |
6feb592d | 111 | andw %ax, %ax |
5a8c9aeb | 112 | jz bios |
3d35ac34 PA |
113 | |
114 | apm: | |
115 | movw $0x1000, %ax | |
116 | movw %ax, %ss | |
117 | movw $0xf000, %sp | |
118 | movw $0x5307, %ax | |
119 | movw $0x0001, %bx | |
120 | movw $0x0003, %cx | |
121 | int $0x15 | |
5a8c9aeb | 122 | /* This should never return... */ |
3d35ac34 | 123 | |
5a8c9aeb JS |
124 | bios: |
125 | ljmpw $0xf000, $0xfff0 | |
3d35ac34 | 126 | |
5a8c9aeb | 127 | .section ".rodata", "a" |
3d35ac34 | 128 | |
8e029fcd JS |
129 | .balign 16 |
130 | GLOBAL(machine_real_restart_idt) | |
3d35ac34 PA |
131 | .word 0xffff /* Length - real mode default value */ |
132 | .long 0 /* Base - real mode default value */ | |
8e029fcd | 133 | END(machine_real_restart_idt) |
3d35ac34 | 134 | |
8e029fcd JS |
135 | .balign 16 |
136 | GLOBAL(machine_real_restart_gdt) | |
5a8c9aeb JS |
137 | /* Self-pointer */ |
138 | .word 0xffff /* Length - real mode default value */ | |
139 | .long pa_machine_real_restart_gdt | |
140 | .word 0 | |
141 | ||
142 | /* | |
143 | * 16-bit code segment pointing to real_mode_seg | |
144 | * Selector value 8 | |
145 | */ | |
146 | .word 0xffff /* Limit */ | |
147 | .long 0x9b000000 + pa_real_mode_base | |
148 | .word 0 | |
149 | ||
3d35ac34 PA |
150 | /* |
151 | * 16-bit data segment with the selector value 16 = 0x10 and | |
152 | * base value 0x100; since this is consistent with real mode | |
153 | * semantics we don't have to reload the segments once CR0.PE = 0. | |
154 | */ | |
155 | .quad GDT_ENTRY(0x0093, 0x100, 0xffff) | |
8e029fcd | 156 | END(machine_real_restart_gdt) |