Skip to content

The T14's one-thread clock is held to its three-active-core turbo bin by hlt idle; deep idle lifts it 10 % - #1066

Merged
Japabu merged 4 commits into
mainfrom
cpu-freq
Oct 27, 2026
Merged

Japabu merged 4 commits into
mainfrom
cpu-freq

Conversation

@Japabu

@Japabu Japabu commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

A diagnosis, measured on the T14. This PR updates the records only and changes no code. The fix, deep idle, is being built separately on branch deep-idle.

Problem and cause

On the T14 (i5-1135G7), osbench's cpu.clock never reads above 3789 MHz under ToyOS. Under Linux it reaches 4190 MHz, and ToyOS's one-thread compute rows run about 1.10 times slower.

The cause is idle. Every idle ToyOS core waits in hlt, which is C1 or C1E. With those cores idling there, a one-thread load gets MSR_TURBO_RATIO_LIMIT's ratio for three or more active cores, 38. Linux's idle cores sit in C7, so a one-thread load there gets the ratio for one or two cores, 42. The register reads 0x2626262626262a2a (#866's dump). Nothing else limits the clock:

Candidate Reading
HWP's highest performance Maximum 42 under both systems (0x80002a01 on ToyOS, 0x80002a04 on Linux)
Turbo disabled No: IA32_MISC_ENABLE[38] is clear, CPUID.06H:EAX[1] is set, and the clock runs above the 2.4 GHz base
PL1, PL2, thermal or current limit No such bit logged. The only limit logged is multi-core turbo
C1E promotion The same on both systems

The ratios agree: 4190 / 3789 = 1.106 and 42 / 38 = 1.105.

Measured on the T14 (r2)

Row (p50) A, hlt B, the scout A/B
cpu.clock max ≤ 3789 in all 17 groups 4189 in 13 groups, crunch and work among them; 3789 in fair, net, procs and mem
crunch.single deflate, sha256, matmul, parse 60.76 ms, 626.9 µs, 1.188 ms, 39.73 ms 54.67 ms, 568.0 µs, 1.067 ms, 35.93 ms 1.10 to 1.11
work.compress_inproc 400.6 ms 358.5 ms 1.12
work.build_shaped.j1 (n=5; B spread 817 to 1594 ms) 967.5 ms 1175.8 ms unresolved
  • B is cpu-freq-scout 3cdfa1a97, measurement only, never lands. Every idle wait is mwait 0x31, the T14's _CST C2. It is taken only where CPUID enumerates MONITOR/MWAIT, the break-on-masked-interrupt extension, the sub-state and ARAT. Wakes are unchanged. All 8 CPUs logged taken=true.
  • A is the orchestrator's choice, not the image I staged. It is fileserver: an fsync of a clean file asks the disk for nothing #1067's benchcase boot at 211829921: main 8348c2a2c plus a change to fileserver only, with the same hlt idle and the same row.
  • L, the Linux C1-only check, was not obtained. turbostat printed "Insanely slow TSC rate, TSC stops in idle?" and exited. My command ran it without </dev/null, so it also consumed the rest of the ssh heredoc: the states were never disabled and the second spin never ran. The orchestrator confirmed that every state still reads enabled. L is recorded as no evidence and was not rerun, because B shows the cause on ToyOS itself.

The fix

The fix is deep idle: issues/idle-cpus-enter-the-c-states-cst-names.md, now assigned and being built on branch deep-idle, whose PR carries the design. No MSR that ToyOS declares holds the clock. Writing ratio 42 into the turbo table would set the frequency limits rather than support the hardware.

The owner's direction of 2026-10-27 was general: do it properly, with no workarounds, whatever the plan order. That direction releases his 2026-10-12 ordering ruling, which the AML track now marks superseded. Building deep idle now, and adding cpu.clock to the track's exit, are the orchestrator's decisions under that direction. The scout is not the design: it has no state choice and no latency bound. cpu-freq-scout stays pushed until the deep-idle PR lands, because it is the measured revision.

AMD (Zen 2, Ryzen 7 4800U), today

  • HWP is refused by name, because CPUID.06H:EAX[7] is clear.
  • PPR 55922 rev. 3.06 documents no CPPC MSR interface for this part. Whether the bench's firmware exposes ACPI _CPC is unread, and no PPR excerpt is in the evidence. The documented mechanism is PStateCtl (MSRC001_0062) with Core Performance Boost (HWCR[25], reset 0). ToyOS writes neither, so firmware's state stands.
  • There is no gap in one-thread speed. crunch.single deflate, matmul and sha256 run at 0.98 to 1.02 of Linux's (session-amd). Parse runs faster under ToyOS on both benches (0.53 here, 0.62 on the T14) and is no clock reading.
  • One gap is unverified: PPR says PstateCmd survives a warm reset, and nothing measured shows ToyOS inheriting a slower P-state.

The change

  • issues/toyos-uses-what-modern-hardware-offers-for-speed.md:
    • The cause moves to the measured list: the MSR, 38 against 42, what was ruled out, and at most 3789 MHz under hlt, with a pointer to the C-state track.
    • The internet_download windows' median (2.67 GHz) stays in the unexplained list: the bin bounds it but does not explain it.
    • The AMD bench is stated by its crunch.single ratios, parse excepted.
  • issues/idle-cpus-enter-the-c-states-cst-names.md:
    • Status is assigned: being built on deep-idle.
    • It is now the only home of the scout's figures, including j1 as an unresolved reading.
    • The hlt idle baseline moves here: never below C1/C1E, package never leaving PC0, 3.16 W idle.
    • Its exit gains cpu.clock's maximum at ratio 42 (4185 to 4190 MHz, Linux's on the same T14) in every group where Linux's reaches 42.
  • issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md: the 2026-10-12 ordering ruling is marked superseded.
  • issues/the-scheduler-and-the-clocks-never-talk.md: no longer says idle-state awareness waits for an AML interpreter.
  • Commit 83fc57c6e's message accounts for each reading the old bullet carried.

