Skip to content

The power-off says what it has to say above the console's last drain, and a one-CPU row reds without it - #767

Merged
Japabu merged 3 commits into
mainfrom
wt/toyos-poweroffline
Oct 8, 2026
Merged

Japabu merged 3 commits into
mainfrom
wt/toyos-poweroffline

Conversation

@Japabu

@Japabu Japabu commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes the red on main: the nightly's tcg / suite at b6bcb9691 (run 37758546165) failed acpi_mediated_access with STALLED: waiting for the probe's power-off to give the lock back — it went quiet, after 18 s: the test's 3 s and the 15 s of GUEST_QUIET.

Everything below is true of ad450251c: e3a2022d1, the head review round 2 read, merged with origin/main ed80f9099 (#763). A row or reading taken at an earlier head names that head.

The merge with #763

#763 brought issues/the-power-offs-global-lock-line-is-logged-after-the-last-console-drain.md to main as status: open; this branch adds the same file. git merge origin/main conflicted in that one path, add/add, and in nothing else (tests/toyos.rs merged by itself). The file was resolved by hand to this branch's bytes.

$ git diff e3a2022d1 ad450251c -- issues/the-power-offs-global-lock-line-is-logged-after-the-last-console-drain.md; echo EXIT=$?
EXIT=0
$ git hash-object issues/the-power-offs-global-lock-line-is-logged-after-the-last-console-drain.md; git rev-parse e3a2022d1:issues/the-power-offs-global-lock-line-is-logged-after-the-last-console-drain.md
f7344e74d77a332ff8c061accac3c4d9bb0b1955
f7344e74d77a332ff8c061accac3c4d9bb0b1955
$ git diff origin/main ad450251c --stat
 ...-offs-give-back-line-is-in-no-black-box-tail.md | 42 ++++++++++++++++++++++
 ...-line-is-logged-after-the-last-console-drain.md | 25 ++++++++++++-
 kernel/src/arch/aarch64/power.rs                   | 10 +++++-
 kernel/src/arch/x86_64/power.rs                    | 23 +++++++++---
 kernel/src/power.rs                                | 16 +++++++--
 tests/toyos.rs                                     | 20 ++++++++---
 6 files changed, 122 insertions(+), 14 deletions(-)
$ git diff e3a2022d1 ad450251c --stat -- kernel/src/power.rs kernel/src/arch/x86_64/power.rs kernel/src/arch/aarch64/power.rs; echo EXIT=$?
EXIT=0

The first prints nothing: the issue file is the blob review round 2 read. The stat is this branch's six files and no other. The last prints nothing: #763 touched no file of this kernel change. What it did rewrite under this change's calls is kernel/src/arch/x86_64/acpi_mode.rs, the claim's lock and release; read at the merged head, acpi_mode::settle(&TakenBack) still gives the lock back with "the machine is stopping", and acpi_mode::quiet, which off calls below the flush, still logs nothing. The gates below are that merged kernel run.

The defect

power::shutdown ran serial::flush_final(), the log ring's last reader for the console, then the USB stop, then arch::power::off. On x86-64 off called acpi_mode::settle, whose give_back logs acpi: the Global Lock given back for a holder that left it taken (the machine is stopping): a record committed after the last drain. It reached the console only if klogd, on another CPU, put it on the wire before this CPU had written SLP_EN. Nothing waited for it. acpi_mediated_access waits for that line.

This was a reading; runs now confirm it. On one CPU there is no other CPU for klogd to run on, and the stop runs with preemption off. With the kernel's change reverted the line never arrives, five boots of five; with it the line arrives, five of five (the table under "Negative control").

What changed

  • The architecture's power-off is two calls. arch::power::settle(Stopping) -> Settled takes the hardware back, waits out an SMI_CMD write and an act for the acpi claim's holder in flight, and gives the lock back: everything that path logs. arch::power::off(Settled) -> ! takes that value and nothing else, so it cannot be reached before the settle, and it logs nothing. power::shutdown is settle, flush_final, stop::before_reset, off.
  • AArch64 hands out nothing a power-off takes back: its settle is empty and its off is unchanged. It already wrote every line straight to the UART under serial::panic_registers.
  • The rule is in kernel/src/power.rs's header, and it names its drain: nothing an end says is logged after the console's last drain. The black box's page is an earlier reader, sealed in quiesce before either end is entered, so what settle says is in no page; the header says that too (see "The page's half").
  • A new guest row, acpi_lock_given_back_on_one_cpu (below).
  • The test's stall carries what the guest said since its boot (tests/toyos.rs), so a stall of either row is evidence and not a sentence. It is what made the control's logs readable.

Chosen over a second flush after settle: a second drain would leave the path able to log past the last one again; here the value off needs exists only once the settle has returned.

The new test, and why this form

acpi_lock_given_back_on_one_cpu is acpi_mediated_access's boot with smp: 1: the same function, which now takes the CPU count (acpi_mediated_access(2) and (1)), one MACHINE_TESTS entry and one run_machine_test arm.

  • Why a test at all. It is the only test in the tree that fails on the defect, and reading did not see the defect: the order went in with The acpi claim's mediated access and the Global Lock, and acpiserver loading the machine's tables through them #749 through its reviews, and on two CPUs the same revert is green on the development machine (acpi_mediated_access, exit 0, control below) and under KVM (six merge-queue runs). Settled makes "off before settle" unrepresentable; it does not make "something below the flush logs" unrepresentable, and that is what this row reds on.
  • Why QEMU, and not a cheaper tier. A type: as above, it holds half. A host test: the behaviour is a record's commit against the console's last drain during a real stop and a real SLP_EN; no host suite runs the kernel's stop. A metal row: the T14 has no serial port and nothing powers it on again after a power-off (issues/no-t14-row-reads-the-power-off-after-the-kernels-own-acpi-enable.md, the owner's rulings there).
  • Why a second row and not smp: 1 on the existing one. The existing row asserts mtrr: cpu1's range registers are …, the second CPU's range registers beside the unlisted read, which one CPU cannot say; and it is the row that crosses the settle's shootdown on two CPUs.
  • Why the shared function and not a trimmed copy. One parameter with two values and one take(cpus); the one-CPU boot judges every arm the two-CPU one does except the cpu1 line. A copy that asserted only the wait would be a second function to keep true.
  • It asserts order and content only; its one clock is the harness's hang ceiling. Cost: one boot, 3 s under TCG on the development machine.

High-risk: the order that moved, and what says each move is safe

The power-off's order was flush, USB stop, take-back (TLB shootdown), SMI_CMD wait, lock give-back, PM1 writes. It is now take-back, SMI_CMD wait, lock give-back, flush, USB stop, PM1 writes.

