#include <sys/cdefs.h>
#include <stand.h>
#include <string.h>
#include <sys/param.h>
#include <machine/cpufunc.h>
#include <machine/psl.h>
#include <machine/segments.h>
#include <machine/frame.h>
#include <machine/tss.h>
#include <efi.h>
#include <efilib.h>
#include "bootstrap.h"
#include "loader_efi.h"
#define NUM_IST 8
#define NUM_EXC 32
static struct region_descriptor fw_idt;
static struct region_descriptor
loader_idt;
static EFI_PHYSICAL_ADDRESS lidt_pa;
static EFI_PHYSICAL_ADDRESS tss_pa;
static EFI_PHYSICAL_ADDRESS exc_stack_pa;
EFI_PHYSICAL_ADDRESS
exc_rsp;
EFI_PHYSICAL_ADDRESS
fw_intr_handlers[NUM_EXC];
static int intercepted[NUM_EXC];
static int ist;
static uint32_t tss_fw_seg;
static uint32_t loader_tss;
static struct region_descriptor fw_gdt;
static EFI_PHYSICAL_ADDRESS loader_gdt_pa;
static UINTN loader_gdt_pa_size;
struct frame {
struct frame *fr_savfp;
uintptr_t fr_savpc;
};
void report_exc(struct trapframe *tf);
static void
stack_trace(struct frame *fp, uintptr_t pc)
{
uintptr_t base;
char buf[80];
base = (uintptr_t)boot_img->ImageBase;
printf("Stack trace:\n");
pager_open();
while (fp != NULL || pc != 0) {
struct frame *nfp;
char *source = "PC";
if (pc >= base && pc < base + boot_img->ImageSize) {
pc -= base;
source = "loader PC";
}
(void) snprintf(buf, sizeof (buf), "FP %016lx: %s 0x%016lx\n",
(uintptr_t)fp, source, pc);
if (pager_output(buf))
break;
if (fp == NULL)
break;
nfp = fp->fr_savfp;
if (nfp != NULL && nfp <= fp) {
printf("FP %016lx: loop detected, stopping trace\n",
(uintptr_t)nfp);
break;
}
fp = nfp;
if (fp != NULL)
pc = fp->fr_savpc;
else
pc = 0;
}
pager_close();
}
void
panic_action(void)
{
struct frame *fp;
uintptr_t rip;
__asm __volatile("movq %%rbp,%0" : "=r" (fp));
rip = fp->fr_savpc;
stack_trace(fp, rip);
printf("--> Press a key on the console to reboot <--\n");
getchar();
printf("Rebooting...\n");
exit(1);
}
void
report_exc(struct trapframe *tf)
{
printf("===================================================="
"============================\n");
printf("Exception %u\n", tf->tf_trapno);
printf("ss 0x%04hx cs 0x%04hx ds 0x%04hx es 0x%04hx fs 0x%04hx "
"gs 0x%04hx\n",
(uint16_t)tf->tf_ss, (uint16_t)tf->tf_cs, (uint16_t)tf->tf_ds,
(uint16_t)tf->tf_es, (uint16_t)tf->tf_fs, (uint16_t)tf->tf_gs);
printf("err 0x%08x rfl 0x%08x addr 0x%016lx\n"
"rsp 0x%016lx rip 0x%016lx\n",
(uint32_t)tf->tf_err, (uint32_t)tf->tf_rflags, tf->tf_addr,
tf->tf_rsp, tf->tf_rip);
printf(
"rdi 0x%016lx rsi 0x%016lx rdx 0x%016lx\n"
"rcx 0x%016lx r8 0x%016lx r9 0x%016lx\n"
"rax 0x%016lx rbx 0x%016lx rbp 0x%016lx\n"
"r10 0x%016lx r11 0x%016lx r12 0x%016lx\n"
"r13 0x%016lx r14 0x%016lx r15 0x%016lx\n",
tf->tf_rdi, tf->tf_rsi, tf->tf_rdx, tf->tf_rcx, tf->tf_r8,
tf->tf_r9, tf->tf_rax, tf->tf_rbx, tf->tf_rbp, tf->tf_r10,
tf->tf_r11, tf->tf_r12, tf->tf_r13, tf->tf_r14, tf->tf_r15);
stack_trace((struct frame *)tf->tf_rbp, tf->tf_rip);
printf("Machine stopped.\n");
}
static void
prepare_exception(unsigned idx, uint64_t my_handler,
int ist_use_table[static NUM_IST])
{
struct gate_descriptor *fw_idt_e, *loader_idt_e;
fw_idt_e = &((struct gate_descriptor *)fw_idt.rd_base)[idx];
loader_idt_e = &((struct gate_descriptor *)loader_idt.rd_base)[idx];
fw_intr_handlers[idx] = fw_idt_e->gd_looffset +
(fw_idt_e->gd_hioffset << 16);
intercepted[idx] = 1;
ist_use_table[fw_idt_e->gd_ist]++;
loader_idt_e->gd_looffset = my_handler;
loader_idt_e->gd_hioffset = my_handler >> 16;
loader_idt_e->gd_selector = fw_idt_e->gd_selector;
loader_idt_e->gd_ist = 0;
loader_idt_e->gd_type = SDT_SYSIGT;
loader_idt_e->gd_dpl = 0;
loader_idt_e->gd_p = 1;
loader_idt_e->gd_xx = 0;
loader_idt_e->sd_xx1 = 0;
}
#define PREPARE_EXCEPTION(N) \
extern char EXC##N##_handler[]; \
prepare_exception(N, (uintptr_t)EXC##N##_handler, ist_use_table);
static void
free_tables(void)
{
if (lidt_pa != 0) {
BS->FreePages(lidt_pa, EFI_SIZE_TO_PAGES(fw_idt.rd_limit));
lidt_pa = 0;
}
if (exc_stack_pa != 0) {
BS->FreePages(exc_stack_pa, 1);
exc_stack_pa = 0;
}
if (tss_pa != 0 && tss_fw_seg == 0) {
BS->FreePages(tss_pa,
EFI_SIZE_TO_PAGES(sizeof (struct amd64tss)));
tss_pa = 0;
}
if (loader_gdt_pa != 0) {
BS->FreePages(loader_gdt_pa, loader_gdt_pa_size);
loader_gdt_pa = 0;
loader_gdt_pa_size = 0;
}
ist = 0;
loader_tss = 0;
}
static int
efi_setup_tss(struct region_descriptor *gdt, uint32_t loader_tss_idx,
struct amd64tss **tss)
{
EFI_STATUS status;
struct system_segment_descriptor *tss_desc;
tss_desc = (struct system_segment_descriptor *)(gdt->rd_base +
(loader_tss_idx << 3));
status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
EFI_SIZE_TO_PAGES(sizeof (struct amd64tss)), &tss_pa);
if (EFI_ERROR(status)) {
printf("efi_setup_tss: AllocatePages tss error %lu\n",
DECODE_ERROR(status));
return (0);
}
*tss = (struct amd64tss *)tss_pa;
bzero(*tss, sizeof (**tss));
tss_desc->sd_lolimit = sizeof (struct amd64tss);
tss_desc->sd_lobase = tss_pa;
tss_desc->sd_type = SDT_SYSTSS;
tss_desc->sd_dpl = 0;
tss_desc->sd_p = 1;
tss_desc->sd_hilimit = sizeof (struct amd64tss) >> 16;
tss_desc->sd_gran = 0;
tss_desc->sd_hibase = tss_pa >> 24;
tss_desc->sd_xx0 = 0;
tss_desc->sd_xx1 = 0;
tss_desc->sd_mbz = 0;
tss_desc->sd_xx2 = 0;
return (1);
}
static int
efi_redirect_exceptions(void)
{
int ist_use_table[NUM_IST];
struct gate_descriptor *loader_idt_e;
struct system_segment_descriptor *tss_desc, *gdt_desc;
struct amd64tss *tss;
struct region_descriptor *gdt_rd, loader_gdt;
uint32_t i;
EFI_STATUS status;
register_t rfl;
sidt(&fw_idt);
status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
EFI_SIZE_TO_PAGES(fw_idt.rd_limit), &lidt_pa);
if (EFI_ERROR(status)) {
printf("efi_redirect_exceptions: AllocatePages IDT error %lu\n",
DECODE_ERROR(status));
lidt_pa = 0;
return (0);
}
status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, 1,
&exc_stack_pa);
if (EFI_ERROR(status)) {
printf("efi_redirect_exceptions: AllocatePages stk error %lu\n",
DECODE_ERROR(status));
exc_stack_pa = 0;
free_tables();
return (0);
}
loader_idt.rd_limit = fw_idt.rd_limit;
loader_idt.rd_base = lidt_pa;
bcopy((void *)fw_idt.rd_base, (void *)loader_idt.rd_base,
loader_idt.rd_limit);
bzero(ist_use_table, sizeof (ist_use_table));
bzero(fw_intr_handlers, sizeof (fw_intr_handlers));
bzero(intercepted, sizeof (intercepted));
sgdt(&fw_gdt);
tss_fw_seg = read_tr();
gdt_rd = NULL;
if (tss_fw_seg == 0) {
for (i = 2; (i << 3) + sizeof (*gdt_desc) <= fw_gdt.rd_limit;
i += 2) {
gdt_desc = (struct system_segment_descriptor *)(
fw_gdt.rd_base + (i << 3));
if (gdt_desc->sd_type == 0 && gdt_desc->sd_mbz == 0) {
gdt_rd = &fw_gdt;
break;
}
}
if (gdt_rd == NULL) {
if (i >= 8190) {
printf("efi_redirect_exceptions: all slots "
"in gdt are used\n");
free_tables();
return (0);
}
loader_gdt.rd_limit = roundup2(fw_gdt.rd_limit +
sizeof (struct system_segment_descriptor),
sizeof (struct system_segment_descriptor)) - 1;
loader_gdt_pa_size =
EFI_SIZE_TO_PAGES(loader_gdt.rd_limit);
i = (loader_gdt.rd_limit + 1 -
sizeof (struct system_segment_descriptor)) /
sizeof (struct system_segment_descriptor) * 2;
status = BS->AllocatePages(AllocateAnyPages,
EfiLoaderData, loader_gdt_pa_size, &loader_gdt_pa);
if (EFI_ERROR(status)) {
printf("efi_setup_tss: AllocatePages gdt error "
"%lu\n", DECODE_ERROR(status));
loader_gdt_pa = 0;
loader_gdt_pa_size = 0;
free_tables();
return (0);
}
loader_gdt.rd_base = loader_gdt_pa;
bzero((void *)loader_gdt.rd_base, loader_gdt.rd_limit);
bcopy((void *)fw_gdt.rd_base,
(void *)loader_gdt.rd_base, fw_gdt.rd_limit);
gdt_rd = &loader_gdt;
}
loader_tss = i << 3;
if (!efi_setup_tss(gdt_rd, i, &tss)) {
tss_pa = 0;
free_tables();
return (0);
}
} else {
tss_desc = (struct system_segment_descriptor *)((char *)
fw_gdt.rd_base + tss_fw_seg);
if (tss_desc->sd_type != SDT_SYSTSS &&
tss_desc->sd_type != SDT_SYSBSY) {
printf("LTR points to non-TSS descriptor\n");
free_tables();
return (0);
}
tss_pa = tss_desc->sd_lobase + (tss_desc->sd_hibase << 24);
tss = (struct amd64tss *)tss_pa;
tss_desc->sd_type = SDT_SYSTSS;
}
PREPARE_EXCEPTION(0);
PREPARE_EXCEPTION(1);
PREPARE_EXCEPTION(2);
PREPARE_EXCEPTION(3);
PREPARE_EXCEPTION(4);
PREPARE_EXCEPTION(5);
PREPARE_EXCEPTION(6);
PREPARE_EXCEPTION(7);
PREPARE_EXCEPTION(8);
PREPARE_EXCEPTION(9);
PREPARE_EXCEPTION(10);
PREPARE_EXCEPTION(11);
PREPARE_EXCEPTION(12);
PREPARE_EXCEPTION(13);
PREPARE_EXCEPTION(14);
PREPARE_EXCEPTION(16);
PREPARE_EXCEPTION(17);
PREPARE_EXCEPTION(18);
PREPARE_EXCEPTION(19);
PREPARE_EXCEPTION(20);
exc_rsp = exc_stack_pa + PAGE_SIZE -
(6 + 3 ) * 8;
for (ist = 1; ist < NUM_IST; ist++) {
if (ist_use_table[ist] == 0)
break;
}
if (ist == NUM_IST) {
printf("efi_redirect_exceptions: all ISTs used\n");
free_tables();
lidt_pa = 0;
return (0);
}
for (i = 0; i < NUM_EXC; i++) {
loader_idt_e = &((struct gate_descriptor *)loader_idt.
rd_base)[i];
if (intercepted[i])
loader_idt_e->gd_ist = ist;
}
(&(tss->tss_ist1))[ist - 1] = exc_stack_pa + PAGE_SIZE;
rfl = intr_disable();
if (loader_gdt_pa != 0)
bare_lgdt(&loader_gdt);
if (loader_tss != 0)
ltr(loader_tss);
lidt(&loader_idt);
intr_restore(rfl);
return (1);
}
static void
efi_unredirect_exceptions(void)
{
register_t rfl;
if (lidt_pa == 0)
return;
rfl = intr_disable();
if (ist != 0)
(&(((struct amd64tss *)tss_pa)->tss_ist1))[ist - 1] = 0;
if (loader_gdt_pa != 0)
bare_lgdt(&fw_gdt);
if (loader_tss != 0)
ltr(tss_fw_seg);
lidt(&fw_idt);
intr_restore(rfl);
free_tables();
}
static int
command_grab_faults(int argc __unused, char *argv[] __unused)
{
int res;
res = efi_redirect_exceptions();
if (!res)
printf("failed\n");
return (CMD_OK);
}
COMMAND_SET(grab_faults, "grab_faults", "grab faults", command_grab_faults);
static int
command_ungrab_faults(int argc __unused, char *argv[] __unused)
{
efi_unredirect_exceptions();
return (CMD_OK);
}
COMMAND_SET(ungrab_faults, "ungrab_faults", "ungrab faults",
command_ungrab_faults);
static int
command_fault(int argc __unused, char *argv[] __unused)
{
__asm("ud2");
return (CMD_OK);
}
COMMAND_SET(fault, "fault", "generate fault", command_fault);