Checks

  • cargo run -- --ci host at 83fc57c6e: exit 0, 103 steps, docs scope (log). An earlier head, 12396aa9f, also passed in full scope (log).
  • The scout built with --build-only (exit 0), and its disassembly shows monitor; mwait with ecx = 1, then sti; nop.
  • Both images were staged with --metal-readback. B's booted image matches the staged hash, c321adf6…f8c9.

Risks

  • B's j1 reading is unresolved. The deep-idle PR's own measurement has to answer it, not this scout.
  • In 4 of 17 groups (fair, net, procs, mem) B still read 3789, which is unexplained. The exit asks for ratio 42 wherever Linux reaches it, so the deep-idle PR has to answer this too.

🤖 Generated with Claude Code

Japabu and others added 2 commits October 27, 2026 22:15
The T14's MSR_TURBO_RATIO_LIMIT gives ratio 42 to one or two active
cores and 38 to three or more. ToyOS's cpu.clock reads at most 3789 MHz
in every osbench group and Linux's up to 4192, 1.105 apart as 42 and 38
are, and the one-thread crunch rows run 1.09 to 1.10 times slower. The
HWP request (maximum 42 under both systems), turbo, the power limits and
C1E promotion are each the same or not binding under both; the difference
left is idle, hlt's C1/C1E against Linux's C7. That a core in C1 counts as
active is inference until a Linux run limited to C1 and the deep-idle
scout read it, so the speed track keeps the bullet under what is
unexplained and the C-state track, which owns the fix, carries the clock
in its exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…f 42

