← Previous Lecture | Next Lecture →
This lecture is part of our free Linux kernel programming course and covers Linux interrupt handling from the ground up. If you are building a Linux device driver that talks to real hardware — a button, a sensor, a UART, a touch controller — sooner or later that hardware needs to tell the CPU “something happened, come service me right now.” That mechanism is the hardware interrupt, and learning to register and handle one correctly is a core skill in any free Linux device drivers course.
In this tutorial we focus on the classic interrupt registration API, request_irq(), understand exactly what each parameter does, see why the industry has moved on to safer alternatives, and write a small, original example driver you can build and test on a modern board running a 6.x kernel.
Before starting this lecture on Linux interrupt handling, you should be comfortable with:
- Writing and loading a basic loadable kernel module (LKM)
- Basic C programming and pointers
- Reading a simple Device Tree node
- Using
dmesgto view kernel log messages
Why Interrupt Handling Matters in Linux Device Drivers
A CPU cannot sit in a loop constantly asking every device “are you ready yet?” — that approach, called polling, wastes power and CPU cycles. Instead, hardware raises an electrical signal on an interrupt request (IRQ) line, and the CPU immediately pauses whatever it was doing to run a small piece of driver code called an interrupt handler. This is the foundation of efficient, responsive Linux interrupt handling, and every embedded systems engineer needs to master it.
Internally, the kernel keeps a table indexed by IRQ number. Each entry points to a list of registered handlers for that line (a line can be shared by more than one device on some architectures). When the CPU receives an interrupt signal, the core interrupt subsystem looks up this table and calls every registered handler function for that IRQ number, in kernel context, asynchronously with respect to whatever process was running.
How Do You Register a Driver’s Interrupt Handler?
Modern Linux offers several ways to register interest in an IRQ line. The table below compares them so you know which one to reach for.
| API | Auto Cleanup? | Threaded? | Recommended For |
|---|---|---|---|
request_irq() |
No — manual free_irq() |
No | Legacy code, learning the basics |
devm_request_irq() |
Yes, tied to device lifetime | No | Simple, fast handlers |
request_threaded_irq() |
No — manual free_irq() |
Yes | Handlers that may sleep |
devm_request_threaded_irq() |
Yes, tied to device lifetime | Yes | Most new drivers today |
We cover devm_request_irq() and threaded interrupts in detail in the next lecture. For now, let’s understand request_irq() thoroughly, since every other variant builds on the same ideas.
The request_irq() API Signature
#include <linux/interrupt.h>
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev_id);
Here is what each argument means in plain language:
- irq — The interrupt number you want to hook into. On modern embedded boards this number is almost always resolved from the Device Tree rather than hard-coded, using APIs such as
platform_get_irq()orgpiod_to_irq(). - handler — A pointer to your callback function. This function runs when the interrupt fires.
- flags — A bitmask controlling behaviour, such as trigger edge (rising, falling) or whether the line is shared between devices.
- name — A short string identifying your driver. This name shows up in
/proc/interrupts, which is very useful for debugging. - dev_id — A cookie pointer passed back to your handler. On a shared line this value must be unique and non-NULL, since it’s also used to identify which handler to remove later.
The function returns 0 on success and a negative error code on failure — always check the return value before assuming your interrupt is live.
A Simple, Original request_irq() Example
Below is a minimal, original platform driver skeleton that hooks a GPIO-based button interrupt using request_irq(). It is written for a modern 6.x kernel using the descriptor-based GPIO API.
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/gpio/consumer.h>
struct btn_priv {
struct gpio_desc *gpiod;
int irq;
};
static irqreturn_t btn_isr(int irq, void *dev_id)
{
struct btn_priv *priv = dev_id;
pr_info("btn: interrupt fired on IRQ %d\n", irq);
/* Keep this function short and non-blocking */
return IRQ_HANDLED;
}
static int btn_probe(struct platform_device *pdev)
{
struct btn_priv *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->gpiod = devm_gpiod_get(&pdev->dev, "button", GPIOD_IN);
if (IS_ERR(priv->gpiod))
return PTR_ERR(priv->gpiod);
priv->irq = gpiod_to_irq(priv->gpiod);
if (priv->irq < 0)
return priv->irq;
ret = request_irq(priv->irq, btn_isr,
IRQF_TRIGGER_RISING, "my-button", priv);
if (ret) {
dev_err(&pdev->dev, "failed to request IRQ: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, priv);
return 0;
}
static int btn_remove(struct platform_device *pdev)
{
struct btn_priv *priv = platform_get_drvdata(pdev);
free_irq(priv->irq, priv);
return 0;
}
static const struct of_device_id btn_of_match[] = {
{ .compatible = "ep,my-button" },
{ }
};
MODULE_DEVICE_TABLE(of, btn_of_match);
static struct platform_driver btn_driver = {
.probe = btn_probe,
.remove = btn_remove,
.driver = {
.name = "ep-button",
.of_match_table = btn_of_match,
},
};
module_platform_driver(btn_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala sample interrupt driver");
Notice that request_irq() is paired with an explicit free_irq() call in the remove function. Forgetting this is one of the most common bugs in Linux interrupt handling code, which is exactly why the managed devm_ variants exist — more on that in the next lecture.
Setting Interrupt Flags
The flags argument controls how your interrupt behaves. The most commonly used flags are:
| Flag | Meaning |
|---|---|
IRQF_TRIGGER_RISING | Fire on a low-to-high signal transition |
IRQF_TRIGGER_FALLING | Fire on a high-to-low signal transition |
IRQF_SHARED | Allow the line to be shared by multiple devices |
IRQF_ONESHOT | Used with threaded interrupts to keep the line masked until the thread finishes |
Common Mistakes When Registering Interrupts
- Doing heavy work inside the handler. The top-half handler should be as short as possible; move real processing to a threaded handler, workqueue, or tasklet.
- Forgetting to check the return value of
request_irq(). A silent failure here means your device will simply never respond. - Passing NULL as dev_id on a shared line. This breaks
free_irq(), since the kernel cannot tell which handler to remove. - Not calling free_irq() on driver removal or probe failure, leaking a kernel resource.
Best Practices
- Prefer resolving the IRQ number from the Device Tree instead of hard-coding it.
- Keep the interrupt handler function short, non-blocking, and free of sleeping calls.
- Use a meaningful, unique
nameso/proc/interruptsstays readable on busy systems. - Always pair
request_irq()withfree_irq()in your cleanup path.
Security Considerations
Interrupt handlers run with full kernel privileges. Never trust raw hardware input blindly inside an ISR — validate register values before acting on them, and avoid copying data to or from user space directly inside a handler. Keep privileged work minimal and push validation and heavier logic to the threaded half or a workqueue.
Summary / Key Takeaways
- Interrupts let hardware notify the CPU instantly instead of relying on wasteful polling.
request_irq()is the classic API for registering a Linux interrupt handler.- Modern drivers increasingly prefer the managed
devm_and threaded variants, covered next. - Always validate the return value and free the IRQ on driver teardown.
Frequently Asked Questions
Q1. What is the difference between an IRQ number and a GPIO number?
A GPIO number identifies a physical pin; an IRQ number identifies the interrupt line that pin is mapped to. You convert one to the other with gpiod_to_irq().
Q2. Can request_irq() be called from process context only?
Yes, it must be called from a context that can sleep, typically your driver’s probe function, not from within another interrupt handler.
Q3. What happens if two devices share the same IRQ line?
You must pass IRQF_SHARED in flags and supply a unique, non-NULL dev_id so the kernel can call every registered handler and later identify each one individually.
Q4. Why does my interrupt handler need to return IRQ_HANDLED?
It tells the kernel your driver actually serviced the interrupt. Returning IRQ_NONE signals that this handler did not recognize the interrupt, which matters on shared lines.
Q5. Is request_irq() still relevant on modern kernels?
Yes, it is still fully supported and is the base that other variants build on, though for new drivers the managed and threaded variants are generally recommended.
Q6. Where can I see which driver owns which interrupt?
Run cat /proc/interrupts on your target board; the rightmost column shows the name you passed to request_irq().
Q7. What is the difference between a fault, a trap, and a hardware interrupt?
Faults and traps are raised by the CPU itself in response to program execution (like an invalid memory access or a system call), while a hardware interrupt is raised asynchronously by an external device.
This lecture is part of the free EmbeddedPathashala Linux Kernel Programming course. Continue to the next lecture to learn about devm_request_irq() and threaded interrupts.
Next Lecture Course Index
2 Comments