arch/x86: Use EFI console as default printk handler
Where we have access to a bootstrap UEFI environment, it's productive to use that console as the default printk handler. That avoids the bringup hassle of trying to configure UART settings blindly, as has been customary. It also emits nice text to the framebuffer on devices with no serial port or other debug harness at all. Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
This commit is contained in:
parent
e73d530cb4
commit
e931b7ba47
4 changed files with 145 additions and 2 deletions
|
@ -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"
|
||||
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Intel Corporation
|
||||
* Copyright (c) 2022 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <spinlock.h>
|
||||
#include <arch/x86/efi.h>
|
||||
#include <sys/mem_manage.h>
|
||||
#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
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue