1 /*-
2 * Copyright (c) 2016 The FreeBSD Foundation
3 *
4 * This software was developed by Konstantin Belousov under sponsorship
5 * from the FreeBSD Foundation.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <stand.h>
30 #include <string.h>
31 #include <sys/param.h>
32 #include <machine/cpufunc.h>
33 #include <machine/psl.h>
34 #include <machine/segments.h>
35 #include <machine/frame.h>
36 #include <machine/tss.h>
37
38 #include <efi.h>
39 #include <efilib.h>
40
41 #include "bootstrap.h"
42 #include "loader_efi.h"
43
44 #define NUM_IST 8
45 #define NUM_EXC 32
46
47 /*
48 * This code catches exceptions but forwards hardware interrupts to
49 * handlers installed by firmware. It differentiates exceptions
50 * vs. interrupts by presence of the error code on the stack, which
51 * causes different stack pointer value on trap handler entry.
52 *
53 * Use kernel layout for the trapframe just to not be original.
54 *
55 * Use free IST slot in existing TSS, or create our own TSS if
56 * firmware did not configured any, to have stack switched to
57 * IST-specified one, e.g. to handle #SS. If hand-off cannot find
58 * unused IST slot, or create a new descriptor in GDT, we bail out.
59 */
60
61 static struct region_descriptor fw_idt; /* Descriptor for pristine fw IDT */
62 static struct region_descriptor loader_idt;/* Descriptor for loader
63 shadow IDT */
64 static EFI_PHYSICAL_ADDRESS lidt_pa; /* Address of loader shadow IDT */
65 static EFI_PHYSICAL_ADDRESS tss_pa; /* Address of TSS */
66 static EFI_PHYSICAL_ADDRESS exc_stack_pa;/* Address of IST stack for loader */
67 EFI_PHYSICAL_ADDRESS exc_rsp; /* %rsp value on our IST stack when
68 exception happens */
69 EFI_PHYSICAL_ADDRESS fw_intr_handlers[NUM_EXC]; /* fw handlers for < 32 IDT
70 vectors */
71 static int intercepted[NUM_EXC];
72 static int ist; /* IST for exception handlers */
73 static uint32_t tss_fw_seg; /* Fw TSS segment */
74 static uint32_t loader_tss; /* Loader TSS segment */
75 static struct region_descriptor fw_gdt; /* Descriptor of pristine GDT */
76 static EFI_PHYSICAL_ADDRESS loader_gdt_pa; /* Address of loader shadow GDT */
77 static UINTN loader_gdt_pa_size;
78
79 struct frame {
80 struct frame *fr_savfp;
81 uintptr_t fr_savpc;
82 };
83
84 void report_exc(struct trapframe *tf);
85 void
report_exc(struct trapframe * tf)86 report_exc(struct trapframe *tf)
87 {
88 struct frame *fp;
89 uintptr_t pc, base;
90 char buf[80];
91
92 base = (uintptr_t)boot_img->ImageBase;
93 /*
94 * printf() depends on loader runtime and UEFI firmware health
95 * to produce the console output, in case of exception, the
96 * loader or firmware runtime may fail to support the printf().
97 */
98 printf("===================================================="
99 "============================\n");
100 printf("Exception %u\n", tf->tf_trapno);
101 printf("ss 0x%04hx cs 0x%04hx ds 0x%04hx es 0x%04hx fs 0x%04hx "
102 "gs 0x%04hx\n",
103 (uint16_t)tf->tf_ss, (uint16_t)tf->tf_cs, (uint16_t)tf->tf_ds,
104 (uint16_t)tf->tf_es, (uint16_t)tf->tf_fs, (uint16_t)tf->tf_gs);
105 printf("err 0x%08x rfl 0x%08x addr 0x%016lx\n"
106 "rsp 0x%016lx rip 0x%016lx\n",
107 (uint32_t)tf->tf_err, (uint32_t)tf->tf_rflags, tf->tf_addr,
108 tf->tf_rsp, tf->tf_rip);
109 printf(
110 "rdi 0x%016lx rsi 0x%016lx rdx 0x%016lx\n"
111 "rcx 0x%016lx r8 0x%016lx r9 0x%016lx\n"
112 "rax 0x%016lx rbx 0x%016lx rbp 0x%016lx\n"
113 "r10 0x%016lx r11 0x%016lx r12 0x%016lx\n"
114 "r13 0x%016lx r14 0x%016lx r15 0x%016lx\n",
115 tf->tf_rdi, tf->tf_rsi, tf->tf_rdx, tf->tf_rcx, tf->tf_r8,
116 tf->tf_r9, tf->tf_rax, tf->tf_rbx, tf->tf_rbp, tf->tf_r10,
117 tf->tf_r11, tf->tf_r12, tf->tf_r13, tf->tf_r14, tf->tf_r15);
118
119 fp = (struct frame *)tf->tf_rbp;
120 pc = tf->tf_rip;
121
122 printf("Stack trace:\n");
123 pager_open();
124 while (fp != NULL || pc != 0) {
125 char *source = "PC";
126
127 if (pc >= base && pc < base + boot_img->ImageSize) {
128 pc -= base;
129 source = "loader PC";
130 }
131 (void) snprintf(buf, sizeof (buf), "FP %016lx: %s 0x%016lx\n",
132 (uintptr_t)fp, source, pc);
133 if (pager_output(buf))
134 break;
135
136 if (fp != NULL)
137 fp = fp->fr_savfp;
138
139 if (fp != NULL)
140 pc = fp->fr_savpc;
141 else
142 pc = 0;
143 }
144 pager_close();
145 printf("Machine stopped.\n");
146 }
147
148 static void
prepare_exception(unsigned idx,uint64_t my_handler,int ist_use_table[static NUM_IST])149 prepare_exception(unsigned idx, uint64_t my_handler,
150 int ist_use_table[static NUM_IST])
151 {
152 struct gate_descriptor *fw_idt_e, *loader_idt_e;
153
154 fw_idt_e = &((struct gate_descriptor *)fw_idt.rd_base)[idx];
155 loader_idt_e = &((struct gate_descriptor *)loader_idt.rd_base)[idx];
156 fw_intr_handlers[idx] = fw_idt_e->gd_looffset +
157 (fw_idt_e->gd_hioffset << 16);
158 intercepted[idx] = 1;
159 ist_use_table[fw_idt_e->gd_ist]++;
160 loader_idt_e->gd_looffset = my_handler;
161 loader_idt_e->gd_hioffset = my_handler >> 16;
162 /*
163 * We reuse uefi selector for the code segment for the exception
164 * handler code, while the reason for the fault might be the
165 * corruption of that gdt entry. On the other hand, allocating
166 * our own descriptor might be not much better, if gdt is corrupted.
167 */
168 loader_idt_e->gd_selector = fw_idt_e->gd_selector;
169 loader_idt_e->gd_ist = 0;
170 loader_idt_e->gd_type = SDT_SYSIGT;
171 loader_idt_e->gd_dpl = 0;
172 loader_idt_e->gd_p = 1;
173 loader_idt_e->gd_xx = 0;
174 loader_idt_e->sd_xx1 = 0;
175 }
176 #define PREPARE_EXCEPTION(N) \
177 extern char EXC##N##_handler[]; \
178 prepare_exception(N, (uintptr_t)EXC##N##_handler, ist_use_table);
179
180 static void
free_tables(void)181 free_tables(void)
182 {
183
184 if (lidt_pa != 0) {
185 BS->FreePages(lidt_pa, EFI_SIZE_TO_PAGES(fw_idt.rd_limit));
186 lidt_pa = 0;
187 }
188 if (exc_stack_pa != 0) {
189 BS->FreePages(exc_stack_pa, 1);
190 exc_stack_pa = 0;
191 }
192 if (tss_pa != 0 && tss_fw_seg == 0) {
193 BS->FreePages(tss_pa, EFI_SIZE_TO_PAGES(sizeof(struct
194 amd64tss)));
195 tss_pa = 0;
196 }
197 if (loader_gdt_pa != 0) {
198 BS->FreePages(loader_gdt_pa, loader_gdt_pa_size);
199 loader_gdt_pa = 0;
200 }
201 ist = 0;
202 loader_tss = 0;
203 }
204
205 static int
efi_setup_tss(struct region_descriptor * gdt,uint32_t loader_tss_idx,struct amd64tss ** tss)206 efi_setup_tss(struct region_descriptor *gdt, uint32_t loader_tss_idx,
207 struct amd64tss **tss)
208 {
209 EFI_STATUS status;
210 struct system_segment_descriptor *tss_desc;
211
212 tss_desc = (struct system_segment_descriptor *)(gdt->rd_base +
213 (loader_tss_idx << 3));
214 status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
215 EFI_SIZE_TO_PAGES(sizeof(struct amd64tss)), &tss_pa);
216 if (EFI_ERROR(status)) {
217 printf("efi_setup_tss: AllocatePages tss error %lu\n",
218 DECODE_ERROR(status));
219 return (0);
220 }
221 *tss = (struct amd64tss *)tss_pa;
222 bzero(*tss, sizeof(**tss));
223 tss_desc->sd_lolimit = sizeof(struct amd64tss);
224 tss_desc->sd_lobase = tss_pa;
225 tss_desc->sd_type = SDT_SYSTSS;
226 tss_desc->sd_dpl = 0;
227 tss_desc->sd_p = 1;
228 tss_desc->sd_hilimit = sizeof(struct amd64tss) >> 16;
229 tss_desc->sd_gran = 0;
230 tss_desc->sd_hibase = tss_pa >> 24;
231 tss_desc->sd_xx0 = 0;
232 tss_desc->sd_xx1 = 0;
233 tss_desc->sd_mbz = 0;
234 tss_desc->sd_xx2 = 0;
235 return (1);
236 }
237
238 static int
efi_redirect_exceptions(void)239 efi_redirect_exceptions(void)
240 {
241 int ist_use_table[NUM_IST];
242 struct gate_descriptor *loader_idt_e;
243 struct system_segment_descriptor *tss_desc, *gdt_desc;
244 struct amd64tss *tss;
245 struct region_descriptor *gdt_rd, loader_gdt;
246 uint32_t i;
247 EFI_STATUS status;
248 register_t rfl;
249
250 sidt(&fw_idt);
251 status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
252 EFI_SIZE_TO_PAGES(fw_idt.rd_limit), &lidt_pa);
253 if (EFI_ERROR(status)) {
254 printf("efi_redirect_exceptions: AllocatePages IDT error %lu\n",
255 DECODE_ERROR(status));
256 lidt_pa = 0;
257 return (0);
258 }
259 status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, 1,
260 &exc_stack_pa);
261 if (EFI_ERROR(status)) {
262 printf("efi_redirect_exceptions: AllocatePages stk error %lu\n",
263 DECODE_ERROR(status));
264 exc_stack_pa = 0;
265 free_tables();
266 return (0);
267 }
268 loader_idt.rd_limit = fw_idt.rd_limit;
269 loader_idt.rd_base = lidt_pa;
270 bcopy((void *)fw_idt.rd_base, (void *)loader_idt.rd_base,
271 loader_idt.rd_limit);
272 bzero(ist_use_table, sizeof(ist_use_table));
273 bzero(fw_intr_handlers, sizeof(fw_intr_handlers));
274 bzero(intercepted, sizeof(intercepted));
275
276 sgdt(&fw_gdt);
277 tss_fw_seg = read_tr();
278 gdt_rd = NULL;
279 if (tss_fw_seg == 0) {
280 for (i = 2; (i << 3) + sizeof(*gdt_desc) <= fw_gdt.rd_limit;
281 i += 2) {
282 gdt_desc = (struct system_segment_descriptor *)(
283 fw_gdt.rd_base + (i << 3));
284 if (gdt_desc->sd_type == 0 && gdt_desc->sd_mbz == 0) {
285 gdt_rd = &fw_gdt;
286 break;
287 }
288 }
289 if (gdt_rd == NULL) {
290 if (i >= 8190) {
291 printf("efi_redirect_exceptions: all slots "
292 "in gdt are used\n");
293 free_tables();
294 return (0);
295 }
296 loader_gdt.rd_limit = roundup2(fw_gdt.rd_limit +
297 sizeof(struct system_segment_descriptor),
298 sizeof(struct system_segment_descriptor)) - 1;
299 loader_gdt_pa_size =
300 EFI_SIZE_TO_PAGES(loader_gdt.rd_limit);
301 i = (loader_gdt.rd_limit + 1 -
302 sizeof(struct system_segment_descriptor)) /
303 sizeof(struct system_segment_descriptor) * 2;
304 status = BS->AllocatePages(AllocateAnyPages,
305 EfiLoaderData, loader_gdt_pa_size, &loader_gdt_pa);
306 if (EFI_ERROR(status)) {
307 printf("efi_setup_tss: AllocatePages gdt error "
308 "%lu\n", DECODE_ERROR(status));
309 loader_gdt_pa = 0;
310 free_tables();
311 return (0);
312 }
313 loader_gdt.rd_base = loader_gdt_pa;
314 bzero((void *)loader_gdt.rd_base, loader_gdt.rd_limit);
315 bcopy((void *)fw_gdt.rd_base,
316 (void *)loader_gdt.rd_base, fw_gdt.rd_limit);
317 gdt_rd = &loader_gdt;
318 }
319 loader_tss = i << 3;
320 if (!efi_setup_tss(gdt_rd, i, &tss)) {
321 tss_pa = 0;
322 free_tables();
323 return (0);
324 }
325 } else {
326 tss_desc = (struct system_segment_descriptor *)((char *)
327 fw_gdt.rd_base + tss_fw_seg);
328 if (tss_desc->sd_type != SDT_SYSTSS &&
329 tss_desc->sd_type != SDT_SYSBSY) {
330 printf("LTR points to non-TSS descriptor\n");
331 free_tables();
332 return (0);
333 }
334 tss_pa = tss_desc->sd_lobase + (tss_desc->sd_hibase << 24);
335 tss = (struct amd64tss *)tss_pa;
336 tss_desc->sd_type = SDT_SYSTSS; /* unbusy */
337 }
338
339 PREPARE_EXCEPTION(0);
340 PREPARE_EXCEPTION(1);
341 PREPARE_EXCEPTION(2);
342 PREPARE_EXCEPTION(3);
343 PREPARE_EXCEPTION(4);
344 PREPARE_EXCEPTION(5);
345 PREPARE_EXCEPTION(6);
346 PREPARE_EXCEPTION(7);
347 PREPARE_EXCEPTION(8);
348 PREPARE_EXCEPTION(9);
349 PREPARE_EXCEPTION(10);
350 PREPARE_EXCEPTION(11);
351 PREPARE_EXCEPTION(12);
352 PREPARE_EXCEPTION(13);
353 PREPARE_EXCEPTION(14);
354 PREPARE_EXCEPTION(16);
355 PREPARE_EXCEPTION(17);
356 PREPARE_EXCEPTION(18);
357 PREPARE_EXCEPTION(19);
358 PREPARE_EXCEPTION(20);
359
360 exc_rsp = exc_stack_pa + EFI_PAGE_SIZE -
361 (6 /* hw exception frame */ + 3 /* scratch regs */) * 8;
362
363 /* Find free IST and use it */
364 for (ist = 1; ist < NUM_IST; ist++) {
365 if (ist_use_table[ist] == 0)
366 break;
367 }
368 if (ist == NUM_IST) {
369 printf("efi_redirect_exceptions: all ISTs used\n");
370 free_tables();
371 lidt_pa = 0;
372 return (0);
373 }
374 for (i = 0; i < NUM_EXC; i++) {
375 loader_idt_e = &((struct gate_descriptor *)loader_idt.
376 rd_base)[i];
377 if (intercepted[i])
378 loader_idt_e->gd_ist = ist;
379 }
380 (&(tss->tss_ist1))[ist - 1] = exc_stack_pa + EFI_PAGE_SIZE;
381
382 /* Switch to new IDT */
383 rfl = intr_disable();
384 if (loader_gdt_pa != 0)
385 bare_lgdt(&loader_gdt);
386 if (loader_tss != 0)
387 ltr(loader_tss);
388 lidt(&loader_idt);
389 intr_restore(rfl);
390 return (1);
391 }
392
393 static void
efi_unredirect_exceptions(void)394 efi_unredirect_exceptions(void)
395 {
396 register_t rfl;
397
398 if (lidt_pa == 0)
399 return;
400
401 rfl = intr_disable();
402 if (ist != 0)
403 (&(((struct amd64tss *)tss_pa)->tss_ist1))[ist - 1] = 0;
404 if (loader_gdt_pa != 0)
405 bare_lgdt(&fw_gdt);
406 if (loader_tss != 0)
407 ltr(tss_fw_seg);
408 lidt(&fw_idt);
409 intr_restore(rfl);
410 free_tables();
411 }
412
413 static int
command_grab_faults(int argc,char * argv[])414 command_grab_faults(int argc, char *argv[])
415 {
416 int res;
417
418 res = efi_redirect_exceptions();
419 if (!res)
420 printf("failed\n");
421 return (CMD_OK);
422 }
423 COMMAND_SET(grab_faults, "grab_faults", "grab faults", command_grab_faults);
424
425 static int
command_ungrab_faults(int argc,char * argv[])426 command_ungrab_faults(int argc, char *argv[])
427 {
428
429 efi_unredirect_exceptions();
430 return (CMD_OK);
431 }
432 COMMAND_SET(ungrab_faults, "ungrab_faults", "ungrab faults",
433 command_ungrab_faults);
434
435 static int
command_fault(int argc,char * argv[])436 command_fault(int argc, char *argv[])
437 {
438
439 __asm("ud2");
440 return (CMD_OK);
441 }
442 COMMAND_SET(fault, "fault", "generate fault", command_fault);
443