//! Interrupt Descriptor Table, CPU-exception handlers, and device-interrupt //! dispatch. Without this, any fault (a stray pointer, a bad page-table entry) //! triple-faults and silently resets the machine. With it, the CPU vectors into //! our stubs, which capture the register state and hand it to a dispatcher. //! //! Vectors split in two: 0-31 are CPU exceptions, handed to the `on_fault` hook //! and never returned from (the kernel's handler kills a faulting user process //! and reschedules, or halts the core for a kernel-mode fault); 32+ are device //! interrupts (a registered handler runs, the APIC is acknowledged, and we //! return to the interrupted code). const gdt = @import("gdt.zig"); const tss = @import("tss.zig"); /// Highest vector we install a gate/stub for (exceptions 0-31 plus the device /// range 32-47, which covers the timer and the spurious vector). const gate_count = 48; /// A device-interrupt handler. It doesn't get the trap frame (a timer or keyboard /// handler doesn't need the interrupted registers); add that if one ever does. pub const Handler = *const fn () void; var handlers = [_]?Handler{null} ** 256; /// Register `handler` for a device-interrupt `vector` (>= 32). pub fn setHandler(vector: usize, handler: Handler) void { handlers[vector] = handler; } /// The ring-3 system_call gate's vector (`int $0x80`, the classic choice — well away /// from the device range) and its handler. Unlike device handlers, a system_call /// handler gets the (mutable) trap frame: it reads its arguments from the saved /// user registers and writes rax as the return value, which isr_common then /// restores into the user context. pub const system_call_vector = 128; var system_call_handler: ?*const fn (*CpuState) void = null; pub fn setSystemCallHandler(handler: *const fn (*CpuState) void) void { system_call_handler = handler; } /// The register + trap frame the ISR stubs build on the stack, laid out so the /// lowest address (where RSP points when we call the handler) is the first field. /// See the push order in `isrCommon` below. pub const CpuState = extern struct { r15: u64, r14: u64, r13: u64, r12: u64, r11: u64, r10: u64, r9: u64, r8: u64, rbp: u64, rdi: u64, rsi: u64, rdx: u64, rcx: u64, rbx: u64, rax: u64, vector: u64, // pushed by the per-vector stub error_code: u64, // real one from the CPU, or 0 pushed by the stub rip: u64, // from here down: pushed by the CPU on entry cs: u64, rflags: u64, rsp: u64, ss: u64, }; /// Where a fault is reported. The kernel overrides this (see setFaultHandler) with /// something that prints to the console; until then, just stop. pub var on_fault: *const fn (*const CpuState) noreturn = defaultFault; fn defaultFault(_: *const CpuState) noreturn { while (true) asm volatile ("hlt"); } /// Names for the 32 defined exception vectors, for readable output. const names = [_][]const u8{ "divide error", "debug", "NMI", "breakpoint", "overflow", "bound range exceeded", "invalid opcode", "device not available", "double fault", "coprocessor segment overrun", "invalid TSS", "segment not present", "stack-segment fault", "general protection fault", "page fault", "reserved (15)", "x87 floating-point", "alignment check", "machine check", "SIMD floating-point", "virtualization", "control protection", "reserved (22)", "reserved (23)", "reserved (24)", "reserved (25)", "reserved (26)", "reserved (27)", "hypervisor injection", "VMM communication", "security exception", "reserved (31)", }; pub fn vectorName(vector: u64) []const u8 { return if (vector < names.len) names[vector] else "unknown"; } /// A 64-bit IDT gate descriptor (16 bytes). const Gate = packed struct { offset_low: u16, selector: u16, ist: u8, // interrupt-stack-table index; 0 = use the current stack flags: u8, // present, DPL, gate type offset_mid: u16, offset_high: u32, reserved: u32 = 0, }; var idt = [_]Gate{std.mem.zeroes(Gate)} ** 256; const Descriptor = packed struct { limit: u16, base: u64, }; /// Loads the IDT (`lidt`). Defined in isr.s. extern fn idt_flush(descriptor: *const Descriptor) callconv(.c) void; fn setGate(vector: usize, handler: u64) void { idt[vector] = .{ .offset_low = @truncate(handler), .selector = gdt.kernel_code, .ist = 0, .flags = 0x8E, // present, ring 0, 64-bit interrupt gate .offset_mid = @truncate(handler >> 16), .offset_high = @truncate(handler >> 32), }; } /// Point every installed vector at its stub (isr.s) and load the IDT. pub fn init() void { @setEvalBranchQuota(20000); // comptimePrint across all the gates adds up inline for (0..gate_count) |vector| { const stub = @extern(*const anyopaque, .{ .name = std.fmt.comptimePrint("isr{d}", .{vector}) }); setGate(vector, @intFromPtr(stub)); } // Run the double-fault handler (vector 8) on IST1: a #DF usually means the // current stack is unusable, so it needs a guaranteed-good one. See tss.zig. idt[8].ist = tss.double_fault_ist; // The system_call gate. Installed outside the 0..gate_count loop (stubs 48-127 // don't exist) and with DPL 3 — without it, `int $0x80` from ring 3 is a // #GP. An interrupt gate (not trap): IF is cleared for the handler, which // the ring-3 exit path relies on. const system_call_stub = @extern(*const anyopaque, .{ .name = "isr128" }); setGate(system_call_vector, @intFromPtr(system_call_stub)); idt[system_call_vector].flags = 0xEE; // present, DPL 3, 64-bit interrupt gate loadOnThisCpu(); } /// Load the (shared, already-populated) IDT on the current core. The gate table is /// read-only after `init`, so every core points its IDTR at the same one. Called by /// the BSP via `init` and by each AP during bring-up. pub fn loadOnThisCpu() void { const descriptor = Descriptor{ .limit = @sizeOf(@TypeOf(idt)) - 1, .base = @intFromPtr(&idt), }; idt_flush(&descriptor); } /// Called by isr_common (isr.s) with a pointer to the trap frame. Exported so the /// assembly stubs can `call` it by name. Exceptions never return here (on_fault /// kills the faulting process or halts the core); device interrupts run their /// handler, get acknowledged, and return. export fn interruptDispatch(state: *CpuState) callconv(.c) void { if (state.vector < 32) { on_fault(state); // CPU exception — never returns } else if (state.vector == system_call_vector) { // Software interrupt from ring 3 — no LAPIC ISR bit is set, so no EOI. if (system_call_handler) |handler| handler(state); } else if (handlers[state.vector]) |handler| { // The handler owns its EOI. It used to be issued here, before the call — // correct for the LAPIC timer, but impossible to reconcile with a // level-triggered device line, which must be **masked at the I/O APIC // before** it is acknowledged or it redelivers instantly and storms // (the driver that would quiet it lives in ring 3 and hasn't run yet). // Only the handler knows which discipline its source needs, so only the // handler can sequence it. See apic.timerTick and irq.dispatch. handler(); } // else: spurious/unhandled device interrupt — don't acknowledge it } const std = @import("std");