Repository navigation
AArch64 stage 4: the boot-timing handoff names the CPU's counter, and the loader hands the count the kernel's clock reads - #657
Conversation
… virt judges it Closes issues/kernel/the-boot-timing-handoff-is-named-for-the-tsc.md, the stage-4 item that waited on an ABI brief; the owner's ruling that the ABI is completely unstable lifts that wait. KernelArgs carried loader_entry_tsc, loader_handoff_tsc and root_read_tsc, and the kernel's report said "the TSC went backwards". On AArch64 both loaders' readings come from arch::counter, CNTVCT_EL0 there, so the ABI and the report named a counter the machine does not have. - toyos-abi: the fields are loader_entry_counter, loader_handoff_counter (readings of the CPU's counter) and root_read_ticks (a span in its ticks). Same offsets; the offset_of! asserts still hold them. - The loader: its tsc() wrapper, whose doc said the counter "counts from reset", goes; every reading calls arch::counter. Its lines say "Loader counter:" and "counter ticks". src/kernelconsole.rs's fixture of the ROOT read line follows. - The kernel: report_power_on says "the counter went backwards". - tests: boot_from_power_on takes the guest's Arch and reads the rate off that architecture's clock record: x86-64's TSC period, AArch64's CNTFRQ_EL0 rate divided into a second as the kernel divides it. A "counter went backwards" line is its own refusal. virt_boot_from_power_on (VirtEl2) runs the same judge on AArch64, where the loader reads the generic timer at EL2 and both binaries speak on the PL011. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
… kernel's clock reads
KernelArgs' counter readings are converted by the kernel against its own
cpu::counter, CNTVCT_EL0. Entered at EL2, the kernel's entry writes
CNTVOFF_EL2 zero before its clock starts, so from then on its virtual count is
the physical count. The loader read CNTVCT_EL0 before that write, under
whatever offset firmware left; the Arm ARM resets CNTVOFF_EL2 to an UNKNOWN
value. The loader's readings and the kernel's were in one frame only where
firmware left the offset zero.
At EL2 the loader now reads CNTPCT_EL0, which EL2 may always read and which no
offset moves. At EL1 the offset is the hypervisor's, the kernel never writes
it, and CNTVCT_EL0 stays the right count. cpu_as_entered and counter share
current_el.
Shown by virt_boot_from_power_on under a mutation that puts an hour's offset
in CNTVOFF_EL2 around each loader reading and takes it back after, interrupts
masked so firmware's timer never sees it: green with this change, and red
("the counter went backwards") with the EL2 arm reading CNTVCT_EL0 again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016t9wjdQkB8SH7bmfUoiy6L
Two conflicts. bootloader/src/main.rs: main's start_kernel takes no layout and a RootImage that is always there; the branch's side is the rename alone, entry_counter and root_read_ticks, on main's signature, handoff and call. tests/toyos.rs: main's cut of the guest suite (520c0d1) deleted the x86-64 guest test boot_from_power_on, its MACHINE_TESTS entry and comment, its run_machine_test arm and its judge; the guest-suite track owes it a metal row. The branch had changed all four. Hunk by hunk of the branch's side: - the SCREEN_TESTS row virt_boot_from_power_on: kept, in main's two-field shape; - the MACHINE_TESTS comment and the run_machine_test arm passing Arch::X86_64: gone with the test main deleted; - the run_screen_test arm virt_boot_from_power_on: kept; - LOADER_COUNTER, POWER_ON, COUNTER_BACKWARDS, GENERIC_TIMER_HZ and the judge: kept for the virt row, AArch64's alone. TSC_PERIOD, the Arch parameter, the x86-64 period arm and the IA32_TSC_ADJUST tail had the deleted test as their only caller and go with it. A refusal carries the serial it read, as the other virt rows' do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
…he entry's three writes Measured at 7192287 with bootloader/src/arch/aarch64.rs's counter put back to 38d2886's, one read of CNTVCT_EL0 at either level: `cargo test --test toyos-build -- virt_boot_from_power_on --nocapture` exits 0. QEMU's firmware hands the loader EL2 with CNTVOFF_EL2 zero, so the two counts read the same there and the row cannot tell which the loader took. 97bb624's message says the arm is "shown by virt_boot_from_power_on under a mutation"; that pair was not run until 7192287, where it reads: an hour in CNTVOFF_EL2 around each loader reading exits 0 as the loader stands, and exits 1 on "boot: the counter went backwards" with the EL2 arm reading CNTVCT_EL0. A mutation is no standing test, so stage 4's owed list takes the arm, with the exit the entry's writes already have there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
|
Mutation patches for the head 91b4b08, each applied with
|
|
Review of #657 at Net lines, Evidence at the head: none of the three is missing. Ruled
BLOCKER
NOTE
REMOVE
SEND BACK |
…, and the handoff's tier is metal's Review of 91b4b08 (#657, issuecomment-5960716527). The row was added to judge 97bb624, the loader reading `CNTPCT_EL0` at EL2. With that arm reverted and nothing else it stays green (f0, exit 0): on QEMU's firmware the two counts are one. What it did fail on is the architecture-neutral handoff in `kernel/src/main.rs` and `bootloader/src/main.rs`, whose tier `issues/build/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md` already names: the metal row `boot_from_power_on`, the judge 520c0d1 cut from QEMU. So the row, its four constants, the judge and the arm go, and `tests/toyos.rs` is `main`'s again. The track's sentence that filed the loader's EL2 read as owed its red goes with it. The exit `main` wrote for the entry's three writes has two arms, and the second, "a loader that writes the opposite values before the handoff", is the negative control this pull request carries: with an hour staged in `CNTVOFF_EL2` around each loader reading, the loader as it stands is green and the loader reading `CNTVCT_EL0` is red, the judge carried in the patch. That red is measured at this commit and recorded in the pull request's body; nothing is owed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
|
Mutation patches for the head 5bf3347, which has no
|
|
Review of #657 at Round 1's BLOCKER
Round 1's NOTEs, REMOVE and rulings
Net lines, Evidence at the head: none of the three is missing. BLOCKERNone. NOTE
REMOVENone. LAND AFTER NAMED CHANGES |
Main moved during the round: #657 (8f6abbc) and #649 (c59e09e) landed after b102097 merged 5daab30. No conflict, and no file is changed on both sides since 5daab30; the `rust` gitlink is the same on both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm
Stage 4 of
issues/kernel/toyos-runs-on-arm64.md: the boot-timing handoff names the CPU's counter, and the AArch64 loader fills it with the count the kernel's clock reads. This closesissues/kernel/the-boot-timing-handoff-is-named-for-the-tsc.md.Head 5bf3347, on
mainat dd87383 (#647).What changed, per decision
The ABI (
toyos-abi/src/boot.rs):loader_entry_tscandloader_handoff_tscbecomeloader_entry_counterandloader_handoff_counter, readings ofcpu::counter.root_read_tscbecomesroot_read_ticks, a span in that counter's ticks.offset_of!asserts still pin them.LAYOUTis the struct's size and does not move.The loader:
tsc()wrapper is deleted. Its doc said the counter "counts from reset", which is not true of the generic timer. Every reading callsarch::counter.Loader counter:andcounter ticks.src/kernelconsole.rs's fixture of the ROOT-read line follows.The kernel:
report_power_onsays "the counter went backwards".x86-64 is a rename.
arch::counterthere isRDTSC, whichtsc()called, so the loader hands the same bytes at the same offsets. Three lines' text is all that changes on that architecture.AArch64 at EL2: the loader reads
CNTPCT_EL0(bootloader/src/arch/aarch64.rs, 97bb624).CNTVCT_EL0.CNTVOFF_EL2zero before its clock starts, so from then on its virtual count equals the physical count.CNTVCT_EL0before that write, under whatever offset firmware had left. The Arm ARM resetsCNTVOFF_EL2to an UNKNOWN value.CNTVCT_EL0is still read there.counterandcpu_as_enteredsharecurrent_el.No test is added, and
tests/toyos.rshas no diff againstmain.virt_boot_from_power_on, is deleted with its four constants, its judge and its arm (5bf3347).kernel/src/main.rsandbootloader/src/main.rs. That behaviour's tier is already named onmain: stage A ofissues/build/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.mdowes it the metal rowboot_from_power_on, the judge 520c0d1 cut from QEMU.The merge (7192287).
main's cut of the guest suite (520c0d1) deleted the x86-64 guest testboot_from_power_on, which this branch had changed, and those changes went with it. Inbootloader/src/main.rsthe branch's side is the rename onmain'sstart_kernel.Issues:
issues/kernel/portable-kernel-code-names-the-tsc.md.issues/boot-media/the-loader-does-only-what-must-precede-the-handover.mdnamesroot_read_ticks.main's. 91b4b08 had filed the loader's EL2 read as owed its red beside the entry's three EL2 writes; 5bf3347 takes that sentence out. The exitmainwrote there has two arms, and the second, "a loader that writes the opposite values before the handoff", is f1 and f2 below: the red is shown here, so nothing is owed.Lines against
main(git diff --shortstat origin/main...5bf33470c): 11 files, +67 −83. Production +64 −55, a host-test fixture +1 −1, tests 0, issues +2 −27.Gates, all at 5bf3347
Logs are in
/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/657-r2/;summary-gates.txt,summary-guest.txtandsummary-mutations.txtcarry each exit with the host's load, which ran between 4 and 12 on 14 cores.cargo run -- --build-onlybuild-x86.logcargo run -- --arch aarch64 --build-onlybuild-a64.logcargo run -- --clippyclippy.logcargo run -- --ci hostci-host.logcargo test --test toyos-build -- virt_ --nocaptureguest-virt.logcargo test --test toyos-build -- --nocapture(the whole guest suite)guest-whole.logCheck: the loader's count at EL2, against an offset firmware may leave
The handoff is the loader-to-kernel boundary of the ABI. The claim 97bb624 makes is that the count the loader hands is in the kernel's frame whatever
CNTVOFF_EL2holds at the loader's entry.Each mutation is one checked patch on the clean head: the deleted row (
judge.patch: registration, constants, judge, arm, as 91b4b08 had them) and a change tobootloader/src/arch/aarch64.rs. Each is run ascargo test --test toyos-build -- virt_boot_from_power_on --nocaptureand taken back, the tree clean after each. The patches and the script are in the newest mutation comment below. Logs aremut-<name>.log; every one rebuilt the loader, and the red is the judge's own line.CNTVOFF_EL2around each loader reading at EL2, the loader as it standsboot: power-on to loader 633 ms, loader 167 ms (ROOT read 8 ms), kernel to Boot: complete 1563 msCNTVCT_EL0boot: the counter went backwards: 3600631144000 at the loader's entry, 3600799116000 at its handoff, 2362285000 at Boot: completecounterreverted and nothing stagedboot: power-on to loader 638 ms, loader 168 ms (ROOT read 8 ms), kernel to Boot: complete 1563 mscpu_as_enteredalso printsmrs cntvoff_el2, nothing writtenCPU: entered at EL2, HCR_EL2.E2H 0, CNTVOFF_EL2 0x0, ID_AA64MMFR4_EL1.E2H0 0x0: the kernel's entry writes E2H clearcurrent_el, which the staging itself needs; every behaviour 97bb624 changed is reverted in it.CNTVOFF_EL2as 0x0 incpu_as_entered, whichstart_kernelcalls beside the loader's handoff reading and not at its entry, under theVirtEl2profile; the loader writes the register nowhere, so there the two counts are one and a plain revert shows nothing. That is why the control stages the offset.Oracle.
CNTVCT_EL0as the physical count minusCNTVOFF_EL2and resetsCNTVOFF_EL2to an UNKNOWN value. QEMU's TCG is a third party's implementation of that definition, and f2's red is its model applying the offset.CNTFRQ_EL0, which the Arm ARM makes firmware's to program.The T14: nothing is requested
No
METALrow reaches this diff.boot_from_power_onis not aMETALrow onmain. 520c0d1 cut the guest test, and stage A ofissues/build/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.mdlists it as owed a metal row.git grep -n boot_from_power_on -- tests srcat the head answers nothing.git grep -n -E 'Loader (TSC|counter)|TSC cycles|counter ticks|power-on to loader|went backwards|loader_(entry|handoff)_|root_read_' -- tests srcanswerssrc/kernelconsole.rs's host fixture, and the wall clock's and two censuses' own "went backwards" lines.What I am unsure of
main's state since 520c0d1 and the guest-suite track owns it. The metal row it owes runs the architecture-neutral source both architectures share. The AArch64 loader's EL2 arm is held by nothing standing: it was shown red once, by f2, with a judge that is not in the tree.guest-whole.log([serial 1]) printedLoader counter: 2193977000 at entry, 2671751000 at the handoff,TSC: 999MHz (period=1000400fs, a ROOT readin 33459000 counter ticks, andboot: power-on to loader 2194 ms, loader 477 ms (ROOT read 33 ms).bcmakes those counts 2194, 477 and 33 ms at that period.🤖 Generated with Claude Code
https://claude.ai/code/session_013UDZQ6fSKw14e4w2TKTRfm