136 lines
7.8 KiB
Markdown
136 lines
7.8 KiB
Markdown
# M21 execution plan: ACPI events + system power
|
|
|
|
The operational plan for the event side of the acpi service and orderly
|
|
shutdown — the capstone [m19-m20-plan.md](m19-m20-plan.md) previewed. Same
|
|
rules as its predecessors: one phase at a time, each green before the next;
|
|
this file is the build order and the checklist.
|
|
|
|
**Definition of green, every phase:** `zig build` clean, `zig build test`
|
|
clean, `python3 test/qemu_test.py` passes (existing scenarios plus the
|
|
phase's new one), and the relevant design doc updated. Commit per green phase
|
|
(no co-author trailers). Failing cases preserve their serial logs
|
|
(`<case>-failed-serial.log`).
|
|
|
|
**Workflow:** dedicated worktree; branch `feat/power-events` off `main`;
|
|
auto-merge to main when the branch is green; keep the branch; push everything.
|
|
|
|
## Settled decisions (2026-07-13, approved)
|
|
|
|
1. **S5 is executed by the acpi service from ring 3.** No new syscall: the
|
|
broad port grant (M20 decision 5) already made this physically possible —
|
|
the service holds the PM1 control ports in its io grant and derives `_S5`
|
|
from its own namespace (`aml.sleepState`). Formalizing it adds no
|
|
authority. The kernel keeps `power.zig` for its own test paths and
|
|
panic-time use.
|
|
2. **The power surface is domain-named** (decision 7 of the last plan): a
|
|
`power-protocol` module + `ServiceId.power = 5`, registered by the acpi
|
|
service — on ARM, a PSCI/mailbox service registers the same id and
|
|
subscribers never know the difference. Messages: `subscribe` (endpoint as
|
|
the call's capability, the input/manager pattern), `shutdown` (accepted
|
|
only from PID 1 — init), and events published as buffered messages:
|
|
`power_button`, `lid`, `ac`, `battery`, generic `notify` with a code.
|
|
3. **The service learns event ports from its own FADT copy**: the kernel adds
|
|
the FADT as one more memory resource on the acpi-tables node; the service
|
|
tells it apart from the AML blobs by signature ("FACP" header — the blob
|
|
resources are header-stripped bytecode and start with no signature). The
|
|
kernel's own FADT parse is untouched.
|
|
4. **The acpi service converts to the harness** (`runtime.service.run`):
|
|
protocol messages (subscribe/shutdown), the SCI notification, and the
|
|
existing report flow fold into one loop — the shape it was always meant
|
|
to have.
|
|
5. **GPE/Notify correctness is proven by host unit tests** (synthetic AML
|
|
with a Notify inside a method body; aml.zig joins the `zig build test`
|
|
loop). The QEMU scenario proves the power button — a *fixed* event,
|
|
deterministically injectable via QMP `system_powerdown` — because QEMU
|
|
cannot raise GPEs deterministically on this config. Battery/AC/lid and the
|
|
embedded controller (`_Qxx`) are interface-complete here and validated on
|
|
real hardware (the laptop) later.
|
|
|
|
## Ground truth the phases build on (verified 2026-07-13)
|
|
|
|
- `system/devices/power.zig` `shutdown()` is the kernel's S5 write
|
|
(SLP_TYP|SLP_EN to PM1a/PM1b control); there is no power syscall.
|
|
- init (`system/services/init/init.zig`) spawns vfs/input/device-manager
|
|
fire-and-forget — no child ids kept, no signals, no event loop. The whole
|
|
stop toolkit exists in `runtime.process` (stop/sendSignal/bindSignals).
|
|
- `test/qemu_test.py` has no QMP channel (serial is a one-way file).
|
|
- The kernel parses PM1 *control* blocks and SCI_INT from the FADT; the PM1
|
|
**event** blocks (offsets 56/60, len at 88) and **GPE0/GPE1** blocks
|
|
(offsets 80/84, lens 92/93) are unparsed — the service reads them from its
|
|
FADT copy (decision 3).
|
|
- The acpi-tables node carries the SCI as its only `len == 1` irq resource
|
|
(the broad window is len 256) — that is how the service finds it to
|
|
`irqBind`.
|
|
- `notify_opcode = 0x86` exists in `system/devices/aml/opcodes.zig` but the
|
|
interpreter never handles it — a GPE `_Lxx` body containing Notify fails
|
|
evaluation today. Everything else a GPE handler needs (field access,
|
|
control flow, method calls) is proven by the ring-3 `_STA`/`_CRS` work.
|
|
- The dead-code sweep (spawned task) also edits `system/devices/acpi.zig`;
|
|
M21.0 checks whether it landed and rebases before touching that file.
|
|
|
|
## Status
|
|
|
|
- [ ] **M21.0** — baseline: rebase over anything newly merged (the dead-code
|
|
sweep touches acpi.zig); cut `feat/power-events`; add the QMP channel to
|
|
the harness (`-qmp unix:.../qmp.sock,server,nowait`, a small client with
|
|
the `qmp_capabilities` handshake, a per-case `qmp_after` hook that sends
|
|
a command N seconds after boot); existing suite stays green.
|
|
- [ ] **M21.1** — SCI + the power button: kernel appends the FADT as an
|
|
acpi-tables memory resource; new `power-protocol` module +
|
|
`ServiceId.power`; the acpi service converts to the harness, registers
|
|
`.power`, parses the event/GPE blocks from its FADT copy, enables ACPI
|
|
mode if needed (SMI dance, spin on SCI_EN), binds the SCI, sets
|
|
PWRBTN_EN; on SCI reads/clears PM1_STS and publishes `power_button`
|
|
(log: `power: button pressed`), always irqAck. Scenario `power-button`:
|
|
`qmp_after system_powerdown` → expect the log line.
|
|
- [ ] **M21.2** — Notify + GPE dispatch: interpreter handles `notify_opcode`
|
|
into a bounded queue drained after evaluate(); on GPE status bits the
|
|
service evaluates `\_GPE._Lxx`/`_Exx`, maps notified nodes to events
|
|
(PNP0C0A→battery, ACPI0003→ac, PNP0C0D→lid, else generic), clears
|
|
GPE_STS, acks. EC `_Qxx` explicitly out (hardware track). Host unit
|
|
tests for Notify in aml.zig; aml.zig joins the `zig build test` loop.
|
|
- [ ] **M21.3** — orderly shutdown: init keeps child ids (spawnSupervised +
|
|
exit endpoint), binds signals, subscribes to `.power`; on `power_button`
|
|
logs `init: shutting down`, runs `stop(child, 2000, endpoint)` in
|
|
reverse spawn order, then sends `shutdown` to `.power`; the acpi service
|
|
(sender PID 1 only) logs `power: entering S5` and writes SLP_TYP|SLP_EN
|
|
from ring 3. Scenario `orderly-shutdown`: boot via init, `qmp_after
|
|
system_powerdown`, ordered regex button→shutting-down→entering-S5, pass
|
|
on QEMU exit. Docs + memory updated.
|
|
- [ ] **merge** `feat/power-events` → main, push, keep the branch — **loop
|
|
ends here**.
|
|
|
|
---
|
|
|
|
## Phase notes
|
|
|
|
**M21.0 QMP:** open the unix socket after Popen, complete the
|
|
`qmp_capabilities` handshake, then send the hook's command (for these
|
|
scenarios: `{"execute": "system_powerdown"}`). The socket is additive — no
|
|
existing case may notice it. Note e3fe3f3 recently reworked how the harness
|
|
boots; adapt to its current shape rather than the pre-rework description.
|
|
|
|
**M21.1 SCI details:** PM1_STS is at the event block base (write-1-to-clear);
|
|
PM1_EN at base + block_len/2; PWRBTN bit is 8 in both. If PM1b exists, mirror
|
|
reads/writes to both blocks. Enable ACPI mode only when SCI_EN (PM1 control
|
|
bit 0) is clear — OVMF boots may already have it set. The publish path reuses
|
|
the manager's subscriber table pattern (bounded, drop-on-failed-send).
|
|
|
|
**M21.2 GPE walk:** GPE0_STS bytes live at the GPE0 block base, GPE0_EN in
|
|
the block's upper half; for a set+enabled bit n, the handler method is
|
|
`_L%02X` (level) or `_E%02X` (edge) under `\_GPE`. Evaluate, drain the notify
|
|
queue, clear the status bit, ack. A missing handler method is clear-and-log,
|
|
not an error.
|
|
|
|
**M21.3 ordering:** init subscribes with retries — the acpi service registers
|
|
`.power` well after init starts. The stop sequence runs vfs last (other
|
|
services may flush through it). The S5 write mirrors `power.zig`'s
|
|
`sleepValue` (SLP_TYP bits [12:10], SLP_EN bit 13); if the write returns, log
|
|
`power: S5 write did not take` so the scenario fails loudly instead of
|
|
hanging.
|
|
|
|
**Explicitly out of scope:** the embedded controller and `_Qxx` queries,
|
|
battery `_BST`/`_BIF` evaluation beyond the interface stubs, lid/AC on QEMU
|
|
(no emulation), reboot over the power protocol, S3 sleep, per-device D-states
|
|
(a future lifecycle-vocabulary extension), thermal zones.
|