xref: /linux/drivers/firmware/efi/libstub/x86-mixed.S (revision 1260ed77798502de9c98020040d2995008de10cc)
1/* SPDX-License-Identifier: GPL-2.0 */
2/*
3 * Copyright (C) 2014, 2015 Intel Corporation; author Matt Fleming
4 *
5 * Early support for invoking 32-bit EFI services from a 64-bit kernel.
6 *
7 * Because this thunking occurs before ExitBootServices() we have to
8 * restore the firmware's 32-bit GDT and IDT before we make EFI service
9 * calls.
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 identity
13 * mapped pagetable and haven't transitioned to 64-bit virtual addresses
14 * yet.
15 */
16
17#include <linux/linkage.h>
18#include <asm/desc_defs.h>
19#include <asm/msr.h>
20#include <asm/page_types.h>
21#include <asm/pgtable_types.h>
22#include <asm/processor-flags.h>
23#include <asm/segment.h>
24
25	.text
26	.code32
27#ifdef CONFIG_EFI_HANDOVER_PROTOCOL
28SYM_FUNC_START(efi32_stub_entry)
29	call	1f
301:	popl	%ecx
31
32	/* Clear BSS */
33	xorl	%eax, %eax
34	leal	(_bss - 1b)(%ecx), %edi
35	leal	(_ebss - 1b)(%ecx), %ecx
36	subl	%edi, %ecx
37	shrl	$2, %ecx
38	cld
39	rep	stosl
40
41	add	$0x4, %esp		/* Discard return address */
42	movl	8(%esp), %ebx		/* struct boot_params pointer */
43	jmp	efi32_startup
44SYM_FUNC_END(efi32_stub_entry)
45#endif
46
47/*
48 * Called using a far call from __efi64_thunk() below, using the x86_64 SysV
49 * ABI (except for R8/R9 which are inaccessible to 32-bit code - EAX/EBX are
50 * used instead).  EBP+16 points to the arguments passed via the stack.
51 *
52 * The first argument (EDI) is a pointer to the boot service or protocol, to
53 * which the remaining arguments are passed, each truncated to 32 bits.
54 */
55SYM_FUNC_START_LOCAL(efi_enter32)
56	/*
57	 * Convert x86-64 SysV ABI params to i386 ABI
58	 */
59	pushl	32(%ebp)	/* Up to 3 args passed via the stack */
60	pushl	24(%ebp)
61	pushl	16(%ebp)
62	pushl	%ebx		/* R9 */
63	pushl	%eax		/* R8 */
64	pushl	%ecx
65	pushl	%edx
66	pushl	%esi
67
68	/* Disable paging */
69	movl	%cr0, %eax
70	btrl	$X86_CR0_PG_BIT, %eax
71	movl	%eax, %cr0
72
73	/* Disable long mode via EFER */
74	movl	$MSR_EFER, %ecx
75	rdmsr
76	btrl	$_EFER_LME, %eax
77	wrmsr
78
79	call	*%edi
80
81	/* We must preserve return value */
82	movl	%eax, %edi
83
84	call	efi32_enable_long_mode
85
86	addl	$32, %esp
87	movl	%edi, %eax
88	lret
89SYM_FUNC_END(efi_enter32)
90
91	.code64
92SYM_FUNC_START(__efi64_thunk)
93	push	%rbp
94	movl	%esp, %ebp
95	push	%rbx
96
97	/* Move args #5 and #6 into 32-bit accessible registers */
98	movl	%r8d, %eax
99	movl	%r9d, %ebx
100
101	lcalll	*efi32_call(%rip)
102
103	pop	%rbx
104	pop	%rbp
105	RET
106SYM_FUNC_END(__efi64_thunk)
107
108	.code32
109SYM_FUNC_START_LOCAL(efi32_enable_long_mode)
110	movl	%cr4, %eax
111	btsl	$(X86_CR4_PAE_BIT), %eax
112	movl	%eax, %cr4
113
114	movl	$MSR_EFER, %ecx
115	rdmsr
116	btsl	$_EFER_LME, %eax
117	wrmsr
118
119	/* Disable interrupts - the firmware's IDT does not work in long mode */
120	cli
121
122	/* Enable paging */
123	movl	%cr0, %eax
124	btsl	$X86_CR0_PG_BIT, %eax
125	movl	%eax, %cr0
126	ret
127SYM_FUNC_END(efi32_enable_long_mode)
128
129/*
130 * This is the common EFI stub entry point for mixed mode. It sets up the GDT
131 * and page tables needed for 64-bit execution, after which it calls the
132 * common 64-bit EFI entrypoint efi_stub_entry().
133 *
134 * Arguments:	0(%esp)	image handle
135 * 		4(%esp)	EFI system table pointer
136 *		%ebx	struct boot_params pointer (or NULL)
137 *
138 * Since this is the point of no return for ordinary execution, no registers
139 * are considered live except for the function parameters. [Note that the EFI
140 * stub may still exit and return to the firmware using the Exit() EFI boot
141 * service.]
142 */
143SYM_FUNC_START_LOCAL(efi32_startup)
144	movl	%esp, %ebp
145
146	subl	$8, %esp
147	sgdtl	(%esp)			/* Save GDT descriptor to the stack */
148	movl	2(%esp), %esi		/* Existing GDT pointer */
149	movzwl	(%esp), %ecx		/* Existing GDT limit */
150	inc	%ecx			/* Existing GDT size */
151	andl	$~7, %ecx		/* Ensure size is multiple of 8 */
152
153	subl	%ecx, %esp		/* Allocate new GDT */
154	andl	$~15, %esp		/* Realign the stack */
155	movl	%esp, %edi		/* New GDT address */
156	leal	7(%ecx), %eax		/* New GDT limit */
157	pushw	%cx			/* Push 64-bit CS (for LJMP below) */
158	pushl	%edi			/* Push new GDT address */
159	pushw	%ax			/* Push new GDT limit */
160
161	/* Copy GDT to the stack and add a 64-bit code segment at the end */
162	movl	$GDT_ENTRY(DESC_CODE64, 0, 0xfffff) & 0xffffffff, (%edi,%ecx)
163	movl	$GDT_ENTRY(DESC_CODE64, 0, 0xfffff) >> 32, 4(%edi,%ecx)
164	shrl	$2, %ecx
165	cld
166	rep	movsl			/* Copy the firmware GDT */
167	lgdtl	(%esp)			/* Switch to the new GDT */
168
169	call	1f
1701:	pop	%edi
171
172	/* Record mixed mode entry */
173	movb	$0x0, (efi_is64 - 1b)(%edi)
174
175	/* Set up indirect far call to re-enter 32-bit mode */
176	leal	(efi32_call - 1b)(%edi), %eax
177	addl	%eax, (%eax)
178	movw	%cs, 4(%eax)
179
180	/* Disable paging */
181	movl	%cr0, %eax
182	btrl	$X86_CR0_PG_BIT, %eax
183	movl	%eax, %cr0
184
185	/* Set up 1:1 mapping */
186	leal	(pte - 1b)(%edi), %eax
187	movl	$_PAGE_PRESENT | _PAGE_RW | _PAGE_PSE, %ecx
188	leal	(_PAGE_PRESENT | _PAGE_RW)(%eax), %edx
1892:	movl	%ecx, (%eax)
190	addl	$8, %eax
191	addl	$PMD_SIZE, %ecx
192	jnc	2b
193
194	movl	$PAGE_SIZE, %ecx
195	.irpc	l, 0123
196	movl	%edx, \l * 8(%eax)
197	addl	%ecx, %edx
198	.endr
199	addl	%ecx, %eax
200	movl	%edx, (%eax)
201	movl	%eax, %cr3
202
203	call	efi32_enable_long_mode
204
205	/* Set up far jump to 64-bit mode (CS is already on the stack) */
206	leal	(efi_stub_entry - 1b)(%edi), %eax
207	movl	%eax, 2(%esp)
208
209	movl	0(%ebp), %edi
210	movl	4(%ebp), %esi
211	movl	%ebx, %edx
212	ljmpl	*2(%esp)
213SYM_FUNC_END(efi32_startup)
214
215/*
216 * efi_status_t efi32_pe_entry(efi_handle_t image_handle,
217 *			       efi_system_table_32_t *sys_table)
218 */
219SYM_FUNC_START(efi32_pe_entry)
220	pushl	%ebx				// save callee-save registers
221
222	/* Check whether the CPU supports long mode */
223	movl	$0x80000001, %eax		// assume extended info support
224	cpuid
225	btl	$29, %edx			// check long mode bit
226	jnc	1f
227	leal	8(%esp), %esp			// preserve stack alignment
228	xor	%ebx, %ebx			// no struct boot_params pointer
229	jmp	efi32_startup			// only ESP and EBX remain live
2301:	movl	$0x80000003, %eax		// EFI_UNSUPPORTED
231	popl	%ebx
232	RET
233SYM_FUNC_END(efi32_pe_entry)
234
235#ifdef CONFIG_EFI_HANDOVER_PROTOCOL
236	.org	efi32_stub_entry + 0x200
237	.code64
238SYM_FUNC_START_NOALIGN(efi64_stub_entry)
239	jmp	efi_handover_entry
240SYM_FUNC_END(efi64_stub_entry)
241#endif
242
243	.data
244	.balign	8
245SYM_DATA_START_LOCAL(efi32_call)
246	.long	efi_enter32 - .
247	.word	0x0
248SYM_DATA_END(efi32_call)
249SYM_DATA(efi_is64, .byte 1)
250
251	.bss
252	.balign PAGE_SIZE
253SYM_DATA_LOCAL(pte, .fill 6 * PAGE_SIZE, 1, 0)
254