diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index ed69fb15fbb..1d792456c35 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -326,7 +326,6 @@ config EXCEPTION_DEBUG config X86_VERY_EARLY_CONSOLE bool "Support very early boot printk" depends on PRINTK - default y help Non-emulated X86 devices often require special hardware to attach a debugger, which may not be easily available. This option adds a @@ -487,6 +486,21 @@ config X86_EFI Enable EFI support. This means you build your image with zefi support. See arch/x86/zefi/README.txt for more information. +config X86_EFI_CONSOLE + bool + depends on X86_EFI && X86_64 && !X86_VERY_EARLY_CONSOLE + default y + help + This enables the use of the UEFI console device as the + Zephyr printk handler. It requires that no interferences + with hardware used by the firmware console (e.g. a UART or + framebuffer) happens from Zephyr code, and that all memory + used by the firmware environment and its page tables be + separate and preserved. In general this is safe to assume, + but no automatic checking exists at runtime to verify. + Likewise be sure to disable any other console/printk + drivers! + source "arch/x86/core/Kconfig.ia32" source "arch/x86/core/Kconfig.intel64" diff --git a/arch/x86/core/efi.c b/arch/x86/core/efi.c index 95d0e8cd67f..60917058901 100644 --- a/arch/x86/core/efi.c +++ b/arch/x86/core/efi.c @@ -1,11 +1,18 @@ /* - * Copyright (c) 2020 Intel Corporation + * Copyright (c) 2022 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ +#include #include #include +#include "../zefi/efi.h" /* ZEFI not on include path */ + +#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; @@ -27,3 +34,120 @@ void efi_init(struct efi_boot_arg *efi_arg) 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]; + + __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"); + + 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; + + 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 diff --git a/arch/x86/zefi/zefi.c b/arch/x86/zefi/zefi.c index a3cecb07751..fd8059121b4 100644 --- a/arch/x86/zefi/zefi.c +++ b/arch/x86/zefi/zefi.c @@ -179,6 +179,9 @@ uintptr_t __abi efi_entry(void *img_handle, struct efi_system_table *sys_tab) unsigned char *code = (void *)zefi_entry; + efi_arg.efi_systab = efi; + __asm__ volatile("movq %%cr3, %0" : "=r"(efi_arg.efi_cr3)); + printf("Jumping to Entry Point: %p (%x %x %x %x %x %x %x)\n", code, code[0], code[1], code[2], code[3], code[4], code[5], code[6]); diff --git a/include/zephyr/arch/x86/efi.h b/include/zephyr/arch/x86/efi.h index bc65a2287f2..d51c8ee1bd8 100644 --- a/include/zephyr/arch/x86/efi.h +++ b/include/zephyr/arch/x86/efi.h @@ -13,6 +13,8 @@ #ifndef _ASMLANGUAGE struct efi_boot_arg { + void *efi_systab; /* EFI system table */ + unsigned long long efi_cr3; /* EFI page table */ void *acpi_rsdp; };