PR #1066's scout, where every idle CPU waits in MWAIT 0x31 instead of
hlt, read cpu.clock at 4189 MHz in 13 of 17 osbench groups. The hlt
control read at most 3789 MHz in every group, and crunch.single ran 1.10
to 1.11 times faster under the scout. So the bullet moves from the
speed track's unexplained list to its measured one. The C-state track
records the gain beside the scout's slower work.build_shaped.j1. That
reading is unattributed: the control ran on a later main whose memory
charging differs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu Japabu changed the title The T14's one-thread clock is its three-active-core turbo bin, held there by hlt idle The T14's one-thread clock is held to its three-active-core turbo bin by hlt idle; deep idle lifts it 10 % Oct 27, 2026
@Japabu

Japabu commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review r1: #1066 at 20097a0f9 (records only)

Architecture. This is a records-only change of +33/−35 lines in three issue files. The diagnosis belongs where it is placed: the clock ceiling is a consequence of idle, so the C-state track owns the fix and its exit, and the speed track keeps the measured cause. Writing ratio 42 into the turbo table is correctly rejected. It removes the stale "after the AML interpreter" dependency, which was already obsolete once #875 landed, and that is right.

Verified against the evidence.

The records state the bin as a measured consequence and do not assert the "C1 counts as active" mechanism, which is the right labelling. Neither the records nor the PR cite Intel 631121 or the SDM, so there is no vendor claim to check there.

Findings

BLOCKER 1. issues/idle-cpus-enter-the-c-states-cst-names.md:20-22: "ran work.build_shaped.j1 1.22 times slower" is not established.

  • Both arms are n=5 p50s. B's range is 817–1594 ms and A's is 905–987 ms (r2/{A,B}/kernel-excerpt.log), so B's fastest run beats A's fastest.
  • B's mhz_settled is 4189 against A's 2492.
  • The record will steer the deep-idle governor, so a slowdown has to be measured before the record states one.
  • Fix: state it as an unresolved reading, for example "its j1 p50 read 1176 ms against 967 ms, with B spread 817–1594 ms over 5 runs, on a different main; unresolved". The PR body's "B is slower than both bases" needs the same correction.

BLOCKER 2. issues/idle-cpus-enter-the-c-states-cst-names.md:10-13, commit 20097a0f9's message, and the PR body present the orchestrator's design and ordering as the owner's.

  • The owner's recorded direction of 2026-10-27 was general: finish the work properly with no workarounds, the plan order does not matter, and he trusts the orchestrator.
  • These specifics are the orchestrator's reading of that direction: acpiserver reads _CST, the kernel selects per idle entry by exit latency, a wake-latency limit applies, and the work runs ahead of stages 2 to 4.
  • The cpu.clock exit addition was accepted by the orchestrator. Commit 20097a0f9 says "which the owner accepted", and the PR body says "a scope addition the owner accepted".
  • Fix: attribute the decisions as "orchestrator, under the owner's 2026-10-27 direction". Alternatively, keep only "assigned: being built on branch deep-idle" and leave the design to that branch's PR. Correct the commit message. The PR body correction is the orchestrator's.
  • The AML track's "Superseded (owner, 2026-10-27)" may keep the owner's name, because his "order doesn't matter" does release his 2026-10-12 ordering.

BLOCKER 3. issues/idle-cpus-enter-the-c-states-cst-names.md:10-11: "the scheduler plan's stages 2 to 4" points at nothing.

  • No issue defines that plan's stage list. git grep finds no deep-idle "stage 5". the-scheduler-and-the-clocks-never-talk.md has stages 3 and 4 only.
  • issues/README.md requires naming the file.
  • Fix: name the file, or drop the clause, which is ordering history and not a constraint.

