/* * Copyright (c) 2022 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "../zefi/efi.h" /* ZEFI not on include path */ #include #include #define EFI_CON_BUFSZ 128 /* Big stack for the EFI code to use */ static uint64_t __aligned(64) efi_stack[1024]; struct efi_boot_arg *efi; void *efi_get_acpi_rsdp(void) { if (efi == NULL) { return NULL; } return efi->acpi_rsdp; } void efi_init(struct efi_boot_arg *efi_arg) { if (efi_arg == NULL) { return; } z_phys_map((uint8_t **)&efi, (uintptr_t)efi_arg, sizeof(struct efi_boot_arg), 0); } /* EFI thunk. Not a lot of code, but lots of context: * * We need to swap in the original EFI page tables for this to work, * as Zephyr has only mapped memory it uses and IO it knows about. In * theory we might need to restore more state too (maybe the EFI code * uses special segment descriptors from its own GDT, maybe it relies * on interrupts in its own IDT, maybe it twiddles custom MSRs or * plays with the IO-MMU... the posibilities are endless). But * experimentally, only the memory state seems to be required on known * hardware. This is safe because in the existing architecture Zephyr * has already initialized all its own memory and left the rest of the * system as-is; we already know it doesn't overlap with the EFI * environment (because we've always just assumed that's the case, * heh). * * Similarly we need to swap the stack: EFI firmware was written in an * environment where it would be running on multi-gigabyte systems and * likes to overflow the tiny stacks Zephyr code uses. (There is also * the problem of the red zone -- SysV reserves 128 bytes of * unpreserved data "under" the stack pointer for the use of the * current function. Our compiler would be free to write things there * that might be clobbered by the EFI call, which doesn't understand * that rule. Inspection of generated code shows that we're safe, but * still, best to swap stacks explicitly.) * * And the calling conventions are different: the EFI function uses * Microsoft's ABI, not SysV. Parameters go in RCX/RDX/R8/R9 (though * we only pass two here), and return value is in RAX (which we * multiplex as an input to hold the function pointer). R10 and R11 * are also caller-save. Technically X/YMM0-5 are caller-save too, * but as long as this (SysV) function was called per its own ABI they * have already been saved by our own caller. Also note that there is * a 32 byte region ABOVE the return value that must be allocated by * the caller as spill space for the 4 register-passed arguments (this * ABI is so weird...). We also need two call-preserved scratch * registers (for preserving the stack pointer and page table), those * are R12/R13. * * Finally: note that the firmware on at least one board (an Up * Squared APL device) will internally ENABLE INTERRUPTS before * returing from its OutputString method. This is... unfortunate, and * says poor things about reliability using this code as it will * implicitly break the spinlock we're using. The OS will be able to * take an interrupt just fine, but if the resulting ISR tries to log, * we'll end up in EFI firmware reentrantly! The best we can do is an * unconditional CLI immediately after returning. */ static uint64_t efi_call(void *fn, uint64_t arg1, uint64_t arg2) { void *stack_top = &efi_stack[ARRAY_SIZE(efi_stack) - 4]; /* During the efi_call window the interrupt is enabled, that * means an interrupt could happen and trigger scheduler at * end of the interrupt. Try to prevent swap happening. */ k_sched_lock(); __asm__ volatile("movq %%cr3, %%r12;" /* save zephyr page table */ "movq %%rsp, %%r13;" /* save stack pointer */ "movq %%rsi, %%rsp;" /* set stack */ "movq %%rdi, %%cr3;" /* set EFI page table */ "callq *%%rax;" "cli;" "movq %%r12, %%cr3;" /* reset paging */ "movq %%r13, %%rsp;" /* reset stack */ : "+a"(fn) : "c"(arg1), "d"(arg2), "S"(stack_top), "D"(efi->efi_cr3) : "r8", "r9", "r10", "r11", "r12", "r13"); k_sched_unlock(); return (uint64_t) fn; } int efi_console_putchar(int c) { static struct k_spinlock lock; static uint16_t efibuf[EFI_CON_BUFSZ + 1]; static int n; static void *conout; static void *output_string_fn; struct efi_system_table *efist = efi->efi_systab; /* Limit the printk call in interrupt context for * EFI cosnsole. This is a workaround that prevents * the printk call re-entries when an interrupt * happens during the EFI call window. */ if (arch_is_in_isr()) { return 0; } if (c == '\n') { efi_console_putchar('\r'); } k_spinlock_key_t key = k_spin_lock(&lock); /* These structs live in EFI memory and aren't mapped by * Zephyr. Extract the needed pointers by swapping page * tables. Do it via lazy evaluation because this code is * routinely needed much earlier than any feasible init hook. */ if (conout == NULL) { uint64_t cr3; __asm__ volatile("movq %%cr3, %0" : "=r"(cr3)); __asm__ volatile("movq %0, %%cr3" :: "r"(efi->efi_cr3)); conout = efist->ConOut; output_string_fn = efist->ConOut->OutputString; __asm__ volatile("movq %0, %%cr3" :: "r"(cr3)); } /* Buffer, to reduce trips through the thunking layer. * Flushes when full and at newlines. */ efibuf[n++] = c; if (c == '\n' || n == EFI_CON_BUFSZ) { efibuf[n] = 0U; (void)efi_call(output_string_fn, (uint64_t)conout, (uint64_t)efibuf); n = 0; } k_spin_unlock(&lock, key); return 0; } #ifdef CONFIG_X86_EFI_CONSOLE int arch_printk_char_out(int c) { return efi_console_putchar(c); } #endif