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