172 lines
5.6 KiB
C
172 lines
5.6 KiB
C
/*
|
|
* Copyright (c) 2022 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/spinlock.h>
|
|
#include <zephyr/arch/x86/efi.h>
|
|
#include <zephyr/kernel/mm.h>
|
|
#include "../zefi/efi.h" /* ZEFI not on include path */
|
|
#include <zephyr/kernel.h>
|
|
#include <kernel_arch_func.h>
|
|
|
|
#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;
|
|
}
|
|
|
|
k_mem_map_phys_bare((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
|