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