346 lines
9.0 KiB
ArmAsm
346 lines
9.0 KiB
ArmAsm
/* 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 <linux/linkage.h>
|
|
#include <asm/msr.h>
|
|
#include <asm/page_types.h>
|
|
#include <asm/processor-flags.h>
|
|
#include <asm/segment.h>
|
|
|
|
.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)
|