162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0 */ 262306a36Sopenharmony_ci#include <linux/linkage.h> 362306a36Sopenharmony_ci#include <asm/segment.h> 462306a36Sopenharmony_ci#include <asm/page_types.h> 562306a36Sopenharmony_ci#include <asm/processor-flags.h> 662306a36Sopenharmony_ci#include <asm/msr-index.h> 762306a36Sopenharmony_ci#include "realmode.h" 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci/* 1062306a36Sopenharmony_ci * The following code and data reboots the machine by switching to real 1162306a36Sopenharmony_ci * mode and jumping to the BIOS reset entry point, as if the CPU has 1262306a36Sopenharmony_ci * really been reset. The previous version asked the keyboard 1362306a36Sopenharmony_ci * controller to pulse the CPU reset line, which is more thorough, but 1462306a36Sopenharmony_ci * doesn't work with at least one type of 486 motherboard. It is easy 1562306a36Sopenharmony_ci * to stop this code working; hence the copious comments. 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * This code is called with the restart type (0 = BIOS, 1 = APM) in 1862306a36Sopenharmony_ci * the primary argument register (%eax for 32 bit, %edi for 64 bit). 1962306a36Sopenharmony_ci */ 2062306a36Sopenharmony_ci .section ".text32", "ax" 2162306a36Sopenharmony_ci .code32 2262306a36Sopenharmony_ciSYM_CODE_START(machine_real_restart_asm) 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#ifdef CONFIG_X86_64 2562306a36Sopenharmony_ci /* Switch to trampoline GDT as it is guaranteed < 4 GiB */ 2662306a36Sopenharmony_ci movl $__KERNEL_DS, %eax 2762306a36Sopenharmony_ci movl %eax, %ds 2862306a36Sopenharmony_ci lgdtl pa_tr_gdt 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci /* Disable paging to drop us out of long mode */ 3162306a36Sopenharmony_ci movl %cr0, %eax 3262306a36Sopenharmony_ci andl $~X86_CR0_PG, %eax 3362306a36Sopenharmony_ci movl %eax, %cr0 3462306a36Sopenharmony_ci ljmpl $__KERNEL32_CS, $pa_machine_real_restart_paging_off 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ciSYM_INNER_LABEL(machine_real_restart_paging_off, SYM_L_GLOBAL) 3762306a36Sopenharmony_ci xorl %eax, %eax 3862306a36Sopenharmony_ci xorl %edx, %edx 3962306a36Sopenharmony_ci movl $MSR_EFER, %ecx 4062306a36Sopenharmony_ci wrmsr 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci movl %edi, %eax 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci#endif /* CONFIG_X86_64 */ 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci /* Set up the IDT for real mode. */ 4762306a36Sopenharmony_ci lidtl pa_machine_real_restart_idt 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci /* 5062306a36Sopenharmony_ci * Set up a GDT from which we can load segment descriptors for real 5162306a36Sopenharmony_ci * mode. The GDT is not used in real mode; it is just needed here to 5262306a36Sopenharmony_ci * prepare the descriptors. 5362306a36Sopenharmony_ci */ 5462306a36Sopenharmony_ci lgdtl pa_machine_real_restart_gdt 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci /* 5762306a36Sopenharmony_ci * Load the data segment registers with 16-bit compatible values 5862306a36Sopenharmony_ci */ 5962306a36Sopenharmony_ci movl $16, %ecx 6062306a36Sopenharmony_ci movl %ecx, %ds 6162306a36Sopenharmony_ci movl %ecx, %es 6262306a36Sopenharmony_ci movl %ecx, %fs 6362306a36Sopenharmony_ci movl %ecx, %gs 6462306a36Sopenharmony_ci movl %ecx, %ss 6562306a36Sopenharmony_ci ljmpw $8, $1f 6662306a36Sopenharmony_ciSYM_CODE_END(machine_real_restart_asm) 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci/* 6962306a36Sopenharmony_ci * This is 16-bit protected mode code to disable paging and the cache, 7062306a36Sopenharmony_ci * switch to real mode and jump to the BIOS reset code. 7162306a36Sopenharmony_ci * 7262306a36Sopenharmony_ci * The instruction that switches to real mode by writing to CR0 must be 7362306a36Sopenharmony_ci * followed immediately by a far jump instruction, which set CS to a 7462306a36Sopenharmony_ci * valid value for real mode, and flushes the prefetch queue to avoid 7562306a36Sopenharmony_ci * running instructions that have already been decoded in protected 7662306a36Sopenharmony_ci * mode. 7762306a36Sopenharmony_ci * 7862306a36Sopenharmony_ci * Clears all the flags except ET, especially PG (paging), PE 7962306a36Sopenharmony_ci * (protected-mode enable) and TS (task switch for coprocessor state 8062306a36Sopenharmony_ci * save). Flushes the TLB after paging has been disabled. Sets CD and 8162306a36Sopenharmony_ci * NW, to disable the cache on a 486, and invalidates the cache. This 8262306a36Sopenharmony_ci * is more like the state of a 486 after reset. I don't know if 8362306a36Sopenharmony_ci * something else should be done for other chips. 8462306a36Sopenharmony_ci * 8562306a36Sopenharmony_ci * More could be done here to set up the registers as if a CPU reset had 8662306a36Sopenharmony_ci * occurred; hopefully real BIOSs don't assume much. This is not the 8762306a36Sopenharmony_ci * actual BIOS entry point, anyway (that is at 0xfffffff0). 8862306a36Sopenharmony_ci * 8962306a36Sopenharmony_ci * Most of this work is probably excessive, but it is what is tested. 9062306a36Sopenharmony_ci */ 9162306a36Sopenharmony_ci .text 9262306a36Sopenharmony_ci .code16 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci .balign 16 9562306a36Sopenharmony_cimachine_real_restart_asm16: 9662306a36Sopenharmony_ci1: 9762306a36Sopenharmony_ci xorl %ecx, %ecx 9862306a36Sopenharmony_ci movl %cr0, %edx 9962306a36Sopenharmony_ci andl $0x00000011, %edx 10062306a36Sopenharmony_ci orl $0x60000000, %edx 10162306a36Sopenharmony_ci movl %edx, %cr0 10262306a36Sopenharmony_ci movl %ecx, %cr3 10362306a36Sopenharmony_ci movl %cr0, %edx 10462306a36Sopenharmony_ci testl $0x60000000, %edx /* If no cache bits -> no wbinvd */ 10562306a36Sopenharmony_ci jz 2f 10662306a36Sopenharmony_ci wbinvd 10762306a36Sopenharmony_ci2: 10862306a36Sopenharmony_ci andb $0x10, %dl 10962306a36Sopenharmony_ci movl %edx, %cr0 11062306a36Sopenharmony_ci LJMPW_RM(3f) 11162306a36Sopenharmony_ci3: 11262306a36Sopenharmony_ci andw %ax, %ax 11362306a36Sopenharmony_ci jz bios 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ciapm: 11662306a36Sopenharmony_ci movw $0x1000, %ax 11762306a36Sopenharmony_ci movw %ax, %ss 11862306a36Sopenharmony_ci movw $0xf000, %sp 11962306a36Sopenharmony_ci movw $0x5307, %ax 12062306a36Sopenharmony_ci movw $0x0001, %bx 12162306a36Sopenharmony_ci movw $0x0003, %cx 12262306a36Sopenharmony_ci int $0x15 12362306a36Sopenharmony_ci /* This should never return... */ 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_cibios: 12662306a36Sopenharmony_ci ljmpw $0xf000, $0xfff0 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci .section ".rodata", "a" 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci .balign 16 13162306a36Sopenharmony_ciSYM_DATA_START(machine_real_restart_idt) 13262306a36Sopenharmony_ci .word 0xffff /* Length - real mode default value */ 13362306a36Sopenharmony_ci .long 0 /* Base - real mode default value */ 13462306a36Sopenharmony_ciSYM_DATA_END(machine_real_restart_idt) 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci .balign 16 13762306a36Sopenharmony_ciSYM_DATA_START(machine_real_restart_gdt) 13862306a36Sopenharmony_ci /* Self-pointer */ 13962306a36Sopenharmony_ci .word 0xffff /* Length - real mode default value */ 14062306a36Sopenharmony_ci .long pa_machine_real_restart_gdt 14162306a36Sopenharmony_ci .word 0 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci /* 14462306a36Sopenharmony_ci * 16-bit code segment pointing to real_mode_seg 14562306a36Sopenharmony_ci * Selector value 8 14662306a36Sopenharmony_ci */ 14762306a36Sopenharmony_ci .word 0xffff /* Limit */ 14862306a36Sopenharmony_ci .long 0x9b000000 + pa_real_mode_base 14962306a36Sopenharmony_ci .word 0 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci /* 15262306a36Sopenharmony_ci * 16-bit data segment with the selector value 16 = 0x10 and 15362306a36Sopenharmony_ci * base value 0x100; since this is consistent with real mode 15462306a36Sopenharmony_ci * semantics we don't have to reload the segments once CR0.PE = 0. 15562306a36Sopenharmony_ci */ 15662306a36Sopenharmony_ci .quad GDT_ENTRY(0x0093, 0x100, 0xffff) 15762306a36Sopenharmony_ciSYM_DATA_END(machine_real_restart_gdt) 158