1*4882a593Smuzhiyun/* SPDX-License-Identifier: GPL-2.0 */ 2*4882a593Smuzhiyun/* 3*4882a593Smuzhiyun * ACPI wakeup real mode startup stub 4*4882a593Smuzhiyun */ 5*4882a593Smuzhiyun#include <linux/linkage.h> 6*4882a593Smuzhiyun#include <asm/segment.h> 7*4882a593Smuzhiyun#include <asm/msr-index.h> 8*4882a593Smuzhiyun#include <asm/page_types.h> 9*4882a593Smuzhiyun#include <asm/pgtable_types.h> 10*4882a593Smuzhiyun#include <asm/processor-flags.h> 11*4882a593Smuzhiyun#include "realmode.h" 12*4882a593Smuzhiyun#include "wakeup.h" 13*4882a593Smuzhiyun 14*4882a593Smuzhiyun .code16 15*4882a593Smuzhiyun 16*4882a593Smuzhiyun/* This should match the structure in wakeup.h */ 17*4882a593Smuzhiyun .section ".data", "aw" 18*4882a593Smuzhiyun 19*4882a593Smuzhiyun .balign 16 20*4882a593SmuzhiyunSYM_DATA_START(wakeup_header) 21*4882a593Smuzhiyun video_mode: .short 0 /* Video mode number */ 22*4882a593Smuzhiyun pmode_entry: .long 0 23*4882a593Smuzhiyun pmode_cs: .short __KERNEL_CS 24*4882a593Smuzhiyun pmode_cr0: .long 0 /* Saved %cr0 */ 25*4882a593Smuzhiyun pmode_cr3: .long 0 /* Saved %cr3 */ 26*4882a593Smuzhiyun pmode_cr4: .long 0 /* Saved %cr4 */ 27*4882a593Smuzhiyun pmode_efer: .quad 0 /* Saved EFER */ 28*4882a593Smuzhiyun pmode_gdt: .quad 0 29*4882a593Smuzhiyun pmode_misc_en: .quad 0 /* Saved MISC_ENABLE MSR */ 30*4882a593Smuzhiyun pmode_behavior: .long 0 /* Wakeup behavior flags */ 31*4882a593Smuzhiyun realmode_flags: .long 0 32*4882a593Smuzhiyun real_magic: .long 0 33*4882a593Smuzhiyun signature: .long WAKEUP_HEADER_SIGNATURE 34*4882a593SmuzhiyunSYM_DATA_END(wakeup_header) 35*4882a593Smuzhiyun 36*4882a593Smuzhiyun .text 37*4882a593Smuzhiyun .code16 38*4882a593Smuzhiyun 39*4882a593Smuzhiyun .balign 16 40*4882a593SmuzhiyunSYM_CODE_START(wakeup_start) 41*4882a593Smuzhiyun cli 42*4882a593Smuzhiyun cld 43*4882a593Smuzhiyun 44*4882a593Smuzhiyun LJMPW_RM(3f) 45*4882a593Smuzhiyun3: 46*4882a593Smuzhiyun /* Apparently some dimwit BIOS programmers don't know how to 47*4882a593Smuzhiyun program a PM to RM transition, and we might end up here with 48*4882a593Smuzhiyun junk in the data segment descriptor registers. The only way 49*4882a593Smuzhiyun to repair that is to go into PM and fix it ourselves... */ 50*4882a593Smuzhiyun movw $16, %cx 51*4882a593Smuzhiyun lgdtl %cs:wakeup_gdt 52*4882a593Smuzhiyun movl %cr0, %eax 53*4882a593Smuzhiyun orb $X86_CR0_PE, %al 54*4882a593Smuzhiyun movl %eax, %cr0 55*4882a593Smuzhiyun ljmpw $8, $2f 56*4882a593Smuzhiyun2: 57*4882a593Smuzhiyun movw %cx, %ds 58*4882a593Smuzhiyun movw %cx, %es 59*4882a593Smuzhiyun movw %cx, %ss 60*4882a593Smuzhiyun movw %cx, %fs 61*4882a593Smuzhiyun movw %cx, %gs 62*4882a593Smuzhiyun 63*4882a593Smuzhiyun andb $~X86_CR0_PE, %al 64*4882a593Smuzhiyun movl %eax, %cr0 65*4882a593Smuzhiyun LJMPW_RM(3f) 66*4882a593Smuzhiyun3: 67*4882a593Smuzhiyun /* Set up segments */ 68*4882a593Smuzhiyun movw %cs, %ax 69*4882a593Smuzhiyun movw %ax, %ss 70*4882a593Smuzhiyun movl $rm_stack_end, %esp 71*4882a593Smuzhiyun movw %ax, %ds 72*4882a593Smuzhiyun movw %ax, %es 73*4882a593Smuzhiyun movw %ax, %fs 74*4882a593Smuzhiyun movw %ax, %gs 75*4882a593Smuzhiyun 76*4882a593Smuzhiyun lidtl .Lwakeup_idt 77*4882a593Smuzhiyun 78*4882a593Smuzhiyun /* Clear the EFLAGS */ 79*4882a593Smuzhiyun pushl $0 80*4882a593Smuzhiyun popfl 81*4882a593Smuzhiyun 82*4882a593Smuzhiyun /* Check header signature... */ 83*4882a593Smuzhiyun movl signature, %eax 84*4882a593Smuzhiyun cmpl $WAKEUP_HEADER_SIGNATURE, %eax 85*4882a593Smuzhiyun jne bogus_real_magic 86*4882a593Smuzhiyun 87*4882a593Smuzhiyun /* Check we really have everything... */ 88*4882a593Smuzhiyun movl end_signature, %eax 89*4882a593Smuzhiyun cmpl $REALMODE_END_SIGNATURE, %eax 90*4882a593Smuzhiyun jne bogus_real_magic 91*4882a593Smuzhiyun 92*4882a593Smuzhiyun /* Call the C code */ 93*4882a593Smuzhiyun calll main 94*4882a593Smuzhiyun 95*4882a593Smuzhiyun /* Restore MISC_ENABLE before entering protected mode, in case 96*4882a593Smuzhiyun BIOS decided to clear XD_DISABLE during S3. */ 97*4882a593Smuzhiyun movl pmode_behavior, %edi 98*4882a593Smuzhiyun btl $WAKEUP_BEHAVIOR_RESTORE_MISC_ENABLE, %edi 99*4882a593Smuzhiyun jnc 1f 100*4882a593Smuzhiyun 101*4882a593Smuzhiyun movl pmode_misc_en, %eax 102*4882a593Smuzhiyun movl pmode_misc_en + 4, %edx 103*4882a593Smuzhiyun movl $MSR_IA32_MISC_ENABLE, %ecx 104*4882a593Smuzhiyun wrmsr 105*4882a593Smuzhiyun1: 106*4882a593Smuzhiyun 107*4882a593Smuzhiyun /* Do any other stuff... */ 108*4882a593Smuzhiyun 109*4882a593Smuzhiyun#ifndef CONFIG_64BIT 110*4882a593Smuzhiyun /* This could also be done in C code... */ 111*4882a593Smuzhiyun movl pmode_cr3, %eax 112*4882a593Smuzhiyun movl %eax, %cr3 113*4882a593Smuzhiyun 114*4882a593Smuzhiyun btl $WAKEUP_BEHAVIOR_RESTORE_CR4, %edi 115*4882a593Smuzhiyun jnc 1f 116*4882a593Smuzhiyun movl pmode_cr4, %eax 117*4882a593Smuzhiyun movl %eax, %cr4 118*4882a593Smuzhiyun1: 119*4882a593Smuzhiyun btl $WAKEUP_BEHAVIOR_RESTORE_EFER, %edi 120*4882a593Smuzhiyun jnc 1f 121*4882a593Smuzhiyun movl pmode_efer, %eax 122*4882a593Smuzhiyun movl pmode_efer + 4, %edx 123*4882a593Smuzhiyun movl $MSR_EFER, %ecx 124*4882a593Smuzhiyun wrmsr 125*4882a593Smuzhiyun1: 126*4882a593Smuzhiyun 127*4882a593Smuzhiyun lgdtl pmode_gdt 128*4882a593Smuzhiyun 129*4882a593Smuzhiyun /* This really couldn't... */ 130*4882a593Smuzhiyun movl pmode_entry, %eax 131*4882a593Smuzhiyun movl pmode_cr0, %ecx 132*4882a593Smuzhiyun movl %ecx, %cr0 133*4882a593Smuzhiyun ljmpl $__KERNEL_CS, $pa_startup_32 134*4882a593Smuzhiyun /* -> jmp *%eax in trampoline_32.S */ 135*4882a593Smuzhiyun#else 136*4882a593Smuzhiyun jmp trampoline_start 137*4882a593Smuzhiyun#endif 138*4882a593SmuzhiyunSYM_CODE_END(wakeup_start) 139*4882a593Smuzhiyun 140*4882a593Smuzhiyunbogus_real_magic: 141*4882a593Smuzhiyun1: 142*4882a593Smuzhiyun hlt 143*4882a593Smuzhiyun jmp 1b 144*4882a593Smuzhiyun 145*4882a593Smuzhiyun .section ".rodata","a" 146*4882a593Smuzhiyun 147*4882a593Smuzhiyun /* 148*4882a593Smuzhiyun * Set up the wakeup GDT. We set these up as Big Real Mode, 149*4882a593Smuzhiyun * that is, with limits set to 4 GB. At least the Lenovo 150*4882a593Smuzhiyun * Thinkpad X61 is known to need this for the video BIOS 151*4882a593Smuzhiyun * initialization quirk to work; this is likely to also 152*4882a593Smuzhiyun * be the case for other laptops or integrated video devices. 153*4882a593Smuzhiyun */ 154*4882a593Smuzhiyun 155*4882a593Smuzhiyun .balign 16 156*4882a593SmuzhiyunSYM_DATA_START(wakeup_gdt) 157*4882a593Smuzhiyun .word 3*8-1 /* Self-descriptor */ 158*4882a593Smuzhiyun .long pa_wakeup_gdt 159*4882a593Smuzhiyun .word 0 160*4882a593Smuzhiyun 161*4882a593Smuzhiyun .word 0xffff /* 16-bit code segment @ real_mode_base */ 162*4882a593Smuzhiyun .long 0x9b000000 + pa_real_mode_base 163*4882a593Smuzhiyun .word 0x008f /* big real mode */ 164*4882a593Smuzhiyun 165*4882a593Smuzhiyun .word 0xffff /* 16-bit data segment @ real_mode_base */ 166*4882a593Smuzhiyun .long 0x93000000 + pa_real_mode_base 167*4882a593Smuzhiyun .word 0x008f /* big real mode */ 168*4882a593SmuzhiyunSYM_DATA_END(wakeup_gdt) 169*4882a593Smuzhiyun 170*4882a593Smuzhiyun .section ".rodata","a" 171*4882a593Smuzhiyun .balign 8 172*4882a593Smuzhiyun 173*4882a593Smuzhiyun /* This is the standard real-mode IDT */ 174*4882a593Smuzhiyun .balign 16 175*4882a593SmuzhiyunSYM_DATA_START_LOCAL(.Lwakeup_idt) 176*4882a593Smuzhiyun .word 0xffff /* limit */ 177*4882a593Smuzhiyun .long 0 /* address */ 178*4882a593Smuzhiyun .word 0 179*4882a593SmuzhiyunSYM_DATA_END(.Lwakeup_idt) 180