λ

furix

cute os

settings

arch/x86_64/idt.c

6932 bytes raw

#include <idt.h>

#include <gdt.h>
#include <io.h>
#include <lapic.h>
#include <serial.h>
#include <types.h>

/* interrupt gate, present, ring0, 64-bit */
#define IDT_GATE_INTERRUPT 0x8E

/* IST indices, 1-based; 0 means "use the current stack" */
#define IDT_IST_NONE 0

struct idt_entry {
  uint16_t offset_low;
  uint16_t selector;
  uint8_t ist;
  uint8_t type_attr;
  uint16_t offset_mid;
  uint32_t offset_high;
  uint32_t zero;
} __attribute__((packed));

static struct idt_entry idt[256];
static struct desc_ptr idtr;

/*
 * a second table for application processors, published as idt_ap_pointer.
 *
 * the BSP's table cannot be reused as-is: its gates select 0x08, which is a
 * 32-bit code segment, and a long-mode interrupt gate must name a 64-bit one,
 * so that selector raises #GP instead of delivering the vector.
 *
 * IST indexes used to be a second reason, but are no longer: each AP now loads
 * its own TSS from its trampoline GDT (see gdt_ap_tss_base), so the same IST
 * stacks the BSP uses are safe here too and the gates carry the same indexes.
 *
 * 4 KiB, written once at bring-up, so the duplication costs nothing measurable.
 */
static struct idt_entry idt_ap[256];
struct desc_ptr idt_ap_pointer;

/* one entry per vector; NULL means "report and halt" */
static void (*handlers[256])(void);

/* filled in by isr.S: isr_stub_table[vector] is the stub for that vector */
extern uint64_t isr_stub_table[];

/* the stub table and this struct must agree or every frame is misinterpreted. */
typedef char assert_frame_vector[(offsetof(struct interrupt_frame, vector) == 15 * 8) ? 1 : -1];
typedef char assert_frame_error[(offsetof(struct interrupt_frame, error_code) == 16 * 8) ? 1 : -1];
typedef char assert_frame_rip[(offsetof(struct interrupt_frame, rip) == 17 * 8) ? 1 : -1];
typedef char assert_frame_rflags[(offsetof(struct interrupt_frame, rflags) == 19 * 8) ? 1 : -1];
typedef char assert_frame_rsp[(offsetof(struct interrupt_frame, rsp) == 20 * 8) ? 1 : -1];

static void idt_set_gate(uint16_t vector, uint64_t handler, uint8_t ist) {
  idt[vector].offset_low = (uint16_t)(handler & 0xFFFF);
  idt[vector].selector = 0x08; /* kernel code, from gdt.c */
  idt[vector].ist = ist;
  idt[vector].type_attr = IDT_GATE_INTERRUPT;
  idt[vector].offset_mid = (uint16_t)((handler >> 16) & 0xFFFF);
  idt[vector].offset_high = (uint32_t)(handler >> 32);
  idt[vector].zero = 0;
}

/*
 * the AP twin of idt_set_gate: no IST, and the trampoline's long-mode code
 * selector (0x18) rather than the BSP's 32-bit one. see idt_ap above.
 */
static void idt_ap_set_gate(uint16_t vector, uint64_t handler, uint8_t ist) {
  idt_ap[vector].offset_low = (uint16_t)(handler & 0xFFFF);
  idt_ap[vector].selector = AP_CODE_SELECTOR_64;
  idt_ap[vector].ist = ist;
  idt_ap[vector].type_attr = IDT_GATE_INTERRUPT;
  idt_ap[vector].offset_mid = (uint16_t)((handler >> 16) & 0xFFFF);
  idt_ap[vector].offset_high = (uint32_t)(handler >> 32);
  idt_ap[vector].zero = 0;
}

static void spurious_handler(void);

void idt_set_handler(uint8_t vector, void (*handler)(void)) {
  handlers[vector] = handler;
}

