//! 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");
