Repository navigation
clock: the TSC keeps CPUID 15H's rate or one the HPET refines; a step the RTC confirms is slewed, a second demotes it - #1104
Conversation
Review of #1104 at 68afed5ArchitectureThe ownership is sound. Every process reads the TSC itself, so the kernel keeps it. The HPET is only the reference, and it lives in x86's arch module. The bound math lives in the pure crate ( The hypervisor exception is sound capability selection, and metal stays strict. It does change the exit of the deleted Size: about +900 production and +500 test/model lines, against 700 deleted ( What I checked and found correct
BLOCKERS
SUGGESTIONS
Evidence assessment
SEND BACK |
…he package may have slept in is left out Review r1 of #1104. One CPU's pass a second takes the HPET sample for the whole machine's rate, drift and steps; the others no longer read the HPET on their wake path. Each CPU a second still checks its IA32_TSC_ADJUST, and now that its TSC reads no earlier than the latest any CPU's check read before it loaded it: a reading behind it proves this CPU behind by the difference, the round trips' argument one way, for a load, an ordered read and a fetch_max. The HPET may stop in a deep package state (on the T14, PC10, which the idle work now reaches); SDM Vol. 4 documents MSR_PKG_C10_RESIDENCY (632H) for neither the T14's 06_8CH nor the AMD, so no residency is read. A package state deeper than a core's own cannot hold while that core is out of every declared idle state, so a span is held only where some CPU's deep-idle counts (now counted at entry too) did not move and it was not in one at the sample. Every other span is left out: the watch keeps held segments, a rate's bound counts a tick and two brackets a segment, and the step and drift checks run over held spans only. The panics say the TSC and the HPET disagree, not which moved. The stated-rate report no longer prints the mult's rounding as a bound. tsc-step stages a whole-machine step in the sampler's readings. Accelerated QEMU guests get +invtsc and take the metal path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he package may have slept in is left out Review r1 of #1104. One CPU's pass a second takes the HPET sample for the whole machine's rate, drift and steps; the others no longer read the HPET on their wake path. Each CPU a second still checks its IA32_TSC_ADJUST, and now that its TSC reads no earlier than the latest any CPU's check read before it loaded it: a reading behind it proves this CPU behind by the difference, the round trips' argument one way, for a load, an ordered read and a fetch_max. The HPET may stop in a deep package state (on the T14, PC10, which the idle work now reaches); SDM Vol. 4 documents MSR_PKG_C10_RESIDENCY (632H) for neither the T14's 06_8CH nor the AMD, so no residency is read. A package state deeper than a core's own cannot hold while that core is out of every declared idle state, so a span is held only where some CPU's deep-idle counts (now counted at entry too) did not move and it was not in one at the sample. Every other span is left out: the watch keeps held segments, a rate's bound counts a tick and two brackets a segment, and the step and drift checks run over held spans only. The panics say the TSC and the HPET disagree, not which moved. The stated-rate report no longer prints the mult's rounding as a bound. tsc-step stages a whole-machine step in the sampler's readings. Accelerated QEMU guests get +invtsc and take the metal path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he package may have slept in is left out Review r1 of #1104. One CPU's pass a second takes the HPET sample for the whole machine's rate, drift and steps; the others no longer read the HPET on their wake path. Each CPU a second still checks its IA32_TSC_ADJUST, and now that its TSC reads no earlier than the latest any CPU's check read before it loaded it: a reading behind it proves this CPU behind by the difference, the round trips' argument one way, for a load, an ordered read and a fetch_max. The HPET may stop in a deep package state (on the T14, PC10, which the idle work now reaches); SDM Vol. 4 documents MSR_PKG_C10_RESIDENCY (632H) for neither the T14's 06_8CH nor the AMD, so no residency is read. A package state deeper than a core's own cannot hold while that core is out of every declared idle state, so a span is held only where some CPU's deep-idle counts (now counted at entry too) did not move and it was not in one at the sample. Every other span is left out: the watch keeps held segments, a rate's bound counts a tick and two brackets a segment, and the step and drift checks run over held spans only. The panics say the TSC and the HPET disagree, not which moved. The stated-rate report no longer prints the mult's rounding as a bound. tsc-step stages a whole-machine step in the sampler's readings. Accelerated QEMU guests get +invtsc and take the metal path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ery CPU holds it to the HPET The x86-64 clock took the TSC's rate from one 50 ms count against the HPET at boot and never looked again. On the AMD bench that count read 41 to 66 ppm low (HPET read after the first TSC read and before the last, so its interval brackets the TSC's), the T14 kept its own 285 ppm-off measurement beside the 2,419,200,000 Hz CPUID 15H states, and nothing checked that the TSC was invariant, that the CPUs agreed or that the rate held. The rate. Where CPUID 15H enumerates its crystal, the clock keeps that rate (SDM Vol. 2A, CPUID 15H). Leaf 16H is no longer read as a rate: the SDM confines its data to display. Elsewhere the boot counts the TSC against the HPET over 50 ms in six samples, each the narrowest of three reads bracketed by RDTSCP;LFENCE, and derives the rate's bound from the brackets and the HPET's tick; a sample off the line between first and last (a step inside the window) refuses the calibration, which is retaken up to three times. The check. Each CPU samples the HPET once a second from its scheduler pass and holds it, at the kept rate, against the previous sample (a step) and the first (a drift), allowing the readings' brackets, one HPET tick and the kept rate's bound plus the HPET specification's 0.05%. Reports at 1, 2, 4, ... s log the rate the HPET measured with its bound; where the kept rate was measured, a better measurement replaces it. A step, a drift or a write that IA32_TSC_ADJUST shows panics by name. A CPU that does not state an invariant TSC is refused, except under a hypervisor (TCG cannot state one), where the HPET alone holds it, and says so. Every AP's TSC is bounded against the BSP's by 256 round trips of a cache line as it starts, replacing the 11 ms bracket; an offset the trips prove panics. The clock page carries a line (counter, stamp, ns per tick in 32.32) behind a two-copy latch instead of four constants, so the kernel can retune the rate from where the clock stands without a step, and an NMI or a process reads it without waiting on the writer. The kernel reads its own clock off the same page. The latch has a loom model and a negative control. Closes issues/the-kernels-50-ms-tsc-calibration-misses-the-tscs-rate.md, issues/the-system-clock-takes-the-tsc-on-one-reading-and-never-checks-it-again.md and issues/ap-tsc-trail-is-assumed-and-never-checked.md, on the metal rows `timer_calibration`, `smp_roster_and_tsc_trail` and `clock_holds_over_minutes`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le is one read IA-PC HPET 1.0a §2.2's minimum implementation allows the main counter ±0.05% over any interval of 1 ms or more and ±0.2% over any of 100 µs or less. The check held every span to the first; samples on two CPUs land tens of µs apart, where the spec allows four times as much. Spans under 1 ms now take the 0.2%; the table says nothing between 100 µs and 1 ms. The T14's boot at 378995d bracketed one HPET read at 35,690 TSC cycles, 14.8 µs, so three reads per runtime sample were 44 µs in a scheduler pass once a second on each CPU. Every bound allows a read's own bracket, so the narrowest of three buys a sample precision and nothing the check needs: a runtime sample is one read. The boot's anchor and calibration keep three. The same bracket, 14.3 µs over 50 ms, is the T14's old 285 ppm calibration cluster. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he package may have slept in is left out Review r1 of #1104. One CPU's pass a second takes the HPET sample for the whole machine's rate, drift and steps; the others no longer read the HPET on their wake path. Each CPU a second still checks its IA32_TSC_ADJUST, and now that its TSC reads no earlier than the latest any CPU's check read before it loaded it: a reading behind it proves this CPU behind by the difference, the round trips' argument one way, for a load, an ordered read and a fetch_max. The HPET may stop in a deep package state (on the T14, PC10, which the idle work now reaches); SDM Vol. 4 documents MSR_PKG_C10_RESIDENCY (632H) for neither the T14's 06_8CH nor the AMD, so no residency is read. A package state deeper than a core's own cannot hold while that core is out of every declared idle state, so a span is held only where some CPU's deep-idle counts (now counted at entry too) did not move and it was not in one at the sample. Every other span is left out: the watch keeps held segments, a rate's bound counts a tick and two brackets a segment, and the step and drift checks run over held spans only. The panics say the TSC and the HPET disagree, not which moved. The stated-rate report no longer prints the mult's rounding as a bound. tsc-step stages a whole-machine step in the sampler's readings. Accelerated QEMU guests get +invtsc and take the metal path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The AMD bench's boot at 68afed5 panicked at 17.585 s on the TSC reading 1,723,545 ns ahead of the HPET over 680 ms, the event Linux's watchdog saw in 5 of 30 boots. Two counters cannot say which of them moved. At every HPET sample the sampler now reads the ACPI PM timer too (the FADT's PM_TMR_BLK and its X_ twin, 24 or 32 bits by TMR_VAL_EXT, declared read-only to the kernel), and where the TSC and the HPET disagree across a span, holds the TSC to the PM timer over the same span: - the PM timer agrees with the TSC: the HPET moved, and the span is left out with all three counters' spans on the record; - it disagrees by what the HPET does: two of the chipset's counters agree against the TSC, which panics, saying so; the two are both the FCH's (the PCH's on Intel) and may share a clock, so a stall common to both reads as the TSC having moved; - neither: all three are on the record and the span is left out. Each report also counts the left-out spans across which the HPET, and the PM timer, read apart from the TSC, which is what holds the idle guard to evidence. A PM timer that is absent or does not count within 100 µs is said, and a step then panics as before. hpet-stall stages an HPET 10 ms behind beside tsc-step's TSC ahead; a_stalled_hpet_is_left_out and a_stepped_tsc_panics hold the two outcomes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ewed away, a second demotes the TSC
On the AMD bench, the TSC steps forward about 1.7 ms once in every boot.
The HPET, the ACPI PM timer and the RTC all agree on it. The RTC runs off its
own 32.768 kHz crystal; the HPET and the PM timer run off the FCH's 14.318
MHz clock. The step lands inside an early spell in which every core sits in
port 0x415 (issues/the-amd-benchs-tsc-steps-1-7-ms-forward-once-a-boot.md).
Until now the idle guard left such spans out unseen. The guard goes; every
span is now arbitrated:
- **The TSC and the HPET agree:** the span is held, as before.
- **The PM timer agrees with the TSC:** the HPET moved, and the span is left
out.
- **The PM timer agrees with the HPET, or there is no PM timer:** the RTC
decides. Its update-ended interrupt (IRQ8, vector 0x26, counted as `rtc` in
the census) times the TSC against each edge. An edge reads late by its
interrupt's latency and never early, so the least-latency of three edges
on each side of the span gives the RTC's reading. Taken within a quarter of
the step:
- **near the step:** the TSC stepped;
- **near zero:** the chipset's counters moved together, and the span is
left out;
- **neither, or no edge yet:** taken as a TSC step, logged "unconfirmed by
the RTC".
- **A TSC step:**
- **the boot's first, forward:** logged loudly with its size and slewed away
at no more than 500 ppm (RFC 5905's MAXFREQ, the frequency tolerance NTP
disciplines a clock within). 1.7 ms takes 3.4 s, and monotonic time
never steps.
- **a second, or one backward:** demotes the TSC.
**Demotion.** A per-CPU check finding `IA32_TSC_ADJUST` changed, or a CPU's
TSC behind another's, now demotes the TSC instead of panicking. The demoted
TSC leaves for the HPET for the rest of the boot:
- the clock page is marked demoted;
- `toyos_abi::clock::nanos_since_boot` and `stamp_ns` take the new
`SYS_CLOCK_NOW`, which counts by the HPET from the later of the clock now
and the last held sample plus the HPET since;
- the kernel's deadlines and log stamps stay on the TSC.
**ABI.** The page gains its demoted word (TOYOCLK3).
**Test.** `a_stepped_tsc_is_slewed_then_demoted` replaces
`a_stepped_tsc_panics`. The `tsc-step` actuator now stages a second step once
the first is slewed away, and shifts the RTC's edges with the TSC.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…emotion hands over monotonically, and the sampler goes on after it
Fixes review r2's required findings (R1–R7) and four of its optional ones.
- **R1, torn reads.** `Latch::read_with` runs the counter read inside the
read section: the words, the counter, an Acquire fence, then the recheck.
A publication that lands while the reader is preempted sends it round
again.
- The page readers (`ClockPage::read_now`, `nanos_since_boot`, `stamp_ns`)
and the kernel's own (`clock::stamp`, `nanos_since_boot`) read this way.
- The ABI's counter is fenced after its read as well as before
(`lfence`/`isb`), and the kernel's is `counter_ordered`.
- The loom model adds a counter the writer advances once its line is
published. A reading of it always has the new line. A `should_panic`
companion shows the old order, counter read after the section, does
extrapolate a superseded line.
- **R2, demotion handover.** A page reader checks the page's demoted word
after reading the counter. `demote` holds its lock throughout: it marks
the page, fences, reads the counter, and raises `DEMOTED_LATEST` to the
line at that reading. Every value a reader that found the page unmarked
returned is then at or below every later syscall answer.
- `stamp_now` reads the counter before the mark, as a page reader does.
- **Demoted time.** It counts by the HPET from the last held sample's clock,
the HPET's own clock, and the floor holds it until the HPET catches up. A
forward step no longer leaves processes ahead for good.
- **R3, after a demotion.**
- `demote` retunes the line to the kept rate, so no slew stays in it.
- The per-CPU checks and the sampler go on. A step after the demotion is
logged loudly and not slewed, and so is a check failure.
- The dead demoted branch in `sample` is gone.
- **R4, the metal rows.** `clock_kept_its_counter` prints every TSC STEP,
left-out and waiting-on-the-RTC line. It fails `smp_roster_and_tsc_trail`,
`timer_calibration` and `clock_holds_over_minutes` on a demotion, and a
host test holds it. The "panics on a step" doc is fixed.
- **R5, records.**
- The new issue `a-demoted-tsc-keeps-the-kernels-clock-apart-from-processes`
records the backward-step sequence.
- The AMD issue no longer says monotonic time never steps: readers see the
forward step, and only the correction never steps back.
- **R6, comments.** The `Demoted` docs, the ABI header (the kernel demotes
the page when it stops trusting the counter) and `reference.rs`'s header
(the RTC's least latency and drift are its one estimate).
- **R7, the census.** The census's sources come from a macro the
architecture hands its own variants to (`arch::census_sources!`), so
generic code names no RTC and carries no `target_arch`.
Optional findings:
- **Taken:**
- `Slew::mult` pays over two periods, so a sample up to a period late
cannot overpay. A later one overpays, and the next sample pays it back
(host case). The tail now halves each period: 1.7 ms takes about 12 s.
- `apart_across` takes out the RTC crystal's drift, measured from edges at
least 4 s before the span. A host case shows a 60 ppm crystal reads a
stall as none, and reads a step as one; with the correction disabled it
fails.
- The demoted HPET is extended from its last answer.
- The "behind" line names both causes.
- **Left:** a publish-once demoted cell, and arbitrating drift and AP-start
offsets, which still panic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t's count of them
`clock_kept_its_counter` matched "left out", which every report line carries
("0 spans left out"), so each clock row printed every report. It now matches
the records the sampler writes when it leaves a span out ("so this span is
left out", "so the span was left out"), beside the TSC STEP lines and the
lines that wait on the RTC. Seen judging the T14's r8 readbacks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ever reaches across a step, and an AP's proven offset demotes
Fixes review r3's findings.
- **Cross-CPU handover.** `demote` floored processes' time at the
demoting CPU's clock after the mark. A reader on a CPU whose TSC leads,
the very case a demotion is for, could have read further ahead, and a
later `SYS_CLOCK_NOW` then read behind it. Now, once the page is marked,
a round (`HANDOVER`, a `shootdown::Shootdown`) asks every online CPU to
floor `DEMOTED_LATEST` at its own clock. Each CPU answers from its kick
handler, or itself while it waits. A page reader that found the page
unmarked read its counter before its own CPU's floor, so no answer reads
before it, however far that TSC leads.
- `SYS_CLOCK_NOW` waits for the round to end (`HANDED_OVER`), serving
its own CPU while it waits. Every wait panics past `time::DEAF_CPU`.
- The first `demote` claims `IS_DEMOTED` by a swap. A later one only
logs and never waits, so no CPU waits on the round while holding what
another needs.
- Before the APs are released, no other CPU has run a process, and only
the demoting CPU floors.
- What is left: a reader that used a line the sampler replaced before
the mark is ahead of its CPU's floor by that retune's bend, its change
of rate over the CPU's lead. Every retune already bends that much.
- **AArch64 `nomem`.** The ABI's `counter` and the kernel's
`counter_ordered` were `nomem`, which let the compiler move the latch's
loads across the counter read. Both drop it.
- **The drift base across a step.** `Edges::apart_across` measured the
RTC crystal's drift from edges at least 4 s before a span. Where an
earlier TSC step lay between them, it read the step as drift and took
it out of the next step's reading. `settle` now forgets the edges before
a step it takes (`Edges::forget_before`). A host case shows a 1.7 ms
step after a 4 ms one reads as itself only with the edges forgotten.
- **An AP's proven offset demotes.** It panicked at AP start, while the
runtime check demotes on the same fact. It now demotes.
- **Comments.** `kernel/src/clock.rs`'s header no longer says the two
clocks are one after a demotion. The demoted-offset case is added to
`issues/a-demoted-tsc-keeps-the-kernels-clock-apart-from-processes.md`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r4 of #1104 at f5a102e (fec4998..f5a102e)ArchitectureThe handover is the right shape. It reuses The r3 headlines:
A userland action does not reach the 5 s panic directly. RequiredB1
B2
B3 the handover's residual is real and bounded, but recorded only in a doc comment and the PR body (
Optional
Evidence
SEND BACK (B1, B2, B3). |
… the release floors alone, and floors take the replaced line too
Fixes review r4's B1–B3 and takes its three optional findings.
- **B1, a deadlock.** The demoter's wait served only the handover. With
interrupts closed, as a scheduler pass can be, it answered neither a TLB
shootdown nor an `SMI_CMD` write. So a CPU waiting on one of those, also
with interrupts closed, and the demoter waited on each other until
`DEAF_CPU`. `awaiting` now calls `tlb::poll` every spin, as every spinner
with `IF` clear must. `tlb::poll` serves the handover too, so a `Lock`
spinner with `IF` clear answers it as well.
- **B2, a round before the release.** `hand_over` issued a generation even
when no AP answered yet, so every AP owed it. Each then floored at its own
later clock, which jumped processes' time forward and held it for an
offset nothing had read. Before the release, the demoting CPU now floors
alone and issues nothing. The docs of `hand_over` and `HANDED_OVER`, and
the issue, say so.
- **B3, the residual.** A reader that read the line a retune then replaced
could be ahead of its CPU's floor by the rate change times its lead. The
window included the sampler's own retune to the kept rate during the
round.
- `clock::demote` now holds every retune under `RETUNING` until
`clock::handed_over`, which also makes the demotion's own retune. So
`slew()`'s redundant retune no longer lands during the round.
- `clock::retune` keeps the line it replaces (`REPLACED`).
- `clock::floor_at` floors along the later of the line in force and that
one. A reader of either is covered exactly.
- A reader of a line two or more retunes old is covered while its CPU's
lead stays under the time between those retunes over their change of
rate: about 2000 s at a slew's 500 ppm. That remainder, its bound and
its exit are in
`issues/a-demoted-tsc-keeps-the-kernels-clock-apart-from-processes.md`.
- **Optional:**
- S1: `serve_here` checks `owes` before it takes the `IrqGuard`, so a kick
with no round in flight pays a per-CPU read and two loads.
- S2: a finding after the demotion is logged once, not every second.
- S3: `awaiting` checks its deadline every 1024 spins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r5 of #1104 at 0650c9d (f5a102e..0650c9d)AssessmentThe r4 fixes have the right shape. They add 102 lines and remove 42 across four files, and every change sits in the clock or in
RequiredR1
R2
R3
Optional
VerdictSEND BACK (R1, R2, R3). All three are comment and record fixes; nothing needs another run beyond a compile. |
Fixes review r5's R1–R3, which are all text, and takes its S1 and S2. - **R1.** The `HELD` and `hand_over` docs claimed every page reader was covered. They now say only the line in force and the one it replaced are, and point to the issue for the rest. - **R2.** The issue's "about 2000 s" assumed retunes a second apart. A CPU leading the previous sampler by λ samples about 1 s − λ later, so coverage needs a lead under about a second. The bound stands: the rate change times the lead, 0.5 ms per second of lead at 500 ppm. - **R3.** `serve_here`'s doc said every spinner with interrupts closed answers. Only the kick handler and `tlb::poll`'s spinners do, and the round ends because the demoter answers every round itself. The "two loads" count is gone. - **S1.** The issue says processes' time may hold up to the last retune's change of rate times the time since it past the furthest-ahead clock. - **S2.** `awaiting` checks `done` before it reads the clock for a deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebase onto main brings #1123's paged objects, which free a frame with `pmm::free_drawn` as the shared regions now do, and #1104's clock page, which reads its object's one page: both take the commit's types. SYS_SHM_COMMIT is 160, past #1121's SYS_MESSAGE_RECV. The guest tests map with a protection, as main's `shm_map` asks, and a connect's answer carries main's three bells. The kernel's `Stat` size assertion, left from the removed `committed` field, goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebase onto main brings #1123's paged objects, which free a frame with `pmm::free_drawn` as the shared regions now do, and #1104's clock page, which reads its object's one page: both take the commit's types. SYS_SHM_COMMIT is 160, past #1121's SYS_MESSAGE_RECV. The guest tests map with a protection, as main's `shm_map` asks, and a connect's answer carries main's three bells. The kernel's `Stat` size assertion, left from the removed `committed` field, goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebase onto main brings #1123's paged objects, which free a frame with `pmm::free_drawn` as the shared regions now do, and #1104's clock page, which reads its object's one page: both take the commit's types. SYS_SHM_COMMIT is 160, past #1121's SYS_MESSAGE_RECV. The guest tests map with a protection, as main's `shm_map` asks, and a connect's answer carries main's three bells. The kernel's `Stat` size assertion, left from the removed `committed` field, goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
x86-64's clock took the TSC's rate from one 50 ms count against the HPET at boot, then never looked again.
issues/the-amd-benchs-tsc-steps-1-7-ms-forward-once-a-boot.md).What changes
The rate.
RDTSCP; LFENCE. The rate's bound comes from the brackets and one HPET tick (kernel::clock::reference).The runtime check.
IA32_TSC_ADJUSTis unchanged, and that its TSC is not behind the latest any CPU's check read before it.rtcsource on cpu0) times the TSC against each edge. It runs off the RTC's 32.768 kHz crystal; the HPET and the PM timer run off the FCH's (PCH's) 14.318 MHz clock.TSC STEPwith its size, and slewed away at no more than 500 ppm. That is RFC 5905'sMAXFREQ, the frequency tolerance NTP disciplines a clock within (Appendix A.1.1).IA32_TSC_ADJUSTchanged, a CPU behind another, or an AP's start proving its offset. Each demotes instead of panicking.DEMOTED.toyos_abi::clock::nanos_since_bootandstamp_nsthen take the newSYS_CLOCK_NOW(152).SYS_CLOCK_NOWcounts by the HPET from the last held sample's clock, which is the HPET's own.demotemarks the page, then runs one round (ashootdown::Shootdown). Every online CPU, kicked, floors every later answer at its own clock, along the later of the line in force and the one it replaced. Retunes are held from the mark until the handover ends. A page reader checks the mark after its counter read, so a reader that found the page unmarked read before its own CPU's floor, however far that CPU's TSC leads.SYS_CLOCK_NOWwaits for the round, serving its own CPU meanwhile. Every wait panics pasttime::DEAF_CPU(5 s).SMI_CMD's) throughtlb::poll, so the round ends even when other CPUs spin with interrupts closed.tlb::pollserves the handover too, so aLockspinner answers it.Eligibility. A CPU that does not state an invariant TSC is refused, except under a hypervisor: QEMU's TCG filters
+invtsc. There, the references alone hold the TSC, and the log says so. RDTSCP is required. The TCG CPU model gains+rdtscp, and the accelerated model gains+invtsc.Cross-CPU agreement at start. Every AP's TSC is bounded against the BSP's by 256 round trips of a cache line, and the bound is logged. A proven offset demotes the TSC. This replaces the 11 ms bracket.
The clock page. It carries a line (counter, stamp, ns per tick in 32.32) behind a two-copy latch, plus the demoted word (
TOYOCLK3). The kernel retunes the rate, and slews, from where the clock stands, without a step.nomem, so the compiler cannot move the latch's loads across it.The census. The architecture supplies its own sources (
arch::census_sources!). x86-64 addsrtc, and generic code names no architecture's source.The metal rows.
smp_roster_and_tsc_trail,timer_calibrationandclock_holds_over_minutesfail on a demotion. They print everyTSC STEP, left-out and waiting-on-the-RTC line.The AMD step
Decisions
kernel/loom/tests/clock_latch.rsmodels the latch, andclock-latch-fence-offis its control.rdmsrand one shared-line read-modify-write a second. IRQ8 adds one interrupt a second on cpu0.Checks and evidence
Evidence: INDEX.md. The head, 0650c9d, is on main 427f654.
cargo run -- --ci hostexits 0 (108 steps green).cargo run -- --build-onlyexits 0.clock_kept_its_counterfails a demoted boot.should_paniccompanion shows the old order extrapolates a superseded line. The handover reusesShootdown, whose protocol loom already models.a_stepped_tsc_is_slewed_then_demotedat 0650c9d, where cpu1 demoted and collected cpu0's floor; both tests at f5a102e):a_stepped_tsc_is_slewed_then_demotedpasses. The staged 10 ms step is slewed away. The second step demotes the TSC from cpu0, the round completes, and a process's reading on cpu1 then reaches the kernel.a_stalled_hpet_is_left_outpasses.irq_census_conservation,smp_roster_and_tsc_trail,timer_calibration,clock_holds_over_minutes).Neither bench demoted, proved an offset or took a second step, and neither is AArch64. With no round in flight, the handover's hint costs a per-CPU read and four loads. It is paid on every kick and, through
tlb::poll, on everyLockspin. Neither changes what r8 measured: no bench took a round, and a hint in a spin delays only the spinner. So nothing r8 measured runs differently.Risks
issues/a-demoted-tsc-keeps-the-kernels-clock-apart-from-processes.md:issues/a-demoted-tsc-keeps-the-kernels-clock-apart-from-processes.md.SYS_CLOCK_NOWspins until each online CPU takes its kick, which panics past 5 s.tsc-stepstages its steps in the sampler's readings, because TCG ignores a write to the TSC.SYS_CLOCK_NOWis 152, which paged objects: a file's pages are the kernel's, read by its holders without a request (memory stage 7a) #1123 also takes. Whichever lands second renumbers against main at its rebase.CNTFRQ_EL0, with nothing to hold it to.🤖 Generated with Claude Code