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 StandardGdt = arch.structures.gdt.StandardGdt; 8const Tss = arch.structures.tss.Tss; 9 10var pg_ctx: arch.mm.paging.Context = undefined; 11 12pub const limine_requests = struct { 13 export var start_marker: limine.RequestsStartMarker linksection(".limine_reqs_start") = .{}; 14 export var end_marker: limine.RequestsEndMarker linksection(".limine_reqs_end") = .{}; 15 16 pub export var base_revision: limine.BaseRevision linksection(".limine_reqs") = .{ .revision = 3 }; 17 pub export var framebuffer: limine.FramebufferRequest linksection(".limine_reqs") = .{}; 18 pub export var hhdm: limine.HhdmRequest linksection(".limine_reqs") = .{}; 19 pub export var memmap: limine.MemoryMapRequest linksection(".limine_reqs") = .{}; 20 pub export var rsdp_req: limine.RsdpRequest linksection(".limine_reqs") = .{}; 21 pub export var dtb_req: limine.DtbRequest linksection(".limine_reqs") = .{}; 22 pub export var modules: limine.ModuleRequest linksection(".limine_reqs") = .{}; 23 pub export var mp: limine.SmpMpFeature.MpRequest linksection(".limine_reqs") = .{ .flags = .{ .x2apic = true } }; 24}; 25 26pub fn bsp_init() callconv(.c) noreturn { 27 // Don't optimize away the limine requests 28 inline for (@typeInfo(limine_requests).@"struct".decls) |decl| { 29 std.mem.doNotOptimizeAway(&@field(limine_requests, decl.name)); 30 } 31 32 // If the base revision isn't supported, we can't boot 33 if (!limine_requests.base_revision.isSupported()) { 34 @branchHint(.cold); 35 arch.instructions.die(); 36 } 37 38 // Die if we don't have a memory map or Higher Half Direct Mapping 39 if (limine_requests.memmap.response == null) { 40 @branchHint(.cold); 41 arch.instructions.die(); 42 } 43 44 if (limine_requests.hhdm.response == null) { 45 @branchHint(.cold); 46 arch.instructions.die(); 47 } 48 const hhdm_offset = limine_requests.hhdm.response.?.offset; 49 common.init_data.hhdm_slide = hhdm_offset; 50 51 // Set up the temporary Physical Memory Allocator 52 common.mm.bootmem.init(); 53 54 // Add in a framebuffer if found 55 initConsole(); 56 57 // Get basic information through CPUID 58 arch.instructions.cpuid.init(); 59 60 // Add in ACPI/dtb if found, prefer ACPI 61 initHwDesc(); 62 63 // Attach the root task 64 if (limine_requests.modules.response) |module_response| { 65 if (module_response.module_count > 0) { 66 const mod = module_response.modules.?[0]; 67 const mod_addr: [*]align(4096) u8 = @ptrCast(mod.address); 68 const mod_size = mod.size; 69 log.info("Loading root task with {s} @ {*}", .{ mod.path, mod.address }); 70 common.init_data.root_task = mod_addr[0..mod_size]; 71 } 72 } else { 73 @branchHint(.unlikely); 74 @panic("No root task found!"); 75 } 76 77 // Initialize per-cpu data (GDT and TSS) 78 arch.per_cpu_init_data.init(limine_requests.mp.response.?.cpu_count); 79 80 // Install the IDT 81 arch.interrupts.idt.init(); 82 83 // Set up our own GDT and TSS 84 const gdt = &arch.per_cpu_init_data.gdt_buf[0]; 85 gdt.* = .{}; 86 const tss = &arch.per_cpu_init_data.tss_buf[0]; 87 // TSS rsp 0x3800 88 tss.* = .{ 89 .rsp0 = 0x7ffe_0000_8000, 90 .rsp1 = 0x7ffe_0000_8000, 91 .rsp2 = 0x7ffe_0000_8000, 92 }; 93 94 gdt.tss_desc.set_tss_addr(tss); 95 gdt.load(); 96 log.info("BSP successfully setup GDT+TSS!", .{}); 97 98 // AP bootstrap 99 bootstrapAPs(); 100 101 // Calibrate our TSC 102 arch.tsc.calibrate_pit() catch { 103 log.info("Failed to calibrate with PIT!", .{}); 104 arch.instructions.die(); 105 }; 106 log.info("TSC estimate: {} MHz", .{arch.tsc.tsc_khz / 1000}); 107 108 log.info("Setting up scheduling...", .{}); 109 110 pg_ctx = arch.mm.paging.Context.get_current(); 111 112 initApic() catch |err| { 113 log.err("Failed to set up APIC! {}", .{err}); 114 @panic("apic"); 115 }; 116 117 log.info("Allocating code for userspace...", .{}); 118 119 // Allocate a stack (0x3000 - 0x4000) 120 common.mm.paging.map(.{ 121 .vaddr = 0x7ffe_0000_0000, 122 .size = 65536, 123 .memory_type = .MemoryWriteBack, 124 .perms = .{ 125 .x = false, 126 .u = true, 127 .w = true, 128 }, 129 .context = &pg_ctx, 130 }) catch @panic("couldn't map user stack"); 131 132 const entry = common.loadRootTask(&pg_ctx) catch |err| { 133 log.err("Couldn't load the root task! {}", .{err}); 134 @panic("ggz"); 135 }; 136 log.info("Dropping to userspace entry 0x{x:0>16}", .{entry}); 137 138 init_syscalls(); 139 140 arch.interrupts.apic.armTimer(1000); 141 enter_userspace(entry, 0x69, 0x7ffe_0001_0000); 142} 143 144// Get ready for system calls (set MSRs) 145fn init_syscalls() void { 146 // Set up the STAR MSR with the segment descriptors 147 const IA32_STAR = arch.registers.MSR(u64, 0xC0000081); 148 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; 149 IA32_STAR.write(star_value); 150 151 // Set up the EFER MSR with SCE (System Call Enable) 152 const IA32_EFER = arch.registers.MSR(u64, 0xC0000080); 153 const efer_val = IA32_EFER.read() | 0b1; 154 IA32_EFER.write(efer_val); 155 156 // Set up LSTAR with the syscall handler and FMASK to clear interrupts 157 const IA32_LSTAR = arch.registers.MSR(u64, 0xC0000082); 158 IA32_LSTAR.write(@intFromPtr(syscall_entry)); 159 160 const IA32_FMASK = arch.registers.MSR(u64, 0xC0000084); 161 IA32_FMASK.write(1 << 9); 162} 163 164const syscall_entry = @extern(*anyopaque, .{ 165 .name = "syscall_entry", 166}); 167export fn syscall_handler(rdi: usize, rsi: usize) callconv(.c) void { 168 std.log.info("Got a syscall! rdi=0x{x}, rsi=0x{x}", .{ rdi, rsi }); 169} 170 171fn enter_userspace(entry: u64, arg: u64, stack: u64) noreturn { 172 log.info("usercode64 GDT 0x{x}, userdata64 GDT 0x{x}", .{ arch.structures.gdt.StandardGdt.selectors.user_code, arch.structures.gdt.StandardGdt.selectors.user_data }); 173 const cr3 = arch.registers.ControlRegisters.Cr3.read(); 174 arch.registers.ControlRegisters.Cr3.write(cr3); 175 asm volatile ( 176 \\ push %[userdata64] 177 \\ push %[stack] 178 \\ push $0x202 179 \\ push %[usercode64] 180 \\ push %[entry] 181 \\ 182 \\ mov %[userdata64], %%rax 183 \\ mov %%rax, %%es 184 \\ mov %%rax, %%ds 185 \\ 186 \\ xor %%rsi, %%rsi 187 \\ xor %%rax, %%rax 188 \\ xor %%rdx, %%rdx 189 \\ xor %%rcx, %%rcx 190 \\ xor %%rbp, %%rbp 191 \\ xor %%rbx, %%rbx 192 \\ 193 \\ xor %%r8, %%r8 194 \\ xor %%r9, %%r9 195 \\ xor %%r10, %%r10 196 \\ xor %%r11, %%r11 197 \\ xor %%r12, %%r12 198 \\ xor %%r13, %%r13 199 \\ xor %%r14, %%r14 200 \\ xor %%r15, %%r15 201 \\ 202 \\ iretq 203 \\ 204 : 205 : [arg] "{rdi}" (arg), 206 [stack] "r" (stack), 207 [entry] "r" (entry), 208 [userdata64] "i" (arch.structures.gdt.StandardGdt.selectors.user_data), 209 [usercode64] "i" (arch.structures.gdt.StandardGdt.selectors.user_code), 210 ); 211 unreachable; 212} 213 214fn initApic() !void { 215 const has_x2apic = limine_requests.mp.response.?.flags.x2apic; 216 arch.interrupts.apic.singleton = switch (has_x2apic) { 217 true => .x2apic, 218 false => blk: { 219 // Map the APIC first! 220 const apic_base = common.mm.physToHHDM([*]volatile u8, 0xFEE0_0000); 221 try common.mm.paging.mapPhys(.{ 222 .vaddr = @intFromPtr(apic_base), 223 .paddr = 0xFEE0_0000, 224 .size = 0x1000, 225 .memory_type = .DeviceUncacheable, 226 .perms = .{ 227 .x = false, 228 .u = false, 229 .w = true, 230 }, 231 .context = &pg_ctx, 232 }); 233 break :blk .{ .xapic = apic_base }; 234 }, 235 }; 236 // Set up the spurious vector, TPR, and 237 // calibrate the timer 238 arch.interrupts.apic.init.initialSetup(); 239 240 // Enable one-shot interrupts 241 arch.interrupts.apic.init.enableOneshotInterrupt(); 242} 243 244fn initConsole() void { 245 if (limine_requests.framebuffer.response) |fb_response| { 246 if (fb_response.framebuffer_count > 0) { 247 const fb = common.aux.fb_from_limine(fb_response.getFramebuffers()[0]); 248 common.init_data.framebuffer = fb; 249 // Create a canvas for the console to render to 250 const canvas: [*]u8 = @ptrFromInt(common.init_data.bootmem.allocMem(fb.width * fb.height * fb.bypp) catch @panic("Couldn't allocate a canvas")); 251 @memset(canvas[0 .. fb.width * fb.height * fb.bypp], 0); 252 253 common.init_data.console = console.Console.init(fb, canvas); 254 } 255 } 256} 257 258fn initHwDesc() void { 259 if (limine_requests.dtb_req.response) |dtb_response| { 260 common.init_data.hardware_description = .{ .dtb = dtb_response.dtb_ptr }; 261 } 262 if (limine_requests.rsdp_req.response) |rsdp_response| { 263 common.init_data.hardware_description = .{ .acpi_rsdp = rsdp_response.address }; 264 } 265} 266 267fn bootstrapAPs() void { 268 log.info("Bootstrapping APs...", .{}); 269 const cpus = limine_requests.mp.response.?.getCpus(); 270 for (cpus) |cpu| { 271 cpu.goto_address = ap_init; 272 } 273} 274 275fn ap_init(mp_info: *limine.SmpMpFeature.MpInfo) callconv(.c) noreturn { 276 // Set up the IDT 277 arch.interrupts.idt.load(); 278 279 // Set up our GDT and TSS 280 const gdt = &arch.per_cpu_init_data.gdt_buf[mp_info.processor_id]; 281 gdt.* = .{}; 282 const tss = &arch.per_cpu_init_data.tss_buf[mp_info.processor_id]; 283 tss.* = .{}; 284 285 gdt.tss_desc.set_tss_addr(tss); 286 gdt.load(); 287 288 log.info("CPU {}: setup GDT and TSS, killing myself rn...", .{mp_info.processor_id}); 289 290 arch.instructions.die(); 291}