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}