root/arch/x86/realmode/rm/wakeup_asm.S
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * ACPI wakeup real mode startup stub
 */
#include <linux/linkage.h>
#include <asm/segment.h>
#include <asm/msr-index.h>
#include <asm/page_types.h>
#include <asm/pgtable_types.h>
#include <asm/processor-flags.h>
#include "realmode.h"
#include "wakeup.h"

        .code16

/* This should match the structure in wakeup.h */
        .section ".data", "aw"

        .balign 16
SYM_DATA_START(wakeup_header)
        video_mode:     .short  0       /* Video mode number */
        pmode_entry:    .long   0
        pmode_cs:       .short  __KERNEL_CS
        pmode_cr0:      .long   0       /* Saved %cr0 */
        pmode_cr3:      .long   0       /* Saved %cr3 */
        pmode_cr4:      .long   0       /* Saved %cr4 */
        pmode_efer:     .quad   0       /* Saved EFER */
        pmode_gdt:      .quad   0
        pmode_misc_en:  .quad   0       /* Saved MISC_ENABLE MSR */
        pmode_behavior: .long   0       /* Wakeup behavior flags */
        realmode_flags: .long   0
        real_magic:     .long   0
        signature:      .long   WAKEUP_HEADER_SIGNATURE
SYM_DATA_END(wakeup_header)

        .text
        .code16

        .balign 16
SYM_CODE_START(wakeup_start)
        cli
        cld

        LJMPW_RM(3f)
3:
        /* Apparently some dimwit BIOS programmers don't know how to
           program a PM to RM transition, and we might end up here with
           junk in the data segment descriptor registers.  The only way
           to repair that is to go into PM and fix it ourselves... */
        movw    $16, %cx
        lgdtl   %cs:wakeup_gdt
        movl    %cr0, %eax
        orb     $X86_CR0_PE, %al
        movl    %eax, %cr0
        ljmpw   $8, $2f
2:
        movw    %cx, %ds
        movw    %cx, %es
        movw    %cx, %ss
        movw    %cx, %fs
        movw    %cx, %gs

        andb    $~X86_CR0_PE, %al
        movl    %eax, %cr0
        LJMPW_RM(3f)
3:
        /* Set up segments */
        movw    %cs, %ax
        movw    %ax, %ss
        movl    $rm_stack_end, %esp
        movw    %ax, %ds
        movw    %ax, %es
        movw    %ax, %fs
        movw    %ax, %gs

        lidtl   .Lwakeup_idt

        /* Clear the EFLAGS */
        pushl $0
        popfl

        /* Check header signature... */
        movl    signature, %eax
        cmpl    $WAKEUP_HEADER_SIGNATURE, %eax
        jne     bogus_real_magic

        /* Check we really have everything... */
        movl    end_signature, %eax
        cmpl    $REALMODE_END_SIGNATURE, %eax
        jne     bogus_real_magic

        /* Call the C code */
        calll   main

        /* Restore MISC_ENABLE before entering protected mode, in case
           BIOS decided to clear XD_DISABLE during S3. */
        movl    pmode_behavior, %edi
        btl     $WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE, %edi
        jnc     1f

        movl    pmode_misc_en, %eax
        movl    pmode_misc_en + 4, %edx
        movl    $MSR_IA32_MISC_ENABLE, %ecx
        wrmsr
1:

        /* Do any other stuff... */

#ifndef CONFIG_64BIT
        /* This could also be done in C code... */
        movl    pmode_cr3, %eax
        movl    %eax, %cr3

        btl     $WAKEUP_BEHAVIOR_RESTORE_CR4, %edi
        jnc     1f
        movl    pmode_cr4, %eax
        movl    %eax, %cr4
1:
        btl     $WAKEUP_BEHAVIOR_RESTORE_EFER, %edi
        jnc     1f
        movl    pmode_efer, %eax
        movl    pmode_efer + 4, %edx
        movl    $MSR_EFER, %ecx
        wrmsr
1:

        lgdtl   pmode_gdt

        /* This really couldn't... */
        movl    pmode_entry, %eax
        movl    pmode_cr0, %ecx
        movl    %ecx, %cr0
        ljmpl   $__KERNEL_CS, $pa_startup_32
        /* -> jmp *%eax in trampoline_32.S */
#else
        jmp     trampoline_start
#endif
SYM_CODE_END(wakeup_start)

bogus_real_magic:
1:
        hlt
        jmp     1b

        .section ".rodata","a"

        /*
         * Set up the wakeup GDT.  We set these up as Big Real Mode,
         * that is, with limits set to 4 GB.  At least the Lenovo
         * Thinkpad X61 is known to need this for the video BIOS
         * initialization quirk to work; this is likely to also
         * be the case for other laptops or integrated video devices.
         */

        .balign 16
SYM_DATA_START(wakeup_gdt)
        .word   3*8-1           /* Self-descriptor */
        .long   pa_wakeup_gdt
        .word   0

        .word   0xffff          /* 16-bit code segment @ real_mode_base */
        .long   0x9b000000 + pa_real_mode_base
        .word   0x008f          /* big real mode */

        .word   0xffff          /* 16-bit data segment @ real_mode_base */
        .long   0x93000000 + pa_real_mode_base
        .word   0x008f          /* big real mode */
SYM_DATA_END(wakeup_gdt)

        .section ".rodata","a"
        .balign 8

        /* This is the standard real-mode IDT */
        .balign 16
SYM_DATA_START_LOCAL(.Lwakeup_idt)
        .word   0xffff          /* limit */
        .long   0               /* address */
        .word   0
SYM_DATA_END(.Lwakeup_idt)