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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
|
/* SPDX-License-Identifier: GPL-2.0 */
/*
* 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 and IDT before we make EFI service
* calls.
*
* On the plus side, we don't have to worry about mangling 64-bit
* addresses into 32-bits because we're executing with an identity
* mapped pagetable and haven't transitioned to 64-bit virtual addresses
* yet.
*/
#include <linux/linkage.h>
#include <asm/desc_defs.h>
#include <asm/msr.h>
#include <asm/page_types.h>
#include <asm/pgtable_types.h>
#include <asm/processor-flags.h>
#include <asm/segment.h>
.text
.code32
#ifdef CONFIG_EFI_HANDOVER_PROTOCOL
SYM_FUNC_START(efi32_stub_entry)
call 1f
1: popl %ecx
/* Clear BSS */
xorl %eax, %eax
leal (_bss - 1b)(%ecx), %edi
leal (_ebss - 1b)(%ecx), %ecx
subl %edi, %ecx
shrl $2, %ecx
cld
rep stosl
add $0x4, %esp /* Discard return address */
movl 8(%esp), %ebx /* struct boot_params pointer */
jmp efi32_startup
SYM_FUNC_END(efi32_stub_entry)
#endif
/*
* Called using a far call from __efi64_thunk() below, using the x86_64 SysV
* ABI (except for R8/R9 which are inaccessible to 32-bit code - EAX/EBX are
* used instead). EBP+16 points to the arguments passed via the stack.
*
* The first argument (EDI) is a pointer to the boot service or protocol, to
* which the remaining arguments are passed, each truncated to 32 bits.
*/
SYM_FUNC_START_LOCAL(efi_enter32)
/*
* Convert x86-64 SysV ABI params to i386 ABI
*/
pushl 32(%ebp) /* Up to 3 args passed via the stack */
pushl 24(%ebp)
pushl 16(%ebp)
pushl %ebx /* R9 */
pushl %eax /* R8 */
pushl %ecx
pushl %edx
pushl %esi
/* 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
call efi32_enable_long_mode
addl $32, %esp
movl %edi, %eax
lret
SYM_FUNC_END(efi_enter32)
.code64
SYM_FUNC_START(__efi64_thunk)
push %rbp
movl %esp, %ebp
push %rbx
/* Move args #5 and #6 into 32-bit accessible registers */
movl %r8d, %eax
movl %r9d, %ebx
lcalll *efi32_call(%rip)
pop %rbx
pop %rbp
RET
SYM_FUNC_END(__efi64_thunk)
.code32
SYM_FUNC_START_LOCAL(efi32_enable_long_mode)
movl %cr4, %eax
btsl $(X86_CR4_PAE_BIT), %eax
movl %eax, %cr4
movl $MSR_EFER, %ecx
rdmsr
btsl $_EFER_LME, %eax
wrmsr
/* Disable interrupts - the firmware's IDT does not work in long mode */
cli
/* Enable paging */
movl %cr0, %eax
btsl $X86_CR0_PG_BIT, %eax
movl %eax, %cr0
ret
SYM_FUNC_END(efi32_enable_long_mode)
/*
* This is the common EFI stub entry point for mixed mode. It sets up the GDT
* and page tables needed for 64-bit execution, after which it calls the
* common 64-bit EFI entrypoint efi_stub_entry().
*
* Arguments: 0(%esp) image handle
* 4(%esp) EFI system table pointer
* %ebx struct boot_params pointer (or NULL)
*
* Since this is the point of no return for ordinary execution, no registers
* are considered live except for the function parameters. [Note that the EFI
* stub may still exit and return to the firmware using the Exit() EFI boot
* service.]
*/
SYM_FUNC_START_LOCAL(efi32_startup)
movl %esp, %ebp
subl $8, %esp
sgdtl (%esp) /* Save GDT descriptor to the stack */
movl 2(%esp), %esi /* Existing GDT pointer */
movzwl (%esp), %ecx /* Existing GDT limit */
inc %ecx /* Existing GDT size */
andl $~7, %ecx /* Ensure size is multiple of 8 */
subl %ecx, %esp /* Allocate new GDT */
andl $~15, %esp /* Realign the stack */
movl %esp, %edi /* New GDT address */
leal 7(%ecx), %eax /* New GDT limit */
pushw %cx /* Push 64-bit CS (for LJMP below) */
pushl %edi /* Push new GDT address */
pushw %ax /* Push new GDT limit */
/* Copy GDT to the stack and add a 64-bit code segment at the end */
movl $GDT_ENTRY(DESC_CODE64, 0, 0xfffff) & 0xffffffff, (%edi,%ecx)
movl $GDT_ENTRY(DESC_CODE64, 0, 0xfffff) >> 32, 4(%edi,%ecx)
shrl $2, %ecx
cld
rep movsl /* Copy the firmware GDT */
lgdtl (%esp) /* Switch to the new GDT */
call 1f
1: pop %edi
/* Record mixed mode entry */
movb $0x0, (efi_is64 - 1b)(%edi)
/* Set up indirect far call to re-enter 32-bit mode */
leal (efi32_call - 1b)(%edi), %eax
addl %eax, (%eax)
movw %cs, 4(%eax)
/* Disable paging */
movl %cr0, %eax
btrl $X86_CR0_PG_BIT, %eax
movl %eax, %cr0
/* Set up 1:1 mapping */
leal (pte - 1b)(%edi), %eax
movl $_PAGE_PRESENT | _PAGE_RW | _PAGE_PSE, %ecx
leal (_PAGE_PRESENT | _PAGE_RW)(%eax), %edx
2: movl %ecx, (%eax)
addl $8, %eax
addl $PMD_SIZE, %ecx
jnc 2b
movl $PAGE_SIZE, %ecx
.irpc l, 0123
movl %edx, \l * 8(%eax)
addl %ecx, %edx
.endr
addl %ecx, %eax
movl %edx, (%eax)
movl %eax, %cr3
call efi32_enable_long_mode
/* Set up far jump to 64-bit mode (CS is already on the stack) */
leal (efi_stub_entry - 1b)(%edi), %eax
movl %eax, 2(%esp)
movl 0(%ebp), %edi
movl 4(%ebp), %esi
movl %ebx, %edx
ljmpl *2(%esp)
SYM_FUNC_END(efi32_startup)
/*
* efi_status_t efi32_pe_entry(efi_handle_t image_handle,
* efi_system_table_32_t *sys_table)
*/
SYM_FUNC_START(efi32_pe_entry)
pushl %ebx // save callee-save registers
/* Check whether the CPU supports long mode */
movl $0x80000001, %eax // assume extended info support
cpuid
btl $29, %edx // check long mode bit
jnc 1f
leal 8(%esp), %esp // preserve stack alignment
xor %ebx, %ebx // no struct boot_params pointer
jmp efi32_startup // only ESP and EBX remain live
1: movl $0x80000003, %eax // EFI_UNSUPPORTED
popl %ebx
RET
SYM_FUNC_END(efi32_pe_entry)
#ifdef CONFIG_EFI_HANDOVER_PROTOCOL
.org efi32_stub_entry + 0x200
.code64
SYM_FUNC_START_NOALIGN(efi64_stub_entry)
jmp efi_handover_entry
SYM_FUNC_END(efi64_stub_entry)
#endif
.data
.balign 8
SYM_DATA_START_LOCAL(efi32_call)
.long efi_enter32 - .
.word 0x0
SYM_DATA_END(efi32_call)
SYM_DATA(efi_is64, .byte 1)
.bss
.balign PAGE_SIZE
SYM_DATA_LOCAL(pte, .fill 6 * PAGE_SIZE, 1, 0)
|