diff --git a/.gitignore b/.gitignore index 674959d..e1c5be8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ zig-out/ .idea/ .claude/ -.github/ \ No newline at end of file +.github//var/ diff --git a/system/kernel/architecture/x86_64/apic.zig b/system/kernel/architecture/x86_64/apic.zig index 73efa17..9757c77 100644 --- a/system/kernel/architecture/x86_64/apic.zig +++ b/system/kernel/architecture/x86_64/apic.zig @@ -529,8 +529,22 @@ var warp_ap_ready: u32 = 0; var warp_stop: u32 = 0; var warp_checks: u32 = 0; // completed per-AP rendezvous count (for the tsc-sync test) -const warp_rounds: u32 = 1 << 20; // locked reads on the BSP: ~1 ms at GHz rates -const warp_spin_limit: u64 = 1 << 32; // bound every rendezvous wait so a lost core can't hang boot +// The warp check is bounded by TIME, not iterations: a warp tick is a locked +// read-modify-write on a cacheline two cores are fighting over — microseconds +// under real contention, not the nanosecond an uncontended count assumes (a +// 1<<20-round budget measured 2-21 SECONDS per core on a 16-core machine), and +// a PAUSE costs ~140 cycles on modern Intel, so an iteration-counted await +// mis-measures by two orders of magnitude too. ~5 ms of pairwise hammering per +// core is plenty to catch a lagging TSC (Linux's check_tsc_warp budget), and +// ~100 ms is a generous rendezvous window for a healthy core. +const warp_check_ns: u64 = 5_000_000; // per-AP pairwise check duration +const warp_await_ns: u64 = 100_000_000; // rendezvous wait before giving up + +/// TSC ticks for `ns` nanoseconds (valid whenever the warp check runs: the TSC +/// is the clocksource, so tsc_hz is calibrated). +fn warpTicksFor(ns: u64) u64 { + return @intCast(@as(u128, ns) * tsc_hz / 1_000_000_000); +} fn warpTick() void { while (@cmpxchgWeak(u32, &warp_lock, 0, 1, .acquire, .monotonic) != null) asm volatile ("pause"); @@ -545,11 +559,11 @@ fn warpTick() void { @atomicStore(u32, &warp_lock, 0, .release); } -/// Spin (bounded) until `flag` is nonzero; false on timeout. +/// Spin (time-bounded) until `flag` is nonzero; false on timeout. fn warpAwait(flag: *u32) bool { - var spins: u64 = 0; - while (@atomicLoad(u32, flag, .acquire) == 0) : (spins += 1) { - if (spins >= warp_spin_limit) return false; + const deadline = rdtsc() +% warpTicksFor(warp_await_ns); + while (@atomicLoad(u32, flag, .acquire) == 0) { + if (rdtsc() -% deadline < (1 << 62)) return false; // past the deadline asm volatile ("pause"); } return true; @@ -568,8 +582,8 @@ pub fn checkWarpSource() void { @atomicStore(u32, &warp_bsp_ready, 0, .release); return; } - var i: u32 = 0; - while (i < warp_rounds) : (i += 1) warpTick(); + const deadline = rdtsc() +% warpTicksFor(warp_check_ns); + while (rdtsc() -% deadline >= (1 << 62)) warpTick(); // until the time budget is spent @atomicStore(u32, &warp_stop, 1, .release); @atomicStore(u32, &warp_bsp_ready, 0, .release); warp_checks += 1; @@ -583,9 +597,10 @@ pub fn checkWarpTarget() void { if (clock_source != .tsc) return; if (!warpAwait(&warp_bsp_ready)) return; @atomicStore(u32, &warp_ap_ready, 1, .release); - var spins: u64 = 0; - while (@atomicLoad(u32, &warp_stop, .acquire) == 0) : (spins += 1) { - if (spins >= warp_spin_limit) return; + // The BSP owns the budget; this bound only protects against a lost BSP. + const deadline = rdtsc() +% warpTicksFor(2 * warp_check_ns + warp_await_ns); + while (@atomicLoad(u32, &warp_stop, .acquire) == 0) { + if (rdtsc() -% deadline < (1 << 62)) return; // past the deadline warpTick(); } }