Linux Power Management Hands-On Basics
Observe Device Power Management and System Power Management on a real, running Linux system, then see exactly where dev_pm_ops slots into a driver.
In the previous lecture we built the conceptual model behind Linux kernel power management: Device Power Management (Runtime PM), which acts on one device at a time while the system runs, and System Power Management (Sleep States), which transitions the whole machine together. This lecture is deliberately hands-on and deliberately introductory — part of EmbeddedPathashala’s free Linux device drivers course — because before you write a single real callback, you should be able to look at a running kernel and answer two questions with your own eyes: what sleep states does this machine support, and is this particular device currently runtime-suspended or active? We will answer both using nothing but sysfs, then build a tiny original platform driver skeleton, ep_pm_demo, that shows exactly where dev_pm_ops attaches to a real driver structure. The deep implementation of runtime suspend/resume logic and wakeup sources is deliberately left for later lectures in this series.
What You Will Learn
Prerequisites
You need basic C knowledge, a Linux machine (a virtual machine is perfectly fine) with kernel headers installed so you can build an out-of-tree module, and root access for the sysfs writes and module loading shown below. Reading the previous lecture in this series on the Device PM / System PM model is strongly recommended before continuing here.
Observing System Power Management From Userspace
System-wide sleep states are controlled entirely through two files under /sys/power/. Start by checking which states your kernel actually supports on this hardware — this varies significantly between a desktop, a laptop, a cloud VM, and an embedded board.
$ cat /sys/power/state
freeze mem disk
$ cat /sys/power/mem_sleep
s2idle [deep]
The first line tells you this kernel supports suspend-to-idle (“freeze”), the “mem” target, and hibernation (“disk”). The second line tells you what “mem” currently resolves to — here, square brackets mark “deep” as the active choice, meaning writing “mem” to /sys/power/state would trigger suspend-to-RAM. On a machine without deep suspend support, this file typically only lists [s2idle]. Selecting a different variant is a normal userspace write:
# echo s2idle > /sys/power/mem_sleep
# cat /sys/power/mem_sleep
[s2idle] deep
Triggering an actual suspend is a single write, and it requires root because it affects the entire machine:
# echo mem > /sys/power/state
After the system resumes, the kernel log is the fastest way to confirm what happened and how long it took:
$ dmesg | grep -i "PM: suspend"
[ 5123.104433] PM: suspend entry (deep)
[ 5123.812221] PM: suspend exit
Observing Device Power Management From Userspace
Every device registered with the driver core that participates in power management exposes a power/ directory under its entry in /sys/devices/ (also reachable through bus-specific symlinks such as /sys/bus/platform/devices/<name>/power/). This is where you inspect Runtime PM state without reading a single line of driver source.
$ ls /sys/bus/platform/devices/ep_pm_demo.0/power/
async autosuspend_delay_ms control runtime_active_kids
runtime_active_time runtime_enabled runtime_status
runtime_suspended_time runtime_usage wakeup
$ cat /sys/bus/platform/devices/ep_pm_demo.0/power/control
auto
$ cat /sys/bus/platform/devices/ep_pm_demo.0/power/runtime_status
suspended
$ cat /sys/bus/platform/devices/ep_pm_demo.0/power/wakeup
control tells you whether the driver core is allowed to runtime-manage this device (“auto”) or has been forced fully on by userspace (“on”). runtime_status reports the field the PM core actually tracks internally — active, suspended, suspending, or resuming. An empty wakeup file means the device never called device_set_wakeup_capable(), so it simply cannot be a wakeup source; that is expected for our skeleton driver and will change once we cover wakeup sources in a later lecture.
Sysfs Map for Observing Linux Kernel Power Management
An Original Skeleton: ep_pm_demo
Now let’s connect the sysfs files above to actual driver code. The module below is intentionally minimal — every callback body is a single dev_info() log line rather than real hardware access, because the goal of this lecture is purely to show where the Linux kernel power management callbacks attach to a driver, not to implement real power sequencing yet. It registers its own dummy platform device internally, so it binds and probes immediately on insmod without needing a device tree entry or board file.
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
static int ep_pm_demo_runtime_suspend(struct device *dev)
{
dev_info(dev, "ep_pm_demo: runtime_suspend() - power the device down here\n");
return 0;
}
static int ep_pm_demo_runtime_resume(struct device *dev)
{
dev_info(dev, "ep_pm_demo: runtime_resume() - power the device up here\n");
return 0;
}
static int ep_pm_demo_suspend(struct device *dev)
{
dev_info(dev, "ep_pm_demo: suspend() - system sleep transition starting\n");
return 0;
}
static int ep_pm_demo_resume(struct device *dev)
{
dev_info(dev, "ep_pm_demo: resume() - system sleep transition ending\n");
return 0;
}
static const struct dev_pm_ops ep_pm_demo_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(ep_pm_demo_suspend, ep_pm_demo_resume)
SET_RUNTIME_PM_OPS(ep_pm_demo_runtime_suspend,
ep_pm_demo_runtime_resume,
NULL)
};
static int ep_pm_demo_probe(struct platform_device *pdev)
{
dev_info(&pdev->dev, "ep_pm_demo: probe() - this is where pm_runtime_enable() belongs\n");
pm_runtime_enable(&pdev->dev);
return 0;
}
static void ep_pm_demo_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static struct platform_driver ep_pm_demo_driver = {
.probe = ep_pm_demo_probe,
.remove = ep_pm_demo_remove,
.driver = {
.name = "ep_pm_demo",
.pm = &ep_pm_demo_pm_ops,
},
};
static struct platform_device *ep_pm_demo_pdev;
static int __init ep_pm_demo_init(void)
{
ep_pm_demo_pdev = platform_device_register_simple("ep_pm_demo", -1, NULL, 0);
if (IS_ERR(ep_pm_demo_pdev))
return PTR_ERR(ep_pm_demo_pdev);
return platform_driver_register(&ep_pm_demo_driver);
}
static void __exit ep_pm_demo_exit(void)
{
platform_driver_unregister(&ep_pm_demo_driver);
platform_device_unregister(ep_pm_demo_pdev);
}
module_init(ep_pm_demo_init);
module_exit(ep_pm_demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("ep_pm_demo - conceptual skeleton showing where dev_pm_ops slots in");
Notice the two things this skeleton deliberately does not do yet: it never calls pm_runtime_get_sync() or pm_runtime_put() anywhere, so nothing ever actually triggers a runtime suspend on its own, and it never calls device_init_wakeup(). Both are exactly the subjects of upcoming lectures in this series — here we only care about the shape of the structure.
Build, Load, and Observe
$ make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
$ sudo insmod ep_pm_demo.ko
$ dmesg | tail -n 3
[ 812.334221] ep_pm_demo ep_pm_demo.0: ep_pm_demo: probe() - this is where pm_runtime_enable() belongs
Now inspect the device the same way we did earlier in this lecture:
$ cat /sys/bus/platform/devices/ep_pm_demo.0/power/runtime_status
suspended
It reports “suspended” immediately — not because our placeholder callback ran, but because every device’s initial Runtime PM status is RPM_SUSPENDED by default, regardless of the hardware’s real state, until a driver explicitly marks it otherwise. This is a deliberate, documented kernel behavior, and it is exactly the kind of detail that trips people up if they only ever read driver code and never cross-check it against the running system through sysfs. Unload the module the same way you would any other:
$ sudo rmmod ep_pm_demo
$ dmesg | tail -n 1
Common Mistakes and Troubleshooting
If /sys/power/state does not exist at all, your kernel was built without CONFIG_SUSPEND — this is common on minimal server and container kernels and is not a bug in anything you did. If a device’s power/ directory is missing runtime attributes such as runtime_status, the most likely explanation is that the subsystem called pm_runtime_no_callbacks() for it, or that pm_runtime_enable() was simply never called in its probe function — both are legitimate, documented states, not errors. Writing to /sys/power/state without root privileges fails with a permission error, which is expected, since a successful write can suspend the entire machine including your own SSH session — always test suspend/resume behavior on hardware or a VM you can physically or remotely wake back up, never on a machine you cannot afford to lose access to. Finally, if insmod on the demo module fails, double check that your kernel headers match uname -r exactly; a mismatched build tree is the single most common reason an otherwise-correct out-of-tree module refuses to build.
Best Practices
Always read a device’s actual runtime_status from sysfs before trusting assumptions about its power state during debugging — logs alone can lie if a callback silently returned early. When experimenting with real system suspend on a laptop or dev board, keep a serial console or a way to physically wake the hardware available, since sleep-state bugs can leave a board unresponsive to network access. Treat the sysfs interface described here as a debugging and observability tool first, and only reach for Runtime PM’s programmatic API (pm_runtime_get_sync(), pm_runtime_put(), and friends) once you actually understand what state your device is in and why, which is precisely what this lecture’s sysfs walkthrough is meant to teach.
Summary and Key Takeaways
You now know how to read Linux kernel power management state directly from a running system: /sys/power/state and /sys/power/mem_sleep for the system-wide sleep-state machinery, and each device’s power/ directory under sysfs for its individual Runtime PM status. You also built and loaded ep_pm_demo, an original platform driver skeleton that shows exactly where dev_pm_ops, the runtime callbacks, and the system-sleep callbacks attach to a real driver — without yet implementing any actual hardware power sequencing. That implementation, along with autosuspend delays, usage counters, and wakeup sources, is the subject of the lectures that follow in this free Linux device drivers course.
Frequently Asked Questions
How can I check which sleep states my Linux system supports?
Read /sys/power/state. It lists the sleep states the running kernel currently supports, such as “freeze”, “mem”, and “disk”. Read /sys/power/mem_sleep to see which variant – s2idle, shallow, or deep – the “mem” entry currently maps to.
How do I see whether a specific device is runtime-suspended right now?
Read the runtime_status file under that device’s power/ directory in sysfs, for example /sys/bus/platform/devices/ep_pm_demo.0/power/runtime_status. It reports active, suspended, suspending, or resuming.
Why does writing to /sys/power/state require root?
Because a successful write can suspend or hibernate the entire machine, affecting every process and every user on the system, the kernel restricts this write to privileged users only.
What does the ep_pm_demo driver in this lecture actually do?
It is a minimal original platform driver skeleton that registers its own dummy platform device, probes immediately, and logs a message from each dev_pm_ops callback slot so you can see exactly where runtime and system-sleep callbacks attach to a driver structure.
Why are the callback bodies just dev_info() calls instead of real hardware code?
This is an introductory, hands-on lecture focused on observing power management state and understanding where the callbacks fit structurally. Real runtime-suspend logic, autosuspend delays, and wakeup source handling are covered in depth in later lectures of this series.
My device’s power/control file doesn’t exist – why?
Either the device was never registered with pm_runtime_enable() by its driver, or its subsystem called pm_runtime_no_callbacks() for it, which intentionally removes the non-debugging runtime PM sysfs attributes. Both are normal, documented outcomes rather than errors.
Next: Implementing Real Runtime PM Callbacks
You have observed Linux kernel power management from userspace and seen where dev_pm_ops attaches to a driver. The next lecture puts real logic behind those callbacks.
Continue to the Next Lecture Back to the Concepts Lecture