Suspend-to-RAM S3 Deep
Suspend-to-RAM (S3, exposed to userspace as
deepin/sys/power/mem_sleepand reached by writingmeminto/sys/power/state) is the firmware-assisted system-sleep state in which everything except main memory is powered down and DRAM is held in self-refresh so its contents survive. Unlike suspend-to-idle, which never leaves the kernel, S3 hands control to the platform firmware: after the kernel suspends all devices, takes every non-boot CPU offline, and callssuspend_ops->enter(PM_SUSPEND_MEM), the firmware physically gates the System-on-Chip (SoC) power, and the machine resumes only when firmware wakes and jumps back to a kernel resume vector. On ACPI x86 this maps to ACPI sleep state S3 throughdrivers/acpi/sleep.c. S3 yields “significant energy savings” but greater resume latency than shallower states (sleep-states.rst, v6.12). Critically, on much modern x86 hardware S3 is increasingly absent — OEMs targeting Microsoft “Modern Standby” ship firmware that omits a working S3 entirely, sodeepnever appears. This note traces the real path inkernel/power/suspend.canddrivers/acpi/sleep.cat Linux 6.12 LTS.
Mental Model
Think of S3 as a baton pass. The OS does all the software work to make the machine quiescent — freeze userspace, quiesce devices, collapse onto one CPU, stop timekeeping — and then, at the very last instruction, executes the firmware’s “enter S3” routine and stops being in control. The CPU stops fetching instructions; the SoC voltage rails collapse; only the DRAM controller keeps the memory chips ticking in self-refresh (a low-power mode where the DRAM periodically refreshes its own cells without the memory controller’s involvement, retaining contents at a fraction of active power). The kernel’s entire state — every page, every register saved to RAM — sits frozen in those self-refreshing chips. When a wakeup event fires, firmware is what runs first: it re-initializes the SoC and chipset, then jumps to a resume entry point the kernel registered before sleeping, and the kernel reverses everything.
This is the opposite division of labour from s2idle. In s2idle the OS is the sleep mechanism (it runs an idle loop and the CPUs are technically still alive). In S3 the OS prepares for sleep and firmware performs it. Two consequences follow directly: (1) S3 can reach a much deeper power state because firmware can power down rails the OS cannot touch — but (2) S3 depends utterly on the firmware doing its part correctly, and resume costs a full firmware re-initialization, so it is slower to wake.
flowchart TB ENTER["echo deep > /sys/power/mem_sleep<br/>echo mem > /sys/power/state"] --> FREEZE["Freeze userspace + suspend devices<br/>(prepare/suspend/late/noirq)"] FREEZE --> OFFLINE["pm_sleep_disable_secondary_cpus():<br/>take all CPUs offline except boot CPU"] OFFLINE --> IRQ["arch_suspend_disable_irqs()<br/>syscore_suspend()<br/>(stop timekeeping, save core state)"] IRQ --> ENTEROP["suspend_ops->enter(PM_SUSPEND_MEM)<br/>= acpi_suspend_enter() → acpi_suspend_lowlevel()"] ENTEROP --> FW["FIRMWARE owns the machine:<br/>SoC powered off, DRAM in self-refresh"] FW -. "wakeup event" .-> RESUMEFW["Firmware re-inits SoC,<br/>jumps to kernel resume vector"] RESUMEFW --> RESUME["syscore_resume(), online CPUs,<br/>resume devices, thaw userspace"]
Figure: the S3 suspend/resume baton pass. What it shows: the kernel does the software quiescing (freeze, suspend devices, offline secondary CPUs, stop timekeeping), then suspend_ops->enter() hands control to firmware, which powers the SoC down with only DRAM self-refreshing; a wakeup makes firmware re-init and re-enter the kernel. The insight to take: the dashed firmware arc is where the machine is genuinely off — everything left of it is kernel prep, everything right is kernel resume; the deep power saving and the resume latency both come from that firmware round-trip, which s2idle never makes.
Mechanical Walk-through
Selecting and validating S3
S3 is PM_SUSPEND_MEM, value 3 in enum suspend_states (suspend.h, v6.12). It is reachable through the mem string in /sys/power/state, but which variant mem means is chosen via /sys/power/mem_sleep: writing deep binds mem to PM_SUSPEND_MEM, shallow to standby (S1), and s2idle to suspend-to-idle. The label table in kernel/power/suspend.c makes this explicit (v6.12):
static const char * const mem_sleep_labels[] = {
[PM_SUSPEND_TO_IDLE] = "s2idle",
[PM_SUSPEND_STANDBY] = "shallow",
[PM_SUSPEND_MEM] = "deep",
};Unlike s2idle, S3 is not unconditionally available. The kernel exposes it only if the platform registers support. valid_state() gates it:
static bool valid_state(suspend_state_t state)
{
return suspend_ops && suspend_ops->valid && suspend_ops->valid(state) &&
suspend_ops->enter;
}So deep appears in mem_sleep_states[] only when a platform driver has called suspend_set_ops() with a ->valid() callback that returns true for PM_SUSPEND_MEM. If no firmware advertises S3, valid_state(PM_SUSPEND_MEM) is false, deep is never added, and the whole S3 path is unreachable — this is the mechanism behind “deep is missing on my laptop.” Contrast sleep_state_supported(), which special-cases s2idle as always-true: state == PM_SUSPEND_TO_IDLE || (valid_state(state) && !cxl_mem_active()) (suspend.c:251, v6.12). (The cxl_mem_active() guard refuses S3 while Compute Express Link memory is active, since losing power to CXL-attached memory would corrupt it.)
The ACPI platform ops
On ACPI x86, the platform driver is drivers/acpi/sleep.c. At boot, acpi_sleep_suspend_setup() walks ACPI states S1..S3, marks each acpi_sleep_state_supported(i) as available in sleep_states[], and — if any are supported — registers acpi_suspend_ops via suspend_set_ops() (sleep.c:856, v6.12):
static const struct platform_suspend_ops acpi_suspend_ops = {
.valid = acpi_suspend_state_valid,
.begin = acpi_suspend_begin,
.prepare_late = acpi_pm_prepare,
.enter = acpi_suspend_enter,
.wake = acpi_pm_finish,
.end = acpi_pm_end,
};The acpi_suspend_states[] table maps Linux PM states to ACPI sleep numbers — PM_SUSPEND_MEM → ACPI_STATE_S3 (sleep.c:550). acpi_suspend_begin() checks that the target ACPI state is actually supported (sleep_states[acpi_state]) and, for anything deeper than S1, calls pm_set_suspend_via_firmware() to record that firmware will own the transition.
Entering S3 — the kernel side of the baton pass
The generic suspend_enter() (suspend.c:403, v6.12) runs the device phases (dpm_suspend_late(), dpm_suspend_noirq()), and then — because the state is not PM_SUSPEND_TO_IDLE — takes the S3 branch rather than the s2idle_loop():
error = pm_sleep_disable_secondary_cpus(); /* offline all but boot CPU */
...
arch_suspend_disable_irqs(); /* local_irq_disable() */
BUG_ON(!irqs_disabled());
system_state = SYSTEM_SUSPEND;
error = syscore_suspend(); /* stop timekeeping, save core */
if (!error) {
*wakeup = pm_wakeup_pending();
if (!(suspend_test(TEST_CORE) || *wakeup)) {
error = suspend_ops->enter(state); /* HAND OFF TO FIRMWARE */
}
syscore_resume();
}
system_state = SYSTEM_RUNNING;
arch_suspend_enable_irqs();Three things happen that s2idle deliberately skips: every non-boot CPU is taken offline (not merely idled), interrupts are globally disabled, and syscore_suspend() stops the timekeeping subsystem and saves low-level core state. The last-chance pm_wakeup_pending() check avoids the classic suspend race where a wakeup arrives in the instant before sleep — if one is pending, the code returns -EBUSY instead of sleeping into a state nothing will wake it from. Then suspend_ops->enter(state) is called.
Inside acpi_suspend_enter()
acpi_suspend_enter() (sleep.c:590, v6.12) is the actual firmware hand-off for S3:
case ACPI_STATE_S3:
if (!acpi_suspend_lowlevel)
return -ENOSYS;
error = acpi_suspend_lowlevel();
if (error)
return error;
pr_info("Low-level resume complete\n");
pm_set_resume_via_firmware();
break;acpi_suspend_lowlevel() is architecture-specific assembly (on x86, it saves the processor context, writes the wakeup vector into the firmware’s wakeup structure, and executes acpi_enter_sleep_state(ACPI_STATE_S3), which writes the SLP_TYP/SLP_EN bits to the ACPI PM control register that physically triggers the S3 transition). Execution does not return from here until the machine has resumed: the firmware powers the SoC down, and when a wakeup event fires later, firmware re-initializes the hardware and jumps to the saved resume vector, which restores the processor context and returns from acpi_suspend_lowlevel() as if nothing happened. The pr_info("Low-level resume complete\n") is the first line that runs after the (possibly hours-long) sleep.
On resume, acpi_suspend_enter() re-enables the SCI, clears GPE status to suppress spurious interrupts (acpi_hw_disable_all_gpes()), unblocks Embedded Controller transactions, and restores ACPI Non-Volatile Sleeping (NVS) memory (suspend_nvs_restore()) — firmware may have clobbered that region, so the kernel saved and restores it. The comment in the source is candid about the assembly: “It’s unfortunate, but it works. Please fix if you’re feeling frisky.” (sleep.c:588).
mem_sleep selection: s2idle vs shallow vs deep
The /sys/power/mem_sleep file holds the list of supported mem-string variants and lets userspace pick which one mem triggers. The default — what mem does with nothing written to mem_sleep — is decided at boot. The documentation states it is “either ‘deep’ (on the majority of systems supporting suspend-to-RAM) or ‘s2idle’”, overridable by the mem_sleep_default= kernel command-line parameter (sleep-states.rst, v6.12). The code that sets it:
suspend_state_t mem_sleep_current = PM_SUSPEND_TO_IDLE; /* default before any ops */
suspend_state_t mem_sleep_default = PM_SUSPEND_MAX;
void suspend_set_ops(const struct platform_suspend_ops *ops)
{
...
if (valid_state(PM_SUSPEND_MEM)) {
mem_sleep_states[PM_SUSPEND_MEM] = mem_sleep_labels[PM_SUSPEND_MEM];
if (mem_sleep_default >= PM_SUSPEND_MEM)
mem_sleep_current = PM_SUSPEND_MEM;
}
}So when a platform supports S3, deep becomes available and becomes the default (mem_sleep_current = PM_SUSPEND_MEM) — unless something lowers it. The mem_sleep_default= boot parameter, parsed by mem_sleep_default_setup(), can pin it to s2idle, shallow, or deep (suspend.c:193, v6.12). And — the crucial modern wrinkle — on a platform whose FADT advertises Low Power S0 Idle, lps0_device_attach() in drivers/acpi/x86/s2idle.c overrides the default back down to PM_SUSPEND_TO_IDLE (s2idle.c:521, v6.12). This is why a machine can support S3 (offer deep) yet default to s2idle.
To force S3 at runtime: echo deep > /sys/power/mem_sleep; echo mem > /sys/power/state. As the docs note, “there is only one way to make the system go into the suspend-to-RAM state” — that exact pair (sleep-states.rst, v6.12). To make it permanent, set mem_sleep_default=deep on the kernel command line. See The sys-power sysfs Interface for the full /sys/power/ surface.
Why deep is increasingly absent on Modern-Standby machines
The single most important practical fact about S3 in 2026 is that it is frequently not there. As OEMs adopted Microsoft’s “Modern Standby” (s0ix) model, many shipped firmware that implements Low Power S0 Idle but provides no working ACPI S3 — acpi_sleep_state_supported(ACPI_STATE_S3) returns false, S3 is never registered, and /sys/power/mem_sleep shows only [s2idle]. Even where firmware nominally declares S3, vendors increasingly stop validating it (since Windows uses Modern Standby), so it may be present-but-broken: the machine suspends but resumes with a hung device, a black screen, or a hard lock. The Linux kernel’s own AMD work treats LPS0 as the modern prerequisite — a 2022 patch “Don’t offer s2idle on AMD platforms without LPS0,” and AMD’s guidance is that S3 is deprecated in favour of s0ix on recent platforms (patchwork, linux-acpi 2022).
Uncertain
Verify: (1) that Microsoft’s certification program actively discourages or omits S3 (vs. merely promoting Modern Standby), and (2) the specifics of which recent platforms drop S3 and when. Reason: the kernel mechanism (S3 registered only when
valid_statepasses; LPS0 lowering the default) is verified against v6.12 source, but the OEM/firmware motivation and the per-vendor timeline rest on community wikis (ArchWiki) and vendor commentary, not a primary Microsoft logo-requirement document fetched here. To resolve: read current Microsoft “Modern Standby” hardware-design / WHCP requirements and the relevant OEM platform documentation. uncertain
Wakeup-source plumbing for S3
The set of devices that can wake the system from S3 is narrower than from s2idle, because most of the SoC is powered off — only hardware that retains power in S3 (the ACPI power button, the Real-Time Clock alarm, a Wake-on-LAN-capable NIC with auxiliary power, certain USB ports with wakeup enabled) can signal a wakeup (sleep-states.rst, v6.12). This is the structural difference from s2idle’s “in-band interrupts” model: an S3 wakeup “need not be an in-band hardware interrupt” — it is a platform signal that firmware detects and acts on, then re-enters the kernel.
Mechanically, a device is armed as an S3 wakeup source through the device-PM core’s wakeup framework (device_set_wakeup_enable(), enable_irq_wake()), surfaced in sysfs as each device’s power/wakeup attribute and aggregated in /sys/power/wakeup_count. On ACPI, the relevant devices have _PRW (Power Resources for Wake) methods and GPE assignments; the kernel programs those GPEs to remain enabled across S3 so the corresponding event resumes the machine. See Wakeup Sources and Wakeirq for the full wakeup-IRQ machinery and ACPI Power States G S C P and D States for the ACPI state/GPE vocabulary. After resume, acpi_suspend_enter() clears all GPE status before re-enabling interrupts to avoid the spurious-wakeup events that a just-powered-up chipset would otherwise inject.
Failure Modes
-
deepnot listed in/sys/power/mem_sleep. Firmware does not expose a usable S3;valid_state(PM_SUSPEND_MEM)is false. Not a kernel bug — the only paths are a firmware/DSDT override or using s2idle. Verify withcat /sys/power/mem_sleep(shows[s2idle]only) anddmesg | grep -i 'ACPI: .*S3'. -
S3 entered but resume hangs / black screen. The classic broken-S3 symptom on Modern-Standby hardware where S3 is declared but unvalidated by the vendor. A device’s
resume_noirq/resumecallback touches hardware the firmware did not fully re-initialize, or a GPU/firmware blob fails to reload. Diagnose by suspending with/sys/power/pm_testlevels (platform,processors,core) to bisect where in the sequence it breaks, and checkdmesgafter a forced reboot. -
Immediate wake from S3. A GPE or wakeup device left armed (often the Embedded Controller, a USB controller, or a PCIe device with PME enabled) fires the instant firmware enters S3. Inspect
/sys/power/wakeup_countand per-devicepower/wakeup; the offending source is often visible indmesgresume logs or viacat /proc/acpi/wakeup. -
Memory corruption after resume. If ACPI NVS handling is wrong (or
nvs_nosaveis misused), firmware-clobbered NVS memory is not restored, leading to subtle post-resume instability. The kernel’ssuspend_nvs_alloc()/suspend_nvs_restore()exist precisely to prevent this. CXL-attached memory cannot survive S3 at all, which is whysleep_state_supported()refuses S3 whilecxl_mem_active().
Alternatives and When to Choose Them
- Suspend-to-idle (s2idle) — the default and often only option on 2020+ laptops; pure software, no firmware S3 needed, faster resume, but its deep savings depend on LPS0/s0ix and on every driver cooperating. Choose it when
deepis absent or unreliable. - Standby (S1,
shallow) — between s2idle and S3; offlines non-boot CPUs but keeps more powered than S3. Rare in practice. - Hibernation (suspend-to-disk) — when you need zero power and to survive battery removal; pays a full kernel-boot resume cost. The only state that does not keep DRAM powered.
- Stay in S3 only when firmware genuinely supports it and it survives a real resume test — on hardware where it works, S3 still beats shallow s2idle for battery life over long suspends.
Production Notes
The operational reality is a fork: on older or desktop/server hardware where firmware S3 works, deep remains the right choice for overnight suspends — RAM-retained, deeply powered, well-tested. On modern laptops the question is often moot because deep is gone, and the engineering effort shifts entirely to making s2idle/s0ix reach a deep hardware sleep. When S3 is available but flaky, the highest-leverage tool is /sys/power/pm_test to bisect the suspend sequence, combined with per-device power/wakeup auditing for immediate-wake bugs. The deeper lesson is architectural: S3’s reliability rested on firmware owning a single, well-defined low-power state; as the industry moved that responsibility into the OS (s2idle) and the firmware’s _DSM hints (s0ix), suspend reliability became a software problem distributed across hundreds of drivers — which is exactly why this S3-vs-s2idle split is the defining story of modern Linux laptop power management.
See Also
- Suspend-to-Idle s2idle — the pure-software sibling that replaced S3 on modern x86
- Linux System Sleep States — the four sleep states and how
/sys/power/stateselects among them - ACPI Power States G S C P and D States — the ACPI S/G/C/P/D vocabulary S3 lives in
- The sys-power sysfs Interface —
/sys/power/state,mem_sleep,wakeup_count,pm_test - Wakeup Sources and Wakeirq — how devices are armed to wake the system from S3
- System Suspend and Resume Phases — the device suspend/resume phase ordering S3 rides on
- The Freezer and Freezing Tasks — how userspace is frozen before the firmware hand-off
- Hibernation Suspend-to-Disk — the suspend-to-disk alternative that retains nothing in RAM
- Linux Power Management MOC — parent map (§6 System Sleep States)