Microkernel thing OS experiment (Zig ⚡)
1const limine = @import("limine"); 2const std = @import("std"); 3const arch = @import("root.zig"); 4const common = @import("common"); 5const console = @import("console"); 6const log = std.log.scoped(.amd64_init); 7const Idt = arch.structures.Idt; 8const StandardGdt = arch.structures.gdt.StandardGdt; 9const Tss = arch.structures.tss.Tss; 10 11var per_cpu_init_data: PerCpuInitData = .{}; 12 13pub const limine_requests = struct { 14 export var start_marker: limine.RequestsStartMarker linksection(".limine_reqs_start") = .{}; 15 export var end_marker: limine.RequestsEndMarker linksection(".limine_reqs_end") = .{}; 16 17 pub export var base_revision: limine.BaseRevision linksection(".limine_reqs") = .{ .revision = 3 }; 18 pub export var framebuffer: limine.FramebufferRequest linksection(".limine_reqs") = .{}; 19 pub export var hhdm: limine.HhdmRequest linksection(".limine_reqs") = .{}; 20 pub export var memmap: limine.MemoryMapRequest linksection(".limine_reqs") = .{}; 21 pub export var rsdp_req: limine.RsdpRequest linksection(".limine_reqs") = .{}; 22 pub export var dtb_req: limine.DtbRequest linksection(".limine_reqs") = .{}; 23 pub export var modules: limine.ModuleRequest linksection(".limine_reqs") = .{}; 24 pub export var mp: limine.SmpMpFeature.MpRequest linksection(".limine_reqs") = .{ .flags = .{ .x2apic = true } }; 25}; 26 27pub fn bsp_init() callconv(.c) noreturn { 28 // Don't optimize away the limine requests 29 inline for (@typeInfo(limine_requests).@"struct".decls) |decl| { 30 std.mem.doNotOptimizeAway(&@field(limine_requests, decl.name)); 31 } 32 33 // If the base revision isn't supported, we can't boot 34 if (!limine_requests.base_revision.isSupported()) { 35 @branchHint(.cold); 36 arch.instructions.die(); 37 } 38 39 // Die if we don't have a memory map or Higher Half Direct Mapping 40 if (limine_requests.memmap.response == null) { 41 @branchHint(.cold); 42 arch.instructions.die(); 43 } 44 45 if (limine_requests.hhdm.response == null) { 46 @branchHint(.cold); 47 arch.instructions.die(); 48 } 49 const hhdm_offset = limine_requests.hhdm.response.?.offset; 50 common.init_data.hhdm_slide = hhdm_offset; 51 52 // Add in a framebuffer if found 53 initConsole(); 54 55 // Add in ACPI/dtb if found, prefer ACPI 56 initHwDesc(); 57 58 // Set up the temporary Physical Memory Allocator 59 common.mm.bootmem.init(); 60 61 // Attach the root task 62 if (limine_requests.modules.response) |module_response| { 63 if (module_response.module_count > 0) { 64 const mod = module_response.modules.?[0]; 65 const mod_addr: [*]align(4096) u8 = @ptrCast(mod.address); 66 const mod_size = mod.size; 67 log.info("Loading root task with {s} @ {*}", .{ mod.path, mod.address }); 68 common.init_data.root_task = mod_addr[0..mod_size]; 69 } 70 } else { 71 @branchHint(.unlikely); 72 @panic("No root task found!"); 73 } 74 75 // Initialize per-cpu data (GDT and TSS) 76 per_cpu_init_data.init(); 77 78 // Install the IDT 79 initIdt(); 80 81 // AP bootstrap 82 bootstrapAPs(); 83 84 // Set up our own GDT and TSS 85 const gdt = &per_cpu_init_data.gdt_buf[0]; 86 gdt.* = .{}; 87 const tss = &per_cpu_init_data.tss_buf[0]; 88 // TSS rsp 0x3800 89 tss.* = .{ 90 .rsp0 = 0x3800, 91 .rsp1 = 0x3800, 92 .rsp2 = 0x3800, 93 }; 94 95 gdt.tss_desc.set_tss_addr(tss); 96 gdt.load(); 97 log.info("BSP successfully setup GDT+TSS!", .{}); 98 99 log.info("Allocating code for userspace...", .{}); 100 101 const user_code = common.init_data.bootmem.allocPhys(0x1000) catch @panic("alloc bruh"); 102 const user_stack = common.init_data.bootmem.allocPhys(0x1000) catch @panic("alloc bruh2"); 103 104 // TODO: load the actual root task ELF for fucks sake 105 // instead of the current glorified shellcode 106 107 // Map our executable page to 0x1000 108 common.mm.paging.mapPhys(.{ 109 .vaddr = 0x8000, 110 .paddr = user_code, 111 .size = 0x1000, 112 .memory_type = .MemoryWriteBack, 113 .perms = .{ 114 .executable = true, 115 .writable = false, 116 .userspace_accessible = true, 117 }, 118 }) catch { 119 @panic("Mapping failed!!!!!"); 120 }; 121 122 // Map our stack page to 0x3000 (starts at 0x4000) 123 common.mm.paging.mapPhys(.{ 124 .vaddr = 0x3000, 125 .paddr = user_stack, 126 .size = 0x1000, 127 .memory_type = .MemoryWriteBack, 128 .perms = .{ 129 .executable = false, 130 .writable = true, 131 .userspace_accessible = true, 132 }, 133 }) catch { 134 @panic("Mapping failed!!!!!"); 135 }; 136 137 // Place shellcode there (does a couple syscalls then jmp $ infinite loop) 138 const memory: [*]u8 = common.mm.physToHHDM([*]u8, user_code); 139 const shellcode = [_]u8{ 0x48, 0xBF, 0xE1, 0xAB, 0xDB, 0xBA, 0xB5, 0x00, 0x6B, 0xB1, 0x48, 0xBE, 0x06, 0x42, 0x69, 0x20, 0x94, 0x06, 0x42, 0x69, 0x0F, 0x05, 0xBF, 0x11, 0xCA, 0x00, 0x00, 0xBE, 0x69, 0x69, 0x69, 0x69, 0x0F, 0x05, 0xEB, 0xFE }; 140 @memcpy(memory[0..@sizeOf(@TypeOf(shellcode))], shellcode[0..]); 141 142 // Set up MSRs to enable syscalls 143 init_syscalls(); 144 145 // Finally, iretq ourselves into this bitch 146 enter_userspace(0x8000, 0x69, 0x3800); 147} 148 149// Get ready for system calls (set MSRs) 150fn init_syscalls() void { 151 // Set up the STAR MSR with the segment descriptors 152 const IA32_STAR = arch.registers.MSR(u64, 0xC0000081); 153 const star_value: u64 = 0 | @as(u64, arch.structures.gdt.StandardGdt.selectors.kernel_code) << 32 | (@as(u64, arch.structures.gdt.StandardGdt.selectors.tss_desc + 8) | 3) << 48; 154 IA32_STAR.write(star_value); 155 log.debug("Wrote 0x{x:0>16} to IA32_STAR", .{star_value}); 156 157 // Set up the EFER MSR with SCE (System Call Enable) 158 const IA32_EFER = arch.registers.MSR(u64, 0xC0000080); 159 const efer_val = IA32_EFER.read() | 0b1; 160 IA32_EFER.write(efer_val); 161 log.debug("Wrote 0x{x:0>16} to IA32_EFER", .{efer_val}); 162 163 // Set up LSTAR with the syscall handler and FMASK to clear interrupts 164 const IA32_LSTAR = arch.registers.MSR(u64, 0xC0000082); 165 IA32_LSTAR.write(@intFromPtr(syscall_entry)); 166 167 log.debug("Wrote 0x{x:0>16} to IA32_LSTAR", .{@intFromPtr(syscall_entry)}); 168 169 const IA32_FMASK = arch.registers.MSR(u64, 0xC0000084); 170 IA32_FMASK.write(1 << 9); 171 log.debug("Wrote 0x{x:0>16} to IA32_FMASK", .{1 << 9}); 172} 173 174const syscall_entry = @extern(*anyopaque, .{ 175 .name = "syscall_entry", 176}); 177export fn syscall_handler(rdi: usize, rsi: usize) callconv(.c) void { 178 std.log.info("Got a syscall! rdi=0x{x}, rsi=0x{x}", .{ rdi, rsi }); 179} 180 181fn enter_userspace(entry: u64, arg: u64, stack: u64) noreturn { 182 log.info("usercode64 GDT 0x{x}, userdata64 GDT 0x{x}", .{ arch.structures.gdt.StandardGdt.selectors.user_code, arch.structures.gdt.StandardGdt.selectors.user_data }); 183 const cr3 = arch.registers.ControlRegisters.Cr3.read(); 184 arch.registers.ControlRegisters.Cr3.write(cr3); 185 asm volatile ( 186 \\ push %[userdata64] 187 \\ push %[stack] 188 \\ push $0x2 189 \\ push %[usercode64] 190 \\ push %[entry] 191 \\ 192 \\ mov %[userdata64], %%rax 193 \\ mov %%rax, %%es 194 \\ mov %%rax, %%ds 195 \\ 196 \\ xor %%rsi, %%rsi 197 \\ xor %%rax, %%rax 198 \\ xor %%rdx, %%rdx 199 \\ xor %%rcx, %%rcx 200 \\ xor %%rbp, %%rbp 201 \\ xor %%rbx, %%rbx 202 \\ 203 \\ xor %%r8, %%r8 204 \\ xor %%r9, %%r9 205 \\ xor %%r10, %%r10 206 \\ xor %%r11, %%r11 207 \\ xor %%r12, %%r12 208 \\ xor %%r13, %%r13 209 \\ xor %%r14, %%r14 210 \\ xor %%r15, %%r15 211 \\ 212 \\ iretq 213 \\ 214 : 215 : [arg] "{rdi}" (arg), 216 [stack] "r" (stack), 217 [entry] "r" (entry), 218 [userdata64] "i" (arch.structures.gdt.StandardGdt.selectors.user_data), 219 [usercode64] "i" (arch.structures.gdt.StandardGdt.selectors.user_code), 220 ); 221 unreachable; 222} 223 224fn initConsole() void { 225 if (limine_requests.framebuffer.response) |fb_response| { 226 if (fb_response.framebuffer_count > 0) { 227 const fb = console.Framebuffer.from_limine(fb_response.getFramebuffers()[0]); 228 common.init_data.framebuffer = fb; 229 // At this point, log becomes usable 230 common.init_data.console = console.Console.from_font(fb, console.DefaultFont); 231 common.init_data.console.?.setColor(0x3bcf1d, 0); 232 } 233 } 234} 235 236fn initHwDesc() void { 237 if (limine_requests.dtb_req.response) |dtb_response| { 238 common.init_data.hardware_description = .{ .dtb = dtb_response.dtb_ptr }; 239 } 240 if (limine_requests.rsdp_req.response) |rsdp_response| { 241 common.init_data.hardware_description = .{ .acpi_rsdp = rsdp_response.address }; 242 } 243} 244 245pub fn initIdt() void { 246 const idt_addr: usize = @intFromPtr(per_cpu_init_data.idt); 247 248 // Install the known exception handlers 249 per_cpu_init_data.idt.breakpoint.installHandler(breakpoint_handler); 250 per_cpu_init_data.idt.double_fault.installHandler(double_fault); 251 per_cpu_init_data.idt.general_protection_fault.installHandler(gpf); 252 per_cpu_init_data.idt.page_fault.installHandler(page_fault); 253 254 // Load the Idt Register 255 const reg: Idt.Idtr = .{ .addr = idt_addr, .limit = @sizeOf(Idt) - 1 }; 256 reg.load(); 257} 258 259// TODO: update the type reflection thing to make a custom 260// function type for the ISR 261pub const PageFaultErrorCode = packed struct { 262 present: bool, 263 write: bool, 264 user: bool, 265 reserved_write: bool, 266 instruction_fetch: bool, 267 protection_key: bool, 268 shadow_stack: bool, 269 _reserved: u8, 270 sgx: bool, 271 _reserved2: u48, 272 273 pub fn val(self: *const PageFaultErrorCode) u64 { 274 return @bitCast(self.*); 275 } 276}; 277pub fn page_fault(stack_frame: *arch.structures.Idt.InterruptStackFrame, err_code_u64: u64) callconv(.{ .x86_64_interrupt = .{} }) void { 278 const err_code: PageFaultErrorCode = @bitCast(err_code_u64); 279 log.err("PAGE FAULT @ 0x{x:0>16}, code 0x{x}!!!!!!!!!!!", .{ stack_frame.instruction_pointer, err_code.val() }); 280 const cr2 = arch.registers.ControlRegisters.Cr2.read(); 281 switch (err_code.write) { 282 true => log.err("Tried to write to vaddr 0x{x:0>16}", .{cr2}), 283 false => log.err("Tried to read from vaddr 0x{x:0>16}", .{cr2}), 284 } 285 log.err("dying...", .{}); 286 arch.instructions.die(); 287} 288 289pub fn breakpoint_handler(stack_frame: *Idt.InterruptStackFrame) callconv(.{ .x86_64_interrupt = .{} }) void { 290 log.warn("Breakpoint @ 0x{x:0>16}, returning execution...", .{stack_frame.instruction_pointer}); 291} 292 293pub fn gpf(stack_frame: *Idt.InterruptStackFrame, err_code: u64) callconv(.{ .x86_64_interrupt = .{} }) void { 294 log.warn("gpf @ 0x{x:0>16} ERR CODE {}, returning execution...", .{ stack_frame.instruction_pointer, err_code }); 295 arch.instructions.die(); 296} 297 298pub fn double_fault(stack_frame: *Idt.InterruptStackFrame, err_code: u64) callconv(.{ .x86_64_interrupt = .{} }) noreturn { 299 common.init_data.console.?.setColor(0xf40d17, 0); 300 log.err("FATAL DOUBLE FAULT @ 0x{x:0>16}, code 0x{x}!!!!!!!!!!!", .{ stack_frame.instruction_pointer, err_code }); 301 log.err("dying...", .{}); 302 arch.instructions.die(); 303} 304 305fn bootstrapAPs() void { 306 log.info("Bootstrapping APs...", .{}); 307 const cpus = limine_requests.mp.response.?.getCpus(); 308 for (cpus) |cpu| { 309 cpu.goto_address = ap_init; 310 } 311} 312 313fn ap_init(mp_info: *limine.SmpMpFeature.MpInfo) callconv(.c) noreturn { 314 // Set up the IDT 315 const idt_addr: usize = @intFromPtr(per_cpu_init_data.idt); 316 const reg: Idt.Idtr = .{ .addr = idt_addr, .limit = @sizeOf(Idt) - 1 }; 317 reg.load(); 318 319 // Set up our GDT and TSS 320 const gdt = &per_cpu_init_data.gdt_buf[mp_info.processor_id]; 321 gdt.* = .{}; 322 const tss = &per_cpu_init_data.tss_buf[mp_info.processor_id]; 323 tss.* = .{}; 324 325 gdt.tss_desc.set_tss_addr(tss); 326 gdt.load(); 327 328 log.info("CPU {}: setup GDT and TSS, killing myself rn...", .{mp_info.processor_id}); 329 330 arch.instructions.die(); 331} 332 333const PerCpuInitData = struct { 334 gdt_buf: []StandardGdt = undefined, 335 tss_buf: []Tss = undefined, 336 idt: *Idt = undefined, 337 338 const Self = @This(); 339 pub fn init(self: *Self) void { 340 // 1. Allocate an IDT 341 const idt_addr = common.init_data.bootmem.allocMem(@sizeOf(Idt)) catch |err| { 342 std.log.err("init PerCpuInitData: IDT alloc failed: {}", .{err}); 343 @panic("rip bozo"); 344 }; 345 self.idt = @ptrFromInt(idt_addr); 346 347 // 2. Allocate space for GDT and TSS data 348 const cpu_count = limine_requests.mp.response.?.cpu_count; 349 const gdt_size = @sizeOf(StandardGdt); 350 const tss_size = @sizeOf(Tss); 351 352 const total_required_size = gdt_size * cpu_count + tss_size * cpu_count; 353 const buf: [*]u8 = @ptrFromInt(common.init_data.bootmem.allocMem(total_required_size) catch |err| { 354 std.log.err("init PerCpuInitData: GDT/TSS alloc failed: {}", .{err}); 355 @panic("rip bozo"); 356 }); 357 358 // 3. Transmute and fill out the structure 359 const gdt_buf: [*]StandardGdt = @ptrCast(@alignCast(buf[0 .. gdt_size * cpu_count])); 360 const tss_buf: [*]Tss = @ptrCast(@alignCast(buf[gdt_size * cpu_count ..][0 .. tss_size * cpu_count])); 361 self.gdt_buf = gdt_buf[0..cpu_count]; 362 self.tss_buf = tss_buf[0..cpu_count]; 363 } 364};