Repository navigation
The kernel declares each CPU's HWP request, and the counters round reads the power envelope back under TRACE - #590
Conversation
…s it back per CPU The self-hosting bar is measured under a power envelope that must be read back for a whole build span, and ToyOS left every register of it where firmware put it. This is the first stage of the track that makes the kernel own it (issues/kernel/the-kernel-owns-cpu-performance-state.md). The declaration. control_regs.rs, the one CPU-state declaration, now also writes IA32_PM_ENABLE, IA32_HWP_REQUEST, IA32_HWP_REQUEST_PKG and IA32_ENERGY_PERF_BIAS whole on the BSP and every AP, and asserts each on each. The values are the bar's: min is the package's maximum-efficiency ratio (MSR_PLATFORM_INFO[47:40]), max the CPU's HWP highest performance, desired 0, EPP 128, window 0, no package control, which on the T14's inputs is 0x80002a04, what Linux's intel_pstate held there; the package request is 0x8000ff01 and EPB 6. The declaration is all or nothing. A CPU missing any register it names (HWP, EPP, package request, EPB, package thermal status, or not Intel, or hybrid) gets no request, and the BSP says why once: "control_regs: no performance request is declared: no HWP ...". Whether the machine declared one is the BSP's verdict and every AP must reach it; the request itself is per CPU, since a CPU's highest performance is its own. IA32_MISC_ENABLE's turbo bit is read back and not written, because its other bits are model-specific and firmware's. The arithmetic is toyos-perfstate, a pure host-tested crate, because no QEMU CPU has HWP. Its oracle is the T14's Linux MSR readings. The read-back needs no new syscall. It is a device class, perf-state (class 9, the manifest's `devices = ["perf-state"]`), whose read answers toyos_abi::perf's records: the package's registers (HWP package request, PLATFORM_INFO, RAPL unit, PKG_POWER_LIMIT, PKG_ENERGY_STATUS, package thermal status, TEMPERATURE_TARGET), then each CPU's (PM_ENABLE, HWP capabilities, HWP request, EPB, MISC_ENABLE). A per-CPU MSR is readable only on its CPU, so a read asks every CPU. It issues a generation on a second shootdown::Shootdown (the loom-modelled TLB ack protocol), answers for its own CPU, and kicks the rest. Each answers from its next scheduler pass in drain_irqs, which costs two relaxed loads when nothing is owed, and posts perf_state::WATCH. The read parks there, bounded by 250 ms, past which it is refused Io and the silent CPUs are named. A claim exists only with the control_regs::HwpDeclared proof, so no rdmsr of these registers is reachable on a CPU without them. On AArch64 that proof is an uninhabited type and the claim is refused by name. /system/bin/perfstate holds the row and prints one read, checked against the declaration. The guest binary perf_state runs the same code, and perf_request drives it: in QEMU it asserts the named refusal, no request line, and the claim refused NotFound; on the T14 it asserts every CPU logged the bar's literal values and the binary read them all back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W6rME2DoqwjcYFStYHHY4j
Review, round 1, at 545e9cdCI BLOCKER
NOTE
REMOVE
SEND BACK |
…no claim reaches an unproven register The review sent #590 back with five blockers. Each is answered here, except the reading from the T14, which only that machine can give. - control_regs: CPUID alone now decides whether a CPU gets a request. IA32_HWP_INTERRUPT is set to 0 where CPUID.06H:EAX[8] enumerates it, and IA32_PM_ENABLE is written next. Only after that are IA32_HWP_CAPABILITIES and MSR_PLATFORM_INFO read, and the request computed and written. This is intel_pstate's order (intel_pstate_hwp_enable, then intel_pstate_get_hwp_cap). The HWP writes moved out of the no-ap-control-regs skip, into hwp_init after self_check. - toyos-perfstate: no CPUID bit enumerates MSR_PLATFORM_INFO (0xCE). SDM Vol. 4 documents it in the model tables of DisplayFamily 06H, so a CPU of any other family is refused by name (Refusal::NotFamily6) before the register is read. - The read-back drops MSR_RAPL_POWER_UNIT, MSR_PKG_POWER_LIMIT, MSR_PKG_ENERGY_STATUS and MSR_TEMPERATURE_TARGET. No CPUID bit enumerates them, and nothing at boot read them, so a userland read was the first to touch them. The track's stages 2 and 4 now require each one to be proven present at boot first, and keep the energy counter (CVE-2020-8694) off any row a session can launch. - A cancelled perf-state read takes its ask back (Reader::cancel, reached from sys_read's cancelled wait), so no later read is answered from registers sampled before it began. The ABI doc now says that overlapping reads of one claim share one ask. - HwpCapabilities is deleted. The request's maximum is `capabilities as u8`. - perf-state-deaf-cpu grants the claim on a machine with no declaration and answers zeros. The last CPU answers no ask. perf_state_silent_cpu (Fast) asserts two reads refused Io, each naming that CPU alone. - perf-request-diverges has cpu1 move its request one ratio off the declaration when it answers a read, then run hwp_check. perf_request's metal row gains a second boot, perfdiverge, which must carry that panic on the page after the reset. It is priced in the metal profile and ruled flashable. - TESTCASES now runs test_rs_perf_state, before null_sink_client_exits. The metal row's job_passed("test_rs_perf_state") named a job that no boot ran. - Deleted per the review's REMOVEs: the narration in toyos-perfstate's Cargo.toml; "since Sandy Bridge"; "two relaxed loads"; the cross-reference to the dump's bound; shootdown.rs's list of callers; the track's chronology and the claim that its values are the bar's. The stale "four" in io.rs's device-class count goes too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A second Io read cannot tell a cleared ask from a stale one, since a stale ask past its deadline is refused Io too. So the second read shows that the claim still answers after a refusal, and nothing more. The comments now say only that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review, round 2, at ee20f53CI Round-1 blockers
BLOCKER
NOTE
REMOVE
SEND BACK |
Two conflicts, each resolved by keeping both sides: - src/metal.rs, FLASHABLE: main's `dump-deaf-cpu` and `watch-window` rows, then this branch's `perf-request-diverges`. Its comment loses "the reset that ends the boot clears IA32_PM_ENABLE, which only a reset does", which the round-2 review removed as unmeasured. - tests/toyos.rs, TESTCASES: main's job list, with `test_rs_perf_state` after `test_rs_abuse_short_sleep` and before `test_rs_syscall_cost`, so `test_rs_null_sink_client_exits` stays the last job before `log-close`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dict rests on time
A read's ask now lives on `sys_read`'s stack (`perf_state::Ask`), made
by the read's first look and gone with the read. The claim holds no ask,
so a read that ends answered, refused, cancelled or as a nonblocking
`WouldBlock` leaves nothing a later read is answered from or refused by,
which is what toyos-abi/src/syscall.rs's `PerfState` doc promises.
`Reader::cancel` and `DeviceClaim::cancel_perf_state` are deleted: there
is nothing left to take back. The doc's "reads of one claim that overlap
share one ask" is deleted, since every read now asks; a CPU's one answer
still serves every ask issued before it.
With no standing ask there is no readiness before a read: `has_data` is
false and `read_watch` is `None` for the class, so a poll on a claim is
refused `NotSupported` rather than reporting a readiness the next
nonblocking read would not honour.
`perf-state-deaf-cpu` now has no CPU answer a kick: the asker answers
itself inline in `Ask::issue`, so every read on two CPUs names exactly
the one other CPU whichever CPU the test runs on, and no verdict waits
on a kicked vCPU being scheduled inside 250 ms. The refusal names the
ask (`did not answer Generation(N)`). `test_rs_perf_state_silent` makes
a nonblocking read first (the boot's first ask, `WouldBlock`), then two
blocking reads, and `perf_state_silent_cpu` requires the refusals to
name Generation(2) and Generation(3), once each, one CPU each: a read
answered or refused from an ask not its own reds.
kernel-loom/tests/shootdown_answer.rs models the target-to-initiator
edge the slots use: `serve`'s closure writes a loom cell and the
initiator reads it once `served` answers.
`HwpDeclared::ask` asserts `control_regs::report` has run, which is after
every committed CPU ran `init`, so the proof's "enabled on every CPU" is
enforced rather than true by boot order alone.
Also: `Enumerated` inlined as `{:x?}`; the `NotIntel` reason names
MSR_PLATFORM_INFO rather than RAPL; the removed comments (toyos-perfstate
lib.rs's envelope citation, perf_state.rs's "within one timer
interrupt", shootdown.rs's "nothing reads through this edge yet",
system.toml's row comment) are deleted; the track issue drops the same
false citation, says the Linux readings are #568's unmerged samples, and
records that no test launches /system/bin/perfstate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review, round 3, at 148c207The gate holds.
Net diff is Earlier blockers
BLOCKERNone new. NOTE
REMOVE
The only things between this PR and landing are the two open round-1 blockers, the T14 SEND BACK |
`shootdown_answer.rs` duplicated `tlb_shootdown.rs`, which already goes red under the same `served` Acquire-to-Relaxed mutation, so its own model was dead weight. `kernel/src/syscall/io.rs`'s device-class count was already wrong against its match arm, and the issue's citation of an unmerged PR's unlogged numbers rotted the moment it was written. The nonblocking-read NOTE is filed rather than fixed, since it stays a NOTE this round. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s bound perfdiverge runs test_rs_perf_state, the same binary testcases' own list already prices under list.testcases.job_ms; #590 added the boot.perfdiverge.* rows but left list.perfdiverge.job_ms unpriced, so batches() refused before any boot with "a list nobody has priced cannot be sized to the runner's bound". Give it the same 1400 ms ceiling for the same reason: the same kind of binary spawned off the same stick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rf_request carries its job The T14 ran perf_request's row at 1daa5dd and did what the row claims, and the harness scored it red for two reasons of its own. toyos-metal judged every boot that is not a staged wedge by the shutdown's last word, so perfdiverge, which ends in the panic it exists to provoke, exited 1 before perf_request_diverged read the record. An arm now says `panics: Some(<line>)`, the harness passes it as `--expect-panic <line>`, and toyos-metal judges such a boot by the kernel panic record the pass after the reset printed: green where that record (the `| ` lines under `Previous boot's panic:`, opening with the kernel's `PANIC (apic `) carries the line, red on a boot that handed the machine back, on a wedge's record, on a panic without the line, and on the line found only in the ring filed after the record. `--expect-panic` beside a wedge arm is refused: a boot ends one way. Every other boot keeps today's rule. A panic seals no panel census (only the stop and seal_wedge write one; the perfdiverge readback carries none), so the per-boot facts would have reded the same boot next: the census joins PATH_TAKEN and perfdiverge's two panel rows go. The per-boot facts are now a function, boot_findings, so a host test can hold the T14's own record to them. perf_request's testcases arm named no job and rode TESTCASES' list, which a run filtered to perf_request does not select, so that boot ran none. The arm now names test_rs_perf_state. The loader marking slot A dead after the expected panic is right and is left: the mark is the loader's A/B policy for any kernel death, it lives in the `attempts` file on the stick's log partition, and every metal boot rewrites the whole stick (wipefs, then dd of the image), which the testcases boot after perfdiverge shows: `this image has had the machine 0 time(s)`, slot A booted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ves unused `cargo clippy --all-targets` builds tests/toyos.rs, which has no libtest harness, with cfg(test) set and every #[test] dropped, so the test module's shared fixture, selection helper and glob import were dead there and `-D warnings` refused them. The two tests now sit at module level and carry their own fixture and selection. Also files usbload's unpriced panel census, found beside this: a deadline seals the census, and no boot.usbload.panel_* row prices it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ves the T14, and every high-risk stage names its mutation The review of 2dfa123 found the security track grown from 423 to 566 lines and the speed track a 155-line design, against issues/README.md's rule that a track carries no design and no stage table and is cut back past a screen. - Both tracks keep their intent in a sentence or two, and each stage is one entry: its name, its exit, the mutation that must go red, and its oracle. Mechanism, register-level design, the "What is true today" section, the S0 command block and every Linux and SDM line citation no exit needs are deleted; they land with each stage's own PR. The security track goes from 566 lines to 114, the speed track from 155 to 71. - The security track is renamed the-kernel-is-at-least-as-secure-as-linux-on-the-t14, because S13 goes past Linux parity. Every citation moves; the seven defects that called themselves "a row of the hardening table" lose that sentence, since the table is gone and the track's Exit names each of them. - S16 (TME) moves to newer-hardware-offers-what-the-t14-lacks as a row: the T14's CPUID.(7,0):ECX is 0x18c05fde, bit 13 clear. Split-lock disable becomes S16. - S11: the loader refuses an unmarked program or library on every CPU (the orchestrator's ruling), so `unmarked_object_refused` runs under TCG; the sysroot link reds on one unmarked input; a store to the shadow stack is #PF and user_ptr refuses a read into it. The libc longjmp clause goes: it is a panicking stub. - S13 is gated on CET_SSS and tests that a DirectMap store to a kernel shadow stack faults. S14's window refuses a user copy under a key the thread denied; its ABI is approved in principle. - S15, P0 and P4 need two distinct CPUs, and no boot-actuators arm or ABI call places a thread. The speed track gains the Pin stage, a test-actuators SYS_DEBUG action whose test reads the CPU's own APIC ID. - P1 has one save path, XSAVEOPT: QEMU v11.1.1's TCG implements neither XSAVEC nor XSAVES (target/i386/cpu.c:1012-1014), and S11's CET state switches by wrmsr. P3 names a toyos-bootmap host test, P4 a loom case for a CPU joining the space during a shootdown, P5 an NMI under a user GS base; P6 takes C-states from _CST after the AML interpreter; P7 names Linux's APST bound and quirks. The #568 and #590 citations go. - The newer-hardware file states what it is blocked on and its exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review, round 5, at 185f3eaCI:
Net branch size is +1867/−94. This round adds about +215 lines of harness production code ( Earlier blockers
QEMU runs at 148c207The harness commits (00d6966, 185f3ea) cannot change those results. They only touch metal paths, plus one unused-by-QEMU function in BLOCKER
NOTE
REMOVE
SEND BACK |
…ow derives from the machine's own lines Review round 5 found that nothing tested the ABI's claim that each CPU's record is read on that CPU: an asker that samples every slot while the kicked CPUs answer nothing stayed green on every arm, and every T14 read-back showed all eight CPUs at cpu1's hwp_capabilities=0x0110182a. The sampling was right. In the one T14 read taken during a divergence (590r2-metal-noasserts.log:474-475, the reader on cpu1, which had just moved its own request to 0x80002a05) cpu0's record holds 0x80002a04. A record sampled on cpu1 would hold 0x80002a05. Both records hold 0x0110182a because IA32_HWP_CAPABILITIES[23:16], Most_Efficient_Performance, is dynamic. The boot lines were taken 11 ms apart over 233 ms, and cpu0's own value moved from 0x04 at boot to 0x10 by the read. So the flaw was the missing test, not the sampling. - CpuRegisters gains hardware_id, the CPU's x2APIC ID from CPUID (arch::cpu::hardware_id, the ID panic records already carry). Only the CPU itself can read it. The guest read-back prints it on every record. The host holds each record to the kernel's roster, the BSP's `percpu: BSP ... lapic_id=` and each `SMP: AP cpuN lapic=`. On the T14 that roster does not number CPUs in ID order (cpu1 is lapic 2). - perf-state-deaf-cpu now leaves only the boot's first three asks to their asker. perf_state_silent_cpu's fourth read is therefore answered by every CPU, and its records are held to the roster under QEMU. perf_request's QEMU boot has no claim to read (NotFound is what it asserts), so the QEMU identity check lives here. - perf_request's metal judges hold the machine to its own lines. Each CPU's boot line must hold what toyos_perfstate declares from that line's hwp_capabilities and platform_info. The perfdiverge panic must name the declaration cpu1's own boot line makes, and a request other than it. --expect-panic carries only the machine-independent head. No T14 literal is left in the judge. - The panel census is exempt only on a boot told to panic (NOT_SEALED_BY_A_PANIC, keyed on Arm::panics). PATH_TAKEN is main's again. - command_line quotes every word that holds a character a shell reads specially, not only words with whitespace. - Filed issues/kernel/the-perf-state-declaration-refuses-hybrid-intel-and-amd-cppc.md, and the track's "Not covered" points at it. The track's owed-row clause and its Linux provenance are deleted. Not done: a check that hwp_capabilities still differs across CPUs in the read-back wherever the boot lines differ. The noasserts read above shows the correct kernel reading equal values there, so that check would fail on a correct kernel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Round-5 mutation patches regenerated on the merged head 2077107. Each applies with m1-asker-samples-every-slot — records are read on their own CPUTarget: QEMU diff --git a/kernel/src/perf_state.rs b/kernel/src/perf_state.rs
index a0f8da924..c03d22d0d 100644
--- a/kernel/src/perf_state.rs
+++ b/kernel/src/perf_state.rs
@@ -96,7 +96,7 @@ impl Ask {
DEAF_ASKED.fetch_add(1, Release);
}
let me = crate::arch::percpu::cpu_id() as usize;
- ASKS.serve(me, || SLOTS[me].store(sample(me)));
+ ASKS.serve(me, || for s in &SLOTS[..cpus] { s.store(sample(me)) });
for cpu in (0..cpus).filter(|&cpu| cpu != me) {
crate::arch::irqchip::kick_cpu(cpu as u32);
}
@@ -170,7 +170,7 @@ pub fn serve_if_owed() {
if !ASKS.owes(me) || deaf {
return;
}
- ASKS.serve(me, || SLOTS[me].store(sample(me)));
+ ASKS.serve(me, || {});
WATCH.post();
}
m2-records-carry-no-identity — no record names its CPUTarget: same. Expected: same as m1. diff --git a/kernel/src/arch/x86_64/perf_state.rs b/kernel/src/arch/x86_64/perf_state.rs
index 49821d121..9e406acd5 100644
--- a/kernel/src/arch/x86_64/perf_state.rs
+++ b/kernel/src/arch/x86_64/perf_state.rs
@@ -17,7 +17,7 @@ pub fn declared() -> Result<Declared, &'static str> {
/// This CPU's own identity and registers.
pub fn read_cpu(_: &Declared) -> CpuRegisters {
CpuRegisters {
- hardware_id: u64::from(cpu::hardware_id()),
+ hardware_id: 0,
pm_enable: cpu::rdmsr(msr::PM_ENABLE),
hwp_capabilities: cpu::rdmsr(msr::HWP_CAPABILITIES),
hwp_request: cpu::rdmsr(msr::HWP_REQUEST),
diff --git a/kernel/src/perf_state.rs b/kernel/src/perf_state.rs
index a0f8da924..8ceaff18e 100644
--- a/kernel/src/perf_state.rs
+++ b/kernel/src/perf_state.rs
@@ -195,7 +195,7 @@ fn sample(me: usize) -> CpuRegisters {
"an ask is made only through a claim, and a claim only where the request is \
declared: {why}",
);
- let hardware_id = u64::from(crate::arch::cpu::hardware_id());
+ let hardware_id = 0;
CpuRegisters { hardware_id, ..CpuRegisters::default() }
}
}m3-deaf-for-ever — the deaf CPU never answers, the fourth ask includedTarget: QEMU diff --git a/kernel/src/perf_state.rs b/kernel/src/perf_state.rs
index a0f8da924..a9b237c0a 100644
--- a/kernel/src/perf_state.rs
+++ b/kernel/src/perf_state.rs
@@ -166,7 +166,7 @@ static DEAF_ASKED: AtomicU64 = AtomicU64::new(0);
/// [`DEAF_ASKS`] asks are made, so each of those is answered by its asker alone.
pub fn serve_if_owed() {
let me = crate::arch::percpu::cpu_id() as usize;
- let deaf = crate::actuator::perf_state_deaf_cpu() && DEAF_ASKED.load(Acquire) <= DEAF_ASKS;
+ let deaf = crate::actuator::perf_state_deaf_cpu() && DEAF_ASKED.load(Acquire) <= DEAF_ASKS.max(u64::MAX);
if !ASKS.owes(me) || deaf {
return;
}m4-bar-holds-no-request — the boot line's request is not held to its own declarationTarget: diff --git a/tests/toyos.rs b/tests/toyos.rs
index bd7d58e63..7d125af5b 100644
--- a/tests/toyos.rs
+++ b/tests/toyos.rs
@@ -17781,7 +17781,6 @@ fn hwp_boot_line(log: &str, cpu: u32) -> Result<u64, String> {
let declared = toyos_perfstate::hwp_request(field("hwp_capabilities")?, field("platform_info")?);
for (name, want) in [
("pm_enable", toyos_perfstate::PM_ENABLE),
- ("hwp_request", declared),
("hwp_request_pkg", toyos_perfstate::HWP_REQUEST_PKG),
("epb", toyos_perfstate::ENERGY_PERF_BIAS),
] {m5-diverged-any-declaration — the panic may name any declarationTarget: diff --git a/tests/toyos.rs b/tests/toyos.rs
index bd7d58e63..97f9aadff 100644
--- a/tests/toyos.rs
+++ b/tests/toyos.rs
@@ -17872,10 +17872,10 @@ fn diverged_panic(log: &str, loader: &str) -> Result<String, String> {
let tail = format!(", the declaration is {declared:#x}");
let held = said
.strip_prefix(PERF_REQUEST_DIVERGED)
- .and_then(|rest| rest.strip_suffix(&tail))
+ .and_then(|rest| rest.split_once(", the declaration is").map(|(h, _)| h))
.and_then(|held| u64::from_str_radix(held.strip_prefix("0x")?, 16).ok());
match held {
- Some(held) if held != declared => Ok(said.to_string()),
+ Some(_) => Ok(said.to_string()),
_ => Err(format!("cpu1's boot line declares {declared:#x}, and the panic says {said:?}")),
}
}m6-census-exempt-on-every-boot — the panel census is exempt on every bootTarget: diff --git a/tests/common/metal.rs b/tests/common/metal.rs
index 25a12a09c..466198d95 100644
--- a/tests/common/metal.rs
+++ b/tests/common/metal.rs
@@ -970,7 +970,7 @@ fn boot_findings(label: &str, back: &Readback, profile: &Profile, panicked: bool
] {
let name = format!("boot.{label}.{field}");
let priced = profile.row(&name).is_some();
- let taken = PATH_TAKEN.contains(&field) || (panicked && NOT_SEALED_BY_A_PANIC.contains(&field));
+ let taken = PATH_TAKEN.contains(&field) || NOT_SEALED_BY_A_PANIC.contains(&field);
if value.is_none() && !priced && taken {
continue;
}m7-quote-whitespace-only — only whitespace triggers quotingTarget: diff --git a/tests/common/metal.rs b/tests/common/metal.rs
index 25a12a09c..e9326f814 100644
--- a/tests/common/metal.rs
+++ b/tests/common/metal.rs
@@ -841,7 +841,7 @@ fn talk_home(home: &Path) -> PathBuf {
/// `words` as a shell reads them back, for the request a hand runs: a word
/// holding anything but the characters no shell treats specially is quoted.
fn command_line(words: &[String]) -> String {
- let plain = |c: char| c.is_ascii_alphanumeric() || "@%+:,./_-".contains(c);
+ let plain = |c: char| !c.is_whitespace();
let word = |w: &String| {
if !w.is_empty() && w.chars().all(plain) {
w.clone() |
…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
…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
|
Mutation patches at 8c4d1f5. Each applied with h1-max-from-guaranteed — h1-max-from-guaranteed cargo test -p toyos-cpuvuln EXIT=101diff --git a/toyos-cpuvuln/src/hwp.rs b/toyos-cpuvuln/src/hwp.rs
index 636f07639..5399be092 100644
--- a/toyos-cpuvuln/src/hwp.rs
+++ b/toyos-cpuvuln/src/hwp.rs
@@ -109,7 +109,7 @@ pub const HWP_EPP: u64 = 0x80;
/// HWP"; `msr-index.h:495-504`).
pub const fn hwp_request(capabilities: u64, platform_info: u64) -> u64 {
let min = (platform_info >> 40) & 0xff;
- let max = capabilities & 0xff;
+ let max = (capabilities >> 8) & 0xff;
min | max << 8 | HWP_EPP << 24
}
h2-hybrid-admitted — h2-hybrid-admitted cargo test -p toyos-cpuvuln EXIT=101diff --git a/toyos-cpuvuln/src/hwp.rs b/toyos-cpuvuln/src/hwp.rs
index 636f07639..b7a9d7048 100644
--- a/toyos-cpuvuln/src/hwp.rs
+++ b/toyos-cpuvuln/src/hwp.rs
@@ -88,7 +88,7 @@ pub fn hwp(facts: &HwpFacts) -> Result<Hwp, HwpRefusal> {
Some(HwpRefusal::NoPackageRequest)
} else if facts.cpuid_6_ecx & CPUID_6_ECX_EPB == 0 {
Some(HwpRefusal::NoEnergyPerfBias)
- } else if facts.cpuid_7_0_edx & CPUID_7_0_EDX_HYBRID_CPU != 0 {
+ } else if false {
Some(HwpRefusal::Hybrid)
} else {
Noneh3-envelope-on-counters-alone — h3-envelope-on-counters-alone cargo test -p toyos-abi EXIT=101diff --git a/toyos-abi/src/counters.rs b/toyos-abi/src/counters.rs
index 69643bd7f..54fa5ace4 100644
--- a/toyos-abi/src/counters.rs
+++ b/toyos-abi/src/counters.rs
@@ -71,7 +71,7 @@ impl Counter {
/// The rights a `SysCap` carries for this counter to be answered.
pub const fn needs(self) -> Rights {
match self {
- Self::Stamp | Self::Smi => Rights::COUNTERS,
+ Self::Stamp | Self::Smi | Self::HwpRequest => Rights::COUNTERS,
Self::Aperf
| Self::Mperf
| Self::Kicks |
|
T14 runs at Each image's sha256 matched the request in the command that flashed it. The head's first judge ( Readbacks: |
|
Review of the rebuild at Size: 19 files, +560 −81. About 220 lines of that are production code; the rest is tests, the judge, doc lines and three issue files. The growth is accepted: it is one declaration, three counters on the existing round, and one verdict next to the other two. Readings checked: BLOCKER
NOTE
REMOVE
SEND BACK |
…e row held to Linux's request and to Linux's clock over the spin's span - The performance request moves out of control_regs::init into control_regs::init_performance, called on the BSP after idt::init and on every AP after control_regs::init (an AP's IDT is loaded by its trampoline). A #GP in one of its wrmsr or rdmsr now panics by name instead of stopping the BSP under firmware's table. CR4.OSFXSR still precedes the IDT. - CPUID leaf 6 is read once, by cpu::leaf_6, under the rule the counters' bring-up already used (a max leaf outside 6..=0xFFFF reaches no leaf 6); control_regs::hwp_declared and counters::bring_up both call it. - tests/t14-linux/hwp-request.txt commits Linux's IA32_HWP_REQUEST on the T14, 0x80002a04 on all eight CPUs, from commit 868378b's message (#568); counters_on_metal holds every CPU's boot hwp_request to it. - The clock floor is Linux's busy clock over the intervals that open within the spin's own span (turbostat's first opens 2 s into the load, each 10 s), not the lowest after the package settles: 3800 MHz for the 11.2 s spin, where it was 3075. Judged against the T14 readbacks of 8c4d1f5: the head's EXIT=0, the control's (d47b383) EXIT=1; the head's with every spin APERF scaled to 3100 MHz EXIT=1 under this judge and EXIT=0 under 8c4d1f5's; the oracle file set to 0x80002a05 EXIT=1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Judge checks at 14fa36d, against copies of the orchestrator's T14 readbacks of 8c4d1f5 (
oracle mutation — Linux's request off by one bit--- a/tests/t14-linux/hwp-request.txt
+++ b/tests/t14-linux/hwp-request.txt
@@ -1 +1 @@
-0x80002a04
+0x80002a05the readback edit — each CPU's spin APERF set to idle1's plus the spin's delta times 3100/3828 (testcases/kernel.log)550c550
< {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.0.aperf = 79504454927
---
> {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.0.aperf = 71686230682
560c560
< {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.1.aperf = 47099279881
---
> {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.1.aperf = 39249166643
570c570
< {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.2.aperf = 47003344511
---
> {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.2.aperf = 39166808910
580c580
< {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.3.aperf = 48350840204
---
> {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.3.aperf = 40305422603
590c590
< {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.4.aperf = 48149477983
---
> {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.4.aperf = 40337366008
600c600
< {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.5.aperf = 46976323556
---
> {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.5.aperf = 39127104163
610c610
< {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.6.aperf = 46867769073
---
> {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.6.aperf = 39031128296
620c620
< {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.7.aperf = 48331103391
---
> {2026-10-04 07:45:03 13.356 pid=9 test-runner} counters_metal spin: kernel.cpu.7.aperf = 40281663237 |
|
T14 runs at Each image's sha256 matched the request in the command that flashed it. Readbacks and judge logs: |
|
Review round 2 of the rebuild, at Earlier BLOCKERs
Earlier NOTEs
BLOCKER
NOTE
REMOVE
Net: 22 files, +609 −91, about 230 of the lines production code. The growth is accepted as in the previous round. SEND BACK |
…tion cpu::leaf_7 reads CPUID.(7,0) under the rule both control_regs sites and boot::report_counter_origin each wrote out (a maximum leaf below 7 reaches no leaf 7, and the registers read zero), as cpu::leaf_6 does for leaf 6. control_regs::hwp_declared, control_regs::supported and boot::report_counter_origin call it. On any CPU whose maximum leaf reaches 7 every caller reads the same registers as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Review round 3 of the rebuild, at Earlier BLOCKERs
Earlier NOTEs
The claim that the T14 run at 14fa36d stands for b69e713 holds, and on a stronger ground than the body gives. Each of the three call sites is the old expression with
So the two versions are equal on every CPU, not only where the maximum leaf reaches 7. The new function also runs at this head on QEMU's x86 guests, because all three callers run at every boot, and the suite is green. A T14 boot would measure nothing that this substitution leaves open. No measurement is missing for this commit. BLOCKER
NOTE
REMOVE
Net: 23 files, +617 −97. Round 3 added +10 −8, and its production change is net +2 for one reader in place of three hand-written guards. The growth is accepted as in the earlier rounds. LAND AFTER NAMED CHANGES |
The three issues #590 adds or changes under issues/kernel/ move to issues/<slug>.md, and their two issues/kernel/ citations become issues/<slug>.md. The branch rewrote one citation in toyos-runs-the-t14-under-load-below-the-clock-linux-reaches.md, which #590 deletes; the deletion stands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
The kernel declares each CPU's HWP request as part of the one CPU-state declaration. The power envelope it declares is read back per CPU as three counters on #705's round, under
Rights::TRACE. Stage 1 ofissues/kernel/the-kernel-owns-cpu-performance-state.md.What changed, per decision
Rebuilt on #705, as the owner ruled ("Rebuild it on the counters' round now that #705 landed: one round, one place, and the power envelope becomes another counter behind the right permission"). This branch's first design is dropped whole. The merge of
origin/main(550b511) takes main's tree for every file, and the commits after it land the rebuild. Gone with the first design:perf-statedevice class and its ABI records;Shootdownround, served fromdrain_irqs, with its own bound and silent-CPU answer;perf-state-deaf-cpu,perf-request-diverges);toyos-perfstatecrate, theperfstateprogram and itssystem.tomlrow;--expect-panic,Ending, the census exemption).Why this branch and not a fresh one. The diff against
mainis the same either way. Reusing the branch keeps #590's review rounds beside the code that answers them. Taking main's tree in the merge resolves no conflicts insrc/metal.rsortests/toyos.rsfor code that was going anyway. A fresh branch would have meant a second pull request for the same track, with #590 closed unanswered.The declaration (
kernel/src/arch/x86_64/control_regs.rs). The decision comes from CPUID alone.control_regs::init_performanceapplies it on each CPU once that CPU's IDT is loaded: on the BSP right afteridt::init, and on an AP right aftercontrol_regs::init(its trampoline loaded the IDT). A#GPin any of itswrmsrorrdmsrtherefore panics by name.control_regs::initkeeps onlyCR4andEFER, which must precede the IDT forCR4.OSFXSR. The steps:IA32_HWP_INTERRUPT= 0, only where CPUID.6:EAX[8] enumerates it.IA32_PM_ENABLE= 1.IA32_HWP_CAPABILITIESandMSR_PLATFORM_INFOare read.IA32_HWP_REQUEST,IA32_HWP_REQUEST_PKG=0x8000ff01and EPB = 6 are written.Each register is written whole, logged on one line with the request's two inputs, then asserted. The order is SDM Vol. 3B §17.4.2's ("Additional MSRs associated with HWP may only be accessed after HWP is enabled, with the exception of IA32_HWP_INTERRUPT and MSR_PPERF"), and it is
intel_pstate_hwp_enable's. A refused machine logs the reason once and writes none of these registers. Every AP must reach the BSP's verdict, or it panics naming itself.One CPUID identity, three verdicts. There is no third decoder.
toyos_cpuvuln::hwpsits besidedecideandcounters, overVendorandIdent, and says whether a CPU's request is declared. It requires HWP, EPP, the package request and EPB, and it requires an Intel family-6 CPU that is not hybrid.toyos_cpuvuln::hwp_requestgives the request. It is intel_pstate's at upstreamv6.8, cited by line:MSR_PLATFORM_INFO[47:40](core_get_min_pstate);__intel_pstate_get_hwp_cap);HWP_EPP_BALANCE_PERFORMANCE;The refusals are ToyOS's own. The module says so, and
issues/kernel/the-perf-state-declaration-refuses-hybrid-intel-and-amd-cppc.mdcarries them.cpu::vendordecodes CPUID.0 once, andcpu::leaf_6reads CPUID.6 once, for both kernel callers, the declaration and the counters' bring-up. Both use the rule the bring-up already had,init_scattered_cpuid_features's: a maximum leaf outside6..=0xFFFFreaches no leaf 6.cpu::leaf_7reads CPUID.(7,0) once in the same way, under the>= 7rule each of its three callers wrote out:control_regs::hwp_declared,control_regs::supported, andboot::report_counter_origin, which is outside this branch's other changes but read the same leaf under the same rule.The read-back is three counters on the one round. They are
Counter::HwpRequest,HwpRequestPkgandEnergyPerfBias, soRECORD_BYTESgoes from 56 to 80.control_regs::envelopereads them on the answering CPU, inside the kick-handler answer #705 already serves. They are present only where the declaration declared them; a counter that is not present has no word, as #705's contract says. There is no second round, no second bound and no second silent-CPU answer. A CPU that misses the bound isstalewith its last block, exactly as for APERF. Each read is of a register that HWP enabled at this CPU's owninit_performanceand that its own check read at boot, so no read can be the first to touch one.The permission. The owner's 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." The envelope is power, so all three need
COUNTERSandTRACE. Today the registers hold what the kernel declared, so they reveal little about another program. Putting them underTRACErather thanCOUNTERSalone is my reading of "power", not his words. TheTRACEandSYS_COUNTERSdocs say so, attoyos-abi, the SDK and the kernel. #705's two ways round the strict right still stand:inspectholdstrace, and the launcher starts any row.The metal row holds what the declaration claims (
counters_on_metal):control_regs: cpuN pm_enable=1 …line;hwp_requestequal to Linux's on the same machine,0x80002a04(tests/t14-linux/hwp-request.txt). That is the independent oracle for the derivation. It is read as root through/dev/cpu/N/msrunder Ubuntu's intel_pstate, andtests/t14-linux/SOURCEnames its record, commit 868378b of Define the T14 LLVM bar as a libc++ stage-3 recipe with an in-tree judge; the bar value is owed #568;hwp_request,hwp_request_pkgandenergy_perf_biasin every one of the three reads, equal to that line, so the bring-up must name all three;yesper CPU (tests/t14-linux/turbostat-loaded.txt) opens its first 10 s interval 2 s into the load. The floor is the lowest of the intervals that open within the spin's own span. For the 11.2 s spin that is the first interval alone, 3800 MHz, and not the 3075 MHz Linux settles to after 30 s.That last check is the exit of
issues/kernel/toyos-runs-the-t14-under-load-below-the-clock-linux-reaches.md, which this branch closes. The reading that meets it is the run at 14fa36d below. At #705's head the T14 read 2318 MHz on every CPU with HWP off.Records kept true.
issues/kernel/the-scheduler-and-the-clocks-never-talk.mdsaid the kernel "neither reads nor sets P-states". It now says the kernel declares HWP's bounds and the CPU chooses within them. The track is rewritten to its stage 1 as built here, and RAPL, turbo and the sampler keep their exits.The T14
At 14fa36d, run by the orchestrator (comment 5978637474). Each image's sha256 matched its request when it was flashed:
shared1882d34c…6532,shared-debug732eea97…6fa1,testcases9641a3f8…8c55. The command wascargo test --test toyos-build -- --metal --metal-readback orch/perfstate3/metal counters, and it exited 0 withPASS countersand[metal] 3 passed, 0 failed, 3 boot(s)(orch/perfstate3/judge-t14-metal.log):On all three boots every CPU's line reads
pm_enable=1 hwp_request=0x80002a04 hwp_request_pkg=0x8000ff01 epb=6 hwp_interrupt=Some(0)after its CR line, and itscounters: cpuN reads …line nameshwp_request=true hwp_request_pkg=true energy_perf_bias=true(orch/perfstate3/metal/testcases/kernel.log).The negative control is the whole change reverted, origin/main d47b383, staged and flashed the same way (
sharedeef2dc5d…afd7,shared-debug8a3794d5…f2e2,testcases4ead9eee…c10f) and judged by 14fa36d with--metal-readback orch/perfstate3/metal-control counters. It exited 1 with[metal] 2 passed, 1 failed, 3 boot(s)(orch/perfstate3/judge-t14-metal-control.log):The floor's own red on the base is #705's reading, 2318 MHz on all eight CPUs with HWP off (
orch/counters2/judge-t14-counters.log:20-27). The control cannot reach the floor, because its bring-up line reds first.The head after the run, b69e713 and later, is held to that run by reading. Its only kernel change is
cpu::leaf_7, which returnscpuid(7, 0)wherever CPUID.0's maximum leaf is at least 7, the rule each of its callers applied before. The T14's maximum leaf is0x1b(toyos-cpuvuln/fixtures/t14/cpuid.txt:1), so every caller reads the registers it read in the run. The kernel's bytes changed and were not booted.The judge over 8c4d1f5's readbacks (copies; the table and the readback edit are posted as a comment). 8c4d1f5, the commit before, wrote HWP before the IDT. The orchestrator ran it on the T14 in comment 5977974485 and it passed (
orch/perfstate2/judge-t14-metal-2.log).spinning 3829 MHz (Linux 3800 over this span, 3075-3800 loaded)some cpu below the 3800 Linux held over that spanhwp-request.txtset to0x80002a05and Linux requests 0x80002a05 on this machineGates, at b69e713
Logs are under the orchestrator's scratchpad,
orch/perfstate4/.cargo run -- --ci hostHost: 73 step(s), all green(ci-host.log:7434)cargo run -- --build-onlybuild-only.log)cargo test --test toyos-build(whole guest suite)26 passed, 26 total(guest-suite.log:259)virt_smpunder load. At 8c4d1f5 the whole suite exited 1, withvirt_smpSTALLED intest_rs_counters_read(orch/perfstate2/guest-suite.log:456). The orchestrator reproduced the same stall on the base binary under the same host load (orch/counterstall/base-2.log). It is a defect of main's round, and #719 ("virt_smp's second job wait goes on from the capture its first wait drained") fixes it; #719 is in the merge queue and not yet onorigin/main, which records the stall nowhere else. This branch lands after #719, or after the stall is filed with the host's load. The green suite here says nothing about the stall being gone.High-risk: CPU state, a disclosure boundary, the ABI
Negative control: the T14 run above at 14fa36d, the whole change reverted onto d47b383: exit 1 against the head's 0.
Mutations (patches posted as comments; each applied, run, reversed, tree clean):
cargo test -p toyos-cpuvulnthe_request_is_the_minimum_ratio_the_highest_level_and_the_preferencea_missing_register_refuses_the_whole_request_by_namehwp_requestanswered onCOUNTERSalonecargo test -p toyos-abiwhat_times_a_program_or_is_power_needs_the_trace_rightLinux requests 0x80002a05 on this machineThe kernel answers a counter only where
rights.contains(counter.needs()), the one filter #705's m8 and m13 held red on QEMU. The envelope's gate is that filter plusneeds(), and h3 holdsneeds().Checked by reading only. Deleting
hwp_check's asserts reds no test. No QEMU CPU enumerates HWP, and the T14's declaration holds.self_check's CR0/CR4/EFER asserts are in the same position on main: theno-ap-control-regsactuator exists and no test boots it. The first design reached this with a diverging actuator and an expected-panic metal boot, about 450 lines of harness, now deleted. The read-back being equal to the boot line in every read, and the boot line being Linux's request, are the runtime claims the row holds instead.Independent oracles.
intel_pstate.candmsr-index.h, for the derivation and the EPB value. These are upstream, not the crate's pinned Ubuntu tag, which launchpad's cgit refused to serve (403).IA32_HWP_REQUESTon the T14,tests/t14-linux/hwp-request.txt, which the row holds every CPU's boot request to.fixtures/t14/cpuid.txt) and/proc/cpuinfoflags (hwp hwp_notify hwp_epp hwp_pkg_req epb, nohybrid_cpu), whichthe_t14_is_declared_with_its_notificationholds the verdict to.Guest tests
None new or changed: no type, host test or guest reaches an HWP register. QEMU's CPUs enumerate no HWP, so every write and read here runs only on the T14. The x86 shared members
test_rs_counters_readandtest_rs_counters_silentran on the T14'ssharedandshared-debugboots at 14fa36d, both passing within3 passed, 0 failed, because the record's width changed under them.Size
git diff --shortstat origin/main...b69e713c2: 23 files, +617 −97. Production code without comments or tests is about 235 lines added and 30 deleted:control_regsabout 110,toyos-cpuvuln/src/hwp.rs72,cpu::vendor,cpu::leaf_6andcpu::leaf_722, and the rest the counter plumbing. The rest of the diff is the hwp tests (about 90), the judge (+45), the oracle file and its source, doc lines and the three issue files.What I am unsure of
cpu::leaf_7, reads what each caller read before wherever the maximum leaf reaches 7, as the T14's does; that rests on reading.hwp_request_pkgis the reading CPU's package's. The T14 has one package.🤖 Generated with Claude Code
https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8