Linux Power Management Hands-On Basics-Free Linux Device Drivers Course

Linux Power Management Hands-On Basics

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

Reading /sys/power/state and /sys/power/mem_sleep Reading per-device runtime PM sysfs attributes Watching kernel log messages during suspend/resume Writing and loading an original ep_pm_demo skeleton driver Where dev_pm_ops attaches to a platform_driver

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

/sys/power/stateWrite here to trigger a system-wide sleep transition
/sys/power/mem_sleepChooses what “mem” means: s2idle, shallow, or deep
…/power/controlauto or on — allow or forbid Runtime PM for one device
…/power/runtime_statusactive, suspended, suspending, or resuming
…/power/autosuspend_delay_msIdle time required before an autosuspend fires
…/power/wakeupenabled or disabled — can this device wake the system?

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

Leave a Reply

Your email address will not be published. Required fields are marked *