BLOCKER 4. issues/idle-cpus-enter-the-c-states-cst-names.md:26: the exit "osbench's cpu.clock at ratio 42" does not say which reading or which groups.

  • The scout itself reads 42 in 13 groups and 38 in four (fair, net, procs, mem), and nobody has read why.
  • The p50 is ramp policy (issues/osbench-times-cpus-leaving-idle-and-not-their-settled-clock.md). A's p50 in two groups is 2792 and 2890.
  • As written, a later reviewer cannot tell whether the exit is met.
  • Fix: state the statistic and scope. For example, use the maximum in the groups whose other CPUs idle, or the maximum in crunch, with Linux's 4186–4190 as the oracle.

BLOCKER 5. issues/toyos-uses-what-modern-hardware-offers-for-speed.md:59-61: the AMD sentence uses a statistic this PR's own deleted text called no reading of the ceiling, and it misquotes the numbers.

  • ToyOS's p50 is 4090–4289, not "4289".
  • Linux's p50 reaches 4037 (tail), so "at most 4033" is false (r1/session-amd/compare.md).
  • The T14 sentence beside it uses the maximum, so the two benches are compared on different statistics.
  • The evidence that actually shows "no gap" is the one-thread work: crunch.single deflate, sha256 and matmul at 0.98–1.02 of Linux. Parse is 0.53 on both benches and is not a clock reading.
  • Fix: state the AMD result by that ratio, with parse excepted, or by the maximum if one is in evidence.

BLOCKER 6. The deletion at origin/main:issues/toyos-uses-what-modern-hardware-offers-for-speed.md:56- drops readings this PR does not explain.

  • Bin 38 explains a 3.8 GHz ceiling. It does not explain the 2.67 GHz (and 2.53 GHz) median effective clock over the internet_download windows.
  • The deleted text also carried the idle package's 3.16 W and "package never leaves PC0".
  • Commit 020413eeb says the bullet "moves" to the measured list, but these readings did not move.
  • Fix: either say where each now lives, or keep it. For example, the download-window median could be named as ramp/idle policy under osbench-times-cpus-leaving-idle…. The 3.16 W hlt baseline belongs in the C-state track, whose exit measures the idle minute's package energy.

BLOCKER 7. issues/toyos-uses-what-modern-hardware-offers-for-speed.md:56-58 duplicates issues/idle-cpus-enter-the-c-states-cst-names.md:17-20.

  • Both files carry 4189 MHz, 13 of 17 and 1.10–1.11. The speed track's own rule for its Measured list is "Each issue carries its reading".
  • Two homes for one reading will drift, and the j1 caveat already lives in only one of them.
  • Fix: keep the scout figures in the C-state track. Reduce the speed bullet to the cause (the MSR, 38 against 42, what was ruled out, at most 3789 under hlt) plus the pointer.

SUGGESTION 1. issues/the-scheduler-and-the-clocks-never-talk.md:22-26 says both couplings "wait for an AML interpreter". That has been stale since #875, and it sits next to the ordering this PR supersedes. A one-clause edit would keep the two records from contradicting each other.

SUGGESTION 2. r2/checks/ci-host-20097a0f9.log ends at "all green" with no EXIT= line, unlike the other two logs, so its "exit 0" claim is not recorded. A docs-only change needs only a consistency review, so this is cosmetic.

