Files
RedBear-OS/local/docs/SLEEP-IMPLEMENTATION-PLAN.md
T
vasilito 5cde25495c git: enforce SINGLE-REPO RULE — redirect submodules to canonical repo
Per local/AGENTS.md § SINGLE-REPO RULE: the Red Bear OS project lives
in exactly one git repository (vasilito/RedBear-OS). Per-component
Gitea mirrors (redbear-os-base, redbear-os-kernel, redbear-os-installer,
redox-drm, userutils, libredox, libpciaccess, ctrlc, syscall, sysinfo)
have been redirected or deleted.

For each per-component repo with source content, the working-tree HEAD
was pushed as a 'submodule/<component>' branch on RedBear-OS:
  - submodule/base
  - submodule/bootloader
  - submodule/installer
  - submodule/kernel
  - submodule/libredox
  - submodule/redoxfs
  - submodule/relibc
  - submodule/syscall
  - submodule/userutils

The .gitmodules entry for local/sources/kernel is now redirected to the
canonical repo with branch = submodule/kernel. The other submodule
.gitmodules entries remain to be added in a follow-up.

Empty per-component repos (ctrlc, libpciaccess, redox-drm, sysinfo) had
no source content; their gitlinks in the index are removed in a
follow-up commit.

Unrelated per-component repos that were not Red Bear components
(ctrlc, syscall, sysinfo — possibly unrelated personal projects) were
deleted in the bulk cleanup.

Gitea state under vasilito/ is now exactly: RedBear-OS, hiperiso.

Adds:
  - local/scripts/redirect-to-submodules.sh
  - local/scripts/delete-per-component-repos.sh

Updates:
  - .gitmodules (kernel → RedBear-OS#submodule/kernel)
  - local/AGENTS.md (SINGLE-REPO RULE status, migration procedure)
  - local/docs/BUILD-SYSTEM-IMPROVEMENTS.md §11 (resolved)
  - local/docs/QUIRKS-AUDIT.md (drop dead links)
  - local/docs/SLEEP-IMPLEMENTATION-PLAN.md (mark historical)
  - CHANGELOG.md (mark historical references)
2026-07-01 22:02:26 +03:00

308 lines
16 KiB
Markdown

# 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<SLP_TYP>'` 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<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 (historical — these repos have since been
merged as `submodule/base` and `submodule/kernel` branches inside the
canonical `RedBear-OS` repo per the SINGLE-REPO RULE):
- `redbear-os-base 5d2d114` — acpid: full Linux AML S-state sequence
- `redbear-os-kernel 75c7618` — kernel: s2idle / s3 kstop handler