|
| 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/msr.h> |
| 19 | +#include <asm/page_types.h> |
| 20 | +#include <asm/processor-flags.h> |
| 21 | +#include <asm/segment.h> |
| 22 | + |
| 23 | + .code64 |
| 24 | + .text |
| 25 | +/* |
| 26 | + * When booting in 64-bit mode on 32-bit EFI firmware, startup_64_mixed_mode() |
| 27 | + * is the first thing that runs after switching to long mode. Depending on |
| 28 | + * whether the EFI handover protocol or the compat entry point was used to |
| 29 | + * enter the kernel, it will either branch to the 64-bit EFI handover |
| 30 | + * entrypoint at offset 0x390 in the image, or to the 64-bit EFI PE/COFF |
| 31 | + * entrypoint efi_pe_entry(). In the former case, the bootloader must provide a |
| 32 | + * struct bootparams pointer as the third argument, so the presence of such a |
| 33 | + * pointer is used to disambiguate. |
| 34 | + * |
| 35 | + * +--------------+ |
| 36 | + * +------------------+ +------------+ +------>| efi_pe_entry | |
| 37 | + * | efi32_pe_entry |---->| | | +-----------+--+ |
| 38 | + * +------------------+ | | +------+----------------+ | |
| 39 | + * | startup_32 |---->| startup_64_mixed_mode | | |
| 40 | + * +------------------+ | | +------+----------------+ V |
| 41 | + * | efi32_stub_entry |---->| | | +------------------+ |
| 42 | + * +------------------+ +------------+ +---->| efi64_stub_entry | |
| 43 | + * +-------------+----+ |
| 44 | + * +------------+ +----------+ | |
| 45 | + * | startup_64 |<----| efi_main |<--------------+ |
| 46 | + * +------------+ +----------+ |
| 47 | + */ |
| 48 | +SYM_FUNC_START(startup_64_mixed_mode) |
| 49 | + lea efi32_boot_args(%rip), %rdx |
| 50 | + mov 0(%rdx), %edi |
| 51 | + mov 4(%rdx), %esi |
| 52 | + mov 8(%rdx), %edx // saved bootparams pointer |
| 53 | + test %edx, %edx |
| 54 | + jnz efi64_stub_entry |
| 55 | + /* |
| 56 | + * efi_pe_entry uses MS calling convention, which requires 32 bytes of |
| 57 | + * shadow space on the stack even if all arguments are passed in |
| 58 | + * registers. We also need an additional 8 bytes for the space that |
| 59 | + * would be occupied by the return address, and this also results in |
| 60 | + * the correct stack alignment for entry. |
| 61 | + */ |
| 62 | + sub $40, %rsp |
| 63 | + mov %rdi, %rcx // MS calling convention |
| 64 | + mov %rsi, %rdx |
| 65 | + jmp efi_pe_entry |
| 66 | +SYM_FUNC_END(startup_64_mixed_mode) |
| 67 | + |
| 68 | +SYM_FUNC_START(__efi64_thunk) |
| 69 | + push %rbp |
| 70 | + push %rbx |
| 71 | + |
| 72 | + movl %ds, %eax |
| 73 | + push %rax |
| 74 | + movl %es, %eax |
| 75 | + push %rax |
| 76 | + movl %ss, %eax |
| 77 | + push %rax |
| 78 | + |
| 79 | + /* Copy args passed on stack */ |
| 80 | + movq 0x30(%rsp), %rbp |
| 81 | + movq 0x38(%rsp), %rbx |
| 82 | + movq 0x40(%rsp), %rax |
| 83 | + |
| 84 | + /* |
| 85 | + * Convert x86-64 ABI params to i386 ABI |
| 86 | + */ |
| 87 | + subq $64, %rsp |
| 88 | + movl %esi, 0x0(%rsp) |
| 89 | + movl %edx, 0x4(%rsp) |
| 90 | + movl %ecx, 0x8(%rsp) |
| 91 | + movl %r8d, 0xc(%rsp) |
| 92 | + movl %r9d, 0x10(%rsp) |
| 93 | + movl %ebp, 0x14(%rsp) |
| 94 | + movl %ebx, 0x18(%rsp) |
| 95 | + movl %eax, 0x1c(%rsp) |
| 96 | + |
| 97 | + leaq 0x20(%rsp), %rbx |
| 98 | + sgdt (%rbx) |
| 99 | + sidt 16(%rbx) |
| 100 | + |
| 101 | + leaq 1f(%rip), %rbp |
| 102 | + |
| 103 | + /* |
| 104 | + * Switch to IDT and GDT with 32-bit segments. These are the firmware |
| 105 | + * GDT and IDT that were installed when the kernel started executing. |
| 106 | + * The pointers were saved by the efi32_entry() routine below. |
| 107 | + * |
| 108 | + * Pass the saved DS selector to the 32-bit code, and use far return to |
| 109 | + * restore the saved CS selector. |
| 110 | + */ |
| 111 | + lidt efi32_boot_idt(%rip) |
| 112 | + lgdt efi32_boot_gdt(%rip) |
| 113 | + |
| 114 | + movzwl efi32_boot_ds(%rip), %edx |
| 115 | + movzwq efi32_boot_cs(%rip), %rax |
| 116 | + pushq %rax |
| 117 | + leaq efi_enter32(%rip), %rax |
| 118 | + pushq %rax |
| 119 | + lretq |
| 120 | + |
| 121 | +1: addq $64, %rsp |
| 122 | + movq %rdi, %rax |
| 123 | + |
| 124 | + pop %rbx |
| 125 | + movl %ebx, %ss |
| 126 | + pop %rbx |
| 127 | + movl %ebx, %es |
| 128 | + pop %rbx |
| 129 | + movl %ebx, %ds |
| 130 | + /* Clear out 32-bit selector from FS and GS */ |
| 131 | + xorl %ebx, %ebx |
| 132 | + movl %ebx, %fs |
| 133 | + movl %ebx, %gs |
| 134 | + |
| 135 | + pop %rbx |
| 136 | + pop %rbp |
| 137 | + RET |
| 138 | +SYM_FUNC_END(__efi64_thunk) |
| 139 | + |
| 140 | + .code32 |
| 141 | +/* |
| 142 | + * EFI service pointer must be in %edi. |
| 143 | + * |
| 144 | + * The stack should represent the 32-bit calling convention. |
| 145 | + */ |
| 146 | +SYM_FUNC_START_LOCAL(efi_enter32) |
| 147 | + /* Load firmware selector into data and stack segment registers */ |
| 148 | + movl %edx, %ds |
| 149 | + movl %edx, %es |
| 150 | + movl %edx, %fs |
| 151 | + movl %edx, %gs |
| 152 | + movl %edx, %ss |
| 153 | + |
| 154 | + /* Reload pgtables */ |
| 155 | + movl %cr3, %eax |
| 156 | + movl %eax, %cr3 |
| 157 | + |
| 158 | + /* Disable paging */ |
| 159 | + movl %cr0, %eax |
| 160 | + btrl $X86_CR0_PG_BIT, %eax |
| 161 | + movl %eax, %cr0 |
| 162 | + |
| 163 | + /* Disable long mode via EFER */ |
| 164 | + movl $MSR_EFER, %ecx |
| 165 | + rdmsr |
| 166 | + btrl $_EFER_LME, %eax |
| 167 | + wrmsr |
| 168 | + |
| 169 | + call *%edi |
| 170 | + |
| 171 | + /* We must preserve return value */ |
| 172 | + movl %eax, %edi |
| 173 | + |
| 174 | + /* |
| 175 | + * Some firmware will return with interrupts enabled. Be sure to |
| 176 | + * disable them before we switch GDTs and IDTs. |
| 177 | + */ |
| 178 | + cli |
| 179 | + |
| 180 | + lidtl 16(%ebx) |
| 181 | + lgdtl (%ebx) |
| 182 | + |
| 183 | + movl %cr4, %eax |
| 184 | + btsl $(X86_CR4_PAE_BIT), %eax |
| 185 | + movl %eax, %cr4 |
| 186 | + |
| 187 | + movl %cr3, %eax |
| 188 | + movl %eax, %cr3 |
| 189 | + |
| 190 | + movl $MSR_EFER, %ecx |
| 191 | + rdmsr |
| 192 | + btsl $_EFER_LME, %eax |
| 193 | + wrmsr |
| 194 | + |
| 195 | + xorl %eax, %eax |
| 196 | + lldt %ax |
| 197 | + |
| 198 | + pushl $__KERNEL_CS |
| 199 | + pushl %ebp |
| 200 | + |
| 201 | + /* Enable paging */ |
| 202 | + movl %cr0, %eax |
| 203 | + btsl $X86_CR0_PG_BIT, %eax |
| 204 | + movl %eax, %cr0 |
| 205 | + lret |
| 206 | +SYM_FUNC_END(efi_enter32) |
| 207 | + |
| 208 | +/* |
| 209 | + * This is the common EFI stub entry point for mixed mode. |
| 210 | + * |
| 211 | + * Arguments: %ecx image handle |
| 212 | + * %edx EFI system table pointer |
| 213 | + * %esi struct bootparams pointer (or NULL when not using |
| 214 | + * the EFI handover protocol) |
| 215 | + * |
| 216 | + * Since this is the point of no return for ordinary execution, no registers |
| 217 | + * are considered live except for the function parameters. [Note that the EFI |
| 218 | + * stub may still exit and return to the firmware using the Exit() EFI boot |
| 219 | + * service.] |
| 220 | + */ |
| 221 | +SYM_FUNC_START(efi32_entry) |
| 222 | + call 1f |
| 223 | +1: pop %ebx |
| 224 | + |
| 225 | + /* Save firmware GDTR and code/data selectors */ |
| 226 | + sgdtl (efi32_boot_gdt - 1b)(%ebx) |
| 227 | + movw %cs, (efi32_boot_cs - 1b)(%ebx) |
| 228 | + movw %ds, (efi32_boot_ds - 1b)(%ebx) |
| 229 | + |
| 230 | + /* Store firmware IDT descriptor */ |
| 231 | + sidtl (efi32_boot_idt - 1b)(%ebx) |
| 232 | + |
| 233 | + /* Store boot arguments */ |
| 234 | + leal (efi32_boot_args - 1b)(%ebx), %ebx |
| 235 | + movl %ecx, 0(%ebx) |
| 236 | + movl %edx, 4(%ebx) |
| 237 | + movl %esi, 8(%ebx) |
| 238 | + movb $0x0, 12(%ebx) // efi_is64 |
| 239 | + |
| 240 | + /* Disable paging */ |
| 241 | + movl %cr0, %eax |
| 242 | + btrl $X86_CR0_PG_BIT, %eax |
| 243 | + movl %eax, %cr0 |
| 244 | + |
| 245 | + jmp startup_32 |
| 246 | +SYM_FUNC_END(efi32_entry) |
| 247 | + |
| 248 | +#define ST32_boottime 60 // offsetof(efi_system_table_32_t, boottime) |
| 249 | +#define BS32_handle_protocol 88 // offsetof(efi_boot_services_32_t, handle_protocol) |
| 250 | +#define LI32_image_base 32 // offsetof(efi_loaded_image_32_t, image_base) |
| 251 | + |
| 252 | +/* |
| 253 | + * efi_status_t efi32_pe_entry(efi_handle_t image_handle, |
| 254 | + * efi_system_table_32_t *sys_table) |
| 255 | + */ |
| 256 | +SYM_FUNC_START(efi32_pe_entry) |
| 257 | + pushl %ebp |
| 258 | + movl %esp, %ebp |
| 259 | + pushl %eax // dummy push to allocate loaded_image |
| 260 | + |
| 261 | + pushl %ebx // save callee-save registers |
| 262 | + pushl %edi |
| 263 | + |
| 264 | + call verify_cpu // check for long mode support |
| 265 | + testl %eax, %eax |
| 266 | + movl $0x80000003, %eax // EFI_UNSUPPORTED |
| 267 | + jnz 2f |
| 268 | + |
| 269 | + call 1f |
| 270 | +1: pop %ebx |
| 271 | + |
| 272 | + /* Get the loaded image protocol pointer from the image handle */ |
| 273 | + leal -4(%ebp), %eax |
| 274 | + pushl %eax // &loaded_image |
| 275 | + leal (loaded_image_proto - 1b)(%ebx), %eax |
| 276 | + pushl %eax // pass the GUID address |
| 277 | + pushl 8(%ebp) // pass the image handle |
| 278 | + |
| 279 | + /* |
| 280 | + * Note the alignment of the stack frame. |
| 281 | + * sys_table |
| 282 | + * handle <-- 16-byte aligned on entry by ABI |
| 283 | + * return address |
| 284 | + * frame pointer |
| 285 | + * loaded_image <-- local variable |
| 286 | + * saved %ebx <-- 16-byte aligned here |
| 287 | + * saved %edi |
| 288 | + * &loaded_image |
| 289 | + * &loaded_image_proto |
| 290 | + * handle <-- 16-byte aligned for call to handle_protocol |
| 291 | + */ |
| 292 | + |
| 293 | + movl 12(%ebp), %eax // sys_table |
| 294 | + movl ST32_boottime(%eax), %eax // sys_table->boottime |
| 295 | + call *BS32_handle_protocol(%eax) // sys_table->boottime->handle_protocol |
| 296 | + addl $12, %esp // restore argument space |
| 297 | + testl %eax, %eax |
| 298 | + jnz 2f |
| 299 | + |
| 300 | + movl 8(%ebp), %ecx // image_handle |
| 301 | + movl 12(%ebp), %edx // sys_table |
| 302 | + movl -4(%ebp), %esi // loaded_image |
| 303 | + movl LI32_image_base(%esi), %esi // loaded_image->image_base |
| 304 | + leal (startup_32 - 1b)(%ebx), %ebp // runtime address of startup_32 |
| 305 | + /* |
| 306 | + * We need to set the image_offset variable here since startup_32() will |
| 307 | + * use it before we get to the 64-bit efi_pe_entry() in C code. |
| 308 | + */ |
| 309 | + subl %esi, %ebp // calculate image_offset |
| 310 | + movl %ebp, (image_offset - 1b)(%ebx) // save image_offset |
| 311 | + xorl %esi, %esi |
| 312 | + jmp efi32_entry // pass %ecx, %edx, %esi |
| 313 | + // no other registers remain live |
| 314 | + |
| 315 | +2: popl %edi // restore callee-save registers |
| 316 | + popl %ebx |
| 317 | + leave |
| 318 | + RET |
| 319 | +SYM_FUNC_END(efi32_pe_entry) |
| 320 | + |
| 321 | + .section ".rodata" |
| 322 | + /* EFI loaded image protocol GUID */ |
| 323 | + .balign 4 |
| 324 | +SYM_DATA_START_LOCAL(loaded_image_proto) |
| 325 | + .long 0x5b1b31a1 |
| 326 | + .word 0x9562, 0x11d2 |
| 327 | + .byte 0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b |
| 328 | +SYM_DATA_END(loaded_image_proto) |
| 329 | + |
| 330 | + .data |
| 331 | + .balign 8 |
| 332 | +SYM_DATA_START_LOCAL(efi32_boot_gdt) |
| 333 | + .word 0 |
| 334 | + .quad 0 |
| 335 | +SYM_DATA_END(efi32_boot_gdt) |
| 336 | + |
| 337 | +SYM_DATA_START_LOCAL(efi32_boot_idt) |
| 338 | + .word 0 |
| 339 | + .quad 0 |
| 340 | +SYM_DATA_END(efi32_boot_idt) |
| 341 | + |
| 342 | +SYM_DATA_LOCAL(efi32_boot_cs, .word 0) |
| 343 | +SYM_DATA_LOCAL(efi32_boot_ds, .word 0) |
| 344 | +SYM_DATA_LOCAL(efi32_boot_args, .long 0, 0, 0) |
| 345 | +SYM_DATA(efi_is64, .byte 1) |
0 commit comments