SUGGESTION 3 (PR body, the orchestrator's). "PPR 55922 documents no CPPC" covers the MSR interface only. Whether the AMD bench's firmware exposes ACPI _CPC is unread, and the evidence holds no PPR excerpt. The phrase "crunch rows 0.98 to 1.02" omits parse at 0.53. Neither claim is in the records.

SUGGESTION 4. issues/idle-is-hlt-so-every-wake-of-an-idle-cpu-is-an-ipi.md says "no boot prints CPUID.01H:ECX yet". The scout's T14 boot read monitor=true and leaf 5 (40, 40, 3, 0x11121020). This is optional; the deep-idle branch will settle it in any case.

SEND BACK

The C-state track is assigned: it is being built on branch deep-idle,
and that branch's PR carries its design. The AML track's 2026-10-12
ordering ruling is marked superseded, because the owner's direction of
2026-10-27 releases the plan's order. The track's exit gains a cpu.clock
reading, which the orchestrator accepted under that direction. The exit
names its statistic and scope: the maximum at ratio 42 in every group
where Linux's, on the same T14, reaches 42.

Where each reading of the old T14 clock bullet lives now:
- The cause, MSR_TURBO_RATIO_LIMIT 38 against 42 with what was ruled out
  and "at most 3789 under hlt", stays in the speed track's measured list.
- The scout's figures, and its work.build_shaped.j1 p50 stated as an
  unresolved reading with its spread, live in the C-state track only.
- Cores never below C1/C1E, package never leaving PC0 and the 3.16 W idle
  package move to the C-state track, whose exit measures idle energy.
- The internet_download windows' 2.67 (and 2.53) GHz median stays in
  the speed track as unexplained: the bin bounds it, it does not explain it.
- Intel's part specification gives way to the MSR it summarises. The
  5.65 W against PL1 and #1065's network-session maxima are subsumed by
  "no power limit bit logged" and the full session's at most 3789. Both
  remain in #866 and on evidence branch pr-1065.

The AMD sentence states the one-thread crunch.single ratios, parse
excepted, rather than a cpu.clock p50. The scheduler-clocks track no
longer says idle-state awareness waits for an AML interpreter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

r1 response. Head 83fc57c6e. That commit replaces 20097a0f9 and carries the corrected message. Host checks: log, exit 0.

  • B1. j1 is now an unresolved reading. The C-state track and the PR body give the p50s 1176 and 967 ms, B's 5-run spread of 817 to 1594 ms, and the control's different main. The body adds B's mhz_settled of 4189 against A's 2492. "Slower than both bases" is gone.
  • B2. The C-state track now says only "being built on branch deep-idle" and leaves the design to that branch's PR. The new commit message and the body attribute building it now, and the exit addition, to the orchestrator under the owner's general direction of 2026-10-27. The AML track's "Superseded (owner, 2026-10-27)" stays, as you allowed.
  • B3. The "stages 2 to 4" clause is dropped.
  • B4. The exit now reads: cpu.clock's maximum at ratio 42 (4185 to 4190 MHz, as Linux's reads it on the same T14) in every group where Linux's reaches 42. That excludes the groups where Linux's own probes stay near 1.3 GHz, crunch among them.
  • B5. The AMD sentence now gives the crunch.single deflate, matmul and sha256 ratios, 0.98 to 1.02. Parse is excepted as no clock reading.
  • B6. Each deleted reading is accounted for in 83fc57c6e's message:
  • B7. The scout's figures now live only in the C-state track. The speed bullet keeps the cause, at most 3789 under hlt, and a pointer.

Suggestions:

  • S1 is taken: the-scheduler-and-the-clocks-never-talk.md no longer says idle-state awareness waits for an AML interpreter.
  • S2: the new log records EXIT=0.
  • S3 is taken in the body: "no CPPC MSR interface; ACPI _CPC unread", and parse is named.
  • S4 is left for the deep-idle branch.

@Japabu

Japabu commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review r2: #1066 at 83fc57c6e (records only, focused)

Architecture. Nothing changes from r1. The C-state track owns the fix and its exit. The speed track keeps the measured cause and points to the C-state track, and each reading now has one home. The diff is +45/−40 across four issue files.

