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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ members = [
"toyos-net-wire",
"toyos-pci",
"toyos-pcid",
"toyos-perfstate",
"toyos-proclife",
"toyos-ps2",
"toyos-quiesce",
Expand Down Expand Up @@ -184,6 +185,9 @@ toyos-sched = { path = "toyos-sched", features = ["check"] }
# The Bulk-Only phases, so the harness judging a wedge reads the word
# `toyos_xhci::bot::Phase` declares instead of spelling it a second time.
toyos-xhci = { path = "toyos-xhci" }
# The performance request, so `perf_request`'s metal row holds each CPU's boot
# line to the declaration the kernel makes from that line's own inputs.
toyos-perfstate = { path = "toyos-perfstate" }
# The second reader `pkg_install_gbae` is judged against: what is read back off
# the guest's volume is compared with a third party's decoding of the committed
# archive, never with `userland/pkg`'s own. The archive itself is committed
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
status: open
kind: tooling
opened: 2026-09-29
---

# `usbload` seals a panel census that `tests/metal-profile.toml` does not price, so its next metal run reds on it

Evidence, read from the tree at `00d6966a`:
- `tests/metal-profile.toml` prices `boot.usbload.complete_ms`, `back_secs`,
`stick_secs` and `deadline_lateness_ms`, and no `boot.usbload.panel_max_us`
or `boot.usbload.panel_us`. Every other boot but `perfdiverge`, which ends in
a panic and seals none, is priced for both.
- `usbload` is ended by the boot deadline, and `seal_wedge`
(`kernel/src/drivers/panic_console/mod.rs`) seals `{said}{Census}` on that
path, so the page after the reset carries `panel: paints=`.
- `tests/common/metal.rs`'s `boot_findings` judges every census a boot carries,
and `Profile::judge` answers `Unfit::Unpriced` for a name with no row.

Not measured on the T14. No `usbload` run has been read since the census
existed.

**Exit**: `boot.usbload.panel_max_us` and `boot.usbload.panel_us` priced as
`boot.deadlinewedge.*`'s are, since both are ended by the same bound, and a
`usbload` metal run that reads green on both.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
status: open
kind: defect
opened: 2026-09-29
---

# A nonblocking perf-state read can only answer `WouldBlock`

`kernel/src/object/ops.rs:396` (`DeviceType::PerfState => claim.read_perf_state(&mut None, buf)`)
is the nonblocking arm: on SMP it always issues a fresh ask, sends an IPI to
kick every other CPU, and still returns `WouldBlock`, because the ask cannot
be answered within the call that issued it. No retry ever succeeds, and a
poll on the claim is refused rather than reporting the readiness a
nonblocking read would need. A CPU-wide shootdown that can only ever answer
"try a blocking read instead" is pure cost.

Exit: `read_block_device`/poll on `PerfState` refuse `NotSupported` by name,
so a caller learns not to retry nonblocking rather than paying the kick to be
told again; `object::ops.rs`'s `&mut None` arm for `PerfState` goes with it.
62 changes: 62 additions & 0 deletions issues/kernel/the-kernel-owns-cpu-performance-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
status: open
kind: track
opened: 2026-09-28
---

# The kernel owns CPU performance state

The self-hosting bar is measured under a fixed power envelope that must be
read back for a whole build span.
The kernel declares the envelope from the one CPU-state declaration
(`kernel/src/arch/x86_64/control_regs.rs`), and a `perf-state` claim reads it
back per CPU.

**A register reaches a claim only once boot has proven it.** The kernel has no
`rdmsr` fault fixup, so a register a userland read is the first to touch is a
kernel `#GP` any holder of the claim can cause. Each register a stage adds is
enumerated by CPUID or read at boot under the declaration's proof
(`control_regs::HwpDeclared`) before any read of the claim can reach it.

- **1 — the HWP request, declared and read back.** `IA32_HWP_INTERRUPT` 0
where CPUID enumerates it, `IA32_PM_ENABLE`, `IA32_HWP_REQUEST` (min: the
package's maximum-efficiency ratio; max: the CPU's highest performance; EPP
128), `IA32_HWP_REQUEST_PKG` and EPB 6, written whole on every CPU and
asserted on each, refused by name on a CPU that lacks any of them or is not
DisplayFamily 06H (`MSR_PLATFORM_INFO`'s family). Read back through the
`perf-state` claim, together with the turbo bit and package thermal status
(`/system/bin/perfstate`). *Exit*: in QEMU, `perf_request` (the refusal) and
`perf_state_silent_cpu` (a CPU that never answers is refused `Io` by name);
on the T14, `perf_request`'s metal row.
No test launches `/system/bin/perfstate`, so its row's `devices` is
unmeasured.
- **2 — RAPL, declared.** PL1, PL2 and their windows through
`MSR_PKG_POWER_LIMIT`, and the MMIO mirror in the host bridge's MCHBAR, at
the bar's values; the peak limit beside them. A limit firmware locked (bit
63) is refused by name, never worked around. `MSR_RAPL_POWER_UNIT` and
`MSR_PKG_POWER_LIMIT` are enumerated by no CPUID bit, so each is read at
boot before the claim answers it. *Exit*: the T14 reads both back at the
bar's values.
- **3 — turbo, declared.** `IA32_MISC_ENABLE` bit 38 is read back and not
written: its other bits are model-specific and firmware's, so declaring one
bit needs the owner's ruling on writing that register whole. Linux's
`platform_profile` has no ToyOS counterpart, and it is how the bar's firmware
limits were chosen. *Exit*: the ruling, and the bit declared or recorded
as firmware's.
- **4 — the sampler.** A program that reads the envelope every 60 s and at a
span's start and end, and turns `MSR_PKG_ENERGY_STATUS` into the first
60 s's package power and `IA32_PACKAGE_THERM_STATUS` with
`MSR_TEMPERATURE_TARGET` into a temperature, which are the bar's validity
conditions. The energy counter and the temperature target are enumerated by
no CPUID bit, so each is read at boot before the claim answers it; and the
energy counter is a side channel (CVE-2020-8694), so it reaches only a row
the owner rules on, never the `perfstate` row any session can launch.
*Exit*: one valid span on the T14.

**Not covered.** Hybrid Intel and AMD CPPC are refused:
`issues/kernel/the-perf-state-declaration-refuses-hybrid-intel-and-amd-cppc.md`.
AArch64 declares nothing, and the AArch64 kernel refuses the claim by name.

**What only the T14 proves.** No QEMU CPU enumerates HWP (TCG's `qemu64`, and
KVM, which reduces leaf 6 to `ARAT`), so every write and every read of these
registers runs only there.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
status: open
kind: defect
opened: 2026-09-29
---

# The perf-state declaration refuses hybrid Intel and AMD CPPC

Every Intel client CPU since Alder Lake is hybrid, and AMD CPUs name their
performance controls through CPPC, not HWP. On both, the kernel declares no
performance request, firmware's values stand, and a `perf-state` claim is
refused `NotFound`.

The refusal sites, all in `toyos-perfstate/src/lib.rs`'s `refusal`:
- `Refusal::Hybrid`, for CPUID.07H:EDX[15]. The reason it gives is that the
declared minimum is a ratio (`MSR_PLATFORM_INFO[47:40]`), and a hybrid
CPU's HWP scale is not its ratio scale.
- `Refusal::NoHwp`, for CPUID.06H:EAX[7] clear. AMD does not set that bit, so
this is where an AMD CPU with CPPC is refused. The CPPC controls are not
read at all.
- `Refusal::NotIntel`, for HWP on any vendor but `GenuineIntel`, because the
minimum comes from `MSR_PLATFORM_INFO`, which is Intel's.

`kernel/src/arch/x86_64/control_regs.rs`'s `hwp_declared` logs the refusal once
and declares nothing on any CPU.

**Exit**: on a hybrid Intel CPU, each core type's request is declared on that
core's own HWP scale, and the minimum is not read as a ratio. On an AMD CPU
with CPPC, `MSR_AMD_CPPC_ENABLE` and `MSR_AMD_CPPC_REQ` are declared from its
`MSR_AMD_CPPC_CAP1` and asserted on every CPU. On each machine the claim reads
the declaration back, and `perf_request`'s metal row passes on one machine of
each kind.
5 changes: 5 additions & 0 deletions kernel/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ toyos-gpt = { path = "../toyos-gpt" }
toyos-hda = { path = "../toyos-hda" }
toyos-pci = { path = "../toyos-pci" }
toyos-pcid = { path = "../toyos-pcid" }
toyos-perfstate = { path = "../toyos-perfstate" }
toyos-tco = { path = "../toyos-tco" }
toyos-proclife = { path = "../toyos-proclife" }
toyos-ps2 = { path = "../toyos-ps2" }
Expand Down
11 changes: 11 additions & 0 deletions kernel/src/actuator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,17 @@ actuators! {
/// Make one CPU ignore a kick.
dump_deaf_cpu = "dump-deaf-cpu";

/// Grant a `perf-state` claim where no performance request was declared,
/// each record its CPU's identity and zeros, and have no CPU but its asker
/// answer the boot's first three asks: what the read's bound refuses, and
/// then a read every CPU answers, on a machine QEMU can stage.
perf_state_deaf_cpu = "perf-state-deaf-cpu";

/// Have cpu1 move its HWP request off the declaration when it answers a
/// `perf-state` read, then check it as boot does: the negative control on
/// that check, which only a CPU with HWP reaches.
perf_request_diverges = "perf-request-diverges";

/// On one CPU, file Ctrl+Alt+D's request inside each kind of pass that may not serve it and inside a report, and count the Ring 3 returns each is left pending across.
dump_in_blocking_pass = "dump-in-blocking-pass";

Expand Down
1 change: 1 addition & 0 deletions kernel/src/arch/aarch64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub mod irqchip;
pub mod keyboard_controller;
pub mod paging;
pub mod percpu;
pub mod perf_state;
pub mod pio;
pub mod pmu;
pub mod rtc;
Expand Down
24 changes: 24 additions & 0 deletions kernel/src/arch/aarch64/perf_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! The performance envelope's registers. AArch64 declares no performance
//! request, so there is no proof to read one with and every claim is refused.

use toyos_abi::perf::{CpuRegisters, PackageRegisters};

/// Uninhabited: nothing on this architecture can hold one.
pub enum Declared {}

pub fn declared() -> Result<Declared, &'static str> {
Err("AArch64 declares no performance request: this kernel programs none of its CPUs' \
performance controls")
}

pub fn read_cpu(declared: &Declared) -> CpuRegisters {
match *declared {}
}

pub fn read_package(declared: &Declared) -> PackageRegisters {
match *declared {}
}

pub fn diverge(declared: &Declared, _: u32) {
match *declared {}
}
Loading
Loading