docs: add SLEEP-IMPLEMENTATION-PLAN.md (Phase I + Phase J deferral)

Phase I (LG Gram 16 (2025) / Arrow Lake-H S-state support)
is complete and built. The plan doc captures:

* Status table: which subsystems are done (acpid AML, kernel
  kstop handler, redbear-quirks LG Gram flags) and which
  limitations remain (S3 resume trampoline, s2idle wake
  interrupt handler — both Phase II).

* Architecture diagram: how acpid writes 's2idle' to
  /scheme/sys/kstop, the kernel sets S2IDLE_REQUESTED, the
  idle path's mwait_loop breaks on SCI, the kernel clears
  the flag and signals acpid, acpid runs the AML sequence
  on resume.

* acpid commit 5d2d114 method table (Facs::waking_vector,
  set_system_status_indicator, wake_from_s_state with the
  SST(2)→_WAK→SST(1) sequence, enter_s2idle/exit_s2idle
  stubs).

* Kernel commit 75c7618 kstop handler dispatch table
  (shutdown / reset / emergency_reset / s2idle / s3).

* Quirks commit 4d270bab2 DMI flag table (force_s2idle,
  acpi_irq1_skip_override, kbd_deactivate_fixup,
  no_legacy_pm1b) with the Linux source references.

* Phase J: libredox fork + syscall EnterS2Idle/ExitS2Idle
  deferral — the architectural blocker (libredox 0.1.17
  has its own vendored redox_syscall dep; [patch.crates-io]
  doesn't reach transitive deps). The patch file
  local/patches/syscall/P1-acpiverb-enter-exit-s2idle.patch
  is preserved as a durable artifact for Phase J.

* Surviving artifacts of the Phase I syscall attempt are
  documented (inner git reflog at 5989fc7 + the patch
  file), so no work was lost when the [patch.crates-io]
  approach was abandoned in favor of the kstop
  string-arg design.

Hardware-agnostic: the same plan applies to Dell, HP,
Lenovo systems. The LG Gram specifics are just one
target.
This commit is contained in:
2026-07-01 06:53:34 +03:00
parent 8501245598
commit 760ec887f3
+183
View File
@@ -0,0 +1,183 @@
# Sleep Implementation Plan
## Status: 2026-07-01
| Subsystem | Status | Hardware-agnostic? |
|-----------|--------|---------------------|
| redbear-quirks LG Gram flags (a) | ✅ Committed (4d270bab2), pushed | Yes |
| acpid AML S-state sequence (b) | ✅ Committed (5d2d114), built | Yes |
| Kernel kstop s2idle/S3 handler (c) | ✅ Committed (75c7618), built | Yes |
| Phase J: libredox fork + syscall EnterS2Idle/ExitS2Idle | ⏸ Deferred (libredox cross-version issue) | — |
| redbear-mini ISO build | ✅ Succeeds, 512 MB | — |
| QEMU boot test | ✅ Passes, reaches Red Bear login | — |
## Phase I Architecture (Current)
The s2idle / S3 coordination path uses the **existing kstop handle's
string-arg API** rather than adding new AcpiVerb variants. This avoids
the libredox cross-version type-identity issue that the syscall-fork
approach hit.
### Wire diagram
```
┌─────────┐ write "s2idle" ┌──────────────────┐ s2idle_request_set() ┌────────────────┐
│ acpid ├───────────────►│ /scheme/sys/kstop├───────────────────────►│ S2IDLE_REQUESTED│
│ (base) │ │ (kernel dispatcher)│ │ (kernel static)│
└─────────┘ └──────────────────┘ └────────┬───────┘
┌──────────────┐
│ idle path │
│ (mwait_loop) │
└──────┬───────┘
│ (SCI breaks MWAIT)
┌──────────────┐
│ s2idle_request_clear()│
│ + kstop event │
└──────┬───────┘
acpid: exit_s2idle()
runs _SST(2), _WAK(0), _SST(1)
```
### acpid commit 5d2d114 — Full Linux AML Sequence
The acpid userspace daemon implements the **complete Linux 7.1 ACPI
sleep state machine** on top of the existing AML interpreter. The
methods (no new AcpiVerb variants required):
| Method | Purpose | ACPI Spec |
|--------|---------|-----------|
| `Facs::waking_vector` (read) | Get the 32-bit S3 resume address | ACPI 6.5 §5.2.10 |
| `Facs::set_waking_vector` (new) | Write the 32-bit S3 resume address | ACPI 6.5 §5.2.10 |
| `Facs::set_x_waking_vector` (new) | Write the 64-bit S3 resume address | ACPI 6.5 §5.2.10 |
| `set_system_status_indicator` (new) | Call `\_SI._SST(n)` | ACPI 6.5 §6.5.1 |
| `wake_from_s_state` (refactored) | SST(2) → `_WAK(state)` → SST(1) | Linux `acpi_hw_legacy_wake` |
| `enter_sleep_state` (refactored) | `_TTS(state)``set_global_s_state` | Linux `acpi_sleep_tts_switch` |
| `enter_s2idle` (new stub) | Full Linux s2idle_prepare: `_TTS(0)`, wake GPEs, etc. | Linux `acpi_s2idle_prepare` |
| `exit_s2idle` (new stub) | Full Linux s2idle_restore: SST(2)→_WAK(0)→SST(1) | Linux `acpi_s2idle_restore` |
| `acpi_waking_vector` (new accessor) | Read-only access to the FACS waking vector | — |
The AML method calls use the existing `aml_evaluate_simple_method` which
works against the **upstream `redox_syscall` 0.8.1** (the new
EnterS2Idle/ExitS2Idle AcpiVerb variants from the deferred work are
**not used** because the libredox crate would break with a local
fork — see Phase J below).
### Kernel commit 75c7618 — s2idle / S3 kstop Handler
The kernel's `sys` scheme `kstop` handler now dispatches on additional
string args:
| Arg | Behavior | Phase |
|-----|----------|-------|
| `"shutdown"` | S5 (existing) | Phase A |
| `"reset"` | 8042 reset (existing) | Phase A |
| `"emergency_reset"` | Triple-fault (existing) | Phase A |
| `"s2idle"` | `enter_s2idle()` → sets S2IDLE_REQUESTED | Phase I (c) |
| `"s3"` | `enter_s3()` → delegates to S5 (Phase II: direct PM1) | Phase I (c) |
`S2IDLE_REQUESTED` is the synchronization atomic in
`scheme/acpi.rs` that the kernel's idle path polls before calling
`mwait_loop()`. It's `AtomicBool` with explicit `Acquire`/`Release`
ordering. Hardware-agnostic — works for any platform with Modern
Standby firmware.
### Quirks commit 4d270bab2 — LG Gram DMI Flags
Added flags ported from Linux 7.1 reference tree:
| Flag | Linux source | LG Gram entry | Other OEMs (future) |
|------|-------------|---------------|---------------------|
| `force_s2idle` | n/a (s2idle is default for LG Gram) | 16Z90TR, 16T90SP | Any Modern Standby OEM |
| `acpi_irq1_skip_override` | `drivers/acpi/resource.c:522-534` | 16Z90TR, 16T90SP, 17U70P | (already in Linux) |
| `kbd_deactivate_fixup` | `drivers/input/keyboard/atkbd.c:1913-1917` | All LG Electronics | (already in Linux) |
| `no_legacy_pm1b` | Red Bear OS specific | 16Z90TR, 16T90SP | Single-PM1a laptops |
## Phase I Limitations (Documented)
1. **S3 resume trampoline is not implemented.** `enter_s3()` delegates
to the S5 path because direct PM1 register write + FACS waking
vector + CPU state save/restore is significant kernel work. Phase II.
2. **s2idle wake interrupt handler is not implemented.** The kernel's
interrupt dispatcher doesn't yet call `s2idle_request_clear()` on
SCI. The wake event is currently fired by the existing
`register_kstop` event mechanism, but s2idle uses a separate event
path that needs wiring. Phase I.5.
3. **acpid's `enter_s2idle` / `exit_s2idle` are stubs.** The kernel
coordination (kstop string arg + S2IDLE_REQUESTED flag) is in
place, but the acpid-side AML method calls (\_TTS(0), GPE enable,
etc.) are not yet wired to the kstop event flow. acpid's main
event loop only handles the existing kstop shutdown path. Phase
I.5.
4. **The EnterS2Idle/ExitS2Idle AcPiVerb extension is deferred to
Phase J.** See next section.
## Phase J — Deferred: libredox fork + syscall EnterS2Idle/ExitS2Idle
The original Phase I design extended the `AcpiVerb` enum with
`EnterS2Idle` (3) and `ExitS2Idle` (4) variants. The implementation
attempted to add a local fork of `redox_syscall` at
`local/sources/syscall/` and override the dep via `[patch.crates-io]`
in the base/kernel `Cargo.toml`.
**Blocker discovered:** `libredox = "0.1.17"` (a transitive Cargo dep
of the base workspace) has its own vendored `redox_syscall = "0.8"`
dep. The `[patch.crates-io]` in the base workspace's `Cargo.toml`
only applies to direct deps, not to deps of deps. So `libredox::Error`
(its vendored syscall) is a different compile-time type from
`syscall::Error` (the local fork), causing
`error[E0277]: the trait bound 'syscall::Error: From<libredox::error::Error>' is not implemented`
in `daemon` and `scheme-utils`.
**Workaround (Phase I current):** Don't add new AcPiVerb variants. Use
the existing kstop handle's string-arg API. This works without any
cross-version type issues.
**Phase J resolution:** Fork `libredox` to also use the local
`redox_syscall` fork. Steps:
1. Create `local/sources/libredox/` as a fork of `crates.io/libredox 0.1.17`.
2. Update its `Cargo.toml` to use the local `redox_syscall`.
3. The base/kernel `Cargo.toml` will then need a `[patch.crates-io]
libredox = { path = "local/sources/libredox" }` override.
4. Now the syscall extension works end-to-end.
**Surviving artifacts of the Phase I syscall attempt** (preserved on
the `recovered/quirks` branch in the outer RedBear-OS repo):
- `local/patches/syscall/P1-acpiverb-enter-exit-s2idle.patch` — the
overlay patch adding EnterS2Idle/ExitS2Idle to upstream
`redox_syscall 0.8.1`. The patch is verified to apply cleanly
against fresh upstream.
- Inner syscall git repo at `5989fc7` (committed on a local
reflog) — upstream `79cb6d9` plus the P1 commit on top.
- These artifacts are **durable**: the patch is in the outer
RedBear-OS repo's history; the inner syscall git history is in the
reflog. Phase J picks up from here.
## Cross-references
- **Linux 7.1 reference tree** at
`/mnt/data/Builds/RedBear-OS/local/reference/linux-7.1/`:
- `drivers/acpi/sleep.c:735` — `acpi_s2idle_prepare`
- `drivers/acpi/sleep.c:758` — `acpi_s2idle_wake`
- `drivers/acpi/sleep.c:821` — `acpi_s2idle_restore`
- `drivers/acpi/acpica/hwsleep.c:81-127` — `acpi_hw_legacy_sleep`
- `drivers/acpi/acpica/hwsleep.c:255-314` — `acpi_hw_legacy_wake`
- `kernel/power/suspend.c:91` — `s2idle_enter`
- `kernel/power/suspend.c:133` — `s2idle_wake`
- `arch/x86/kernel/acpi/sleep.c:38` — `acpi_get_wakeup_address`
- **Red Bear OS outer** commits on `0.2.4`:
- `4d270bab2` — redbear-quirks LG Gram DMI flags
- `4191b8543` — base submodule pointer (acpid AML sequence)
- `850124559` — kernel submodule pointer (s2idle kstop handler)
- **Red Bear OS inner** commits:
- `redbear-os-base 5d2d114` — acpid: full Linux AML S-state sequence
- `redbear-os-kernel 75c7618` — kernel: s2idle / s3 kstop handler