Skip to content
Draft
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 @@ -533,6 +533,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
77 changes: 0 additions & 77 deletions issues/ap-tsc-trail-is-assumed-and-never-checked.md

This file was deleted.

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.

Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ opened: 2026-10-12
# The x86 clock calibrates against an HPET, or the boot panics

`arch::boot::clock` (`kernel/src/arch/x86_64/boot.rs`) takes the HPET's base
from its ACPI table with `.expect("ACPI: HPET not found")`, and every rate the
kernel keeps descends from that one measurement: the TSC's
(`kernel/src/arch/x86_64/hpet.rs`) and the local APIC timer's, calibrated
against the TSC (`apic::init_timer`). CPUID 15H/16H is read after it, as a
cross-check only. A machine whose firmware publishes no `HPET` table stops
from its ACPI table with `.expect("ACPI: HPET not found")`, and the clock
needs it whatever the CPU states: it measures the TSC's rate against the HPET
where CPUID 15H states none, and holds the TSC to it while it runs, a stated
rate too (`kernel/src/arch/x86_64/clocksource.rs`); the local APIC timer is
calibrated against the TSC (`apic::init_timer`). A machine whose firmware publishes no `HPET` table stops
there, before a timer ticks, and before PCI is enumerated, so a console that
is a virtio function never carries the panic.

Expand Down Expand Up @@ -44,5 +44,6 @@ a third-party copy of ACPI 6.6 numbers them; 6.5 itself is unread.
**Owner**: whoever holds `issues/toyos-runs-in-any-open-source-hypervisor.md`.

**Exit**: a q35 boot with `-machine hpet=off` reaches `Boot: complete`, its
`TSC:` line naming the source its rate came from; and a machine that states no
source is refused by a line that names each one it looked for.
`clock:` line naming the source its rate came from and the reference that
holds it; and a machine that states no source is refused by a line that names
each one it looked for.
10 changes: 10 additions & 0 deletions kernel/loom/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ tlb-tags-enter-relaxed = []
# Never on by default and never reachable from a kernel build, which declares
# the same name only so `cfg` checking knows it.
seqlock-writer-fence-off = []
# The negative control for the clock page's latch. It drops the fence between
# the writer's move of readers to the other copy and its rewrite of the one
# they left (`sdk/abi/src/clock/latch.rs`), and `clock_latch.rs` must red:
#
# cargo test --manifest-path kernel/loom/Cargo.toml --features clock-latch-fence-off \
# --test clock_latch
#
# Never on by default; `toyos-abi` declares the same name only so `cfg`
# checking knows it.
clock-latch-fence-off = []

# The negative control for the console backend's lock. It builds
# `serial_lock.rs`'s `try_lock` with `then_some`, whose argument is built on a
Expand Down
6 changes: 6 additions & 0 deletions kernel/loom/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,12 @@ pub mod capture_access;
#[path = "../../../sdk/toyos/src/log/ring.rs"]
pub mod log_ring;

/// The clock page's latch, which the kernel replaces the clock's line through
/// while every process and the kernel's own NMIs read it: not the kernel's
/// file but the ABI's, driven by `tests/clock_latch.rs`.
#[path = "../../../sdk/abi/src/clock/latch.rs"]
pub mod clock_latch;

/// The seqlock the panic console's descriptor and each CPU's counters are
/// published through, driven by `tests/panic_console_publish.rs` and
/// `tests/counters_round.rs`.
Expand Down
61 changes: 61 additions & 0 deletions kernel/loom/tests/clock_latch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! Loom: a reader of the clock page's latch gets one line whole, and never an
//! older one after a newer.
//!
//! The line is three words a reader turns into a time; half of one
//! publication and half of the next is a clock that steps. One writer
//! replaces it (the kernel's retune) while a reader on another CPU reads it
//! with no lock, and a reader never waits for the writer. The negative control
//! is the `clock-latch-fence-off` feature, which must red this file.
#![cfg(feature = "loom")]

use kernel_loom::clock_latch::Latch;
use loom::sync::Arc;
use loom::thread;

const WORDS: usize = 3;

/// A bound so the two publications' twelve stores finish exploring; under it
/// the `clock-latch-fence-off` control still reaches a torn read.
const PREEMPTIONS: usize = 3;

fn model(f: impl Fn() + Sync + Send + 'static) {
let mut builder = loom::model::Builder::new();
builder.preemption_bound = Some(PREEMPTIONS);
builder.check(f);
}

/// Two publications, so the reader can meet either copy being rewritten.
#[test]
fn a_read_is_one_line_whole() {
model(|| {
let latch = Arc::new(Latch::<WORDS>::new());
latch.publish([1; WORDS]);
let writer = {
let latch = latch.clone();
thread::spawn(move || {
latch.publish([2; WORDS]);
latch.publish([3; WORDS]);
})
};
let line = latch.read();
assert!(line == [1; WORDS] || line == [2; WORDS] || line == [3; WORDS], "a read kept two lines' words: {line:?}");
writer.join().unwrap();
assert_eq!(latch.read(), [3; WORDS]);
});
}

#[test]
fn a_later_read_is_never_an_older_line() {
model(|| {
let latch = Arc::new(Latch::<WORDS>::new());
latch.publish([1; WORDS]);
let writer = {
let latch = latch.clone();
thread::spawn(move || latch.publish([2; WORDS]))
};
let first = latch.read();
let second = latch.read();
assert!(second[0] >= first[0], "a later read went back to an older line: {first:?} then {second:?}");
writer.join().unwrap();
});
}
Loading