hachem.wtf
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-09-10 08:23:52 +0200
committerhachem <im@hachem.wtf>2026-09-10 08:23:52 +0200
commit8fc9aa8d84486c561776cda8f94e4dfa897a5d4d (patch)
tree76d7fc5df77b80a1387e5c5fee88d5fda8315bcc
parentba4714f311e58b59043797ebd5c17caeccc74050 (diff)
feat: gdt/idt initialization
-rw-r--r--boot/boot.zig6
-rw-r--r--boot/gdt.zig43
-rw-r--r--boot/idt.zig167
3 files changed, 216 insertions, 0 deletions
diff --git a/boot/boot.zig b/boot/boot.zig
index 2303409..6cc3d12 100644
--- a/boot/boot.zig
+++ b/boot/boot.zig
@@ -2,6 +2,8 @@ const std = @import("std");
const limine = @import("limine.zig");
const serial = @import("serial.zig");
const framebuffer = @import("framebuffer.zig");
+const gdt = @import("gdt.zig");
+const idt = @import("idt.zig");
// We need a panic handler otherwise zig won't be happy,
// std usually formats a message through std.Io.Writer which
@@ -27,6 +29,10 @@ export fn _start() callconv(.c) noreturn {
serial.init();
serial.write("if you see this, it means revel didn't shit the bed\r\n");
+ gdt.load();
+ idt.init();
+ serial.write("gdt + idt loaded\r\n");
+
if (framebuffer.get()) |screen| {
// XOR texture
var y: usize = 0;
diff --git a/boot/gdt.zig b/boot/gdt.zig
new file mode 100644
index 0000000..c428e2c
--- /dev/null
+++ b/boot/gdt.zig
@@ -0,0 +1,43 @@
+// we have to make our own gdt even though limine gives us cause the
+// spec says that the one they provide is only a temp one.
+// in long mode, segment base/limits are ignrored so all we really encode
+// is the access byte and the long mode bit.
+// see: https://wiki.osdev.org/Global_Descriptor_Table
+
+pub const KERNEL_CODE: u16 = 0x08;
+pub const KERNEL_DATA: u16 = 0x10;
+
+var gdt = [_]u64{
+ 0x0000000000000000, // null
+ 0x00209A0000000000, // 0x08 kernel code: present, ring 0, executable, L=1
+ 0x0000920000000000, // 0x10 kernel data: present, ring 0, writable
+};
+
+const Gdtr = packed struct {
+ limit: u16,
+ base: u64,
+};
+
+var gdtr: Gdtr = undefined;
+
+// load the GDT, then reload CS (via a far return) and the data segments so the
+// CPU actually uses our descriptors instead of Limine's shit one.
+pub fn load() void {
+ gdtr = .{ .limit = @sizeOf(@TypeOf(gdt)) - 1, .base = @intFromPtr(&gdt) };
+ asm volatile (
+ \\ lgdt (%[gdtr])
+ \\ pushq $0x08
+ \\ leaq 1f(%rip), %rax
+ \\ pushq %rax
+ \\ lretq
+ \\ 1:
+ \\ movw $0x10, %ax
+ \\ movw %ax, %ds
+ \\ movw %ax, %es
+ \\ movw %ax, %fs
+ \\ movw %ax, %gs
+ \\ movw %ax, %ss
+ :
+ : [gdtr] "r" (&gdtr),
+ : .{ .rax = true, .memory = true });
+}
diff --git a/boot/idt.zig b/boot/idt.zig
new file mode 100644
index 0000000..adff4b3
--- /dev/null
+++ b/boot/idt.zig
@@ -0,0 +1,167 @@
+// the idt is for the 32 cpu excpetion vectors, if i dont implement this,
+// any fault triple-falts and the machine just fucking dies for some reason
+// this just maps interupts and halts ig
+//
+// see: https://wiki.osdev.org/Interrupt_Descriptor_Table
+
+const gdt = @import("gdt.zig");
+const serial = @import("serial.zig");
+
+// 64bit interrupt gate
+const Gate = packed struct {
+ offset_low: u16,
+ selector: u16,
+ ist: u8,
+ type_attr: u8, // 0x8E = present, ring 0, 64-bit interrupt gate
+ offset_mid: u16,
+ offset_high: u32,
+ reserved: u32,
+};
+
+const Idtr = packed struct {
+ limit: u16,
+ base: u64,
+};
+
+var idt = [_]Gate{@bitCast(@as(u128, 0))} ** 256;
+var idtr: Idtr = undefined;
+
+// what the common stub leaves on the stack, low address -> high.
+// `mov rdi, rsp` hands the handler a pointer to this
+const Frame = extern struct {
+ r15: u64,
+ r14: u64,
+ r13: u64,
+ r12: u64,
+ r11: u64,
+ r10: u64,
+ r9: u64,
+ r8: u64,
+ rbp: u64,
+ rdi: u64,
+ rsi: u64,
+ rdx: u64,
+ rcx: u64,
+ rbx: u64,
+ rax: u64,
+ vector: u64,
+ error_code: u64,
+ // pushed by the CPU on the exception:
+ rip: u64,
+ cs: u64,
+ rflags: u64,
+ rsp: u64,
+ ss: u64,
+};
+
+const names = [_][]const u8{
+ "divide by zero", "debug",
+ "non-maskable interrupt", "breakpoint",
+ "overflow", "bound range exceeded",
+ "invalid opcode", "device not available",
+ "double fault", "coprocessor segment overrun",
+ "invalid TSS", "segment not present",
+ "stack-segment fault", "general protection fault",
+ "page fault", "reserved",
+ "x87 floating-point", "alignment check",
+ "machine check", "SIMD floating-point",
+ "virtualization", "control protection",
+};
+
+fn name(vector: u64) []const u8 {
+ return if (vector < names.len) names[vector] else "unknown";
+}
+
+// only these vectors push a real error code, the rest the stub pushes a 0
+// so the frame layout is uniform
+fn hasErrorCode(comptime vector: u8) bool {
+ return switch (vector) {
+ 8, 10, 11, 12, 13, 14, 17, 21 => true,
+ else => false,
+ };
+}
+
+// one naked (kinky) entry stub per vector, so normalise the stack (dummy error code if
+// needed), push the vector number, jump to the shared save and dispatch path
+fn stub(comptime vector: u8) fn () callconv(.naked) void {
+ return struct {
+ fn entry() callconv(.naked) void {
+ if (comptime !hasErrorCode(vector)) asm volatile ("pushq $0");
+ asm volatile ("pushq %[v]\n jmp isrCommon"
+ :
+ : [v] "i" (@as(u32, vector)),
+ );
+ }
+ }.entry;
+}
+
+// Save the GP registers into a Frame, hand its address to the Zig handler
+export fn isrCommon() callconv(.naked) void {
+ asm volatile (
+ \\ push %rax
+ \\ push %rbx
+ \\ push %rcx
+ \\ push %rdx
+ \\ push %rsi
+ \\ push %rdi
+ \\ push %rbp
+ \\ push %r8
+ \\ push %r9
+ \\ push %r10
+ \\ push %r11
+ \\ push %r12
+ \\ push %r13
+ \\ push %r14
+ \\ push %r15
+ \\ mov %rsp, %rdi
+ \\ call exceptionHandler
+ );
+}
+
+fn writeHex(value: u64) void {
+ const digits = "0123456789abcdef";
+ var buf: [18]u8 = undefined;
+ buf[0] = '0';
+ buf[1] = 'x';
+ var i: usize = 0;
+ while (i < 16) : (i += 1) {
+ const shift: u6 = @intCast((15 - i) * 4);
+ buf[2 + i] = digits[(value >> shift) & 0xF];
+ }
+ serial.write(&buf);
+}
+
+export fn exceptionHandler(frame: *Frame) callconv(.c) noreturn {
+ serial.write("\r\n!!! CPU DID AN OOPSIE: ");
+ serial.write(name(frame.vector));
+ serial.write("\r\n vector="); writeHex(frame.vector);
+ serial.write(" err="); writeHex(frame.error_code);
+ serial.write("\r\n rip="); writeHex(frame.rip);
+ serial.write(" cs="); writeHex(frame.cs);
+ serial.write("\r\n rflags="); writeHex(frame.rflags);
+ serial.write(" rsp="); writeHex(frame.rsp);
+ serial.write("\r\n");
+ while (true) asm volatile ("hlt");
+}
+
+fn setGate(vector: u8, handler: u64) void {
+ idt[vector] = .{
+ .offset_low = @truncate(handler),
+ .selector = gdt.KERNEL_CODE,
+ .ist = 0,
+ .type_attr = 0x8E,
+ .offset_mid = @truncate(handler >> 16),
+ .offset_high = @truncate(handler >> 32),
+ .reserved = 0,
+ };
+}
+
+// fill the exception vectors and load the IDT
+pub fn init() void {
+ inline for (0..32) |v| setGate(v, @intFromPtr(&stub(v)));
+ idtr = .{ .limit = @sizeOf(@TypeOf(idt)) - 1, .base = @intFromPtr(&idt) };
+ asm volatile ("lidt (%[idtr])"
+ :
+ : [idtr] "r" (&idtr),
+ : .{ .memory = true });
+}