Files
RedBear-OS/local/docs/SLEEP-IMPLEMENTATION-PLAN.md
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

16 KiB

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:735acpi_s2idle_prepare
    • drivers/acpi/sleep.c:758acpi_s2idle_wake
    • drivers/acpi/sleep.c:821acpi_s2idle_restore
    • drivers/acpi/acpica/hwsleep.c:81-127acpi_hw_legacy_sleep
    • drivers/acpi/acpica/hwsleep.c:255-314acpi_hw_legacy_wake
    • kernel/power/suspend.c:91s2idle_enter
    • kernel/power/suspend.c:133s2idle_wake
    • arch/x86/kernel/acpi/sleep.c:38acpi_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