efi_thunk_64.S 3.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
/*
 * 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 before we make EFI serivce calls,
 * since the firmware's 32-bit IDT is still currently installed and it
 * needs to be able to service interrupts.
 *
 * On the plus side, we don't have to worry about mangling 64-bit
 * addresses into 32-bits because we're executing with an identify
 * 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
ENTRY(efi64_thunk)
	push	%rbp
	push	%rbx

	subq	$8, %rsp
	leaq	efi_exit32(%rip), %rax
	movl	%eax, 4(%rsp)
	leaq	efi_gdt64(%rip), %rax
	movl	%eax, (%rsp)
	movl	%eax, 2(%rax)		/* Fixup the gdt base address */

	movl	%ds, %eax
	push	%rax
	movl	%es, %eax
	push	%rax
	movl	%ss, %eax
	push	%rax

	/*
	 * Convert x86-64 ABI params to i386 ABI
	 */
	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)

	sgdt	save_gdt(%rip)

	leaq	1f(%rip), %rbx
	movq	%rbx, func_rt_ptr(%rip)

	/*
	 * Switch to gdt with 32-bit segments. This is the firmware GDT
	 * that was installed when the kernel started executing. This
	 * pointer was saved at the EFI stub entry point in head_64.S.
	 */
	leaq	efi32_boot_gdt(%rip), %rax
	lgdt	(%rax)

	pushq	$__KERNEL_CS
	leaq	efi_enter32(%rip), %rax
	pushq	%rax
	lretq

1:	addq	$32, %rsp

	lgdt	save_gdt(%rip)

	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:
	addq	$8, %rsp
	pop	%rbx
	pop	%rbp
	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

	/* 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.
	 */
	cli

	movl	56(%esp), %eax
	movl	%eax, 2(%eax)
	lgdtl	(%eax)

	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

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

	/* Enable paging */
	movl	%cr0, %eax
	btsl	$X86_CR0_PG_BIT, %eax
	movl	%eax, %cr0
	lret
ENDPROC(efi_enter32)

	.data
	.balign	8
	.global	efi32_boot_gdt
efi32_boot_gdt:	.word	0
		.quad	0

save_gdt:	.word	0
		.quad	0
func_rt_ptr:	.quad	0

	.global efi_gdt64
efi_gdt64:
	.word	efi_gdt64_end - efi_gdt64
	.long	0			/* Filled out by user */
	.word	0
	.quad	0x0000000000000000	/* NULL descriptor */
	.quad	0x00af9a000000ffff	/* __KERNEL_CS */
	.quad	0x00cf92000000ffff	/* __KERNEL_DS */
	.quad	0x0080890000000000	/* TS descriptor */
	.quad   0x0000000000000000	/* TS continued */
efi_gdt64_end: