/* SPDX-License-Identifier: GPL-2.0 */ /* * 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 and IDT before we make EFI service * calls. * * On the plus side, we don't have to worry about mangling 64-bit * addresses into 32-bits because we're executing with an identity * mapped pagetable and haven't transitioned to 64-bit virtual addresses * yet. */ #include #include #include #include #include #include #include .text .code32 #ifdef CONFIG_EFI_HANDOVER_PROTOCOL SYM_FUNC_START(efi32_stub_entry) call 1f 1: popl %ecx /* Clear BSS */ xorl %eax, %eax leal (_bss - 1b)(%ecx), %edi leal (_ebss - 1b)(%ecx), %ecx subl %edi, %ecx shrl $2, %ecx cld rep stosl add $0x4, %esp /* Discard return address */ movl 8(%esp), %ebx /* struct boot_params pointer */ jmp efi32_startup SYM_FUNC_END(efi32_stub_entry) #endif /* * Called using a far call from __efi64_thunk() below, using the x86_64 SysV * ABI (except for R8/R9 which are inaccessible to 32-bit code - EAX/EBX are * used instead). EBP+16 points to the arguments passed via the stack. * * The first argument (EDI) is a pointer to the boot service or protocol, to * which the remaining arguments are passed, each truncated to 32 bits. */ SYM_FUNC_START_LOCAL(efi_enter32) /* * Convert x86-64 SysV ABI params to i386 ABI */ pushl 32(%ebp) /* Up to 3 args passed via the stack */ pushl 24(%ebp) pushl 16(%ebp) pushl %ebx /* R9 */ pushl %eax /* R8 */ pushl %ecx pushl %edx pushl %esi /* 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 call efi32_enable_long_mode addl $32, %esp movl %edi, %eax lret SYM_FUNC_END(efi_enter32) .code64 SYM_FUNC_START(__efi64_thunk) push %rbp movl %esp, %ebp push %rbx /* Move args #5 and #6 into 32-bit accessible registers */ movl %r8d, %eax movl %r9d, %ebx lcalll *efi32_call(%rip) pop %rbx pop %rbp RET SYM_FUNC_END(__efi64_thunk) .code32 SYM_FUNC_START_LOCAL(efi32_enable_long_mode) movl %cr4, %eax btsl $(X86_CR4_PAE_BIT), %eax movl %eax, %cr4 movl $MSR_EFER, %ecx rdmsr btsl $_EFER_LME, %eax wrmsr /* Disable interrupts - the firmware's IDT does not work in long mode */ cli /* Enable paging */ movl %cr0, %eax btsl $X86_CR0_PG_BIT, %eax movl %eax, %cr0 ret SYM_FUNC_END(efi32_enable_long_mode) /* * This is the common EFI stub entry point for mixed mode. It sets up the GDT * and page tables needed for 64-bit execution, after which it calls the * common 64-bit EFI entrypoint efi_stub_entry(). * * Arguments: 0(%esp) image handle * 4(%esp) EFI system table pointer * %ebx struct boot_params pointer (or NULL) * * Since this is the point of no return for ordinary execution, no registers * are considered live except for the function parameters. [Note that the EFI * stub may still exit and return to the firmware using the Exit() EFI boot * service.] */ SYM_FUNC_START_LOCAL(efi32_startup) movl %esp, %ebp subl $8, %esp sgdtl (%esp) /* Save GDT descriptor to the stack */ movl 2(%esp), %esi /* Existing GDT pointer */ movzwl (%esp), %ecx /* Existing GDT limit */ inc %ecx /* Existing GDT size */ andl $~7, %ecx /* Ensure size is multiple of 8 */ subl %ecx, %esp /* Allocate new GDT */ andl $~15, %esp /* Realign the stack */ movl %esp, %edi /* New GDT address */ leal 7(%ecx), %eax /* New GDT limit */ pushw %cx /* Push 64-bit CS (for LJMP below) */ pushl %edi /* Push new GDT address */ pushw %ax /* Push new GDT limit */ /* Copy GDT to the stack and add a 64-bit code segment at the end */ movl $GDT_ENTRY(DESC_CODE64, 0, 0xfffff) & 0xffffffff, (%edi,%ecx) movl $GDT_ENTRY(DESC_CODE64, 0, 0xfffff) >> 32, 4(%edi,%ecx) shrl $2, %ecx cld rep movsl /* Copy the firmware GDT */ lgdtl (%esp) /* Switch to the new GDT */ call 1f 1: pop %edi /* Record mixed mode entry */ movb $0x0, (efi_is64 - 1b)(%edi) /* Set up indirect far call to re-enter 32-bit mode */ leal (efi32_call - 1b)(%edi), %eax addl %eax, (%eax) movw %cs, 4(%eax) /* Disable paging */ movl %cr0, %eax btrl $X86_CR0_PG_BIT, %eax movl %eax, %cr0 /* Set up 1:1 mapping */ leal (pte - 1b)(%edi), %eax movl $_PAGE_PRESENT | _PAGE_RW | _PAGE_PSE, %ecx leal (_PAGE_PRESENT | _PAGE_RW)(%eax), %edx 2: movl %ecx, (%eax) addl $8, %eax addl $PMD_SIZE, %ecx jnc 2b movl $PAGE_SIZE, %ecx .irpc l, 0123 movl %edx, \l * 8(%eax) addl %ecx, %edx .endr addl %ecx, %eax movl %edx, (%eax) movl %eax, %cr3 call efi32_enable_long_mode /* Set up far jump to 64-bit mode (CS is already on the stack) */ leal (efi_stub_entry - 1b)(%edi), %eax movl %eax, 2(%esp) movl 0(%ebp), %edi movl 4(%ebp), %esi movl %ebx, %edx ljmpl *2(%esp) SYM_FUNC_END(efi32_startup) /* * efi_status_t efi32_pe_entry(efi_handle_t image_handle, * efi_system_table_32_t *sys_table) */ SYM_FUNC_START(efi32_pe_entry) pushl %ebx // save callee-save registers /* Check whether the CPU supports long mode */ movl $0x80000001, %eax // assume extended info support cpuid btl $29, %edx // check long mode bit jnc 1f leal 8(%esp), %esp // preserve stack alignment xor %ebx, %ebx // no struct boot_params pointer jmp efi32_startup // only ESP and EBX remain live 1: movl $0x80000003, %eax // EFI_UNSUPPORTED popl %ebx RET SYM_FUNC_END(efi32_pe_entry) #ifdef CONFIG_EFI_HANDOVER_PROTOCOL .org efi32_stub_entry + 0x200 .code64 SYM_FUNC_START_NOALIGN(efi64_stub_entry) jmp efi_handover_entry SYM_FUNC_END(efi64_stub_entry) #endif .data .balign 8 SYM_DATA_START_LOCAL(efi32_call) .long efi_enter32 - . .word 0x0 SYM_DATA_END(efi32_call) SYM_DATA(efi_is64, .byte 1) .bss .balign PAGE_SIZE SYM_DATA_LOCAL(pte, .fill 6 * PAGE_SIZE, 1, 0)