Commit ea5e1919 authored by Ard Biesheuvel's avatar Ard Biesheuvel Committed by Ingo Molnar
Browse files

efi/x86: Simplify mixed mode call wrapper



Calling 32-bit EFI runtime services from a 64-bit OS involves
switching back to the flat mapping with a stack carved out of
memory that is 32-bit addressable.

There is no need to actually execute the 64-bit part of this
routine from the flat mapping as well, as long as the entry
and return address fit in 32 bits. There is also no need to
preserve part of the calling context in global variables: we
can simply push the old stack pointer value to the new stack,
and keep the return address from the code32 section in EBX.

While at it, move the conditional check whether to invoke
the mixed mode version of SetVirtualAddressMap() into the
64-bit implementation of the wrapper routine.

Signed-off-by: default avatarArd Biesheuvel <ardb@kernel.org>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Cc: Arvind Sankar <nivedita@alum.mit.edu>
Cc: Matthew Garrett <mjg59@google.com>
Cc: linux-efi@vger.kernel.org
Link: https://lkml.kernel.org/r/20200103113953.9571-11-ardb@kernel.org


Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
parent e5f930fe
Loading
Loading
Loading
Loading
+0 −6
Original line number Diff line number Diff line
@@ -164,12 +164,6 @@ extern void parse_efi_setup(u64 phys_addr, u32 data_len);
extern void efifb_setup_from_dmi(struct screen_info *si, const char *opt);

extern void efi_thunk_runtime_setup(void);
extern efi_status_t efi_thunk_set_virtual_address_map(
	void *phys_set_virtual_address_map,
	unsigned long memory_map_size,
	unsigned long descriptor_size,
	u32 descriptor_version,
	efi_memory_desc_t *virtual_map);
