← Previous Lecture Next Lecture →
In the previous lecture of this free Linux kernel programming course, we learned how threaded interrupt handlers split work between a fast primary handler and a sleep-capable kernel thread. Now we look at the API that modern Linux drivers actually use in practice: devm_request_threaded_irq(), the device-managed version of threaded interrupt allocation.
This lecture explains why “managed” resource APIs exist, walks through every parameter, and gives you an original, simple, modern-kernel driver example you can adapt for your own free Linux device drivers course project.
What You Will Learn
- What “device-managed” (devm_) APIs mean in the Linux kernel
- The exact function signature of
devm_request_threaded_irq(), parameter by parameter - How it compares to plain
request_threaded_irq() - A simple, original modern driver example using the managed API
- When the managed API is not the right choice
- Debugging and best practice tips
Prerequisites
This lecture builds directly on threaded interrupt handler concepts — primary handler, threaded handler, IRQF_ONESHOT — covered in the previous lecture of this free Linux kernel programming course. If any of those terms are new to you, review that lecture first.
Why “Managed” (devm_) APIs Exist
A typical Linux driver’s probe() function allocates several resources: memory, IRQ lines, clocks, regulators. If probe() fails halfway through, or when the device is later removed, every one of those resources must be released in exactly the right order. Getting this wrong is one of the most common sources of driver bugs — memory leaks, IRQ lines left registered, use-after-free crashes on unbind.
The kernel’s device-managed (devm_) family of APIs solves this by tying a resource’s lifetime to the underlying struct device. Once you allocate through a devm_* call, the kernel automatically frees that resource when the device is detached or the driver is unbound — you do not need to write manual cleanup code for it.
|
request_threaded_irq()
You must call |
devm_request_threaded_irq()
Kernel frees the IRQ automatically on device detach or driver unbind — no manual free_irq() needed |
Function Signature Explained
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname,
void *dev_id);
| Parameter | Meaning |
dev | Pointer to the owning struct device; ties the IRQ’s lifetime to this device |
irq | The IRQ number to allocate, usually obtained via a helper like platform_get_irq() or gpiod_to_irq() |
handler | Primary handler, runs in hardirq context, can be NULL |
thread_fn | Threaded handler, runs in process context, does the real work |
irqflags | Trigger type and flags, e.g. IRQF_ONESHOT | IRQF_TRIGGER_FALLING |
devname | Name shown in /proc/interrupts and in the thread name |
dev_id | Cookie passed back to your handlers, typically your driver’s private data pointer |
The return value follows the standard kernel convention: 0 on success, a negative -Exxx errno value on failure. Always check it.
A Simple Original Example: I2C Sensor on a Modern Kernel
Here is an original, simplified example of an I2C client driver using devm_request_threaded_irq(), written for a current 6.x kernel. It represents a generic motion sensor that raises an interrupt line when new data is ready.
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/regmap.h>
struct my_sensor_dev {
struct i2c_client *client;
};
#define SENSOR_REG_STATUS 0x00
#define SENSOR_REG_DATA 0x01
#define SENSOR_STATUS_READY BIT(0)
/* Threaded handler only: no primary handler needed for this device */
static irqreturn_t sensor_thread_fn(int irq, void *data)
{
struct my_sensor_dev *sensor = data;
int status, value;
status = i2c_smbus_read_byte_data(sensor->client, SENSOR_REG_STATUS);
if (status < 0 || !(status & SENSOR_STATUS_READY))
return IRQ_NONE;
value = i2c_smbus_read_byte_data(sensor->client, SENSOR_REG_DATA);
if (value < 0)
return IRQ_NONE;
dev_info(&sensor->client->dev, "new sensor reading: %d\n", value);
return IRQ_HANDLED;
}
static int my_sensor_probe(struct i2c_client *client)
{
struct my_sensor_dev *sensor;
int ret;
sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL);
if (!sensor)
return -ENOMEM;
sensor->client = client;
i2c_set_clientdata(client, sensor);
if (client->irq <= 0)
return -EINVAL;
/* NULL primary handler: kernel supplies the default, and
* IRQF_ONESHOT is applied automatically in this case */
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, sensor_thread_fn,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"my_sensor", sensor);
if (ret) {
dev_err(&client->dev, "failed to request threaded irq: %d\n", ret);
return ret;
}
dev_info(&client->dev, "my_sensor driver probed, irq=%d\n", client->irq);
return 0;
}
static const struct i2c_device_id my_sensor_id[] = {
{ "my_sensor", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, my_sensor_id);
static struct i2c_driver my_sensor_driver = {
.driver = {
.name = "my_sensor",
},
.probe = my_sensor_probe,
.id_table = my_sensor_id,
};
module_i2c_driver(my_sensor_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple devm_request_threaded_irq I2C sensor example");
Notice there is no remove() function freeing the IRQ, and no manual free_irq() call anywhere. Because the IRQ was requested through the devm_ variant tied to &client->dev, the kernel releases it automatically when the I2C device is unbound.
Comparison Table
| Aspect | request_threaded_irq() | devm_request_threaded_irq() |
| Cleanup | Manual free_irq() required | Automatic on device detach |
| Extra parameter | None | struct device *dev as first argument |
| Typical use | Non-probe contexts, custom lifetimes | Standard driver probe() functions |
| Risk of leaks | Higher if error paths are incomplete | Lower, tied to device lifecycle |
When NOT to Use the Managed API
The managed API is the recommended default, but it is not always the right tool:
- If you need to free the IRQ at a specific point before the device is removed (for example, temporarily disabling interrupt handling while reconfiguring hardware), a manual
free_irq()gives you precise control thatdevm_cleanup ordering does not - If your resource’s lifetime does not match the device’s lifetime, forcing it into the devm model can hide bugs rather than prevent them
- Mixing manual and managed cleanup for the same resource in one driver is a common source of double-free bugs — pick one style per resource
Debugging Tips
- Check registered interrupts:
cat /proc/interrupts | grep my_sensor - Confirm the interrupt thread is running:
ps -eLo pid,comm | grep "irq/" - Use
dev_err()return codes fromdevm_request_threaded_irq()to catch IRQ number or flag mistakes early during probe
Best Practices and Security Considerations
- Always validate
client->irq(or equivalent) before requesting it — a negative or zero value means the interrupt was never wired up in the device tree or ACPI table - Never trust interrupt-triggered data blindly; validate register reads before acting on them, since a misbehaving or spoofed device could otherwise trigger unexpected driver behavior
- Keep the threaded handler’s critical sections short even though it can sleep, to avoid starving other interrupt threads
- Prefer
devm_request_threaded_irq()in standardprobe()flows to minimize resource-leak bugs on error paths
Key Takeaways
devm_request_threaded_irq()ties an IRQ’s lifetime to astruct device, removing the need for manualfree_irq()in most drivers- All parameters besides the added
struct device *devmatch plainrequest_threaded_irq() - The managed API is the modern recommended default for driver
probe()functions, but manual control is still sometimes required
FAQ
Q1. What does devm_request_threaded_irq() do differently from request_threaded_irq()?
It links the IRQ allocation to a struct device, so the kernel automatically frees it when that device is removed, instead of requiring a manual free_irq() call.
Q2. Do I still need to call free_irq() with the managed API?
No. That is the entire point of the devm_ pattern — cleanup happens automatically on device detach or driver unbind.
Q3. Is devm_request_threaded_irq() always the right choice?
In most standard probe() flows, yes. But if you need precise control over exactly when the IRQ is freed, independent of the device’s overall lifecycle, manual request_threaded_irq() and free_irq() may be more appropriate.
Q4. Does the managed API change how flags like IRQF_ONESHOT work?
No, flag behavior is identical; only the resource cleanup mechanism differs.
Q5. What happens if devm_request_threaded_irq() fails?
It returns a negative errno value. You should log the error and return it from probe() so the kernel knows the driver failed to initialize.
Q6. Can devm_request_threaded_irq() be used in platform drivers as well as I2C drivers?
Yes, it works with any bus type as long as you have a valid struct device pointer and IRQ number.
You now understand both the plain and managed threaded interrupt APIs. Move on to the next lecture to keep building your embedded Linux driver skills.

2 Comments