Commit 48140f8b authored by Ard Biesheuvel's avatar Ard Biesheuvel
Browse files

Merge branch 'x86-mixed-mode' into efi/next

parents 7eb17214 fb84cefd
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -105,7 +105,6 @@ vmlinux-objs-$(CONFIG_INTEL_TDX_GUEST) += $(obj)/tdx.o $(obj)/tdcall.o $(obj)/td
vmlinux-objs-$(CONFIG_UNACCEPTED_MEMORY) += $(obj)/mem.o

vmlinux-objs-$(CONFIG_EFI) += $(obj)/efi.o
vmlinux-objs-$(CONFIG_EFI_MIXED) += $(obj)/efi_mixed.o
vmlinux-libs-$(CONFIG_EFI_STUB) += $(objtree)/drivers/firmware/efi/libstub/lib.a

$(obj)/vmlinux: $(vmlinux-objs-y) $(vmlinux-libs-y) FORCE
+0 −7
Original line number Diff line number Diff line
@@ -263,13 +263,6 @@ SYM_FUNC_START(startup_32)
	 * used to perform that far jump.
	 */
	leal	rva(startup_64)(%ebp), %eax
#ifdef CONFIG_EFI_MIXED
	cmpb	$1, rva(efi_is64)(%ebp)
	je	1f
	leal	rva(startup_64_mixed_mode)(%ebp), %eax
1:
#endif

	pushl	$__KERNEL_CS
	pushl	%eax

+3 −0
Original line number Diff line number Diff line
@@ -62,6 +62,8 @@ KBUILD_CFLAGS := $(filter-out $(CC_FLAGS_LTO), $(KBUILD_CFLAGS))
# `-fdata-sections` flag from KBUILD_CFLAGS_KERNEL
KBUILD_CFLAGS_KERNEL := $(filter-out -fdata-sections, $(KBUILD_CFLAGS_KERNEL))

KBUILD_AFLAGS			:= $(KBUILD_CFLAGS) -D__ASSEMBLY__

lib-y				:= efi-stub-helper.o gop.o secureboot.o tpm.o \
				   file.o mem.o random.o randomalloc.o pci.o \
				   skip_spaces.o lib-cmdline.o lib-ctype.o \
@@ -83,6 +85,7 @@ lib-$(CONFIG_EFI_GENERIC_STUB) += efi-stub.o string.o intrinsics.o systable.o \
lib-$(CONFIG_ARM)		+= arm32-stub.o
lib-$(CONFIG_ARM64)		+= kaslr.o arm64.o arm64-stub.o smbios.o
lib-$(CONFIG_X86)		+= x86-stub.o smbios.o
lib-$(CONFIG_EFI_MIXED)		+= x86-mixed.o
lib-$(CONFIG_X86_64)		+= x86-5lvl.o
lib-$(CONFIG_RISCV)		+= kaslr.o riscv.o riscv-stub.o
lib-$(CONFIG_LOONGARCH)		+= loongarch.o loongarch-stub.o
+253 −0
Original line number Diff line number Diff line
@@ -15,143 +15,19 @@
 */

#include <linux/linkage.h>
#include <asm/asm-offsets.h>
#include <asm/desc_defs.h>
#include <asm/msr.h>
#include <asm/page_types.h>
#include <asm/pgtable_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
@@ -163,30 +39,31 @@ SYM_FUNC_START(efi32_stub_entry)
	rep	stosl

	add	$0x4, %esp		/* Discard return address */
	popl	%ecx
	popl	%edx
	popl	%esi
	movl	%esi, 8(%ebx)
	jmp	efi32_entry
	movl	8(%esp), %ebx		/* struct boot_params pointer */
	jmp	efi32_startup
SYM_FUNC_END(efi32_stub_entry)
#endif

/*
 * EFI service pointer must be in %edi.
 * 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 stack should represent the 32-bit calling convention.
 * 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)
	/* 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
	/*
	 * 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
@@ -204,113 +81,154 @@ SYM_FUNC_START_LOCAL(efi_enter32)
	/* 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
	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

	lidtl	16(%ebx)
	lgdtl	(%ebx)
	/* 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	%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
	/* 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
	lret
SYM_FUNC_END(efi_enter32)
	ret
SYM_FUNC_END(efi32_enable_long_mode)

/*
 * This is the common EFI stub entry point for mixed 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:	%ecx	image handle
 * 		%edx	EFI system table pointer
 * 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_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)
SYM_FUNC_START_LOCAL(efi32_startup)
	movl	%esp, %ebp

	/* Store firmware IDT descriptor */
	sidtl	(efi32_boot_idt - 1b)(%ebx)
	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 */

	/* Store firmware stack pointer */
	movl	%esp, (efi32_boot_sp - 1b)(%ebx)
	call	1f
1:	pop	%edi

	/* Store boot arguments */
	leal	(efi32_boot_args - 1b)(%ebx), %ebx
	movl	%ecx, 0(%ebx)
	movl	%edx, 4(%ebx)
	movb	$0x0, 12(%ebx)          // efi_is64
	/* Record mixed mode entry */
	movb	$0x0, (efi_is64 - 1b)(%edi)

	/*
	 * 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)
	/* 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

	jmp	startup_32
SYM_FUNC_END(efi32_entry)
	/* 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	%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
	/* 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
	leave
	RET
SYM_FUNC_END(efi32_pe_entry)

@@ -324,18 +242,12 @@ SYM_FUNC_END(efi64_stub_entry)

	.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_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)
+29 −23
Original line number Diff line number Diff line
@@ -397,17 +397,13 @@ static void __noreturn efi_exit(efi_handle_t handle, efi_status_t status)
		asm("hlt");
}

void __noreturn efi_stub_entry(efi_handle_t handle,
			       efi_system_table_t *sys_table_arg,
			       struct boot_params *boot_params);

/*
 * Because the x86 boot code expects to be passed a boot_params we
 * need to create one ourselves (usually the bootloader would create
 * one for us).
 */
efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
				   efi_system_table_t *sys_table_arg)
static efi_status_t efi_allocate_bootparams(efi_handle_t handle,
					    struct boot_params **bp)
{
	efi_guid_t proto = LOADED_IMAGE_PROTOCOL_GUID;
	struct boot_params *boot_params;
@@ -416,21 +412,15 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
	unsigned long alloc;
	char *cmdline_ptr;

	efi_system_table = sys_table_arg;

	/* Check if we were booted by the EFI firmware */
	if (efi_system_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE)
		efi_exit(handle, EFI_INVALID_PARAMETER);

	status = efi_bs_call(handle_protocol, handle, &proto, (void **)&image);
	if (status != EFI_SUCCESS) {
		efi_err("Failed to get handle for LOADED_IMAGE_PROTOCOL\n");
		efi_exit(handle, status);
		return status;
	}

	status = efi_allocate_pages(PARAM_SIZE, &alloc, ULONG_MAX);
	if (status != EFI_SUCCESS)
		efi_exit(handle, status);
		return status;

	boot_params = memset((void *)alloc, 0x0, PARAM_SIZE);
	hdr	    = &boot_params->hdr;
@@ -446,14 +436,14 @@ efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
	cmdline_ptr = efi_convert_cmdline(image);
	if (!cmdline_ptr) {
		efi_free(PARAM_SIZE, alloc);
		efi_exit(handle, EFI_OUT_OF_RESOURCES);
		return EFI_OUT_OF_RESOURCES;
	}

	efi_set_u64_split((unsigned long)cmdline_ptr, &hdr->cmd_line_ptr,
			  &boot_params->ext_cmd_line_ptr);

	efi_stub_entry(handle, sys_table_arg, boot_params);
	/* not reached */
	*bp = boot_params;
	return EFI_SUCCESS;
}

static void add_e820ext(struct boot_params *params,
@@ -740,13 +730,16 @@ static efi_status_t parse_options(const char *cmdline)
	return efi_parse_options(cmdline);
}

static efi_status_t efi_decompress_kernel(unsigned long *kernel_entry)
static efi_status_t efi_decompress_kernel(unsigned long *kernel_entry,
					  struct boot_params *boot_params)
{
	unsigned long virt_addr = LOAD_PHYSICAL_ADDR;
	unsigned long addr, alloc_size, entry;
	efi_status_t status;
	u32 seed[2] = {};

	boot_params_ptr	= boot_params;

	/* determine the required size of the allocation */
	alloc_size = ALIGN(max_t(unsigned long, output_len, kernel_total_size),
			   MIN_KERNEL_ALIGN);
@@ -777,7 +770,7 @@ static efi_status_t efi_decompress_kernel(unsigned long *kernel_entry)
			seed[0] = 0;
		}

		boot_params_ptr->hdr.loadflags |= KASLR_FLAG;
		boot_params->hdr.loadflags |= KASLR_FLAG;
	}

	status = efi_random_alloc(alloc_size, CONFIG_PHYSICAL_ALIGN, &addr,
@@ -815,20 +808,27 @@ static void __noreturn enter_kernel(unsigned long kernel_addr,
void __noreturn efi_stub_entry(efi_handle_t handle,
			       efi_system_table_t *sys_table_arg,
			       struct boot_params *boot_params)

{
	efi_guid_t guid = EFI_MEMORY_ATTRIBUTE_PROTOCOL_GUID;
	struct setup_header *hdr = &boot_params->hdr;
	const struct linux_efi_initrd *initrd = NULL;
	unsigned long kernel_entry;
	struct setup_header *hdr;
	efi_status_t status;

	boot_params_ptr = boot_params;

	efi_system_table = sys_table_arg;
	/* Check if we were booted by the EFI firmware */
	if (efi_system_table->hdr.signature != EFI_SYSTEM_TABLE_SIGNATURE)
		efi_exit(handle, EFI_INVALID_PARAMETER);

	if (!IS_ENABLED(CONFIG_EFI_HANDOVER_PROTOCOL) || !boot_params) {
		status = efi_allocate_bootparams(handle, &boot_params);
		if (status != EFI_SUCCESS)
			efi_exit(handle, status);
	}

	hdr = &boot_params->hdr;

	if (have_unsupported_snp_features())
		efi_exit(handle, EFI_UNSUPPORTED);

@@ -870,7 +870,7 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
	if (efi_mem_encrypt > 0)
		hdr->xloadflags |= XLF_MEM_ENCRYPTION;

	status = efi_decompress_kernel(&kernel_entry);
	status = efi_decompress_kernel(&kernel_entry, boot_params);
	if (status != EFI_SUCCESS) {
		efi_err("Failed to decompress kernel\n");
		goto fail;
@@ -940,6 +940,12 @@ void __noreturn efi_stub_entry(efi_handle_t handle,
	efi_exit(handle, status);
}

efi_status_t __efiapi efi_pe_entry(efi_handle_t handle,
				   efi_system_table_t *sys_table_arg)
{
	efi_stub_entry(handle, sys_table_arg, NULL);
}

#ifdef CONFIG_EFI_HANDOVER_PROTOCOL
void efi_handover_entry(efi_handle_t handle, efi_system_table_t *sys_table_arg,
			struct boot_params *boot_params)
Loading