/* * Copyright (C) 2014, 2015 Intel Corporation; author Matt Fleming * * Early support for invoking 32-bit EFI services from a 64-bit kernel. * * Because this thunking occurs before ExitBootServices() we have to * restore the firmware's 32-bit GDT before we make EFI serivce calls, * since the firmware's 32-bit IDT is still currently installed and it * needs to be able to service interrupts. * * On the plus side, we don't have to worry about mangling 64-bit * addresses into 32-bits because we're executing with an identify * mapped pagetable and haven't transitioned to 64-bit virtual addresses * yet. */ #include <linux/linkage.h> #include <asm/msr.h> #include <asm/page_types.h> #include <asm/processor-flags.h> #include <asm/segment.h> .code64 .text ENTRY(efi64_thunk) push %rbp push %rbx subq $8, %rsp leaq efi_exit32(%rip), %rax movl %eax, 4(%rsp) leaq efi_gdt64(%rip), %rax movl %eax, (%rsp) movl %eax, 2(%rax) /* Fixup the gdt base address */ movl %ds, %eax push %rax movl %es, %eax push %rax movl %ss, %eax push %rax /* * Convert x86-64 ABI params to i386 ABI */ subq $32, %rsp movl %esi, 0x0(%rsp) movl %edx, 0x4(%rsp) movl %ecx, 0x8(%rsp) movq %r8, %rsi movl %esi, 0xc(%rsp) movq %r9, %rsi movl %esi, 0x10(%rsp) sgdt save_gdt(%rip) leaq 1f(%rip), %rbx movq %rbx, func_rt_ptr(%rip) /* * Switch to gdt with 32-bit segments. This is the firmware GDT * that was installed when the kernel started executing. This * pointer was saved at the EFI stub entry point in head_64.S. */ leaq efi32_boot_gdt(%rip), %rax lgdt (%rax) pushq $__KERNEL_CS leaq efi_enter32(%rip), %rax pushq %rax lretq 1: addq $32, %rsp lgdt save_gdt(%rip) pop %rbx movl %ebx, %ss pop %rbx movl %ebx, %es pop %rbx movl %ebx, %ds /* * Convert 32-bit status code into 64-bit. */ test %rax, %rax jz 1f movl %eax, %ecx andl $0x0fffffff, %ecx andl $0xf0000000, %eax shl $32, %rax or %rcx, %rax 1: addq $8, %rsp pop %rbx pop %rbp ret ENDPROC(efi64_thunk) ENTRY(efi_exit32) movq func_rt_ptr(%rip), %rax push %rax mov %rdi, %rax ret ENDPROC(efi_exit32) .code32 /* * EFI service pointer must be in %edi. * * The stack should represent the 32-bit calling convention. */ ENTRY(efi_enter32) movl $__KERNEL_DS, %eax movl %eax, %ds movl %eax, %es movl %eax, %ss /* Reload pgtables */ movl %cr3, %eax movl %eax, %cr3 /* Disable paging */ movl %cr0, %eax btrl $X86_CR0_PG_BIT, %eax movl %eax, %cr0 /* Disable long mode via EFER */ movl $MSR_EFER, %ecx rdmsr btrl $_EFER_LME, %eax wrmsr call *%edi /* We must preserve return value */ movl %eax, %edi /* * Some firmware will return with interrupts enabled. Be sure to * disable them before we switch GDTs. */ cli movl 56(%esp), %eax movl %eax, 2(%eax) lgdtl (%eax) movl %cr4, %eax btsl $(X86_CR4_PAE_BIT), %eax movl %eax, %cr4 movl %cr3, %eax movl %eax, %cr3 movl $MSR_EFER, %ecx rdmsr btsl $_EFER_LME, %eax wrmsr xorl %eax, %eax lldt %ax movl 60(%esp), %eax pushl $__KERNEL_CS pushl %eax /* Enable paging */ movl %cr0, %eax btsl $X86_CR0_PG_BIT, %eax movl %eax, %cr0 lret ENDPROC(efi_enter32) .data .balign 8 .global efi32_boot_gdt efi32_boot_gdt: .word 0 .quad 0 save_gdt: .word 0 .quad 0 func_rt_ptr: .quad 0 .global efi_gdt64 efi_gdt64: .word efi_gdt64_end - efi_gdt64 .long 0 /* Filled out by user */ .word 0 .quad 0x0000000000000000 /* NULL descriptor */ .quad 0x00af9a000000ffff /* __KERNEL_CS */ .quad 0x00cf92000000ffff /* __KERNEL_DS */ .quad 0x0080890000000000 /* TS descriptor */ .quad 0x0000000000000000 /* TS continued */ efi_gdt64_end: