| /* |
| * 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 |
| GLOBAL(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 |
| END(wakeup_header) |
| |
| .text |
| .code16 |
| |
| .balign 16 |
| ENTRY(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 wakeup_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 |
| |
| 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 |
| GLOBAL(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 */ |
| END(wakeup_gdt) |
| |
| .section ".rodata","a" |
| .balign 8 |
| |
| /* This is the standard real-mode IDT */ |
| .balign 16 |
| GLOBAL(wakeup_idt) |
| .word 0xffff /* limit */ |
| .long 0 /* address */ |
| .word 0 |
| END(wakeup_idt) |