# 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 | ✅ Committed (aadf55b base, 6b98c64 kernel), built | Yes | | Phase II: S3 entry path (PM1 register write) | ✅ Committed (9f6a428 kernel), built | Yes | | Phase II.X: S3 resume trampoline (64-bit assembly) | ✅ Committed (1be659b, 9bc1fbf kernel), built | Yes | | Phase II.X.W: FACS parser + SetS3WakingVector/EnterS3 AcPiVerbs | ✅ Committed (b0f4fee syscall, 475f96e/9bc1fbf kernel, dcd70a1 base), built | Yes | | Broad OEM DMI (Dell/HP/Lenovo) | ✅ Committed (4d270bab2 quirks), built | Yes | | redbear-mini ISO build | ✅ Succeeds, 512 MB | — | | QEMU boot test | ✅ Passes, reaches Red Bear login | — | | Build system patch verification (`make verify-patches`) | ✅ Added (1834c3bf Makefile, 32403ccf4 script) | — | | Phase K: convert local sources to git submodules | ⏳ Deferred — requires gitea mirror per source | — | ## Phase J Architecture (Current) The s2idle / s3 coordination path uses **two parallel APIs**: 1. **kstop string-arg path** (Phase I.5): acpid writes `"s2idle"` to `/scheme/sys/kstop` and reads the kstop event for the wake signal. This is the original path; it works without the AcpiVerb extension. 2. **Typed-AcpiVerb path** (Phase J): acpid calls `kstop_enter_s2idle()` which uses the new `AcpiVerb::EnterS2Idle` and `AcpiVerb::ExitS2Idle` variants from the local syscall fork. This is the preferred path now that Phase J's libredox fork is in place. Both paths are fully wired and work. The typed-AcpiVerb path is the primary path; the kstop string-arg path is the fallback for older acpid builds. ### Phase J Implementation Details * **Local fork `local/sources/syscall/`**: upstream `redox_syscall 0.8.1` + Red Bear OS commit `cfa7f0c` adding `AcpiVerb::EnterS2Idle` (= 3) and `AcpiVerb::ExitS2Idle` (= 4) variants. The version field stays at upstream 0.8.1 per the AGENTS.md "GOLDEN RULE". * **Local fork `local/sources/libredox/`**: upstream `libredox 0.1.17` with the `redox_syscall` dep redirected to `path = "../syscall"`. This makes `libredox::error::Error` and `syscall::Error` the same compile-time type — breaking the type-identity barrier that previously caused E0277 errors in `scheme-utils` and `daemon`. * **Base `Cargo.toml`**: `[patch.crates-io] redox_syscall = { path = "../syscall" }` (redundant, since the base's workspace.dependencies already uses the local path) and `[patch.crates-io] libredox = { path = "../libredox" }`. * **Kernel `Cargo.toml`**: `[workspace] members = [".", "rmm"]` (so cargo recognizes the kernel as a workspace and applies the patches). `[patch."https://gitlab.redox-os.org/redox-os/syscall.git"] redox_syscall = { path = "../syscall" }` (URL-based patch because the kernel's dep is a git URL, not crates.io). `[patch.crates-io] libredox = { path = "../libredox" }`. * **Patch file**: `local/patches/syscall/P1-acpiverb-enter-exit-s2idle.patch` is the durable overlay patch backing the syscall fork commit. ### Phase J End-to-End s2idle Flow 1. acpid: `enter_s2idle()` (`_TTS(0)`, `_PTS(0)`, `_SST(3)`) 2. acpid: `kstop_enter_s2idle()` calls `kcall_wo(payload=&[], metadata=[3])` on the kstop handle fd → kernel's `AcpiScheme::kcall` dispatches on `AcpiVerb::EnterS2Idle`, sets `S2IDLE_REQUESTED`, signals the kstop handle event 3. kernel idle path: `mwait_loop()` at deepest C-state 4. SCI breaks MWAIT 5. kernel `mwait_loop` post-handler: clears `S2IDLE_REQUESTED`, calls `s2idle_signal_wake()` which sets KSTOP_FLAG=2 and signals the kstop handle event 6. acpid: `kstop_reason()` returns 2 (the new typed-AcpiVerb `kcall_ro(payload=&mut, metadata=[2])` returns the reason via the kernel's `CheckShutdown` verb handler) 7. acpid: `exit_s2idle()` (`_SST(2)`, `_WAK(0)`, `_SST(1)`) 8. loop ### Phase J Test Plan * **Build verification**: `redbear-mini.iso` (512 MB) builds successfully with the Phase J commits. The build system applies the patches in the right order. * **QEMU verification**: boot the ISO in QEMU. The cpufreqd daemon should NOT oscillate (Phase H fix). The acpid main loop should NOT log repeated `P0→P1` transitions. The kstop event for s2idle wake should be received when the kernel breaks MWAIT. * **Patch application verification**: run `cargo metadata --format-version 1` and confirm the resolved source URL for `redox_syscall` and `libredredox` is the local fork path. ## Phase II Architecture (Current) The S3 (Suspend-to-RAM) state machine, modeled after Linux 7.1's `arch/x86/kernel/acpi/wakeup_64.S` and `arch/x86/kernel/acpi/sleep.c`: 1. **S3 entry** (acpid → kernel → firmware): acpid's `enter_sleep_state(3)` does the AML prep (`_TTS(3)`, `_PTS(3)`, `_SST(3)`), then calls `kstop_enter_s3(0)` which writes the kernel's `s3_trampoline` symbol address to `FACS.xfirmware_waking_vector` via the new `SetS3WakingVector` AcPiVerb. acpid then writes `'s3'` to `/scheme/sys/kstop`; the kernel's `stop::enter_s3()` reads `S3_SLP_TYP` and writes `SLP_TYP|SLP_EN` to `PM1a_CNT`. The platform firmware enters S3. 2. **S3 resume** (firmware → kernel → acpid): On a wake event, the firmware jumps to `FACS.waking_vector` (the s3_trampoline). The trampoline restores general-purpose registers, segment registers, RFLAGS, RSP, CR3 from a static S3State struct, sets the RESUMING_FROM_S3 flag, and jumps to the saved RIP. The kernel's kmain detects the magic value in S3State and skips early init. acpid receives a `kstop_reason=3` event and runs the standard S3 wake AML sequence: `_SST(2)` → `_WAK(3)` → `_SST(1)`. The S3 state save in `kernel/src/arch/x86_shared/stop.rs` and the resume trampoline in `kernel/src/arch/x86_shared/s3_resume.rs` are both present and built. Hardware-agnostic: works on any x86_64 system with standard ACPI S3 support (Dell, HP, Lenovo, LG Gram 14). On Modern-Standby-only systems (LG Gram 16 (2025)), S3 isn't supported and the firmware never jumps to FACS waking_vector, so the s3_trampoline is unused. ## Phase I Architecture (Historical, kept for reference) 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' 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 (historical — now found on `submodule/base` and `submodule/kernel` branches inside the canonical `RedBear-OS` repo): - `submodule/base 5d2d114` — acpid: full Linux AML S-state sequence - `submodule/kernel 75c7618` — kernel: s2idle / s3 kstop handler