Microkernel thing OS experiment (Zig ⚡)
1const std = @import("std");
2const arch = @import("../root.zig");
3const idt = arch.interrupts.idt;
4const log = std.log.scoped(.apic);
5const common = @import("common");
6
7pub var lapic_timer_khz: usize = 0;
8pub var tsc_deadline_available = false;
9pub var has_x2apic: bool = false;
10
11// tbh every cpu will be either x2apic or not, and if xapic it will
12// have the exact same base address anyways so this is fine
13pub var singleton: LAPIC = undefined;
14
15// Must instantiate this!
16pub const LAPIC = union(enum) {
17 xapic: [*]volatile u8,
18 x2apic,
19
20 const Self = @This();
21
22 // ID Register
23
24 pub const IdRegister = packed struct(u32) {
25 _reserved0: u24 = 0,
26 id: u8,
27 };
28
29 pub fn getIdRegister(lapic: Self) IdRegister {
30 return @bitCast(lapic.getRegister(.lapic_id));
31 }
32
33 // Version Register
34 pub const VersionRegister = packed struct(u32) {
35 version: u8,
36 _reserved0: u8 = 0,
37 max_lvt_entry: u8,
38 support_for_eoi_broadcast_suppression: bool,
39 _reserved1: u7 = 0,
40 };
41
42 pub fn getVersionRegister(lapic: Self) VersionRegister {
43 return @bitCast(lapic.getRegister(.version));
44 }
45
46 // Spurious Interrupt
47 pub const SpuriousInterruptRegister = packed struct(u32) {
48 idt_entry: u8,
49 apic_soft_enable: bool,
50 focus_processor_checking: bool = false,
51 _reserved0: u2 = 0,
52 eoi_broadcast_suppression: bool = false,
53 _reserved1: u19 = 0,
54 };
55
56 pub fn getSpuriousInterruptRegister(lapic: Self) SpuriousInterruptRegister {
57 return @bitCast(lapic.getRegister(.spurious_vector));
58 }
59
60 pub fn setSpuriousInterruptRegister(lapic: Self, val: SpuriousInterruptRegister) void {
61 lapic.setRegister(.spurious_vector, @bitCast(val));
62 }
63
64 // Task Priority
65 pub const TaskPriorityRegister = packed struct(u32) {
66 priority_sub_class: u4,
67 priority_class: u4,
68 _reserved0: u24 = 0,
69 };
70
71 pub fn getTaskPriorityRegister(lapic: Self) TaskPriorityRegister {
72 return @bitCast(lapic.getRegister(.task_priority));
73 }
74
75 pub fn setTaskPriorityRegister(lapic: Self, val: TaskPriorityRegister) void {
76 lapic.setRegister(.task_priority, @bitCast(val));
77 }
78
79 // Initial Count
80 pub fn setInitialCountRegister(lapic: Self, count: u32) void {
81 lapic.setRegister(.initial_count, count);
82 }
83
84 // Current Count
85 pub fn getCurrentCountRegister(lapic: Self) u32 {
86 return lapic.getRegister(.current_count);
87 }
88
89 // Divide Configuration
90 pub const DivideConfigurationRegister = enum(u32) {
91 div2 = 0,
92 div4 = 1,
93 div8 = 2,
94 div16 = 3,
95 div32 = 8,
96 div64 = 9,
97 div128 = 10,
98 div1 = 11,
99 };
100
101 pub fn getDivideConfigurationRegister(lapic: LAPIC) DivideConfigurationRegister {
102 return @enumFromInt(lapic.getRegister(.divide_configuration));
103 }
104
105 pub fn setDivideConfigurationRegister(lapic: LAPIC, register: DivideConfigurationRegister) void {
106 lapic.setRegister(.divide_configuration, @intFromEnum(register));
107 }
108
109 // LVT Timer Register
110 pub const LVTTimerRegister = packed struct(u32) {
111 idt_entry: u8,
112 _reserved0: u4 = 0,
113 status: DeliveryStatus = .idle,
114 _reserved1: u3 = 0,
115 masked: bool,
116 mode: Mode,
117 _reserved2: u13 = 0,
118
119 pub const DeliveryStatus = enum(u1) {
120 idle = 0,
121 send_pending = 1,
122 };
123
124 pub const Mode = enum(u2) {
125 oneshot = 0,
126 periodic = 1,
127 tsc_deadline = 2,
128 };
129 };
130
131 pub fn getLVTTimerRegister(lapic: LAPIC) LVTTimerRegister {
132 return @enumFromInt(lapic.getRegister(.lvt_timer));
133 }
134
135 pub fn setLVTTimerRegister(lapic: LAPIC, register: LVTTimerRegister) void {
136 lapic.setRegister(.lvt_timer, @bitCast(register));
137 }
138
139 pub fn getRegister(lapic: Self, reg: Register) u32 {
140 switch (lapic) {
141 .xapic => |base| {
142 const ptr: *align(0x10) volatile u32 = @ptrCast(@alignCast(base + reg.xapic()));
143 return ptr.*;
144 },
145 .x2apic => {
146 return arch.registers.readMSR(u32, reg.x2apic());
147 },
148 }
149 }
150
151 pub fn setRegister(lapic: Self, reg: Register, value: u32) void {
152 switch (lapic) {
153 .xapic => |base| {
154 const ptr: *align(0x10) volatile u32 = @ptrCast(@alignCast(base + reg.xapic()));
155 ptr.* = value;
156 },
157 .x2apic => {
158 arch.registers.writeMSR(u32, reg.x2apic(), value);
159 },
160 }
161 }
162
163 pub const Register = enum(u32) {
164 // From Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3
165 lapic_id = 0x2,
166 version = 0x3,
167 task_priority = 0x8,
168 process_priority = 0xa,
169 eoi = 0xb,
170 logical_destination = 0xd,
171 spurious_vector = 0xf,
172
173 in_service_0_31 = 0x10,
174 in_service_63_32 = 0x11,
175 in_service_95_64 = 0x12,
176 in_service_127_96 = 0x13,
177 in_service_159_128 = 0x14,
178 in_service_191_160 = 0x15,
179 in_service_223_192 = 0x16,
180 in_service_255_224 = 0x17,
181
182 trigger_mode_0_31 = 0x18,
183 trigger_mode_63_32 = 0x19,
184 trigger_mode_95_64 = 0x1a,
185 trigger_mode_127_96 = 0x1b,
186 trigger_mode_159_128 = 0x1c,
187 trigger_mode_191_160 = 0x1d,
188 trigger_mode_223_192 = 0x1e,
189 trigger_mode_255_224 = 0x1f,
190
191 interrupt_request_0_31 = 0x20,
192 interrupt_request_63_32 = 0x21,
193 interrupt_request_95_64 = 0x22,
194 interrupt_request_127_96 = 0x23,
195 interrupt_request_159_128 = 0x24,
196 interrupt_request_191_160 = 0x25,
197 interrupt_request_223_192 = 0x26,
198 interrupt_request_255_224 = 0x27,
199
200 error_status = 0x28,
201 lvt_cmi = 0x2f,
202
203 interrupt_command_0_31 = 0x30,
204 interrupt_command_32_63 = 0x31,
205 lvt_timer = 0x32,
206 lvt_thermal_sensor = 0x33,
207 lvt_performance_monitoring = 0x34,
208 lvt_lint0 = 0x35,
209 lvt_lint1 = 0x36,
210 lvt_error = 0x37,
211 initial_count = 0x38,
212 current_count = 0x39,
213 divide_configuration = 0x3e,
214 self_ipi = 0x3f,
215
216 const Self = @This();
217
218 // Get an offset to apply to the xAPIC base
219 pub fn xapic(reg: Register) usize {
220 return @intFromEnum(reg) * 0x10;
221 }
222
223 // Get an MSR number to write to
224 pub fn x2apic(reg: Register) u32 {
225 return 0x800 | @intFromEnum(reg);
226 }
227 };
228};
229
230pub const init = struct {
231 pub fn initialSetup() void {
232 // First, make the APIC accessible
233 initSingleton() catch |err| {
234 log.err("Failed to map APIC! {}", .{err});
235 @panic("initSingleton");
236 };
237 // Set up the interrupt handlers
238 singleton.setSpuriousInterruptRegister(.{
239 .apic_soft_enable = true,
240 .idt_entry = 0xFF,
241 });
242 // lapic.setTaskPriorityRegister(.{
243 // .priority_class = 0,
244 // .priority_sub_class = 0,
245 // });
246 arch.interrupts.idt.add_handler(.{ .interrupt = 0xFF }, u64, spurious_interrupt_handler, 0, 0);
247 arch.interrupts.idt.add_handler(.{ .interrupt = 48 }, u64, timer_handler, 0, 0);
248
249 // Calibrate against the TSC
250 calibrateTimer();
251 // Set up the LVT Timer Register
252 enableOneshotInterrupt();
253 }
254
255 fn initSingleton() !void {
256 arch.interrupts.apic.singleton = switch (has_x2apic) {
257 true => .x2apic,
258 false => blk: {
259 // Map the APIC first!
260 const apic_base = common.mm.physToHHDM([*]volatile u8, 0xFEE0_0000);
261 try common.mm.paging.mapPhys(.{
262 .vaddr = @intFromPtr(apic_base),
263 .paddr = 0xFEE0_0000,
264 .size = 0x1000,
265 .memory_type = .DeviceUncacheable,
266 .perms = .{
267 .x = false,
268 .u = false,
269 .w = true,
270 },
271 });
272 break :blk .{ .xapic = apic_base };
273 },
274 };
275 }
276
277 fn calibrateTimer() void {
278 singleton.setDivideConfigurationRegister(.div2);
279 singleton.setLVTTimerRegister(.{
280 .idt_entry = 0x69,
281 .mode = .oneshot,
282 .masked = true,
283 });
284
285 var warmup_clk_count: u64 = 0;
286 for (0..5) |_| {
287 singleton.setInitialCountRegister(0xFFFF_FFFF);
288 arch.tsc.delay_poll(1);
289 const count = singleton.getCurrentCountRegister();
290 singleton.setInitialCountRegister(0);
291
292 warmup_clk_count += 0xFFFF_FFFF - count;
293 }
294
295 std.mem.doNotOptimizeAway(&warmup_clk_count);
296
297 // Now, do it again
298 var tick_count: u64 = 0;
299 for (0..5) |_| {
300 singleton.setInitialCountRegister(0xFFFF_FFFF);
301 arch.tsc.delay_poll(5);
302 const count = singleton.getCurrentCountRegister();
303 singleton.setInitialCountRegister(0);
304
305 tick_count += 0xFFFF_FFFF - count;
306 }
307
308 // This would be the number of ticks per 5ms interval
309 const norm = tick_count / 5;
310
311 lapic_timer_khz = norm / 5;
312
313 log.debug("timer: {} kHz", .{lapic_timer_khz});
314 }
315
316 fn enableOneshotInterrupt() void {
317 const mode: LAPIC.LVTTimerRegister.Mode = switch (tsc_deadline_available) {
318 true => .tsc_deadline,
319 false => blk: {
320 singleton.setInitialCountRegister(0);
321 singleton.setDivideConfigurationRegister(.div2);
322 break :blk .oneshot;
323 },
324 };
325
326 // TODO: detect and support tsc_deadline, ditto @ armTimer
327 singleton.setLVTTimerRegister(.{
328 .idt_entry = 48,
329 .mode = mode,
330 .masked = false,
331 });
332 }
333};
334
335pub fn armTimer(ms: usize) void {
336 if (tsc_deadline_available) {
337 const IA32_TSC_DEADLINE = arch.registers.MSR(u64, 0x6E0);
338 const delta = arch.tsc.tsc_khz * ms;
339 const target = arch.tsc.rdtsc() + delta;
340
341 IA32_TSC_DEADLINE.write(target);
342 } else {
343 const ticks: u32 = @truncate(lapic_timer_khz * ms);
344 singleton.setInitialCountRegister(ticks);
345 }
346}
347
348pub fn spurious_interrupt_handler(_: *idt.InterruptFrame(u64)) callconv(idt.CallConv) void {
349 log.warn("Got a spurious interrupt!", .{});
350}
351
352pub fn timer_handler(stack_trace: *idt.InterruptFrame(u64)) callconv(idt.CallConv) void {
353 defer {
354 singleton.setRegister(.eoi, 0);
355 armTimer(20);
356 }
357 // 1. Get the next task. If there is no next task, just keep scheduling.
358 const task = common.scheduler.getNextTask() orelse return;
359 // 2. Swap the next task state with the current interrupt trace
360 std.mem.swap(arch.interrupts.idt.SavedRegisters, &task.regs, &stack_trace.regs);
361 std.mem.swap(u64, &task.rip, &stack_trace.rip);
362 std.mem.swap(u64, &task.rsp, &stack_trace.rsp);
363 // If task has a new cr3, swap current CR3 and task cr3 too
364 if (task.cr3_val != stack_trace.cr3) {
365 arch.registers.ControlRegisters.Cr3.write(task.cr3_val);
366 task.cr3_val = stack_trace.cr3;
367 }
368 // 3. Now, `task` has our current state, so enqueue it.
369 common.scheduler.pushTask(task);
370}