← black metal kernel — series index

// black metal kernel — episode 03 of 08

interrupt handling:
when hardware screams at you

// kernel programming in rust — zero-cost abstractions — no gc — no mercy

IDT naked fn iretq x86-64 exceptions

// 03 — interrupt handling: context preservation

an interrupt is sudden death for a careless kernel. the cpu forcibly halts the current instruction stream, pushes a minimal state frame onto the stack, and transfers execution to your handler. if you do not perfectly save and restore the interrupted state before returning, you corrupt the target thread permanently. rust's safety guarantees cannot prevent you from returning with the wrong register values. `naked` functions ensure the compiler does not emit standard C ABI prologues or epilogues, giving you raw control over the exact stack frame.

handling hardware exceptions correctly requires mapping the cpu's push mechanisms directly to structural types in rust using `repr(C)`. there is no room for padding errors. the hardware expects the Interrupt Descriptor Table (IDT) to be bit-perfect. every handler must acknowledge the interrupt using an End-Of-Interrupt (EOI) signal, or the local APIC will never interrupt that core again.

#![feature(naked_functions)]

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct BlackInterruptFrame {
    pub black_rip:    u64,
    pub black_cs:     u64,
    pub black_rflags: u64,
    pub black_rsp:    u64,
    pub black_ss:     u64,
}

#[naked]
pub unsafe extern "C" fn black_page_fault_handler() {
    core::arch::asm!(
        "push rbp",
        "push r15",
        "push r14",
        "push r13",
        "push r12",
        "push r11",
        "push r10",
        "push r9",
        "push r8",
        "push rdi",
        "push rsi",
        "push rdx",
        "push rcx",
        "push rbx",
        "push rax",
        "mov rdi, rsp",
        "call black_page_fault_router",
        "pop rax",
        "pop rbx",
        "pop rcx",
        "pop rdx",
        "pop rsi",
        "pop rdi",
        "pop r8",
        "pop r9",
        "pop r10",
        "pop r11",
        "pop r12",
        "pop r13",
        "pop r14",
        "pop r15",
        "pop rbp",
        "add rsp, 8",
        "iretq",
        options(noreturn)
    );
}

#[no_mangle]
extern "C" fn black_page_fault_router(black_frame: *const BlackInterruptFrame) {
    let black_cr2: u64;
    unsafe {
        core::arch::asm!("mov {}, cr2", out(reg) black_cr2);
    }
    if black_cr2 == 0 {
        panic!("black ptr fault");
    }
}
// expanded — naked functions, calling conventions, and iretq

when a hardware interrupt occurs on x86-64, the cpu unconditionally pushes the instruction pointer (`RIP`), code segment (`CS`), and flags (`RFLAGS`) onto the stack. if a privilege level change occurred, it also pushes the stack pointer (`RSP`) and stack segment (`SS`). this stack frame corresponds exactly to our BlackInterruptFrame struct.

however, normal rust functions cannot be directly used as interrupt handlers because rust functions adhere to standard calling conventions (like the C ABI) which assume the processor is in a normal state. they generate "prologue" code that modifies the stack pointer before executing the function logic. to prevent this, we mark the handler as #[naked]. a naked function contains absolutely no compiler-generated setup or teardown. it is a raw sequence of assembly instructions.

in the assembly block, we manually save all general-purpose registers to the stack. this preserves the execution state of whatever code was abruptly halted by the hardware. we then execute "mov rdi, rsp". in the System V AMD64 ABI, the first argument to a function is passed in the `RDI` register. by moving the stack pointer into `RDI`, we are passing a pointer to the saved register stack as the first argument to black_page_fault_router, which receives it cleanly as `black_frame`.

finally, the iretq instruction is executed. it does not just return; it forcefully pops the RIP, CS, and RFLAGS that the cpu pushed originally, atomically restoring both the instruction pointer and the privilege level. a standard `ret` instruction would crash the machine. the cpu resumes exactly as if nothing happened.

// 03 / 08 — black_ptr owns its truth — BLACK0X80