H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 1 | #include <linux/linkage.h> |
| 2 | #include <linux/init.h> |
| 3 | #include <asm/segment.h> |
| 4 | #include <asm/page_types.h> |
H. Peter Anvin | 6505139 | 2012-06-16 21:47:37 -0700 | [diff] [blame] | 5 | #include <asm/processor-flags.h> |
| 6 | #include <asm/msr-index.h> |
H. Peter Anvin | e5684ec | 2012-05-08 21:22:37 +0300 | [diff] [blame] | 7 | #include "realmode.h" |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 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 | * |
H. Peter Anvin | 6505139 | 2012-06-16 21:47:37 -0700 | [diff] [blame] | 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). |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 19 | */ |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 20 | .section ".text32", "ax" |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 21 | .code32 |
Jarkko Sakkinen | 8e029fc | 2012-05-08 21:22:40 +0300 | [diff] [blame] | 22 | ENTRY(machine_real_restart_asm) |
H. Peter Anvin | 6505139 | 2012-06-16 21:47:37 -0700 | [diff] [blame] | 23 | |
| 24 | #ifdef CONFIG_X86_64 |
H. Peter Anvin | 9751d76 | 2012-06-21 10:25:03 -0700 | [diff] [blame] | 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 |
H. Peter Anvin | 6505139 | 2012-06-16 21:47:37 -0700 | [diff] [blame] | 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 |
H. Peter Anvin | 9751d76 | 2012-06-21 10:25:03 -0700 | [diff] [blame] | 34 | ljmpl $__KERNEL32_CS, $pa_machine_real_restart_paging_off |
H. Peter Anvin | 6505139 | 2012-06-16 21:47:37 -0700 | [diff] [blame] | 35 | |
H. Peter Anvin | 9751d76 | 2012-06-21 10:25:03 -0700 | [diff] [blame] | 36 | GLOBAL(machine_real_restart_paging_off) |
H. Peter Anvin | 6505139 | 2012-06-16 21:47:37 -0700 | [diff] [blame] | 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 | |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 46 | /* Set up the IDT for real mode. */ |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 47 | lidtl pa_machine_real_restart_idt |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 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 | */ |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 54 | lgdtl pa_machine_real_restart_gdt |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 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 |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 65 | ljmpw $8, $1f |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 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 | */ |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 90 | .text |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 91 | .code16 |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 92 | |
Jarkko Sakkinen | 8e029fc | 2012-05-08 21:22:40 +0300 | [diff] [blame] | 93 | .balign 16 |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 94 | machine_real_restart_asm16: |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 95 | 1: |
| 96 | xorl %ecx, %ecx |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 97 | movl %cr0, %edx |
| 98 | andl $0x00000011, %edx |
| 99 | orl $0x60000000, %edx |
| 100 | movl %edx, %cr0 |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 101 | movl %ecx, %cr3 |
| 102 | movl %cr0, %edx |
Jarkko Sakkinen | 34d0b02 | 2012-05-10 10:11:38 +0300 | [diff] [blame] | 103 | testl $0x60000000, %edx /* If no cache bits -> no wbinvd */ |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 104 | jz 2f |
| 105 | wbinvd |
| 106 | 2: |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 107 | andb $0x10, %dl |
| 108 | movl %edx, %cr0 |
H. Peter Anvin | e5684ec | 2012-05-08 21:22:37 +0300 | [diff] [blame] | 109 | LJMPW_RM(3f) |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 110 | 3: |
H. Peter Anvin | 6feb592 | 2012-05-08 21:22:39 +0300 | [diff] [blame] | 111 | andw %ax, %ax |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 112 | jz bios |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 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 |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 122 | /* This should never return... */ |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 123 | |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 124 | bios: |
| 125 | ljmpw $0xf000, $0xfff0 |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 126 | |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 127 | .section ".rodata", "a" |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 128 | |
Jarkko Sakkinen | 8e029fc | 2012-05-08 21:22:40 +0300 | [diff] [blame] | 129 | .balign 16 |
| 130 | GLOBAL(machine_real_restart_idt) |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 131 | .word 0xffff /* Length - real mode default value */ |
| 132 | .long 0 /* Base - real mode default value */ |
Jarkko Sakkinen | 8e029fc | 2012-05-08 21:22:40 +0300 | [diff] [blame] | 133 | END(machine_real_restart_idt) |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 134 | |
Jarkko Sakkinen | 8e029fc | 2012-05-08 21:22:40 +0300 | [diff] [blame] | 135 | .balign 16 |
| 136 | GLOBAL(machine_real_restart_gdt) |
Jarkko Sakkinen | 5a8c9ae | 2012-05-08 21:22:27 +0300 | [diff] [blame] | 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 | |
H. Peter Anvin | 3d35ac3 | 2011-02-14 18:36:03 -0800 | [diff] [blame] | 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) |
Jarkko Sakkinen | 8e029fc | 2012-05-08 21:22:40 +0300 | [diff] [blame] | 156 | END(machine_real_restart_gdt) |