r1 blockers

  • B1, resolved. j1 now reads "1176 ms against the control's 967, with its own 5 runs spread from 817 to 1594 ms, the control on a different main: unresolved". The figures match r2/{A,B}/kernel-excerpt.log.
  • B2, resolved. The C-state track now says only that it is being built on deep-idle, and that branch exists (local worktree toyos-deep-idle). Commit 83fc57c6e attributes the exit addition to the orchestrator under the owner's 2026-10-27 direction. The AML track's "Superseded (owner, 2026-10-27)" was allowed in r1.
  • B3, resolved. The stage clause is gone.
  • B4, resolved. The reformulated exit is unambiguous and can be judged:
    • It names one statistic, the per-group maximum.
    • Its value, 4185 to 4190 MHz, matches every Linux maximum at ratio 42 in r1/session-t14/linux-cpu-clock.txt.
    • Its scope is set by the same session's Linux reading. In that session, Linux stays at about 1296 in crunch, files, nvme, tail and time. persist reached 42 in one of its two Linux runs, so it falls inside "reaches 42" read existentially.
    • The scout's four 3789 groups (fair, net, procs, mem) reach 4185 to 4186 under Linux in both runs. The scout therefore visibly does not meet the exit, and the record says so.
  • B5, resolved. The AMD figures are deflate 1.01, matmul 1.02 and sha256 0.98 (r1/session-amd/compare.md). "Parse, faster under ToyOS on both benches" holds at 0.62 on the T14 and 0.53 on the AMD bench.
  • B6, resolved. Each reading the old bullet carried is accounted for in 83fc57c6e's message and in the records:
  • B7, resolved. The scout's figures live only in the C-state track. The speed bullet carries the cause, at most 3789 under hlt (B: 13×4189 and 4×3789; A ≤ 3789), and the pointer.

Suggestions taken

  • S1. The scheduler track no longer waits on AML.
  • S2. r2/checks/ci-host-83fc57c6e.log ends EXIT=0 after 103 green steps.
  • S3. The PR body now has "no CPPC MSR interface; ACPI _CPC unread", and parse is named.

I found nothing new that is false in the four records.

Findings

SUGGESTION 1. Commit 020413eeb's message reaches main's history, because the PR merges with --merge. It still says the scout's j1 is "slower", which is the B1 claim this round retracted. Its "moves" is accurate for the final state, since the bullet is in the measured list.

  • The records are the authority, and 83fc57c6e immediately follows with the correction and the per-reading accounting. A reader of git log sees both, so this does not block.
  • A reword is cheap on the implementer's own branch: drop "slower", or fold 020413eeb into 83fc57c6e.

LAND

@Japabu

Japabu commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator Author

Focused r2 LAND at 83fc57c. Records-only; T14 evidence (hlt control at 2118299, scout at 3cdfa1a) is in the evidence links. Landing.

@Japabu
Japabu marked this pull request as ready for review October 27, 2026 22:58
@Japabu
Japabu added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit ea6414e Oct 27, 2026
8 checks passed
@Japabu
Japabu deleted the cpu-freq branch October 27, 2026 23:02
Japabu added a commit that referenced this pull request Oct 28, 2026
…t 0 again

The group-start roster instrument (6a0fa3f) opened the counters' Diag
before each group's first row, and Diag took the process's one SysCap
endowment. `osbench identity` and `osbench fair` each take it again for a
row of their own, found nothing and refused, so both groups exited 1 on
every bench in every boot since. The metal judge says so when it is run
over the readbacks; it was not run.

The endowment is now taken once, by `sys::syscap()`, and every user borrows
it: the counters' Diag, identity's inventory read and fair's job builder.
`osbench_capability_groups_exit_0` boots benchcase.toml under QEMU with
those two groups alone and holds each to exit 0, so a capability taken
twice fails a PR's own QEMU run rather than a nightly bench.

Review r3's suggestions:
- A wait the bound or a new declaration sends to C1 publishes exit 0, so
  `idle_exit` is what the CPU waits in.
- `Plan::learn`'s Unforeseen and Deadline arms have a test.
- The AMD deflate finding records only what was measured. B4 ran the slow
  mode on CPU 10, which earlier read fast, so the record is renamed: the
  mode varies per boot at the same clock and with no running neighbour.
- The track names #1066's exit as unmet, 8 to 10 of 17 group starts against
  Linux's 13, with acpiserver's start-up a measured busy core at four.
- A stray blank line goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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