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