Repository navigation
ACPI stage 1: the machine in ACPI mode for a userland server's claim; its SCI, power button and EC events served - #713
Conversation
… its SCI, power button and EC events served
The kernel declares every I/O port it drives in one place (`arch::pio`), the
only maker of the token `cpu::{in,out}{b,w}` take: COM1, the 8259 pair, the
POST port, the CMOS RTC and the PCI configuration mechanism fixed, and the
i8042, the reset register, the PM1a control block, SMI_CMD and the TCO block
as their probes and tables name them. An `isa` row is refused naming the
holder of any port it shares; the I/O permission bitmap covers the whole
port space.
The `isa` rows are filled at boot: the i8042's, and the ACPI fixed hardware's
(the FADT's PM1a event and GPE0 blocks, the ECDT's two EC ports, the SCI as a
level line). `DeviceType::Acpi` claims the second; the mint writes
ACPI_ENABLE to SMI_CMD where SCI_EN reads clear, parks and polls SCI_EN every
1 ms for up to 3 s, and logs this CPU's SMI count before and after through the
counters; the release writes ACPI_DISABLE where the mint wrote the enable and
reads SCI_EN back. A machine with no ECDT, or a control-method power button,
stays in legacy mode, refused by name. A level line is masked by its handler
and unmasked by the holder's acknowledgement, a 4-byte write of 1 to the claim.
The I/O APIC's topology is written once; each unit's register pair is a
`Masked` lock a handler may take, a routed pin keeps its entry's low word, and
a mask is one write of it. The SVR is written whole and asserted. The SCI
defaults to level, active low where no override says otherwise (ACPI 6.5
Table 5.9). `power::off` disables and clears every event on a machine in ACPI
mode, then writes SLP_TYP and SLP_TYP|SLP_EN over the bits PM1a_CNT holds.
`/system/bin/acpiserver` takes the claim: disables and clears every event,
enables the power button, the EC's GPE and the GPEs the AML would run (none in
stage 1, `aml.rs`), drains the EC's backlog, and acknowledges. A press stops
the machine through the supervisor; the EC's GPE drains the controller of its
queries (`ec.rs`, a transaction with no I/O), which then run as `aml::query`
says, after the drain. Each query number is logged once and the counts every
30 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
Stage 1 reads the embedded controller from the ECDT so the T14 switches to ACPI mode now; that path goes when the interpreter reads the controller from the DSDT, and a machine without an ECDT stays in legacy mode until then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
… host Seen in this branch's whole guest suite at b9430ed (load average 82 on 14 cores); the test alone at that load passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Mutation patches and the negative control, each applied with m1-isr-skips-maskdiff --git a/kernel/src/isa.rs b/kernel/src/isa.rs
index b190377c3..1418e7db4 100644
--- a/kernel/src/isa.rs
+++ b/kernel/src/isa.rs
@@ -232,11 +232,6 @@ pub fn has_irq(row: usize) -> bool {
/// own and the I/O APIC's masked one, and allocates nothing.
pub fn isr(row: usize) {
IRQ[row].took();
- for &line in lines(row) {
- if pio::level(line) {
- pio::set_masked(line, true);
- }
- }
WATCHES[row].post_in_place();
}
m2-sci-conforms-edge-highdiff --git a/toyos-acpi/src/madt.rs b/toyos-acpi/src/madt.rs
index 5c87c3371..005e317f1 100644
--- a/toyos-acpi/src/madt.rs
+++ b/toyos-acpi/src/madt.rs
@@ -208,5 +208,5 @@ pub fn isa_line(irq: u8, overrides: &[SourceOverride]) -> Line {
/// sharable, level, active-low interrupt, which is its default whether no
/// override names it or one names it conforming.
pub fn sci_line(sci_int: u16, overrides: &[SourceOverride]) -> Line {
- line(u32::from(sci_int), overrides, (Trigger::Level, Polarity::Low))
+ line(u32::from(sci_int), overrides, (Trigger::Edge, Polarity::High))
}m3-claim-skips-holderdiff --git a/kernel/src/isa.rs b/kernel/src/isa.rs
index b190377c3..19f652a78 100644
--- a/kernel/src/isa.rs
+++ b/kernel/src/isa.rs
@@ -124,7 +124,7 @@ pub fn claim(set: IsaId) -> Result<usize, ClaimError> {
/// left masked for the holder's first acknowledgement.
pub fn claim_row(row: usize) -> Result<usize, ClaimError> {
let function = function(row).ok_or(ClaimError::Absent)?;
- if let Some((run, holder)) = function.runs.iter().find_map(|&run| pio::holder(run).map(|h| (run, h))) {
+ if let Some((run, holder)) = function.runs.iter().find_map(|&run| pio::holder(run).map(|h| (run, h))).filter(|_| false) {
log!("isa: {}'s ports {:#x}+{} are {holder}'s", function.name, run.first(), run.count());
return Err(ClaimError::KernelDriven);
}m4-ack-unmasks-nothingdiff --git a/kernel/src/isa.rs b/kernel/src/isa.rs
index b190377c3..d3eb72f4d 100644
--- a/kernel/src/isa.rs
+++ b/kernel/src/isa.rs
@@ -180,9 +180,7 @@ pub fn ack(row: usize) -> Result<(), ()> {
if level.peek().is_none() {
return Err(());
}
- for line in level {
- pio::set_masked(line, false);
- }
+ level.for_each(drop);
Ok(())
}
m5-declare-skips-clashdiff --git a/toyos-userbound/src/port.rs b/toyos-userbound/src/port.rs
index 65b0af1f6..aef8d8487 100644
--- a/toyos-userbound/src/port.rs
+++ b/toyos-userbound/src/port.rs
@@ -131,9 +131,6 @@ impl<const N: usize> Reserved<N> {
/// Reserve `ports` for `holder`, refused where another holder has one of them.
pub fn declare(&mut self, holder: &'static str, ports: Ports) -> Result<(), Undeclared> {
- if let Some(first) = self.holder(ports) {
- return Err(Undeclared::Clash(first));
- }
let slot = self.runs.iter_mut().find(|slot| slot.is_none()).ok_or(Undeclared::Full)?;
*slot = Some((holder, ports));
Ok(())m7-x-disagreement-unrefuseddiff --git a/toyos-acpi/src/fadt.rs b/toyos-acpi/src/fadt.rs
index 013ed347b..d09cb49dc 100644
--- a/toyos-acpi/src/fadt.rs
+++ b/toyos-acpi/src/fadt.rs
@@ -284,9 +284,6 @@ pub fn fixed_hardware<P: Phys>(fadt: &Table<P>) -> Result<FixedHardware, FixedRe
if space != SPACE_SYSTEM_IO {
return Err(FixedRefused::NotSystemIo { field, space });
}
- if legacy != 0 && u64::from(legacy) != extended {
- return Err(FixedRefused::Disagrees { field, legacy, extended });
- }
Ok(extended)
};
let block = |field, address: u64, len: u8| -> Result<Block, FixedRefused> {m8-unserved-gpe-takendiff --git a/userland/acpiserver/src/sci.rs b/userland/acpiserver/src/sci.rs
index 274a541f1..ad85a944b 100644
--- a/userland/acpiserver/src/sci.rs
+++ b/userland/acpiserver/src/sci.rs
@@ -53,7 +53,7 @@ pub fn events(served: &Served, pm1: (u16, u16), gpe0: &[(u8, u8)]) -> Result<Vec
events.push(match n {
n if served.ec_gpe == Some(n) => Event::Ec,
n if served.runtime.contains(&n) => Event::Runtime(n),
- n => return Err(Unserved::Gpe(n)),
+ n => Event::Runtime(n),
});
}
} |
|
T14 runs at Each image's sha256 checked in the command that flashed it; judged from the clean head, each row alone.
The attended |
…tlasts a count interval acpi_server_death was red on the T14 because tests/acpicase gave test-runner `device` without `dup`: test-runner hands a job its capability only as a duplicate, a duplicate needs `dup`, and on PermissionDenied it spawns the job with none, so test_rs_acpi_release panicked at `take(SYSCAP_LABEL)`. The boot's configuration, not the server: acpiserver never ran on that boot. acpi_server_events was red because the row rode the shared testcases boot, which reached `reboot` at 10.466 s; acpiserver writes its counts line once a count interval (30 s) has passed, and its one EC query (0x4f, at 2.962 s) was logged as a first sighting as it should be. The server was right and the boot was too short for what the judge reads. The row now has its own boot holding the job list open to 54 s with test_rs_acpi_hold, inside the runner's 60 s bound, and its judge first asks that the hold ran out. The same hold replaces acpi_press_hold, whose 180 s sleep outlasted the runner's 60 s job bound: the runner would have killed it and rebooted at 60 s, and the "no press in 180 s" line it printed could never be written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
…is no hang The attended press on the T14 (#713 at b3b9ccd) ended its log at the supervisor's "(Shutdown)" line, and the machine stayed on until a second press. The same stop path, run for a reboot in ACPI mode on the acpi_server_events boot, reached "Rebooting." 8 ms after the supervisor's line; a power-off differs from it only in arch::power::off, which then wrote SLP_EN and halted for ever: a machine that is on, silent, and that the next boot cannot tell from one the power left. off() now gives the platform two seconds after SLP_EN and then panics, naming PM1a_CNT before and after, SCI_EN, the PM1 status and enable, and the SMI count either side of the write: the panel shows it and the black box carries it through the panic's reset. Measured on QEMU: the press's whole stop takes 20 ms there, so no QEMU test reaches the T14's stall. toyos-metal judged the press boot a hang because S5 takes the black box's DRAM with it, which a correct power-off does too: a boot whose log ends asking for a power-off now passes the stick's verdict on the next pass finding no record. The press row reads that instead of a "Shutting down." no S5 can leave, and a new acpi_power_off row asks for the same power-off from a job, with no hand on the button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Round 3 mutation and probe patches, each applied to M9: S5 the platform ignores — diff --git a/kernel/src/arch/x86_64/power.rs b/kernel/src/arch/x86_64/power.rs
index 93b4bf320..96ff97cc8 100644
--- a/kernel/src/arch/x86_64/power.rs
+++ b/kernel/src/arch/x86_64/power.rs
@@ -177,7 +177,7 @@ pub fn off() -> ! {
if held & SCI_EN != 0 {
super::acpi_mode::quiet();
}
- let typed = held & !(SLP_TYP | SLP_EN) | u16::from(SLP_TYPA.load(Ordering::Relaxed)) << 10;
+ let typed = held & !(SLP_TYP | SLP_EN) | 7 << 10;
let smis_before = super::counters::read().smi;
// SAFETY: the block `init_off` declared and the `SLP_TYPa` the DSDT's `\_S5_` names, both decoded before `SOFT_OFF` was set.
unsafe {Probe (not a mutation): the QEMU press's console after the press — diff --git a/tests/common/power.rs b/tests/common/power.rs
index 0c845bf28..e10dd406a 100644
--- a/tests/common/power.rs
+++ b/tests/common/power.rs
@@ -505,6 +505,7 @@ pub fn acpi_power_button(test_config: &Path) -> Result<(), String> {
stop.power_button();
let pressed_at = console.len();
ended(&mut qemu, &mut stop, &mut console, SHUTTING_DOWN, "guest-shutdown")?;
+ eprintln!("PROBE-CONSOLE-BEGIN\n{}\nPROBE-CONSOLE-END", &console[pressed_at..]);
let after = serial::Serial::named("the press", console[pressed_at..].to_string());
after.must_say(ACPI_PRESSED)?;
after.must_say(&format!("{} (Shutdown)", toyos_build::bootlog::STOPPING))?; |
|
T14 results at
Both judges exit 1 on the record rows alone, not on the rows under test: A boot that ends in a power-off records no panel timing; the harness expects it of every boot. The first press attempt was red: ACPI mode on the T14: Logs: |
Conflicts, every hunk of both sides kept: - kernel/src/sync.rs, kernel/src/watch.rs: this branch moved `Masked` into `sync.rs`; #716 changed its `lock` to `self.0.lock_masked(closed)`. The moved type carries #716's line, and `Lock::lock_masked` is now private to `sync`, so only `Masked` reaches it (#716's deferred visibility change: lockdep exempts a lock only if every take of it is masked). Main's `OwedLock` kept beside it. - tests/toyos.rs: `counters_on_metal`'s doc carries main's HWP request and power-envelope checks and this branch's ACPI-mode SMI flatness. - system.toml: acpiserver's entry and main's compositor comment. - issues/: the branch's five issues move into the flat tracker; every citation of a subdirectory path in the branch's files is moved with them. The track keeps the ECDT ruling and main's power-off-through-the-server stage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
The panel's census crosses to the host only on the black-box page, and S5 takes the page with it. At 7c3a7dc both power-off rows passed on the T14 (`acpi_power_off`, `acpi_power_button_pressed`) and each judge still exited 1 on `boot.<label>.panel_us`/`panel_max_us: this boot recorded none`. Whether a boot owes the census is now decided on its log's own last word: `bootlog::asked_to_power_off`, the supervisor's stop line naming `Shutdown`, the same string `boot_verdict` passes a recordless power-off on. A boot whose stop asked for a reboot still owes it and is red without it. `metal_power_off_owes_no_panel` plants both: green and `complete_ms` alone for the power-off, red and no record for the reboot. With the judge change reverted it reds (EXIT=101, `left: (true, None)`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
The exit: `acpi_power_off` and `acpi_power_button_pressed` pass on the T14, each read off a boot of the same head. Both boots ran at 7c3a7dc (the orchestrator's T14 runs, PR #713's comment 5980916904): `acpi_power_off` — the machine stayed off until the owner powered it on; the press — the server's `the power button was pressed, on SCI 7 of this boot` at 4.821 s and the machine went off. Each judge then exited 1 only on the panel census a power-off cannot leave; re-judged with the census fix at 619a7f0, each exits 0 (`acpi_power_off EXIT=0`, `acpi_power_button_pressed EXIT=0`). The no-hand power-off and the press both power off, so the stall at b3b9ccd did not recur on either; its root cause stays unread. The instrument the issue asked for stays at its site: `power::off` panics naming the registers if the machine still runs two seconds after `SLP_EN`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Round 4 negative control of the judge fix, applied to diff --git b/tests/common/metal.rs a/tests/common/metal.rs
index b2f660671..ea887072c 100644
--- b/tests/common/metal.rs
+++ a/tests/common/metal.rs
@@ -1053,10 +1053,8 @@ pub fn judge_readbacks(
}
let mut findings: Vec<String> = Vec::new();
// The census crosses only on the page, and a page the pass after the
- // reset cleared as another image's carries none; nor does a boot whose
- // log ends asking for a power-off, which takes the page with it.
- let owes_a_panel = bootlog::foreign_done(&back.loader).is_err()
- && !bootlog::asked_to_power_off(&back.log);
+ // reset cleared as another image's carries none.
+ let owes_a_panel = bootlog::foreign_done(&back.loader).is_err();
for (field, value, owed) in [
("complete_ms", back.boot_ms, true),
("panel_max_us", panel.map(|panel| panel.max_micros), owes_a_panel), |
The attended testcases-press boot at ee6aade powered off correctly and the driver refused it: nobody powered the T14 on within return_secs(), so it wrote no readback. Its log, copied out of the stick's partition saved before the next flash, shows the press reached acpiserver 17.552 s after EC query 0x28 was first taken; at b3b9ccd the gap was 10.009 s, at 7c3a7dc 17 ms. 0x28 is taken on the three attended press boots and on no unattended boot, and no table of the T14 defines _Q28. Built into a readback directory with toyos-fat32 and bootlog::split_listing, the boot's logs judged by `--metal --metal-readback <dir> acpi_power_button_pressed` exit 1 on the missing verdict.txt: back_secs, stick_secs and the loop's verdict were never measured, and are not forged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Review round 1 of #713 at Net: BLOCKER
NOTE
REMOVE
Does
|
|
T14 results at
Judge logs: |
No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
…ds corrected
acpiserver: stage 1 enables no GPE but the embedded controller's, so
`aml::{runtime_gpes, gpe, Gpe, Trigger, Disposition}`, `Served.runtime` and
`Event::Runtime` go; every other GPE is refused as `Unserved::Gpe`, and
`aml::query` returns nothing. The armed line loses its "0 GPE(s) the
namespace runs".
toyos-acpi: `pm1a_control` is the one decoder of the PM1a control block,
`X_`-aware, which both `power::init_off` and `fixed_hardware`'s caller use;
`FixedHardware` loses its copy, and `acpi_mode::init` no longer reconciles
two decodes with a filter.
ioapic: a unit's register pair is a `Window` inside its `Masked` lock, so a
read or write is reachable only through that unit's guard.
pio/quiesce: `taken_back` answers only after a stop that stopped every
userland thread but its caller (`quiesce::userland_stopped`); after one that
fell short the power-off's quiet panics by name rather than racing a holder
that may still run, and the S5 panic's PM1 reading says it is unread.
supervisor: a `NotSupported` claim names the `isa:` and `acpi:` lines too.
Issues: the track's false first sentence goes, and the "Stopgap" ruling
carries only what the owner ruled, the stage's two design lines beside it;
the firmware-interrupt issue loses its false "no ACPI enable handshake" line
and records the `counters` row's ACPI-mode reading at `ee6aadecb` and what of
its exit stays unmet (the second read is at `spin`); the press issue is
renamed from a lag to what the record shows, with both readings and the
owner's account; the EC issue records that the command port takes the
maker's commands too, firmware update not ruled out.
REMOVE: the T14's handover time in `acpi_mode.rs`, Linux's SMI reading in
the counters row's doc, "Fix 2 of the design's roast" in `sci.rs`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Round 7 mutation patches at M8 (retargeted): another GPE taken as the EC's — diff --git a/userland/acpiserver/src/sci.rs b/userland/acpiserver/src/sci.rs
index 675c796a6..8a8b22e23 100644
--- a/userland/acpiserver/src/sci.rs
+++ b/userland/acpiserver/src/sci.rs
@@ -48,7 +48,7 @@ pub fn events(served: &Served, pm1: (u16, u16), gpe0: &[(u8, u8)]) -> Result<Vec
for bit in (0..8).filter(|bit| fired & 1 << bit != 0) {
let n = (byte * 8 + bit) as u16;
events.push(match n {
- n if served.ec_gpe == Some(n) => Event::Ec,
+ _ if served.ec_gpe.is_some() => Event::Ec,
n => return Err(Unserved::Gpe(n)),
});
}M7 (retargeted to the shared diff --git a/toyos-acpi/src/fadt.rs b/toyos-acpi/src/fadt.rs
--- a/toyos-acpi/src/fadt.rs
+++ b/toyos-acpi/src/fadt.rs
@@ -276,9 +276,6 @@ fn address<P: Phys>(fadt: &Table<P>, field: Field, legacy_at: usize, x_at: usize
if space != SPACE_SYSTEM_IO {
return Err(FixedRefused::NotSystemIo { field, space });
}
- if legacy != 0 && u64::from(legacy) != extended {
- return Err(FixedRefused::Disagrees { field, legacy, extended });
- }
Ok(extended)
}
M3: diff --git a/kernel/src/isa.rs b/kernel/src/isa.rs
--- a/kernel/src/isa.rs
+++ b/kernel/src/isa.rs
@@ -124,7 +124,7 @@ pub fn claim(set: IsaId) -> Result<usize, ClaimError> {
/// left masked for the holder's first acknowledgement.
pub fn claim_row(row: usize) -> Result<usize, ClaimError> {
let function = function(row).ok_or(ClaimError::Absent)?;
- if let Some((run, holder)) = function.runs.iter().find_map(|&run| pio::holder(run).map(|h| (run, h))) {
+ if let Some((run, holder)) = function.runs.iter().find_map(|&run| pio::holder(run).map(|h| (run, h))).filter(|_| false) {
log!("isa: {}'s ports {:#x}+{} are {holder}'s", function.name, run.first(), run.count());
return Err(ClaimError::KernelDriven);
} |
|
T14 results at
Note on M3: judging its directory with the The two attended rows ( |
|
Review round 2 of #713 at Net: Round-1 BLOCKERs
Round-1 NOTEs and REMOVEs: the supervisor line, BLOCKER
NOTE
REMOVENone. SEND BACK |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
…'s record says Review round 2 at 5d0278a, BLOCKER 1: `acpi_mode::quiet` panicked whenever the stop ended with any userland thread still running, which `quiesce::PARK` says a thread in the block layer can do with no kernel bug. On q35 (handed over in ACPI mode, no holder needed) and on any image with acpiserver, a userland program could so turn SYS_SHUTDOWN into a kernel panic. The hazard is only the holder writing the row's ports after `quiet`. A thread writes a port only from Ring 3, and once the stop's stage is open every return to Ring 3 passes `leave_user_if_due`, which stops every thread but the caller. So `pio::take_back` issues a TLB shootdown (`Origin::Stop`), which returns only once every other CPU has answered from Ring 0: after it no thread but the caller can be in Ring 3. The condition no longer reads the record, and `quiesce::STOPPED`/`userland_stopped` go. `quiesce::stop` returns a `Stopping` witness beside the record, threaded through `power::shutdown` to `arch::power::off`, so the row can be taken back only after a stop. `quiesce::stop` arms its progress watch before the stage opens rather than after: nothing between the opening and the first sweep is then a point where a pass can take the caller's CPU, which is what makes the new test exact. `machine_shutdown_short_stop` (guest, q35, one CPU, actuator `stop-budget-spent`): `test_rs_stop_short` asks for the power-off with a thread spinning, the stop's one sweep finds it queued behind the caller, the record says threads were left running, and QEMU must still stop for `guest-shutdown`. Review NOTE 3: `toyos_acpi` refuses a PM1a event or control block neither field names as `FixedRefused::Absent`, not as a `Length` carrying a length that is not wrong. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Round 8 patches at Negative control — the whole fix reverted onto diff --git a/kernel/src/arch/aarch64/power.rs b/kernel/src/arch/aarch64/power.rs
index 477042084..91144dcae 100644
--- a/kernel/src/arch/aarch64/power.rs
+++ b/kernel/src/arch/aarch64/power.rs
@@ -40,7 +40,7 @@ pub fn reset() -> ! {
/// 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(_stopping: crate::quiesce::Stopping) -> ! {
+pub fn off() -> ! {
cpu::disable_interrupts();
let Some(psci) = psci::conduit() else { cpu::halt() };
irqchip::off_all_but_self();
diff --git a/kernel/src/arch/x86_64/acpi_mode.rs b/kernel/src/arch/x86_64/acpi_mode.rs
index f266b0aad..d7d9ad3a4 100644
--- a/kernel/src/arch/x86_64/acpi_mode.rs
+++ b/kernel/src/arch/x86_64/acpi_mode.rs
@@ -33,7 +33,7 @@ use toyos_acpi::{Ec, FixedHardware, PowerButton};
use toyos_userbound::Ports;
use super::cpu;
-use super::pio::{self, Declared, TakenBack};
+use super::pio::{self, Declared};
use super::power::SCI_EN;
use crate::device::ClaimError;
use crate::isa::{self, Function};
@@ -265,21 +265,25 @@ fn leave(hardware: &Hardware) {
const PM1_STATUS: u16 = 1 << 0 | 1 << 4 | 1 << 5 | 1 << 8 | 1 << 9 | 1 << 10 | 1 << 14 | 1 << 15;
/// What the PM1 event block reads, status then enable, for a power-off that
-/// did not take.
-pub fn pm1_events(taken: &TakenBack) -> String {
+/// did not take; once userland has stopped, as [`quiet`] is.
+pub fn pm1_events() -> String {
let Some(hardware) = hardware() else { return "no ACPI row, so no PM1 event block read".into() };
- let events = taken.run(run(hardware.fixed.pm1a_event));
+ let Some(events) = pio::taken_back(run(hardware.fixed.pm1a_event)) else {
+ return "the PM1 event block unread: the stop left userland running".into();
+ };
let half = hardware.fixed.pm1a_event.len / 2;
format!("PM1 status {:#06x} under enable {:#06x}", cpu::inw(events.port(0)), cpu::inw(events.port(half)))
}
/// Every fixed and general-purpose event disabled and its status cleared:
-/// the power-off's, on a machine in ACPI mode.
-pub fn quiet(taken: &TakenBack) {
+/// the power-off's, on a machine in ACPI mode, once userland has stopped.
+pub fn quiet() {
let Some(hardware) = hardware() else { return };
- let events = taken.run(run(hardware.fixed.pm1a_event));
+ let Some(events) = pio::taken_back(run(hardware.fixed.pm1a_event)) else {
+ panic!("power: the stop left userland running, so the events its ACPI holder enabled cannot be quieted for S5");
+ };
let half = hardware.fixed.pm1a_event.len / 2;
- // SAFETY: the PM1a event block the FADT names, taken back from any holder.
+ // SAFETY: the PM1a event block the FADT names, taken back from a holder that no longer runs.
unsafe {
cpu::outw(events.port(half), 0);
cpu::outw(events.port(0), PM1_STATUS);
@@ -287,7 +291,7 @@ pub fn quiet(taken: &TakenBack) {
if hardware.fixed.gpe0.len == 0 {
return;
}
- let gpe = taken.run(run(hardware.fixed.gpe0));
+ let gpe = pio::taken_back(run(hardware.fixed.gpe0)).expect("the PM1 block was taken back after the same stop");
let half = hardware.fixed.gpe0.len / 2;
for byte in 0..half {
// SAFETY: the GPE0 block, taken back as the PM1 block is; its status bits clear on a one.
diff --git a/kernel/src/arch/x86_64/pio.rs b/kernel/src/arch/x86_64/pio.rs
index f2f32fb3c..02b4fdcc1 100644
--- a/kernel/src/arch/x86_64/pio.rs
+++ b/kernel/src/arch/x86_64/pio.rs
@@ -109,23 +109,12 @@ pub fn holder(ports: Ports) -> Option<&'static str> {
FIXED.iter().find(|(_, fixed)| fixed.0.overlaps(ports)).map(|&(name, _)| name).or_else(|| RUNTIME.lock().holder(ports))
}
-/// Every row's ports, the kernel's again: the power-off's, and nobody else's.
-pub struct TakenBack(());
-
-/// Take every row's ports back from whoever holds them, whatever the stop's
-/// record says. Once `stopping` exists a thread enters Ring 3 only past
-/// `scheduler::leave_user_if_due`, which stops all but the stop's caller; the
-/// shootdown returns only once every other CPU has answered it from Ring 0,
-/// so no thread that was in Ring 3 before is there still.
-pub fn take_back(_stopping: &crate::quiesce::Stopping) -> TakenBack {
- super::tlb::shootdown(crate::invalidation::Origin::Stop);
- TakenBack(())
-}
-
-impl TakenBack {
- pub fn run(&self, ports: Ports) -> Declared {
- Declared(ports)
- }
+/// A run some row grants, taken back by the kernel once this boot's stop has
+/// stopped every userland thread but its caller, which never returns to Ring
+/// 3: the power-off's, and nobody else's. `None` after a stop that fell short,
+/// whose holder may still run.
+pub fn taken_back(ports: Ports) -> Option<Declared> {
+ crate::quiesce::userland_stopped().then_some(Declared(ports))
}
/// A [`Declared`] kept where a later reader finds it; empty until set.
diff --git a/kernel/src/arch/x86_64/power.rs b/kernel/src/arch/x86_64/power.rs
index 573164ea1..51b85b60f 100644
--- a/kernel/src/arch/x86_64/power.rs
+++ b/kernel/src/arch/x86_64/power.rs
@@ -166,13 +166,12 @@ const S5_TAKES: Tripwire = Tripwire::absurd(
/// 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(stopping: crate::quiesce::Stopping) -> ! {
+pub fn off() -> ! {
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);
let held = cpu::inw(control);
if held & SCI_EN != 0 {
- super::acpi_mode::quiet(&taken);
+ super::acpi_mode::quiet();
}
let typed = held & !(SLP_TYP | SLP_EN) | u16::from(SLP_TYPA.load(Ordering::Relaxed)) << 10;
let smis_before = super::counters::read().smi;
@@ -191,7 +190,7 @@ pub fn off(stopping: crate::quiesce::Stopping) -> ! {
the write of {:#06x} and reads {now:#06x} now, SCI_EN {}; {}; cpu{}'s SMI count {} before the write and {} now",
typed | SLP_EN,
if now & SCI_EN == 0 { "clear" } else { "set" },
- super::acpi_mode::pm1_events(&taken),
+ super::acpi_mode::pm1_events(),
super::percpu::cpu_id(),
smis_before.map_or_else(|| "unread".into(), |n| alloc::format!("{n}")),
super::counters::read().smi.map_or_else(|| "unread".into(), |n| alloc::format!("{n}")),
diff --git a/kernel/src/invalidation.rs b/kernel/src/invalidation.rs
index bf6511946..e12d820a5 100644
--- a/kernel/src/invalidation.rs
+++ b/kernel/src/invalidation.rs
@@ -5,8 +5,7 @@
/// `Shared` window or rollback unmap), `Pcid` (pool reclaim), `Mmio`, `Unmap`
/// (`Unmapped::drop`), `Pipe`, `Staged` (the ack-delay actuator), `Bench`
/// (`arch::tlb::bench`'s own, so a measured shootdown is never counted as one
-/// a path in this kernel needed), `Stop` (the power-off's, for the answer from
-/// Ring 0 every other CPU owes it, not for a flush).
+/// a path in this kernel needed).
#[derive(Clone, Copy)]
#[repr(usize)]
pub enum Origin {
@@ -19,14 +18,11 @@ pub enum Origin {
Staged,
#[cfg_attr(not(feature = "boot-actuators"), allow(dead_code))]
Bench,
- // Ports are x86-64's alone, so AArch64 builds a variant it never issues.
- #[allow(dead_code)]
- Stop,
}
impl Origin {
- pub const COUNT: usize = 8;
+ pub const COUNT: usize = 7;
/// Order matches the variants; `tests/toyos.rs`'s `irq_census_conservation` reads the line back.
pub const NAMES: [&'static str; Self::COUNT] =
- ["dlopen", "pcid", "mmio", "unmap", "pipe", "staged", "bench", "stop"];
+ ["dlopen", "pcid", "mmio", "unmap", "pipe", "staged", "bench"];
}
diff --git a/kernel/src/power.rs b/kernel/src/power.rs
index cfca578ed..0b39a9d64 100644
--- a/kernel/src/power.rs
+++ b/kernel/src/power.rs
@@ -48,12 +48,12 @@ pub fn reset_now() -> ! {
}
/// Power the machine off, or halt on one that offers no power-off.
-pub fn shutdown(stopping: crate::quiesce::Stopping) -> ! {
+pub fn shutdown() -> ! {
// 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(stopping)
+ crate::arch::power::off()
}
diff --git a/kernel/src/quiesce.rs b/kernel/src/quiesce.rs
index 4f79aa36b..fa43c5260 100644
--- a/kernel/src/quiesce.rs
+++ b/kernel/src/quiesce.rs
@@ -36,7 +36,7 @@
//!
//! Lock order: [`process::PROCESS_TABLE`] alone.
-use core::sync::atomic::{AtomicBool, AtomicU32, Ordering::AcqRel, Ordering::Relaxed};
+use core::sync::atomic::{AtomicBool, AtomicU32, Ordering::AcqRel, Ordering::Acquire, Ordering::Relaxed, Ordering::Release};
use toyos_quiesce::{must_stop, Record, Sweep, ThreadId};
use kernel::sched::task::WaitClass;
@@ -123,10 +123,6 @@ pub fn note_progress() {
}
}
-/// This boot's stop has begun: from here every thread but its caller that
-/// returns to Ring 3 is stopped at that boundary, whatever the [`Record`] says.
-pub struct Stopping(());
-
/// Stop every userland thread but the caller, and answer with what it took.
///
/// Returns when the machine is stopped or when [`PARK`] is spent, never
@@ -134,25 +130,23 @@ pub struct Stopping(());
/// it lands, because a machine nobody can turn off is worse than one whose
/// last word overlapped somebody's syscall.
#[must_use]
-pub fn stop() -> (Record, Stopping) {
+pub fn stop() -> Record {
// Refused by name rather than defaulted: a caller with no task identity is
// not a reboot syscall.
let caller = ThreadId {
pid: percpu::current_pid().expect("quiesce::stop: the caller holds no process").raw(),
tid: percpu::current_tid().expect("quiesce::stop: the caller holds no thread").raw(),
};
- // Armed before the first sweep, so a transition landing between a sweep
- // and the park after it leaves a record that park returns on at once; and
- // before the stage opens, so nothing between the opening and the first
- // sweep is a point where a pass can take this CPU.
- let parkable = crate::scheduler::Parkable::at_entry();
- let armed = watch::arm(&PROGRESS, 0, WaitClass::Other)
- .expect("quiesce::stop: the caller holds no task to park");
CALLER_PID.store(caller.pid, Relaxed);
CALLER_TID.store(caller.tid, Relaxed);
// Last: a gate that sees the stop sees the caller it must not stop.
STAGE.open(STOPPING);
+ // Armed before the first sweep, so a transition landing between a sweep
+ // and the park after it leaves a record that park returns on at once.
+ let parkable = crate::scheduler::Parkable::at_entry();
+ let armed = watch::arm(&PROGRESS, 0, WaitClass::Other)
+ .expect("quiesce::stop: the caller holds no task to park");
crate::arch::irqchip::kick_all_but_self();
let cpus = crate::smp::cpu_count();
@@ -175,21 +169,28 @@ pub fn stop() -> (Record, Stopping) {
// moment the stop ended, and every line between here and the record's
// own would open more.
let (in_flight, begun) = crate::block::userland_operations();
- return (
- Record {
- sweep: swept,
- elapsed_ms: elapsed / 1_000_000,
- budget_ms: budget.nanos() / 1_000_000,
- sweeps,
- cpus,
- in_flight,
- begun,
- },
- Stopping(()),
- );
+ let record = Record {
+ sweep: swept,
+ elapsed_ms: elapsed / 1_000_000,
+ budget_ms: budget.nanos() / 1_000_000,
+ sweeps,
+ cpus,
+ in_flight,
+ begun,
+ };
+ STOPPED.store(record.stopped_the_machine(), Release);
+ return record;
}
}
+/// Whether this boot's stop ended with every userland thread but its caller
+/// stopped.
+pub fn userland_stopped() -> bool {
+ STOPPED.load(Acquire)
+}
+
+static STOPPED: AtomicBool = AtomicBool::new(false);
+
/// Mark every parked thread but the caller and count the rest.
///
/// The table lock is held for the walk and given up before the park: a thread
diff --git a/kernel/src/syscall/machine.rs b/kernel/src/syscall/machine.rs
index c9ee138b0..ccb143d81 100644
--- a/kernel/src/syscall/machine.rs
+++ b/kernel/src/syscall/machine.rs
@@ -66,7 +66,7 @@ fn read_on_cursor<C: crate::user_ptr::UserSafe>(
}
}
-fn quiesce(last: &str) -> Result<crate::quiesce::Stopping, SyscallError> {
+fn quiesce(last: &str) -> Result<(), SyscallError> {
// Refused by name, and first: nothing below runs twice.
if !crate::quiesce::claim_the_shutdown() {
log!("power: this machine is already stopping, so this caller stops with the rest");
@@ -90,7 +90,7 @@ fn quiesce(last: &str) -> Result<crate::quiesce::Stopping, SyscallError> {
crate::arch::watchdog::disarm();
// Every userland thread stops here, the log's writer with the rest:
// `/system/bin/supervisor` had it flush before it asked for this stop.
- let (stopped, stopping) = crate::quiesce::stop();
+ let stopped = crate::quiesce::stop();
crate::log::console::drain_for_the_stop();
// The final census: no process runs after this to report another.
crate::irq_census::log_census();
@@ -130,7 +130,7 @@ fn quiesce(last: &str) -> Result<crate::quiesce::Stopping, SyscallError> {
// `power::reboot`/`power::shutdown` do — which every reset this kernel
// performs goes through. It is bounded, and the reset follows either way.
crate::drivers::xhci::seal_shut();
- Ok(stopping)
+ Ok(())
}
/// Powers the machine off; requires a `SysCap` carrying [`Rights::POWER`]. Returns only when refused.
@@ -138,10 +138,10 @@ pub(super) fn sys_shutdown(syscap: RawHandle) -> u64 {
if let Err(e) = demand_syscap(syscap, Rights::POWER) {
return e.refuse();
}
- match quiesce("Shutting down.") {
- Ok(stopping) => power::shutdown(stopping),
- Err(e) => e.to_u64(),
+ if let Err(e) = quiesce("Shutting down.") {
+ return e.to_u64();
}
+ power::shutdown();
}
/// Returns the machine to firmware; requires a `SysCap` carrying [`Rights::POWER`]. Returns only when refused.M10 — --- a/kernel/src/arch/x86_64/pio.rs
+++ b/kernel/src/arch/x86_64/pio.rs
@@ -119,5 +119,4 @@
/// so no thread that was in Ring 3 before is there still.
pub fn take_back(_stopping: &crate::quiesce::Stopping) -> TakenBack {
- super::tlb::shootdown(crate::invalidation::Origin::Stop);
TakenBack(())
} |
|
Round 13 mutation at diff --git a/src/bootlog.rs b/src/bootlog.rs
index 5fd2d9e28..7d0fea692 100644
--- a/src/bootlog.rs
+++ b/src/bootlog.rs
@@ -551,8 +551,8 @@ mod tests {
/// Only the supervisor's own stop line, naming a shutdown, is a power-off.
#[test]
fn a_power_off_is_the_supervisors_stop_naming_a_shutdown() {
- let booted = "[ 1.151 cpu0 kernel] Boot: complete (1151ms)\n";
- let stop = |how: &str| format!("{booted}[16.705 supervisor] {STOPPING} ({how})\n");
+ let booted = "[kernel 1.151 cpu0] Boot: complete (1151ms)\n";
+ let stop = |how: &str| format!("{booted}{{16.705 supervisor}} {STOPPING} ({how})\n");
assert!(asked_to_power_off(&stop("Shutdown")));
assert!(!asked_to_power_off(&stop("Reboot")));
assert!(!asked_to_power_off(booted)); |
|
T14 results at The two attended rows follow with the owner. Judge logs: |
|
Attended T14 results at
The press, in #737's line shape (stamps from the counter's zero): One press; 0x28 to the power-button event 16 ms, as at |
|
Review round 7 of #713 at Merge with
|
|
Review round 7 of #713 at
Net Earlier findingsRound 6 had no BLOCKER. Its one NOTE was about the body's prose, and it is superseded below. Merge with
|
… count The attended press at 8d7004d passed green on a boot where the owner says his first press did nothing and only a later press stopped the machine. A press that never latched PWRBTN_STS leaves the log as clean as no press at all, so no judge that reads only the log can tell that boot from a clean one. The row now takes the presses from outside the guest: the attended run writes the owner's count into the readback's presses.txt once the machine is off, and the judge reds unless that count is one and the server served it. presses.txt is one of READBACK_FILES, so the loop clears it before every boot and a count is always of the boot beside it. acpi_power_button's QEMU check of the supervisor's stop reads through bootlog::asked_to_power_off, the one exact reader, rather than a substring. The press issue records the 8d7004d boot and the owner's account, drops the reading that a lost first press raised 0x28, which that boot refutes, records the owner's ruling of 2026-10-05, moves to the AML interpreter's stages and takes an exit ten consecutive green attended boots meet. Stage 1's exit no longer claims that every press stops the machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WcU2Dsw6mDYtwYfzVHPzM8
|
Evidence for round 8 at Negative control for the press row. It uses
Suites
The worktree was clean after both judgements. |
|
Review round 8 of #713 at Net size:
Round 7
What the two attended rows read, and which unattended tests read it at head
BLOCKER
NOTE
SEND BACK Generated by Claude Code |
|
Review round 8 of #713 at Net size:
Round 7
What the two attended rows read, and which unattended tests read it at head
BLOCKER
NOTE
REMOVE
SEND BACK Generated by Claude Code |
|
Review round 8 of #713 at Net size:
Round 7
What the two attended rows read, and which unattended tests read it at head
BLOCKER
NOTE
REMOVE
SEND BACK Generated by Claude Code |
Review round 8 of #713, on the owner's rulings "A test that requires manual steps from me is forbidden." and "No automated test is allowed that requires physical buttons to be pressed or anything we cant do now with the t14. I can test it on demand but no ci there not always someone available physically". Deleted: the metal rows acpi_power_off and acpi_power_button_pressed, their judges (acpi_off_on_metal, acpi_press_on_metal, powered_off_in_acpi_mode), Readback::presses and READBACK_PRESSES, and the harness's acceptance of a boot that ends in S5: boot_verdict's asked_to_power_off arm with its test, judge_readbacks' panel exemption with a_power_off_owes_no_panel. Only a boot a hand powered on again ever reached that acceptance. asked_to_power_off stays for QEMU's acpi_power_button. Issues: the S5 issue is renamed to the gap the deletion leaves, that no T14 row reads the power-off after the kernel's own ACPI_ENABLE, and records what QEMU reads of it. Measured on QEMU 11.1.1's q35 under OVMF and TCG, bare, through the monitor: PM1a_CNT (0x604) read 0x0001 as OVMF left it, 0x0000 after `o/b 0xb2 3`, 0x0001 after `o/b 0xb2 2`, so a harness can put a guest in legacy mode with nothing shipped for it; no test does yet. The press issue's exit is an on-demand check by the owner, and it names the window between the enable and the server's clear in which a press is dropped. Stage 1's exit names the rows that read it, and the track names the interpreter stage that owns the press issue. acpi_mode::release wrote ACPI_DISABLE and logged one read of PM1a_CNT, after it had already released the row. It now writes the disable with the row still held and reads SCI_EN until it is clear, spun and bounded at 100 ms, since the task a claim's last handle goes with may be dying and cannot park. The row going first let a claimant mint between the release and the disable, find SCI_EN still set, write nothing, and lose ACPI mode to the disable that followed. A firmware that does not clear the bit is said by name, ENABLED stays set, and the next release writes the disable again. On the T14 at 8d7004d the one read was already clear. FixedHardware carries `legacy: Option<LegacyMode>` in place of smi_cmd, acpi_enable and acpi_disable: the two commands are NonZeroU8, and a FADT that leaves any of the three zero names no way in that this kernel takes. Table 5.9 reserves each as zero on a machine without legacy mode, so a machine with ACPI_ENABLE and no ACPI_DISABLE stays in legacy mode, refused by name, where before the release would have written 0 to SMI_CMD. Not run at this commit: the guest suite and the metal staging. The primary checkout's rust/build is gone, so no worktree has a compiler to build a guest image with. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ves it no bound The orchestrator's ruling: the QEMU reach of the enable-then-S5 path, the monitor clearing SCI_EN before the claim is minted, is the exit of issues/no-t14-row-reads-the-power-off-after-the-kernels-own-acpi-enable.md and is not built in stage 1. The issue's exit says so, as his placement. The owner's second ruling on tests, "No automated test is allowed that requires physical buttons to be pressed ...", is dated 2026-10-05 in the three issues that quote it. HANDBACK's comment called the 100 ms an estimate. ACPI 6.5 was read, from the Internet Archive's copy of uefi.org's HTML (uefi.org answers curl with a 403 challenge page): section 4.8.2.5 has OSPM write ACPI_DISABLE and poll SCI_EN "until it is sampled as RESET" and names no bound; Table 5.9's ACPI_ENABLE row says only that OSPM waits synchronously and the system releases ownership "as quickly as possible"; no FADT field carries a time. The comment now says that none is given and that the number is this kernel's. Comment only: three lines for three. Found in the same reading and filed, not fixed: issues/the-kernel-writes-smi-cmd-from-whichever-cpu-its-caller-runs-on.md. Table 5.9 has OSPM issue SMI_CMD commands from the boot processor; the kernel writes on the claimant's CPU, and on the T14's acpi_server_death boots at ff4945d and 8d7004d the enable was written from cpu7 and took. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
T14 at
The |
|
Review round 9 of #713 at Net size, Round 8
What the round added, read
BLOCKER
NOTE
SEND BACK |
…is an issue tests/metal gains the six numbers the acpi_server_ judgement measured on the T14 at 922a6b7 (boot.acpicase.* and boot.testcases-hold.*), which no commit of the branch carried. Review round 9's notes, in issues/ alone: - The gap issue is owned by the track's "power-off through the server" stage, which rewrites the power-off its exit's test reds on, and no longer by the T14 session issue, whose exit is read on the T14. The orchestrator's placement. - The press issue's exit names where its ten logs are read: each boot ends in S5 and leaves no readback, so each is read off the stick's log partition. "and none will" went: the ruling is about what the T14 can do now. - New: past HANDBACK the row is handed back with ACPI_DISABLE unanswered, so a firmware slower than 100 ms takes ACPI mode from the next holder. Recorded with its owner, what is known and an exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The The script, #!/bin/sh
# What q35's PM1a_CNT (0x604) reads under OVMF, and after the monitor writes
# ACPI_DISABLE (3) and ACPI_ENABLE (2) to SMI_CMD (0xb2). No guest image.
cd "$(dirname "$0")" || exit 99
rm -f mon.sock vars.fd transcript.txt qemu.out
cp /opt/homebrew/share/qemu/edk2-i386-vars.fd vars.fd && chmod 644 vars.fd || exit 98
hmp() {
printf '>>> %s\n' "$1" >> transcript.txt
{ printf '%s\n' "$1"; sleep 1; } | nc -U mon.sock >> transcript.txt 2>&1
printf '\n<<< nc exit %s\n' "$?" >> transcript.txt
}
set -x
qemu-system-x86_64 -machine q35 -accel tcg -nodefaults -display none -m 256 \
-drive if=pflash,format=raw,unit=0,file=/opt/homebrew/share/qemu/edk2-x86_64-code.fd,readonly=on \
-drive if=pflash,format=raw,unit=1,file=vars.fd,readonly=off \
-monitor unix:mon.sock,server=on,wait=off > qemu.out 2>&1 &
set +x
pid=$!
n=0
until [ -S mon.sock ] || [ $((n += 1)) -gt 20 ]; do sleep 1; done
[ -S mon.sock ] || { echo "no monitor socket"; kill "$pid"; exit 97; }
# Wait on the event, bounded: OVMF sets SCI_EN on its way to the boot manager.
n=0
until { hmp 'i/h 0x604'; grep -q 'portw\[0x0604\] = 0x0001' transcript.txt; } || [ $((n += 1)) -gt 60 ]; do :; done
echo "polls before SCI_EN read set: $n" >> transcript.txt
hmp 'info version'
hmp 'i/h 0x604'
hmp 'o/b 0xb2 3'
hmp 'i/h 0x604'
hmp 'o/b 0xb2 2'
hmp 'i/h 0x604'
hmp 'quit'
wait "$pid"
rc=$?
echo "qemu EXIT=$rc" >> transcript.txt
rm -f vars.fd mon.sock
exit "$rc"
Firmware, sha256: The monitor transcript, What it reads: |
|
Review round 10 of #713 at Net size, Does the reading at
|
userland/Cargo.toml conflicted: #713 added the member `acpiserver` on the line this branch added `acpiserver/aml`. Both hold, in order. The track merged without a conflict and came out with two stages named "the interpreter": #713's, which the ACPI server runs and which owns the T14's press issue, and this branch's, the crate and its host exit. The press issue names its owner as the stage "the interpreter", so they are folded into one stage carrying both exits. This branch's "inside the server that uses it" is dropped with the fold: /system/bin/acpiserver now exists and does not link the crate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
None of the three moved the fork pin or touched a file under sdk/std or one of the 21 paths the backend left. #740 filed issues/std-says-a-launch-moves-its-handles-to-the-launcher-even-when-the-move-is-refused.md against the backend's old paths in the fork; its two citations now name sdk/std/sys/process.rs and sdk/std/os/process.rs, and its owner and exit a commit here rather than a fork commit with a gitlink bump. The comment it quotes is at sdk/std/sys/process.rs:576, unchanged by the move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
ACPI stage 1: the machine in ACPI mode for a userland server's claim; its SCI, power button and EC events served
Stage 1 of
issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md: the kernel puts the machine in ACPI mode for a userland server'sacpiclaim and back when it goes;/system/bin/acpiserverserves the SCI, the fixed power button and the embedded controller's events. Design: PR 2 of the counters + ACPI design, with every roast item touching it.Round 14 (review round 9 at
922a6b7c7): the T14's reading of this kernel, and the recordThe head is
9dcfa09bc:922a6b7c7, a merge oforigin/main(df1a77221, #743) and one commit. No kernel, userland or harness source changed, so the guest and T14 readings at922a6b7c7stand for it.git diff --stat 922a6b7c7 9dcfa09bc:README.mdand the two issuesmunmap-…andusersafe-…are the merge's and equalorigin/main's (git diff --stat df1a77221 9dcfa09bcover the three is empty). The branch's own commit is thetests/metalrecord and three files underissues/.cargo run -- --ci hostat9dcfa09bc: EXIT=0 (orch/acpi1-r17/host.exit), "Host: 76 step(s), all green" (host.log:8403);host.headis9dcfa09bcdef761969993ae31ea60b0fe6b03276, and the tree was clean after it (host.status-afteris empty). Load average 36.30 at its start (host.load).BLOCKER 1: the T14 at
922a6b7c7The orchestrator ran the five boots (comment 6039409933); nobody was at the machine. His run log is
orch/t14-713r16.log: each image's sha256 is the oneorch/acpi1-r16/images.sha256staged and was checked before its flash, each boot's rc is 0, and the script's rc is 0. The judges ran from the clean tree at922a6b7c7. Everything below is read from the readbacks'kernel.logs underorch/acpi1-r16/.verdict.txtmetal-acpi_server/acpicase2d8c1f2f0be2cf14130ede611293369d78d301b1c0fe91597ac36d14d1eeff8emetal-acpi_server/testcases-holdae15eb50c4a1cb89b40ee8bd4fc65448a04e94dcece5f17a6fdb9d4dcb2b86a0metal-counters/sharedc46cc487f11f2d8d2b0311923e630a4b0668697f67dca8c16f645f0953bf4195metal-counters/shared-debug2ef538f3f1442c48e69606bacb469dc95a5eeee1291cb1056bcdaeb9cad9c6d6metal-counters/testcasesabb72e225960dab08d7c0b6d00e63aa63ab61ee6a4750c32bdf1e5d3bb48939cThe two judge commands:
cargo test --test toyos-build -- --metal --metal-readback orch/acpi1-r16/metal-acpi_server acpi_server_: EXIT=0,[metal] 2 passed, 0 failed, 2 boot(s), withPASS acpi_server_eventsandPASS acpi_server_death(metal-acpi_server/judge-acpi_server_.log).cargo test --test toyos-build -- --metal --metal-readback orch/acpi1-r16/metal-counters counters: EXIT=0,[metal] 3 passed, 0 failed, 3 boot(s), withPASS counters(metal-counters/judge-counters.log).acpi_server_death, onacpicase(metal-acpi_server/acpicase/kernel.log, 385 lines). In this order::346[… 14.613 cpu7 kernel] acpi: ACPI mode: ACPI_ENABLE 0xf0 written to SMI_CMD 0xb2, SCI_EN set 16752ns after; cpu7's SMI count 4807 before the write and 4808 after:351[… 14.616 test-runner pid=8] acpi_release: the server said: acpiserver: armed: power button served, embedded controller on GPE 0x6e at 0x66/0x62:364exit: acpiserver pid=9 code=137:365[… 14.616 cpu0 kernel] acpi: legacy mode again: ACPI_DISABLE 0xf1 written to SMI_CMD, PM1a_CNT reads 0x0000 17068ns after, SCI_EN clearThe duration is 17068 ns: the line is this head's, which no earlier kernel wrote with a time in it.
legacy mode againoccurs once in the log andacpi: ACPI mode:once;still in ACPI modeandACPI_DISABLE not writtenoccur nowhere in it. The job list's stop is:385, the supervisor'spower: the machine stops, and logkeeper makes the log whole first (Reboot)at 14.625 s. The enable was written from cpu7 and the disable from cpu0, asissues/the-kernel-writes-smi-cmd-from-whichever-cpu-its-caller-runs-on.mdrecords of the earlier heads.acpi_server_events, ontestcases-hold(metal-acpi_server/testcases-hold/kernel.log, 387 lines)::348[… 13.452 cpu0 kernel] acpi: ACPI mode: ACPI_ENABLE 0xf0 written to SMI_CMD 0xb2, SCI_EN set 15657ns after; cpu0's SMI count 4818 before the write and 4819 after:354[… 13.454 acpiserver] acpiserver: armed: power button served, embedded controller on GPE 0x6e at 0x66/0x62:367[… 15.195 acpiserver] acpiserver: embedded controller query 0x4f taken for the first time, served by nothing: stage 1 runs no AML, the one first-sighting line, once:368[… 43.454 acpiserver] acpiserver: 26 SCIs; embedded controller queries taken: 0x4f x13, the counts line:369acpi_hold: held to 54000 ms, and nothing stopped the machine, and:387the supervisor's stop line at 66.203 slegacy mode again,still in ACPI modeandACPI_DISABLE not writtenoccur nowhere in the log.counters, onshared,shared-debugandtestcases(metal-counters/<boot>/kernel.log). The mint's line on each, its CPU's SMI count rising by one across the write:shared:347[… 12.690 cpu0 kernel] acpi: ACPI mode: ACPI_ENABLE 0xf0 written to SMI_CMD 0xb2, SCI_EN set 12764ns after; cpu0's SMI count 4818 before the write and 4819 aftershared-debug:347[… 12.696 cpu0 kernel] … SCI_EN set 14303ns after; cpu0's SMI count 4819 before the write and 4820 aftertestcases:347[… 12.833 cpu0 kernel] … SCI_EN set 13349ns after; cpu0's SMI count 4818 before the write and 4819 afterlegacy mode again,still in ACPI modeandACPI_DISABLE not writtenoccur in none of the three logs. The row's own job,counters_metal, runs ontestcases;sharedandshared-debugcarry the two shared members (counters_read,counters_silent), each exit 0. Ontestcases:kernel.cpu.<n>.smi = 4819for n = 0 to 7 atidle0(15.266218180 s), atidle1(16.266280242 s) and atspin(27.448835558 s): 24 readings, all 4819, which is the writer's count after the enable. The judge:8 cpus, SMI flat on each over 12182 ms.:367is stamped 15.264 s (counters_metal settle: the log holds this second line) and:36827.244 s; nothing lies between, so nothing in 15.266 s to 16.266 s. The first query, 0x4f, came at 14.968 s (:365), beforeidle0.BLOCKER 2: the
SCI_ENmeasurement has its command, transcript and exitRun again this round and posted whole as comment 6039735451: the script with the QEMU invocation, the monitor transcript and the exit.
sh orch/acpi1-r17/sci-en/measure.shEXIT=0, QEMU's own exit after the monitor'squit. Bare QEMU 11.1.1,-machine q35 -accel tcg -nodefaultsunder OVMF, no guest image.i/h 0x604read0x0000on the first poll, before OVMF had enabled ACPI, then0x0001;0x0000aftero/b 0xb2 3;0x0001aftero/b 0xb2 2. The same three readings the gap issue records. No change to the tree.NOTEs
issues/toyos-runs-the-machine-in-acpi-mode-and-interprets-its-aml.md, which rewrites the power-off the exit's test reds on. The orchestrator's placement, not the owner's, and the issue says so.ee6aadecb. "and none will" is deleted.HANDBACKthe row is released with a disable outstanding: recorded asissues/the-acpi-row-is-released-with-an-acpi-disable-the-firmware-has-not-answered.md, a file of its own because the CPU issue's slug is another claim. Owner: the ACPI track, whose stage 1 landed the release. Known: the bound is the kernel's own number, the T14 answered in 17068 ns, no tier reaches the expiry, no failure is recorded. Exit: no claimant is handed the row while a disable this kernel wrote is unanswered, and a test reds where one is. No kernel change this round.tests/metal/lenovo-20w0003amz.tomlcarries the six numbers from the run at922a6b7c7(boot.acpicase.*1207 ms, 2495 us, 12966 us;boot.testcases-hold.*1212 ms, 2477 us, 13240 us), applied from the judge's own diff,orch/acpi1-r16/metal-acpi_server/judge-acpi_server_.record-rows.patch.kernel/src/object/mod.rssays of it, and the lines that called the T14 rows staged and unread say what ran.Round 13 (review round 8 at
47ac0ce19): the attended rows go, and the release readsSCI_ENuntil it is clearThis round's head was
922a6b7c7:04bcf041d, which carries the round's code, and one commit on it that changes issues and one three-line comment inacpi_mode.rs(below). Host, build and guest gates are green at922a6b7c7(Gates). The T14 has since run the three rows the round changes at922a6b7c7, all green: the release's poll and its rewritten line are read on the hardware they are for (round 14, above, and T14 rows).On the owner's rulings, both of 2026-10-05: "A test that requires manual steps from me is forbidden." and "No automated test is allowed that requires physical buttons to be pressed or anything we cant do now with the t14. I can test it on demand but no ci there not always someone available physically".
acpi_power_offandacpi_power_button_pressed, with thetestcases-offandtestcases-pressarms;acpi_off_on_metal,acpi_press_on_metalandpowered_off_in_acpi_mode;Readback::presses,READBACK_PRESSESand itsREADBACK_FILESentry;boot_verdict'sasked_to_power_offarm and its testa_power_off_leaves_no_record_and_is_no_hang,judge_readbacks' panel exemption, anda_power_off_owes_no_panelwith its registration;tests/toyos.rs,acpi_hold.rs) andbootlog.rs's paragraph on what a power-off leaves the next loader pass.bootlog::asked_to_power_offand its test stay: QEMU'sacpi_power_buttonreads them.acpi_power_button, andsci.rs's host tests of the decode.counters(reds onacpi: legacy mode again) andacpi_server_events(the row and the server'sarmed:line).SLP_ENtakes after the kernel's ownACPI_ENABLE: unguarded on the T14 untilissues/no-t14-row-reads-the-power-off-after-the-kernels-own-acpi-enable.md's exit is met. On q35 the power-off is read (machine_shutdown,machine_shutdown_short_stop,acpi_power_button), but never after the kernel's enable, since OVMF hands over in ACPI mode.PWRBTN_STSand reaches the server: unguarded untilissues/the-t14s-power-button-event-came-up-to-17-s-after-ec-query-0x28.md's exit is met, which is an on-demand check by the owner and no row.-machine q35 -nodefaultswith OVMF under TCG, through the monitor:i/h 0x604read0x0001as OVMF left it,0x0000aftero/b 0xb2 3(ACPI_DISABLE), and0x0001again aftero/b 0xb2 2. The whole QEMU invocation, the monitor transcript and the exit, from a re-run in round 14, are comment 6039735451. So the harness can put a guest in legacy mode from outside, with nothing shipped for it. A test needs the write to land before the claim is minted, which means a config whose claim a prompted job mints (tests/acpicasemints it from a job, but runs its job list unprompted) and a job that holds the claim into the power-off. It is not built in stage 1. The orchestrator's ruling, not the owner's: that QEMU test is the gap issue's exit. The issue records the measurement and says the exit is his placement. It would read QEMU's ICH9 model and not the T14's firmware, which no row reads on this path. This is the reviewer's either/or answered with neither arm: a test is possible and is not here.issues/no-t14-row-reads-the-power-off-after-the-kernels-own-acpi-enable.mdand rewritten as the gap, with its last readings (ff4945d6d,8d7004d3b), what QEMU reads of it, the same owner (moved to the track's power-off stage in round 14), and an exit no person is needed for: the QEMU test above. Its one citation moved.8d7004d3bpress boot). The false "while the firmware still had the machine in legacy mode" goes.acpi_power_button; the T14'scounters,acpi_server_events,acpi_server_death), says the two things no T14 row reads and whose they are, and says that moving the power-off's T14 reader out of the exit is the orchestrator's placement. Both rulings are recorded beside "The attended press waits for the owner", which they supersede. A stage "the interpreter" is named as the press issue's owner; its exit is this round's proposal, labelled as the orchestrator's placement, and is his to confirm or change.issues/a-stop-shows-nothing-on-the-panel.mdloses its paragraph on a boot held open for an attended press.issues/the-kernel-writes-smi-cmd-from-whichever-cpu-its-caller-runs-on.md. ACPI 6.5 Table 5.9 has OSPM issueSMI_CMDcommands "synchronously from the boot processor". The kernel writes the enable on the CPU the claimant mints on and the disable on the CPU the last handle goes on. On the T14'sacpi_server_deathboots atff4945d6dand8d7004d3bthe enable was written from cpu7 and the disable from cpu0, and both took; every other recorded boot wrote the enable from cpu0. No failure is recorded. This has been so since the mint was written, not since this round.SCI_ENuntil it is clear (the outside review's finding 1; production code,acpi_mode.rsandtoyos-acpi/src/fadt.rs, +81/−39).releasereleased the row, then wroteACPI_DISABLEand logged one read ofPM1a_CNT,SCI_EN clearorstill set.leavewrites the disable and readsPM1a_CNTuntilSCI_ENis clear, spun and bounded at 100 ms (HANDBACK). It spins because the task a claim's last handle goes with may be dying, and a dying task cannot park. Past the bound it logsacpi: still in ACPI mode: …by name andENABLEDstays set, so the next release writes the disable again and the next mint does not say the firmware handed the machine over in ACPI mode.ACPI_ENABLErow says only that "OSPM will synchronously wait for the ntransfer [sic] of SMI ownership to complete, so the ACPI system releases SMI ownership as quickly as possible", and no FADT field carries a time. So the 100 ms is this kernel's own number, not a measurement and not the document's, andHANDBACK's comment says that in place of "an estimate". On the T14 at8d7004d3bthe one read after the write was already clear.SCI_ENstill set, write nothing, and then lose ACPI mode to the disable that followed. The poll would have widened that window.ACPI_DISABLE == 0.FixedHardwarenow carrieslegacy: Option<LegacyMode>, whose two commands areNonZeroU8, in place ofsmi_cmd,acpi_enableandacpi_disable. A FADT that leaves any of the three zero names no way in that the kernel takes, so such a machine stays in legacy mode, refused by name. Before, a machine withACPI_ENABLEand noACPI_DISABLEwould have had 0 written toSMI_CMDon release. Host-tested intoyos-acpi/tests/corpus.rs(each of the three zeroed givesNone).acpi_death_on_metalloses itsends_with("SCI_EN clear")branch: the kernel now writeslegacy mode againonly of a read with the bit clear. The line keeps its head and its tail, with the time the bit took before the tail, so the judge reads the8d7004d3breadback and a new one alike.still in ACPI modeline and the mint after it need a firmware that ignoresACPI_DISABLE; nothing here has one. The reordering closes a race no test schedules. Both are checked by reading.orch/chatgpt/pr713.md), claim by claim.SCI_EN: held, and fixed as above. Its "ACPI 6.5 specifies … pollSCI_ENuntil it is RESET" and "the FADT defines zero as reserved on systems without Legacy Mode" both hold against the document, read from the Internet Archive's copy ofuefi.org/specs/ACPI/6.5/(chapter 4 captured 2025-01-02, sha256 of its HTML1ff59e14…d0f6; chapter 5 captured 2024-12-28,6a238690…6339), sinceuefi.orgitself answerscurlwith a 403 challenge page. The first is §4.8.2.5, quoted above. The second is Table 5.9:ACPI_ENABLEandACPI_DISABLEare each "reserved and must be zero on systems that do not support Legacy Mode", andSMI_CMD"must be zero on system that does not support System Management mode". Table 5.9'sACPI_DISABLErow also describes a different handback, in which the OS masks the SCI's interrupts and clearsSCI_ENitself before the write; §4.8.2.5 and Table 4.13 ("It is the responsibility of the hardware to set or reset this bit. OSPM always preserves this bit position") contradict it, and the kernel follows those two. The new issue records the contradiction. Its "leave()can complete while the machine is observably still in ACPI mode" was true only as a loggedstill set; on the T14 the one read was clear (acpi1-r13/metal-acpi_server/acpicase/kernel.log:364).userland/acpiserver/src/main.rs, "one before it is lost"), and recorded in the press issue with the window's measured width. Its proposal, a takeover and event initialisation that are atomic to the user, is not acted on and not ruled.What changed, per decision
One declaration of kernel-driven ports, the only maker of the port token (roast BLOCKER 2).
arch::pioholdsFIXED(COM1, the 8259 pair, POST, CMOS RTC, the PCI configuration mechanism —CONFIG_ADDRESS's first port alone, so 0xCF9 stays free for a reset register; const-asserted disjoint) anddeclare()for what boot finds (the i8042, the reset register, the PM1a control block,SMI_CMD, the TCO block).cpu::{inb,outb,inw,outw}takepio::Port, which only a declaration mints; every port the kernel touches went through it.isa::claim_rowrefuses a row any of whose ports a holder declared, naming it.pio::take_backmints the row's blocks forpower::offalone (round 8, below). The pureReservedtable is intoyos-userboundand host-tested; the I/O bitmap covers all 0x10000 ports (8 KiB + 1 per CPU's TSS).i8042::drives()goes: a probed controller is declared, and that is the refusal.isarows are runtime, filled once at boot: row 0 the i8042 (isa:0060,0064:1,12), row 1 the ACPI fixed hardware (DeviceType::Acpi = 10 => "acpi"), each its own vector (0x2C, 0x2D). A level line is masked at the claim, masked by its handler before EOI, and unmasked by the holder's acknowledgement — a 4-byte write oftoyos_abi::acpi::ACKto the claim, from the bound process; a row with no level line refuses itInvalidArgument. Arch-neutral:isa.rssees onlypio::Line/pio::level. An SCI on another row's GSI is refused.ACPI mode (
kernel/src/arch/x86_64/acpi_mode.rs). The row is the FADT's PM1a event and GPE0 blocks (toyos_acpi::fixed_hardware, lengths from*_LEN,X_refused only where both forms name an address and differ; hardware-reduced, PM1b, GPE1 refused by name) and the ECDT's two ports (toyos_acpi::ecdt). The mint:SCI_ENalready set → nothing written; else refused by name where the FADT leavesSMI_CMD,ACPI_ENABLEorACPI_DISABLEzero, no usable ECDT, or a control-method power button; elseACPI_ENABLEtoSMI_CMDwith this CPU's SMI count before and after read through the counters (arch::counters::read, PR 1), thenSCI_ENpolled every 1 ms, parked in between, bounded at 3 s (roast NOTE). A mint that wrote the enable and then failed writesACPI_DISABLE. The release writesACPI_DISABLEwhere the mint wrote the enable and readsSCI_ENuntil it is clear (owner, "Back to legacy mode"; round 13, above). NoSMI_CMDwrite is made once the stop has begun (round 9, below).No-ECDT machines (owner, 2026-10-04, "Stopgap, delete later"): the ECDT path is a stopgap deleted when the interpreter reads the EC from the DSDT; a machine without one stays in legacy mode until then. Recorded in the track and at the site.
I/O APIC (roast): topology written once; each unit's register pair a
Maskedlock (moved fromwatch.rstosync.rs) a handler may take — carrying A syscall's body runs with interrupts open, and a kernel-mode timer expiry re-arms one quantum #716'sself.0.lock_masked(closed), withLock::lock_maskednow private tosync, so onlyMaskedreaches it and lockdep's exemption covers only locks every take of which is masked (A syscall's body runs with interrupts open, and a kernel-mode timer expiry re-arms one quantum #716's deferred visibility change, owed at this merge);routeholds nothing acrossremap_pinorlog!; a pin keeps its RTE low word, so mask/unmask is one write. Its "never from an ISR" and "no power-button or lid events" lines andset_masked's read-modify-write go.SCI default (roast fix 1 + NOTE):
toyos_acpi::sci_linegives level, active low where no override names the SCI or one conforms (ACPI 6.5 Table 5.9);isa_linekeeps the ISA bus's edge/high. The INTI decode moved out ofioapic.rsinto the crate.SVR declared whole (roast NOTE):
apic::SVR= enable | spurious vector, every other bit (EOI-broadcast suppression) clear, written and asserted on every CPU.power::off(roast NOTE, ACPI 6.5 §16.1.6's order): the row is taken back from any holder (pio::take_back, round 8); on a machine in ACPI mode every PM1 and GPE0 event is disabled and its status cleared first; thenSLP_TYP, thenSLP_TYP|SLP_EN, over the bitsPM1a_CNTreads (SCI_EN no longer cleared). A write the platform does not act on is a panic two seconds afterSLP_EN(S5_TAKES, aTripwire), naming PM1a_CNT before and after,SCI_EN, the PM1 status and enable (acpi_mode::pm1_events) and this CPU's SMI count either side of the write: the panel shows it and the black box carries it through the panic's reset. Before, it halted for ever after the write.A power-off was no hang to
toyos-metal, and owed no panel census (rounds 3 and 4): both deleted in round 13, with the rows that needed them.Merge of
origin/mainatc4ab2b1e1(The kernel declares each CPU's HWP request, and the counters round reads the power envelope back under TRACE #590, kernel: delete the log-inside-the-pass caveat, and file the 4ff5221ee wedge it came from #708, A launch starts only what the caller's row lists, and swap and update only in a login session #709, IOMMU stage 1: a claimed function speaks only through its own remapping entry, has no untranslated space, and no domain reaches a host bridge's window #710, The network stack cites RFCs and its own code, not documents outside the tree #712, The owner's rulings of 2026-10-04 are recorded in their tracks, and the T14's root-bridge fixture becomes decoded values #714, The sysroot's std, core and alloc are built without debug assertions #715, A syscall's body runs with interrupts open, and a kernel-mode timer expiry re-arms one quantum #716, The primary's fork checkout, and its nested submodules, are moved to the commit its tree pins #718, virt_smp's second job wait goes on from the capture its first wait drained #719, Every image says which build it is: /system/etc/os-release, logged first by the supervisor #722, An idle CPU that finds XHCI taken halts, and the release kicks it #725, and The issue tracker is flat: issues/<slug>.md, and no area is left #723, the flatissues/), every hunk of both sides kept:Maskedinsync.rswith A syscall's body runs with interrupts open, and a kernel-mode timer expiry re-arms one quantum #716's line (above) and main'sOwedLockbeside it;counters_on_metalkeeps main's HWP-request and power-envelope checks and this branch's ACPI-mode SMI flatness;system.tomlkeeps acpiserver and main's compositor comment; the track keeps the ECDT ruling and main's power-off-through-the-server stage; this branch's issues move to flat paths, every citation of a subdirectory path in its files moved with them./system/bin/acpiserver(exempt: owns the claim). Start: every event disabled and cleared, then PWRBTN and the EC's GPE enabled, the EC drained, ack. Each SCI:sci::events(pure, host-tested) reads status&enable off both blocks; an event this server never enabled panics naming it; 64 empty SCIs in a row panic. Press →toyos::power::stop(Shutdown)through the supervisor. EC GPE (edge): cleared, then the drain queues query numbers (≤ 32, else panic), and they run throughaml::queryafter the drain (roast BLOCKER 4).ec.rsis a sans-IO transaction (queries only in stage 1; reads/writes join with AML); each wait is the host's, bounded 500 ms, panicking withEC_SC.aml.rsholdsquery(q)alone, which serves nothing in stage 1; every GPE but the EC's is refused asUnserved::Gpe(round 7: the runtime-GPE arm,Trigger,Gpe,DispositionandServed.runtimewere dead and went). Logging per the track's ruling: a line the first time a query number is taken, counts every 30 s where they moved. Noinspect acpi.*port (the track's ruling puts the counts in the log).SDK/ABI:
toyos_abi::acpi::{AcpiInfo, Block, ACK},toyos_abi::ioport(x86-64in/out; AArch64 dies naming the port) re-exported astoyos::ioport(toyos::portis the IPC ports'),toyos::AcpiDev. The tests'arch/port.rsgoes.Manifests:
system.tomlandtests/testcasesstart acpiserver (devices = ["acpi"],receives = ["power"]), so every T14 row ontests/testcasesruns in ACPI mode;tests/latencycasedoes not, so the latency rows keep their baseline. Newtests/acpicasefor the death row, its test-runner holdingdeviceanddup.Issues: filed
issues/the-acpi-servers-holder-drives-the-embedded-controller-unfiltered.md(owner's ruling: the weakness written down) andissues/the-acpi-server-talks-to-the-embedded-controller-without-the-global-lock.md(no_GLKanywhere in the T14's DSDT/SSDTs, byte-searched outside the tree). Citations of the deletedGRANTABLEupdated in two issues; the port-switch cost issue now says two rows.Extracts only (owner):
toyos-acpi/tests/thinkpad_t14.rslays the T14's FACP/ECDT/APIC out from the fields the decoders read and seals them; held against Linux's lines on the same machine (EC_CMD/EC_SC=0x66, EC_DATA=0x62,GPE=0x6e,INT_SRC_OVR … 9 high level, PM1a_CNT 0x1804,SMI_CMD=0xb2,ACPI_ENABLE=0xf0). Out of tree, the whole captured tables were checked against the extract and decoded (MSDM never opened):FACP: 276 bytes, revision 6, every extract field matches/ECDT: 83 bytes, revision 1, every extract field matches/APIC: 2 overrides, the extract's two; sci_line(9) = Line { gsi: 9, trigger: Level, polarity: High }, EXIT=0.Round 7 (review round 1 at
fe36f86f9):toyos_acpi::pm1a_controlis the one decoder of the PM1a control block (X_-aware, length fromPM1_CNT_LEN);power::init_offdeclares what it returns,FixedHardwareno longer carries a copy, andacpi_mode::inittakespower::pm1a_control()without reconciling two decodes. A soft-off now also refuses a control block whoseX_twin disagrees or that runs past the port space.ioapic: a unit's index/data pair is aWindowinside itsMaskedlock, soread/writeare reachable only through that unit's guard.quiesce::stoppinglost its one caller and went.NotSupportedline names theisa:andacpi:lines besidepcidev:andpartclaim:.issues/the-t14s-firmware-interrupts-every-cpu-every-2-2-s-under-toyos.mdloses its false "no ACPI enable handshake" bullet and records thecountersrow atee6aadecband what of its exit stays unmet: its second read is atspin, not at the stop's report — so it stays open. The press issue is renamedissues/the-t14s-power-button-event-came-up-to-17-s-after-ec-query-0x28.md, states the lag and the lost-first-press readings with the owner's account, and is owned by stage 1. The EC issue records that the command port takes the maker's own commands too, a firmware update among them not ruled out.acpi_mode.rs, Linux's SMI reading in thecountersdoc (and its claim to be the issue's exit), "Fix 2 of the design's roast" insci.rs.Merge of
origin/mainat010a283a9(A killed program is recorded as file, offset and build-id, and userland names its frames #711, The root and kernel locks take the per-range maxima the five main locks already carry, the first stage of the one-workspace track #724, The loader clears the screen at its start and stamps every line with its milliseconds #721): no conflicts.Merge of
origin/mainat2328e4488(The kernel's diary is readable inside the machine, on the trace right, by a reader that ships #717, The layout is a track, its findings are filed, and step 0 lands: every package says what it is #727, No kernel asm names a register LLVM may give its operands: cpuid via core, cmpxchg16b and run_on_stack on named registers #730): no conflicts, no hand resolution. In the merged treeAcpi = 10is still the only class 10,acpiserverhas thedescriptionThe layout is a track, its findings are filed, and step 0 lands: every package says what it is #727 requires, andcpu.rs'sin/outwrappers name their registers as No kernel asm names a register LLVM may give its operands: cpuid via core, cmpxchg16b and run_on_stack on named registers #730 requires.Round 8 (review round 2 at
5d0278a60):5d0278a60acpi_mode::quietpanicked whenever the stop's record had a userland thread still running, whichquiesce::PARKsays a thread in the block layer can do with no kernel bug; on q35 (handed over in ACPI mode, soquietruns on every image) a userland program could turnSYS_SHUTDOWNinto a kernel panic. The hazard is only a holder writing the row's ports afterquiet, and a thread writes a port only from Ring 3. Once the stop's stage is open, every return to Ring 3 passesscheduler::leave_user_if_due, which stops every thread but the caller;pio::take_backthen issues a TLB shootdown (invalidation::Origin::Stop), which returns only once every other CPU has answered it from Ring 0. After it no thread but the caller, which never returns, can be in Ring 3, soquietand the S5 panic's PM1 reading take the row back unconditionally. Nothing reads the record any more:quiesce::STOPPEDanduserland_stoppedgo.quiesce::stopreturns aStoppingwitness beside the record, threaded throughpower::shutdownto both architectures'power::off, so the row can be taken back only after a stop.quiesce::stoparms its progress watch before the stage opens, not after: no lock is dropped between the opening and the first sweep, so no pass can take the caller's CPU between them. That is what makes the new test exact on one CPU.stop-budget-spent(the stop gets a zero budget: one sweep), new guest testmachine_shutdown_short_stopand its binarytest_rs_stop_short(below).toyos_acpirefuses a PM1a event or control block that neither field names asFixedRefused::Absent { field }, not as aLengthcarrying a length that is not wrong (NOTE 3);corpus.rsasserts both.Merge of
origin/mainat0613f93f7(Logs on screen are coloured by severity and source, with a compact stamp #720, toyos-ld is gone, and no linker runs inside ToyOS #726, File grants: one port per file-server role, a connection is the grant it was minted, and each instance has a quarter of each bound #729, The userland lock takes the per-range maxima the five main locks already carry, the second alignment of the one-workspace track #732): no conflicts, no hand resolution.Round 9 (review round 3 at
3e73ab0ae):SMI_CMDwrite once the stop has begun (NOTE 1). The take-back covered only Ring 3: a holder whose process was inprocess::leave(Ring 0) when the stop's budget ran out dropped its claim on another CPU and reachedacpi_mode::release→leave, which on the T14 (where the mint wrote the enable) wroteACPI_DISABLEconcurrently withquietand theSLP_TYP/SLP_ENwrites. EverySMI_CMDwrite — the mint'sACPI_ENABLEand bothACPI_DISABLEpaths, the release's and a failed mint's — now holds one lock (acpi_mode::SMI_CMD_WRITE) and is not made oncequiesce::begun()(the stop'sSTAGE, which only opens);power::offtakes that lock once (acpi_mode::settle, which needs theTakenBack, so it runs only after a stop) before it readsPM1a_CNT. So a write in flight when the stage opened has finished before the power-off readsSCI_EN, every later one sees the stage open and is skipped with a log line, and the power-off quiets whichever mode it finds. A claim minted during the stop is refused by name. A stage check alone, the review's named fix, leaves a check-then-act window on a short stop: a release that read the stage closed could still be writing during the power-off; the lock closes it.TakenBack::run(row, at)hands out theatth run of a row the boot filled (isa::runs), not aDeclaredfor anyPorts(NOTE 2);acpi_modenames the PM1 event and GPE0 runs by their places in its row.acpi_mode, and the decision is one lock and one flag. Not QEMU: OVMF hands q35 over in ACPI mode, so the mint writes nothing,ENABLEDstays clear and noSMI_CMDwrite is ever made there (acpi_power_buttonasserts it); measured: M11, the stage check removed (m11-smi-cmd-ungated.patch), stays green onmachine_shutdown,machine_shutdown_short_stopandacpi_power_button(below). Not a metal row: the T14 writes the enable, but the race needs a holder's teardown in Ring 0 across the stage's opening, which no row can schedule — every thread but the caller is stopped at its Ring 3 boundary, so none starts an exit after the stage opens, and one already exiting reachesreleaseat a time no test controls. Checked by reading: everyoutbtoSMI_CMDis undersmi_cmd_write();settlefollowstake_backinoff, which followsSTAGE.openin the same thread.Merge of
origin/mainat9de5d7df9(The kernel's idle report is gone, and the counters row measures a quiet idle second #728, Routine lines go on stdout, so only a failure is drawn as an error #733, The interrupt census keeps one word per delivery, so it always adds up #734, Review: prose is never a blocker, cosmetic findings are not raised, and a body's measurements stay evidence #735, The host suite's assumption of a non-root user is filed #736;5c89f8954).tests/toyos.rsconflicted incounters_on_metal, in its doc and its SMI summary line. Both sides are kept: The kernel's idle report is gone, and the counters row measures a quiet idle second #728's refusal of any line, the kernel's or a program's, stamped in a millisecond fromidle0's toidle1's, and this branch's ACPI mode with every CPU's SMI count flat fromidle0tospin(which replaced main's legacy-mode rise of two or more). The judge's code merged with no hand edit; only the doc and theeprintln!were resolved by hand.Rounds 10 and 11:
acpiserver's lines and the quiet second (464504f7d, replaced by89c331ac1;tests/toyos-rust-tests/src/bin/counters_metal.rsalone).countersboot come at times the machine chooses: acpiserver's first sighting of each EC query number, the kernel'sisa: the ACPI fixed hardware took its first interrupton the server's first read after an SCI (the same millisecond or one before), and acpiserver's counts line 30 s after arming. Across the T14 readbacks of this branch, the first query, 0x4f on every unattended boot, came 0.04 to 2.14 s after the server armed (2.144 s at464504f7d'scountersboot,acpi1-r10/metal-counters/testcases: armed 1.180 s, query 3.325 s). The job starts within 5 ms of the arming, and The kernel's idle report is gone, and the counters row measures a quiet idle second #728's settle putidle0about 50 ms after the job started, so the second spans part of the window where that first query lands.464504f7d'scountersrow was green only because its query came at 3.325 s, afteridle1at 2.232 s.464504f7d,acpi1-r10/metal-retake-shown,metal-retake-off).464504f7dread the log back after the three reads and took them again, once, if every line stamped in the second was acpiserver's or the kernel's first-interrupt line. Both staged arms widened the second to 3 s, so it caught the first query. In both, three lines fell in the second, not two:isa: the ACPI fixed hardware took its first interruptandquery 0x4f taken for the first timeat 2.953 s (2.154 s inretake-off), thenexit: logkeeper tid=3 code=0 cpu=0ms2 to 3 ms later. That third line is the kernel's record of a logkeeper thread ending, after the write the two ACPI lines caused. It is neither acpiserver's nor the first-interrupt line, so the retake refused to fire, andretake-shownwas red on the same lines asretake-off. The design also left the green to chance, since it rested on no first sighting landing in either of two takes.counters_metalopens the log from its first line (logkeeper serves a reader the boot so far), and before The kernel's idle report is gone, and the counters row measures a quiet idle second #728's settle it waits until three lines are there: acpiserver'sarmed:line, and, where the server serves an embedded controller, the kernel's first-interrupt line and acpiserver's firsttaken for the first timeline. The wait is bounded at 10 s and panics loudly past that. Then comes the settle, which waits out the log write those lines cause, the logkeeper exit line included, and only thenidle0. So every line that comes once, at a time the machine chooses, is stamped before the second opens. The one later line the server is certain to log is its counts line, no earlier than 30 s after itsarmed:line (COUNTS). Only the judge refuses it: it reds on any line stamped in the second and names it (round 12 below deletes the job's copy of that check). An estimate from the recorded timings: the first query within 2.14 s of arming, then the settle, then 1 s, so the second should end about 3 to 4 s after arming, far inside 30 s. At89c331ac1the one T14countersboot was green (T14 rows, below), but that boot's first interrupt and query came at 1.239 s, before the settle's lines (1.325 s, 1.367 s), so main's order would probably have kept them out too. That boot does not show the wait doing anything; the round-12 pair is meant to show it. The judge is unchanged. The retake, itsacpifilter and thewithinargument ofsettleare gone.git diff 464504f7d 89c331ac1 --stat:counters_metal.rsonly).counters_metalruns on no guest (tests/toyos.rs'sRUST_SKIP), because its product is the T14's counters. The control is the round-12 P/N pair of T14 arms (below).Merge of
origin/mainate559859ed(The loader lock takes the per-range maxima and its profile strips debuginfo, the last alignment of the one-workspace track #738;1d4fb9358): no conflicts, no hand resolution. The loader lock takes the per-range maxima and its profile strips debuginfo, the last alignment of the one-workspace track #738 touches onlybootloader/and one issue.Round 12 (review round 5 at
89c331ac1;cd5e11869,counters_metal.rsalone, +11/−24):COUNTS_MSwas a private copy of acpiserver'sCOUNTS, and theidle1assertion against it checked what the judge already refuses and names: any line stamped fromidle0toidle1, the counts line included. A copy that drifts from the server would leave the judge as the only check anyway. Deleted with them:acpi_said's returned stamp and the module doc's sentence about the assertion.Log::untilchecks its deadline on every pass (NOTE). Before, it checked only when the pipe would block, so a log that kept handing back lines without the awaited one ran on to the runner's 60 s job ceiling, which does not name what was awaited. The check now runs before every read.settleandacpi_saidboth wait through it.Merge of
origin/mainatf260e0b98(Every line of the log opens with one head, time first, counted from the CPU counter's zero #737: every line of the log opens with one head, time first, counted from the counter's zero; one formatter,toyos_abi::log::Head, and one parser,toyos_logstream::parse). Merge commit8d7004d3b. Every hunk of both sides is kept, and every line this branch reads now goes throughparseor its wrappers (program_line,record_ms); nothing this branch writes builds a head of its own (the kernel'slog!and logkeeper write every head). Four files conflicted:src/bootlog.rs: this branch'sasked_to_power_offandPOWER_OFF_ASKEDsit beside main'sLOADER_CLOCK_*,LOGKEEPER_*andone_clock, and main's new doc forrecord_millis. By hand:asked_to_power_offreads the stop line throughprogram_lineand compares the text afterSTOPPINGwith(Shutdown)exactly. Before, it matched the raw line's tail.tests/checks.rs: both registrations are kept,metal_power_off_owes_no_paneland main'smetal_loader_kernel_and_program_count_from_one_zero.tests/checks/metal.rs: both checks are kept.a_power_off_owes_no_panel's supervisor line is now written in the one head ([2026-09-29 18:22:40 1.214 supervisor]). It now runs through main'splant, which prepends the logkeeper spawn and the supervisor's word of it, and through main'sloader, which states no clock. Soone_clockpasses it, and the check still asserts only the panel.tests/toyos-rust-tests/src/bin/counters_metal.rs: both imports are kept, this branch'stoyos::Pipeand main'sstamp_ns. By hand:acpi_saidused to match the kernel's first-interrupt line asrecord_ms(line).is_some() && line.ends_with("] isa: …"). It now usesparse(line)withSource::Kerneland the exact textisa: the ACPI fixed hardware took its first interrupt.Beyond the conflicts, by hand, so that no second reader is left: the power-off fixtures in
src/bootlog.rsandsrc/metal.rsare in the one head (old-head lines no longer parse, so these tests would red rather than pass wrongly).acpi_events_on_metal(tests/toyos.rs) used to pick the server's lines by the wordacpiserveranywhere in a line, which the supervisor's and the kernel's lines about the server also carry. It now picks them byprogram_line's tag. Everything else merged with no hand edit,counters_on_metal's doc included: main'sstamp_nssentence sits beside this branch's ACPI-mode sentence.Measured
acpi: the ACPI row: PM1a events 0x600+4, GPE0 0x620+16, SCI gsi 9 level/high, … embedded controller none (the ECDT is unusable: Absent); the firmware handed over in ACPI mode. So the guest test exercises the claim, the SCI and the server, and never the enable write, its wait or the disable: those are the T14's.PM1a_CNT=0x0000).The attended press at
b3b9ccd69Readback
orch/acpi1-r2/metal/testcases-press/,log-partition.imgincluded. The log ends at{16.705 acpiserver} acpiserver: the power button was pressed, on SCI 19 of this boot; asking the supervisor to power offand the supervisor's(Shutdown)at the same millisecond; EC query 0x28 was first taken at 6.696 s. The owner: nothing happened at his press; a second press about 5 s later turned the machine off at once. The next loader pass:Boot attempts: the previous boot of this image never reported— an empty black box, which a correct S5 also leaves.PWRBTN_STScame 10 s later, and the power-off after it stalled until the second press. (b) The first press raised 0x28 and noPWRBTN_STS; the second press, at about 16.7 s, was served and the machine went off at once. Reading (b) needs no stall in ToyOS at all. The host recorded no press's time on that boot. Atff4945d6done deliberate press was served 16 ms after its 0x28 (below), which favours (b); the press's time was recorded only to the minute, so the press issue stays open.Rebooting.in 8 ms; on QEMU the press's whole stop takes 20 ms (orch/acpi1-r3/probe-qemu.log). A power-off differs from the reboot only inarch::power::off. Since round 3 a write ofSLP_ENthe platform does not act on panics within 2 s with the registers; no T14 power-off since has stalled (7c3a7dc7d,ee6aadecb), andissues/the-t14-stayed-on-after-a-power-off-in-acpi-mode.mdis deleted on that exit.issues/the-t14s-power-button-event-came-up-to-17-s-after-ec-query-0x28.md: 0x28 → press 10.009 s atb3b9ccd69, 0.017 s at7c3a7dc7d, 17.552 s atee6aadecb, 0.016 s atff4945d6d(added this round); 0x28 on those four press boots and on no unattended boot; no T14 table defines_Q28. A first press that leaves only 0x28 leaves the stage-1 exit's "a press stops the machine" unmet on the T14.issues/a-stop-shows-nothing-on-the-panel.md, not built here.Gates
At
922a6b7c7(round 13, the head; logs underorch/acpi1-r16/). One script,gates.sh, ran the three in turn from the clean committed tree (gates.headis922a6b7c72f59a267478b13059d97bf2c0579b6f;gates.statusandgates.status-afterare empty) and wrote each exit togates.exits.cargo run -- --ci host: EXIT=0 (host EXIT=0), "Host: 76 step(s), all green" (host.log:7440). It ranfixed_hardware_reads_whichever_form_names_a_blockanda_power_off_is_the_supervisors_stop_naming_a_shutdown.cargo run -- --build-only: EXIT=0 (build EXIT=0,build.log).cargo test(the whole guest suite): EXIT=0 (guest EXIT=0), "30 passed, 30 total" (guest.log:882), withPASS acpi_power_button(:664),PASS machine_shutdown(:709) andPASS machine_shutdown_short_stop(:779). Load average was 73.16 at its start and 43.46 at its end (guest.load).counters_metalis inRUST_SKIP; the staging below built it.8d7004d3breadbacks (copies underacpi1-r16/old-8d7004d3b/, so the originals are untouched;judge-old.exits):--metal-readback … acpi_server_EXIT=0, "2 passed, 0 failed, 2 boot(s)" (judge-old-acpi_server.log);--metal-readback … countersEXIT=0, "3 passed, 0 failed, 3 boot(s)" (judge-old-counters.log). This shows that the judges round 13 edited still read a real T14 log,acpi_death_on_metalwithout its dropped branch included. It is no reading of this head's kernel: those boots ran8d7004d3b's.8d7004d3bdo not stand for this head.git diff --stat 8d7004d3b 922a6b7c7over the image sources iskernel/src/arch/x86_64/acpi_mode.rs+59/−27,toyos-acpi/src/fadt.rs+21/−11,toyos-acpi/src/lib.rsandacpi_hold.rs's doc: the kernel in every image changes, andacpi_server_deathreads the line the change rewrites. The three rows were staged and then run at922a6b7c7, all green (T14 rows, below). None of them needs a person at the machine.04bcf041ditself was never gated whole. Its host run (acpi1-r15/host.exit, EXIT=0) was on that tree less one later edit, and its guest suite could not build.922a6b7c7differs from it by issues and by three comment lines ofacpi_mode.rs, replaced three for three.At
8d7004d3b(before round 13).Logs under the orchestrator's scratchpad,
orch/acpi1-r13/. Each command's exit is written to a file beside its log.cargo run -- --ci hostat8d7004d3b: EXIT=0 (host-8d7004d3b.exit), "Host: 76 step(s), all green" (host-8d7004d3b.log). It ranbootlog::tests::a_power_off_is_the_supervisors_stop_naming_a_shutdown,metal::tests::a_power_off_leaves_no_record_and_is_no_hang,checks::metal_power_off_owes_no_panelandchecks::metal_loader_kernel_and_program_count_from_one_zero.cargo test(the whole guest suite) at8d7004d3b: EXIT=0 (guest-8d7004d3b.exit), "30 passed, 30 total" (guest-8d7004d3b.log), withacpi_power_button,machine_shutdownandmachine_shutdown_short_stopPASS. Load average was 10.85 during the run.counters_metalis inRUST_SKIP; the staging below built it.mut-old-head-fixture.patch(posted as a comment):a_power_off_is_the_supervisors_stop_naming_a_shutdown's fixture put back in the pre-Every line of the log opens with one head, time first, counted from the CPU counter's zero #737 head.cargo test --lib a_power_off_is_the_supervisorsEXIT=101 atsrc/bootlog.rs:556(mut-old-head-fixture.log,.exit), and the tree was restored clean. A fixture in the old head can no longer pass for a power-off.Round 12's gates at
cd5e11869, logs underorch/acpi1-r12/.host.headandguest.headholdcd5e118697a59de0b70be625d9b21b09318a17d6, andrun.shran both.cargo run -- --ci hostatcd5e11869: EXIT=0 (run.exits:host EXIT=0), "Host: 76 step(s), all green" (acpi1-r12/host.log).cargo test(whole guest suite) atcd5e11869: EXIT=0 (run.exits:guest EXIT=0), "30 passed, 30 total". Load was 3.23 at its start and 15.33 at its end (guest.log,guest.load). It does not runcounters_metal, which is inRUST_SKIP. The staging below built it at head and in both arms (BUILT x86_64 binaries of tests/toyos-rust-testsin eachstage-*.log).89c331ac1(orch/acpi1-r11/):--build-onlyEXIT=0;--ci hostEXIT=0, "Host: 76 step(s), all green"; the guest suite EXIT=0, "30 passed, 30 total".464504f7d(orch/acpi1-r10/):--ci hostEXIT=0, "Host: 76 step(s), all green"; the guest suite EXIT=0, "30 passed, 30 total".ff4945d6d(orch/acpi1-r9/):--ci hostEXIT=0, "Host: 76 step(s), all green"; the guest suite EXIT=0, "30 passed, 30 total",machine_shutdown_short_stopPASS,machine_shutdownPASS,acpi_power_buttonPASS; load 30.72 at its start, 55.32 at its end;cargo run -- --build-onlyEXIT=0 (build1.log).3e73ab0ae(orch/acpi1-r8/):--ci hostEXIT=0 "76 step(s), all green", the guest suite EXIT=0 "30 passed".5d0278a60(orch/acpi1-r7/)--ci hostEXIT=0 and the guest suite EXIT=0 "29 passed"; atee6aadecb/619a7f05c(orch/acpi1-r4/)--ci hostEXIT=0, the guest suite EXIT=0 "28 passed", the judge fix's negative control EXIT=101 (acpi1-r4/neg-judge-reverted.log).The T14's two reds at
a0f4e9ade, root causesacpi_server_death: the test's boot configuration.tests/acpicasegave test-runnersyscap = ["device"]withoutdup. test-runner hands each job its capability only as a duplicate (userland/test-runner/src/main.rs,run_one), a duplicate needsdup, and onPermissionDeniedit spawns the job with none — sotest_rs_acpi_releasefound nothing under its label:thread 'main' (1) panicked at src/bin/acpi_release.rs:20:60: test-runner endows a device-minting capability,exit: test_rs_acpi_release pid=8 code=101. acpiserver never ran on that boot (noisa: the ACPI fixed hardware's ports areline, noACPI mode:line).tests/metaldevicecasealready grants["device", "dup"]for the same handing;tests/acpicasenow does too.acpi_server_events: the server was right; the row's boot was too short for what its judge reads. On the shared testcases boot the server armed (acpiserver: armed: power button served, embedded controller on GPE 0x6e at 0x66/0x62, 0 GPE(s) the namespace runs, 1.196 s), took the SCI (isa: the ACPI fixed hardware took its first interrupt, 2.962 s) and logged its first query (acpiserver: embedded controller query 0x4f taken for the first time, served by nothing: stage 1 runs no AML, 2.962 s), and the job list reachedrebootat 10.466 s. The server writes its counts line once its 30 s count interval has passed, so no boot that ends at 10 s can carry one. The row now bootstestcases-hold, whose one job,test_rs_acpi_hold, holds the list open to 54 s (JOB_BOUND_MS - JOB_BOUND_MS / 10, the shapelan_talk_holduses), and its judge first asks that job to have passed. The judge's checks are unchanged.acpi_press_holdslept 180 s, past the runner's 60 s job bound (toyos_tco::JOB_BOUND_MS), so on the attended boot the runner would have killed it and rebooted at 60 s, and its "no press in 180 s" line could never be written. The press boot now runs the sametest_rs_acpi_hold: the owner has from the server's arming (about 1.2 s) to 54 s, and the press judge readsacpi_hold: held toas nobody having pressed.Why a new guest test (
acpi_power_button,machine_shutdown_short_stop)QMP
system_powerdownon q35 raises the fixed power-button event: the press → SCI through the I/O APIC level line → the handler's mask → the server's record → the supervisor's stop → S5, which QEMU reports asguest-shutdown. No type or host test reaches the interrupt path or the firmware's register block; the T14's press is read by no row (round 13). It asserts the press arrived as SCI 1 — the level line masked until served — thenSTOPPING (Shutdown),Shutting down.and QEMU'sguest-shutdown.machine_shutdown_short_stop: a stop that ends with a userland thread running, then the power-off, on q35 withacpiserverholding the row in ACPI mode. One CPU andstop-budget-spent:test_rs_stop_shortspawns a spinning thread and asks the supervisor for the power-off, so the stop's one sweep finds the spinner queued behind the caller. It asserts the server armed, the firmware handed over in ACPI mode (soquietruns), the stop record says threads were left running (stop: 14 of 17 userland thread(s) stopped across 1 cpu(s) in 0 ms of a 0 ms budget over 1 sweep(s)), no kernel death, and QEMU'sguest-shutdown. Not a type: what5d0278a60did wrong was a runtime branch on the stop's record, and what must hold is that the whole stop and power-off end the machine. Not a host test: no host test runsquiesce::stoporpower::off. Not a metal row: the T14 hands over in legacy mode, a power-off leaves it needing a hand, and a stop short on purpose needs the actuator's one-CPU guest. It is the test that reds on BLOCKER 1 (negative control below).Negative control and mutations (patches posted as comments 5977460273, round 7 5982314577, round 8 5983718133, round 9 5984251318, round 10 5985109579, round 11 5985481401, round 12 5985746125)
Round 12, the
countersrow's control: arms P and N (comment 5985746125), run on the T14 atcd5e11869(comment 5985874439): P EXIT=0, 3 passed, with the query at 2.483 s before the settle; N EXIT=1 from the judge, naming the first-interrupt and first-query lines at 1.804 s inside the second. Both arms setIDLEto 3 s, which is still far insideCOUNTS, so that a line the wait failed to keep out has a wider second to land in.wt/toyos-acpi1-arm-p,efb44c9f4, a child of head) changes only that. Expected GREEN.wt/toyos-acpi1-arm-n,69eb4fc10, a child of P) also makesacpi_saidstop waiting at the arming line (armed.is_some()). It built with noallow. Expected RED from the judge, withtest_rs_counters_metalexiting 0 andthe idle second … holds linesnamingisa: the ACPI fixed hardware took its first interruptandacpiserver: embedded controller query 0x4f taken for the first time.idle0, the boot did not exercise the mutation. It is reported as such and staged again, and it does not count as a pass.acpi1-r12/image-carries.txt). Thecounters_metalbinary each staging built hashes the same as the bytes of itstestcases/image.imgat offset 116445184. Disassembly (metal-*/counters_metal.dis): beforesleepat 0x27410, head loadsmovl $0x1, %ediand both arms load$0x3. Inuntil::<acpi_said::{closure#0}>::{closure#1}, P computesdonefrom the armed byte,interruptandquery. N computes it ascmpb $0x2, (%rax); setne %al, which isarmed.is_some().Round 11's control,
acpi-in-second.patch: red, but it showed nothing about the change (orchestrator's T14 run, comment 5985637817; review round 5). The patch calledacpi_saidaftersettleon the sameLog.settlehad already read pastacpiserver: armed:(1.189 s), soacpi_saidcould never see an arming line. It panicked at its 10 s bound, at 11.240 s (metal-acpi-in-second/testcases/kernel.log:369-370), andtest_rs_counters_metalexited 101, so the judge stopped atjob_passedand never read the second. Had it run as meant, it would have tested The kernel's idle report is gone, and the counters row measures a quiet idle second #728's judge on a counts line, which is main's claim and not this round's. It also droppedloaded. The round-12 pair replaces it. The run does show the 10 s bound is loud on metal: the panic came 10.04 s after the wait began, naming what it waited for.M11 (round 9) the stop's stage no longer gates
SMI_CMDwrites (acpi1-r9/m11-smi-cmd-ungated.patch, applied and reverted inm11.sh,m11.exits):cargo test -- machine_shutdownEXIT=0, "2 passed, 2 total" (m11-shutdown.log);cargo test -- acpi_power_buttonEXIT=0, "1 passed" (m11-button.log). Green as expected: no QEMU boot writesSMI_CMD(round 9, above, says why no tier reaches it).Round 8, BLOCKER 1's fix reverted onto
3e73ab0ae(the eight product files back to the merge2746aef8; the test, its binary and the actuator's budget kept):cargo test -- machine_shutdown_short_stopEXIT=1,QEMU had not exited 25 s after it was asked to(acpi1-r8/negative-control.log). A second run of the same patch with one line that writes the console out (negative-control-2.log, EXIT=1) shows the stop recordstop: 14 of 17 userland thread(s) stopped across 1 cpu(s) in 0 ms of a 0 ms budget over 1 sweep(s)and thenPANIC: panicked at src/arch/x86_64/acpi_mode.rs:283:9: power: the stop left userland running, so the events its ACPI holder enabled cannot be quieted for S5— the reviewer's panic. With the fix: EXIT=0 (guest-debug2.log, and the suite above).M10 (round 8)
take_backissues no shootdown:cargo test -- machine_shutdownEXIT=0, both green (acpi1-r8/m10.log). No test reaches it: the stop's own kick, sent when the stage opens, reaches every CPU milliseconds beforequiet(the USB flush and seal lie between), and a CPU in Ring 3 takes it at once, so no guest can put a holder in Ring 3 atquiet. The shootdown makes that an acknowledgement rather than a delivery time; checked by readingleave_user_if_due,Shootdown::serve's acquire of the generationissuepublishes afterSTAGE.open, and the exit path every Ring 3 return takes (quiesce.rs's header).Whole change reverted (product code and the testcases manifest reverted onto this head, test kept):
acpi_power_buttonEXIT=1,STALLED: waiting for the ACPI server arming. On the T14 the control is main's owncountersrow (Boot 1: ΔSMI ≥ 2 alike, legacy mode).M1
isa::isrskips the level mask: EXIT=1 —the power button was pressed, on SCI 10000 of this boot.M4
isa::ackunmasks nothing: EXIT=1 — the press never arrives (STALLED: waiting for the boot's last word).M2
sci_line's default edge/high:toyos-acpi --test corpusEXIT=101 (the_sci_defaults_to_level_and_active_low_where_nothing_says_otherwise).M5
Reserved::declareskips the clash:toyos-userboundEXIT=101.M7 (round 7, in the shared
addressdecoder)X_disagreement unrefused: corpus EXIT=101 (acpi1-r7/m7.log).M8 (round 7, retargeted) another GPE taken as the EC's: acpiserver host tests EXIT=101 (
acpi1-r7/m8.log).M3
claim_rowskips the holder check: on the T14 at5d0278a60(orchestrator, comment 5982813833),cargo test --test toyos-build -- --metal --metal-readback orch/acpi1-r7/metal-m3 isa_claim_refusedEXIT=1,FAIL test_rs_isa_claim_refused: test_rs_isa_claim_refused exited 101 on the T14(orch/acpi1-r7/metal-m3/judge2-isa_claim_refused.log); its kernel log:a claim on the i8042 answered Ok(()), not PermissionDenied, GSI 1 and 12 routed to the claimant. Red again at3e73ab0ae(below), and restaged atff4945d6d.M9 (round 3) S5 the platform ignores:
offwritesSLP_TYP7, which q35 does not act on;cargo test -- machine_shutdownEXIT=1 —QEMU stopped this guest for None, not "guest-shutdown", the console carryingPANIC: … power: S5 did not take: the machine still runs 2000ms (…) after SLP_EN; PM1a_CNT read 0x0001 before the write of 0x3c01 and reads 0x1c01 now, SCI_EN set; PM1 status 0x0000 under enable 0x0000; cpu0's SMI count unread before the write and unread now(acpi1-r3/mut-s5-ignored.log). This is why no guest test of the panic is added: it needs a platform that ignores a validSLP_TYP, which only an actuator shipped for the test could stage, and QEMU never stalls on the valid one (measured above), so no QEMU test reds on the T14's stall.off()clearing SCI_EN, or skippingquiet: no QEMU or unattended red; on the T14SLP_SMI_ENroutes the sleep write to SMM, and no T14 row reads a power-off (round 13). Checked by reading against the spec's sequence.Oracles
ACPI 6.5 (FADT Table 5.9/5.10, ECDT Table 5.88, MADT INTI flags, PM1 Tables 4.13/4.16, EC §12); Linux's readings of the T14 (above); QEMU's ICH9 model (the guest test); the T14 rows below.
T14 rows
Run at
922a6b7c7, all green (the orchestrator's run, comment 6039409933; per boot in round 14, at the top). Round 13 changes the kernel in every image, so every row's old reading is of another kernel; three rows read what it changes, and they are the ones staged:counters(reds onacpi: legacy mode again, and reads the mint's line),acpi_server_events(the row and the server's lines on a boot held 54 s) andacpi_server_death(the release: the disable, the poll, and the rewrittenlegacy mode again: … PM1a_CNT reads … <time> after, SCI_EN clear). Every boot ends in the job list's reboot; none needs a person. Theisa_rows andacpi_table_inventoryare not restaged: the round's brief names these three. Their8d7004d3breadings are of the previous kernel;kernel/src/isa.rsandkernel/src/arch/x86_64/pio.rsare unchanged since it (the image sources' diff is in Gates).Staged by
orch/acpi1-r16/stage.shfrom the clean head (stage.headis922a6b7c72f59a267478b13059d97bf2c0579b6f;stage.statusis empty). Each stage rancargo test --test toyos-build -- --metal --metal-readback orch/acpi1-r16/<dir> <filter>and exited 2 with the machine untouched (stage.exits,stage-<dir>.log). Hashes are inorch/acpi1-r16/images.sha256;orch/acpi1-r16/request.txtlists the boots, the hashes and the two judge commands.metal-counters,counterscounterssharedc46cc487…4195,shared-debug2ef538f3…c6d6,testcasesabb72e22…939cmetal-acpi_server,acpi_server_acpi_server_events,acpi_server_deathacpicase2d8c1f2f…ff8e,testcases-holdae15eb50…86a0What the green run shows, and what it does not.
acpi_server_deathgreen shows the T14's firmware clearedSCI_ENinside the poll, 17068 ns after the write, and the kernel said so. It cannot show the 100 ms expiry or thestill in ACPI modeline, which need a firmware that ignoresACPI_DISABLE; what follows the expiry isissues/the-acpi-row-is-released-with-an-acpi-disable-the-firmware-has-not-answered.md. The judge's run on the two new boots asked fortests/metal/lenovo-20w0003amz.tomlto be committed with their six timing numbers (boot.acpicase.*,boot.testcases-hold.*); round 14's commit carries them.Results at
8d7004d3b(the previous kernel; orchestrator's runs, comments 5986396904 and 5989607386; worktree clean at that head, each image's sha256 checked before its flash, every boot rc=0).isa_EXIT=0, "3 passed, 0 failed, 2 boot(s)";acpi_server_EXIT=0, "2 passed, 0 failed, 2 boot(s)";acpi_table_inventoryEXIT=0, "1 passed, 0 failed, 1 boot(s)";countersEXIT=0, "3 passed, 0 failed, 3 boot(s)". The two attended rows, with the owner:acpi_power_offEXIT=0 andacpi_power_button_pressedEXIT=0, the latter on a boot the owner reports pressing three times on (the press issue). Both rows are deleted in round 13. Judge logsorch/acpi1-r13/metal-*/judge-*.log.What the merge of #737 changed on the T14, and what was staged at
8d7004d3b. Two kinds of change reached the judges.judge_readbacksnow runsbootlog::one_clockon every boot. Every line on the stick is written in the new head, and every judge reads it throughparse. The loader and the kernel's clock change with Every line of the log opens with one head, time first, counted from the CPU counter's zero #737 too.acpi_power_offandacpi_power_button_pressed:asked_to_power_off, which both of them,boot_verdictandjudge_readbacks' panel exemption rest on, now reads the stop line throughprogram_line.acpi_server_events:acpi_events_on_metalpicks the server's lines by tag.counters: the judge's code is unchanged, but the job'sacpi_saidnow matches the first-interrupt line throughparse, and main'sstamp_nsis whatidle0,idle1andspinare read at.isa_rows,acpi_server_deathandacpi_table_inventory.The two attended rows are deleted in round 13; what follows is the record of why they were re-run at
8d7004d3b. Their judges could not be shown unchanged in what they read. Their verdict rested on the supervisor's stop line. #737 changed that line's bytes, from{… supervisor}to[… supervisor], and the merge changed the reader.judge_readbacksnow also requiresone_clockof their boot, which no power-off boot has been judged by. The loader and the kernel's clock changed under them too. The power-off path in the kernel andacpiserverdid not change (git diff --stat cd5e11869 8d7004d3b -- kernel/src/arch/x86_64/power.rs kernel/src/arch/x86_64/acpi_mode.rs kernel/src/quiesce.rs userland/acpiserver userland/supervisoris empty). But what the judges read did change, and the old readbacks were written in a head the new parser refuses.Staged by
orch/acpi1-r13/stage.shfrom the clean head8d7004d3b(stage.head;stage.statusis empty). Each stage rancargo test --test toyos-build -- --metal --metal-readback orch/acpi1-r13/<dir> <filter>, and each exited 2 with the machine untouched (stage.exits,stage-<dir>.log). Hashes are inorch/acpi1-r13/images.sha256.metal-isa,isa_isa_rowsisa-withheld165ef532…1cd6,shared9ac606cd…6101metal-acpi_server,acpi_server_acpi_server_events,acpi_server_deathacpicase9a6543b9…39ed,testcases-hold5353bc16…2691metal-acpi_table_inventory,acpi_table_inventoryacpi_table_inventorytestcases7e48479d…001fmetal-counters,counterscountersshared83a2de6d…06f0,shared-debug5e706408…7abc,testcasesb3cb2c0b…dc06metal-acpi_power_off,acpi_power_off(attended: the owner powers it on again)acpi_power_offtestcases-offfe9002a0…ae582metal-acpi_power_button_pressed,acpi_power_button_pressed(attended: one brief press)acpi_power_button_pressedtestcases-press65e17dac…c880The results below are of earlier heads.
Results at
cd5e11869(orchestrator's run, comment 5985874439; worktree clean; images checked againstorch/acpi1-r12/images.sha256):countersEXIT=0, 3 passed; arm P EXIT=0, 3 passed; arm N EXIT=1, the judge naming the ACPI lines in the second. Judge logsorch/acpi1-r12/metal-*/judge-counters.log. The rows below atff4945d6dstand: this branch's production code is unchanged since then.Results at
ff4945d6d(the orchestrator's runs, comments 5984390036 and 5984507827: worktree clean at that head, each image's sha256 checked againstacpi1-r9/images.sha256before its flash, every boot rc=0). Each judge iscargo test --test toyos-build -- --metal --metal-readback orch/acpi1-r9/<dir> <filter>, run from the clean head, with its log in the orchestrator's job directory. The two attended rows ran with the owner.isa_ports_are_the_binders_alone,isa_lines_reach_their_holder,isa_claim_refusedmetal-isa,isa_isa-withheld0e839e58…21b8,sharedd1c4aeb5…cc5facpi1-r9/metal-isa/judge-isa_.logacpi_server_events,acpi_server_deathmetal-acpi_server,acpi_server_acpicase128f80aa…6d55,testcases-hold7d99d5aa…b9cacpi1-r9/metal-acpi_server/judge-acpi_server_.logacpi_table_inventorymetal-acpi_table_inventory,acpi_table_inventorytestcases5bb0eda7…1eeacpi1-r9/metal-acpi_table_inventory/judge-acpi_table_inventory.logcountersmetal-counters,counterssharedfb9abb40…f41,shared-debug674f9e9c…e7f,testcases62f2b647…8f7a8 cpus, SMI flat on each over 12232 ms,SCI_EN set 16006ns after; cpu0's SMI count 4818 before the write and 4819 afteracpi1-r9/metal-counters/judge-counters.logm3-claim-skips-holder.patch), expected redmetal-m3,isa_claim_refusedsharedf6bb8afc…c195FAIL test_rs_isa_claim_refused: test_rs_isa_claim_refused exited 101 on the T14, "0 passed, 1 failed, 1 boot(s)"acpi1-r9/metal-m3/judge-isa_claim_refused.logacpi_power_off(attended)owner-acpi_power_off,acpi_power_offtestcases-offd2b732d6…20e3acpi1-r9/owner-acpi_power_off/judge-acpi_power_off.logacpi_power_button_pressed(attended)owner-acpi_power_button_pressed,acpi_power_button_pressedtestcases-press0b20db5e…f639acpi1-r9/owner-acpi_power_button_pressed/judge-acpi_power_button_pressed.logThe press, per its run instructions: the owner pressed once, about 10 s after the panel showed the loader's last line, and reports the machine "almost immediately went off" (
owner-acpi_power_button_pressed/owner-notes.txt; the press's time was recorded only to the minute). The boot's own record,testcases-press/kernel.log:So one press raised 0x28 and the power-button event 16 ms later, then the stop: no lag. Added to the press issue's table this round.
Earlier heads: at
3e73ab0ae(comment 5983862418,orch/acpi1-r8/) and5d0278a60(comment 5982813833,orch/acpi1-r7/) the five unattended entries above ran with the same exits.Results at
464504f7d(the orchestrator's run, its comment on this PR: worktree clean, each image's sha256 checked againstacpi1-r10/images.sha256before its flash, all 15 boots rc=0).isa_EXIT=0, "3 passed";acpi_server_EXIT=0, "2 passed";acpi_table_inventoryEXIT=0, "1 passed";countersEXIT=0, "3 passed". It was green only because the first query came at 3.325 s, afteridle1(round 11, above). M3 (expected red) EXIT=1,FAIL test_rs_isa_claim_refused.retake-shown(expected green) EXIT=1 andretake-off(expected red) EXIT=1, both on the lines round 11 explains. Judge logs:acpi1-r10/metal-*/judge-*.log.Results at
89c331ac1(the orchestrator's run, comment 5985637817: worktree clean at head, each image's sha256 checked againstacpi1-r11/images.sha256before its flash, all six boots rc=0).countersEXIT=0, "3 passed, 0 failed, 3 boot(s)". On that boot the first interrupt and query came before the settle's lines (round 11, above), so it does not show the wait at work. The controlacpi-in-secondEXIT=1, "2 passed, 1 failed, 3 boot(s)",FAIL counters: test_rs_counters_metal exited 101 on the T14. That is the job's own wait failing, not the judge naming a line (Negative control, above). Judge logs:acpi1-r11/metal-*/judge-counters.log.What round 12 changes on the T14, and what is staged. Only
counters_metal.rschanged on the branch's side (git diff --stat 89c331ac1 cd5e11869 -- . ':!bootloader' ':!issues'), and that binary runs only in thecountersrow. The merge brings #738, which changes the loader's lock and profile only. Staged byacpi1-r12/stage.sh. Each arm is checked out as a detached commit with a clean tree (<dir>.statusempty,<dir>.headits commit), and thencargo test --test toyos-build -- --metal --metal-readback orch/acpi1-r12/<dir> countersis run. Each exited 2 with the machine untouched (stage.exits,stage-<dir>.log). The worktree was back onwt/toyos-acpi1and clean afterwards. Hashes are inacpi1-r12/images.sha256.acpi1-r12/metal-counters/request.txt: the row at headcd5e11869, expected green.shared(aa513ced…ca454a),shared-debug(3c320edf…99133d),testcases(6bd5b6c5…f84c6f).acpi1-r12/metal-arm-p/request.txt: P,efb44c9f4, expected green.shared(cfa4dd02…cf9cd7),shared-debug(e5d7605f…04bbab),testcases(54e5b231…466aa2).acpi1-r12/metal-arm-n/request.txt: N,69eb4fc10, expected red from the judge on the two lines above.shared(7f9b16ae…08c1b6),shared-debug(01906c40…5e730e),testcases(5662ba48…812a45). Onlytestcasescarriescounters_metal, but the filter stages all three boots.What round 10 changed on the T14, and what was restaged then. The branch's own change this round is
counters_metaland an issue. The merge brings main's kernel and userland changes (#728 deletes the kernel's idle report, #734 changes the interrupt census, #733 moves routine lines to stdout), and these run on every image. So every unattended row is restaged at464504f7d. The power-off and press path did not change: neither the round's commit nor the merge touchesacpiserver,toyos::power, the supervisor, logkeeper,quiesce,power::offoracpi_mode(git diff --name-only ff4945d6d 464504f7d). The merge's kernel diff is 23 files, +100/−306: the idle report and the PMM category counters only it read, the census, and the call sites of both. The two attended rows are therefore not restaged, and theirff4945d6dresults above stand for this path.Round 10 staged every unattended row, M3 and the two retake arms at
464504f7d(acpi1-r10/stage.sh,stage.exits; patches in comment 5985109579); their results are above.Unsure
HANDBACKis this kernel's own number: the specification names no bound and nothing measured one. The T14's firmware took 17068 ns on the one boot that read it. The claim's last handle goes from the deferred queue with no lock held (ZeroHandles::on_zero_handles's contract,kernel/src/object/mod.rs), so the spin of up to 100 ms holds nothing, and it lasts that long only where the firmware does not answer. Past the bound the row is handed back with the disable unanswered: filed in round 14, not fixed. The interpreter stage's exit in the track is a proposal.SMI_CMDwritten from the boot processor, as Table 5.9 words it. The T14's took the enable from cpu7 twice. Filed, not fixed.uefi.org; the two HTML files are kept underorch/acpi1-r16/spec/with the hashes above.S5_TAKES' 2 s, which would turn a T14 whose S5 is slow but works into a panic.b3b9ccd69's press boot stalled in the power-off or lost its first press. The one deliberate press atff4945d6dfavours a lost first press, but that press's time was recorded only to the minute, so the press issue's exit stays unmet.machine_shutdown_short_stopruns on one CPU, where the shootdown answers itself. That the shootdown's answers from other CPUs end every Ring 3 run rests on reading (M10, above).ACPI_ENABLEwas written, so in legacy mode whatever served those events did it out of the OS's sight._Q4Fin the DSDT callsADBG("QUERY_METHOD_UCSI")and notifies\_SB.UBTC(the USB-C UCSI device), which ToyOS has no driver for, and no table defines_Q28. So the stage ruling's "nothing the firmware does in legacy mode today is lost" stands unverified for these two queries.9dcfa09bc:origin/main...9dcfa09bc+3667/−592 over 85 files;tests/,src/andtoyos-acpi/tests/+814/−100,issues/+395/−22, the rest (production) +2458/−470, unchanged by round 14. Before it, at922a6b7c7:origin/main...922a6b7c7+3624/−592 over 83 files;tests/,src/andtoyos-acpi/tests/+808/−100,issues/+358/−22, the rest (production) +2458/−470. Round 13 alone (47ac0ce19..922a6b7c7) is +284/−302 over 19 files: production +81/−39,tests/,src/andtoyos-acpi/tests/+32/−191,issues/+171/−72. Before it:origin/main...8d7004d3b+3591/−585 over 86 files;tests/,src/andtoyos-acpi/tests/+933/−102,issues/+242/−13, production unchanged by the merge. Before it,origin/main...cd5e11869+3583/−585 over 86 files. Of that,tests/,src/andtoyos-acpi/tests/are +925/−102,issues/+242/−13, and the rest (production) +2416/−470, the same as atff4945d6d. Round 12 alone (1d4fb9358..cd5e11869) iscounters_metal.rs+11/−24. Round 11 alone (464504f7d..89c331ac1) iscounters_metal.rs+101/−78: the retake goes, andLog,acpi_saidand the counts assertion come in. Round 10 alone (5c89f8954..464504f7d) was +66/−21. Earlier:origin/main...ff4945d6d+3497/−553 over 85 files; round 9 alone (a9bb64c7b..ff4945d6d) +43/−7 over 4 kernel files; round 8 alone (2746aef8..3e73ab0ae) +161/−65 over 14 files; round 7 alone (43f99ee95..5d0278a60) +196/−215.🤖 Generated with Claude Code