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