root/arch/x86/realmode/rm/reboot.S
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/linkage.h>
#include <asm/desc_defs.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
SYM_CODE_START(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

SYM_INNER_LABEL(machine_real_restart_paging_off, SYM_L_GLOBAL)
        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
SYM_CODE_END(machine_real_restart_asm)

/*
 * 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
SYM_DATA_START(machine_real_restart_idt)
        .word   0xffff          /* Length - real mode default value */
        .long   0               /* Base - real mode default value */
SYM_DATA_END(machine_real_restart_idt)

        .balign 16
SYM_DATA_START(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(DESC_DATA16, 0x100, 0xffff)
SYM_DATA_END(machine_real_restart_gdt)