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