moved what could go wrong what says it does not
The take-back's TLB shootdown now precedes the flush and the USB stop A CPU that does not answer would hang the stop before its last drain, where before the drain was already out Read: tlb::wait_for panics past DEAF_CPU and never logs, and a panic drains for itself (serial::panic_flush). The shootdown needs nothing the flush or the USB stop provides: pio::take_back asks only that Stopping exist. Run: every power-off row below crosses it on two CPUs; machine_shutdown_short_stop and acpi_lock_given_back_on_one_cpu on one.
The wait for an SMI_CMD write in flight now precedes them A writer that never lets WRITING go would hang before the drain Read: smi_cmd::write holds WRITING with preemption off for one round, itself bounded by a DEAF_CPU assert, and refuses once the stop has begun, so at most one write is waited out, as before. Run: acpi_power_button powers off a machine whose claim was minted.
The Global Lock goes back, with GBL_RLS where the firmware asked, before the flush and the USB stop The firmware gets its lock and possibly an SMI earlier, with xHCI still running Read: earlier is the direction ACPI asks for; xHCI running and owned by the OS is the state every runtime SMI finds it in. give_back touches the FACS word and PM1a_CNT only. Run: both ACPI rows ask for the power-off holding the lock and read the give-back's line.
A machine with no soft-off settles before it halts, where it halted first A settle that does not return on such a machine Read only, no machine here has none: with no PM1a block acpi_mode::hardware() is None and its settle only takes and drops HOLDER; smi_cmd::settle takes and drops WRITING; the shootdown is the one every unmap makes.

