Linux Wakeup Source Driver Example
Hands-on: checking and enabling wakeup capability from sysfs, then a full ep_wakebutton GPIO driver using the current wake-IRQ API
The previous lecture covered the theory of a Linux wakeup source — the activate/deactivate lifecycle and the struct wakeup_source data model. This lecture is the hands-on half of that pair, part of the same free linux device drivers course: first the real sysfs commands you’ll actually type on a board to check and control wakeup behavior, then a complete original driver, ep_wakebutton, that wires a GPIO interrupt into a wakeup source using the current mainline API — verified directly against the kernel source, not copied from any book.
What You Will Learn
- How to check whether a device is wakeup-capable, and how to enable/disable it, purely from sysfs
- How to read the per-device wakeup statistics that back the struct wakeup_source fields from the previous lecture
- How a minimal platform GPIO driver wires
device_init_wakeup()together with the wake-IRQ helper API - Why the current recommended pattern does not require manual
enable_irq_wake()/disable_irq_wake()calls inside your own suspend/resume callbacks - What a correct wakeup-triggered resume looks like in dmesg
Prerequisites
- Read the previous lecture on Linux wakeup source fundamentals — this lecture assumes you already understand the activate/deactivate lifecycle and what
device_init_wakeup()does and doesn’t do - Comfortable building and loading an out-of-tree kernel module against a target kernel
- Basic GPIO/interrupt driver experience (
gpiod_*API,request_threaded_irq())
Checking and Enabling Wakeup Capability From Sysfs
Every device that has had device_init_wakeup() called against it in its driver exposes a power/wakeup file. If the file is absent, the device was never marked wakeup-capable — that’s diagnostic on its own. Start by finding the device and checking capability:
# Locate the device's sysfs power directory (example: a platform device)
ls /sys/bus/platform/devices/ | grep wakebutton
# Check whether the wakeup attribute exists at all (capability)
ls /sys/bus/platform/devices/ep-wakebutton.0/power/ | grep wakeup
# Read current policy: "enabled" or "disabled"
cat /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup
Enabling or disabling is a plain write. This only changes policy — it has no effect if the device was never marked capable in the first place:
# Turn wakeup policy ON for this device
echo enabled > /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup
# Turn wakeup policy OFF again
echo disabled > /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup
# Confirm the change took effect
cat /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup
Once a wakeup source is registered, the per-device statistics files described in the previous lecture become readable. These map directly onto the struct wakeup_source fields:
cat /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup_count
cat /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup_active_count
cat /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup_abort_count
cat /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup_active
cat /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup_total_time_ms
cat /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup_max_time_ms
cat /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup_last_time_ms
cat /sys/bus/platform/devices/ep-wakebutton.0/power/wakeup_prevent_sleep_time_ms
For a system-wide view across every registered wakeup source, not just one device, use the aggregate debugfs report (requires PM debugging enabled in the kernel config):
cat /sys/kernel/debug/wakeup_sources
Verified: The Current Recommended Wake-IRQ Pattern
Older code (and older books) commonly show a driver calling enable_irq_wake(irq) by hand inside its suspend() callback and disable_irq_wake(irq) inside resume(). That pattern still compiles and still works, but it is no longer what current mainline drivers are expected to do. Checked directly against drivers/base/power/wakeirq.c and drivers/base/power/main.c in current mainline Linux, the verified modern pattern is:
- Call
device_init_wakeup(dev, true)once, inprobe(). - Immediately call
dev_pm_set_wake_irq(dev, irq), also inprobe()— this attaches the device’s existing IO interrupt as the wake IRQ for that device’s wakeup_source. - Do not call
enable_irq_wake()/disable_irq_wake()yourself. The PM core callsdevice_wakeup_arm_wake_irqs()during the suspend-noirq phase anddevice_wakeup_disarm_wake_irqs()during the resume-noirq phase, for every registered device — these internally callenable_irq_wake()/disable_irq_wake()against each wake IRQ based on that device’s currentdevice_may_wakeup()result. Arming and disarming is handled centrally, once, for the whole system, instead of scattered across every driver’s suspend/resume callback. - Call
dev_pm_clear_wake_irq(dev)inremove()to detach it (or usedevm_pm_set_wake_irq()in probe so this is automatic).
In other words: your driver’s own suspend()/resume() callbacks generally need zero wake-IRQ-specific code once dev_pm_set_wake_irq() has been called in probe. The one thing they may still want to do is log or react to the wakeup, which is exactly what the example below does — for visibility, not because it’s required for the mechanism to work.
Example Driver: ep_wakebutton
The following is an original, minimal platform driver for an imaginary GPIO wakeup button, prefixed ep_ for this course. It is not derived from any vendor or book example. It calls device_init_wakeup() and dev_pm_set_wake_irq() in probe(), and relies on the PM core to arm/disarm the wake IRQ automatically, as verified above.
// ep_wakebutton.c — original demo: GPIO button as a system wakeup source
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/pm_wakeirq.h>
#include <linux/pm.h>
struct ep_wakebutton {
struct gpio_desc *gpiod;
int irq;
struct device *dev;
};
static irqreturn_t ep_wakebutton_irq(int irq, void *data)
{
struct ep_wakebutton *wb = data;
/*
* pm_wakeup_event() marks this device's wakeup_source active for
* a bounded window, which is what actually aborts an in-progress
* suspend or triggers a resume — see the previous lecture's
* activate/deactivate lifecycle.
*/
pm_wakeup_event(wb->dev, 0);
dev_info(wb->dev, "wakeup event detected on irq %d\n", irq);
return IRQ_HANDLED;
}
static int ep_wakebutton_probe(struct platform_device *pdev)
{
struct ep_wakebutton *wb;
int ret;
wb = devm_kzalloc(&pdev->dev, sizeof(*wb), GFP_KERNEL);
if (!wb)
return -ENOMEM;
wb->dev = &pdev->dev;
wb->gpiod = devm_gpiod_get(&pdev->dev, "wakeup", GPIOD_IN);
if (IS_ERR(wb->gpiod))
return PTR_ERR(wb->gpiod);
wb->irq = gpiod_to_irq(wb->gpiod);
if (wb->irq irq;
ret = devm_request_threaded_irq(&pdev->dev, wb->irq, NULL,
ep_wakebutton_irq,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"ep_wakebutton", wb);
if (ret)
return ret;
/* Step 1: mark this device wakeup-capable and enable it by default. */
device_init_wakeup(&pdev->dev, true);
/*
* Step 2: attach the existing IO interrupt as the wake IRQ.
* The PM core now arms/disarms enable_irq_wake()/disable_irq_wake()
* for this IRQ automatically during suspend-noirq/resume-noirq —
* no manual calls needed in ep_wakebutton_suspend/resume below.
*/
ret = dev_pm_set_wake_irq(&pdev->dev, wb->irq);
if (ret)
dev_warn(&pdev->dev, "failed to set wake irq: %d\n", ret);
platform_set_drvdata(pdev, wb);
dev_info(&pdev->dev, "ep_wakebutton ready, irq %d armed as wake source\n",
wb->irq);
return 0;
}
static void ep_wakebutton_remove(struct platform_device *pdev)
{
struct ep_wakebutton *wb = platform_get_drvdata(pdev);
dev_pm_clear_wake_irq(&pdev->dev);
device_init_wakeup(&pdev->dev, false);
dev_info(&pdev->dev, "ep_wakebutton removed, wake irq %d detached\n",
wb->irq);
}
static int ep_wakebutton_suspend(struct device *dev)
{
/*
* No enable_irq_wake() call here on purpose — dev_pm_set_wake_irq()
* in probe() already told the PM core which IRQ to arm. This
* callback only needs to handle device-specific quiescing, if any.
*/
dev_info(dev, "ep_wakebutton: entering suspend, wake irq stays armed by PM core\n");
return 0;
}
static int ep_wakebutton_resume(struct device *dev)
{
dev_info(dev, "ep_wakebutton: resume callback entered\n");
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(ep_wakebutton_pm_ops,
ep_wakebutton_suspend, ep_wakebutton_resume);
static const struct of_device_id ep_wakebutton_of_match[] = {
{ .compatible = "ep,wakebutton" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_wakebutton_of_match);
static struct platform_driver ep_wakebutton_driver = {
.probe = ep_wakebutton_probe,
.remove = ep_wakebutton_remove,
.driver = {
.name = "ep_wakebutton",
.of_match_table = ep_wakebutton_of_match,
.pm = &ep_wakebutton_pm_ops,
},
};
module_platform_driver(ep_wakebutton_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala original GPIO wakeup source demo driver");
ep_wakebutton Wakeup Flow
Expected dmesg Output on a Wakeup-Triggered Resume
The following is representative output from pressing the button while the board is in suspend-to-RAM, showing the driver’s own log lines interleaved with the standard PM core messages:
[ 42.108841] ep_wakebutton ep-wakebutton.0: ep_wakebutton ready, irq 47 armed as wake source
...
[ 205.331120] PM: suspend entry (deep)
[ 205.338902] ep_wakebutton ep-wakebutton.0: ep_wakebutton: entering suspend, wake irq stays armed by PM core
[ 205.402711] PM: suspend devices took 0.064 seconds
[ 205.409553] PM: suspend-to-idle
[ 205.415006] Suspending console(s) (use no_console_suspend to debug)
...
[ 212.660312] ep_wakebutton ep-wakebutton.0: wakeup event detected on irq 47
[ 212.660390] PM: pm_wakeup_event: wakeup source ep-wakebutton.0 active
[ 212.661015] PM: Triggering wakeup from IRQ 47
[ 212.700442] PM: resume from suspend, current wakeup source: 'ep-wakebutton.0'
[ 212.712233] ep_wakebutton ep-wakebutton.0: ep_wakebutton: resume callback entered
[ 212.740901] PM: suspend exit
Reading this back against the sysfs statistics: after this single press, wakeup_count and event_count for ep-wakebutton.0 both increment by one, wakeup_active_count increments by one, and wakeup_last_time_ms updates to reflect how long the source stayed active during the resume — exactly the correlation between dmesg and the wakeup_source fields covered in the previous lecture.
Common Mistakes and Troubleshooting
- Calling enable_irq_wake() manually alongside dev_pm_set_wake_irq(). This double-arms the IRQ wake state and can produce mismatched enable/disable counts, sometimes surfacing as a WARN from the IRQ core about unbalanced wake-irq usage. Pick one pattern — the modern one shown here doesn’t need manual calls.
- Calling dev_pm_set_wake_irq() before device_init_wakeup(). The wake IRQ attaches to the device’s wakeup_source object, which doesn’t exist until
device_init_wakeup()has registered it — always init wakeup first. - Writing “enabled” to power/wakeup and expecting a resume that never comes. The policy write has no effect if
dev_pm_set_wake_irq()(or an equivalent dedicated wake-IRQ call) was never made — capability plus policy is necessary but not sufficient; an actual IRQ has to be attached. - Forgetting IRQF_ONESHOT with a threaded handler on a level-sensitive wake line. Can lead to the interrupt re-firing before the thread has run, flooding event_count.
- Not calling dev_pm_clear_wake_irq() in remove(). Leaves a stale wake_irq structure attached to a device that’s going away; use the devm variant to avoid this entirely.
Best Practices
- Prefer
dev_pm_set_wake_irq()over manualenable_irq_wake()/disable_irq_wake()for any device whose wake trigger is the same IRQ it already uses for normal operation. - Use
dev_pm_set_dedicated_wake_irq()instead when the hardware has a separate, dedicated wake-up interrupt line distinct from its normal IO interrupt — it sets up its own threaded handler tied purely to waking the device. - Always pair sysfs experimentation with the statistics files — after every
echo enabled/disabled, re-checkpower/wakeupand the relevantwakeup_*counters to confirm the change actually took effect on real hardware. - Keep suspend/resume callbacks focused on device quiescing/restoring, not wake-IRQ bookkeeping — the PM core already owns that once
dev_pm_set_wake_irq()has been called.
Summary and Conclusion
This lecture turned the wakeup source theory from the previous lecture into working sysfs commands and a complete, original driver. The verified current pattern for a device that wakes the system using its own IO interrupt is: device_init_wakeup() followed by dev_pm_set_wake_irq(), both once in probe(), with no manual enable_irq_wake()/disable_irq_wake() calls needed in suspend()/resume() — the PM core arms and disarms the wake IRQ centrally during the noirq phases. Combined with the power/wakeup sysfs attribute for policy and the per-device statistics files for debugging, this gives you everything needed to build and verify a real wakeup-capable driver in this free linux kernel development course.
Frequently Asked Questions
Do I still need to call enable_irq_wake() in my suspend callback?
Not if you’ve called dev_pm_set_wake_irq() in probe(). The PM core arms and disarms that IRQ’s wake state automatically during the suspend-noirq and resume-noirq phases.
What’s the difference between dev_pm_set_wake_irq() and dev_pm_set_dedicated_wake_irq()?
dev_pm_set_wake_irq() reuses the device’s existing IO interrupt as the wake trigger. dev_pm_set_dedicated_wake_irq() is for hardware with a separate wake-only interrupt line, and sets up its own threaded handler for it.
Why does echo enabled to power/wakeup not make my device wake the system?
The policy write only matters if the device is also wired to a real wake IRQ via dev_pm_set_wake_irq() or a dedicated equivalent. Policy without an attached wake IRQ has nothing to arm.
Where should dev_pm_set_wake_irq() be called from?
In probe(), immediately after device_init_wakeup(), since the wake IRQ attaches to the wakeup_source that device_init_wakeup() just registered.
How do I clean up the wake IRQ when my driver unloads?
Call dev_pm_clear_wake_irq() in remove(), or use devm_pm_set_wake_irq() in probe() so it’s released automatically on driver detach.
What does the dmesg line about the resume wakeup source actually mean?
It names the specific wakeup_source (by the name registered at device_init_wakeup() time) that the PM core identified as responsible for aborting suspend or triggering the resume.
Is the manual enable_irq_wake()/disable_irq_wake() pattern in suspend()/resume() wrong?
It still works and is still valid for cases outside the wake-IRQ helper framework, but for standard cases it has been superseded by dev_pm_set_wake_irq(), which centralizes arming/disarming in the PM core instead of every driver managing it separately.
Continue the Power Management Series
You’ve now covered both the theory and a working implementation of a Linux wakeup source. Move on to the next lecture in this free embedded systems course to see driver wakeup events in more advanced scenarios.
Continue to Driver Wakeup Events Review Wakeup Source Fundamentals