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};