Microkernel thing OS experiment (Zig ⚡)
at dev 12 kB view raw
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}