diff options
| -rw-r--r-- | boot/boot.zig | 6 | ||||
| -rw-r--r-- | boot/gdt.zig | 43 | ||||
| -rw-r--r-- | boot/idt.zig | 167 |
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 }); +} |
