The bare metal decompressor code was never really intended to run in a hosted environment such as the EFI boot services, and does a few things that are becoming problematic in the context of EFI boot now that the logo requirements are getting tighter: EFI executables will no longer be allowed to consist of a single executable section that is mapped with read, write and execute permissions if they are intended for use in a context where Secure Boot is enabled (and where Microsoft's set of certificates is used, i.e., every x86 PC built to run Windows). To avoid stepping on reserved memory before having inspected the E820 tables, and to ensure the correct placement when running a kernel build that is non-relocatable, the bare metal decompressor moves its own executable image to the end of the allocation that was reserved for it, in order to perform the decompression in place. This means the region in question requires both write and execute permissions, which either need to be given upfront (which EFI will no longer permit), or need to be applied on demand using the existing page fault handling framework. However, the physical placement of the kernel is usually randomized anyway, and even if it isn't, a dedicated decompression output buffer can be allocated anywhere in memory using EFI APIs when still running in the boot services, given that EFI support already implies a relocatable kernel. This means that decompression in place is never necessary, nor is moving the compressed image from one end to the other. Since EFI already maps all of memory 1:1, it is also unnecessary to create new page tables or handle page faults when decompressing the kernel. That means there is also no need to replace the special exception handlers for SEV. Generally, there is little need to do any of the things that the decompressor does beyond - initialize SEV encryption, if needed, - perform the 4/5 level paging switch, if needed, - decompress the kernel - relocate the kernel So do all of this from the EFI stub code, and avoid the bare metal decompressor altogether. Signed-off-by: Ard Biesheuvel <ardb@kernel.org> Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de> Link: https://lore.kernel.org/r/20230807162720.545787-24-ardb@kernel.org
322 lines
7.7 KiB
ArmAsm
322 lines
7.7 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 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
|
|
#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
|
|
|
|
/* 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
|
|
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
|
|
* %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_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 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)
|
|
|
|
/*
|
|
* 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
|
|
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)
|
|
|
|
#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_args, .long 0, 0, 0)
|
|
SYM_DATA(efi_is64, .byte 1)
|