342 lines
8.2 KiB
ArmAsm
342 lines
8.2 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/asm-offsets.h>
|
||
|
#include <asm/msr.h>
|
||
|
#include <asm/page_types.h>
|
||
|
#include <asm/processor-flags.h>
|
||
|
#include <asm/segment.h>
|
||
|
#include <asm/setup.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 common 64-bit EFI stub
|
||
|
* entrypoint efi_stub_entry() directly, or via 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 | |
|
||
|
* +------------------+ | | +------+----------------+ |
|
||
|
* | efi32_stub_entry |---->| | | |
|
||
|
* +------------------+ +------------+ | |
|
||
|
* V |
|
||
|
* +------------+ +----------------+ |
|
||
|
* | startup_64 |<----| efi_stub_entry |<--------+
|
||
|
* +------------+ +----------------+
|
||
|
*/
|
||
|
SYM_FUNC_START(startup_64_mixed_mode)
|
||
|
lea efi32_boot_args(%rip), %rdx
|
||
|
mov 0(%rdx), %edi
|
||
|
mov 4(%rdx), %esi
|
||
|
|
||
|
/* Switch to the firmware's stack */
|
||
|
movl efi32_boot_sp(%rip), %esp
|
||
|
andl $~7, %esp
|
||
|
|
||
|
#ifdef CONFIG_EFI_HANDOVER_PROTOCOL
|
||
|
mov 8(%rdx), %edx // saved bootparams pointer
|
||
|
test %edx, %edx
|
||
|
jnz efi_stub_entry
|
||
|
#endif
|
||
|
/*
|
||
|
* 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
|
||
|
#ifdef CONFIG_EFI_HANDOVER_PROTOCOL
|
||
|
SYM_FUNC_START(efi32_stub_entry)
|
||
|
call 1f
|
||
|
1: popl %ecx
|
||
|
leal (efi32_boot_args - 1b)(%ecx), %ebx
|
||
|
|
||
|
/* 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 */
|
||
|
popl %ecx
|
||
|
popl %edx
|
||
|
popl %esi
|
||
|
movl %esi, 8(%ebx)
|
||
|
jmp efi32_entry
|
||
|
SYM_FUNC_END(efi32_stub_entry)
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* 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
|
||
|
*
|
||
|
* 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_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 firmware stack pointer */
|
||
|
movl %esp, (efi32_boot_sp - 1b)(%ebx)
|
||
|
|
||
|
/* Store boot arguments */
|
||
|
leal (efi32_boot_args - 1b)(%ebx), %ebx
|
||
|
movl %ecx, 0(%ebx)
|
||
|
movl %edx, 4(%ebx)
|
||
|
movb $0x0, 12(%ebx) // efi_is64
|
||
|
|
||
|
/*
|
||
|
* Allocate some memory for a temporary struct boot_params, which only
|
||
|
* needs the minimal pieces that startup_32() relies on.
|
||
|
*/
|
||
|
subl $PARAM_SIZE, %esp
|
||
|
movl %esp, %esi
|
||
|
movl $PAGE_SIZE, BP_kernel_alignment(%esi)
|
||
|
movl $_end - 1b, BP_init_size(%esi)
|
||
|
subl $startup_32 - 1b, BP_init_size(%esi)
|
||
|
|
||
|
/* Disable paging */
|
||
|
movl %cr0, %eax
|
||
|
btrl $X86_CR0_PG_BIT, %eax
|
||
|
movl %eax, %cr0
|
||
|
|
||
|
jmp startup_32
|
||
|
SYM_FUNC_END(efi32_entry)
|
||
|
|
||
|
/*
|
||
|
* 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 %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
|
||
|
|
||
|
movl 8(%ebp), %ecx // image_handle
|
||
|
movl 12(%ebp), %edx // sys_table
|
||
|
jmp efi32_entry // pass %ecx, %edx
|
||
|
// no other registers remain live
|
||
|
|
||
|
2: popl %edi // restore callee-save registers
|
||
|
popl %ebx
|
||
|
leave
|
||
|
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_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_sp, .long 0)
|
||
|
SYM_DATA_LOCAL(efi32_boot_args, .long 0, 0, 0)
|
||
|
SYM_DATA(efi_is64, .byte 1)
|