diff options
Diffstat (limited to 'arch/x86/realmode/rm/reboot.S')
-rw-r--r-- | arch/x86/realmode/rm/reboot.S | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/arch/x86/realmode/rm/reboot.S b/arch/x86/realmode/rm/reboot.S new file mode 100644 index 000000000000..f932ea61d1c8 --- /dev/null +++ b/arch/x86/realmode/rm/reboot.S | |||
@@ -0,0 +1,156 @@ | |||
1 | #include <linux/linkage.h> | ||
2 | #include <linux/init.h> | ||
3 | #include <asm/segment.h> | ||
4 | #include <asm/page_types.h> | ||
5 | #include <asm/processor-flags.h> | ||
6 | #include <asm/msr-index.h> | ||
7 | #include "realmode.h" | ||
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 | * | ||
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). | ||
19 | */ | ||
20 | .section ".text32", "ax" | ||
21 | .code32 | ||
22 | ENTRY(machine_real_restart_asm) | ||
23 | |||
24 | #ifdef CONFIG_X86_64 | ||
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 | ||
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 | ||
34 | ljmpl $__KERNEL32_CS, $pa_machine_real_restart_paging_off | ||
35 | |||
36 | GLOBAL(machine_real_restart_paging_off) | ||
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 | |||
46 | /* Set up the IDT for real mode. */ | ||
47 | lidtl pa_machine_real_restart_idt | ||
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 | */ | ||
54 | lgdtl pa_machine_real_restart_gdt | ||
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 | ||
65 | ljmpw $8, $1f | ||
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 | */ | ||
90 | .text | ||
91 | .code16 | ||
92 | |||
93 | .balign 16 | ||
94 | machine_real_restart_asm16: | ||
95 | 1: | ||
96 | xorl %ecx, %ecx | ||
97 | movl %cr0, %edx | ||
98 | andl $0x00000011, %edx | ||
99 | orl $0x60000000, %edx | ||
100 | movl %edx, %cr0 | ||
101 | movl %ecx, %cr3 | ||
102 | movl %cr0, %edx | ||
103 | testl $0x60000000, %edx /* If no cache bits -> no wbinvd */ | ||
104 | jz 2f | ||
105 | wbinvd | ||
106 | 2: | ||
107 | andb $0x10, %dl | ||
108 | movl %edx, %cr0 | ||
109 | LJMPW_RM(3f) | ||
110 | 3: | ||
111 | andw %ax, %ax | ||
112 | jz bios | ||
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 | ||
122 | /* This should never return... */ | ||
123 | |||
124 | bios: | ||
125 | ljmpw $0xf000, $0xfff0 | ||
126 | |||
127 | .section ".rodata", "a" | ||
128 | |||
129 | .balign 16 | ||
130 | GLOBAL(machine_real_restart_idt) | ||
131 | .word 0xffff /* Length - real mode default value */ | ||
132 | .long 0 /* Base - real mode default value */ | ||
133 | END(machine_real_restart_idt) | ||
134 | |||
135 | .balign 16 | ||
136 | GLOBAL(machine_real_restart_gdt) | ||
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 | |||
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) | ||
156 | END(machine_real_restart_gdt) | ||