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