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}