Skip to content

Counters: every CPU answers a read for itself on its kick vector, under the counters and trace rights - #705

Merged
Japabu merged 6 commits into
mainfrom
wt/toyos-counters
Oct 4, 2026
Merged

Japabu merged 6 commits into
mainfrom
wt/toyos-counters

Conversation

@Japabu

@Japabu Japabu commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

PR 1 of the counters + ACPI stage 1 design (lane A1 of the observability strategy): the general counters, their read, and the one cross-CPU request round. Stage 1 of ACPI (PR 2) reads its SMI flatness through this table.

What lands

  • ABI. SYS_COUNTERS = 124 answers one toyos_abi::counters::Record per online CPU (56 bytes LE: CPU index, hardware id, flags, a word per counter), with SYS_DEVICE_INVENTORY's contract: an empty buffer is the CPU count and asks no CPU anything, a short one ResourceExhausted, one past the kernel's most CPUs InvalidArgument. Counter is stamp, smi, aperf, mperf, kicks; an absent counter has no word, never a zero; stale is a CPU that missed the read's bound, carrying the last block it published and the stamp it published it at. No CPU count is in the ABI. Rights::COUNTERS (bit 13) and Rights::TRACE (bit 14), ALL 0x7fff, each documented with what it discloses.
  • The rights, read once. demand_syscap answers every right the handle holds, read under the same guard that checked it, so SYS_COUNTERS looks its handle up once: COUNTERS refused ends there, and what else the capability holds decides which counters are answered.
  • The round. kernel/src/counters.rs: a read issues a generation of shootdown::Shootdown's protocol, then, under one IrqGuard, kicks every other CPU and answers for its own, so the CPU the kick skipped is the CPU answered for; it parks on an IrqWatch (watch::wait_until) until every CPU has answered or 100 ms after the issue. Each CPU answers from its kick handler on both architectures, wherever it was, by publishing its block through seqlock::Published<N> inside serve's closure. A read within 10 ms of the last issue waits on that round, so a holder makes no CPU take more than one kick per 10 ms. Never unbounded, never a panic. IrqWatch::post_in_place's contract names a thread's post, which the reader's own answer and an inbox's poll wake make.
  • The kick leaves the timer's vector. x86-64: vector 0xFC through device_irq_entry! (Ring 3 exit_to_user, mask-windows hooks), irq_census::Source::Kick; 0x20 is an expiry and nothing else. AArch64: SGI 0 counts as Kick, not Timer, and answers the round with the preempt count raised. Every IPI is raised after a fence ordering the stores it announces: cpu::wrmsr_fence (mfence; lfence) before every x2APIC ICR write (Linux's weak_wrmsr_fence), dsb ishst before ICC_SGI1R_EL1 with nomem gone from both asm blocks (Linux's gic_ipi_send_mask). This closes issues/kernel/an-ipi-can-overtake-the-stores-it-announces.md.
  • Which counters a CPU has. toyos_cpuvuln::counters over CounterFacts (vendor, signature, CPUID.1:ECX, CPUID.6:ECX), Linux arch/x86/events/msr.c at the crate's pinned tag (Ubuntu-6.8.0-142.142, 53e5d07aac02): APERF/MPERF where leaf 6's ECX bit 0 says, any vendor; MSR_SMI_COUNT on test_intel's Intel family 6 models, outside a hypervisor; the list is in table.rs beside the crate's other four tables. Each CPU decides on its own CPUID at bring-up (counters::bring_up, BSP after arch::boot::interrupts, each AP in process::ap_idle), and that bring-up's first sample reads each admitted MSR once, so a wrong verdict is #GP at boot and never inside a read. It logs counters: cpuN reads smi=… aperf=… mperf=….
  • The reader ships. inspect gains the kernel root (kernel.cpu.<n>.{stale,hardware_id,<counter>}, rendered by toyos_inspect::kernel), asked on the same SysCap as dev.*, taken once. SysCap::counters in the SDK. system.toml grants inspect counters and trace; the test estate's test-runner holds both in tests/testcases and tests/virtsmpcase; the supervisor's minted capability carries both, since rights only shrink from it (measured: without it the supervisor panicked narrowing inspect's duplicate).
  • Moved and deleted. The panic console's seqlock drivers/panic_console/published.rs becomes seqlock.rs, Published<const N>, its loom model and seqlock-writer-fence-off kept. Shootdown::serve_if_owed answers whether it served, so a kick that owes nothing posts nothing. Linux's turbostat readings of the T14 move beside their one reader, tests/t14-linux/, with a SOURCE carrying their commands; toyos-cpuvuln/fixtures/t14/SOURCE carries perf-msr.txt's; issues/hardware/linuxs-readings-of-the-t14-and-the-tcg-model-lack-reads-owed-before-the-t14s-wipe.md points at the fixtures instead of repeating them. Deleted: the kick on 0x20 (its Ring 0 branch counted kicks as timer expiries and re-armed the one-shot from scratch on every kick), the false quiesce.rs comment, Intid::Kick's "rides the timer's vector", the IPI-ordering issue, and issues/diagnostics/toyos-explains-itself.md's "nothing reads APERF, MPERF, MSR_SMI_COUNT".
  • Filed. issues/kernel/toyos-runs-the-t14-under-load-below-the-clock-linux-reaches.md (defect: 2318 MHz on every CPU against Linux's 3075–3800), issues/kernel/two-t14-cpus-idle-busier-under-toyos-than-any-under-linux.md (defect: cpu0 1.37%, cpu7 1.45% at ce1786ff0 and 1.35%, 1.42% at a059144e2 against Linux's 0.11–0.36%, not yet attributed), issues/design-debt/toyos-cpuvuln-has-one-consumer-and-lives-outside-it.md, and issues/hardware/a-boots-readback-is-lost-to-one-ssh-that-times-out.md (the Drive run's jobcase readback, below).

Decisions

  1. One round (roast BLOCKER 1). Open The kernel declares each CPU's HWP request, and the counters round reads the power envelope back under TRACE #590, parked and untouched, holds a second copy; when it is picked up it rides this one. Served from the kick handler, which a CPU in a long syscall still reaches once it opens interrupts; drain_irqs is not a service point.
  2. The rights. The owner's ruling, his words: "A program needs a permission to read counters; the revealing ones (power, per-device interrupts) need the strict diary permission. Admin tools get them; ordinary programs and the toolbox don't until each program can be granted rights on its own." COUNTERS reads the stamp and the SMI count; TRACE, the strict right, is needed beside it for APERF, MPERF and kicks. Putting frequency and the kick counts under the strict right is the design and roast's recommendation (APERF/MPERF are the Hertzbleed power channel; per-CPU wake counts time keystrokes), not his words. The two things he named are not in this table: energy (RAPL) is C2's, and per-device interrupt counts stay where they are, in the irq: line. TRACE is the bit the trace track's step 1 will read its ring under; its syscall takes the next number, so nothing renumbers. Two ways round the strict right stand until the umbrella track's exit, for the owner's veto:
    • inspect holds trace, and issues/isolation/the-launcher-starts-any-row-for-any-holder.md lets every launcher holder start any row: the shell, the terminal, the compositor and every sshserver login can run inspect kernel.* and read every CPU's frequency, busy fraction and kick count, at up to 100 Hz (one round per 10 ms join).
    • The irq: line gains kick= per CPU, and every process exit prints that line into the log, which any program can read today (no per-program file views). So the per-CPU kick counts TRACE gates in this table are readable without it, as i8042= already is and as the same kicks were inside timer= before this branch. Moving that line behind the strict right is the umbrella track's exit (issues/diagnostics/toyos-explains-itself.md).
  3. No kernel-msr crate (roast SHRINK): toyos-cpuvuln already decodes vendor, family, model and the hypervisor bit at the same pin. Two named choices: MSR_SMI_COUNT refused under a hypervisor departs from Linux (a hypervisor answers its own count for the guest, QEMU's env->msr_smi_count), and APERF/MPERF are admitted under one as Linux admits them, so a hypervisor that enumerates them and faults the read ends the boot that read them first. MSR_SMI_COUNT is masked to 32 bits (SDM: 63:32 reserved; msr.c:254-256 sign-extends from bit 31).
  4. The design's m2 is deleted (roast BLOCKER 3): QEMU 11.1.1's TCG answers MSR_SMI_COUNT from env->msr_smi_count and every unknown MSR with 0 (target/i386/tcg/system/misc_helper.c, read at the pinned version), so no guest reds on a gate admitting too much. The gate is held on the host instead, and the bring-up read is checked only by reading.
  5. No round in the record (roast SHRINK); stale says it. The hardware id stays, for its reader: nothing else in the ABI maps a CPU index to the core and thread it is, and inspect kernel.cpu.<n>.hardware_id is how a per-CPU reading is matched to what the firmware's MADT, the kernel's own SMP: AP cpuN lapic= line and Linux's turbostat (Core/CPU, SMT siblings) call that CPU; on a hybrid or SMT part a busy fraction means nothing without it. The counters row checks it against the bring-up's LAPIC ids, which is also what shows each CPU answered for itself.
  6. The silent CPU is staged by SYS_DEBUG (COUNTERS_DEAF, COUNTERS_HEAR, test kernel only), not by the dump's deaf window as the roast's NOTE proposed. That window opens on the kernel's own clock 3 s into a boot for 400 ms and is judged on metal alone because a guest the host starves misses it; no reader can be placed inside it on purpose. The staged CPU answers no round until heard, which a reader cannot tell from interrupts closed past the bound, and it counts nothing, so the "first three serves" flake the roast named cannot arise. It is a second deaf actuator, beside the dump's, kept to what the reader sees.
  7. Ring 0 expiries are not in this table. A2's exit reads them and A2 changes that stub; it adds the slot. Moving the kick already stops ring0_timer_fires and TimerFireBurst counting kicks.
  8. Policy numbers [e]: the 100 ms answer bound and the 10 ms join. A park, not a spin: two readers cannot hold each other, and no reader spins with interrupts closed.

Where the size lands

git diff --shortstat origin/main...a059144e2 (origin/main at 3f1db70): 68 files, +1893 −339, the issue reconciliation and the moved fixtures included. Production code (non-blank, non-comment lines in kernel/src, toyos-abi/src, toyos-cpuvuln/src, toyos-inspect/src, toyos/src, userland/inspect/src, toyos-manifest/src, less their unit-test modules and the test-only actuator), counted at round 1: about 440 added, 41 deleted, about 400 net, beside about 230 doc lines; round 2 removed four lines of it (one lookup, not two). The strategy budgeted 250–400 [e]; this is at its top, by what the estimate did not carry: the strict right as a second right with its gating, the kick vector moved with a fence on both architectures, and the reader rendering kernel.* inside the shipped inspect. Cut on the way: the kernel-msr crate, a second round, a second seqlock, the round field, Ring 0 expiries, a separate T14 boot, and a one-CPU AArch64 guest case (below: it tested a latency).

Gates

At cba61e819, round 4's one issue edit (the idle-busy reading promoted to kind: defect), no code changed, origin/main unmoved; log under orch/counters4/:

  • cargo run -- --ci host: EXIT=0, Host: 73 step(s), all green (ci-host.log:7288).

At ee468ce76, the merge of origin/main (21f06e8: #707, issues only) and round 3's issue edits, no code changed; log under orch/counters3/:

  • cargo run -- --ci host: EXIT=0, Host: 73 step(s), all green (ci-host.log:7289).

At a059144e2, the merge of origin/main (3f1db70: #700, #702, #704) and round 2's fixes; logs under the orchestrator's scratchpad, orch/counters2/:

  • cargo run -- --build-only: EXIT=0 (build-1.log).
  • cargo run -- --ci host: EXIT=0, 73 steps green, the loom model counters_round and its shootdown-served-relaxed control among them (ci-host.log).
  • cargo test --test toyos-build (the whole QEMU suite; the kick move touches every wake): EXIT=0, 26 of 26 (guest-suite.log).
  • The new lookup's mutation, m13 below: EXIT=1 (mut/m13.virt_smp.log, mut/summary.txt).
  • T14, the orchestrator's run at this head (Counters: every CPU answers a read for itself on its kick vector, under the counters and trace rights #705 (comment)): the kernel changed in round 2, so the counters row's boot and the shared boots carrying test_rs_counters_read and test_rs_counters_silent were staged again (metal-stage.log, metal/request.txt) and flashed in order, sha256 recorded at flash: testcases (the counters row) 97b2dd0feb677507e8718d75f0d2faba61102911cd3bc6c981362c683c2d2136, shared (test_rs_counters_read) 327b9b5df9291e3e6de8afea9cfe16520c940661c0e639833416296a4e4160af, shared-debug (test_rs_counters_silent) 5bbd06b0649d9fd5f792c3e2fc8d8c155886890620a3b8e3986164153c6f26bf; each boot passed. Judged from the clean head: cargo test --test toyos-build -- --metal --metal-readback orch/counters2/metal counters, EXIT=0, [metal] 3 passed, 0 failed, 3 boot(s), PASS counters. Every CPU reports smi=true aperf=true mperf=true. Readbacks orch/counters2/metal/{testcases,shared,shared-debug}/, judge log orch/counters2/judge-t14-counters.log.

At ce1786ff0 (round 1), logs under orch/counters1/:

  • cargo run -- --ci host: EXIT=0, 73 steps (ci-host-rebased.log).
  • cargo test --test toyos-build: EXIT=0, 26 of 26 (guest-suite-rebased.log).
  • Loom, cargo test -p kernel-loom --test counters_round --test panic_console_publish --test tlb_shootdown: EXIT=0 (loom-green.log); --features shootdown-served-relaxed --test counters_round: EXIT=101, a_cpu_read_as_answered_is_read_with_its_answer red, the reader found the round answered and its snapshot None (loom-served-relaxed.log).
  • The x86-64 members under QEMU (the scratch preflight, TCG, 2 CPUs): test_rs_counters_read and test_rs_counters_silent exit 0, EXIT=0; each CPU logged counters: cpuN reads smi=false aperf=false mperf=false (preflight-final.log).
  • Host tests new in this branch, in --ci host: toyos-abi counters (6), toyos-cpuvuln counters (5), toyos-inspect kernel (4).
  • T14, the orchestrator's run (Counters: every CPU answers a read for itself on its kick vector, under the counters and trace rights #705 (comment)): the whole metal profile, cargo test --test toyos-build -- --metal in Drive mode, 26 boots in 2437 s: [metal] 275 passed, 4 failed, 26 boot(s) (drive/counters-full.log, readbacks drive/counters-full-readback/). The four reds are the jobcase boot's rows (usb_reset_hands_devices_back, blackbox_done_chain, machine_reboot, machine_soft_off_decoded), each toyos-metal exited exit status: 2: reading a log file on the machine exited exit status: 255: Connection timed out during banner exchange: the machine answered ssh 48 s after its reboot and the stick enumerated, then the read's ssh timed out, the readback lost and no row's verdict red. The staged jobcase image (sha256 22065d4f3cd282f99e853c3aba80d7a0a6bd0a215d019360d512ee11176a245e, checked in the command that flashed) was booted again (readback metal/jobcase/) and judged from the clean head: blackbox_done_chain, machine_reboot, machine_soft_off_decoded each EXIT=0, 1 passed, 0 failed, 1 boot(s); usb_reset_hands_devices_back, which spans jobcase and metaldevicecase, over that readback and the Drive run's metaldevicecase: EXIT=0, 1 passed, 0 failed, 2 boot(s) (judge-t14-*.log). 279 of 279 green. test_rs_counters_read and test_rs_counters_silent passed in shared and shared-debug (drive/counters-full.log:16573-16615, :23966-23970).

High-risk checks (ABI, concurrency, a security boundary)

Negative control. The whole change reverted onto its base cannot build the guest binaries that read it (SYS_COUNTERS is not in main's ABI), so it is refused at the missing syscall and says nothing; the controls are the mutations and the loom feature. Each mutation was a checked patch (git apply --check), built, run and reversed, the tree clean after each; the patches are posted as comments. Guest mutations of round 1 ran a scratch QEMU preflight (never committed) booting tests/testcases with test_rs_counters_read on the shipping kernel and test_rs_counters_silent on the test kernel (x86-64, TCG, 2 CPUs), and the AArch64 ones virt_smp (8 CPUs):

mutation run exit red at
m1 the reader fills every CPU's block itself x86 preflight / virt_smp 1 / 1 "2 cpus named hardware id 1" / the job exited 101 (the kernel's exit record) and the harness stalled on its end line
m3 the kick sent on the timer's vector x86 preflight 1 counters_read never returned (other CPUs never answer); ended by hand after 6 min, green takes 4 s
m4 the wait has no bound x86 preflight 1 counters_silent never returned; ended by hand after 16 min
m5 the reader does not answer for its own CPU x86 preflight / virt_smp / a one-CPU AArch64 case 0 / 0 / 0 green: the wake ending the reader's park kicks its CPU, and the kick answers. On one CPU each read waited its bound (29 s against 6 s), a latency no guest test may assert. Checked by reading; the one-CPU case was deleted
m7 the kick counted as the timer x86 preflight / virt_smp 1 / 1 "0 of 2 cpus took a kick between two rounds"
m8 TRACE not demanded x86 preflight / virt_smp 1 / 1 "COUNTERS alone was answered cpu0's kicks"
m12 a missed bound not said x86 preflight 1 "cpu1 answers no round and was read fresh"
m13 (round 2) the one lookup answers TRACE it did not read off the handle cargo test --test toyos-build -- virt_smp at a059144e2 1 "COUNTERS alone was answered cpu0's kicks" (counters_read.rs:116)
h1 SMI admitted on any vendor cargo test -p toyos-cpuvuln counters 101 an_intel_model_off_the_list_counts_no_smis (the T14's family and model on Centaur)
h2 SMI admitted under a hypervisor same 101 a_guest_counts_no_smis

The fences have no control: no QEMU guest reds on them, TCG and HVF not modelling the ordering, as the closed issue said; they are checked by reading against the SDM ("MSR Access in x2APIC Mode") and Linux. The kick and the self-answer under one guard have no control either: the window it closes is a reader migrating between the two, which needs interrupts on inside a syscall ("interrupts on in syscalls" is not landed), so no run reaches it; checked by reading.

Independent oracles. Linux's msr.c at the pinned commit (the model list, test_aperfmperf, the 32-bit SMI count) and scattered.c (leaf 6's range check); Linux on the T14 itself, perf stat -a -A over the msr PMU's events, committed as toyos-cpuvuln/fixtures/t14/perf-msr.txt with its command in SOURCE: the events Linux's probe listed are exactly aperf, mperf, smi and tsc, which the_t14_has_what_linux_counted_on_it holds the T14's verdict to; QEMU 11.1.1's target/i386/cpu.c for the TCG model's facts (qemu64 is AuthenticAMD family 0xF model 0x6B, leaf 6's ECX zero on every model), which the TCG guest's own counters: cpuN reads smi=false aperf=false mperf=false agreed with; loom for the one ordering edge x86 hides; the T14, with Linux's turbostat on the same machine committed beside the row that reads it.

The T14 row

counters rides the shared testcases boot (the roast folded Boot 1 into the full run): test_rs_counters_metal reads every CPU at idle0, after an idle second at idle1, and after a fixed spin on a thread per CPU at spin, printing inspect kernel.*'s own lines. Held: one record per CPU the bring-up started, each naming the LAPIC id the bring-up gave it and carrying the counters its counters: cpuN reads line names, none stale; from idle0 to spin, refused if under 4.444 s apart, every CPU's SMI count rose alike and by two or more, the firmware's legacy mode as the firmware issue measured it, the positive control ACPI stage 1's flatness is read against (that stage changes this row); across the spin every CPU's MPERF ran 0.9 [e] of its stamp or more (MPERF counts at the TSC's rate in C0, SDM). Read, not held: each CPU's idle busy fraction and its busy frequency under the spin, beside Linux's turbostat (tests/t14-linux/turbostat-*.txt: idle Busy% 0.11–0.36 across its summaries, loaded Bzy_MHz 3075–3800), and what the spin's round cost its reader.

What it read at ce1786ff0 (drive/counters-full.log:29934-29943, PASS counters): 8 CPUs, SMI +9 each over 19575 ms, TSC 2419 MHz; MPERF busy across the spin 0.986–0.990 on every CPU; spinning 2318 MHz on every CPU against Linux's 3075–3800; idle busy 1.37% on cpu0 and 1.45% on cpu7 against Linux's 0.11–0.36%, cpu3 0.11%, the other five 0.00–0.01%; a whole round cost its reader 17768 ns. What it read at a059144e2 (orch/counters2/judge-t14-counters.log:19-28, PASS counters): 8 CPUs, SMI +9 each over 19875 ms, TSC 2419 MHz; MPERF busy across the spin 0.985–0.990 on every CPU; spinning 2318 MHz on every CPU against Linux's 3075–3800; idle busy 1.35% on cpu0, 1.42% on cpu7 and 0.37% on cpu4 against Linux's 0.11–0.36%, cpu3 0.11%, the other four 0.00–0.03%; a whole round cost its reader 18548 ns. The second boot reads the same two busy CPUs and the same clock. The frequency and the idle busy fraction are filed (above), not fixed here.

test_rs_counters_read and test_rs_counters_silent (the test kernel's shared boot) are shared-boot members on the T14, the tier below a QEMU guest.

Guest tests, and why QEMU

virt_smp (and every boot of tests/virtsmpcase) runs test_rs_counters_read on AArch64: SGI 0 answering the round on eight CPUs, the syscall's refusals, a handle nobody holds ending the caller, twice as many readers as CPUs all returning. No type or host test reaches an interrupt handler or the syscall boundary, and the T14 is x86-64, so AArch64's kick is reached only here. x86-64's are metal members, not QEMU tests.

What each new check sees that reading cannot

counters_round (loom): the served acquire that makes a copied block this round's, invisible on x86's TSO; shootdown-served-relaxed is its control in src/ci.rs. The guest members: that every CPU answers for itself through the kick, that a missed bound is said, that the rights are demanded at the boundary. The host tests: Linux's verdict on fixture facts. The metal row: what the hardware counts.

New dependency

The kernel depends on toyos-cpuvuln, ToyOS's own no_std, forbid(unsafe) crate; no third-party crate arrives. It now has one consumer and stays at the root, outside this brief: issues/design-debt/toyos-cpuvuln-has-one-consumer-and-lives-outside-it.md records it with its owner and exit.

Unsure

  • The 100 ms and 10 ms policy numbers [e], and whether a round's wake of idle CPUs is audible (D4): the row reads one round's cost, nothing judges it.
  • The MPERF 0.9 bound [e], and SMI alike on every CPU: the T14 read +9 on all eight; a round's answers straddle an SMI only if one lands in the microseconds between two CPUs' answers.
  • Hybrid CPUs' per-core verdicts are untested; each CPU decides on its own CPUID.
  • The self-answer is a latency (m5).

Interplay

#590 (parked) takes this round over when picked up. A2 consumes the kick vector and adds Ring 0 expiries to this table; with the kick off 0x20 its "a staged kick leaves Ring 0 expiries unchanged" can hold. The trace track's step 1 reads its ring under TRACE. PR 2 reads SMI flatness through this table and changes the counters row's SMI gate.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8

…er the counters and trace rights

`SYS_COUNTERS` answers one record per online CPU: its own counter stamp,
its firmware-interrupt count (`MSR_SMI_COUNT`), `APERF` and `MPERF`, and
the kicks it took, each read by that CPU itself. A read is a round of
`shootdown::Shootdown`'s protocol: the reader issues a generation, kicks
every other CPU, answers for its own, and parks on a watch until every CPU
has answered or 100 ms after the issue; a CPU silent past that is stale
with the last block it published. Rounds within 10 ms of an issue are one.

The kick leaves the timer's vector: x86-64 raises it on 0xFC through
`device_irq_entry!`, AArch64's SGI 0 counts as a kick, and both kick
handlers answer the round. Every IPI is raised after a fence that orders
the stores it announces (`mfence; lfence` before the x2APIC ICR write,
`dsb ishst` before `ICC_SGI1R_EL1`), which closes
`issues/kernel/an-ipi-can-overtake-the-stores-it-announces.md`.

Which model-specific counters a CPU has is `toyos_cpuvuln::counters`, Linux
`arch/x86/events/msr.c` at the pinned Ubuntu tag, decided by each CPU over
its own CPUID at bring-up, whose first sample reads each admitted MSR once.
`MSR_SMI_COUNT` is refused under a hypervisor, a departure from Linux.

`Rights::COUNTERS` reads the stamp and the SMI count; `Rights::TRACE`
beside it the counters that time programs. `inspect` and `test-runner`
hold both, and `inspect kernel.*` renders them.

The panic console's seqlock becomes `seqlock::Published<N>`; a loom model
holds the reader's `served` acquire to the answer it copies, with its
negative control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
@Japabu

Japabu commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

Mutation patches for this branch, each applied as a checked patch (git apply --check), built, run, and reversed (git apply -R) by a scratch driver; the tree was clean after each (git status --porcelain empty). Guest mutations ran under a scratch QEMU preflight (preflight.patch below, never committed) that boots tests/testcases and runs test_rs_counters_read on the shipping kernel and test_rs_counters_silent on the test kernel, x86-64 TCG, 2 CPUs; the AArch64 ones also ran virt_smp (8 CPUs). Results are in the pull request body.

preflight.patch

diff --git a/tests/toyos.rs b/tests/toyos.rs
index 464e5fe5a..2382c57c0 100644
--- a/tests/toyos.rs
+++ b/tests/toyos.rs
@@ -208,6 +208,7 @@ const MACHINE_TESTS: &[&str] = &[
     // no way to turn it back on, so only a machine QEMU reports stopping can
     // be asked. `machine_soft_off_decoded` reads the T14's own decode.
     "machine_shutdown",
+    "zz_counters_preflight",
 ];
 
 /// **The metal profile**: which registrations run on the ThinkPad T14, what
@@ -2406,6 +2407,18 @@ fn run_machine_test(name: &str, test_config: &Path) -> Result<(), String> {
         "iommu_virtio_platform" => common::iommu::iommu_virtio_platform(test_config),
         "nested_nmi_is_loud" => faults::nested_nmi_is_loud(test_config),
         "machine_shutdown" => power::machine_shutdown(test_config),
+        "zz_counters_preflight" => {
+            let dir = compile::repo_root().join("tests/toyos-rust-tests");
+            let runs: [(&[&str], &str); 2] = [(&[], "counters_read"), (toyos_build::build::TEST_KERNEL, "counters_silent")];
+            for (features, bin) in runs {
+                let bins = vec![(bin.to_string(), qemu::build_toyos_bin(common::qemu::SUITE_ARCH, &dir, bin))];
+                let mut q = QemuInstance::boot_with_options(test_config, &[], &bins, BootOptions { kernel_features: features, ..Default::default() });
+                let r = q.run_test(&format!("test_rs_{bin}"), Duration::from_secs(180));
+                eprintln!("  [preflight] {bin}: exit {:?} error {}\n{}", r.exit_code, r.error.is_some(), r.stdout);
+                if r.exit_code != Some(0) { return Err(format!("{bin} exit {:?}", r.exit_code)); }
+            }
+            Ok(())
+        }
         other => Err(format!("unknown machine test {other}")),
     }
 }

h1-smi-any-vendor.patch

diff --git a/toyos-cpuvuln/src/table.rs b/toyos-cpuvuln/src/table.rs
index 8b748b5fe..25b250b51 100644
--- a/toyos-cpuvuln/src/table.rs
+++ b/toyos-cpuvuln/src/table.rs
@@ -376,5 +376,5 @@ pub(crate) fn counts_smis(id: &Ident) -> bool {
         ALDERLAKE, ALDERLAKE_L, ATOM_GRACEMONT, RAPTORLAKE, RAPTORLAKE_P, RAPTORLAKE_S,
         METEORLAKE, METEORLAKE_L,
     ];
-    id.vendor == Vendor::Intel && id.family == 6 && MODELS.contains(&id.model)
+    id.family == 6 && MODELS.contains(&id.model)
 }

h2-smi-under-hypervisor.patch

diff --git a/toyos-cpuvuln/src/counters.rs b/toyos-cpuvuln/src/counters.rs
index 4ad06afcb..c1f9d64cc 100644
--- a/toyos-cpuvuln/src/counters.rs
+++ b/toyos-cpuvuln/src/counters.rs
@@ -41,8 +41,7 @@ pub fn counters(facts: &CounterFacts) -> Counters {
         // `test_aperfmperf` (`msr.c:20-23,158-159`), whatever the vendor.
         aperf_mperf: facts.cpuid_6_ecx & CPUID_6_ECX_APERFMPERF != 0,
         // `test_intel` (`msr.c:161`), outside a hypervisor.
-        smi: table::counts_smis(&Ident::new(facts.vendor, facts.signature))
-            && facts.cpuid_1_ecx & CPUID_1_ECX_HYPERVISOR == 0,
+        smi: table::counts_smis(&Ident::new(facts.vendor, facts.signature)),
     }
 }
 

m1-reader-fills-every-block.patch

diff --git a/kernel/src/counters.rs b/kernel/src/counters.rs
index 6f8e82917..0cd93fecc 100644
--- a/kernel/src/counters.rs
+++ b/kernel/src/counters.rs
@@ -139,11 +139,13 @@ pub fn read(rights: Rights, out: &mut UserBytesMut) -> Result<usize, SyscallErro
             }
         }
     };
