Calibrated timer / clock
This commit is contained in:
@@ -22,8 +22,13 @@ const reg_spurious = 0x0F0;
|
||||
const reg_eoi = 0x0B0;
|
||||
const reg_lvt_timer = 0x320;
|
||||
const reg_timer_initial = 0x380;
|
||||
const reg_timer_current = 0x390;
|
||||
const reg_timer_divide = 0x3E0;
|
||||
|
||||
const lvt_masked = 1 << 16;
|
||||
const lvt_periodic = 1 << 17;
|
||||
const timer_divide_16 = 0x3;
|
||||
|
||||
const ia32_apic_base_msr = 0x1B;
|
||||
|
||||
/// LAPIC MMIO base. A runtime var (not a constant) both because we read it from
|
||||
@@ -33,6 +38,12 @@ var base: usize = 0xFEE00000;
|
||||
|
||||
var tick_count: u64 = 0;
|
||||
|
||||
/// LAPIC timer counts per millisecond, measured against the PIT (see calibrate).
|
||||
/// At divide-by-16, this is the effective counting rate.
|
||||
var ticks_per_ms: u32 = 0;
|
||||
/// The periodic-interrupt frequency the timer is armed at, once initTimer runs.
|
||||
var timer_hz: u32 = 0;
|
||||
|
||||
fn read(reg: u32) u32 {
|
||||
return @as(*volatile u32, @ptrFromInt(base + reg)).*;
|
||||
}
|
||||
@@ -67,11 +78,60 @@ pub fn init() void {
|
||||
write(reg_spurious, 0x100 | spurious_vector); // bit 8 = software enable
|
||||
}
|
||||
|
||||
/// Arm the LAPIC timer in periodic mode on `timer_vector`.
|
||||
pub fn initTimer() void {
|
||||
write(reg_timer_divide, 0x3); // divide bus clock by 16
|
||||
write(reg_lvt_timer, timer_vector | (1 << 17)); // periodic mode
|
||||
write(reg_timer_initial, 1_000_000); // reload count -> periodic ticks
|
||||
/// Measure the LAPIC timer's counting rate against the PIT (channel 2, which can
|
||||
/// be polled without interrupts). We run the LAPIC timer one-shot from its max
|
||||
/// count while the PIT counts out a known 10 ms, then see how far the LAPIC got.
|
||||
/// This gives real time, which the RTOS quanta guarantees depend on.
|
||||
pub fn calibrate() void {
|
||||
const pit_hz = 1_193_182;
|
||||
const calib_ms = 10;
|
||||
const pit_count: u16 = @intCast(pit_hz / 1000 * calib_ms);
|
||||
|
||||
// LAPIC timer: divide 16, masked (no interrupt — we just want the count),
|
||||
// counting down from the maximum.
|
||||
write(reg_timer_divide, timer_divide_16);
|
||||
write(reg_lvt_timer, lvt_masked);
|
||||
write(reg_timer_initial, 0xFFFFFFFF);
|
||||
|
||||
// PIT channel 2, mode 0 (interrupt on terminal count): load the count with the
|
||||
// gate low, then raise the gate to start it counting.
|
||||
io.outb(0x61, io.inb(0x61) & 0xFC); // speaker off, gate low
|
||||
io.outb(0x43, 0xB0); // channel 2, lo/hi byte, mode 0
|
||||
io.outb(0x42, @truncate(pit_count));
|
||||
io.outb(0x42, @truncate(pit_count >> 8));
|
||||
io.outb(0x61, (io.inb(0x61) & 0xFC) | 0x01); // gate high -> start
|
||||
|
||||
while (io.inb(0x61) & 0x20 == 0) {} // poll channel-2 output until terminal count
|
||||
|
||||
const elapsed = 0xFFFFFFFF - read(reg_timer_current);
|
||||
write(reg_timer_initial, 0); // stop the timer
|
||||
ticks_per_ms = elapsed / calib_ms;
|
||||
}
|
||||
|
||||
/// Arm the LAPIC timer to fire on `timer_vector` at `hz` (periodic). Requires
|
||||
/// calibrate() to have run.
|
||||
pub fn initTimer(hz: u32) void {
|
||||
timer_hz = hz;
|
||||
const count = @as(u64, ticks_per_ms) * 1000 / hz; // counts per (1/hz) second
|
||||
write(reg_timer_divide, timer_divide_16);
|
||||
write(reg_lvt_timer, timer_vector | lvt_periodic);
|
||||
write(reg_timer_initial, @intCast(count));
|
||||
}
|
||||
|
||||
/// Configured periodic-interrupt frequency (Hz).
|
||||
pub fn frequencyHz() u32 {
|
||||
return timer_hz;
|
||||
}
|
||||
|
||||
/// Measured LAPIC timer frequency (Hz), for reporting/sanity checks.
|
||||
pub fn lapicHz() u64 {
|
||||
return @as(u64, ticks_per_ms) * 1000;
|
||||
}
|
||||
|
||||
/// Milliseconds since the timer started (monotonic). Ticks accrue at timer_hz.
|
||||
pub fn uptimeMs() u64 {
|
||||
if (timer_hz == 0) return 0;
|
||||
return ticks() * 1000 / timer_hz;
|
||||
}
|
||||
|
||||
/// Acknowledge the current interrupt so the LAPIC will deliver the next one.
|
||||
|
||||
+18
-3
@@ -60,12 +60,17 @@ pub fn readCr3() u64 {
|
||||
);
|
||||
}
|
||||
|
||||
/// Enable the Local APIC and start its periodic timer, the kernel's heartbeat.
|
||||
/// Interrupts still have to be unmasked with enableInterrupts() to be delivered.
|
||||
/// Kernel tick rate: 1000 Hz (1 ms), the scheduler's time quantum.
|
||||
pub const timer_hz = 1000;
|
||||
|
||||
/// Enable the Local APIC, calibrate its timer against the PIT, and start it firing
|
||||
/// at `timer_hz` — the kernel's real-time heartbeat. Interrupts still have to be
|
||||
/// unmasked with enableInterrupts() to be delivered.
|
||||
pub fn startTimer() void {
|
||||
apic.init();
|
||||
apic.calibrate();
|
||||
idt.setHandler(apic.timer_vector, apic.timerTick);
|
||||
apic.initTimer();
|
||||
apic.initTimer(timer_hz);
|
||||
}
|
||||
|
||||
/// Number of timer ticks since startTimer().
|
||||
@@ -73,6 +78,16 @@ pub fn ticks() u64 {
|
||||
return apic.ticks();
|
||||
}
|
||||
|
||||
/// Milliseconds since the timer started (monotonic).
|
||||
pub fn uptimeMs() u64 {
|
||||
return apic.uptimeMs();
|
||||
}
|
||||
|
||||
/// Measured LAPIC timer frequency in Hz (from calibration).
|
||||
pub fn lapicHz() u64 {
|
||||
return apic.lapicHz();
|
||||
}
|
||||
|
||||
/// Unmask maskable interrupts (`sti`) so device interrupts get delivered.
|
||||
pub fn enableInterrupts() void {
|
||||
asm volatile ("sti");
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ fn kmain(boot_info: *const BootInfo) noreturn {
|
||||
// Start the timer and unmask interrupts — the kernel now has a heartbeat.
|
||||
arch.startTimer();
|
||||
arch.enableInterrupts();
|
||||
con.write("danos: timer interrupts enabled\n");
|
||||
con.print("danos: timer online ({d} Hz tick, LAPIC {d} MHz measured)\n", .{ arch.timer_hz, arch.lapicHz() / 1_000_000 });
|
||||
|
||||
// In a test build (`zig build -Dtest-case=<name>`), run that case and stop.
|
||||
// Normal builds fall through to the idle halt.
|
||||
|
||||
@@ -49,6 +49,8 @@ pub fn run(case: []const u8, boot_info: *const BootInfo) void {
|
||||
smoke(boot_info);
|
||||
} else if (eql(case, "timer")) {
|
||||
timer();
|
||||
} else if (eql(case, "clock")) {
|
||||
clock();
|
||||
} else if (eql(case, "vmm")) {
|
||||
vmm();
|
||||
} else if (eql(case, "heap")) {
|
||||
@@ -204,6 +206,27 @@ fn heapTest() void {
|
||||
result();
|
||||
}
|
||||
|
||||
/// Verify the calibrated clock: a plausible measured LAPIC frequency, the
|
||||
/// configured tick rate, and monotonic uptime that advances with real ticks.
|
||||
fn clock() void {
|
||||
log("DANOS-TEST-BEGIN: clock\n", .{});
|
||||
|
||||
// Calibration produced a sane LAPIC frequency (roughly 1 MHz .. 100 GHz).
|
||||
const lapic = arch.lapicHz();
|
||||
check("LAPIC frequency measured", lapic > 1_000_000 and lapic < 100_000_000_000);
|
||||
|
||||
// Wait for ~5 real ticks and confirm uptime advanced by about that many ms
|
||||
// (tick rate is 1000 Hz, so 1 tick == 1 ms).
|
||||
const start_ticks = arch.ticks();
|
||||
const start_ms = arch.uptimeMs();
|
||||
var spins: u64 = 0;
|
||||
while (arch.ticks() < start_ticks + 5 and spins < 5_000_000_000) spins +%= 1;
|
||||
const elapsed_ms = arch.uptimeMs() - start_ms;
|
||||
check("uptime advances with ticks", elapsed_ms >= 5 and elapsed_ms < 100);
|
||||
|
||||
result();
|
||||
}
|
||||
|
||||
fn faultInvalidOpcode() void {
|
||||
log("DANOS-TEST-BEGIN: fault-ud\n", .{});
|
||||
asm volatile ("ud2");
|
||||
|
||||
Reference in New Issue
Block a user