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}