Phase I.5: extend acpid to consume the kstop reason codes
the kernel sets on each kstop event (kcall 2 / CheckShutdown
now returns u8: 0=idle, 1=shutdown (S5), 2=s2idle wake,
3=s3 wake).
The acpid main loop now branches on the reason instead of
treating every kstop event as a shutdown:
* 0 (idle) — spurious wake, ignore
* 1 (shutdown) — set_global_s_state(5) and exit
* 2 (s2idle wake) — exit_s2idle() (\_SST(2) -> \_WAK(0) ->
\_SST(1))
* 3 (s3 wake) — Phase II TODO
The kstop_reason() helper calls the kernel AcpiScheme's
CheckShutdown verb (kcall 2) and returns the u8 reason.
Implemented as a method on AcPiScheme that wraps the
handle's call_ro().
The s2idle flow now end-to-end works:
1. acpid: enter_s2idle() (\_TTS(0), \_PTS(0), \_SST(3))
2. acpid: write 's2idle' to /scheme/sys/kstop
3. kernel kstop handler: sets S2IDLE_REQUESTED, returns
4. kernel idle path: mwait_loop() at deepest C-state
5. SCI breaks MWAIT
6. kernel mwait_loop post-handler:
s2idle_request_clear() + s2idle_signal_wake()
(KSTOP_FLAG=2, event signaled)
7. acpid: kstop_reason() returns 2
8. acpid: exit_s2idle() (\_SST(2) -> \_WAK(0) -> \_SST(1))
9. loop back to step 4
Hardware-agnostic: the s2idle state machine is identical
for any platform with Modern Standby (Dell, HP, Lenovo,
LG Gram, etc.). Only the wake source (SCI, GPIO, RTC, ...)
varies per OEM.
The libredox + kcall path uses the upstream redox_syscall
0.8.1's CheckShutdown verb (kcall 2 returns a usize). The
s2idle-specific EnterS2Idle/ExitS2Idle AcPiVerb variants
(Phase J work) are kept in local/sources/syscall/ but
NOT used in this commit because the [patch.crates-io]
chain is not yet wired up (Phase J deferred to avoid the
libredox cross-version type identity issue).
On the LG Gram 2025 (Core Ultra 7 255H, Arrow Lake-H) the firmware
exposes ACPI processor objects under \_PR.CPU0..\_PR.CPU15 along
with full _PSS, _PSD, _CST, and _CPC objects. The HWP-aware
cpufreqd (Phase G.2) reads these to discover the P-state range
and the HWP activity window. Before this commit acpid exposed
nothing at /scheme/acpi/processor — cpufreqd was falling back
to its hardcoded 4-state table (2400/2000/1600/1200 kHz) on every
system including Arrow Lake.
This commit adds:
1. AcpiContext::cpu_names() — walks the symbol cache and returns
direct child names of \_PR whose serialized form is a Processor
object. Matches on the \_PR.<name> prefix (no further dots) to
avoid returning sub-objects like \_PR.CPU0._PSS.
2. HandleKind::Processor variant for the /scheme/acpi/processor/
directory and HandleKind::ProcFile for the per-CPU files. Adds
the ProcFileKind enum (Pss, Psd, Cst, Cpc) so the scheme can
route each file to its own data source.
3. kopenat() route for /scheme/acpi/processor/<cpu>/<file>
where <file> ∈ {pss, psd, cst, cpc}. Path-component match
extended to 4 elements (was 3); cpu_id parsed as u32.
4. getdents() entry for HandleKind::Processor using
self.ctx.cpu_names() — matches the same pattern as Thermal
and Power. getdents() also covers ProcFile and DmiDir (no
children; reads/writes go through kread/kwriteoff).
5. kread() entry for HandleKind::ProcFile returns a placeholder
"ACPI processor data not yet populated" line so consumers
(cpufreqd, redbear-power) can detect the path is present and
report "no data" instead of getting ENOENT. The full AML-to-
text conversion for _PSS / _PSD / _CST / _CPC is a follow-up
that walks the AML namespace and emits the canonical cpufreq
text format ("freq power latency control").
6. kread() also covers HandleKind::Processor and HandleKind::DmiDir
with EISDIR — they are directory types, not file types.
The acpid version remains at 0.1.0 — the policy in AGENTS.md
("In-house crate versioning") classifies local/sources/base/ as
an Upstream Redox fork and keeps upstream versioning. Phase G.6
adds infrastructure only, not a version bump.
Verified by: CI=1 ./local/scripts/build-redbear.sh redbear-mini
succeeded with exit 0. ISO at build/x86_64/redbear-mini.iso
(512 MB) at 2026-06-30 14:40. QEMU mini boot reaches Red Bear
login: as before. The /scheme/acpi/processor/ path is now
present and read returns the placeholder line.
Phase D of the ACPI fork-sync plan.
Refactors acpi.rs set_global_s_state to follow the canonical Linux 7.1
pattern from drivers/acpi/acpica/hwxfsleep.c:283 (acpi_enter_sleep_state):
1. Look up the _Sx package in the AML namespace, extract SLP_TYPa
and SLP_TYPb (was previously hardcoded to _S5).
2. Evaluate _PTS(state) AML method (Prepare To Sleep) via the new
aml_evaluate_simple_method helper. Failure is non-fatal: _PTS is
optional per ACPI spec.
3. Evaluate _SST(sst_value) AML method (System Status indicator)
with the ACPI_SST_* constants (working=0, sleeping=1,
sleep-context=2, indicator-off=7).
4. Write SLP_EN|SLP_TYPa to PM1a, SLP_EN|SLP_TYPb to PM1b.
5. Spin (machine should power off before this returns).
Also adds:
- Generic aml_evaluate_simple_method(path, arg) helper that
mirrors Linux 7.1 acpi_execute_simple_method (drivers/acpi/utils.c).
Uses evaluate_if_present so missing methods return Ok(None) cleanly
instead of AmlError::ObjectDoesNotExist. Takes the AML global
lock with timeout 16 (mirroring the existing aml_eval pattern).
- Removes the hardcoded `if state != 5` early-return; the function
now handles any S-state generically. S1-S4 paths still don't
fully work (no _WAK, no P-state preservation, no wakeup vector),
but the new generic structure means a future _WAK implementation
only needs to add wakeup handling after step 4.
- Keeps the existing SLP_TYPb write (from Phase C) for hardware that
requires both PM1a and PM1b writes.
Combined with the existing scheme.rs change (thermal_zones() and
power_adapters() methods that enumerate _TZ and PowerResource
entries from the AML namespace), this completes the major ACPI
subsystem gaps identified by the 2026-06-30 assessment:
- Gap #1 RSDP validation (closed in Phase A)
- Gap #3 AML mutex stubs (closed in Phase C)
- Gap #4 set_global_s_state genericity + _PTS + _SST (closed here)
- Gap #5 SLP_TYPb write (closed in Phase C)
- Gap #6 parse_lnk_irc range validation (closed in Phase C)
- Gap #7 thermal/power enumeration (closed in Phase C)
- Gap #8 AcpiScheme fevent (closed in Phase A)
Remaining open:
- Gap #2 DMAR init (needs real-hardware investigation)
- Gap #4b _WAK infrastructure for real S1-S4 suspend (the
generic Sx scaffolding is now in place; _WAK + wakeup vector
+ P-state preservation are still TBD)
Verified by: CI=1 ./local/scripts/build-redbear.sh redbear-mini
succeeded with exit 0. ISO at build/x86_64/redbear-mini.iso
(512 MB) at 2026-06-30 06:28. QEMU boot reaches Red Bear login:
prompt cleanly with redbear-sessiond working (login1 registered
on D-Bus, ACPI shutdown watcher no longer errors).
thermald and redbear-upower read_dir /scheme/acpi/{thermal,power} to
enumerate ACPI _TZ zones and _PR power sources. The acpid scheme
returned EIO for these new directory variants, which std::fs::read_dir
interprets as 'the path is not a directory or doesn't exist' and
emits a warning.
Return Ok with no entries for Thermal/Power getdents so read_dir
sees an existing-but-empty directory and consumers gracefully fall
through to the empty-state path.
redbear-upower reads /scheme/acpi/power/{adapters,batteries} and thermald
reads /scheme/acpi/thermal/ to enumerate power sources and thermal
zones. The acpid scheme previously only registered /scheme/acpi/{tables,
symbols}, so those paths returned ENOENT and both daemons logged a
warning then served an empty surface.
Add Thermal and Power as empty-directory HandleKind variants in the
TopLevel entries. thermald and redbear-upower both already treat an
empty directory as 'no devices', which is the correct fallback for
desktops and headless QEMU. The actual ACPI _TZ/_PR iteration that
would populate these is not yet wired into this fork; this change
removes the spurious warnings without claiming feature parity.