Microkernel thing OS experiment (Zig ⚡)
1const arch = @import("../root.zig"); 2const std = @import("std"); 3const interrupts = arch.interrupts; 4const StandardGdt = arch.structures.gdt.StandardGdt; 5 6// The actual IDT memory 7const entry_count = 256; 8export var interrupt_descriptor_table: [entry_count]Entry = undefined; 9 10// Pointers to the actual ISRs which the global interrupt handler call 11// Each IDT entry pushes the interrupt number then calls the global 12// handler, which pushes more information then calls the user 13// defined handler. Use common sense and don't return from an exception 14// which shouldn't be returned from. 15const DefinedHandler = *const fn (*InterruptFrame(u64)) callconv(.{ .x86_64_sysv = .{} }) void; 16pub export var defined_handlers: [entry_count]DefinedHandler = undefined; 17 18// The actual handlers with addresses in the IDT. 19const ActualHandler = *const fn () callconv(.naked) void; 20const actual_handlers: [entry_count]ActualHandler = blk: { 21 @setEvalBranchQuota(100000000); 22 var ret: [entry_count]ActualHandler = undefined; 23 for (0..256) |i| { 24 ret[i] = make_actual_handler(.{ .interrupt = i }); 25 } 26 break :blk ret; 27}; 28 29fn make_actual_handler(comptime interrupt: Interrupt) ActualHandler { 30 var asm_code: []const u8 = ""; 31 // Make the stack consistent 32 if (!interrupt.has_error_code()) { 33 asm_code = asm_code ++ "pushq $0\n"; 34 } 35 // Push the interrupt number 36 asm_code = asm_code ++ std.fmt.comptimePrint("pushq ${}\n", .{interrupt.interrupt}); 37 // Jump to the common interrupt handler code 38 asm_code = asm_code ++ "jmp _int_handler_common"; 39 const code = asm_code; 40 41 const tmp_struct = struct { 42 fn actual_handler() callconv(.naked) void { 43 asm volatile (code); 44 } 45 }; 46 return tmp_struct.actual_handler; 47} 48 49// The global assembly for the common interrupt handler 50comptime { 51 // Construct the push and pop instructions from SavedRegisters 52 var push_instrs: []const u8 = "\n"; 53 var pop_instrs: []const u8 = "\n"; 54 55 const saved_regs = @typeInfo(SavedRegisters).@"struct".fields; 56 for (saved_regs) |saved_reg| { 57 // We must prepend to push because pushes are basically 58 // building the struct in reverse order, so reverse the effects 59 push_instrs = "\n pushq %" ++ saved_reg.name ++ push_instrs; 60 // Of course, pop in the opposite order as push 61 pop_instrs = pop_instrs ++ "popq %" ++ saved_reg.name ++ "\n"; 62 } 63 64 asm ( 65 \\ .global _int_handler_common 66 \\ .type _int_handler_common, @function 67 \\ _int_handler_common: 68 69 // Push the general purpose registers and then CR3 70 ++ push_instrs ++ 71 \\ mov %cr3, %rax 72 \\ pushq %rax 73 74 // Now, rsp points to the start of InterruptFrame. Read int_num and call into an offset of 75 // the defined handlers list after setting the first arg to the created structure. 76 ++ std.fmt.comptimePrint("\nmov {}(%rsp), %rcx\n", .{@offsetOf(InterruptFrame(u64), "int_num")}) ++ 77 \\ mov %rsp, %rdi 78 \\ callq *defined_handlers(, %rcx, 8) 79 80 // Skip the stack all the way down to the general purpose 81 // registers, then pop all of them 82 // TODO: restore CR3?? 83 ++ std.fmt.comptimePrint("\nadd ${}, %rsp\n", .{@offsetOf(InterruptFrame(u64), "regs")}) ++ pop_instrs ++ 84 // Skip the interrupt number and error code and iretq 85 \\ add $16, %rsp 86 \\ iretq 87 ); 88} 89 90/// IDT Register 91const Idtr = packed struct(u80) { 92 limit: u16, 93 addr: u64, 94 95 /// Load the IDT Register 96 pub fn load(self: *const Idtr) void { 97 asm volatile ("lidt (%[idtr_addr])" 98 : 99 : [idtr_addr] "r" (self), 100 ); 101 } 102}; 103 104// A raw IDT entry 105const Entry = extern struct { 106 func_low: u16, 107 gdt_selector: u16, 108 options: Options, 109 func_mid: u16, 110 func_high: u32, 111 _reserved0: u32 = 0, 112 113 pub const Options = packed struct(u16) { 114 ist_index: u3, 115 _reserved0: u5 = 0, 116 disable_interrupts: bool = true, 117 // type: Type, 118 must_be_one: u3 = 0b111, 119 must_be_zero: u1 = 0, 120 dpl: u2, 121 present: bool, 122 }; 123 124 pub const Owner = enum { 125 kernel, 126 user, 127 }; 128 129 const Self = @This(); 130 pub fn init(func_ptr: usize, dpl: u2, ist: u3) Entry { 131 // _ = typ; 132 return .{ 133 .func_low = @truncate(func_ptr), 134 .gdt_selector = StandardGdt.selectors.kernel_code, 135 .options = .{ 136 .ist_index = ist, 137 .dpl = dpl, 138 .present = true, 139 }, 140 .func_mid = @truncate(func_ptr >> 16), 141 .func_high = @truncate(func_ptr >> 32), 142 }; 143 } 144 145 // Changes the address without changing anything else 146 pub fn set_func(self: *Self, ptr: usize) void { 147 self.func_low = @truncate(ptr); 148 self.func_mid = @truncate(ptr >> 16); 149 self.func_high = @truncate(ptr >> 32); 150 } 151}; 152 153/// A selector error code indexing into the GDT, IDT, or LDT. 154/// Used in a general protection fault for example. 155pub const SelectorErrorCode = packed struct(u64) { 156 external: bool, 157 interrupt: bool, 158 // Only valid if not interrupt 159 type: enum(u1) { 160 gdt = 0, 161 ldt = 1, 162 }, 163 idx: u13, 164 _reserved0: u48 = 0, 165 166 pub const Target = union(enum) { 167 interrupt: Interrupt, 168 gdt_sel: u16, 169 ldt_sel: u13, 170 }; 171 172 const Self = @This(); 173 pub fn parse(self: Self) Target { 174 return switch (self.interrupt) { 175 true => .{ .interrupt = .{ .interrupt = @truncate(self.idx) } }, 176 false => switch (self.type) { 177 .gdt => .{ .gdt_sel = self.idx }, 178 .ldt => .{ .ldt_sel = self.idx }, 179 }, 180 }; 181 } 182}; 183 184/// List of the general built in exceptions 185pub const Exception = enum(u8) { 186 divide_error = 0x00, 187 debug_exeption = 0x01, 188 non_maskable_interrupt = 0x02, 189 breakpoint = 0x03, 190 overflow = 0x04, 191 bound_range_exceeded = 0x05, 192 invalid_opcode = 0x06, 193 device_not_available = 0x07, 194 double_fault = 0x08, 195 // _coprocessor_segment_overrun = 0x09, 196 invalid_tss = 0x0a, 197 segment_not_present = 0x0b, 198 stack_segment_fault = 0x0c, 199 general_protection_fault = 0x0d, 200 page_fault = 0x0e, 201 // _reserved0 = 0x0f, 202 x87_floating_point = 0x10, 203 alignment_check = 0x11, 204 machine_check = 0x12, 205 simd_floating_point = 0x13, 206 virtualization = 0x14, 207 control_protection = 0x15, 208 hypervisor = 0x1c, 209 vmm = 0x1d, 210 security_fault = 0x1e, 211 _, 212 213 fn has_error_code(self: Exception) bool { 214 return switch (self) { 215 .double_fault, .invalid_tss, .segment_not_present, .general_protection_fault, .page_fault, .security_fault => true, 216 else => false, 217 }; 218 } 219}; 220 221pub const Interrupt = packed union { 222 exception: Exception, 223 interrupt: u8, 224 225 pub fn has_error_code(self: Interrupt) bool { 226 return self.exception.has_error_code(); 227 } 228}; 229 230/// Basically all the general purpose registers except rsp, 231/// because RSP is already in the InterruptFrame. Ordered 232/// in the order of the X.Reg from the instruction encoding lol 233pub const SavedRegisters = extern struct { 234 rax: u64, 235 rcx: u64, 236 rdx: u64, 237 rbx: u64, 238 // rsp: u64, 239 rbp: u64, 240 rsi: u64, 241 rdi: u64, 242 r8: u64, 243 r9: u64, 244 r10: u64, 245 r11: u64, 246 r12: u64, 247 r13: u64, 248 r14: u64, 249 r15: u64, 250}; 251 252/// The Interrupt frame which we help generate 253pub fn InterruptFrame(comptime ErrorCode: type) type { 254 if (@bitSizeOf(ErrorCode) != 64) { 255 @compileError("ErrorCode for InterruptFrame must be exactly 64 bits!"); 256 } 257 return extern struct { 258 // CR3 259 cr3: u64, 260 // All the general purpose registers 261 regs: SavedRegisters align(8), 262 // The interrupt number 263 int_num: Interrupt align(8), 264 // Pushed by the CPU (error_code may be pushed by us) 265 error_code: ErrorCode, 266 rip: u64, 267 cs: u16 align(8), 268 eflags: u64, 269 rsp: u64, 270 ss: u16 align(8), 271 }; 272} 273 274// Initialize the IDT with the default unhandled exception 275pub fn init() void { 276 // Set every IDT entry to the corresponding ActualHandler 277 for (0..entry_count) |i| { 278 const actual_handler = @intFromPtr(actual_handlers[i]); 279 interrupt_descriptor_table[i] = Entry.init(actual_handler, 3, 0); 280 } 281 // Now, set every defined handler to the default one 282 @memset(&defined_handlers, arch.interrupts.unhandled_interrupt); 283 284 // Finally, load the idt 285 load(); 286 287 add_handler(.{ .exception = .breakpoint }, arch.interrupts.breakpoint, 3, 0); 288 add_handler(.{ .exception = .double_fault }, arch.interrupts.double_fault, 3, 0); 289 add_handler(.{ .exception = .general_protection_fault }, arch.interrupts.general_protection_fault, 3, 0); 290 add_handler(.{ .exception = .page_fault }, arch.mm.paging.page_fault_handler, 3, 0); 291} 292 293pub fn load() void { 294 const idtr: Idtr = .{ 295 .addr = @intFromPtr(&interrupt_descriptor_table), 296 .limit = 0xFFF, 297 }; 298 idtr.load(); 299} 300 301pub fn add_handler(interrupt: Interrupt, handler: anytype, dpl: u2, ist: u3) void { 302 // Modify the type, dpl, and ist in place 303 var tmp = interrupt_descriptor_table[interrupt.interrupt]; 304 tmp.options.dpl = dpl; 305 tmp.options.ist_index = ist; 306 interrupt_descriptor_table[interrupt.interrupt] = tmp; 307 308 // Add the DefinedHandler 309 defined_handlers[interrupt.interrupt] = @ptrCast(&handler); 310}