/*
 * Copyright (C) 2014 Intel Corporation; author Matt Fleming
 *
 * Support for invoking 32-bit EFI runtime services from a 64-bit
 * kernel.
 *
 * The below thunking functions are only used after ExitBootServices()
 * has been called. This simplifies things considerably as compared with
 * the early EFI thunking because we can leave all the kernel state
 * intact (GDT, IDT, etc) and simply invoke the the 32-bit EFI runtime
 * services from __KERNEL32_CS. This means we can continue to service
 * interrupts across an EFI mixed mode call.
 *
 * We do however, need to handle the fact that we're running in a full
 * 64-bit virtual address space. Things like the stack and instruction
 * addresses need to be accessible by the 32-bit firmware, so we rely on
 * using the identity mappings in the EFI page table to access the stack
 * and kernel text (see efi_setup_page_tables()).
 */

#include <linux/linkage.h>
#include <asm/page_types.h>
#include <asm/segment.h>

	.text
	.code64
ENTRY(efi64_thunk)
	push	%rbp
	push	%rbx

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

	/*
	 * Calculate the physical address of the kernel text.
	 */
	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
	subq	%rax, %rbx
	call	*%rbx

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

/*
 * We run this function from the 1:1 mapping.
 *
 * This function must be invoked with a 1:1 mapped stack.
 */
ENTRY(__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)

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

1:	addq	$32, %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
ENDPROC(__efi64_thunk)

ENTRY(efi_exit32)
	movq	func_rt_ptr(%rip), %rax
	push	%rax
	mov	%rdi, %rax
	ret
ENDPROC(efi_exit32)

	.code32
/*
 * EFI service pointer must be in %edi.
 *
 * The stack should represent the 32-bit calling convention.
 */
ENTRY(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

	lret
ENDPROC(efi_enter32)

	.data
	.balign	8
func_rt_ptr:		.quad 0
efi_saved_sp:		.quad 0
