const arch = @import("../root.zig"); const std = @import("std"); const interrupts = arch.interrupts; const StandardGdt = arch.structures.gdt.StandardGdt; // The actual IDT memory const entry_count = 256; export var interrupt_descriptor_table: [entry_count]Entry = undefined; // Pointers to the actual ISRs which the global interrupt handler call // Each IDT entry pushes the interrupt number then calls the global // handler, which pushes more information then calls the user // defined handler. Use common sense and don't return from an exception // which shouldn't be returned from. pub const CallConv: std.builtin.CallingConvention = .{ .x86_64_sysv = .{} }; pub fn InterruptHandler(comptime E: type) type { return *const fn (*InterruptFrame(E)) callconv(CallConv) void; } pub export var defined_handlers: [entry_count]InterruptHandler(u64) = undefined; // The actual handlers with addresses in the IDT. const ActualHandler = *const fn () callconv(.naked) void; const actual_handlers: [entry_count]ActualHandler = blk: { @setEvalBranchQuota(100000000); var ret: [entry_count]ActualHandler = undefined; for (0..256) |i| { ret[i] = make_actual_handler(.{ .interrupt = i }); } break :blk ret; }; fn make_actual_handler(comptime interrupt: Interrupt) ActualHandler { var asm_code: []const u8 = ""; // Make the stack consistent if (!interrupt.has_error_code()) { asm_code = asm_code ++ "pushq $0\n"; } // Push the interrupt number asm_code = asm_code ++ std.fmt.comptimePrint("pushq ${}\n", .{interrupt.interrupt}); // Jump to the common interrupt handler code asm_code = asm_code ++ "jmp _int_handler_common"; const code = asm_code; const tmp_struct = struct { fn actual_handler() callconv(.naked) void { asm volatile (code); } }; return tmp_struct.actual_handler; } // The global assembly for the common interrupt handler comptime { // Construct the push and pop instructions from SavedRegisters var push_instrs: []const u8 = "\n"; var pop_instrs: []const u8 = "\n"; const saved_regs = @typeInfo(SavedRegisters).@"struct".fields; for (saved_regs) |saved_reg| { // We must prepend to push because pushes are basically // building the struct in reverse order, so reverse the effects push_instrs = "\n pushq %" ++ saved_reg.name ++ push_instrs; // Of course, pop in the opposite order as push pop_instrs = pop_instrs ++ "popq %" ++ saved_reg.name ++ "\n"; } asm ( \\ .global _int_handler_common \\ .type _int_handler_common, @function \\ _int_handler_common: // Push the general purpose registers and then CR3 ++ push_instrs ++ \\ mov %cr3, %rax \\ pushq %rax // Now, rsp points to the start of InterruptFrame. Read int_num and call into an offset of // the defined handlers list after setting the first arg to the created structure. ++ std.fmt.comptimePrint("\nmov {}(%rsp), %rcx\n", .{@offsetOf(InterruptFrame(u64), "int_num")}) ++ \\ mov %rsp, %rdi \\ callq *defined_handlers(, %rcx, 8) // Skip the stack all the way down to the general purpose // registers, then pop all of them // TODO: restore CR3?? ++ std.fmt.comptimePrint("\nadd ${}, %rsp\n", .{@offsetOf(InterruptFrame(u64), "regs")}) ++ pop_instrs ++ // Skip the interrupt number and error code and iretq \\ add $16, %rsp \\ iretq ); } /// IDT Register const Idtr = packed struct(u80) { limit: u16, addr: u64, /// Load the IDT Register pub fn load(self: *const Idtr) void { asm volatile ("lidt (%[idtr_addr])" : : [idtr_addr] "r" (self), ); } }; // A raw IDT entry const Entry = extern struct { func_low: u16, gdt_selector: u16, options: Options, func_mid: u16, func_high: u32, _reserved0: u32 = 0, pub const Options = packed struct(u16) { ist_index: u3, _reserved0: u5 = 0, disable_interrupts: bool = true, // type: Type, must_be_one: u3 = 0b111, must_be_zero: u1 = 0, dpl: u2, present: bool, }; pub const Owner = enum { kernel, user, }; const Self = @This(); pub fn init(func_ptr: usize, dpl: u2, ist: u3) Entry { // _ = typ; return .{ .func_low = @truncate(func_ptr), .gdt_selector = StandardGdt.selectors.kernel_code, .options = .{ .ist_index = ist, .dpl = dpl, .present = true, }, .func_mid = @truncate(func_ptr >> 16), .func_high = @truncate(func_ptr >> 32), }; } // Changes the address without changing anything else pub fn set_func(self: *Self, ptr: usize) void { self.func_low = @truncate(ptr); self.func_mid = @truncate(ptr >> 16); self.func_high = @truncate(ptr >> 32); } }; /// A selector error code indexing into the GDT, IDT, or LDT. /// Used in a general protection fault for example. pub const SelectorErrorCode = packed struct(u64) { external: bool, interrupt: bool, // Only valid if not interrupt type: enum(u1) { gdt = 0, ldt = 1, }, idx: u13, _reserved0: u48 = 0, pub const Target = union(enum) { interrupt: Interrupt, gdt_sel: u16, ldt_sel: u13, }; const Self = @This(); pub fn parse(self: Self) Target { return switch (self.interrupt) { true => .{ .interrupt = .{ .interrupt = @truncate(self.idx) } }, false => switch (self.type) { .gdt => .{ .gdt_sel = self.idx }, .ldt => .{ .ldt_sel = self.idx }, }, }; } }; /// List of the general built in exceptions pub const Exception = enum(u8) { divide_error = 0x00, debug_exeption = 0x01, non_maskable_interrupt = 0x02, breakpoint = 0x03, overflow = 0x04, bound_range_exceeded = 0x05, invalid_opcode = 0x06, device_not_available = 0x07, double_fault = 0x08, // _coprocessor_segment_overrun = 0x09, invalid_tss = 0x0a, segment_not_present = 0x0b, stack_segment_fault = 0x0c, general_protection_fault = 0x0d, page_fault = 0x0e, // _reserved0 = 0x0f, x87_floating_point = 0x10, alignment_check = 0x11, machine_check = 0x12, simd_floating_point = 0x13, virtualization = 0x14, control_protection = 0x15, hypervisor = 0x1c, vmm = 0x1d, security_fault = 0x1e, _, fn has_error_code(self: Exception) bool { return switch (self) { .double_fault, .invalid_tss, .segment_not_present, .general_protection_fault, .page_fault, .security_fault => true, else => false, }; } }; pub const Interrupt = packed union { exception: Exception, interrupt: u8, pub fn has_error_code(self: Interrupt) bool { return self.exception.has_error_code(); } }; /// Basically all the general purpose registers except rsp, /// because RSP is already in the InterruptFrame. Ordered /// in the order of the X.Reg from the instruction encoding lol pub const SavedRegisters = extern struct { rax: u64, rcx: u64, rdx: u64, rbx: u64, // rsp: u64, rbp: u64, rsi: u64, rdi: u64, r8: u64, r9: u64, r10: u64, r11: u64, r12: u64, r13: u64, r14: u64, r15: u64, pub const default = std.mem.zeroes(SavedRegisters); }; /// The Interrupt frame which we help generate pub fn InterruptFrame(comptime ErrorCode: type) type { if (@bitSizeOf(ErrorCode) != 64) { @compileError("ErrorCode for InterruptFrame must be exactly 64 bits!"); } return extern struct { // CR3 cr3: u64, // All the general purpose registers regs: SavedRegisters align(8), // The interrupt number int_num: Interrupt align(8), // Pushed by the CPU (error_code may be pushed by us) error_code: ErrorCode, rip: u64, cs: u16 align(8), eflags: u64, rsp: u64, ss: u16 align(8), pub fn normalize(self: *InterruptFrame(ErrorCode)) *InterruptFrame(u64) { return @ptrCast(self); } }; } // Initialize the IDT with the default unhandled exception pub fn init() void { // Set every IDT entry to the corresponding ActualHandler for (0..entry_count) |i| { const actual_handler = @intFromPtr(actual_handlers[i]); interrupt_descriptor_table[i] = Entry.init(actual_handler, 0, 0); } // Now, set every defined handler to the default one @memset(&defined_handlers, arch.interrupts.unhandled_interrupt); // Finally, load the idt load(); } pub fn load() void { const idtr: Idtr = .{ .addr = @intFromPtr(&interrupt_descriptor_table), .limit = 0xFFF, }; idtr.load(); } pub fn add_handler(interrupt: Interrupt, comptime E: type, handler: InterruptHandler(E), dpl: u2, ist: u3) void { // Modify the type, dpl, and ist in place var tmp = interrupt_descriptor_table[interrupt.interrupt]; tmp.options.dpl = dpl; tmp.options.ist_index = ist; interrupt_descriptor_table[interrupt.interrupt] = tmp; // Add the InterruptHandler defined_handlers[interrupt.interrupt] = @ptrCast(handler); }