Repository navigation
The power-off says what it has to say above the console's last drain, and a one-CPU row reds without it - #767
Conversation
`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
|
Negative control at 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)
} |
|
Review of 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
NOTE
What the brief asked to be held, and what was read
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
|
Round 1 patches, as run at
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)
}
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 { |
030594d to
e3a2022
Compare
|
Review of 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
BLOCKER
NOTE
The new row, ruled
The landing
The proposed ruleDeclined for 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
|
Closing review of Net ( Round 2's BLOCKER
The merge
The issue that staysThe file is BLOCKERNone. NOTE
ArmingIt 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 |
…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
#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
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
…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
Fixes the red on
main: the nightly'stcg / suiteatb6bcb9691(run 37758546165) failedacpi_mediated_accesswithSTALLED: 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 ofGUEST_QUIET.Everything below is true of
ad450251c:e3a2022d1, the head review round 2 read, merged withorigin/mained80f9099(#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.mdtomainasstatus: open; this branch adds the same file.git merge origin/mainconflicted in that one path, add/add, and in nothing else (tests/toyos.rsmerged by itself). The file was resolved by hand to this branch's bytes.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", andacpi_mode::quiet, whichoffcalls below the flush, still logs nothing. The gates below are that merged kernel run.The defect
power::shutdownranserial::flush_final(), the log ring's last reader for the console, then the USB stop, thenarch::power::off. On x86-64offcalledacpi_mode::settle, whosegive_backlogsacpi: 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 ifklogd, on another CPU, put it on the wire before this CPU had writtenSLP_EN. Nothing waited for it.acpi_mediated_accesswaits for that line.This was a reading; runs now confirm it. On one CPU there is no other CPU for
klogdto 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
arch::power::settle(Stopping) -> Settledtakes the hardware back, waits out anSMI_CMDwrite and an act for theacpiclaim'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::shutdownissettle,flush_final,stop::before_reset,off.settleis empty and itsoffis unchanged. It already wrote every line straight to the UART underserial::panic_registers.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 inquiescebefore either end is entered, so whatsettlesays is in no page; the header says that too (see "The page's half").acpi_lock_given_back_on_one_cpu(below).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 valueoffneeds exists only once the settle has returned.The new test, and why this form
acpi_lock_given_back_on_one_cpuisacpi_mediated_access's boot withsmp: 1: the same function, which now takes the CPU count (acpi_mediated_access(2)and(1)), oneMACHINE_TESTSentry and onerun_machine_testarm.acpi_mediated_access, exit 0, control below) and under KVM (six merge-queue runs).Settledmakes "offbeforesettle" unrepresentable; it does not make "something below the flush logs" unrepresentable, and that is what this row reds on.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).smp: 1on the existing one. The existing row assertsmtrr: 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.take(cpus); the one-CPU boot judges every arm the two-CPU one does except thecpu1line. A copy that asserted only the wait would be a second function to keep true.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_CMDwait, lock give-back, PM1 writes. It is now take-back,SMI_CMDwait, lock give-back,flush, USB stop, PM1 writes.tlb::wait_forpanics pastDEAF_CPUand never logs, and a panic drains for itself (serial::panic_flush). The shootdown needs nothing the flush or the USB stop provides:pio::take_backasks only thatStoppingexist. Run: every power-off row below crosses it on two CPUs;machine_shutdown_short_stopandacpi_lock_given_back_on_one_cpuon one.SMI_CMDwrite in flight now precedes themWRITINGgo would hang before the drainsmi_cmd::writeholdsWRITINGwith preemption off for one round, itself bounded by aDEAF_CPUassert, and refuses once the stop has begun, so at most one write is waited out, as before. Run:acpi_power_buttonpowers off a machine whose claim was minted.GBL_RLSwhere the firmware asked, before the flush and the USB stopgive_backtouches the FACS word andPM1a_CNTonly. Run: both ACPI rows ask for the power-off holding the lock and read the give-back's line.acpi_mode::hardware()isNoneand its settle only takes and dropsHOLDER;smi_cmd::settletakes and dropsWRITING; the shootdown is the one every unmap makes.No T14 row is owed (the review's reading):
reboot,reset_now,arch::power::resetandstop.rsare untouched, so what every T14 boot ends with ismain'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 isgit 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 (cmpexit 0), so #763's kernel stays under it. Each run applied it withgit apply --checkandgit applyand reversed it withgit apply -R; the tree was clean after. TCG, x86-64 guest on the development machine, one test at a time.ad450251cacpi_lock_given_back_on_one_cpu[ 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)ad450251cacpi_lock_given_back_on_one_cpuFAIL 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)e3a2022d1acpi_lock_given_back_on_one_cpu[ 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)e3a2022d1acpi_lock_given_back_on_one_cpuFAIL 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)e3a2022d1acpi_mediated_access(two CPUs)[ 2.847 cpu0 kernel] acpi: the Global Lock given back … (the machine is stopping),PASS: on two CPUsklogdwins the race here, which is how the order got in66d81bdd4acpi_mediated_accessunderarm-one-cpu.patch, runs 1, 2, 3PASS acpi_mediated_access (3s)each66d81bdd4FAIL acpi_mediated_access: STALLED: waiting for the probe's power-off to give the lock back — it went quietafter 18 s; each capture has the probe'sacpi: holding the Global Lock, and asking for the power-off with it, thenstop: 13 of 13 userland thread(s) stopped across 1 cpu(s), ends atShutting down., and has no(the machine is stopping)lineThe kernel's code is the same at
66d81bdd4ande3a2022d1(git diff 66d81bdd4 e3a2022d1 -- kernel/is empty); the rows at66d81bdd4are the arm as first measured, before it landed as a row. The rows atad450251care 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 nightlytcg / suiteatb6bcb9691, run 37758546165. The nightly's reading of the fixed kernel is owed and is the orchestrator's: the first nightly on amainthat carries this change, readingacpi_mediated_accessandacpi_lock_given_back_on_one_cpuin itstcg / suite. No nightly reading is claimed here.Gates
On the development machine, at
ad450251cOne command at a time, x86-64 guests under TCG; the script echoed each exit, the head was
ad450251candgit status --porcelain --ignore-submodules=noneprinted nothing before and after. The deciding lines are each command's own log's, not the logs themselves.cargo metadata --lockedcargo run -- --build-onlyFinished \toyos` profile [optimized + debuginfo] target(s) in 25.64s`cargo test --test toyos-build -- acpi_lock_given_back_on_one_cpu[ 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[ 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 totalcargo test --test toyos-build -- acpi_power_buttonPASS acpi_power_button (3s),test result: ok. 1 passed, 1 totalcargo test --test toyos-build -- machine_shutdownPASS machine_shutdown (4s),PASS machine_shutdown_short_stop (4s),test result: ok. 2 passed, 2 totalgit apply --check,git applyof the fix reverted… -- acpi_lock_given_back_on_one_cpuSTALLED … it went quiet,test result: FAILED. 0 passed, 1 failed, 0 invalidated, 1 total,(exit status: 1); the row in the table abovegit apply -RNot run at the merged head:
virt_off_names_the_cpus_left_on, AArch64's arm of the split. It was green ate3a2022d1(exit 0,PASS virt_off_names_the_cpus_left_on (8s)),kernel/src/arch/aarch64/power.rsis 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,hostsuccess (job 113296395164,[ci] Host: 78 step(s), all green),toolchain / buildsuccess (113296395707),guest / suitesuccess (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 hostand 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.mdis not closed: its exit's first clause isacpi_mediated_accessgreen in the nightly'stcg / suite, which only a nightly on amainthat carries this can show. The file is on this branch withstatus: 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 onmainas blobe23d06a9a. Against it this pull request changes exactly two hunks (24 insertions, 1 deletion):status: openisstatus: assigned;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 byklogdwinning", 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_finaltries the wire a bounded number of times,PANIC_LOCK_SPIN_LIMIT, and then powers off without the tail.klogdholds the wire preemptibly, and on one CPU aklogddescheduled inside its hold when the stop passes can never let go, soShutting down.and the give-back's line would both be lost. That ismain's and not this diff's;machine_shutdown_short_stopalready waits onShutting 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 onmain: 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:
quiesceseals the black box's tail (log::seal_tail) beforepower::shutdownis entered, so the linesettlelogs 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 asissues/the-power-offs-give-back-line-is-in-no-black-box-tail.md, with theacpiclaim's author (#749) as owner and an exit a reader checks: no record committed to the ring belowlog::seal_tailon either end.The seal was not moved here. It is
quiesce's, shared with the reboot, which settles nothing, and it stands abovexhci::seal_shut, which must bequiesce's last act, while the settle needs theStoppingand 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, anassert!,reset_now. x86-64'sresetis 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.panic_reboot::reboot_now,hardlockup,deadline) callreset_now, which never flushes; what they say after the panic's own drain goes raw throughpanic_registers.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-treeofad450251cwith each head:wt/toyos-amls2,11031d4c7): exit 1, two paths,kernel/src/arch/x86_64/power.rsandtests/toyos.rs; the issue file no longer conflicts. The resolution below was read ate3a2022d1and not again at this head.kernel/src/arch/x86_64/power.rs, two hunks:Settledandsettlestay, over 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 doc foroff;offkeeps this signature,off(Settled(taken): Settled), takes 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 twoexpectlines under it, and drops the take-back and the two settles, which aresettle's now. 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 refuses a machine with no sleep type before the stop, so the fourth row of the table above is gone with that merge.tests/toyos.rs, three hunks, each both sides:MACHINE_TESTSkeeps both new entries; the function isacpi_mediated_access(cpus: u32)under 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 doc comment with this branch's last sentence;run_machine_testkeepsacpi_mediated_access(2),acpi_lock_given_back_on_one_cpuandacpi_supply_outlives_holder.kernel/src/power.rsandkernel/src/arch/aarch64/power.rsmerge by themselves.wt/toyos-amli1,9cf510ce1): merges clean, exit 0.Unsure of
tcg / suitehas 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).e3a2022d1and at the merged headad450251c(CI run 37772845382); its red arm under KVM is a reading: the revert was run under TCG.settleis read, and run by the five guest tests above and no more.🤖 Generated with Claude Code
https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A