Microkernel thing OS experiment (Zig ⚡)

APIC timer demo

Like the PIT demo, this gives a periodic message much more efficiently.
Scheduling is very close!

pci.express 98b9dd04 e0afa531

verified
Changed files
+199 -37
components
ukernel
arch
amd64
common
+42 -26
components/ukernel/arch/amd64/boot.zig
···
bootstrapAPs();
// Calibrate our TSC
-
const rate = arch.tsc.get_tsc_rate();
-
log.info("TSC estimated CPU MHz = {?}", .{rate / 1000});
+
arch.tsc.calibrate_pit() catch {
+
log.info("Failed to calibrate with PIT!", .{});
+
arch.instructions.die();
+
};
+
log.info("TSC estimate: {} MHz", .{arch.tsc.tsc_khz / 1000});
log.info("Setting up scheduling...", .{});
-
// // Initialize the APIC
-
// // Map the APIC first!
-
// const apic_base = common.mm.physToHHDM([*]volatile u8, 0xFEE0_0000);
-
// common.mm.paging.mapPhys(.{
-
// .vaddr = @intFromPtr(apic_base),
-
// .paddr = 0xFEE0_0000,
-
// .size = 0x1000,
-
// .memory_type = .DeviceUncacheable,
-
// .perms = .{
-
// .executable = false,
-
// .userspace_accessible = false,
-
// .writable = true,
-
// },
-
// }) catch @panic("apic bruh");
-
// const apic: arch.interrupts.apic.LAPIC = .{ .xapic = apic_base };
-
// arch.interrupts.apic.init.initialSetup(apic);
+
+
initApic() catch |err| {
+
log.err("Failed to set up APIC! {}", .{err});
+
@panic("apic");
+
};
log.info("Allocating code for userspace...", .{});
···
const IA32_STAR = arch.registers.MSR(u64, 0xC0000081);
const star_value: u64 = 0 | @as(u64, arch.structures.gdt.StandardGdt.selectors.kernel_code) << 32 | (@as(u64, arch.structures.gdt.StandardGdt.selectors.tss_desc + 8) | 3) << 48;
IA32_STAR.write(star_value);
-
log.debug("Wrote 0x{x:0>16} to IA32_STAR", .{star_value});
// Set up the EFER MSR with SCE (System Call Enable)
const IA32_EFER = arch.registers.MSR(u64, 0xC0000080);
const efer_val = IA32_EFER.read() | 0b1;
IA32_EFER.write(efer_val);
-
log.debug("Wrote 0x{x:0>16} to IA32_EFER", .{efer_val});
// Set up LSTAR with the syscall handler and FMASK to clear interrupts
const IA32_LSTAR = arch.registers.MSR(u64, 0xC0000082);
IA32_LSTAR.write(@intFromPtr(syscall_entry));
-
log.debug("Wrote 0x{x:0>16} to IA32_LSTAR", .{@intFromPtr(syscall_entry)});
-
const IA32_FMASK = arch.registers.MSR(u64, 0xC0000084);
IA32_FMASK.write(1 << 9);
-
log.debug("Wrote 0x{x:0>16} to IA32_FMASK", .{1 << 9});
}
const syscall_entry = @extern(*anyopaque, .{
···
unreachable;
}
+
fn initApic() !void {
+
const has_x2apic = limine_requests.mp.response.?.flags.x2apic;
+
arch.interrupts.apic.singleton = switch (has_x2apic) {
+
true => .x2apic,
+
false => blk: {
+
// Map the APIC first!
+
const apic_base = common.mm.physToHHDM([*]volatile u8, 0xFEE0_0000);
+
try common.mm.paging.mapPhys(.{
+
.vaddr = @intFromPtr(apic_base),
+
.paddr = 0xFEE0_0000,
+
.size = 0x1000,
+
.memory_type = .DeviceUncacheable,
+
.perms = .{
+
.executable = false,
+
.userspace_accessible = false,
+
.writable = true,
+
},
+
});
+
break :blk .{ .xapic = apic_base };
+
},
+
};
+
// Set up the spurious vector and the TPR
+
arch.interrupts.apic.init.initialSetup();
+
+
// Calibrate the APIC timer
+
arch.interrupts.apic.init.calibrateTimer();
+
+
// Enable periodic interrupts
+
arch.interrupts.apic.init.enablePeriodicInterrupt(1000);
+
}
+
fn initConsole() void {
if (limine_requests.framebuffer.response) |fb_response| {
if (fb_response.framebuffer_count > 0) {
···
arch.per_cpu_init_data.idt.general_protection_fault.installHandler(gpf);
arch.per_cpu_init_data.idt.page_fault.installHandler(page_fault);
arch.per_cpu_init_data.idt.interrupts[0xFF - 32].installHandler(arch.interrupts.apic.spurious_interrupt_handler);
-
-
// Install the PIT handler
-
arch.per_cpu_init_data.idt.interrupts[0].installHandler(arch.interrupts.pit.handler);
+
arch.per_cpu_init_data.idt.interrupts[48 - 32].installHandler(arch.interrupts.apic.periodic_handler);
// Load the Idt Register
const reg: Idt.Idtr = .{ .addr = idt_addr, .limit = @sizeOf(Idt) - 1 };
+145 -6
components/ukernel/arch/amd64/interrupts/apic.zig
···
const arch = @import("../root.zig");
const log = std.log.scoped(.apic);
+
pub var lapic_timer_khz: usize = 0;
+
+
// tbh every cpu will be either x2apic or not, and if xapic it will
+
// have the exact same base address anyways so this is fine
+
pub var singleton: LAPIC = undefined;
+
// Must instantiate this!
pub const LAPIC = union(enum) {
xapic: [*]volatile u8,
···
_reserved1: u19 = 0,
};
-
pub fn getSpuriousInterruptRegister(lapic: Self) VersionRegister {
+
pub fn getSpuriousInterruptRegister(lapic: Self) SpuriousInterruptRegister {
return @bitCast(lapic.getRegister(.spurious_vector));
}
···
lapic.setRegister(.spurious_vector, @bitCast(val));
}
+
// Task Priority
+
pub const TaskPriorityRegister = packed struct(u32) {
+
priority_sub_class: u4,
+
priority_class: u4,
+
_reserved0: u24 = 0,
+
};
+
+
pub fn getTaskPriorityRegister(lapic: Self) TaskPriorityRegister {
+
return @bitCast(lapic.getRegister(.task_priority));
+
}
+
+
pub fn setTaskPriorityRegister(lapic: Self, val: TaskPriorityRegister) void {
+
lapic.setRegister(.task_priority, @bitCast(val));
+
}
+
+
// Initial Count
+
pub fn setInitialCountRegister(lapic: Self, count: u32) void {
+
lapic.setRegister(.initial_count, count);
+
}
+
+
// Current Count
+
pub fn getCurrentCountRegister(lapic: Self) u32 {
+
return lapic.getRegister(.current_count);
+
}
+
+
// Divide Configuration
+
pub const DivideConfigurationRegister = enum(u32) {
+
div2 = 0,
+
div4 = 1,
+
div8 = 2,
+
div16 = 3,
+
div32 = 8,
+
div64 = 9,
+
div128 = 10,
+
div1 = 11,
+
};
+
+
pub fn getDivideConfigurationRegister(lapic: LAPIC) DivideConfigurationRegister {
+
return @enumFromInt(lapic.getRegister(.divide_configuration));
+
}
+
+
pub fn setDivideConfigurationRegister(lapic: LAPIC, register: DivideConfigurationRegister) void {
+
lapic.setRegister(.divide_configuration, @intFromEnum(register));
+
}
+
+
// LVT Timer Register
+
pub const LVTTimerRegister = packed struct(u32) {
+
idt_entry: u8,
+
_reserved0: u4 = 0,
+
status: DeliveryStatus = .idle,
+
_reserved1: u3 = 0,
+
masked: bool,
+
mode: Mode,
+
_reserved2: u13 = 0,
+
+
pub const DeliveryStatus = enum(u1) {
+
idle = 0,
+
send_pending = 1,
+
};
+
+
pub const Mode = enum(u2) {
+
oneshot = 0,
+
periodic = 1,
+
tsc_deadline = 2,
+
};
+
};
+
+
pub fn getLVTTimerRegister(lapic: LAPIC) LVTTimerRegister {
+
return @enumFromInt(lapic.getRegister(.lvt_timer));
+
}
+
+
pub fn setLVTTimerRegister(lapic: LAPIC, register: LVTTimerRegister) void {
+
lapic.setRegister(.lvt_timer, @bitCast(register));
+
}
+
pub fn getRegister(lapic: Self, reg: Register) u32 {
switch (lapic) {
.xapic => |base| {
···
pub const init = struct {
// Get the APIC ready (call first)
-
pub fn initialSetup(lapic: LAPIC) void {
-
lapic.setSpuriousInterruptRegister(.{
+
pub fn initialSetup() void {
+
singleton.setSpuriousInterruptRegister(.{
.apic_soft_enable = true,
.idt_entry = 0xFF,
});
-
var lol = arch.instructions.cpuid.cpuid(0x15, 0x00);
-
lol = arch.instructions.cpuid.cpuid(0x40000010, 0x00);
+
// lapic.setTaskPriorityRegister(.{
+
// .priority_class = 0,
+
// .priority_sub_class = 0,
+
// });
}
-
// Calibrate the APIC timer
+
pub fn calibrateTimer() void {
+
singleton.setDivideConfigurationRegister(.div2);
+
singleton.setLVTTimerRegister(.{
+
.idt_entry = 0x69,
+
.mode = .oneshot,
+
.masked = true,
+
});
+
var warmup_clk_count: u64 = 0;
+
for (0..5) |_| {
+
singleton.setInitialCountRegister(0xFFFF_FFFF);
+
arch.tsc.delay_poll(1);
+
const count = singleton.getCurrentCountRegister();
+
singleton.setInitialCountRegister(0);
+
+
warmup_clk_count += 0xFFFF_FFFF - count;
+
}
+
+
std.mem.doNotOptimizeAway(&warmup_clk_count);
+
+
// Now, do it again
+
var tick_count: u64 = 0;
+
for (0..5) |_| {
+
singleton.setInitialCountRegister(0xFFFF_FFFF);
+
arch.tsc.delay_poll(5);
+
const count = singleton.getCurrentCountRegister();
+
singleton.setInitialCountRegister(0);
+
+
tick_count += 0xFFFF_FFFF - count;
+
}
+
+
// This would be the number of ticks per 5ms interval
+
const norm = tick_count / 5;
+
+
lapic_timer_khz = norm / 5;
+
+
log.debug("APIC timer: {} kHz", .{lapic_timer_khz});
+
}
+
+
pub fn enablePeriodicInterrupt(ms: usize) void {
+
singleton.setInitialCountRegister(0);
+
singleton.setDivideConfigurationRegister(.div2);
+
+
singleton.setLVTTimerRegister(.{
+
.idt_entry = 48,
+
.mode = .periodic,
+
.masked = false,
+
});
+
+
const ticks: u32 = @truncate(lapic_timer_khz * ms);
+
+
singleton.setInitialCountRegister(ticks);
+
}
};
pub fn spurious_interrupt_handler(_: *arch.structures.Idt.InterruptStackFrame) callconv(.{ .x86_64_interrupt = .{} }) void {
log.warn("Got a spurious interrupt!", .{});
}
+
+
pub fn periodic_handler(_: *arch.structures.Idt.InterruptStackFrame) callconv(.{ .x86_64_interrupt = .{} }) void {
+
log.warn("Got an ACPI timer interrupt!", .{});
+
singleton.setRegister(.eoi, 0);
+
}
+12 -3
components/ukernel/arch/amd64/tsc.zig
···
const out = arch.port.out;
const in = arch.port.in;
+
pub var tsc_khz: usize = 0;
+
pub inline fn rdtsc() u64 {
var low: u32 = undefined;
var high: u32 = undefined;
···
/// This should be called if we cannot get the TSC rate from
/// CPUID.15h and 16h on Intel platforms. The calibration will be done
/// against the 8254 PIT.
-
pub fn get_tsc_rate() ?usize {
+
pub fn calibrate_pit() !void {
// Set up the PIC
arch.interrupts.pic.init();
···
pollcnt += 1;
}
-
if (pollcnt < 1000) return null;
+
if (pollcnt < 1000) return error.PitError;
+
+
tsc_khz = (end - start) / 50;
+
}
-
return (end - start) / 50;
+
/// Delay for a set amount of ms using crappy polling
+
pub fn delay_poll(ms: usize) void {
+
const start = rdtsc();
+
const target = start + ms * tsc_khz;
+
while (rdtsc() < target) {}
}
-2
components/ukernel/common/loader.zig
···
const vaddr_shift = entry.p_vaddr - real_vaddr;
const memsz_pages = std.mem.alignForward(usize, vaddr_shift + entry.p_memsz, std.heap.pageSize());
-
log.debug("Allocating 0x{x} bytes (0x{x} real) to 0x{x:0>16} from offset 0x{x}", .{ entry.p_filesz, memsz_pages, entry.p_vaddr, entry.p_offset });
-
const page_backing = try common.init_data.bootmem.allocPhys(memsz_pages);
try paging.mapPhys(.{
.vaddr = real_vaddr,