18c2ecf20Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0 */ 28c2ecf20Sopenharmony_ci#include <linux/linkage.h> 38c2ecf20Sopenharmony_ci#include <asm/segment.h> 48c2ecf20Sopenharmony_ci#include <asm/page_types.h> 58c2ecf20Sopenharmony_ci#include <asm/processor-flags.h> 68c2ecf20Sopenharmony_ci#include <asm/msr-index.h> 78c2ecf20Sopenharmony_ci#include "realmode.h" 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci/* 108c2ecf20Sopenharmony_ci * The following code and data reboots the machine by switching to real 118c2ecf20Sopenharmony_ci * mode and jumping to the BIOS reset entry point, as if the CPU has 128c2ecf20Sopenharmony_ci * really been reset. The previous version asked the keyboard 138c2ecf20Sopenharmony_ci * controller to pulse the CPU reset line, which is more thorough, but 148c2ecf20Sopenharmony_ci * doesn't work with at least one type of 486 motherboard. It is easy 158c2ecf20Sopenharmony_ci * to stop this code working; hence the copious comments. 168c2ecf20Sopenharmony_ci * 178c2ecf20Sopenharmony_ci * This code is called with the restart type (0 = BIOS, 1 = APM) in 188c2ecf20Sopenharmony_ci * the primary argument register (%eax for 32 bit, %edi for 64 bit). 198c2ecf20Sopenharmony_ci */ 208c2ecf20Sopenharmony_ci .section ".text32", "ax" 218c2ecf20Sopenharmony_ci .code32 228c2ecf20Sopenharmony_ciSYM_CODE_START(machine_real_restart_asm) 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#ifdef CONFIG_X86_64 258c2ecf20Sopenharmony_ci /* Switch to trampoline GDT as it is guaranteed < 4 GiB */ 268c2ecf20Sopenharmony_ci movl $__KERNEL_DS, %eax 278c2ecf20Sopenharmony_ci movl %eax, %ds 288c2ecf20Sopenharmony_ci lgdtl pa_tr_gdt 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci /* Disable paging to drop us out of long mode */ 318c2ecf20Sopenharmony_ci movl %cr0, %eax 328c2ecf20Sopenharmony_ci andl $~X86_CR0_PG, %eax 338c2ecf20Sopenharmony_ci movl %eax, %cr0 348c2ecf20Sopenharmony_ci ljmpl $__KERNEL32_CS, $pa_machine_real_restart_paging_off 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ciSYM_INNER_LABEL(machine_real_restart_paging_off, SYM_L_GLOBAL) 378c2ecf20Sopenharmony_ci xorl %eax, %eax 388c2ecf20Sopenharmony_ci xorl %edx, %edx 398c2ecf20Sopenharmony_ci movl $MSR_EFER, %ecx 408c2ecf20Sopenharmony_ci wrmsr 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci movl %edi, %eax 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci#endif /* CONFIG_X86_64 */ 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci /* Set up the IDT for real mode. */ 478c2ecf20Sopenharmony_ci lidtl pa_machine_real_restart_idt 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci /* 508c2ecf20Sopenharmony_ci * Set up a GDT from which we can load segment descriptors for real 518c2ecf20Sopenharmony_ci * mode. The GDT is not used in real mode; it is just needed here to 528c2ecf20Sopenharmony_ci * prepare the descriptors. 538c2ecf20Sopenharmony_ci */ 548c2ecf20Sopenharmony_ci lgdtl pa_machine_real_restart_gdt 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci /* 578c2ecf20Sopenharmony_ci * Load the data segment registers with 16-bit compatible values 588c2ecf20Sopenharmony_ci */ 598c2ecf20Sopenharmony_ci movl $16, %ecx 608c2ecf20Sopenharmony_ci movl %ecx, %ds 618c2ecf20Sopenharmony_ci movl %ecx, %es 628c2ecf20Sopenharmony_ci movl %ecx, %fs 638c2ecf20Sopenharmony_ci movl %ecx, %gs 648c2ecf20Sopenharmony_ci movl %ecx, %ss 658c2ecf20Sopenharmony_ci ljmpw $8, $1f 668c2ecf20Sopenharmony_ciSYM_CODE_END(machine_real_restart_asm) 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci/* 698c2ecf20Sopenharmony_ci * This is 16-bit protected mode code to disable paging and the cache, 708c2ecf20Sopenharmony_ci * switch to real mode and jump to the BIOS reset code. 718c2ecf20Sopenharmony_ci * 728c2ecf20Sopenharmony_ci * The instruction that switches to real mode by writing to CR0 must be 738c2ecf20Sopenharmony_ci * followed immediately by a far jump instruction, which set CS to a 748c2ecf20Sopenharmony_ci * valid value for real mode, and flushes the prefetch queue to avoid 758c2ecf20Sopenharmony_ci * running instructions that have already been decoded in protected 768c2ecf20Sopenharmony_ci * mode. 778c2ecf20Sopenharmony_ci * 788c2ecf20Sopenharmony_ci * Clears all the flags except ET, especially PG (paging), PE 798c2ecf20Sopenharmony_ci * (protected-mode enable) and TS (task switch for coprocessor state 808c2ecf20Sopenharmony_ci * save). Flushes the TLB after paging has been disabled. Sets CD and 818c2ecf20Sopenharmony_ci * NW, to disable the cache on a 486, and invalidates the cache. This 828c2ecf20Sopenharmony_ci * is more like the state of a 486 after reset. I don't know if 838c2ecf20Sopenharmony_ci * something else should be done for other chips. 848c2ecf20Sopenharmony_ci * 858c2ecf20Sopenharmony_ci * More could be done here to set up the registers as if a CPU reset had 868c2ecf20Sopenharmony_ci * occurred; hopefully real BIOSs don't assume much. This is not the 878c2ecf20Sopenharmony_ci * actual BIOS entry point, anyway (that is at 0xfffffff0). 888c2ecf20Sopenharmony_ci * 898c2ecf20Sopenharmony_ci * Most of this work is probably excessive, but it is what is tested. 908c2ecf20Sopenharmony_ci */ 918c2ecf20Sopenharmony_ci .text 928c2ecf20Sopenharmony_ci .code16 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci .balign 16 958c2ecf20Sopenharmony_cimachine_real_restart_asm16: 968c2ecf20Sopenharmony_ci1: 978c2ecf20Sopenharmony_ci xorl %ecx, %ecx 988c2ecf20Sopenharmony_ci movl %cr0, %edx 998c2ecf20Sopenharmony_ci andl $0x00000011, %edx 1008c2ecf20Sopenharmony_ci orl $0x60000000, %edx 1018c2ecf20Sopenharmony_ci movl %edx, %cr0 1028c2ecf20Sopenharmony_ci movl %ecx, %cr3 1038c2ecf20Sopenharmony_ci movl %cr0, %edx 1048c2ecf20Sopenharmony_ci testl $0x60000000, %edx /* If no cache bits -> no wbinvd */ 1058c2ecf20Sopenharmony_ci jz 2f 1068c2ecf20Sopenharmony_ci wbinvd 1078c2ecf20Sopenharmony_ci2: 1088c2ecf20Sopenharmony_ci andb $0x10, %dl 1098c2ecf20Sopenharmony_ci movl %edx, %cr0 1108c2ecf20Sopenharmony_ci LJMPW_RM(3f) 1118c2ecf20Sopenharmony_ci3: 1128c2ecf20Sopenharmony_ci andw %ax, %ax 1138c2ecf20Sopenharmony_ci jz bios 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ciapm: 1168c2ecf20Sopenharmony_ci movw $0x1000, %ax 1178c2ecf20Sopenharmony_ci movw %ax, %ss 1188c2ecf20Sopenharmony_ci movw $0xf000, %sp 1198c2ecf20Sopenharmony_ci movw $0x5307, %ax 1208c2ecf20Sopenharmony_ci movw $0x0001, %bx 1218c2ecf20Sopenharmony_ci movw $0x0003, %cx 1228c2ecf20Sopenharmony_ci int $0x15 1238c2ecf20Sopenharmony_ci /* This should never return... */ 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cibios: 1268c2ecf20Sopenharmony_ci ljmpw $0xf000, $0xfff0 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci .section ".rodata", "a" 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci .balign 16 1318c2ecf20Sopenharmony_ciSYM_DATA_START(machine_real_restart_idt) 1328c2ecf20Sopenharmony_ci .word 0xffff /* Length - real mode default value */ 1338c2ecf20Sopenharmony_ci .long 0 /* Base - real mode default value */ 1348c2ecf20Sopenharmony_ciSYM_DATA_END(machine_real_restart_idt) 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci .balign 16 1378c2ecf20Sopenharmony_ciSYM_DATA_START(machine_real_restart_gdt) 1388c2ecf20Sopenharmony_ci /* Self-pointer */ 1398c2ecf20Sopenharmony_ci .word 0xffff /* Length - real mode default value */ 1408c2ecf20Sopenharmony_ci .long pa_machine_real_restart_gdt 1418c2ecf20Sopenharmony_ci .word 0 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci /* 1448c2ecf20Sopenharmony_ci * 16-bit code segment pointing to real_mode_seg 1458c2ecf20Sopenharmony_ci * Selector value 8 1468c2ecf20Sopenharmony_ci */ 1478c2ecf20Sopenharmony_ci .word 0xffff /* Limit */ 1488c2ecf20Sopenharmony_ci .long 0x9b000000 + pa_real_mode_base 1498c2ecf20Sopenharmony_ci .word 0 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci /* 1528c2ecf20Sopenharmony_ci * 16-bit data segment with the selector value 16 = 0x10 and 1538c2ecf20Sopenharmony_ci * base value 0x100; since this is consistent with real mode 1548c2ecf20Sopenharmony_ci * semantics we don't have to reload the segments once CR0.PE = 0. 1558c2ecf20Sopenharmony_ci */ 1568c2ecf20Sopenharmony_ci .quad GDT_ENTRY(0x0093, 0x100, 0xffff) 1578c2ecf20Sopenharmony_ciSYM_DATA_END(machine_real_restart_gdt) 158