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 flanterm = @import("flanterm"); 7const log = std.log.scoped(.amd64_init); 8const StandardGdt = arch.structures.gdt.StandardGdt; 9const Tss = arch.structures.tss.Tss; 10 11var pg_ctx: arch.mm.paging.Context = undefined; 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 // Get basic information through CPUID 55 arch.instructions.cpuid.init(); 56 57 // Add in ACPI/dtb if found, prefer ACPI 58 initHwDesc(); 59 60 // Set up the temporary Physical Memory Allocator 61 common.mm.bootmem.init(); 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 and the TPR 237 arch.interrupts.apic.init.initialSetup(); 238 239 // Calibrate the APIC timer 240 arch.interrupts.apic.init.calibrateTimer(); 241 242 // Enable one-shot interrupts 243 arch.interrupts.apic.init.enableOneshotInterrupt(); 244} 245 246fn initConsole() void { 247 if (limine_requests.framebuffer.response) |fb_response| { 248 if (fb_response.framebuffer_count > 0) { 249 const fb = common.aux.Framebuffer.from_limine(fb_response.getFramebuffers()[0]); 250 common.init_data.framebuffer = fb; 251 252 common.init_data.console = flanterm.Context.init(.{ 253 .fb = fb.address, 254 .width = fb.width, 255 .height = fb.height, 256 .pitch = fb.pitch, 257 .red_mask_size = fb.red_mask_size, 258 .red_mask_shift = fb.red_mask_shift, 259 .green_mask_size = fb.green_mask_size, 260 .green_mask_shift = fb.green_mask_shift, 261 .blue_mask_size = fb.blue_mask_size, 262 .blue_mask_shift = fb.blue_mask_shift, 263 }); 264 } 265 } 266} 267 268fn initHwDesc() void { 269 if (limine_requests.dtb_req.response) |dtb_response| { 270 common.init_data.hardware_description = .{ .dtb = dtb_response.dtb_ptr }; 271 } 272 if (limine_requests.rsdp_req.response) |rsdp_response| { 273 common.init_data.hardware_description = .{ .acpi_rsdp = rsdp_response.address }; 274 } 275} 276 277fn bootstrapAPs() void { 278 log.info("Bootstrapping APs...", .{}); 279 const cpus = limine_requests.mp.response.?.getCpus(); 280 for (cpus) |cpu| { 281 cpu.goto_address = ap_init; 282 } 283} 284 285fn ap_init(mp_info: *limine.SmpMpFeature.MpInfo) callconv(.c) noreturn { 286 // Set up the IDT 287 arch.interrupts.idt.load(); 288 289 // Set up our GDT and TSS 290 const gdt = &arch.per_cpu_init_data.gdt_buf[mp_info.processor_id]; 291 gdt.* = .{}; 292 const tss = &arch.per_cpu_init_data.tss_buf[mp_info.processor_id]; 293 tss.* = .{}; 294 295 gdt.tss_desc.set_tss_addr(tss); 296 gdt.load(); 297 298 log.info("CPU {}: setup GDT and TSS, killing myself rn...", .{mp_info.processor_id}); 299 300 arch.instructions.die(); 301}