diff --git a/issues/ap-tsc-trail-is-assumed-and-never-checked.md b/issues/ap-tsc-trail-is-assumed-and-never-checked.md index cd0118963b2..6623469fb78 100644 --- a/issues/ap-tsc-trail-is-assumed-and-never-checked.md +++ b/issues/ap-tsc-trail-is-assumed-and-never-checked.md @@ -63,3 +63,15 @@ QEMU synthesises every guest TSC from one host clock and cannot answer, so this belongs on the metal session's table beside the control-register delta (`issues/a-metal-session-runs-a-pre-flash-gate-first.md`). Owed by whoever prepares that session: build the log line first, then read it. + +**2026-10-28, part measured.** `smp` now logs each AP's `rdtsc` inside a +bracket of the BSP's, about 19.8 M cycles (11 ms) wide on the AMD bench, so +it answers "trails by more than the boot took" and no smaller offset. Under +the AMD bench's Linux, CPU 0 and each of the other 15 took turns reading +their TSCs through idle gaps up to 1 s (`tsc-probe warp`, PR #1079's +evidence). Every pair's worst step was positive, 54 to 342 cycles, which +bounds each CPU's offset from CPU 0 by 342 cycles, some 190 ns. That is that +firmware's synchronisation under Linux, not a ToyOS measurement: the log +line this file owes is still a bracket, not an offset. +`issues/the-system-clock-takes-the-tsc-on-one-reading-and-never-checks-it-again.md` +holds the clock's other unchecked assumptions. diff --git a/issues/the-kernels-50-ms-tsc-calibration-misses-the-tscs-rate.md b/issues/the-kernels-50-ms-tsc-calibration-misses-the-tscs-rate.md new file mode 100644 index 00000000000..a18cc3dfb0e --- /dev/null +++ b/issues/the-kernels-50-ms-tsc-calibration-misses-the-tscs-rate.md @@ -0,0 +1,52 @@ +--- +status: open +kind: defect +opened: 2026-10-28 +--- + +# The kernel's 50 ms TSC calibration misses the TSC's rate + +The kernel measures the TSC against the HPET once, over 50 ms +(`kernel/src/arch/x86_64/hpet.rs`), and keeps time at that rate for the +whole boot. On the AMD bench (Ryzen 7 4800U) that rate is consistently low. +The TSC counted 1796.6248 MHz against the same HPET over 10 minutes and +against the ACPI PM timer over 5, the same in every idle and busy phase +(`tsc-drift` under the bench's Linux, 2026-10-28, PR #1079's evidence). All +but one of the kernel's calibrations in the session's AMD boot logs read +1796.506 to 1796.551 MHz, 41 to 66 ppm low, and 1796.519 to 1796.532 MHz in +most; the one other reads 1796.597 MHz, 15 ppm low. A rate read low is a +period kept long (`hpet.rs` stores HPET femtoseconds per TSC cycle), so every +interval reads long: at 58 ppm a ToyOS clock on that machine gains about 5 s +a day. + +The error is a bias, not noise: every reading falls on one side. It is not +the period's resolution either: one step of the femtoseconds the kernel +stores is 3,227 Hz there, 1.8 ppm, and truncation biases the other way. An +inference, not measured: the HPET is read before the first TSC read and after +the last, so the HPET interval brackets the TSC interval by up to two +uncached MMIO reads. About 1.5 µs over 50 ms is about 30 ppm low, the sign +and size seen. + +The T14 states its TSC's rate (CPUID 15H, 2,419,200,000 Hz), and the kernel +logs its own measurement beside it. Over 5 minutes against the HPET under +its Linux, the TSC counted 2419.2002 MHz, the stated rate within 0.1 ppm, +worst residual 0.2 µs (`tsc-drift`, 2026-10-28). The session's T14 boot logs +hold two clusters, 285 ppm under and 59 ppm over, in 345 and 250 log files +(some of them copies of one boot), and a few readings between and beyond +them (404 to 440 and 759 to 766 ppm under). Those are the kernel's error: the +285 ppm cluster gains about 25 s a day and the 59 ppm one loses about 5. The +bracketing above explains neither, which needs 14 µs and the other sign. +That the two clusters are two kernel revisions is an inference no one has +checked. On that machine a stated rate is there to be used, and right, and +the kernel keeps its measurement instead. + +**Exit**: on the T14 and the AMD bench, the rate the kernel keeps is within a +bound the fix derives and logs. That bound is set by its window, its reads' +cost and the reference's own accuracy, against the rate measured over minutes +against the same reference: `tsc-drift` on the AMD bench, and CPUID 15H or +`tsc-drift` on the T14. + +Whether the TSC may keep time at all, and whether it still does, is +`issues/the-system-clock-takes-the-tsc-on-one-reading-and-never-checks-it-again.md`. + +**Owner**: the kernel's x86-64 clock (`kernel/src/arch/x86_64/hpet.rs`). diff --git a/issues/the-system-clock-takes-the-tsc-on-one-reading-and-never-checks-it-again.md b/issues/the-system-clock-takes-the-tsc-on-one-reading-and-never-checks-it-again.md new file mode 100644 index 00000000000..ee21047bf49 --- /dev/null +++ b/issues/the-system-clock-takes-the-tsc-on-one-reading-and-never-checks-it-again.md @@ -0,0 +1,53 @@ +--- +status: open +kind: defect +opened: 2026-10-28 +--- + +# The system clock takes the TSC on one reading and never checks it again + +x86-64's system clock is the TSC. The boot measures it against the HPET +once, over 50 ms (`kernel/src/arch/x86_64/hpet.rs`), and `clock::set_counter` +publishes that one period for the machine's life. Three things the clock +then relies on go unchecked: + +- **That the TSC may keep time at all.** CPUID 80000007H:EDX[8], the + invariant TSC (Intel's SDM "Invariant TSC"; AMD's PPRs + `CPUID_Fn80000007_EDX[TscInvariant]`), is read only to admit deep idle + states (`kernel/src/arch/x86_64/idle.rs::facts`). The clock starts on a + TSC without it the same way. +- **That every CPU's TSC agrees.** Each AP's reading at start is checked to + fall inside a bracket of the BSP's, about 19.8 M cycles wide on the AMD + bench, 11 ms (the `SMP:` lines), and nothing compares the CPUs again + (`issues/ap-tsc-trail-is-assumed-and-never-checked.md`). +- **That the rate stays the one measured.** No later reading is set against + the HPET or another reference, so a step or a change of rate after boot + goes into every timestamp, timeout and deadline unannounced. + +On the AMD bench (Ryzen 7 4800U) the one calibration is also 41 to 66 ppm +off (`issues/the-kernels-50-ms-tsc-calibration-misses-the-tscs-rate.md`). +That CPU states the invariant bit. Under its Linux, it counts one rate in +every phase over 10 minutes, and over 15 minutes across 16 CPUs and idle +gaps up to 1 s no CPU's TSC read behind another's and no step exceeded a +read's 1.2 µs. But in 5 of 30 boots' kernel logs that Linux's watchdog saw +the TSC some 1.7 ms ahead of the HPET over half a second, 2.6 s into boot in +the one boot whose dmesg was kept. Every time it was the same size, and it +came with a calibration that was right. The cause is unknown (`tsc-probe` +and the boots' figures, PR #1079's evidence). ToyOS's one calibration also +runs early in boot. A step inside its 50 ms would be a 3% error in every +timestamp after it, and a clock that never checks would not see it. + +**Exit**: the kernel keeps time on the TSC only where every CPU states it +invariant. It bounds the CPUs' offsets at start to a figure it logs and +derives, and checks the rate and offsets against a reference while it runs. +When they leave their bound it fails clearly, or falls back to a counter +that holds and says so in the log. The runtime bound is derived from the +reference's and the reads' own error, so the check does not mistake its +own calibration for the TSC's: 12 of the 17 times the AMD's Linux watchdog +fired, it was Linux's own calibration that was wrong. Where a vendor +documents a cheaper detector, the check prefers it, such as `IA32_TSC_ADJUST` +for a write to the TSC (Intel's SDM, Vol. 3). Each part is shown on the T14 +and the AMD bench. + +**Owner**: the kernel's x86-64 clock (`kernel/src/arch/x86_64/hpet.rs`, +`kernel/src/clock.rs`).