1 /*
2 * Copyright (c) 2022 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/spinlock.h>
8 #include <zephyr/arch/x86/efi.h>
9 #include <zephyr/kernel/mm.h>
10 #include "../zefi/efi.h" /* ZEFI not on include path */
11 #include <zephyr/kernel.h>
12 #include <kernel_arch_func.h>
13
14 #define EFI_CON_BUFSZ 128
15
16 /* Big stack for the EFI code to use */
17 static uint64_t __aligned(64) efi_stack[1024];
18
19 struct efi_boot_arg *efi;
20
21 #ifdef CONFIG_DYNAMIC_BOOTARGS
22 __pinned_noinit char efi_bootargs[CONFIG_BOOTARGS_ARGS_BUFFER_SIZE];
23 #endif
24
efi_get_acpi_rsdp(void)25 void *efi_get_acpi_rsdp(void)
26 {
27 if (efi == NULL) {
28 return NULL;
29 }
30
31 return efi->acpi_rsdp;
32 }
33
efi_init(struct efi_boot_arg * efi_arg)34 void efi_init(struct efi_boot_arg *efi_arg)
35 {
36 if (efi_arg == NULL) {
37 return;
38 }
39
40 k_mem_map_phys_bare((uint8_t **)&efi, (uintptr_t)efi_arg,
41 sizeof(struct efi_boot_arg), 0);
42 }
43
44 /* EFI thunk. Not a lot of code, but lots of context:
45 *
46 * We need to swap in the original EFI page tables for this to work,
47 * as Zephyr has only mapped memory it uses and IO it knows about. In
48 * theory we might need to restore more state too (maybe the EFI code
49 * uses special segment descriptors from its own GDT, maybe it relies
50 * on interrupts in its own IDT, maybe it twiddles custom MSRs or
51 * plays with the IO-MMU... the posibilities are endless). But
52 * experimentally, only the memory state seems to be required on known
53 * hardware. This is safe because in the existing architecture Zephyr
54 * has already initialized all its own memory and left the rest of the
55 * system as-is; we already know it doesn't overlap with the EFI
56 * environment (because we've always just assumed that's the case,
57 * heh).
58 *
59 * Similarly we need to swap the stack: EFI firmware was written in an
60 * environment where it would be running on multi-gigabyte systems and
61 * likes to overflow the tiny stacks Zephyr code uses. (There is also
62 * the problem of the red zone -- SysV reserves 128 bytes of
63 * unpreserved data "under" the stack pointer for the use of the
64 * current function. Our compiler would be free to write things there
65 * that might be clobbered by the EFI call, which doesn't understand
66 * that rule. Inspection of generated code shows that we're safe, but
67 * still, best to swap stacks explicitly.)
68 *
69 * And the calling conventions are different: the EFI function uses
70 * Microsoft's ABI, not SysV. Parameters go in RCX/RDX/R8/R9 (though
71 * we only pass two here), and return value is in RAX (which we
72 * multiplex as an input to hold the function pointer). R10 and R11
73 * are also caller-save. Technically X/YMM0-5 are caller-save too,
74 * but as long as this (SysV) function was called per its own ABI they
75 * have already been saved by our own caller. Also note that there is
76 * a 32 byte region ABOVE the return value that must be allocated by
77 * the caller as spill space for the 4 register-passed arguments (this
78 * ABI is so weird...). We also need two call-preserved scratch
79 * registers (for preserving the stack pointer and page table), those
80 * are R12/R13.
81 *
82 * Finally: note that the firmware on at least one board (an Up
83 * Squared APL device) will internally ENABLE INTERRUPTS before
84 * returing from its OutputString method. This is... unfortunate, and
85 * says poor things about reliability using this code as it will
86 * implicitly break the spinlock we're using. The OS will be able to
87 * take an interrupt just fine, but if the resulting ISR tries to log,
88 * we'll end up in EFI firmware reentrantly! The best we can do is an
89 * unconditional CLI immediately after returning.
90 */
efi_call(void * fn,uint64_t arg1,uint64_t arg2)91 static uint64_t efi_call(void *fn, uint64_t arg1, uint64_t arg2)
92 {
93 void *stack_top = &efi_stack[ARRAY_SIZE(efi_stack) - 4];
94
95 /* During the efi_call window the interrupt is enabled, that
96 * means an interrupt could happen and trigger scheduler at
97 * end of the interrupt. Try to prevent swap happening.
98 */
99 k_sched_lock();
100
101 __asm__ volatile("movq %%cr3, %%r12;" /* save zephyr page table */
102 "movq %%rsp, %%r13;" /* save stack pointer */
103 "movq %%rsi, %%rsp;" /* set stack */
104 "movq %%rdi, %%cr3;" /* set EFI page table */
105 "callq *%%rax;"
106 "cli;"
107 "movq %%r12, %%cr3;" /* reset paging */
108 "movq %%r13, %%rsp;" /* reset stack */
109 : "+a"(fn)
110 : "c"(arg1), "d"(arg2), "S"(stack_top), "D"(efi->efi_cr3)
111 : "r8", "r9", "r10", "r11", "r12", "r13");
112
113 k_sched_unlock();
114 return (uint64_t) fn;
115 }
116
efi_console_putchar(int c)117 int efi_console_putchar(int c)
118 {
119 static struct k_spinlock lock;
120 static uint16_t efibuf[EFI_CON_BUFSZ + 1];
121 static int n;
122 static void *conout;
123 static void *output_string_fn;
124 struct efi_system_table *efist = efi->efi_systab;
125
126 /* Limit the printk call in interrupt context for
127 * EFI cosnsole. This is a workaround that prevents
128 * the printk call re-entries when an interrupt
129 * happens during the EFI call window.
130 */
131 if (arch_is_in_isr()) {
132 return 0;
133 }
134
135 if (c == '\n') {
136 efi_console_putchar('\r');
137 }
138
139 k_spinlock_key_t key = k_spin_lock(&lock);
140
141 /* These structs live in EFI memory and aren't mapped by
142 * Zephyr. Extract the needed pointers by swapping page
143 * tables. Do it via lazy evaluation because this code is
144 * routinely needed much earlier than any feasible init hook.
145 */
146 if (conout == NULL) {
147 uint64_t cr3;
148
149 __asm__ volatile("movq %%cr3, %0" : "=r"(cr3));
150 __asm__ volatile("movq %0, %%cr3" :: "r"(efi->efi_cr3));
151 conout = efist->ConOut;
152 output_string_fn = efist->ConOut->OutputString;
153 __asm__ volatile("movq %0, %%cr3" :: "r"(cr3));
154 }
155
156 /* Buffer, to reduce trips through the thunking layer.
157 * Flushes when full and at newlines.
158 */
159 efibuf[n++] = c;
160 if (c == '\n' || n == EFI_CON_BUFSZ) {
161 efibuf[n] = 0U;
162 (void)efi_call(output_string_fn, (uint64_t)conout, (uint64_t)efibuf);
163 n = 0;
164 }
165
166 k_spin_unlock(&lock, key);
167 return 0;
168 }
169
170 #ifdef CONFIG_X86_EFI_CONSOLE
arch_printk_char_out(int c)171 int arch_printk_char_out(int c)
172 {
173 return efi_console_putchar(c);
174 }
175 #endif
176
177 #ifdef CONFIG_DYNAMIC_BOOTARGS
get_bootargs(void)178 const char *get_bootargs(void)
179 {
180 return efi_bootargs;
181 }
182 #endif /* CONFIG_DYNAMIC_BOOTARGS */
183