1/*
2 * Copyright (C) 2014, 2015 Intel Corporation; author Matt Fleming
3 *
4 * Early support for invoking 32-bit EFI services from a 64-bit kernel.
5 *
6 * Because this thunking occurs before ExitBootServices() we have to
7 * restore the firmware's 32-bit GDT before we make EFI serivce calls,
8 * since the firmware's 32-bit IDT is still currently installed and it
9 * needs to be able to service interrupts.
10 *
11 * On the plus side, we don't have to worry about mangling 64-bit
12 * addresses into 32-bits because we're executing with an identify
13 * mapped pagetable and haven't transitioned to 64-bit virtual addresses
14 * yet.
15 */
16
17#include <linux/linkage.h>
18#include <asm/msr.h>
19#include <asm/page_types.h>
20#include <asm/processor-flags.h>
21#include <asm/segment.h>
22
23	.code64
24	.text
25ENTRY(efi64_thunk)
26	push	%rbp
27	push	%rbx
28
29	subq	$8, %rsp
30	leaq	efi_exit32(%rip), %rax
31	movl	%eax, 4(%rsp)
32	leaq	efi_gdt64(%rip), %rax
33	movl	%eax, (%rsp)
34	movl	%eax, 2(%rax)		/* Fixup the gdt base address */
35
36	movl	%ds, %eax
37	push	%rax
38	movl	%es, %eax
39	push	%rax
40	movl	%ss, %eax
41	push	%rax
42
43	/*
44	 * Convert x86-64 ABI params to i386 ABI
45	 */
46	subq	$32, %rsp
47	movl	%esi, 0x0(%rsp)
48	movl	%edx, 0x4(%rsp)
49	movl	%ecx, 0x8(%rsp)
50	movq	%r8, %rsi
51	movl	%esi, 0xc(%rsp)
52	movq	%r9, %rsi
53	movl	%esi,  0x10(%rsp)
54
55	sgdt	save_gdt(%rip)
56
57	leaq	1f(%rip), %rbx
58	movq	%rbx, func_rt_ptr(%rip)
59
60	/*
61	 * Switch to gdt with 32-bit segments. This is the firmware GDT
62	 * that was installed when the kernel started executing. This
63	 * pointer was saved at the EFI stub entry point in head_64.S.
64	 */
65	leaq	efi32_boot_gdt(%rip), %rax
66	lgdt	(%rax)
67
68	pushq	$__KERNEL_CS
69	leaq	efi_enter32(%rip), %rax
70	pushq	%rax
71	lretq
72
731:	addq	$32, %rsp
74
75	lgdt	save_gdt(%rip)
76
77	pop	%rbx
78	movl	%ebx, %ss
79	pop	%rbx
80	movl	%ebx, %es
81	pop	%rbx
82	movl	%ebx, %ds
83
84	/*
85	 * Convert 32-bit status code into 64-bit.
86	 */
87	test	%rax, %rax
88	jz	1f
89	movl	%eax, %ecx
90	andl	$0x0fffffff, %ecx
91	andl	$0xf0000000, %eax
92	shl	$32, %rax
93	or	%rcx, %rax
941:
95	addq	$8, %rsp
96	pop	%rbx
97	pop	%rbp
98	ret
99ENDPROC(efi64_thunk)
100
101ENTRY(efi_exit32)
102	movq	func_rt_ptr(%rip), %rax
103	push	%rax
104	mov	%rdi, %rax
105	ret
106ENDPROC(efi_exit32)
107
108	.code32
109/*
110 * EFI service pointer must be in %edi.
111 *
112 * The stack should represent the 32-bit calling convention.
113 */
114ENTRY(efi_enter32)
115	movl	$__KERNEL_DS, %eax
116	movl	%eax, %ds
117	movl	%eax, %es
118	movl	%eax, %ss
119
120	/* Reload pgtables */
121	movl	%cr3, %eax
122	movl	%eax, %cr3
123
124	/* Disable paging */
125	movl	%cr0, %eax
126	btrl	$X86_CR0_PG_BIT, %eax
127	movl	%eax, %cr0
128
129	/* Disable long mode via EFER */
130	movl	$MSR_EFER, %ecx
131	rdmsr
132	btrl	$_EFER_LME, %eax
133	wrmsr
134
135	call	*%edi
136
137	/* We must preserve return value */
138	movl	%eax, %edi
139
140	/*
141	 * Some firmware will return with interrupts enabled. Be sure to
142	 * disable them before we switch GDTs.
143	 */
144	cli
145
146	movl	56(%esp), %eax
147	movl	%eax, 2(%eax)
148	lgdtl	(%eax)
149
150	movl	%cr4, %eax
151	btsl	$(X86_CR4_PAE_BIT), %eax
152	movl	%eax, %cr4
153
154	movl	%cr3, %eax
155	movl	%eax, %cr3
156
157	movl	$MSR_EFER, %ecx
158	rdmsr
159	btsl	$_EFER_LME, %eax
160	wrmsr
161
162	xorl	%eax, %eax
163	lldt	%ax
164
165	movl	60(%esp), %eax
166	pushl	$__KERNEL_CS
167	pushl	%eax
168
169	/* Enable paging */
170	movl	%cr0, %eax
171	btsl	$X86_CR0_PG_BIT, %eax
172	movl	%eax, %cr0
173	lret
174ENDPROC(efi_enter32)
175
176	.data
177	.balign	8
178	.global	efi32_boot_gdt
179efi32_boot_gdt:	.word	0
180		.quad	0
181
182save_gdt:	.word	0
183		.quad	0
184func_rt_ptr:	.quad	0
185
186	.global efi_gdt64
187efi_gdt64:
188	.word	efi_gdt64_end - efi_gdt64
189	.long	0			/* Filled out by user */
190	.word	0
191	.quad	0x0000000000000000	/* NULL descriptor */
192	.quad	0x00af9a000000ffff	/* __KERNEL_CS */
193	.quad	0x00cf92000000ffff	/* __KERNEL_DS */
194	.quad	0x0080890000000000	/* TS descriptor */
195	.quad   0x0000000000000000	/* TS continued */
196efi_gdt64_end:
197