diff options
author | H. Peter Anvin <hpa@linux.intel.com> | 2011-02-14 21:36:03 -0500 |
---|---|---|
committer | H. Peter Anvin <hpa@linux.intel.com> | 2011-02-18 00:05:34 -0500 |
commit | 3d35ac346e981162eeba391e496faceed4753e7b (patch) | |
tree | 1f902bd0b8d62aa65b9d216483cbecc9a712a72b /arch/x86/kernel/reboot_32.S | |
parent | 014eea518af3d141e276664cf40ef3da899eba35 (diff) |
x86, reboot: Move the real-mode reboot code to an assembly file
Move the real-mode reboot code out to an assembly file (reboot_32.S)
which is allocated using the common lowmem trampoline allocator.
Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
LKML-Reference: <4D5DFBE4.7090104@intel.com>
Cc: Stephen Rothwell <sfr@canb.auug.org.au>
Cc: Rafael J. Wysocki <rjw@sisk.pl>
Cc: Matthieu Castet <castet.matthieu@free.fr>
Diffstat (limited to 'arch/x86/kernel/reboot_32.S')
-rw-r--r-- | arch/x86/kernel/reboot_32.S | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/arch/x86/kernel/reboot_32.S b/arch/x86/kernel/reboot_32.S new file mode 100644 index 00000000000..f242356a096 --- /dev/null +++ b/arch/x86/kernel/reboot_32.S | |||
@@ -0,0 +1,131 @@ | |||
1 | #include <linux/linkage.h> | ||
2 | #include <linux/init.h> | ||
3 | #include <asm/segment.h> | ||
4 | #include <asm/page_types.h> | ||
5 | |||
6 | /* | ||
7 | * The following code and data reboots the machine by switching to real | ||
8 | * mode and jumping to the BIOS reset entry point, as if the CPU has | ||
9 | * really been reset. The previous version asked the keyboard | ||
10 | * controller to pulse the CPU reset line, which is more thorough, but | ||
11 | * doesn't work with at least one type of 486 motherboard. It is easy | ||
12 | * to stop this code working; hence the copious comments. | ||
13 | * | ||
14 | * This code is called with the restart type (0 = BIOS, 1 = APM) in %eax. | ||
15 | */ | ||
16 | .section ".x86_trampoline","a" | ||
17 | .balign 16 | ||
18 | .code32 | ||
19 | ENTRY(machine_real_restart_asm) | ||
20 | r_base = . | ||
21 | /* Get our own relocated address */ | ||
22 | call 1f | ||
23 | 1: popl %ebx | ||
24 | subl $1b, %ebx | ||
25 | |||
26 | /* Patch post-real-mode segment jump */ | ||
27 | movw dispatch_table(%ebx,%ecx,2),%cx | ||
28 | movw %cx, 101f(%ebx) | ||
29 | movw %ax, 102f(%ebx) | ||
30 | |||
31 | /* Set up the IDT for real mode. */ | ||
32 | lidtl machine_real_restart_idt(%ebx) | ||
33 | |||
34 | /* | ||
35 | * Set up a GDT from which we can load segment descriptors for real | ||
36 | * mode. The GDT is not used in real mode; it is just needed here to | ||
37 | * prepare the descriptors. | ||
38 | */ | ||
39 | lgdtl machine_real_restart_gdt(%ebx) | ||
40 | |||
41 | /* | ||
42 | * Load the data segment registers with 16-bit compatible values | ||
43 | */ | ||
44 | movl $16, %ecx | ||
45 | movl %ecx, %ds | ||
46 | movl %ecx, %es | ||
47 | movl %ecx, %fs | ||
48 | movl %ecx, %gs | ||
49 | movl %ecx, %ss | ||
50 | ljmpl $8, $1f - r_base | ||
51 | |||
52 | /* | ||
53 | * This is 16-bit protected mode code to disable paging and the cache, | ||
54 | * switch to real mode and jump to the BIOS reset code. | ||
55 | * | ||
56 | * The instruction that switches to real mode by writing to CR0 must be | ||
57 | * followed immediately by a far jump instruction, which set CS to a | ||
58 | * valid value for real mode, and flushes the prefetch queue to avoid | ||
59 | * running instructions that have already been decoded in protected | ||
60 | * mode. | ||
61 | * | ||
62 | * Clears all the flags except ET, especially PG (paging), PE | ||
63 | * (protected-mode enable) and TS (task switch for coprocessor state | ||
64 | * save). Flushes the TLB after paging has been disabled. Sets CD and | ||
65 | * NW, to disable the cache on a 486, and invalidates the cache. This | ||
66 | * is more like the state of a 486 after reset. I don't know if | ||
67 | * something else should be done for other chips. | ||
68 | * | ||
69 | * More could be done here to set up the registers as if a CPU reset had | ||
70 | * occurred; hopefully real BIOSs don't assume much. This is not the | ||
71 | * actual BIOS entry point, anyway (that is at 0xfffffff0). | ||
72 | * | ||
73 | * Most of this work is probably excessive, but it is what is tested. | ||
74 | */ | ||
75 | .code16 | ||
76 | 1: | ||
77 | xorl %ecx, %ecx | ||
78 | movl %cr0, %eax | ||
79 | andl $0x00000011, %eax | ||
80 | orl $0x60000000, %eax | ||
81 | movl %eax, %cr0 | ||
82 | movl %ecx, %cr3 | ||
83 | movl %cr0, %edx | ||
84 | andl $0x60000000, %edx /* If no cache bits -> no wbinvd */ | ||
85 | jz 2f | ||
86 | wbinvd | ||
87 | 2: | ||
88 | andb $0x10, %al | ||
89 | movl %eax, %cr0 | ||
90 | .byte 0xea /* ljmpw */ | ||
91 | 101: .word 0 /* Offset */ | ||
92 | 102: .word 0 /* Segment */ | ||
93 | |||
94 | bios: | ||
95 | ljmpw $0xf000, $0xfff0 | ||
96 | |||
97 | apm: | ||
98 | movw $0x1000, %ax | ||
99 | movw %ax, %ss | ||
100 | movw $0xf000, %sp | ||
101 | movw $0x5307, %ax | ||
102 | movw $0x0001, %bx | ||
103 | movw $0x0003, %cx | ||
104 | int $0x15 | ||
105 | |||
106 | END(machine_real_restart_asm) | ||
107 | |||
108 | .balign 16 | ||
109 | /* These must match <asm/reboot.h */ | ||
110 | dispatch_table: | ||
111 | .word bios - r_base | ||
112 | .word apm - r_base | ||
113 | END(dispatch_table) | ||
114 | |||
115 | .balign 16 | ||
116 | machine_real_restart_idt: | ||
117 | .word 0xffff /* Length - real mode default value */ | ||
118 | .long 0 /* Base - real mode default value */ | ||
119 | END(machine_real_restart_idt) | ||
120 | |||
121 | .balign 16 | ||
122 | ENTRY(machine_real_restart_gdt) | ||
123 | .quad 0 /* Self-pointer, filled in by PM code */ | ||
124 | .quad 0 /* 16-bit code segment, filled in by PM code */ | ||
125 | /* | ||
126 | * 16-bit data segment with the selector value 16 = 0x10 and | ||
127 | * base value 0x100; since this is consistent with real mode | ||
128 | * semantics we don't have to reload the segments once CR0.PE = 0. | ||
129 | */ | ||
130 | .quad GDT_ENTRY(0x0093, 0x100, 0xffff) | ||
131 | END(machine_real_restart_gdt) | ||