The sys-power sysfs Interface
/sys/power/is the single userspace control surface for system-wide power management in Linux: the small set of sysfs attribute files through which a user (or a daemon likesystemd-logind) tells the kernel “suspend the whole machine now,” “use this suspend depth,” “hibernate to this disk,” or “test the suspend path without actually sleeping.” The directory is a single kernel object (power_kobj), created bypm_init()inkernel/power/main.c, with each file backed by a pair of show/store handler functions (main.c, v6.12). Writing the stringmemto/sys/power/stateis, mechanically, the way to trigger a suspend-to-RAM from userspace; everythingsystemctl suspendultimately does reduces to a write into one of these files. This note walks the real knobs file by file, pinned to Linux 6.12 LTS (released 2024-11-17), and shows where the in-kernel ABI documentation has drifted out of date.
Mental Model
Think of /sys/power/ as a control panel with two kinds of switches. Action switches (state, disk, resume, wakeup_count, wake_lock) do something when written — they start a suspend, hibernate, or arm a wakeup-counting protocol. Mode switches (mem_sleep, pm_async, sync_on_suspend, pm_test, image_size) only change how the next action behaves; they are sticky settings, not triggers. Reading a mode switch usually shows the available options with the current one in [square brackets], a convention implemented by hand in each show handler.
flowchart TD subgraph USER["Userspace (echo / systemd-logind)"] E1["echo mem > state"] E2["echo deep > mem_sleep"] E3["read wakeup_count; echo N > wakeup_count"] end subgraph KOBJ["power_kobj = /sys/power/ (kernel/power/main.c)"] STATE["state_store()"] MEMSLEEP["mem_sleep_store()<br/>sets mem_sleep_current"] WC["wakeup_count_store()<br/>pm_save_wakeup_count()"] end subgraph CORE["PM core"] PMSUS["pm_suspend(state)"] HIB["hibernate()"] end E2 --> MEMSLEEP E3 --> WC E1 --> STATE STATE -->|"'mem' -> mem_sleep_current"| PMSUS STATE -->|"'disk'"| HIB MEMSLEEP -.->|"selects which S-state 'mem' means"| STATE WC -.->|"abort if a wakeup races the write"| STATE
The control flow from a userspace write to a kernel suspend. What it shows: mode files (mem_sleep) only set a variable that an action file (state) later reads; state itself dispatches mem to pm_suspend() and disk to hibernate(); wakeup_count is a side protocol that lets userspace detect a wakeup event racing its own suspend request. The insight to take: there is no single “suspend” syscall — the entire interface is sysfs string writes dispatched by per-file store handlers, which is why a typo silently does nothing useful and why the files are the right place to look when debugging “my laptop won’t sleep.”
How the Directory Is Built
/sys/power/ is not a filesystem mount; it is a kobject in the kernel’s kobject hierarchy. The pm_init() function (a core_initcall) calls kobject_create_and_add("power", NULL) to create power_kobj directly under /sys/, then sysfs_create_groups(power_kobj, attr_groups) to populate it (main.c lines 1020–1038). Each attribute is declared with the power_attr(name) macro, defined in kernel/power/power.h, which expands to a struct kobj_attribute named <name>_attr with mode 0644 (owner-writable, world-readable) wiring .show = <name>_show and .store = <name>_store; the read-only variant power_attr_ro uses mode S_IRUGO and supplies only a show handler (power.h lines 80–97). The set of files that actually appears depends on Kconfig: the master attribute array g[] in main.c brackets entries with #ifdef CONFIG_SUSPEND, CONFIG_PM_SLEEP, CONFIG_PM_WAKELOCKS, CONFIG_FREEZER, and so on, so a kernel built without suspend support simply has no mem_sleep file. The hibernation-related files (disk, resume, resume_offset, image_size, reserved_size) live in a second attribute group registered separately by pm_disk_init() in kernel/power/hibernate.c (hibernate.c lines 1340–1358).
The Action Files
state — trigger a sleep transition
state is the primary trigger. Reading it lists the available sleep-state labels separated by spaces — some subset of freeze (suspend-to-idle), standby, mem (suspend-to-RAM), and disk (hibernation) — and writing one of those labels starts the transition. The state_show() handler loops over pm_states[] emitting whichever labels the platform supports, and appends disk if hibernation_available() (main.c lines 668–688). The state_store() handler decodes the string back to an enum and dispatches: disk calls hibernate(), any of the suspend states calls pm_suspend(state) — and crucially, if the written state is mem, the kernel first substitutes state = mem_sleep_current, the depth selected through the mem_sleep file (line 734). So echo mem > /sys/power/state does not hard-code a single behaviour; it means “suspend to whatever depth mem_sleep currently points at.”
# What sleep states does this machine offer?
$ cat /sys/power/state
freeze mem disk
# Suspend to RAM now (uses the mem_sleep depth, see below).
# A return to the shell means the system already resumed.
$ echo mem > /sys/power/state
# Hibernate (snapshot RAM to swap, power off).
$ echo disk > /sys/power/stateThe write blocks for the entire suspend/resume cycle: the shell prompt returns only after the machine has woken back up. Both state_store and the related writers take pm_autosleep_lock() and refuse with -EBUSY if autosleep is active (lines 723–730), so manual suspends and the Android-style autosleep loop cannot collide.
disk — choose the hibernation method, and resume — point at an image
disk selects how hibernation finishes once the memory image is written. Reading it shows the modes with the active one bracketed; the v6.12 disk_show()/disk_store() accept platform (hand off to firmware/ACPI), shutdown (kernel powers off), reboot, suspend (a hybrid that suspends to RAM after writing the image, if CONFIG_SUSPEND), and test_resume (hibernate.c lines 1087–1212). platform is offered only when a hibernation_ops driver is registered.
Uncertain
Verify: the exact set of
/sys/power/diskmodes. Reason: the in-tree ABI docDocumentation/ABI/testing/sysfs-powerstill describes the old testing modestestprocandtestfor this file, but the actual v6.12hibernation_modes[]array listsplatform,shutdown,reboot,suspend,test_resume— the doc has drifted out of date. To resolve: the authoritative source is thehibernation_modes[]table anddisk_store()switch inkernel/power/hibernate.cat the v6.12 tag, which I read directly; the modes above are taken from there, not from the ABI doc. uncertain
resume is easy to misread as “trigger a resume.” It is not. Writing a block-device specifier (a major:minor pair or a device name) to /sys/power/resume tells the kernel where the hibernation image lives and immediately attempts a software resume from it via software_resume() — used during early boot from an initramfs to restore a saved image, not to wake a sleeping machine (hibernate.c lines 1214–1272). resume_offset supplements it with a byte offset into the device, needed when the image is in a swap file rather than a swap partition. See Hibernation Suspend-to-Disk for the full snapshot/restore flow.
wakeup_count — race-free suspend
wakeup_count solves a genuine concurrency bug: a wakeup event (a key press, a network packet) can arrive in the tiny window between userspace deciding to suspend and the kernel actually freezing, and would otherwise be lost — the system would sleep through the very event that should have kept it awake. The protocol, documented in a long comment above the handlers (main.c lines 751–819), is: read wakeup_count to get the current count of registered wakeup events N, do your own preparation, then write N back. The write succeeds only if the count is still N (pm_save_wakeup_count); if a wakeup occurred in between, the count changed, the write fails, and userspace must not proceed to write state. After a successful write, the kernel will abort a subsequent suspend if any new wakeup is reported.
# The canonical race-free suspend sequence.
count=$(cat /sys/power/wakeup_count) # read current wakeup count
echo "$count" > /sys/power/wakeup_count || { echo "wakeup raced; abort"; exit 1; }
echo mem > /sys/power/state # only reached if the write succeededA bare read also blocks while wakeup events are being processed (pm_get_wakeup_count(..., true)), giving userspace a clean moment to snapshot the count. This is the foundation wakeup sources build on.
wake_lock / wake_unlock — userspace wakeup sources
These two files (present only with CONFIG_PM_WAKELOCKS, originally from Android) let userspace create named wakeup-source objects on the fly. Writing a whitespace-free name to wake_lock activates a wakeup source of that name (creating it if absent); while any such source is active, reads from wakeup_count block or report a pending event, so autosleep cannot fire. Optionally appending a whitespace-separated timeout in nanoseconds auto-deactivates the lock after it expires (main.c lines 864–897 and the ABI doc). Writing the name to wake_unlock deactivates it. Reads from each file list the currently active (resp. inactive) userspace-created sources.
# Keep the system awake while a download runs.
echo my_download > /sys/power/wake_lock
# ... do work ...
echo my_download > /sys/power/wake_unlock # release it
# Or self-expiring: hold for 5 seconds (5e9 ns), then auto-release.
echo "my_task 5000000000" > /sys/power/wake_lockThe Mode Files
mem_sleep — what “mem” means
mem_sleep selects the operating mode used when mem is written to state. Reading it lists the supported modes with the active one bracketed: s2idle (always present — pure-software suspend-to-idle), shallow (power-on standby), and deep (firmware S3 / classic suspend-to-RAM), the last two present only if the platform supports them (main.c mem_sleep_show, lines 139–163). The store handler sets the variable mem_sleep_current, which state_store then reads. This is the file you change when a modern laptop defaults to s2idle (s0ix “Modern Standby”) but you want the deeper firmware S3 instead. See Linux System Sleep States for the mode semantics.
$ cat /sys/power/mem_sleep
[s2idle] deep # s2idle is active; firmware S3 ("deep") is available
$ echo deep > /sys/power/mem_sleep # make the next 'echo mem' use S3pm_async — parallel device suspend
pm_async (default 1) controls whether the device PM core suspends and resumes drivers asynchronously, i.e. in parallel across a thread pool, rather than strictly one at a time. With it enabled, drivers whose suspend callbacks are marked async-safe run concurrently with each other and the main suspend thread, cutting suspend/resume wall-clock time on systems with many devices; writing 0 forces fully synchronous, deterministic ordering, which is invaluable when bisecting a suspend hang (main.c lines 112–136). The handler rejects any value above 1. See System Suspend and Resume Phases for how the ordering still respects device dependencies even when async.
sync_on_suspend — flush filesystems first
When set (the default unless the kernel was built with CONFIG_SUSPEND_SKIP_SYNC), the kernel calls ksys_sync() to flush dirty filesystem buffers to disk after freezing userspace but before suspending devices, so a battery-dies-during-suspend event does not lose data (main.c lines 212–242). The default initializer reads !IS_ENABLED(CONFIG_SUSPEND_SKIP_SYNC), so the build-time config decides the boot default and this file lets you override it at runtime. Disabling it (echo 0) shaves the sync latency from each suspend at the cost of durability.
pm_test — exercise the suspend path without sleeping
pm_test (present with CONFIG_PM_SLEEP_DEBUG) is a debugging dial. Its store handler accepts one of none, core, processors, platform, devices, freezer, setting pm_test_level (main.c lines 245–306). When a level other than none is set, a subsequent suspend runs the transition only up to that stage, waits ~5 seconds, then unwinds — so you can confirm that, say, the freezer or device-suspend phase works without risking a real sleep you cannot wake from. Reading shows the levels with the active one bracketed.
$ echo devices > /sys/power/pm_test # next suspend stops after suspending devices
$ echo mem > /sys/power/state # runs to the 'devices' stage, waits 5s, resumes
$ echo none > /sys/power/pm_test # back to real suspendsThe neighbouring debug files in the same CONFIG_PM_SLEEP_DEBUG block — pm_print_times (log per-device suspend/resume durations), pm_wakeup_irq (the IRQ number of the first wakeup interrupt of the last cycle), and pm_debug_messages — are the rest of the suspend-debugging toolkit.
The suspend_stats/ Directory
/sys/power/suspend_stats/ is a real sysfs subdirectory (an attribute group named suspend_stats, registered in attr_groups[]) holding per-counter files backed by the static struct suspend_stats (main.c lines 309–501). It records success and fail totals; a failed_<step> counter for each suspend/resume phase (failed_freeze, failed_prepare, failed_suspend, failed_suspend_late, failed_suspend_noirq, failed_resume, failed_resume_early, failed_resume_noirq); and the post-mortem trio last_failed_dev, last_failed_errno, last_failed_step. These are populated by dpm_save_failed_dev(), dpm_save_errno(), and dpm_save_failed_step(), which the device PM core calls whenever a callback fails — so after a botched resume, cat last_failed_dev last_failed_step names the offending driver and phase directly.
$ cat /sys/power/suspend_stats/fail
2
$ cat /sys/power/suspend_stats/last_failed_dev
0000:00:14.0
$ cat /sys/power/suspend_stats/last_failed_step
suspend_noirqThree additional files — last_hw_sleep, total_hw_sleep, max_hw_sleep (in microseconds) — report time the platform actually spent in a hardware low-power state (s0ix), and are made visible only when the firmware advertises low-power-S0 idle: suspend_attr_is_visible() returns 0444 for them only if acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0 (main.c lines 483–501). They are the ground truth for “did my laptop actually reach deep s0ix or just spin in a shallow state.”
Failure Modes and Common Misunderstandings
The most common confusion is resume vs waking up: writing to /sys/power/resume configures the hibernation image device and kicks off a restore; it has nothing to do with waking a suspended machine (that happens via hardware wakeup events, surfaced through pm_wakeup_irq). A second trap is the stale ABI doc: Documentation/ABI/testing/sysfs-power describes /sys/power/disk testing modes testproc/test that the v6.12 code does not implement (it has test_resume instead), so reading the doc and trying to echo test > disk returns -EINVAL — always cross-check against the source. Third, echo mem ignoring your intent: if you expect S3 but the machine sleeps shallowly, the cause is almost always mem_sleep sitting at [s2idle]; on modern Intel/AMD laptops firmware S3 is frequently absent and deep will not even appear. Fourth, a write that returns success but nothing sleeps: a held wake_lock, an active wakeup source, or a value that failed the wakeup_count race check will silently abort the transition — check suspend_stats/fail and the active wakeup sources (pm_print_active_wakeup_sources is dumped to the kernel log when a wakeup_count write fails, per wakeup_count_store). Finally, permission: these files are mode 0644, so writing requires root (CAP_SYS_ADMIN for the operation); a non-root echo fails with -EACCES before any handler runs.
Alternatives and When to Choose Them
For everyday use you should drive suspend/hibernate through systemd (systemctl suspend, systemctl hibernate, systemctl suspend-then-hibernate) or elogind/pm-utils, which run the inhibitor logic, the systemd-sleep hooks, and the wakeup_count race protocol for you before writing state. Direct echo-into-/sys/power/ is the right tool for debugging (combine pm_test, pm_print_times, pm_debug_messages) and for embedded/Android systems that implement their own power-manager. The swsusp/uswsusp userspace hibernation tools use the /dev/snapshot ioctl interface rather than these sysfs files for the image transfer itself, but still read resume/resume_offset for device configuration. PM QoS latency floors are a different surface entirely — see PM QoS Quality of Service Constraints — and the per-CPU-frequency knobs live under /sys/devices/system/cpu/cpufreq/, not here.
Production Notes
On real laptops the single most consulted file is mem_sleep: the migration of x86 firmware from S3 to s0ix-only (“Modern Standby”) means many machines ship with [s2idle] as the only option, and battery-drain-during-suspend bug reports almost always begin with cat /sys/power/mem_sleep and cat /sys/power/suspend_stats/{last_hw_sleep,total_hw_sleep} to confirm the platform is reaching deep s0ix rather than burning power in a shallow idle. For suspend hangs, the standard kernel-developer workflow is to disable pm_async (echo 0 > /sys/power/pm_async) to make ordering deterministic, enable pm_print_times and pm_debug_messages, and use pm_test to bisect which phase fails — the suspend_stats/last_failed_dev and last_failed_step files then name the culprit without needing to parse dmesg. For deeper-than-software debugging where the machine hangs so hard it cannot even log, /sys/power/pm_trace stashes the last PM event point in the RTC across a reboot (at the cost of corrupting the clock), as documented in the ABI file.
See Also
- Linux System Sleep States — the
freeze/standby/mem/diskstates thatstateandmem_sleepselect among - Hibernation Suspend-to-Disk — the snapshot/restore flow behind
disk,resume,resume_offset,image_size - Suspend-to-Idle s2idle — the
s2idlemode that ismem_sleep’s always-available option - System Suspend and Resume Phases — the phase sequence whose per-phase failures
suspend_stats/counts, and whichpm_async/pm_testtune - Wakeup Sources and Wakeirq — what
wakeup_count,wake_lock, andwake_unlockmanipulate - PM QoS Quality of Service Constraints — the other userspace PM surface, for latency floors rather than sleep transitions
- sysfs and the Kernel Object Hierarchy — how
power_kobjand its attribute groups become files - Linux Power Management MOC — the parent map