/* SPDX-License-Identifier: GPL-2.0 */ 
 | 
#include <linux/linkage.h> 
 | 
#include <asm/segment.h> 
 | 
#include <asm/page_types.h> 
 | 
#include <asm/processor-flags.h> 
 | 
#include <asm/msr-index.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 
 | 
 * the primary argument register (%eax for 32 bit, %edi for 64 bit). 
 | 
 */ 
 | 
    .section ".text32", "ax" 
 | 
    .code32 
 | 
ENTRY(machine_real_restart_asm) 
 | 
  
 | 
#ifdef CONFIG_X86_64 
 | 
    /* Switch to trampoline GDT as it is guaranteed < 4 GiB */ 
 | 
    movl    $__KERNEL_DS, %eax 
 | 
    movl    %eax, %ds 
 | 
    lgdtl    pa_tr_gdt 
 | 
  
 | 
    /* Disable paging to drop us out of long mode */ 
 | 
    movl    %cr0, %eax 
 | 
    andl    $~X86_CR0_PG, %eax 
 | 
    movl    %eax, %cr0 
 | 
    ljmpl    $__KERNEL32_CS, $pa_machine_real_restart_paging_off 
 | 
  
 | 
GLOBAL(machine_real_restart_paging_off) 
 | 
    xorl    %eax, %eax 
 | 
    xorl    %edx, %edx 
 | 
    movl    $MSR_EFER, %ecx 
 | 
    wrmsr 
 | 
  
 | 
    movl    %edi, %eax 
 | 
     
 | 
#endif /* CONFIG_X86_64 */ 
 | 
     
 | 
    /* 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) 
 |