void idt_init(void) {
  uint16_t vector;

  for (vector = 0; vector < 256; vector++) {
    idt_set_gate(vector, isr_stub_table[vector], IDT_IST_NONE);
  }

  /*
   * IST1 for #DF and IST2 for NMI: dedicated stacks, so a fault that
   * overflowed (or corrupted) the current stack is still handled.
   */
  idt[VECTOR_DOUBLE_FAULT].ist = IST_DOUBLE_FAULT;
  idt[VECTOR_NMI].ist = IST_NMI;

  handlers[VECTOR_SPURIOUS] = spurious_handler;

  idtr.limit = (uint16_t)(sizeof(idt) - 1);
  idtr.base = (uint64_t)&idt;
  lidt(&idtr);

  for (vector = 0; vector < 256; vector++) {
    idt_ap_set_gate(vector, isr_stub_table[vector], IDT_IST_NONE);
  }

  /*
   * same IST indexes as the BSP table above. these are only safe now that each
   * AP loads its own TSS before this table (the trampoline LTR precedes its
   * LIDT); an IST gate with no TSS loaded raises #GP, and the #GP that reports
   * it would itself fault, giving a triple fault and a reset that looks
   * exactly like "the AP never started".
   */
  idt_ap[VECTOR_DOUBLE_FAULT].ist = IST_DOUBLE_FAULT;
  idt_ap[VECTOR_NMI].ist = IST_NMI;

  idt_ap_pointer.limit = (uint16_t)(sizeof(idt_ap) - 1);
  idt_ap_pointer.base = (uint64_t)&idt_ap;
}

/*
 * a spurious interrupt was raised with IF already clear and nothing to
 * acknowledge, so there is nothing to do but return. the LAPIC has already
 * been EOI'd by the dispatcher.
 */
static void spurious_handler(void) {
}

static void halt(void) {
  for (;;) {
    cpu_hlt();
  }
}

static void print_error_bits(const char *label, uint64_t error_code) {
  serial_puts("    ");
  serial_puts(label);
  serial_puts(" = ");
  serial_put_hex((error_code >> 16) & 0xFFFF);
  serial_puts("\n");
}

/* default for exceptions: decode what we can, then stop. */
static void report_exception(const struct interrupt_frame *frame) {
  uint64_t vector = frame->vector;
  uint64_t error_code = frame->error_code;

  serial_puts("\nCPU EXCEPTION vector=");
  serial_put_hex(vector);
  serial_puts("\n  err = ");
  serial_put_hex(error_code);
  serial_puts("\n");

  if (vector == VECTOR_PAGE_FAULT) {
    serial_puts("  page fault at ");
    serial_put_hex(read_cr2());
    serial_puts("\n  cause: ");
    serial_puts((error_code & 0x1) ? "present" : "not-present");
    serial_puts(", ");
    serial_puts((error_code & 0x2) ? "write" : "read");
    serial_puts(", ");
    serial_puts((error_code & 0x4) ? "user" : "supervisor");
    serial_puts(", ");
    serial_puts((error_code & 0x10) ? "instruction fetch" : "data access");
    serial_puts("\n");
  } else {
    /* #DF / #GP / #SS style: bit0 IF, bit1 OTS, bit2 GPF, bit3 IDT,
       bits 4..15 selector index, bit 15 RPL */
    serial_puts((error_code & 0x1) ? "  [IF]\n" : "");
    serial_puts((error_code & 0x2) ? "  [OTS]\n" : "");
    serial_puts((error_code & 0x4) ? "  [GPF]\n" : "");
    serial_puts((error_code & 0x8) ? "  [IDT]\n" : "");
    print_error_bits("selector", error_code);
  }

  serial_puts("  rip = ");
  serial_put_hex(frame->rip);
  serial_puts("\n  halted.\n");
}

/*
 * single entry point for every vector. the LAPIC is acked before dispatch so a
 * handler that itself takes a long time cannot block a second interrupt on
 * the same vector.
 */
void interrupt_dispatch(const struct interrupt_frame *frame) {
  uint8_t vector = (uint8_t)frame->vector;

  if (vector != VECTOR_PAGE_FAULT) {
    lapic_eoi();
  }

  if (handlers[vector] != NULL) {
    handlers[vector]();
    return;
  }

  /* exceptions 0..31 are unrecoverable here; no fixup path exists yet */
  if (vector < 32) {
    report_exception(frame);
    halt();
  }

  serial_puts("\nUNEXPECTED INTERRUPT vector=");
  serial_put_hex(vector);
  serial_puts(" rip=");
  serial_put_hex(frame->rip);
  serial_puts(" apic_id=");
  serial_put_hex(lapic_id());
  serial_puts("\n");

  halt();
}