xref: /freebsd/stand/efi/loader/arch/amd64/trap.c (revision e0c4386e)
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 <sys/cdefs.h>
30 #include <stand.h>
31 #include <string.h>
32 #include <sys/param.h>
33 #include <machine/cpufunc.h>
34 #include <machine/psl.h>
35 #include <machine/segments.h>
36 #include <machine/frame.h>
37 #include <machine/tss.h>
38 
39 #include <efi.h>
40 #include <efilib.h>
41 
42 #include "bootstrap.h"
43 #include "loader_efi.h"
44 
45 #define	NUM_IST	8
46 #define	NUM_EXC	32
47 
48 /*
49  * This code catches exceptions but forwards hardware interrupts to
50  * handlers installed by firmware.  It differentiates exceptions
51  * vs. interrupts by presence of the error code on the stack, which
52  * causes different stack pointer value on trap handler entry.
53  *
54  * Use kernel layout for the trapframe just to not be original.
55  *
56  * Use free IST slot in existing TSS, or create our own TSS if
57  * firmware did not configured any, to have stack switched to
58  * IST-specified one, e.g. to handle #SS.  If hand-off cannot find
59  * unused IST slot, or create a new descriptor in GDT, we bail out.
60  */
61 
62 static struct region_descriptor fw_idt;	/* Descriptor for pristine fw IDT */
63 static struct region_descriptor loader_idt;/* Descriptor for loader
64 					   shadow IDT */
65 static EFI_PHYSICAL_ADDRESS lidt_pa;	/* Address of loader shadow IDT */
66 static EFI_PHYSICAL_ADDRESS tss_pa;	/* Address of TSS */
67 static EFI_PHYSICAL_ADDRESS exc_stack_pa;/* Address of IST stack for loader */
68 EFI_PHYSICAL_ADDRESS exc_rsp;	/* %rsp value on our IST stack when
69 				   exception happens */
70 EFI_PHYSICAL_ADDRESS fw_intr_handlers[NUM_EXC]; /* fw handlers for < 32 IDT
71 						   vectors */
72 static int intercepted[NUM_EXC];
73 static int ist;				/* IST for exception handlers */
74 static uint32_t tss_fw_seg;		/* Fw TSS segment */
75 static uint32_t loader_tss;		/* Loader TSS segment */
76 static struct region_descriptor fw_gdt;	/* Descriptor of pristine GDT */
77 static EFI_PHYSICAL_ADDRESS loader_gdt_pa; /* Address of loader shadow GDT */
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
86 report_exc(struct trapframe *tf)
87 {
88 	struct frame *fp;
89 	uintptr_t pc, base;
90 	char buf[80];
91 	int ret;
92 
93 	base = (uintptr_t)boot_img->ImageBase;
94 	/*
95 	 * printf() depends on loader runtime and UEFI firmware health
96 	 * to produce the console output, in case of exception, the
97 	 * loader or firmware runtime may fail to support the printf().
98 	 */
99 	printf("===================================================="
100 	    "============================\n");
101 	printf("Exception %u\n", tf->tf_trapno);
102 	printf("ss 0x%04hx cs 0x%04hx ds 0x%04hx es 0x%04hx fs 0x%04hx "
103 	    "gs 0x%04hx\n",
104 	    (uint16_t)tf->tf_ss, (uint16_t)tf->tf_cs, (uint16_t)tf->tf_ds,
105 	    (uint16_t)tf->tf_es, (uint16_t)tf->tf_fs, (uint16_t)tf->tf_gs);
106 	printf("err 0x%08x rfl 0x%08x addr 0x%016lx\n"
107 	    "rsp 0x%016lx rip 0x%016lx\n",
108 	    (uint32_t)tf->tf_err, (uint32_t)tf->tf_rflags, tf->tf_addr,
109 	    tf->tf_rsp, tf->tf_rip);
110 	printf(
111 	    "rdi 0x%016lx rsi 0x%016lx rdx 0x%016lx\n"
112 	    "rcx 0x%016lx r8  0x%016lx r9  0x%016lx\n"
113 	    "rax 0x%016lx rbx 0x%016lx rbp 0x%016lx\n"
114 	    "r10 0x%016lx r11 0x%016lx r12 0x%016lx\n"
115 	    "r13 0x%016lx r14 0x%016lx r15 0x%016lx\n",
116 	    tf->tf_rdi, tf->tf_rsi, tf->tf_rdx, tf->tf_rcx, tf->tf_r8,
117 	    tf->tf_r9, tf->tf_rax, tf->tf_rbx, tf->tf_rbp, tf->tf_r10,
118 	    tf->tf_r11, tf->tf_r12, tf->tf_r13, tf->tf_r14, tf->tf_r15);
119 
120 	fp = (struct frame *)tf->tf_rbp;
121 	pc = tf->tf_rip;
122 
123 	printf("Stack trace:\n");
124 	pager_open();
125 	while (fp != NULL || pc != 0) {
126 		char *source = "PC";
127 
128 		if (pc >= base && pc < base + boot_img->ImageSize) {
129 			pc -= base;
130 			source = "loader PC";
131 		}
132 		(void) snprintf(buf, sizeof (buf), "FP %016lx: %s 0x%016lx\n",
133 		    (uintptr_t)fp, source, pc);
134 		if (pager_output(buf))
135 			break;
136 
137 		if (fp != NULL)
138 			fp = fp->fr_savfp;
139 
140 		if (fp != NULL)
141 			pc = fp->fr_savpc;
142 		else
143 			pc = 0;
144 	}
145 	pager_close();
146 	printf("Machine stopped.\n");
147 }
148 
149 static void
150 prepare_exception(unsigned idx, uint64_t my_handler,
151     int ist_use_table[static NUM_IST])
152 {
153 	struct gate_descriptor *fw_idt_e, *loader_idt_e;
154 
155 	fw_idt_e = &((struct gate_descriptor *)fw_idt.rd_base)[idx];
156 	loader_idt_e = &((struct gate_descriptor *)loader_idt.rd_base)[idx];
157 	fw_intr_handlers[idx] = fw_idt_e->gd_looffset +
158 	    (fw_idt_e->gd_hioffset << 16);
159 	intercepted[idx] = 1;
160 	ist_use_table[fw_idt_e->gd_ist]++;
161 	loader_idt_e->gd_looffset = my_handler;
162 	loader_idt_e->gd_hioffset = my_handler >> 16;
163 	/*
164 	 * We reuse uefi selector for the code segment for the exception
165 	 * handler code, while the reason for the fault might be the
166 	 * corruption of that gdt entry. On the other hand, allocating
167 	 * our own descriptor might be not much better, if gdt is corrupted.
168 	 */
169 	loader_idt_e->gd_selector = fw_idt_e->gd_selector;
170 	loader_idt_e->gd_ist = 0;
171 	loader_idt_e->gd_type = SDT_SYSIGT;
172 	loader_idt_e->gd_dpl = 0;
173 	loader_idt_e->gd_p = 1;
174 	loader_idt_e->gd_xx = 0;
175 	loader_idt_e->sd_xx1 = 0;
176 }
177 #define	PREPARE_EXCEPTION(N)						\
178     extern char EXC##N##_handler[];					\
179     prepare_exception(N, (uintptr_t)EXC##N##_handler, ist_use_table);
180 
181 static void
182 free_tables(void)
183 {
184 
185 	if (lidt_pa != 0) {
186 		BS->FreePages(lidt_pa, EFI_SIZE_TO_PAGES(fw_idt.rd_limit));
187 		lidt_pa = 0;
188 	}
189 	if (exc_stack_pa != 0) {
190 		BS->FreePages(exc_stack_pa, 1);
191 		exc_stack_pa = 0;
192 	}
193 	if (tss_pa != 0 && tss_fw_seg == 0) {
194 		BS->FreePages(tss_pa, EFI_SIZE_TO_PAGES(sizeof(struct
195 		    amd64tss)));
196 		tss_pa = 0;
197 	}
198 	if (loader_gdt_pa != 0) {
199 		BS->FreePages(tss_pa, 2);
200 		loader_gdt_pa = 0;
201 	}
202 	ist = 0;
203 	loader_tss = 0;
204 }
205 
206 static int
207 efi_setup_tss(struct region_descriptor *gdt, uint32_t loader_tss_idx,
208     struct amd64tss **tss)
209 {
210 	EFI_STATUS status;
211 	struct system_segment_descriptor *tss_desc;
212 
213 	tss_desc = (struct system_segment_descriptor *)(gdt->rd_base +
214 	    (loader_tss_idx << 3));
215 	status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
216 	    EFI_SIZE_TO_PAGES(sizeof(struct amd64tss)), &tss_pa);
217 	if (EFI_ERROR(status)) {
218 		printf("efi_setup_tss: AllocatePages tss error %lu\n",
219 		    EFI_ERROR_CODE(status));
220 		return (0);
221 	}
222 	*tss = (struct amd64tss *)tss_pa;
223 	bzero(*tss, sizeof(**tss));
224 	tss_desc->sd_lolimit = sizeof(struct amd64tss);
225 	tss_desc->sd_lobase = tss_pa;
226 	tss_desc->sd_type = SDT_SYSTSS;
227 	tss_desc->sd_dpl = 0;
228 	tss_desc->sd_p = 1;
229 	tss_desc->sd_hilimit = sizeof(struct amd64tss) >> 16;
230 	tss_desc->sd_gran = 0;
231 	tss_desc->sd_hibase = tss_pa >> 24;
232 	tss_desc->sd_xx0 = 0;
233 	tss_desc->sd_xx1 = 0;
234 	tss_desc->sd_mbz = 0;
235 	tss_desc->sd_xx2 = 0;
236 	return (1);
237 }
238 
239 static int
240 efi_redirect_exceptions(void)
241 {
242 	int ist_use_table[NUM_IST];
243 	struct gate_descriptor *loader_idt_e;
244 	struct system_segment_descriptor *tss_desc, *gdt_desc;
245 	struct amd64tss *tss;
246 	struct region_descriptor *gdt_rd, loader_gdt;
247 	uint32_t i;
248 	EFI_STATUS status;
249 	register_t rfl;
250 
251 	sidt(&fw_idt);
252 	status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
253 	    EFI_SIZE_TO_PAGES(fw_idt.rd_limit), &lidt_pa);
254 	if (EFI_ERROR(status)) {
255 		printf("efi_redirect_exceptions: AllocatePages IDT error %lu\n",
256 		    EFI_ERROR_CODE(status));
257 		lidt_pa = 0;
258 		return (0);
259 	}
260 	status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, 1,
261 	    &exc_stack_pa);
262 	if (EFI_ERROR(status)) {
263 		printf("efi_redirect_exceptions: AllocatePages stk error %lu\n",
264 		    EFI_ERROR_CODE(status));
265 		exc_stack_pa = 0;
266 		free_tables();
267 		return (0);
268 	}
269 	loader_idt.rd_limit = fw_idt.rd_limit;
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 			i = (loader_gdt.rd_limit + 1 -
300 			    sizeof(struct system_segment_descriptor)) /
301 			    sizeof(struct system_segment_descriptor) * 2;
302 			status = BS->AllocatePages(AllocateAnyPages,
303 			    EfiLoaderData,
304 			    EFI_SIZE_TO_PAGES(loader_gdt.rd_limit),
305 			    &loader_gdt_pa);
306 			if (EFI_ERROR(status)) {
307 				printf("efi_setup_tss: AllocatePages gdt error "
308 				    "%lu\n",  EFI_ERROR_CODE(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 << 16);
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 + 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 + 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
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
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(grap_faults, "grab_faults", "grab faults", command_grab_faults);
424 
425 static int
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
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