efi_status_t efi_set_virtual_address_map(unsigned long memory_map_size,
					 unsigned long descriptor_size,
					 u32 descriptor_version,
+4 −15
Original line number Diff line number Diff line
@@ -1015,21 +1015,10 @@ static void __init __efi_enter_virtual_mode(void)

	efi_sync_low_kernel_mappings();

	if (!efi_is_mixed()) {
		status = efi_set_virtual_address_map(
				efi.memmap.desc_size * count,
	status = efi_set_virtual_address_map(efi.memmap.desc_size * count,
					     efi.memmap.desc_size,
					     efi.memmap.desc_version,
					     (efi_memory_desc_t *)pa);
	} else {
		status = efi_thunk_set_virtual_address_map(
				efi_phys.set_virtual_address_map,
				efi.memmap.desc_size * count,
				efi.memmap.desc_size,
				efi.memmap.desc_version,
				(efi_memory_desc_t *)pa);
	}

	if (status != EFI_SUCCESS) {
		pr_alert("Unable to switch EFI into virtual mode (status=%lx)!\n",
			 status);
+47 −26
Original line number Diff line number Diff line
@@ -626,61 +626,74 @@ void efi_switch_mm(struct mm_struct *mm)
	switch_mm(efi_scratch.prev_mm, mm, NULL);
}

#ifdef CONFIG_EFI_MIXED
static DEFINE_SPINLOCK(efi_runtime_lock);

#define runtime_service32(func)						 \
/*
 * DS and ES contain user values.  We need to save them.
 * The 32-bit EFI code needs a valid DS, ES, and SS.  There's no
 * need to save the old SS: __KERNEL_DS is always acceptable.
 */
#define __efi_thunk(func, ...)						\
({									\
	u32 table = (u32)(unsigned long)efi.systab;			 \
	u32 *rt, *___f;							 \
	efi_runtime_services_32_t *__rt;				\
	unsigned short __ds, __es;					\
	efi_status_t ____s;						\
									\
	__rt = (void *)(unsigned long)efi.systab->mixed_mode.runtime;	\
									\
	savesegment(ds, __ds);						\
	savesegment(es, __es);						\
									\
	loadsegment(ss, __KERNEL_DS);					\
	loadsegment(ds, __KERNEL_DS);					\
	loadsegment(es, __KERNEL_DS);					\
									\
	____s = efi64_thunk(__rt->func, __VA_ARGS__);			\
									\
	rt = (u32 *)(table + offsetof(efi_system_table_32_t, runtime));	 \
	___f = (u32 *)(*rt + offsetof(efi_runtime_services_32_t, func)); \
	*___f;								 \
	loadsegment(ds, __ds);						\
	loadsegment(es, __es);						\
									\
	____s ^= (____s & BIT(31)) | (____s & BIT_ULL(31)) << 32;	\
	____s;								\
})

/*
 * Switch to the EFI page tables early so that we can access the 1:1
 * runtime services mappings which are not mapped in any other page
 * tables. This function must be called before runtime_service32().
 * tables.
 *
 * Also, disable interrupts because the IDT points to 64-bit handlers,
 * which aren't going to function correctly when we switch to 32-bit.
 */
#define efi_thunk(f, ...)						\
#define efi_thunk(func...)						\
({									\
	efi_status_t __s;						\
	u32 __func;							\
									\
	arch_efi_call_virt_setup();					\
									\
	__func = runtime_service32(f);					\
	__s = efi64_thunk(__func, __VA_ARGS__);				\
	__s = __efi_thunk(func);					\
									\
	arch_efi_call_virt_teardown();					\
									\
	__s;								\
})

efi_status_t efi_thunk_set_virtual_address_map(
	void *phys_set_virtual_address_map,
	unsigned long memory_map_size,
static efi_status_t __init
efi_thunk_set_virtual_address_map(unsigned long memory_map_size,
				  unsigned long descriptor_size,
				  u32 descriptor_version,
				  efi_memory_desc_t *virtual_map)
{
	efi_status_t status;
	unsigned long flags;
	u32 func;

	efi_sync_low_kernel_mappings();
	local_irq_save(flags);

	efi_switch_mm(&efi_mm);

	func = (u32)(unsigned long)phys_set_virtual_address_map;
	status = efi64_thunk(func, memory_map_size, descriptor_size,
			     descriptor_version, virtual_map);
	status = __efi_thunk(set_virtual_address_map, memory_map_size,
			     descriptor_size, descriptor_version, virtual_map);

	efi_switch_mm(efi_scratch.prev_mm);
	local_irq_restore(flags);
@@ -983,8 +996,11 @@ efi_thunk_query_capsule_caps(efi_capsule_header_t **capsules,
	return EFI_UNSUPPORTED;
}

void efi_thunk_runtime_setup(void)
void __init efi_thunk_runtime_setup(void)
{
	if (!IS_ENABLED(CONFIG_EFI_MIXED))
		return;

	efi.get_time = efi_thunk_get_time;
	efi.set_time = efi_thunk_set_time;
	efi.get_wakeup_time = efi_thunk_get_wakeup_time;
@@ -1000,7 +1016,6 @@ void efi_thunk_runtime_setup(void)
	efi.update_capsule = efi_thunk_update_capsule;
	efi.query_capsule_caps = efi_thunk_query_capsule_caps;
}
#endif /* CONFIG_EFI_MIXED */

efi_status_t __init efi_set_virtual_address_map(unsigned long memory_map_size,
						unsigned long descriptor_size,
@@ -1011,6 +1026,12 @@ efi_status_t __init efi_set_virtual_address_map(unsigned long memory_map_size,
	unsigned long flags;
	pgd_t *save_pgd = NULL;

	if (efi_is_mixed())
		return efi_thunk_set_virtual_address_map(memory_map_size,
							 descriptor_size,
							 descriptor_version,
							 virtual_map);

	if (efi_enabled(EFI_OLD_MEMMAP)) {
		save_pgd = efi_old_memmap_phys_prolog();
		if (!save_pgd)
+20 −101
Original line number Diff line number Diff line
@@ -25,15 +25,16 @@

	.text
	.code64
SYM_FUNC_START(efi64_thunk)
SYM_CODE_START(efi64_thunk)
	push	%rbp
	push	%rbx

	/*
	 * Switch to 1:1 mapped 32-bit stack pointer.
	 */
	movq	%rsp, efi_saved_sp(%rip)
	movq	%rsp, %rax
	movq	efi_scratch(%rip), %rsp
	push	%rax

	/*
	 * Calculate the physical address of the kernel text.
@@ -41,113 +42,31 @@ SYM_FUNC_START(efi64_thunk)
	movq	$__START_KERNEL_map, %rax
	subq	phys_base(%rip), %rax

	/*
	 * Push some physical addresses onto the stack. This is easier
	 * to do now in a code64 section while the assembler can address
	 * 64-bit values. Note that all the addresses on the stack are
	 * 32-bit.
	 */
	subq	$16, %rsp
	leaq	efi_exit32(%rip), %rbx
	subq	%rax, %rbx
	movl	%ebx, 8(%rsp)

	leaq	__efi64_thunk(%rip), %rbx
	leaq	1f(%rip), %rbp
	leaq	2f(%rip), %rbx
	subq	%rax, %rbp
	subq	%rax, %rbx
	call	*%rbx

	movq	efi_saved_sp(%rip), %rsp
	pop	%rbx
	pop	%rbp
	retq
SYM_FUNC_END(efi64_thunk)

/*
 * We run this function from the 1:1 mapping.
 *
 * This function must be invoked with a 1:1 mapped stack.
 */
SYM_FUNC_START_LOCAL(__efi64_thunk)
	movl	%ds, %eax
	push	%rax
	movl	%es, %eax
	push	%rax
	movl	%ss, %eax
	push	%rax

	subq	$32, %rsp
	movl	%esi, 0x0(%rsp)
	movl	%edx, 0x4(%rsp)
	movl	%ecx, 0x8(%rsp)
	movq	%r8, %rsi
	movl	%esi, 0xc(%rsp)
	movq	%r9, %rsi
	movl	%esi,  0x10(%rsp)

	leaq	1f(%rip), %rbx
	movq	%rbx, func_rt_ptr(%rip)
	subq	$28, %rsp
	movl	%ebx, 0x0(%rsp)		/* return address */
	movl	%esi, 0x4(%rsp)
	movl	%edx, 0x8(%rsp)
	movl	%ecx, 0xc(%rsp)
	movl	%r8d, 0x10(%rsp)
	movl	%r9d, 0x14(%rsp)

	/* Switch to 32-bit descriptor */
	pushq	$__KERNEL32_CS
	leaq	efi_enter32(%rip), %rax
	pushq	%rax
	pushq	%rdi			/* EFI runtime service address */
	lretq

1:	addq	$32, %rsp

1:	movq	24(%rsp), %rsp
	pop	%rbx
	movl	%ebx, %ss
	pop	%rbx
	movl	%ebx, %es
	pop	%rbx
	movl	%ebx, %ds

	/*
	 * Convert 32-bit status code into 64-bit.
	 */
	test	%rax, %rax
	jz	1f
	movl	%eax, %ecx
	andl	$0x0fffffff, %ecx
	andl	$0xf0000000, %eax
	shl	$32, %rax
	or	%rcx, %rax
1:
	ret
SYM_FUNC_END(__efi64_thunk)

SYM_FUNC_START_LOCAL(efi_exit32)
	movq	func_rt_ptr(%rip), %rax
	push	%rax
	mov	%rdi, %rax
	ret
SYM_FUNC_END(efi_exit32)
	pop	%rbp
	retq

	.code32
/*
 * EFI service pointer must be in %edi.
 *
 * The stack should represent the 32-bit calling convention.
 */
SYM_FUNC_START_LOCAL(efi_enter32)
	movl	$__KERNEL_DS, %eax
	movl	%eax, %ds
	movl	%eax, %es
	movl	%eax, %ss

	call	*%edi

	/* We must preserve return value */
	movl	%eax, %edi

	movl	72(%esp), %eax
	pushl	$__KERNEL_CS
	pushl	%eax

2:	pushl	$__KERNEL_CS
	pushl	%ebp
	lret
SYM_FUNC_END(efi_enter32)

	.data
	.balign	8
func_rt_ptr:		.quad 0
efi_saved_sp:		.quad 0
SYM_CODE_END(efi64_thunk)