Repository navigation
The AArch64 kernel's wall clock is firmware's GetTime, read by the loader and handed over in KernelArgs - #842
Conversation
…ader and handed over in KernelArgs The kernel calls no UEFI service, so the loader asks GetTime (UEFI 2.11 §8.3.1) before ExitBootServices, refuses a date that does not exist at that boundary, and hands the instant in Unix seconds with the counter it was true at: KernelArgs gains wall_clock_secs, wall_clock_counter and wall_clock_known at its end (size 1312 -> 1336, every earlier offset unmoved, LAYOUT follows the size). EFI_TIME::TimeZone is not read: the hardware clock keeps UTC. armed_at's own GetTime goes onto the same reader. arch/aarch64/rtc.rs's owed! goes: it reads the loader's fields, and arch::boot::clock calls clock::init_wall, which now takes the reading and the counter it was true at instead of reading the RTC itself. A reading from before boot is carried forward to it. x86-64 hands its CMOS reading with the counter read as it returns, which is the computation init_wall made before. x86-64 ignoring the loader's reading is filed: issues/x86-64-reads-the-cmos-for-a-wall-clock-the-loader-already-hands-it.md. virt_wall_clock_utc: tests/virtjobcase's RTC is staged at 2033-03-07T09:14:25 (BootOptions::rtc_base, QEMU's -rtc base=), and wall_clock_now's SYS_CLOCK_EPOCH and std SystemTime read within the instant and the seconds since before the boot was built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
|
virt_wall_clock_utc (green arm) at 4dea4e4, part 1 of 1 |
|
negative-control.patch at 4dea4e4, part 1 of 1 |
|
negative control (red arm) at 4dea4e4, part 1 of 1 |
|
guest suite at 4dea4e4, part 1 of 2 |
|
guest suite at 4dea4e4, part 2 of 2 |
|
build-only at 4dea4e4, part 1 of 1 |
|
cargo run -- --ci host at 4dea4e4, part 1 of 9 |
|
cargo run -- --ci host at 4dea4e4, part 2 of 9 |
|
cargo run -- --ci host at 4dea4e4, part 3 of 9 |
|
cargo run -- --ci host at 4dea4e4, part 4 of 9 |
|
cargo run -- --ci host at 4dea4e4, part 5 of 9 |
|
cargo run -- --ci host at 4dea4e4, part 6 of 9 |
|
cargo run -- --ci host at 4dea4e4, part 7 of 9 |
|
cargo run -- --ci host at 4dea4e4, part 8 of 9 |
|
cargo run -- --ci host at 4dea4e4, part 9 of 9 |
|
negative control, first attempt aborted on a full host disk (no verdict) at 4dea4e4, part 1 of 1 |
|
Review of #842 at 4dea4e4 against Net lines ( Evidence at this head: Does the x86 path need a T14 reading?
BLOCKER
NOTE
SEND BACK |
…d the kernel's anchor is pure The review of #842 found that the loader's second `GetTime` call, in `start_kernel`, ran after `watchdog::arm`, inside the TCO's 9.6 s bound, on hardware nothing had measured. The loader now asks `wallclock::now` once, before `blackbox::arm`, and hands that one reading to both the black box's stamp and `start_kernel`. The T14 makes the one `GetTime` call it made before this branch, at the same place; the kernel carries the reading forward on the counter read beside it, so the earlier read costs nothing. `armed_at` goes with its second caller. `init_wall`'s arithmetic moves to `kernel/pure/clock.rs` as `anchor`, with `ticks_to_nanos` beside it, so a host test holds both branches: a reading after boot carried back, and one before it carried forward. The guest test could not see a flipped carry, because on `virt` the gap is about a second and its window is wider than that. The issue's claim that a PC's `GetTime` reads the same CMOS is not measured on the T14, and now says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
|
Mutation script: the review's two named mutations of |
|
Mutation run log, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Negative control script and patch: the whole production change ( |
|
Negative control run log, |
|
Negative control run log, |
|
Review of #842 at 68f759d against Earlier BLOCKER
Earlier NOTEs
The side effect: an invalid firmware date now stamps the black box with 0Accepted. This is not a finding.
Other checks at this head
Net lines (
BLOCKERNone. NOTENone. LAND |
#846, #843, #849, #850, #840, #839, #837) into the batch: the icons' and wallpaper's digests hash with toyos-sha2-hw, and the loader's wall clock reads through its own UEFI bindings The batch's merge of main at f72d53d moved #813's wallpaper and #814's icon digest tests onto `toyos_sha2`. #833, already on main, had replaced the root package's `toyos-sha2` dependency with `toyos-sha2-hw`, so CI's merge of the two compiled no `toyos-build` lib test and both the build system's tests and clippy went red with E0432. Both tests now hash through `toyos_sha2_hw`, as every other SHA-256 the build takes does. #842's `bootloader/src/wallclock.rs` was written on the `uefi` crate, which #815 removes; it now calls `efi`'s `RuntimeServices::get_time`, whose `Time` fields are plain and whose error is the `Status` itself. `start_kernel` takes main's `wall_clock` and the batch's `SystemTable`; the batch's `armed_at` goes, as main replaced it with `wallclock::now`. Cargo.lock takes main's `ntapi`; `nonempty` goes with `gix`, which the batch removed and which was its only user (`cargo metadata --offline`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
BootOptions gains both acpi_tables (this branch) and rtc_base (#842); the one conflict was the two fields and their defaults side by side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
, #836, #839, #840, #842 through #847, #849 and #850, into consent system.toml: sshserver and shell start both toyfetch (#843) and grants. tests/common/qemu.rs: Profile carries both Desktop and MetalAmdVi (#837). tests/toyos.rs: SCREEN_TESTS carries consent_prompt beside virt_wall_clock_utc (#842) and virt_low_ecam (#840). Beyond the conflict lines: #833 replaced the build's toyos-sha2 dependency with toyos-sha2-hw, so consent_prompt digests its job with toyos_sha2_hw::sha256_digest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
The ARM desktop plan's stage U6: the AArch64 kernel gets its wall clock from firmware's
GetTime. The kernel calls no UEFI service, so the loader asks beforeExitBootServicesand hands the answer over inKernelArgs. Theowed!inkernel/src/arch/aarch64/rtc.rsis deleted.What changed, per decision
bootloader/src/wallclock.rs,bootloader/src/main.rs).GetTime(UEFI 2.11 §8.3.1) is asked once, inmain, beforeblackbox::arm. That one reading stamps the black box's page and goes tostart_kernel.GetTimecall, at the place wherearmed_atmade it before this branch. That is beforewatchdog::arm, so it is outside the TCO's 9.6 s bound.armed_atis deleted.GetTimereturns, and the kernel carries the reading forward to its boot on that counter.Civil::is_validrefuses is never handed on. One line in the loader's log says what firmware read, or why there is no reading.EFI_TIME::TimeZoneis not read, because the hardware clock keeps UTC (the owner's ruling in 1090cf6).KernelArgscarries (toyos-abi/src/boot.rs):wall_clock_secs(Unix seconds),wall_clock_counter(the counter when that instant was true) andwall_clock_known(0 or 1).LAYOUTfollows the size.clock::init_walltakes a reading plus the counter it was true at, instead of callingarch::rtc::read(century_reg)itself. That removes x86's century register from a generic signature.kernel/pure/clock.rs):anchor(secs, at, boot, period_fs)gives the Unix second at boot, andticks_to_nanosmoves beside it.init_wallcallsanchor.arch/aarch64/rtc.rs,arch/aarch64/boot.rs::clock):read(args)turns the loader's fields into a reading, or into the refusalNotHanded. Awall_clock_knownother than 0 or 1 panics: the loader writes only those two values.arch/x86_64/boot.rs, one line):rtc::read(century_reg).map(|civil| (civil, cpu::counter())).init_wallreadnanos_since_boot()right after the CMOS read. The new code reads the counter right after it and does the same subtraction, so the anchor is the same number.clock: the RTC reads … UTCis unchanged.issues/x86-64-reads-the-cmos-for-a-wall-clock-the-loader-already-hands-it.md.GetTimefrom the same CMOS is not measured, and the issue says so. Its exit names that measurement.arch/x86_64/rtc.rsis outside this fence.virt_wall_clock_utc(tests/toyos.rs,Profile::Virt, HVF on an Arm host):tests/virtjobcasegains the jobtest_rs_wall_clock_now, and its RTC is staged at2033-03-07T09:14:25through a newBootOptions::rtc_base, which becomes QEMU's-rtc base=.SYS_CLOCK_EPOCHand std'sSystemTime::now(), aswall_clock_nowprints them, lie between that instant and the instant plus the seconds since before the boot was built.SYS_CLOCK_EPOCHanswers.wall_clock_nowis added toDRIVEN_AND_SHARED, since the shared boot still runs it on x86-64.virtthe reading is about a second before boot, and the guest test's window is wider than that.Why a QEMU guest test
GetTimereading the PL031, the loader beforeExitBootServices, and the kernel's anchor read through a syscall and through std.virtis the only AArch64 machine.rtc_basewould leave the guest on the host's current time. That fails this test by years, so the new field cannot be silently inert.Checks (high-risk: the boot ABI)
Every run below is at head
68f759df3(origin/main merged), and every log is posted whole as a comment on this PR.cargo test --test toyos-build(the whole guest suite: 55 passed;virt_wall_clock_utcreads epoch 1993799668, 3 s past the staged instant, and std the same; everyvirt_*row and the x86-64 boots with the reordered loader)bootloader,toyos-abi,kernel) onto origin/main and keep the test. The script checks that the production tree equals origin/main, runsvirt_wall_clock_utc, and restores the tree. Red:wall-clock: no epoch.anchornamed by the review, each a checked patch applied, run and restored:saturating_add→saturating_sub(red: the carry-forward test and the 24 MHz test fail) andanchorreturningsecs(red: every test but the equal case fails). The unmutated run is green.cargo run -- --build-onlycargo run -- --build-only --arch aarch64cargo run -- --ci host(77 steps green; theFAILEDlines in it are the[ci] controlmutation arms reaching their verdicts)anchor's carry-back branch, and a green suite whose x86-64 guests boot with the newKernelArgslayout and the loader's reordered read.console_image_boots,machine_shutdown*,netstack_*,screen_*andnvme_disk_keeps_log_and_home.wall_clock_utc(issues/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md, stage C).Net lines (
git diff --numstat origin/main...68f759df3): production +154 / −51 across 9 files (the new loader reader and the pure anchor among them), tests +96 / −4 (the anchor's host tests among them), and one issue file of +23.Unsure
CNTPCT_EL0, and the kernel readsCNTVCT_EL0after zeroingCNTVOFF_EL2.loader_handoff_counteralready rests on that assumption.virt_jobs_at_el2runswall_clock_nowon the EL2 entry with the RTC staged, but it asserts only that an epoch is printed, not the instant.KernelArgslayout and the loader's reordering have not run on x86 hardware, because the brief names no metal directory. Nothing new runs inside the TCO bound: the oneGetTimecall is atarmed_at's old place, beforewatchdog::arm, and the work added inside the bound is three field stores. Only QEMU's x86-64 guests have run this loader.🤖 Generated with Claude Code
https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C