Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build/arch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ impl Arch {
/// TCG guest that withholds one is a feature this tree stops exercising.
pub const fn cpu(self, accel: Accel) -> &'static str {
match (self, accel.is_hardware()) {
(Arch::X86_64, true) => "host,+rdrand,+smap,+fsgsbase,+x2apic,+smep,+ssse3,+sse4.1,+sse4.2,+xsave,+xsaveopt,+avx,+avx2",
(Arch::X86_64, false) => "qemu64,+rdrand,+smap,+fsgsbase,+x2apic,+smep,+ssse3,+sse4.1,+sse4.2,+xsave,+xsaveopt,+avx,+avx2",
(Arch::X86_64, true) => "host,+invtsc,+rdrand,+smap,+fsgsbase,+x2apic,+smep,+ssse3,+sse4.1,+sse4.2,+xsave,+xsaveopt,+avx,+avx2",
(Arch::X86_64, false) => "qemu64,+rdrand,+rdtscp,+smap,+fsgsbase,+x2apic,+smep,+ssse3,+sse4.1,+sse4.2,+xsave,+xsaveopt,+avx,+avx2",
(Arch::Aarch64, true) => "host",
(Arch::Aarch64, false) => "max",
}
Expand Down
4 changes: 4 additions & 0 deletions build/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,10 @@ pub(crate) const CONTROLS: &[Control] = &[
red(KERNEL_LOOM, "seqlock-writer-fence-off", Some("panic_console_publish"), &[
Fails("a_snapshot_is_one_publication_whole"),
]),
red(KERNEL_LOOM, "clock-latch-fence-off", Some("clock_latch"), &[
Fails("a_read_is_one_line_whole"),
Fails("a_later_read_is_never_an_older_line"),
]),
red(KERNEL_LOOM, "serial-try-lock-then-some", Some("serial_lock"), &[
Fails("a_lost_try_lock_leaves_the_lock_held"),
Fails("two_writers_never_overlap"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
status: open
kind: defect
opened: 2026-11-01
---

# A demoted TSC keeps the kernel's clock apart from processes'

When the clock demotes the TSC (`kernel/src/arch/x86_64/clocksource.rs`,
`demote`), processes' readings of now move to the HPET through
`SYS_CLOCK_NOW`. The kernel's own clock, its deadlines and its log stamps stay
on the TSC at the kept rate. Every later TSC step therefore moves the two
clocks apart, and a log line a process stamps no longer orders against one
the kernel stamps.

A backward step plays out like this:
1. Every reading, the kernel's and processes', goes back by the step.
2. The sampler, or a CPU's check, finds it within a second and demotes the
TSC.
3. Processes' time jumps forward to the HPET's count from the last held
sample, which is the true time, and stays monotonic from there.
4. The kernel's clock stays behind by the step.

After a forward second step, processes' time holds at the TSC's clock until
the HPET's count reaches it, then follows the HPET. The kernel's clock stays
ahead by the step.

Where one CPU's TSC is apart from the others' (written or found behind),
processes' time holds at the furthest-ahead CPU's clock at the handover, or
up to the last retune's change of rate times the time since it past that,
until the HPET's count reaches it. An offset an AP's start proves demotes
before any process runs, so processes' time starts on the HPET's count.
Either way the kernel's clock on each CPU stays apart by that CPU's offset.

The handover floors each CPU along the line in force and the one it replaced,
which covers a process that read either. A process that read the page along
an older line, at least two retunes before the demotion, can read behind its
last reading. Retunes come a second apart by the sampling CPU's clock, so a
CPU that leads the previous sampler by λ samples about 1 s − λ after it. A
reading on a leading CPU is then covered only while the lead stays under
about a second, where consecutive samples come from CPUs that far apart.
Past that, the answer reads behind the process's last reading by at most the
change of rate times the lead: 0.5 ms per second of lead at a slew's
500 ppm.

**Owner:** the clock.

**Exit:** after a demotion, the kernel's clock follows the reference too, so
the kernel and processes read one clock again, and the handover floors along
every line a process could have read.
77 changes: 0 additions & 77 deletions issues/ap-tsc-trail-is-assumed-and-never-checked.md

This file was deleted.

47 changes: 47 additions & 0 deletions issues/the-amd-benchs-tsc-steps-1-7-ms-forward-once-a-boot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
status: open
kind: defect
opened: 2026-10-31
---

# The AMD bench's TSC steps 1.7 ms forward once a boot

On the AMD bench (Lenovo 82A2, BIOS DMCN38WW; Ryzen 7 4800U, CPUID
signature 0x860f01, Family 17h Model 60h), the TSC jumps 1.69–1.73 ms ahead
once in every boot. The HPET, the ACPI PM timer and the RTC agree on it
within 37 µs. The RTC runs off its own 32.768 kHz crystal, and the other two
off the FCH's 14.318 MHz clock. Linux on the same machine sees the event in
5 of 30 boots.

**Owner:** the clock (`kernel/src/arch/x86_64/clocksource.rs`) and
acpiserver, which hands over the idle state the step lands in.

**Where it lands.** Inside one spell in which every CPU sits in port 0x415
(s1, the deepest state acpiserver hands over), early in the boot. One CPU
reads the HPET as each spell ends:
- in `benchcase`, spells 0 to 2 read no step (the third lasted 1.66 ms), and
the TSC is 1.70 ms ahead from the end of spell 3;
- in `testcases`, the boot's first such spell already ends 1.72 ms ahead.

The stepping spell's TSC length includes the jump. No later spell steps.
`benchcase` reaches its first spells at about 17 s, when `osbench_sched`'s
all-core load ends; `testcases` reaches them at about 5 s, right after the
hand-over. The trigger is the package's deep idle with every core in 0x415.
Whether the TSC's jump across it is the platform's or the state's entry is
not known.

**What the clock does meanwhile.** Readers see the forward step: the clock
reads up to 1.7 ms ahead from the step until the sampler finds it, within a
second, and through the RTC's few seconds after. A forward step the HPET and
the PM timer agree on is then slewed away at no more than 500 ppm. This
happens whether the RTC confirms the step or cannot tell, and the correction
never steps back. A second step, or a backward one, demotes the TSC
(`issues/a-demoted-tsc-keeps-the-kernels-clock-apart-from-processes.md`).

**Not read.** AMD's *Processor Programming Reference for Family 17h Model 60h*
(55922), which would cover the TSC in deep C-states and the TSC's relation to
the FCH's clocks. amd.com refuses the build's user agent, so it has not been
fetched.

**Exit:** the cause found and removed, or documented by AMD, so that no AMD
boot takes a step.
52 changes: 0 additions & 52 deletions issues/the-kernels-50-ms-tsc-calibration-misses-the-tscs-rate.md

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ opened: 2026-10-04

Every line of the log opens with its time since the CPU's counter last started
(`toyos_abi::log::Head`). The loader converts the counter at the rate CPUID
leaves 15H/16H state (`loader/src/stamp.rs`, `toyos_tsc::stated_hz`), and
the kernel does the same for its records until it has measured the TSC against
the HPET (`kernel/src/clock.rs`'s `stamp`). A CPU that states neither leaf gets
leaf 15H states (`loader/src/stamp.rs`, `toyos_tsc::stated_hz`), and the
kernel does the same for its records until its clock starts
(`kernel/src/clock.rs`'s `stamp`). Leaf 16H's base frequency is no rate: the
SDM confines its data to display. A CPU that states no rate in 15H gets
`--.---` (`toyos_abi::log::UNTIMED`) in place of the time on every loader line,
one `Loader clock: this CPU states no counter rate` line, and `--.---` on every
kernel record before `clock: HPET at …` — `boot:`, `ACPI:`, the interrupt
kernel record before the clock starts — `boot:`, `ACPI:`, the interrupt
controller's and the per-CPU area's among them. The records after it carry
their time from the same zero.

Expand All @@ -22,8 +23,9 @@ Every AMD part is one. InstLatx64's recorded CPUID dumps
largest standard leaf, as `00000010` on Zen 2 (`0870F10_K17_Matisse_CPUID`),
Zen 3+ (`0A40F41_K19_Rembrandt_01_CPUID`), Zen 4 (`0A60F12_K19_Raphael_01_CPUID`)
and Zen 5 (`0B00F21_K20_Turin_01_CPUID`, `0B20F40_K20_StrixPoint_06_CPUID`,
`0B40F40_K20_GraniteRidge_02_CPUID`): neither leaf exists there. QEMU's
`qemu64` states neither either, and under nightly's `kvm / suite`'s
`0B40F40_K20_GraniteRidge_02_CPUID`): neither leaf exists there, and an Intel
part whose leaf 15H leaves its crystal's hertz zero states none either. QEMU's
`qemu64` has no leaf 15H, and under nightly's `kvm / suite`'s
`-cpu host` whether a line is timed is the runner's CPU's to decide.

A second rate source is the fix, and the choice of one is open: the
Expand All @@ -35,7 +37,6 @@ Until then no guest test asserts a time on the screen: `screen_loader_clears`
reads `--.---` under `qemu64` and whatever the runner's CPU states under
`-cpu host`. The fix is where that test starts asserting a time in it.

Exit: a loader line and a kernel record written before `clock: HPET at …` carry
their time on an x86-64 CPU that states neither leaf 15H's ratio nor leaf 16H's
base frequency, and `screen_loader_clears` asserts that line's time on the
screen.
Exit: a loader line and a kernel record written before the clock starts carry
their time on an x86-64 CPU whose leaf 15H states no rate, and
`screen_loader_clears` asserts that line's time on the screen.

This file was deleted.

Loading