-    if fresh {
+    if fresh && false {
         irqchip::kick_all_but_self();
     }
-    // This CPU's kick, if it has one, waits behind this syscall.
-    serve_here();
+    let me = percpu::cpu_id() as usize;
+    for cpu in 0..cpus {
+        ROUNDS.serve(cpu, || BLOCKS[cpu].publish(sample(me)));
+    }
     let answered = || (0..cpus).all(|cpu| ROUNDS.served(cpu, generation));
     let deadline = Deadline::at(issued + ANSWER.duration());
     if watch::wait_until(&Parkable::at_entry(), &ANSWERED, 0, WaitClass::Other, deadline, answered).is_err() {

m3-kick-on-the-timer-vector.patch

diff --git a/kernel/src/arch/x86_64/apic.rs b/kernel/src/arch/x86_64/apic.rs
index 68ad476b6..ffff11ad0 100644
--- a/kernel/src/arch/x86_64/apic.rs
+++ b/kernel/src/arch/x86_64/apic.rs
@@ -157,7 +157,7 @@ pub(super) fn tlb_ipi() {
 pub fn kick_cpu(cpu_id: u32) {
     if !X2APIC_ENABLED.load(Ordering::Relaxed) { return; }
     let apic_id = crate::smp::hardware_id(cpu_id);
-    send(((apic_id as u64) << 32) | 0x4000 | u64::from(super::idt::KICK_VECTOR));
+    send(((apic_id as u64) << 32) | 0x4000 | u64::from(if true { TIMER_VECTOR } else { super::idt::KICK_VECTOR }));
 }
 
 // Kicked, and not left to arrive on their own: a CPU halted in the idle path has stopped its own timer, so nothing else brings it to the next scheduler pass.

m4-wait-without-bound.patch

diff --git a/kernel/src/counters.rs b/kernel/src/counters.rs
index 6f8e82917..117fe4a33 100644
--- a/kernel/src/counters.rs
+++ b/kernel/src/counters.rs
@@ -145,7 +145,7 @@ pub fn read(rights: Rights, out: &mut UserBytesMut) -> Result<usize, SyscallErro
     // This CPU's kick, if it has one, waits behind this syscall.
     serve_here();
     let answered = || (0..cpus).all(|cpu| ROUNDS.served(cpu, generation));
-    let deadline = Deadline::at(issued + ANSWER.duration());
+    let deadline = if true { Deadline::never() } else { Deadline::at(issued + ANSWER.duration()) };
     if watch::wait_until(&Parkable::at_entry(), &ANSWERED, 0, WaitClass::Other, deadline, answered).is_err() {
         return Err(SyscallError::Gone);
     }

m5-reader-skips-its-own-cpu.patch

diff --git a/kernel/src/counters.rs b/kernel/src/counters.rs
index 6f8e82917..fbebd77f6 100644
--- a/kernel/src/counters.rs
+++ b/kernel/src/counters.rs
@@ -142,8 +142,6 @@ pub fn read(rights: Rights, out: &mut UserBytesMut) -> Result<usize, SyscallErro
     if fresh {
         irqchip::kick_all_but_self();
     }
-    // This CPU's kick, if it has one, waits behind this syscall.
-    serve_here();
     let answered = || (0..cpus).all(|cpu| ROUNDS.served(cpu, generation));
     let deadline = Deadline::at(issued + ANSWER.duration());
     if watch::wait_until(&Parkable::at_entry(), &ANSWERED, 0, WaitClass::Other, deadline, answered).is_err() {

m7-kick-counted-as-timer.patch

diff --git a/kernel/src/arch/aarch64/trap.rs b/kernel/src/arch/aarch64/trap.rs
index 69c8f725f..988ad45bb 100644
--- a/kernel/src/arch/aarch64/trap.rs
+++ b/kernel/src/arch/aarch64/trap.rs
@@ -181,7 +181,7 @@ fn irq(from_el0: bool) {
         irqchip::SGI_HALT => cpu::halt(),
         irqchip::SGI_OFF => super::power::cpu_off(),
         irqchip::SGI_KICK => {
-            percpu::irq_took(Source::Kick);
+            percpu::irq_took(Source::Timer);
             // Raised as an interrupt entry raises it, so the answer's post runs no pass here.
             percpu::preempt_count_up();
             crate::counters::serve_here();
diff --git a/kernel/src/arch/x86_64/idt/kick.rs b/kernel/src/arch/x86_64/idt/kick.rs
index 7e957753a..27767e243 100644
--- a/kernel/src/arch/x86_64/idt/kick.rs
+++ b/kernel/src/arch/x86_64/idt/kick.rs
@@ -1,7 +1,7 @@
 use super::device_irq::device_irq_entry;
 
 extern "sysv64" fn kick_handler() {
-    crate::arch::percpu::irq_took!(Kick);
+    crate::arch::percpu::irq_took!(Timer);
     crate::counters::serve_here();
     crate::preempt::set_need_resched();
     crate::arch::apic::eoi();

m8-trace-not-demanded.patch

diff --git a/kernel/src/counters.rs b/kernel/src/counters.rs
index 6f8e82917..fb351167f 100644
--- a/kernel/src/counters.rs
+++ b/kernel/src/counters.rs
@@ -161,7 +161,7 @@ fn record(cpu: usize, generation: Generation, rights: Rights) -> Record {
     let words = BLOCKS[cpu].snapshot();
     let mut values = [None; Counter::COUNT];
     if let Some(words) = &words {
-        for counter in Counter::ALL.into_iter().filter(|&c| held(words, c) && rights.contains(c.needs())) {
+        for counter in Counter::ALL.into_iter().filter(|&c| held(words, c) && (true || rights.contains(c.needs()))) {
             values[counter as usize] = Some(words[2 + counter as usize]);
         }
     }

m12-stale-never-said.patch

diff --git a/kernel/src/counters.rs b/kernel/src/counters.rs
index 6f8e82917..31c0538df 100644
--- a/kernel/src/counters.rs
+++ b/kernel/src/counters.rs
@@ -168,7 +168,7 @@ fn record(cpu: usize, generation: Generation, rights: Rights) -> Record {
     Record {
         cpu: cpu as u32,
         hardware_id: words.map_or(0, |w| w[0] as u32),
-        stale: !fresh || words.is_none(),
+        stale: (!fresh && false) || words.is_none(),
         values,
     }
 }

@Japabu

Japabu commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

T14 at ce1786ff0: the full profile, 279 of 279 rows green once one lost readback is re-booted.

  1. Full profile, Drive mode (cargo test --test toyos-build -- --metal, 26 boots, 2437 s): [metal] 275 passed, 4 failed, 26 boot(s). The four reds are exactly the jobcase boot's rows (usb_reset_hands_devices_back, blackbox_done_chain, machine_reboot, machine_soft_off_decoded), each with toyos-metal exited exit status: 2: reading a log file on the machine exited exit status: 255: Connection timed out during banner exchange. The machine had answered ssh again 48 s after the reboot and the stick had enumerated; the readback's next ssh to the T14 then timed out (the development machine's network dropped in the same minutes — an unrelated git pull failed with Connection reset by peer). Not about this diff; the harness losing a boot's record to one ssh timeout is filed separately.

  2. The staged jobcase image re-booted (image sha256 22065d4f3cd282f99e853c3aba80d7a0a6bd0a215d019360d512ee11176a245e, checked in the command that flashed): boot passed. Judged alone from the clean head: blackbox_done_chain, machine_reboot, machine_soft_off_decoded each EXIT=0 1 passed, 0 failed, 1 boot(s); usb_reset_hands_devices_back spans jobcase and metaldevicecase, so it was judged over this jobcase readback plus the Drive run's metaldevicecase readback (same head): EXIT=0 1 passed, 0 failed, 2 boot(s).

Logs: orch/counters1/drive/counters-full.log, readbacks orch/counters1/drive/counters-full-readback/, orch/counters1/metal/jobcase/, judges orch/counters1/judge-t14-*.log; the rows the Drive run recorded are in orch/counters1/drive/counters-full.record-rows.patch.

@Japabu

Japabu commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 1 — #705 at ce1786ff0

Round 1: there are no earlier BLOCKERs.

Net lines (git diff --shortstat origin/main...ce1786ff0): 59 files, +1762 −143. Production files: +1032 −82, of which about 205 added lines are in-file unit tests (toyos-abi counters, toyos-cpuvuln counters, toyos-inspect kernel). Harness, loom, fixtures and build: +730 −30. Issues: −31. I accept the production growth on the body's account. Nothing I found could be deleted without weakening a check.

The deleted one-CPU guest case (m5): deleting it was right. With serve_here() gone, a read still comes back fresh, because the wake that ends the park kicks the reader's CPU and that kick answers. On one CPU it costs the 100 ms bound per read (29 s against 6 s). That is a latency, and a QEMU test may not assert one. The case was added and removed inside this branch, so it cuts nothing main had. The line stays without a test that reds on it. That is acceptable only because removing it costs time and never correctness, and the body's Unsure says so.

The trace placement: the body says plainly that APERF/MPERF and the kick counts under TRACE are the design's and the roast's, not his. The ruling is quoted word for word.

The T14 evidence (comment 5975503047): I accept it. The four reds are the jobcase boot's readback lost to an ssh banner timeout. No row's verdict was red. The same image (sha256 checked) was booted again and judged green. The counters row ran in the testcases boot of the Drive run and passed there, and test_rs_counters_read and test_rs_counters_silent passed in shared and shared-debug (drive/counters-full.log:16573-16615, :23966-23970).

BLOCKER

  • PR body, "Gates" / "The T14 row" — the body at this head still says the T14 run is "EXIT=2, staged and not run". It does not carry the hardware reading, and it names no log for the host, guest, loom or preflight claims. Why it blocks: a claim stands only on a measurement in the body (command, exit code, log), and a hardware-targeted change needs its hardware reading there. Put in:
    • the Drive run's command, its 275 passed, 4 failed, the jobcase re-boot and its four judges, with log paths;
    • the counters row's own lines (drive/counters-full.log:29934-29943): SMI +9 each over 19575 ms, MPERF busy 0.986–0.990, spinning 2318 MHz on every CPU against Linux's 3075–3800, idle busy 1.37% on cpu0 and 1.45% on cpu7 against Linux's 0.11–0.36%, and a whole round costing its reader 17768 ns;
    • the log paths behind ci-host-rebased.log (73 steps, EXIT=0), guest-suite-rebased.log (26 of 26, EXIT=0), loom-green.log, loom-served-relaxed.log and preflight-final.log.

NOTE

  • drive/counters-full.log:29935-29942 — the T14 row measured ToyOS spinning at 2318 MHz under full load, where Linux reaches 3075–3800 MHz on the same machine, and idling at 4× Linux's busy fraction on cpu0 and cpu7 — this was found off the task and is not filed. File it in issues/ with these numbers as evidence: either a new defect or added to issues/kernel/the-scheduler-and-the-clocks-never-talk.md, which says only that P-states are never set.
  • kernel/src/syscall/machine.rs:284-288 — the handle is looked up twice, and the second lookup's Err(_) becomes "COUNTERS only" — a sibling thread that closes the handle between the two lookups gets an answer, and every error kind is swallowed silently. The comment above it asserts something the code does not guarantee. Do one lookup that returns the handle's rights, and drop the comment.
  • kernel/src/irq_census.rs:21,54 — kick= goes into the irq: line. Every process exit prints that line into the log, which any program can read today (no per-program file views), and none of it needs TRACE — the body classes per-CPU kicks as TRACE-only, so this route reads them without the right. i8042= already leaks more through the same channel, and the umbrella track's exit owns moving it. Decision 2 should say this gate can be bypassed until then. As written it says the counts "stay where they are" but does not say the new field can be read without TRACE.
  • PR body, Decision 2 — the roast asked that the decision say inspect holding trace gives every launcher holder (shell, terminal, sshserver logins) frequency, busy fraction and per-CPU kicks at up to 100 Hz. The body does not say it. Without it, the owner's veto is uninformed.
  • toyos-cpuvuln/fixtures/t14/SOURCE:17 and the body's Unsure — "the command lines were not kept" is false after the merge with main: since The owner's 2026-10-03 rulings are recorded in their tracks, quoted, under one observability umbrella #704, issues/hardware/linuxs-readings-of-the-t14-and-the-tcg-model-lack-reads-owed-before-the-t14s-wipe.md holds these same readings with their command lines, and it says "No test reads them yet". Reconcile both in the merge: SOURCE takes the commands, and the issue points at the fixtures instead of repeating them.
  • issues/diagnostics/toyos-explains-itself.md:12-13 (on main) — "nothing reads APERF, MPERF, MSR_SMI_COUNT" becomes false of the tree when this lands. Correct it in the merge with origin/main (VT-d: a moved context entry is invalidated under the domain it held, in one 16-byte write, and GCMD follows §11.4.4.1 #700, SYS_SPAWN opens the program's path, never argv[0], and a launched row runs its own program #702 and The owner's 2026-10-03 rulings are recorded in their tracks, quoted, under one observability umbrella #704 are not merged in yet; git merge-tree is clean).
  • toyos-cpuvuln/fixtures/t14/turbostat-idle.txt, turbostat-loaded.txt — the only reader is tests/toyos.rs's counters_on_metal. They belong with that reader, not in a crate that never reads them.
  • kernel/Cargo.toml:498 — the kernel becomes toyos-cpuvuln's only consumer, so Every crate with one consumer moves under it, and the track closes #703's convention now applies. The body names this compromise but nothing in issues/ records it with an owner and an exit. Record it, or move the crate.
  • toyos-abi/src/counters.rs:85 — the body's only reason for keeping hardware_id in the ABI is that a test sees each CPU answered for itself. Nothing ships for a test alone. State the product reason (mapping a CPU index to its APIC id or MPIDR in inspect), or cut it.
  • kernel/src/counters.rs:143-146 — kick_all_but_self() and serve_here() read the CPU id under separate guards. A reader that migrates between them leaves the excluded CPU owing an answer with no kick, so it reads stale after 100 ms. Once "interrupts on in syscalls" lands, this case is reachable. Issue the kick and the self-serve under one IrqGuard.
  • kernel/src/counters.rs:115 — IrqWatch::post_in_place's contract is "a handler makes it with its CPU's preempt count raised", but this call also runs from the reader's syscall with the count at zero. Use the thread-side post there, or widen the contract.
  • kernel/src/arch/x86_64/cpu.rs:35 — the name store_fence reads as sfence, but the function is mfence; lfence. Call it what it is for, e.g. wrmsr_fence.
  • T14 comment 5975503047 — it says the readback lost to one ssh timeout "is filed separately", but I find no such issue on main or in File the sysroot's inherited debug assertions and the allocators' futex-less spinlock; repoint a path #703 moved #707. The orchestrator should file it.

REMOVE

  • kernel/src/syscall/machine.rs:285 — "A cap that resolved above and lacks the bit, which is a word and not a refusal."

SEND BACK

Japabu and others added 2 commits October 4, 2026 03:55
… answers, and review round 1's records

- `demand_syscap` answers every right the handle holds, read under the
  guard that checked it, so `SYS_COUNTERS` looks its handle up once: a
  sibling closing it between two lookups can no longer be answered
  COUNTERS-only, and no error kind is swallowed.
- A reader's kick of every other CPU and its answer for its own run
  under one `IrqGuard`, so the CPU the kick skipped is the CPU answered
  for, wherever the reader migrates.
- `IrqWatch::post_in_place`'s contract names a thread's post, which the
  reader's answer and an inbox's poll wake already make.
- `store_fence` is `wrmsr_fence`: it is `mfence; lfence`, not `sfence`.
- The turbostat readings move beside their one reader, `tests/t14-linux/`,
  and both SOURCE files carry the commands; the Linux-readings issue
  points at the fixtures instead of repeating them.
- The umbrella track no longer says nothing reads APERF, MPERF or
  MSR_SMI_COUNT.
- Filed: the loaded T14 runs below Linux's clock, two CPUs idle busier
  than Linux's, toyos-cpuvuln's one consumer, and a readback lost to one
  ssh timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
@Japabu

Japabu commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

Round 2 mutation m13 at a059144e2: the one lookup answers TRACE it did not read off the handle. Applied with git apply --check then git apply, run with cargo test --test toyos-build -- virt_smp: EXIT=1, counters_read.rs:116 "COUNTERS alone was answered cpu0's kicks"; reversed with git apply -R, 0 dirty paths after.

diff --git a/kernel/src/syscall/machine.rs b/kernel/src/syscall/machine.rs
--- a/kernel/src/syscall/machine.rs
+++ b/kernel/src/syscall/machine.rs
@@ -282,3 +282,3 @@ pub(super) fn sys_counters(syscap: RawHandle, out: &mut UserBytesMut) -> u64 {
     let rights = match demand_syscap(syscap, Rights::COUNTERS) {
-        Ok(rights) => rights,
+        Ok(rights) => rights.union(Rights::TRACE),
         Err(e) => return e.refuse(),

@Japabu

Japabu commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

T14 run at a059144e2 (round 2's three staged boots): GREEN.

Images flashed in order, sha256 recorded at flash: testcases 97b2dd0feb677507e8718d75f0d2faba61102911cd3bc6c981362c683c2d2136, shared 327b9b5df9291e3e6de8afea9cfe16520c940661c0e639833416296a4e4160af, shared-debug 5bbd06b0649d9fd5f792c3e2fc8d8c155886890620a3b8e3986164153c6f26bf; each boot passed. Judged from the clean head: cargo test --test toyos-build -- --metal --metal-readback orch/counters2/metal counters — EXIT=0, [metal] 3 passed, 0 failed, 3 boot(s), PASS counters.

Every CPU reports smi=true aperf=true mperf=true. Readback and judge log: orch/counters2/metal/{testcases,shared,shared-debug}/, orch/counters2/judge-t14-counters.log.

@Japabu

Japabu commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 2 — #705 at a059144e2

Round 1 BLOCKER

  • PR body, "Gates" / "The T14 row" (round 1's hardware reading and log paths): CLOSED for ce1786ff0. The body now has the Drive run's command and 275 passed, 4 failed, the jobcase re-boot with its sha256 and four judges, the counters row's lines (drive/counters-full.log:29934-29943), and the log paths for host, guest, loom and preflight. The new head is a different matter; see below.

Net lines: git diff --shortstat origin/main...a059144e2: 68 files, +1893 −339. Round 2's own commit is 17 files, +158 −223. Its production change is +25 −23, in kernel/src. The rest is issues, the fixtures that moved, and tests/toyos.rs.

Round 2 changes checked against their measurements

  • demand_syscap now returns rights_of, read under the same with_process_data guard as the get. m13 (.union(TRACE)) reds virt_smp at counters_read.rs:116, EXIT=1 (mut/m13.virt_smp.log), and the tree was restored clean.
  • answer(kick) now kicks and self-serves under one IrqGuard. The deaf actuator is still read only inside its cfg(feature = "test-actuators") statement. The kick is sent before the deaf return, so a deaf reader still kicks.
  • post_in_place's contract is widened.
  • wrmsr_fence is renamed, and no store_fence is left anywhere in the tree.
  • The fixtures moved to tests/t14-linux/, and every include_str! path resolves. The citations of the deleted IPI issue and of published.rs are gone.
  • --ci host passed: EXIT=0, 73 steps (orch/counters2/ci-host.log:7530). The guest suite passed: EXIT=0, 26 of 26 (guest-suite.log:426).
  • The T14 run at this head passed: 3 passed, 0 failed, 3 boot(s) (judge-t14-counters.log).

BLOCKER

  • PR body, "Gates" — at a059144e2 the body still lists the T14 as "EXIT=2 (staged, the machine untouched)". "What it read" carries only ce1786ff0's reading. This change targets hardware, and the reading for this head is only in comment 5975827379, not in the body. Put in the body:
    • the judge command and its EXIT=0, [metal] 3 passed, 0 failed, 3 boot(s), PASS counters;
    • the three image sha256s and the readback paths orch/counters2/metal/{testcases,shared,shared-debug}/ and orch/counters2/judge-t14-counters.log;
    • this head's row lines (judge-t14-counters.log:19-28): SMI +9 each over 19875 ms, 2318 MHz spinning on every CPU, MPERF busy 0.985–0.990, idle busy cpu0 1.35% / cpu7 1.42% / cpu4 0.37%, a round costing its reader 18548 ns.

NOTE

  • issues/kernel/toyos-runs-the-t14-under-load-below-the-clock-linux-reaches.md:25-27, issues/kernel/two-t14-cpus-idle-busier-under-toyos-than-any-under-linux.md:18-21 — these two findings name no owner. The two issues filed in the same commit each name one ("the orchestrator"). Zero silent debt asks for ownership. Name an owner who exists in both.
  • issues/kernel/two-t14-cpus-idle-busier-under-toyos-than-any-under-linux.md:8-14 — the second boot at a059144e2 read the same two CPUs again (cpu0 1.35%, cpu7 1.42%, judge-t14-counters.log:20,27). That makes the finding reproducible on a second boot, which is the evidence a promotion to defect needs. Add it beside ce1786ff0's reading.

REMOVE

(none)

SEND BACK

Japabu and others added 2 commits October 4, 2026 04:44
…roduce on a second boot

Both issues filed for the counters row now name the orchestrator, which
holds the T14, as the other two filed in the same branch do. The idle-busy
finding gains the second boot's reading at a059144 (cpu0 1.35%, cpu7
1.42%), the same two CPUs as at ce1786f.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
@Japabu

Japabu commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 3: #705 at ee468ce76

Round 2 BLOCKER

  • PR body, "Gates" / "The T14 row" (a059144e2's hardware reading): CLOSED. The body now gives the judge command cargo test --test toyos-build -- --metal --metal-readback orch/counters2/metal counters with EXIT=0, [metal] 3 passed, 0 failed, 3 boot(s) and PASS counters. It also gives the three image sha256s, the readback paths and judge-t14-counters.log:19-28. Every row number in the body matches orch/counters2/judge-t14-counters.log at those lines: SMI +9 over 19875 ms, 2318 MHz, busy 0.985–0.990, idle 1.35% / 1.42% / 0.37%, 18548 ns.

Round 2 NOTEs: both are addressed. Both issues now name an owner. The second boot's reading is in the idle-busy file, and it matches judge-t14-counters.log:20,27.

Since a059144e2: 8e1cda50d changes 2 issue files (+8 −2). The merge of 21f06e8e0 brings issues only: git diff a059144e2 ee468ce76 -- ':!issues' is empty. git diff --shortstat origin/main...ee468ce76: 68 files, +1899 −339. Production code is unchanged since round 2. cargo run -- --ci host at this head: EXIT=0 (orch/counters3/ci-host.exit), Host: 73 step(s), all green (ci-host.log:7289). The guest suite and T14 readings at a059144e2 still stand for this head, because no code has changed since.

BLOCKER

  • issues/kernel/two-t14-cpus-idle-busier-under-toyos-than-any-under-linux.md:3 — the file is still kind: finding, but its own body now says "The two busy CPUs reproduce across boots". issues/README.md:50-59 says a reviewed finding is either promoted to defect or folded to its site, and "when unsure, promote". Line 24 defines defect as "real, reproducible". A fold is not possible yet, because the cause is not attributed. Make it kind: defect, keep the exit (attribution, then fix it or fold it to the row's doc), and update the PR body's "Filed" bullet to match.

NOTE

(none)

REMOVE

(none)

SEND BACK

Its two busy CPUs reproduced across two boots (ce1786f, a059144), so
it is real and reproducible; issues/README.md promotes such a finding.
The exit keeps attribution first, then a fix or a fold to the row doc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
@Japabu

Japabu commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator Author

Review round 4: #705 at cba61e819

Round 3 BLOCKER

  • issues/kernel/two-t14-cpus-idle-busier-under-toyos-than-any-under-linux.md:3, the finding not promoted although it reproduces: CLOSED. cba61e819 changes the frontmatter to kind: defect, and status: open is legal for a defect (issues/README.md:66). The exit at lines 24-26 is still attribution first, then a fix or a fold to the row's doc. The issue still names its owner (the orchestrator, which holds the T14). The PR body's "Filed" bullet now says defect for this file. The measurement is cargo run -- --ci host at this head: EXIT=0 (orch/counters4/ci-host.exit) and Host: 73 step(s), all green (orch/counters4/ci-host.log:7288, the log's last line). The log was written at 05:08:40, after the commit at 04:59:13.

Since ee468ce76: one commit, one issue file, +3 −3. git diff ee468ce76 cba61e819 touches nothing outside issues/. origin/main is unmoved at 21f06e8e0, which is also the merge base. git diff --shortstat origin/main...cba61e819: 68 files, +1899 −339. Production code is unchanged since round 2. The guest suite, the loom controls and the T14 readings taken at a059144e2 therefore still stand for this head. The body's "Gates" section records the new host run, and its other sections remain true of the tree and of the logs they cite.

BLOCKER

(none)

NOTE

(none)

REMOVE

(none)

LAND

@Japabu
Japabu marked this pull request as ready for review October 4, 2026 03:09
@Japabu
Japabu enabled auto-merge October 4, 2026 03:09
@Japabu
Japabu added this pull request to the merge queue Oct 4, 2026
Merged via the queue into main with commit f21d6bd Oct 4, 2026
6 checks passed
@Japabu
Japabu deleted the wt/toyos-counters branch October 4, 2026 03:56
Japabu added a commit that referenced this pull request Oct 4, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
Japabu added a commit that referenced this pull request Oct 4, 2026
The one conflict, toyos-explains-itself.md, keeps main's text less the
clause "a user symbol is resolved by the kernel", which this branch makes
false.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
Japabu added a commit that referenced this pull request Oct 4, 2026
#705 took syscall 124 for SYS_COUNTERS, so SYS_PORT_MINT becomes 125 and
SYS_PORT_BADGE 126. The profile's bound assertion now names the highest
number, SYS_PORT_BADGE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
Japabu added a commit that referenced this pull request Oct 4, 2026
…he branch's first design

The owner ruled the perf-state read-back be rebuilt on #705's counters
round: one round, one place, and the power envelope another counter
behind the strict right. Every file this branch changed is taken as
origin/main has it: the perf-state device class, its second shootdown
round, its two actuators, the `toyos-perfstate` crate, the `perfstate`
program and the metal harness's expected-panic boot go. What is kept,
the kernel declaring the HWP request from the one CPU-state declaration,
lands again in the next commit on top of what #705 provides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
Japabu added a commit that referenced this pull request Oct 4, 2026
…ds the power envelope back under TRACE

The CPU-state declaration in control_regs gains the performance request:
on a CPU toyos_cpuvuln::hwp admits (HWP, EPP, the package request and
EPB enumerated, Intel family 6, not hybrid), every CPU clears
IA32_HWP_INTERRUPT where it exists, sets IA32_PM_ENABLE, and only then
reads IA32_HWP_CAPABILITIES and MSR_PLATFORM_INFO and writes
IA32_HWP_REQUEST (intel_pstate's: min the package's maximum-efficiency
ratio, max the highest level, EPP 0x80), IA32_HWP_REQUEST_PKG and EPB 6
whole, then logs and asserts each. A refused machine logs the reason
once and writes none of them.

The read-back rides #705's one round instead of a second: three counters,
hwp_request, hwp_request_pkg and energy_perf_bias, each under
Rights::TRACE (the owner put power under the strict right), present only
where the declaration declared them. CPU identity is toyos-cpuvuln's: its
third verdict, `hwp`, beside the vulnerabilities and the counters, and
cpu::vendor now decodes CPUID.0 once for both kernel callers.

The counters metal row holds every CPU's declaration (pm_enable=1) and
its envelope in every read to its boot line, and every CPU's busy clock
across the spin to the lowest Linux reached loaded on the same machine,
which closes the defect that the T14 ran below it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant