Commit | Line | Data |
---|---|---|
3d35ac34 PA |
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 | ||
ee1b06ea PA |
26 | /* Compute the equivalent real-mode segment */ |
27 | movl %ebx, %ecx | |
28 | shrl $4, %ecx | |
29 | ||
3d35ac34 | 30 | /* Patch post-real-mode segment jump */ |
ee1b06ea PA |
31 | movw dispatch_table(%ebx,%eax,2),%ax |
32 | movw %ax, 101f(%ebx) | |
33 | movw %cx, 102f(%ebx) | |
3d35ac34 PA |
34 | |
35 | /* Set up the IDT for real mode. */ | |
36 | lidtl machine_real_restart_idt(%ebx) | |
37 | ||
38 | /* | |
39 | * Set up a GDT from which we can load segment descriptors for real | |
40 | * mode. The GDT is not used in real mode; it is just needed here to | |
41 | * prepare the descriptors. | |
42 | */ | |
43 | lgdtl machine_real_restart_gdt(%ebx) | |
44 | ||
45 | /* | |
46 | * Load the data segment registers with 16-bit compatible values | |
47 | */ | |
48 | movl $16, %ecx | |
49 | movl %ecx, %ds | |
50 | movl %ecx, %es | |
51 | movl %ecx, %fs | |
52 | movl %ecx, %gs | |
53 | movl %ecx, %ss | |
54 | ljmpl $8, $1f - r_base | |
55 | ||
56 | /* | |
57 | * This is 16-bit protected mode code to disable paging and the cache, | |
58 | * switch to real mode and jump to the BIOS reset code. | |
59 | * | |
60 | * The instruction that switches to real mode by writing to CR0 must be | |
61 | * followed immediately by a far jump instruction, which set CS to a | |
62 | * valid value for real mode, and flushes the prefetch queue to avoid | |
63 | * running instructions that have already been decoded in protected | |
64 | * mode. | |
65 | * | |
66 | * Clears all the flags except ET, especially PG (paging), PE | |
67 | * (protected-mode enable) and TS (task switch for coprocessor state | |
68 | * save). Flushes the TLB after paging has been disabled. Sets CD and | |
69 | * NW, to disable the cache on a 486, and invalidates the cache. This | |
70 | * is more like the state of a 486 after reset. I don't know if | |
71 | * something else should be done for other chips. | |
72 | * | |
73 | * More could be done here to set up the registers as if a CPU reset had | |
74 | * occurred; hopefully real BIOSs don't assume much. This is not the | |
75 | * actual BIOS entry point, anyway (that is at 0xfffffff0). | |
76 | * | |
77 | * Most of this work is probably excessive, but it is what is tested. | |
78 | */ | |
79 | .code16 | |
80 | 1: | |
81 | xorl %ecx, %ecx | |
82 | movl %cr0, %eax | |
83 | andl $0x00000011, %eax | |
84 | orl $0x60000000, %eax | |
85 | movl %eax, %cr0 | |
86 | movl %ecx, %cr3 | |
87 | movl %cr0, %edx | |
88 | andl $0x60000000, %edx /* If no cache bits -> no wbinvd */ | |
89 | jz 2f | |
90 | wbinvd | |
91 | 2: | |
92 | andb $0x10, %al | |
93 | movl %eax, %cr0 | |
94 | .byte 0xea /* ljmpw */ | |
95 | 101: .word 0 /* Offset */ | |
96 | 102: .word 0 /* Segment */ | |
97 | ||
98 | bios: | |
99 | ljmpw $0xf000, $0xfff0 | |
100 | ||
101 | apm: | |
102 | movw $0x1000, %ax | |
103 | movw %ax, %ss | |
104 | movw $0xf000, %sp | |
105 | movw $0x5307, %ax | |
106 | movw $0x0001, %bx | |
107 | movw $0x0003, %cx | |
108 | int $0x15 | |
109 | ||
110 | END(machine_real_restart_asm) | |
111 | ||
112 | .balign 16 | |
113 | /* These must match <asm/reboot.h */ | |
114 | dispatch_table: | |
115 | .word bios - r_base | |
116 | .word apm - r_base | |
117 | END(dispatch_table) | |
118 | ||
119 | .balign 16 | |
120 | machine_real_restart_idt: | |
121 | .word 0xffff /* Length - real mode default value */ | |
122 | .long 0 /* Base - real mode default value */ | |
123 | END(machine_real_restart_idt) | |
124 | ||
125 | .balign 16 | |
126 | ENTRY(machine_real_restart_gdt) | |
127 | .quad 0 /* Self-pointer, filled in by PM code */ | |
128 | .quad 0 /* 16-bit code segment, filled in by PM code */ | |
129 | /* | |
130 | * 16-bit data segment with the selector value 16 = 0x10 and | |
131 | * base value 0x100; since this is consistent with real mode | |
132 | * semantics we don't have to reload the segments once CR0.PE = 0. | |
133 | */ | |
134 | .quad GDT_ENTRY(0x0093, 0x100, 0xffff) | |
135 | END(machine_real_restart_gdt) |