No T14 row is owed (the review's reading): reboot, reset_now, arch::power::reset and stop.rs are untouched, so what every T14 boot ends with is main's bytes, and no row reads the power-off. The new order on the T14's firmware is unread, known and tracked in the issue named above.

Negative control

The control is this branch's whole kernel change reverted, git diff e3a2022d1 41b482378 -- kernel/, posted with the one-CPU arm's patch in #767 (comment). On the merged head it is git diff ad450251c 41b482378 -- kernel/src/power.rs kernel/src/arch/x86_64/power.rs kernel/src/arch/aarch64/power.rs, the same bytes as the posted patch (cmp exit 0), so #763's kernel stays under it. Each run applied it with git apply --check and git apply and reversed it with git apply -R; the tree was clean after. TCG, x86-64 guest on the development machine, one test at a time.

head test kernel exit deciding lines
ad450251c acpi_lock_given_back_on_one_cpu as landed 0 [ 2.647 cpu0 kernel] acpi: the Global Lock given back for a holder that left it taken (the machine is stopping), PASS acpi_lock_given_back_on_one_cpu (3s)
ad450251c acpi_lock_given_back_on_one_cpu fix reverted 1 FAIL acpi_lock_given_back_on_one_cpu: STALLED: waiting for the probe's power-off to give the lock back — it went quiet; the appended capture has [ 2.871 test-runner pid=8] acpi: holding the Global Lock, and asking for the power-off with it, [ 2.916 cpu0 kernel] stop: 13 of 13 userland thread(s) stopped across 1 cpu(s) …, ends at [ 2.917 cpu0 kernel] Shutting down., and has no (the machine is stopping) line; STALL acpi_lock_given_back_on_one_cpu (18s); cargo's (exit status: 1)
e3a2022d1 acpi_lock_given_back_on_one_cpu as landed 0 [ 2.796 cpu0 kernel] acpi: the Global Lock given back for a holder that left it taken (the machine is stopping), PASS acpi_lock_given_back_on_one_cpu (3s)
e3a2022d1 acpi_lock_given_back_on_one_cpu fix reverted 1 FAIL acpi_lock_given_back_on_one_cpu: STALLED: waiting for the probe's power-off to give the lock back — it went quiet; the appended capture has [ 2.606 test-runner pid=8] acpi: holding the Global Lock, and asking for the power-off with it, ends at [ 2.650 cpu0 kernel] Shutting down., and has no (the machine is stopping) line; STALL … (18s)
e3a2022d1 acpi_mediated_access (two CPUs) fix reverted 0 [ 2.847 cpu0 kernel] acpi: the Global Lock given back … (the machine is stopping), PASS: on two CPUs klogd wins the race here, which is how the order got in
66d81bdd4 acpi_mediated_access under arm-one-cpu.patch, runs 1, 2, 3 as landed 0, 0, 0 the give-back's line at 2.830, 2.870 and 2.767 s, PASS acpi_mediated_access (3s) each
66d81bdd4 the same, runs 1, 2, 3 fix reverted 1, 1, 1 each FAIL acpi_mediated_access: STALLED: waiting for the probe's power-off to give the lock back — it went quiet after 18 s; each capture has the probe's acpi: holding the Global Lock, and asking for the power-off with it, then stop: 13 of 13 userland thread(s) stopped across 1 cpu(s), ends at Shutting down., and has no (the machine is stopping) line

The kernel's code is the same at 66d81bdd4 and e3a2022d1 (git diff 66d81bdd4 e3a2022d1 -- kernel/ is empty); the rows at 66d81bdd4 are the arm as first measured, before it landed as a row. The rows at ad450251c are the only ones on the kernel that lands, this change over #763's; the two-CPU row under the revert was not run again there.

What the capture says, in the review's terms: the probe asked, the boot's last word came, and the give-back's line is absent. It does not say whether QEMU had exited; #764 brings QMP to this test.

Independent oracle. A recorded real failure: main's nightly tcg / suite at b6bcb9691, run 37758546165. The nightly's reading of the fixed kernel is owed and is the orchestrator's: the first nightly on a main that carries this change, reading acpi_mediated_access and acpi_lock_given_back_on_one_cpu in its tcg / suite. No nightly reading is claimed here.

Gates

On the development machine, at ad450251c

One command at a time, x86-64 guests under TCG; the script echoed each exit, the head was ad450251c and git status --porcelain --ignore-submodules=none printed nothing before and after. The deciding lines are each command's own log's, not the logs themselves.

command exit its log's deciding lines
cargo metadata --locked 0 the lockfile resolves as committed
cargo run -- --build-only 0 Finished \toyos` profile [optimized + debuginfo] target(s) in 25.64s`
cargo test --test toyos-build -- acpi_lock_given_back_on_one_cpu 0 [ 2.647 cpu0 kernel] acpi: the Global Lock given back for a holder that left it taken (the machine is stopping), PASS acpi_lock_given_back_on_one_cpu (3s), test result: ok. 1 passed, 1 total (16.8s; workers: 14s building, 3s testing)
cargo test --test toyos-build -- acpi_mediated_access 0 [ 2.258 cpu1 kernel] mtrr: cpu1's range registers are off …, [ 2.694 cpu0 kernel] acpi: the Global Lock given back … (the machine is stopping), PASS acpi_mediated_access (3s), test result: ok. 1 passed, 1 total
cargo test --test toyos-build -- acpi_power_button 0 PASS acpi_power_button (3s), test result: ok. 1 passed, 1 total
cargo test --test toyos-build -- machine_shutdown 0 PASS machine_shutdown (4s), PASS machine_shutdown_short_stop (4s), test result: ok. 2 passed, 2 total
control: git apply --check, git apply of the fix reverted 0, 0
control: fix reverted, … -- acpi_lock_given_back_on_one_cpu 1 STALLED … it went quiet, test result: FAILED. 0 passed, 1 failed, 0 invalidated, 1 total, (exit status: 1); the row in the table above
control: git apply -R 0 tree clean after

Not run at the merged head: virt_off_names_the_cpus_left_on, AArch64's arm of the split. It was green at e3a2022d1 (exit 0, PASS virt_off_names_the_cpus_left_on (8s)), kernel/src/arch/aarch64/power.rs is the same bytes at both heads, and the guest suite carries it.

CI

At e3a2022d1, run 37769095000, all three checks success:

  • host (job 113284288018): [ci] Host: 78 step(s), all green.
  • toolchain / build (job 113284288293).
  • guest / suite (job 113285233601), under KVM: PASS acpi_mediated_access (3s), PASS acpi_lock_given_back_on_one_cpu (3s), test result: ok. 33 passed, 33 total. This is the new row's first reading under KVM, and what review round 2's one open BLOCKER waited on.

At ad450251c, the merged head: CI run 37772845382, host success (job 113296395164, [ci] Host: 78 step(s), all green), toolchain / build success (113296395707), guest / suite success (113297758591: PASS acpi_mediated_access (3s), PASS acpi_lock_given_back_on_one_cpu (3s), test result: ok. 33 passed, 33 total). cargo run -- --ci host and the whole guest suite were not run on the development machine at either head. The suite is KVM: it shows the new row green there; the row red without the fix is measured under TCG only.

The issue stays, assigned

issues/the-power-offs-global-lock-line-is-logged-after-the-last-console-drain.md is not closed: its exit's first clause is acpi_mediated_access green in the nightly's tcg / suite, which only a nightly on a main that carries this can show. The file is on this branch with status: assigned: it says what #767 landed, that a run has confirmed the reading, and that the nightly's read is owed and is the orchestrator's at landing, who deletes the file on a green.

How it differs from main's copy. #763 landed this file on main as blob e23d06a9a. Against it this pull request changes exactly two hunks (24 insertions, 1 deletion):

  1. line 2: status: open is status: assigned;
  2. after line 57, the last line of main's copy: a blank line and the section ## What landed, and what is still owed, appended to the end of the file.

Lines 1 and 3 to 57 are main's.

"By construction", and its bound. The issue's exit asks for the line on the wire before SLP_EN "by construction and not by klogd winning", and the appended section says that clause is met. It is true of the order: the line is committed above the console's last drain, on the CPU that then drains. It is bounded by that drain being one that may decline (review round 2's NOTE, a reading, no run shows it): serial::flush_final tries the wire a bounded number of times, PANIC_LOCK_SPIN_LIMIT, and then powers off without the tail. klogd holds the wire preemptibly, and on one CPU a klogd descheduled inside its hold when the stop passes can never let go, so Shutting down. and the give-back's line would both be lost. That is main's and not this diff's; machine_shutdown_short_stop already waits on Shutting down. on one CPU through the same last carrier, and the new row is a second test with that exposure and no new kind. It is not yet tracked on main: an issue for it, the power-off's last drain giving up on a wire whose holder cannot run, is filed by #768, which is open.

The page's half

The review's NOTE: quiesce seals the black box's tail (log::seal_tail) before power::shutdown is entered, so the line settle logs is in no page tail, and on a machine with no serial port no reader keeps it. The header now says which drain its rule is about and that the page is not covered. Filed as issues/the-power-offs-give-back-line-is-in-no-black-box-tail.md, with the acpi claim's author (#749) as owner and an exit a reader checks: no record committed to the ring below log::seal_tail on either end.

The seal was not moved here. It is quiesce's, shared with the reboot, which settles nothing, and it stands above xhci::seal_shut, which must be quiesce's last act, while the settle needs the Stopping and runs below it. Sealing after the settle, or settling above the last word, is a second change of the stop's order on the code this pull request's table is about, not two lines.

The same shape elsewhere, read

  • power::reboot: flush_final, an assert!, reset_now. x86-64's reset is one port write and a halt; AArch64's writes PSCI's refusal raw to the UART.
  • stop::before_reset (both ends) writes its account to the black box and commits no record.
  • The fatal paths (panic_reboot::reboot_now, hardlockup, deadline) call reset_now, which never flushes; what they say after the panic's own drain goes raw through panic_registers.
  • A panic inside off (S5 did not take) drains for itself.

None of them logs to the ring after the console's last drain.

Growth

git diff --shortstat origin/main...ad450251c: 6 files, 122 insertions, 14 deletions. Kernel: 41 insertions, 8 deletions, of which 21 lines are comments and 4 are blank; the code is one struct and one function per architecture. Tests: 15 insertions, 5 deletions, the new row among them. The rest is the two issue files: 42 insertions for the new one, 24 and 1 for the one #763 landed.

What the branches in flight merge

git merge-tree of ad450251c with each head:

Unsure of

  • The nightly's tcg / suite has not read this kernel; the two-CPU row's stall there is fixed by the order and by the one-CPU runs, not by a nightly reading, and the order is bounded by a last drain that may decline (above).
  • Under KVM the one-CPU row is read green at e3a2022d1 and at the merged head ad450251c (CI run 37772845382); its red arm under KVM is a reading: the revert was run under TCG.
  • A device call acts on its claim's binding or is refused: a claim lends a &Binding or &isa::Row under the lock its release takes #763's rewrite of the claim's lock under this change's settle is read, and run by the five guest tests above and no more.
  • The no-soft-off order is read and not run.
  • Whether QEMU had exited at each stall is not in the capture.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A

`acpi_mediated_access` reds main's nightly `tcg / suite` (run 37758546165
at b6bcb96) and was seen once more locally under load. The text is the
one written on wt/toyos-pcislot (5d14696), carried here unchanged so
that the fix that follows closes a record main has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
@Japabu

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Negative control at 030594d4c: the kernel's whole change reverted (git diff HEAD HEAD~1 -- kernel/), applied with git apply --check and git apply, cargo test --test toyos-build -- acpi_mediated_access run, restored with git apply -R, tree clean after. Exit 0: green, the race won on this machine.

diff --git a/kernel/src/arch/aarch64/power.rs b/kernel/src/arch/aarch64/power.rs
index a400f9fe7..477042084 100644
--- a/kernel/src/arch/aarch64/power.rs
+++ b/kernel/src/arch/aarch64/power.rs
@@ -35,20 +35,12 @@ pub fn reset() -> ! {
     cpu::halt()
 }
 
-/// What [`off`] takes: this machine hands out nothing a power-off takes back.
-pub struct Settled(());
-
-/// Nothing to settle, and so nothing said.
-pub fn settle(_stopping: crate::quiesce::Stopping) -> Settled {
-    Settled(())
-}
-
 /// `SYSTEM_OFF`, once every other CPU the roster holds has turned itself off
 /// with `CPU_OFF` and PSCI answers it off: the caller puts every core in a
 /// known state first, and this is DEN0022 §5.10.3's own way to. The budget
 /// for all of them is [`DEAF_CPU`]'s span from the SGI: a CPU PSCI still
 /// answers on at its end is named, and the machine powers off regardless.
-pub fn off(_settled: Settled) -> ! {
+pub fn off(_stopping: crate::quiesce::Stopping) -> ! {
     cpu::disable_interrupts();
     let Some(psci) = psci::conduit() else { cpu::halt() };
     irqchip::off_all_but_self();
diff --git a/kernel/src/arch/x86_64/power.rs b/kernel/src/arch/x86_64/power.rs
index 55585056a..0eed43f94 100644
--- a/kernel/src/arch/x86_64/power.rs
+++ b/kernel/src/arch/x86_64/power.rs
@@ -13,7 +13,7 @@ use toyos_acpi::{Reset, Table, TableError, S5, SDT_HEADER_LEN, SDT_REVISION};
 use toyos_userbound::{Mediated, Ports};
 
 use super::cpu;
-use super::pio::{self, Declared, Slot, TakenBack};
+use super::pio::{self, Declared, Slot};
 use crate::drivers::acpi::direct_phys;
 use crate::log;
 use crate::time::{Deadline, Duration, Tripwire};
@@ -153,24 +153,8 @@ const S5_TAKES: Tripwire = Tripwire::absurd(
      this kernel two seconds later never entered it",
 );
 
-/// The hardware taken back for the power-off, with everything [`settle`]
-/// says said: what [`off`] takes, so the log's last drain stands between.
-pub struct Settled(TakenBack);
-
-/// Take the hardware back from whoever was handed it, waiting out a write to
-/// `SMI_CMD` and an act for the `acpi` claim's holder in flight, and give
-/// back a Global Lock that holder was stopped holding.
-pub fn settle(stopping: crate::quiesce::Stopping) -> Settled {
-    let taken = pio::take_back(&stopping);
-    super::smi_cmd::settle(&taken);
-    super::acpi_mode::settle(&taken);
-    Settled(taken)
-}
-
 /// Enter S5, or halt on a machine whose tables named no soft-off.
 ///
-/// **Nothing here logs**: the log's last drain is behind it (`power::shutdown`).
-///
 /// ACPI 6.5 §16.1.6's order: on a machine in ACPI mode, which is the OS's
 /// to put to sleep, every event is disabled and every status cleared first
 /// (`acpi_mode::quiet`), so no event pending at the write wakes it again;
@@ -182,9 +166,12 @@ pub fn settle(stopping: crate::quiesce::Stopping) -> Settled {
 /// the panel shows it, and the black box carries it through the panic's reset,
 /// where a halt would leave a machine that is on, silent, and indistinguishable
 /// from one the power left.
-pub fn off(Settled(taken): Settled) -> ! {
+pub fn off(stopping: crate::quiesce::Stopping) -> ! {
     let (Some(control), true) = (PM1A_CNT.get(), SOFT_OFF.load(Ordering::Acquire)) else { cpu::halt() };
     let control = control.port(0);
+    let taken = pio::take_back(&stopping);
+    super::smi_cmd::settle(&taken);
+    super::acpi_mode::settle(&taken);
     let held = cpu::inw(control);
     if held & SCI_EN != 0 {
         super::acpi_mode::quiet(&taken);
diff --git a/kernel/src/power.rs b/kernel/src/power.rs
index 0e3c69a69..cfca578ed 100644
--- a/kernel/src/power.rs
+++ b/kernel/src/power.rs
@@ -8,13 +8,6 @@
 //! than of whoever asked for one, and what a third caller gets without knowing
 //! it is owed. Resets this kernel does not perform — a TCO or firmware
 //! watchdog, a triple fault, power loss — are outside it and always will be.
-//!
-//! **Nothing an end says is logged after its last drain.**
-//! `serial::flush_final` is the log ring's last reader: a record committed
-//! past it reaches the console only if `klogd`, on another CPU, beats the
-//! register write. What an end says through the ring it says above the flush
-//! (`arch::power::settle`); `arch::power::off` and `arch::power::reset` log
-//! nothing, and a panic in either drains for itself.
 
 use crate::drivers::{serial, xhci::stop};
 
@@ -56,13 +49,11 @@ pub fn reset_now() -> ! {
 
 /// Power the machine off, or halt on one that offers no power-off.
 pub fn shutdown(stopping: crate::quiesce::Stopping) -> ! {
-    // Above the flush, because it logs.
-    let settled = crate::arch::power::settle(stopping);
-    // Nothing drains the log ring after this point, and nothing below logs.
+    // Last chance: nothing drains the log ring after this point.
     serial::flush_final();
     // A power-off takes VBUS with it on a machine whose ports are not
     // always-on and takes nothing on one whose are, so the devices are handed
     // back here for the same reason as at a reboot.
     stop::before_reset();
-    crate::arch::power::off(settled)
+    crate::arch::power::off(stopping)
 }

@Japabu

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Review of 030594d4c against origin/main d78350c71 (merges clean), round 1. Read only; nothing was run.

Net: 4 files, +41 −9. Production (kernel) +38 −8, tests +3 −1. The growth is one value and one function per architecture plus doc lines; accepted.

BLOCKER

  • tests/toyos.rs:1742 and the body's "Negative control" — the control is green on both arms, so no measurement at this head can fail on the claim the change makes, and the cause is still a reading — the body says "No red arm exists on this machine", but the issue this branch carries records one on this machine (252d19247, whole suite 12 wide, load average 45 to 67: STALL after 35 s); the control was run alone, which that same table says passes. Owed before landing, cheapest first: (a) nightly.yml dispatched on this head (workflow_dispatch takes a ref; release is skipped and the cache save is guarded off main, read in the file), and acpi_mediated_access read in its tcg / suite, the instrument that was red; (b) main's rate beside it: run 37763795421 at 1084ddc9a is a second tcg / suite sample of the unfixed kernel, still pending when read, and without it one green run is weighed against one red run of one; (c) a red arm to try locally: the control patch with smp: 1 in this test's BootOptions, both arms, judged on the wait alone (the mtrr: cpu1 line after it is not this arm's subject). On one CPU klogd runs only if the powering-off thread is preempted between the commit and SLP_EN, so the reading predicts STALLED with the fix reverted and the line present with it. I have not run it and do not know that it reds; if it is green on both arms, say so.
  • issues/the-power-offs-global-lock-line-is-logged-after-the-last-console-drain.md (deleted by 030594d4c) — a close whose exit is not met — its exit's first clause is "acpi_mediated_access green in the nightly's tcg / suite", which no run at this head or any other has shown; the body says as much ("closed here on the construction"). Either the dispatch in (a) above is green and its run id goes in the body, or the second commit drops the deletion and the file stays, status: assigned, saying what landed and that the nightly's read is owed and whose it is. Keeping it is also what the sibling branches need: A device call acts on its claim's binding or is refused: a claim lends a &Binding or &isa::Row under the lock its release takes #763 (5d1469615, pushed, not "unpushed" as the body has it) and Power-off goes through the ACPI server: the claim's holder supplies the sleep type, and the kernel's S5 byte scan is deleted #764 (which contains A device call acts on its claim's binding or is refused: a claim lends a &Binding or &isa::Row under the lock its release takes #763) both add this file byte for byte, and a trial merge of each onto this head (git merge-tree) takes the add without a conflict. With the deletion here, either of them landing puts an open issue for a fixed defect back on main with nothing red.
  • Evidence — cargo run -- --ci host green and the whole guest suite at this head are not in the body; it defers both to CI — gh pr checks 767, read once: toolchain / build pass, host pending, guest / suite pending. The landing rests on host and guest / suite at 030594d4c, and on the merge queue's own. Neither reads the fix: guest / suite is KVM, where the unfixed kernel passed six runs of six. Closes without a code change when both are green.

NOTE

What the brief asked to be held, and what was read

  1. The reading is consistent with the code and unconfirmed. log! in Drain::Thread commits and posts a wake (kernel/src/log/mod.rs, emit); nothing drains inline. After flush_final on main the only carrier of a committed record is klogd: woken, scheduled on some CPU, taking the wire, rendering, and then one transmit buffer published to virtio-console and taken by the host, or bursts into the 16550's FIFO. Nothing else could: flush_final had returned, seal_tail had run in quiesce, stop::before_reset writes the black box only, and off's one later drain is its panic's. "It went quiet" does not tell a QEMU that exited from a guest that hung: await_guest reads only whether the capture grew. A stop that hung before the last word, or a probe that never asked, gives the same sentence, which is why the first BLOCKER asks for a measurement and not a second reading. off(Settled) "logs nothing" is true at this head, failure arms included: acpi_mode::quiet, counters::read, pm1_events and stop::before_reset commit no record (the one log! in stop.rs is publish's, at bring-up); S5 did not take, TakenBack::run's expect and the shootdown's deaf-CPU panic all reach halt_all_cpus, which stops the other CPUs and calls serial::panic_flush, a drain of the whole ring through the registers. AArch64's off writes raw under panic_registers. The one site left is the NOTE above.
  2. The moved order, by reading, has no new observer. seal_shut already preceded all of it and still does, so settle runs, as it did inside off, with the xHCI lock sealed and preemption off; what is new is that the controllers are still live during it. The shootdown: no CPU is stopped at either position (x86-64 stops none before S5; AArch64's settle is empty and off_all_but_self stays in off), a CPU spinning on a lock with interrupts closed answers through sync.rs's tlb::poll, and the xHCI interrupt path takes its lock by try_lock_or_owe, so it never spins on the sealed one. SMI_CMD: write refuses once quiesce::begun(), so at most the one write in flight is waited out, now before the USB register stop where it could overlap it before. The lock: settle takes HOLDER, which a mediated access and a lock exchange hold from decision to last instruction, and acting() refuses under it once the stop has begun; both facts hang off STAGE, opened in quiesce::stop, so what The acpi claim's mediated access and the Global Lock, and acpiserver loading the machine's tables through them #749's reviews established does not depend on where in shutdown the settle stands. GBL_RLS now reaches the firmware with xHCI running and owned by the OS, the state every runtime SMI finds it in. T14 rows owed before landing: none. reboot, reset_now, arch::power::reset and stop.rs are untouched by the diff, so what every T14 boot ends with is main's bytes. No row reads the power-off, by the owner's rulings in issues/no-t14-row-reads-the-power-off-after-the-kernels-own-acpi-enable.md; the new order on the T14's firmware is unread and stays so, known and tracked there.
  3. No new test is right; closing is not. Settled holds "no off before settle" and not "nothing logs below the flush"; that second property is nine lines of one function and a thirty-line off, which reading checks, and a gate for it would guard what a reader can see. The stall's appended capture will show whether the probe asked, whether the last word came and whether the give-back's line did, so the next red of this test is evidence. It will not say whether QEMU had exited; Power-off goes through the ACPI server: the claim's holder supplies the sleep type, and the kernel's S5 byte scan is deleted #764 brings QMP to this test and reads the stop's reason, so that is not owed here.
  4. The siblings: as in the second BLOCKER and the second NOTE.

SEND BACK

`power::shutdown` drained the log ring (`serial::flush_final`), stopped
USB, and only then called `arch::power::off`, whose `acpi_mode::settle`
gives back a Global Lock its holder was stopped holding and logs that it
did. The record was committed after the ring's last reader had gone, so it
reached the console only if `klogd`, on the other CPU, put it on the wire
before this CPU had made one port read, `quiet`'s writes and the two
writes of `SLP_TYP` and `SLP_EN`. `acpi_mediated_access` waits for that
line. Under KVM the merge queue's six runs won the race in 3 s each; under
TCG main's nightly lost it (run 37758546165 at b6bcb96, `tcg / suite`:
STALLED after 18 s, which is the test's 3 s and the 15 s of GUEST_QUIET),
and so did a whole-suite run on the development machine at a load average
of 45 to 67 on 14 cores.

The architecture's power-off is now two calls. `arch::power::settle` takes
the hardware back and settles it, which is everything that path logs, and
answers a `Settled`; `arch::power::off` takes that value and nothing else,
so it cannot be reached before the settle, and `power::shutdown` puts the
flush between the two. x86-64's `off` is the PM1 reads and writes and its
panic, which drains for itself. AArch64 hands out nothing a power-off takes
back, so its `settle` is empty; its `off` already wrote every line straight
to the UART. The reboot and the fatal paths were read for the same shape
and do not have it: `reboot` flushes and then asserts and resets, x86-64's
`reset` is one port write, AArch64's writes its refusal raw, and
`stop::before_reset` writes the black box and no record.

What moved besides the log: the take-back's TLB shootdown, the wait for an
`SMI_CMD` write in flight and the lock's give-back now come before the
flush and the USB stop instead of after them, and a machine with no
soft-off settles before it halts where it halted first.

The test's stall now carries what the guest said since its boot: neither
red kept a line of it, which is why the cause was a reading and no run.

`acpi_lock_given_back_on_one_cpu` is `acpi_mediated_access`'s boot on one
CPU. There the power-off's CPU is the only one, the stop runs with
preemption off, and no `klogd` runs beside it: a record committed below
the last drain reaches no wire. With the kernel's change reverted that boot
stalled on the give-back's line three times of three on the development
machine, its capture ending at `Shutting down.`; with it the line arrived
three of three. On two CPUs the same revert is green there, which is how
the order got in. The two-CPU row stays for what one CPU cannot say: the
second CPU's range registers beside the unlisted read.

The issue stays, assigned: its exit names a green nightly `tcg / suite`,
which only a nightly on a main that carries this can show, and the read is
the orchestrator's at landing. The file is the bytes #763 and #764 add,
with its status changed and a closing section appended.

The header's rule is the console's. The black box's tail is sealed in
`quiesce`, before either end is entered, so the settle's line is in no
page: issues/the-power-offs-give-back-line-is-in-no-black-box-tail.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
@Japabu

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Round 1 patches, as run at e3a2022d1 (and, for the one-CPU arm before the row landed, at 66d81bdd4). Each was applied with git apply --check and git apply, run, and reversed with git apply -R by the request script; the tree was clean after.

fix-reverted.patch: the kernel's whole change reverted, git diff e3a2022d1 41b482378 -- kernel/. It is byte for byte what ran at 66d81bdd4 too: the kernel is the same at both heads. With it acpi_lock_given_back_on_one_cpu exits 1 (STALLED on the give-back's line) and acpi_mediated_access, on two CPUs, exits 0.

diff --git a/kernel/src/arch/aarch64/power.rs b/kernel/src/arch/aarch64/power.rs
index a400f9fe7..477042084 100644
--- a/kernel/src/arch/aarch64/power.rs
+++ b/kernel/src/arch/aarch64/power.rs
@@ -35,20 +35,12 @@ pub fn reset() -> ! {
     cpu::halt()
 }
 
-/// What [`off`] takes: this machine hands out nothing a power-off takes back.
-pub struct Settled(());
-
-/// Nothing to settle, and so nothing said.
-pub fn settle(_stopping: crate::quiesce::Stopping) -> Settled {
-    Settled(())
-}
-
 /// `SYSTEM_OFF`, once every other CPU the roster holds has turned itself off
 /// with `CPU_OFF` and PSCI answers it off: the caller puts every core in a
 /// known state first, and this is DEN0022 §5.10.3's own way to. The budget
 /// for all of them is [`DEAF_CPU`]'s span from the SGI: a CPU PSCI still
 /// answers on at its end is named, and the machine powers off regardless.
-pub fn off(_settled: Settled) -> ! {
+pub fn off(_stopping: crate::quiesce::Stopping) -> ! {
     cpu::disable_interrupts();
     let Some(psci) = psci::conduit() else { cpu::halt() };
     irqchip::off_all_but_self();
diff --git a/kernel/src/arch/x86_64/power.rs b/kernel/src/arch/x86_64/power.rs
index 55585056a..0eed43f94 100644
--- a/kernel/src/arch/x86_64/power.rs
+++ b/kernel/src/arch/x86_64/power.rs
@@ -13,7 +13,7 @@ use toyos_acpi::{Reset, Table, TableError, S5, SDT_HEADER_LEN, SDT_REVISION};
 use toyos_userbound::{Mediated, Ports};
 
 use super::cpu;
-use super::pio::{self, Declared, Slot, TakenBack};
+use super::pio::{self, Declared, Slot};
 use crate::drivers::acpi::direct_phys;
 use crate::log;
 use crate::time::{Deadline, Duration, Tripwire};
@@ -153,24 +153,8 @@ const S5_TAKES: Tripwire = Tripwire::absurd(
      this kernel two seconds later never entered it",
 );
 
-/// The hardware taken back for the power-off, with everything [`settle`]
-/// says said: what [`off`] takes, so the log's last drain stands between.
-pub struct Settled(TakenBack);
-
-/// Take the hardware back from whoever was handed it, waiting out a write to
-/// `SMI_CMD` and an act for the `acpi` claim's holder in flight, and give
-/// back a Global Lock that holder was stopped holding.
-pub fn settle(stopping: crate::quiesce::Stopping) -> Settled {
-    let taken = pio::take_back(&stopping);
-    super::smi_cmd::settle(&taken);
-    super::acpi_mode::settle(&taken);
-    Settled(taken)
-}
-
 /// Enter S5, or halt on a machine whose tables named no soft-off.
 ///
-/// **Nothing here logs**: the log's last drain is behind it (`power::shutdown`).
-///
 /// ACPI 6.5 §16.1.6's order: on a machine in ACPI mode, which is the OS's
 /// to put to sleep, every event is disabled and every status cleared first
 /// (`acpi_mode::quiet`), so no event pending at the write wakes it again;
@@ -182,9 +166,12 @@ pub fn settle(stopping: crate::quiesce::Stopping) -> Settled {
 /// the panel shows it, and the black box carries it through the panic's reset,
 /// where a halt would leave a machine that is on, silent, and indistinguishable
 /// from one the power left.
-pub fn off(Settled(taken): Settled) -> ! {
+pub fn off(stopping: crate::quiesce::Stopping) -> ! {
     let (Some(control), true) = (PM1A_CNT.get(), SOFT_OFF.load(Ordering::Acquire)) else { cpu::halt() };
     let control = control.port(0);
+    let taken = pio::take_back(&stopping);
+    super::smi_cmd::settle(&taken);
+    super::acpi_mode::settle(&taken);
     let held = cpu::inw(control);
     if held & SCI_EN != 0 {
         super::acpi_mode::quiet(&taken);
diff --git a/kernel/src/power.rs b/kernel/src/power.rs
index e6c67dac8..cfca578ed 100644
--- a/kernel/src/power.rs
+++ b/kernel/src/power.rs
@@ -8,16 +8,6 @@
 //! than of whoever asked for one, and what a third caller gets without knowing
 //! it is owed. Resets this kernel does not perform — a TCO or firmware
 //! watchdog, a triple fault, power loss — are outside it and always will be.
-//!
-//! **Nothing an end says is logged after the console's last drain.**
-//! `serial::flush_final` is the log ring's last reader for the console: a
-//! record committed past it reaches the wire only if `klogd`, on another CPU,
-//! beats the register write. What an end says through the ring it says above
-//! the flush (`arch::power::settle`); `arch::power::off` and
-//! `arch::power::reset` log nothing, and a panic in either drains for itself.
-//! The black box's page is another reader and an earlier one: its tail is
-//! sealed before either end is entered (`log::seal_tail`), so what `settle`
-//! says is on the console and in no page.
 
 use crate::drivers::{serial, xhci::stop};
 
@@ -59,13 +49,11 @@ pub fn reset_now() -> ! {
 
 /// Power the machine off, or halt on one that offers no power-off.
 pub fn shutdown(stopping: crate::quiesce::Stopping) -> ! {
-    // Above the flush, because it logs.
-    let settled = crate::arch::power::settle(stopping);
-    // Nothing drains the log ring after this point, and nothing below logs.
+    // Last chance: nothing drains the log ring after this point.
     serial::flush_final();
     // A power-off takes VBUS with it on a machine whose ports are not
     // always-on and takes nothing on one whose are, so the devices are handed
     // back here for the same reason as at a reboot.
     stop::before_reset();
-    crate::arch::power::off(settled)
+    crate::arch::power::off(stopping)
 }

arm-one-cpu.patch: the one-CPU arm as first measured at 66d81bdd4, before acpi_lock_given_back_on_one_cpu landed as a row. Test file only; superseded by the landed row.

diff --git a/tests/toyos.rs b/tests/toyos.rs
index 2b4765483..67b70f905 100644
--- a/tests/toyos.rs
+++ b/tests/toyos.rs
@@ -1733,6 +1733,7 @@ fn acpi_mediated_access() -> Result<(), String> {
             // The test kernel, for the Global Lock's actuator too.
             kernel_params: &["i8042-withheld"],
             ready_marker: "acpi: the ACPI row: ",
+            smp: 1,
             extra_root_files: vec![suite_bin(toyos_build::arch::Arch::X86_64, "acpi_mediated")],
             ..Default::default()
         },
@@ -1751,7 +1752,7 @@ fn acpi_mediated_access() -> Result<(), String> {
     said.must_say("acpi: the Global Lock given back for a holder that left it taken (its claim is gone)")?;
     // Whose range registers passed the unlisted read, and how this
     // hypervisor's second CPU holds its own beside them.
-    for line in ["mtrr: the boot processor's range registers: ", "mtrr: cpu1's range registers are "] {
+    for line in ["mtrr: the boot processor's range registers: "] {
         eprintln!("  [acpi] {}", said.must_say(line)?.trim());
     }
     for line in ACPI_MEDIATED_SAID {

@Japabu
Japabu force-pushed the wt/toyos-poweroffline branch from 030594d to e3a2022 Compare October 8, 2026 11:17
@Japabu Japabu changed the title The power-off says what it has to say above the log's last drain The power-off says what it has to say above the console's last drain, and a one-CPU row reds without it Oct 8, 2026
@Japabu

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Review of e3a2022d1 against origin/main d78350c71 (merges clean), round 2. Read only; nothing was run. Reviewed since 030594d4c: kernel/src/power.rs's header, tests/toyos.rs, the two issue files; the kernel's code is unchanged since round 1 (git diff 030594d4c e3a2022d1 -- kernel/ is the header's nine lines).

Net: 6 files, +178 −13. Production (kernel) +41 −8, tests +15 −5, issues +122. Accepted: the test's growth is one row, one parameter and the stall's capture.

Round 1's BLOCKERs

  1. No measurement at the head could fail on the claim: CLOSED. At e3a2022d1, cargo test --test toyos-build -- acpi_lock_given_back_on_one_cpu: as landed, PASS (3s), the give-back's line on the capture after the probe's (r2-one-cpu.log); with fix-reverted.patch, cargo's own exit status: 1, STALLED … it went quiet, the appended capture holding the probe's holding the Global Lock, and asking for the power-off with it, the stop's record across 1 cpu(s), ending at Shutting down., no (the machine is stopping) line (r2-control-one-cpu.log). The two-CPU row under the same patch is green (r2-control-acpi_mediated_access.log). fix-reverted.patch is byte for byte git diff e3a2022d1 41b482378 -- kernel/ (cmp), the whole kernel change. This is suggestion (c); it is the red arm, and the reading is now run. (a) and (b), the nightlies, are the issue's exit and no longer the landing's.
  2. The issue closed on an exit not met: CLOSED. The deletion is gone; the file is added status: assigned, names the orchestrator, says which clauses The power-off says what it has to say above the console's last drain, and a one-CPU row reds without it #767 meets and that the first is not met. Against the siblings' blob e23d06a9a it differs in two hunks (git diff: the status line and the appended section), as the body says.
  3. host and the guest suite unmeasured: OPEN. gh pr checks 767, read once: host pending, toolchain / build pending, guest / suite not yet created. No cargo run -- --ci host at this head is in the body.

BLOCKER

  • Evidence — cargo run -- --ci host green and the whole guest suite at e3a2022d1 are still CI's and still pending — closes with no change to the branch when host and guest / suite are green at this head; guest / suite is also the first reading of the new row under KVM, which the body lists as unread.

NOTE

  • kernel/src/drivers/serial.rs:264 — the one route by which the fixed kernel still loses the line, on main and not this diff's: file it — flush_final is not a drain that must happen: it tries the wire PANIC_LOCK_SPIN_LIMIT times and then powers off without the tail. klogd holds the wire preemptibly (:278); quiesce's drain_inline declines a held wire; seal_shut then leaves preemption off for good. On one CPU a klogd descheduled inside its hold when the stop's thread passes drain_inline can never let go, so the spin is certain to expire, and Shutting down. and the give-back's line are both lost; an iteration count waiting on a holder that cannot run is a wait that cannot succeed. A reading: I did not establish how klogd comes to be descheduled mid-hold there (a quantum expiring inside its hold needs a host stall under TCG), and no run shows it. machine_shutdown_short_stop already waits on Shutting down. on one CPU through the same last carrier, so its history on main is this row's base rate; the new row adds a second test to that exposure and no new kind.
  • PR body and the kept issue, "by construction" — true of the order, and bounded by the item above: the line is committed above the last drain, and the last drain is one that may decline. Prose.
  • PR body, Gates — commands and exits with a deciding line each, not their logs; the logs in the round's scratch directory say what the table says. Prose, as in round 1.

The new row, ruled

  • It stands, and round 1's "no new test" is withdrawn. That ruling rested on reading being able to hold the order; the red arm did not exist yet. With it: this is high-risk code whose claim no test in the tree could fail on (the two-CPU row is green with the fix reverted, measured), and the row is that measurement kept. What Settled refuses, the row does not test; what it reds on is the awaited line going back below the drain, which is the form the defect had (the settle called inside off) and which the type still allows.
  • Tier. The body gives the three reasons and none is answered by a cheaper tier: no type stops a log! below the flush; no host suite runs the stop against a console; the metal machine has no serial port, and the page tail does not carry the line (issues/the-power-offs-give-back-line-is-in-no-black-box-tail.md).
  • Form. A second row over the shared function is the least: the one-CPU boot judges everything the two-CPU one does but the cpu1 range-register line, and loses the settle's shootdown with a holder (its census shows no tlb interrupt), which is why the two-CPU row stays. A flip of the existing row to one CPU would cut both; a trimmed copy would be a second function.
  • Cost. One boot: PASS (3s) under TCG on the development machine, the same image and kernel build as its sibling (the second of the two builds in a row is 1 s, cached); the issue's table has the two-CPU boot at 3 s under KVM. A red costs 18 s.
  • What it asserts. Order and content (must_say_after), no duration; its only clocks are await_guest's liveness ceilings.
  • "One CPU" is QEMU's -smp from BootOptions::smp (tests/common/qemu.rs:2023); the row does not assert it. The capture states it (across 1 cpu(s), the census's widest guest reported 1 cpu(s)), and the control measures it at this head: the same revert is green on two. Drift is an edit to smp: cpus, which a reader sees; the one place it can go unseen is Power-off goes through the ACPI server: the claim's holder supplies the sleep type, and the kernel's S5 byte scan is deleted #764's conflict in this function, and the check there is the control's red arm re-run on Power-off goes through the ACPI server: the claim's holder supplies the sleep type, and the kernel's S5 byte scan is deleted #764's merged head, which the high-risk rule asks of Power-off goes through the ACPI server: the claim's holder supplies the sleep type, and the kernel's S5 byte scan is deleted #764 anyway.
  • Flake in the other direction. With the fix nothing races for the line: it is committed before flush_final, on the CPU that then drains. It is lost only if flush_final does not get the wire: the first NOTE.

The landing

The proposed rule

Declined for tests/CLAUDE.md and reviewer.md. As written it is wrong both ways: machine_shutdown and acpi_power_button wait on a stop's line on two CPUs and are right to, since Shutting down. is logged above two drains and they cross the shootdown; and the class is any wait a second CPU can satisfy by luck, not a stop's lines. What is durable is already at its two sites: the kernel's half in kernel/src/power.rs's header, the test's half in the row's comment in MACHINE_TESTS. tests/CLAUDE.md already has the general caveat (a test whose premise is arranged by a defect passes for the wrong reason).

SEND BACK

…'s bytes

#763 brought issues/the-power-offs-global-lock-line-is-logged-after-the-last-console-drain.md
to main as `status: open`; this branch adds the same file `status: assigned`
with the closing section appended. Add/add in that one path, resolved to this
branch's two hunks: `git diff e3a2022 HEAD -- <the file>` prints nothing.
Nothing else conflicted; tests/toyos.rs merged by itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
@Japabu

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Closing review of wt/toyos-poweroffline at ad450251c against origin/main ed80f9099, round 3. Read only; nothing was built or run. Reviewed since e3a2022d1: the merge commit, CI at the merged head, the body. The change itself was not read again.

Net (git diff --shortstat origin/main...ad450251c): 6 files, +122 −14. Production (kernel) +41 −8, tests +15 −5, issues +66 −1. Unchanged since round 2 but for the issue file main now carries.

Round 2's BLOCKER

  1. host and the guest suite unmeasured at the head: CLOSED. Run 37772845382, headSha ad450251c, concluded success: host (job 113296395164) success, its log ending [ci] Host: 78 step(s), all green; toolchain / build (113296395707) success; guest / suite (113297758591) success, not skipped, with PASS acpi_mediated_access (3s), PASS acpi_lock_given_back_on_one_cpu (3s) and test result: ok. 33 passed, 33 total. Both jobs checked out Merge ad450251c… into ed80f9099…, which is today's origin/main: the tree measured is the tree that lands.

The merge

  • git diff e3a2022d1 ad450251c -- issues/the-power-offs-global-lock-line-is-logged-after-the-last-console-drain.md prints nothing: the file is the blob round 2 read, as round 2 ruled the second lander resolves it.
  • git show --remerge-diff ad450251c has one conflict, add/add in that path, resolved to this branch's side in all three hunks (the status line, the appended section, the markers); nothing else was touched by hand.
  • git diff origin/main ad450251c --stat is this branch's six files and no other.
  • kernel/src/power.rs and both arch/*/power.rs are byte-identical to e3a2022d1; what moved under kernel/src/arch between the two heads is A device call acts on its claim's binding or is refused: a claim lends a &Binding or &isa::Row under the lock its release takes #763's two acpi_mode.rs, main's bytes.
  • The control still reverts the whole kernel change: git diff ad450251c 41b482378 over the three power files is byte for byte git diff e3a2022d1 41b482378 -- kernel/ (cmp exit 0). At the merged head, over A device call acts on its claim's binding or is refused: a claim lends a &Binding or &isa::Row under the lock its release takes #763's kernel: as landed exit 0 with the give-back's line at 2.647 s and PASS; reverted, cargo's exit status: 1, STALLED … it went quiet, the capture holding the probe's ask, the stop's across 1 cpu(s), ending at Shutting down. with no (the machine is stopping) line; restored, tree clean. cargo metadata --locked 0, build-only 0, the four power-off commands 0.
  • git merge-tree --write-tree origin/main ad450251c exits 0.

The issue that stays

The file is status: assigned, kind: defect, which issues/README.md allows, and names who holds it. It says the exit's second and third clauses are met by #767 and the first is not: "no nightly has run tcg / suite on a main that carries #767", the orchestrator reads acpi_mediated_access in the first such nightly and deletes the file on a green. The body's "The issue stays, assigned" says the same and claims no nightly reading. Neither says more than that.

BLOCKER

None.

NOTE

  • Pull request body, "Unsure of", second item — "host and guest / suite have not read the merged head. Under KVM the one-CPU row is read green at e3a2022d1 only" is contradicted by the body's own CI section and by run 37772845382 — prose; the rest of that item (the red arm under KVM is a reading) stands.
  • Pull request body, "Independent oracle" — it names the owed reading as "a nightly.yml dispatch on this head", where the issue and the body's own "The issue stays" name the first nightly tcg / suite on a main that carries this; the second is the exit, and a dispatch on the branch does not meet it — prose.

Arming

It may be armed as it stands. The body's two sentences above are the orchestrator's to correct and wait on nothing. #764's merged head still owes both rows green and the one-CPU row red under the revert, as round 2 said.

LAND

@Japabu
Japabu added this pull request to the merge queue Oct 8, 2026
Japabu added a commit that referenced this pull request Oct 8, 2026
…tles above the console's last drain

Three conflicts, each resolved with both sides accounted for.

kernel/src/arch/x86_64/power.rs: #767's `Settled`, `settle` and
`off(Settled(taken): Settled)` whole. Under that signature stand this
branch's two `expect` lines, in place of #767's halt on a machine with no
soft-off: this branch refuses such a machine before the stop
(`off_refused`), so `off` is never entered without a sleep type and
`SOFT_OFF` is gone with the kernel's `\_S5` reader. The take-back and the two
settles this branch carried in `off` are `settle`'s now. Round 2's NOTE, that
`off` read the sleep type before the last call in flight, is subsumed: `off`
takes what only `settle` returns, so the read is after it by construction.
`off`'s doc line is this branch's, #767's "Nothing here logs" under it.

tests/toyos.rs: #767's `acpi_mediated_access(cpus)`, its `smp: cpus`, its
stall's capture and its row `acpi_lock_given_back_on_one_cpu`; this branch's
`qmp: true`, what the probe supplies and the `guest-shutdown` it reads, and
`acpi_supply_outlives_holder`. The doc comment carries both sides' sentences.

kernel/src/power.rs merged by itself: #767's body, with `settle` above the
final flush, under this branch's doc line and `shutdown_refused`. Both issue
files are #767's bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Merged via the queue into main with commit 15b8f9d Oct 8, 2026
3 checks passed
@Japabu
Japabu deleted the wt/toyos-poweroffline branch October 8, 2026 12:42
Japabu added a commit that referenced this pull request Oct 8, 2026
#767 gave this wait a capture of its own, from the boot log's end. That is
where await_guest's window starts, so with the harness-wide tail a stalled
wait here said its lines twice. The local one goes; the wait is a bare `?`
again. acpi_lock_given_back_on_one_cpu shares the function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Japabu added a commit that referenced this pull request Oct 8, 2026
Clean. #767's commits were already here (921b485), so the merge brings
#765's files alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Japabu added a commit that referenced this pull request Oct 8, 2026
…wer-off issue is closed

The nightly's `portability-macos` sat in `cargo run -- --ci host` until its
`timeout-minutes: 350` cancelled it (run 37778826093, job 113317209315, head
8a883a1): `toyos-userbound`'s `a_port_answers_as_its_declaration_says` never
ended, libtest said so once after 60 s, and the driver waited on cargo with no
bound for the remaining 3 h 36 min. The test's cause is not in the log and is
filed with the measurement that settles it. The unbounded wait is the build
system's, and is fixed here.

`src/ci.rs`: `cargo` and `cargo_logged` both run through `heard`, which reads
the command's output through one pipe and ends a command that says nothing
for `QUIET`, 15 minutes: the command leads a process group, the group is
killed, and the step is red with the last line said. The longest silences
measured in green steps: 78 s in a macOS host job (run 37740449787, a
compile), and in run 37778826093 under 60 s in the Linux `host` and 120 s in
`tcg / suite` (a kernel build). `cargo` used to
hand its child the driver's own stdout and stderr; it now passes the lines
through as `cargo_logged` always did.

`nightly.yml`: the macOS `--ci host` step gets `timeout-minutes: 90`, the
limit the Linux `host` job has, for a step that hangs while it talks.

`issues/the-power-offs-global-lock-line-is-logged-after-the-last-console-drain.md`
is deleted, its exit met: the first nightly `tcg / suite` on a `main` carrying
#767 is run 37778826093 at 8a883a1 (job 113320660798), `PASS
acpi_mediated_access (4s)`, `PASS acpi_lock_given_back_on_one_cpu (4s)`,
`test result: ok. 34 passed, 34 total`. Nothing in the tree cited it. The
order it was about is `kernel/src/power.rs`'s `shutdown`, which says it.

Not built and not run by this commit's author: the request is in the pull
request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
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