/* 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 .code64 .text /* * When booting in 64-bit mode on 32-bit EFI firmware, startup_64_mixed_mode() * is the first thing that runs after switching to long mode. Depending on * whether the EFI handover protocol or the compat entry point was used to * enter the kernel, it will either branch to the 64-bit EFI handover * entrypoint at offset 0x390 in the image, or to the 64-bit EFI PE/COFF * entrypoint efi_pe_entry(). In the former case, the bootloader must provide a * struct bootparams pointer as the third argument, so the presence of such a * pointer is used to disambiguate. * * +--------------+ * +------------------+ +------------+ +------>| efi_pe_entry | * | efi32_pe_entry |---->| | | +-----------+--+ * +------------------+ | | +------+----------------+ | * | startup_32 |---->| startup_64_mixed_mode | | * +------------------+ | | +------+----------------+ V * | efi32_stub_entry |---->| | | +------------------+ * +------------------+ +------------+ +---->| efi64_stub_entry | * +-------------+----+ * +------------+ +----------+ | * | startup_64 |<----| efi_main |<--------------+ * +------------+ +----------+ */ SYM_FUNC_START(startup_64_mixed_mode) lea efi32_boot_args(%rip), %rdx mov 0(%rdx), %edi mov 4(%rdx), %esi mov 8(%rdx), %edx // saved bootparams pointer test %edx, %edx jnz efi64_stub_entry /* * efi_pe_entry uses MS calling convention, which requires 32 bytes of * shadow space on the stack even if all arguments are passed in * registers. We also need an additional 8 bytes for the space that * would be occupied by the return address, and this also results in * the correct stack alignment for entry. */ sub $40, %rsp mov %rdi, %rcx // MS calling convention mov %rsi, %rdx jmp efi_pe_entry SYM_FUNC_END(startup_64_mixed_mode) SYM_FUNC_START(__efi64_thunk) push %rbp push %rbx movl %ds, %eax push %rax movl %es, %eax push %rax movl %ss, %eax push %rax /* Copy args passed on stack */ movq 0x30(%rsp), %rbp movq 0x38(%rsp), %rbx movq 0x40(%rsp), %rax /* * Convert x86-64 ABI params to i386 ABI */ subq $64, %rsp movl %esi, 0x0(%rsp) movl %edx, 0x4(%rsp) movl %ecx, 0x8(%rsp) movl %r8d, 0xc(%rsp) movl %r9d, 0x10(%rsp) movl %ebp, 0x14(%rsp) movl %ebx, 0x18(%rsp) movl %eax, 0x1c(%rsp) leaq 0x20(%rsp), %rbx sgdt (%rbx) sidt 16(%rbx) leaq 1f(%rip), %rbp /* * Switch to IDT and GDT with 32-bit segments. These are the firmware * GDT and IDT that were installed when the kernel started executing. * The pointers were saved by the efi32_entry() routine below. * * Pass the saved DS selector to the 32-bit code, and use far return to * restore the saved CS selector. */ lidt efi32_boot_idt(%rip) lgdt efi32_boot_gdt(%rip) movzwl efi32_boot_ds(%rip), %edx movzwq efi32_boot_cs(%rip), %rax pushq %rax leaq efi_enter32(%rip), %rax pushq %rax lretq 1: addq $64, %rsp movq %rdi, %rax pop %rbx movl %ebx, %ss pop %rbx movl %ebx, %es pop %rbx movl %ebx, %ds /* Clear out 32-bit selector from FS and GS */ xorl %ebx, %ebx movl %ebx, %fs movl %ebx, %gs pop %rbx pop %rbp RET SYM_FUNC_END(__efi64_thunk) .code32 /* * EFI service pointer must be in %edi. * * The stack should represent the 32-bit calling convention. */ SYM_FUNC_START_LOCAL(efi_enter32) /* Load firmware selector into data and stack segment registers */ movl %edx, %ds movl %edx, %es movl %edx, %fs movl %edx, %gs movl %edx, %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 and IDTs. */ cli lidtl 16(%ebx) lgdtl (%ebx) 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 pushl $__KERNEL_CS pushl %ebp /* Enable paging */ movl %cr0, %eax btsl $X86_CR0_PG_BIT, %eax movl %eax, %cr0 lret SYM_FUNC_END(efi_enter32) /* * This is the common EFI stub entry point for mixed mode. * * Arguments: %ecx image handle * %edx EFI system table pointer * %esi struct bootparams pointer (or NULL when not using * the EFI handover protocol) * * 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(efi32_entry) call 1f 1: pop %ebx /* Save firmware GDTR and code/data selectors */ sgdtl (efi32_boot_gdt - 1b)(%ebx) movw %cs, (efi32_boot_cs - 1b)(%ebx) movw %ds, (efi32_boot_ds - 1b)(%ebx) /* Store firmware IDT descriptor */ sidtl (efi32_boot_idt - 1b)(%ebx) /* Store boot arguments */ leal (efi32_boot_args - 1b)(%ebx), %ebx movl %ecx, 0(%ebx) movl %edx, 4(%ebx) movl %esi, 8(%ebx) movb $0x0, 12(%ebx) // efi_is64 /* Disable paging */ movl %cr0, %eax btrl $X86_CR0_PG_BIT, %eax movl %eax, %cr0 jmp startup_32 SYM_FUNC_END(efi32_entry) #define ST32_boottime 60 // offsetof(efi_system_table_32_t, boottime) #define BS32_handle_protocol 88 // offsetof(efi_boot_services_32_t, handle_protocol) #define LI32_image_base 32 // offsetof(efi_loaded_image_32_t, image_base) /* * 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 %ebp movl %esp, %ebp pushl %eax // dummy push to allocate loaded_image pushl %ebx // save callee-save registers pushl %edi call verify_cpu // check for long mode support testl %eax, %eax movl $0x80000003, %eax // EFI_UNSUPPORTED jnz 2f call 1f 1: pop %ebx /* Get the loaded image protocol pointer from the image handle */ leal -4(%ebp), %eax pushl %eax // &loaded_image leal (loaded_image_proto - 1b)(%ebx), %eax pushl %eax // pass the GUID address pushl 8(%ebp) // pass the image handle /* * Note the alignment of the stack frame. * sys_table * handle <-- 16-byte aligned on entry by ABI * return address * frame pointer * loaded_image <-- local variable * saved %ebx <-- 16-byte aligned here * saved %edi * &loaded_image * &loaded_image_proto * handle <-- 16-byte aligned for call to handle_protocol */ movl 12(%ebp), %eax // sys_table movl ST32_boottime(%eax), %eax // sys_table->boottime call *BS32_handle_protocol(%eax) // sys_table->boottime->handle_protocol addl $12, %esp // restore argument space testl %eax, %eax jnz 2f movl 8(%ebp), %ecx // image_handle movl 12(%ebp), %edx // sys_table movl -4(%ebp), %esi // loaded_image movl LI32_image_base(%esi), %esi // loaded_image->image_base leal (startup_32 - 1b)(%ebx), %ebp // runtime address of startup_32 /* * We need to set the image_offset variable here since startup_32() will * use it before we get to the 64-bit efi_pe_entry() in C code. */ subl %esi, %ebp // calculate image_offset movl %ebp, (image_offset - 1b)(%ebx) // save image_offset xorl %esi, %esi jmp efi32_entry // pass %ecx, %edx, %esi // no other registers remain live 2: popl %edi // restore callee-save registers popl %ebx leave RET SYM_FUNC_END(efi32_pe_entry) .section ".rodata" /* EFI loaded image protocol GUID */ .balign 4 SYM_DATA_START_LOCAL(loaded_image_proto) .long 0x5b1b31a1 .word 0x9562, 0x11d2 .byte 0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b SYM_DATA_END(loaded_image_proto) .data .balign 8 SYM_DATA_START_LOCAL(efi32_boot_gdt) .word 0 .quad 0 SYM_DATA_END(efi32_boot_gdt) SYM_DATA_START_LOCAL(efi32_boot_idt) .word 0 .quad 0 SYM_DATA_END(efi32_boot_idt) SYM_DATA_LOCAL(efi32_boot_cs, .word 0) SYM_DATA_LOCAL(efi32_boot_ds, .word 0) SYM_DATA_LOCAL(efi32_boot_args, .long 0, 0, 0) SYM_DATA(efi_is64, .byte 1)