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}