| #include <linux/linkage.h> |
| #include <linux/init.h> |
| #include <asm/segment.h> |
| #include <asm/page_types.h> |
| #include "realmode.h" |
| |
| /* |
| * The following code and data reboots the machine by switching to real |
| * mode and jumping to the BIOS reset entry point, as if the CPU has |
| * really been reset. The previous version asked the keyboard |
| * controller to pulse the CPU reset line, which is more thorough, but |
| * doesn't work with at least one type of 486 motherboard. It is easy |
| * to stop this code working; hence the copious comments. |
| * |
| * This code is called with the restart type (0 = BIOS, 1 = APM) in %eax. |
| */ |
| .section ".text32", "ax" |
| .code32 |
| |
| .balign 16 |
| ENTRY(machine_real_restart_asm) |
| /* Set up the IDT for real mode. */ |
| lidtl pa_machine_real_restart_idt |
| |
| /* |
| * Set up a GDT from which we can load segment descriptors for real |
| * mode. The GDT is not used in real mode; it is just needed here to |
| * prepare the descriptors. |
| */ |
| lgdtl pa_machine_real_restart_gdt |
| |
| /* |
| * Load the data segment registers with 16-bit compatible values |
| */ |
| movl $16, %ecx |
| movl %ecx, %ds |
| movl %ecx, %es |
| movl %ecx, %fs |
| movl %ecx, %gs |
| movl %ecx, %ss |
| ljmpw $8, $1f |
| |
| /* |
| * This is 16-bit protected mode code to disable paging and the cache, |
| * switch to real mode and jump to the BIOS reset code. |
| * |
| * The instruction that switches to real mode by writing to CR0 must be |
| * followed immediately by a far jump instruction, which set CS to a |
| * valid value for real mode, and flushes the prefetch queue to avoid |
| * running instructions that have already been decoded in protected |
| * mode. |
| * |
| * Clears all the flags except ET, especially PG (paging), PE |
| * (protected-mode enable) and TS (task switch for coprocessor state |
| * save). Flushes the TLB after paging has been disabled. Sets CD and |
| * NW, to disable the cache on a 486, and invalidates the cache. This |
| * is more like the state of a 486 after reset. I don't know if |
| * something else should be done for other chips. |
| * |
| * More could be done here to set up the registers as if a CPU reset had |
| * occurred; hopefully real BIOSs don't assume much. This is not the |
| * actual BIOS entry point, anyway (that is at 0xfffffff0). |
| * |
| * Most of this work is probably excessive, but it is what is tested. |
| */ |
| .text |
| .code16 |
| |
| .balign 16 |
| machine_real_restart_asm16: |
| 1: |
| xorl %ecx, %ecx |
| movl %cr0, %edx |
| andl $0x00000011, %edx |
| orl $0x60000000, %edx |
| movl %edx, %cr0 |
| movl %ecx, %cr3 |
| movl %cr0, %edx |
| testl $0x60000000, %edx /* If no cache bits -> no wbinvd */ |
| jz 2f |
| wbinvd |
| 2: |
| andb $0x10, %dl |
| movl %edx, %cr0 |
| LJMPW_RM(3f) |
| 3: |
| andw %ax, %ax |
| jz bios |
| |
| apm: |
| movw $0x1000, %ax |
| movw %ax, %ss |
| movw $0xf000, %sp |
| movw $0x5307, %ax |
| movw $0x0001, %bx |
| movw $0x0003, %cx |
| int $0x15 |
| /* This should never return... */ |
| |
| bios: |
| ljmpw $0xf000, $0xfff0 |
| |
| .section ".rodata", "a" |
| |
| .balign 16 |
| GLOBAL(machine_real_restart_idt) |
| .word 0xffff /* Length - real mode default value */ |
| .long 0 /* Base - real mode default value */ |
| END(machine_real_restart_idt) |
| |
| .balign 16 |
| GLOBAL(machine_real_restart_gdt) |
| /* Self-pointer */ |
| .word 0xffff /* Length - real mode default value */ |
| .long pa_machine_real_restart_gdt |
| .word 0 |
| |
| /* |
| * 16-bit code segment pointing to real_mode_seg |
| * Selector value 8 |
| */ |
| .word 0xffff /* Limit */ |
| .long 0x9b000000 + pa_real_mode_base |
| .word 0 |
| |
| /* |
| * 16-bit data segment with the selector value 16 = 0x10 and |
| * base value 0x100; since this is consistent with real mode |
| * semantics we don't have to reload the segments once CR0.PE = 0. |
| */ |
| .quad GDT_ENTRY(0x0093, 0x100, 0xffff) |
| END(machine_real_restart_gdt) |