Handling Hardware Interrupts in Linux Device Drivers (Part 1): Fundamentals & IRQ Allocation
Welcome to this lesson from the EmbeddedPathashala free Linux device drivers course. If you are searching for a free Linux kernel development course or a hands-on free embedded systems course, you are in the right place. In this tutorial on handling hardware interrupts in Linux device drivers, you will learn what a hardware interrupt actually is, how the modern Linux kernel (6.x series) processes it, and exactly how you, as a driver author, request an interrupt line the correct way. Everything here is written for beginners but keeps the technical depth a working embedded engineer needs.
What You Will Learn
- What a hardware interrupt (IRQ) is and why peripherals use it instead of polling.
- How the interrupt controller (GIC on ARM, APIC on x86) delivers a signal to the CPU.
- The step-by-step flow the kernel follows from an electrical signal to your handler.
- How the generic IRQ layer keeps your driver portable across hardware.
- How to allocate an IRQ line correctly using the modern kernel API.
- Why high-speed devices switch to polling and how NAPI helps.
Prerequisites
To follow this free Linux device drivers course lesson comfortably, you should have:
- Basic C programming knowledge, including pointers and function pointers.
- Familiarity with writing and loading a simple Linux kernel module (LKM).
- A test machine or virtual machine running a recent kernel (6.x recommended).
- Comfort with the terminal and reading files under
/proc.
What Is a Hardware Interrupt?
Most peripheral controllers need a way to tell the operating system or device driver that something important just happened. Instead of the CPU constantly asking “are you ready yet?”, the device raises its hand with an electrical signal. That signal is a hardware interrupt. When handling hardware interrupts in Linux device drivers, this is the fundamental idea you must internalize: the interrupt is a hardware notification that redirects the processor to run a small piece of code immediately.
Think of polling like repeatedly opening your front door every few seconds to check whether a delivery arrived. It wastes energy and time. An interrupt is the doorbell: you do nothing until it rings, and only then do you respond. On a battery-powered device, this difference is enormous, because constant polling drains the battery quickly.
Devices that commonly generate interrupts include network adapters, storage controllers, USB controllers, audio and video hardware, human interface devices such as keyboards and touchscreens, timer chips, and DMA controllers. In every case, the interrupt lets the low-level software run only when work genuinely needs to be done.
|
Polling (Inefficient)
CPU asks device again → again → again → again → wastes cycles and power even when nothing happened.
|
Interrupt (Efficient)
CPU does other work → device raises IRQ only when ready → CPU responds instantly, then returns.
|
How the Kernel Handles Hardware Interrupts
To understand handling hardware interrupts in Linux device drivers, you first need a picture of the hardware path. A modern motherboard contains an interrupt controller chip. On x86 platforms this is typically the I/O-APIC (I/O Advanced Programmable Interrupt Controller). On ARM and ARM64 platforms, it is the Generic Interrupt Controller (GIC), and current ARM64 server and mobile SoCs commonly use GICv3 or GICv4. Whatever the name, the controller sits between the peripherals and the CPU’s interrupt pin.
Each peripheral capable of raising an interrupt has an IRQ line connected to this controller. IRQ stands for Interrupt ReQuest, and it names the interrupt line (or lines) assigned to a device.
| Peripheral (NIC, USB, timer) |
→ | Interrupt Controller (GIC / APIC) |
→ | CPU Interrupt Pin | → | Your Handler |
The Step-by-Step Interrupt Flow
Let us walk through what happens when, say, a network adapter receives a packet. The flow below is simplified on purpose so the concept stays clear:
| Step | What Happens |
|---|---|
| 1 | The device asserts its IRQ line on the interrupt controller. |
| 2 | The controller records which line was asserted in a register. |
| 3 | The controller asserts the CPU’s interrupt pin. |
| 4 | After each instruction the CPU control unit checks for pending interrupts, so it notices almost immediately (unless the interrupt is masked). |
| 5 | Low-level architecture code in the kernel catches the event and begins the generic interrupt path. |
| 6 | The kernel invokes the registered handler routine of the driver responsible for that interrupt. |
On a standard (non-threaded) interrupt, the hardware interrupt is the top priority on the system. It preempts whatever was running, whether that was user-space or kernel-space code, so your handler can run right away. Later in this series we will see that the threaded interrupt model changes this behavior in a helpful way.
When Interrupts Become a Problem: Livelock and NAPI
Interrupts are efficient, but they are not free. Modern high-speed network adapters (10 Gbps and above) can raise interrupts so rapidly that the CPU spends all its time entering and leaving interrupt handlers and never makes real progress. This condition is called livelock: the system is busy but effectively frozen, much like a deadlock in outcome.
The solution on Linux is a receive-path framework called NAPI (New API). NAPI lets a network driver switch between interrupt-driven mode and polled mode depending on load. Under light traffic it uses interrupts for low latency; under heavy traffic it disables the interrupt and polls in batches, which is far more efficient. This hybrid approach is standard in modern kernel networking drivers and is worth remembering whenever you study high-throughput hardware.
Allocating the Hardware IRQ
Now we reach the practical core of handling hardware interrupts in Linux device drivers: telling the kernel “when interrupt number N fires, call my function.” The challenge is that the exact wiring from controller to CPU differs wildly between platforms. The kernel solves this with the generic IRQ layer, an abstraction that hides hardware differences and exposes clean, portable APIs. Because of this layer, the same driver code can run on x86, ARM, ARM64, and other architectures without change.
The primary function for requesting an interrupt line in a modern kernel is request_irq(), declared in <linux/interrupt.h>. Here is its shape:
#include <linux/interrupt.h>
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev);
Each parameter has a clear job. The table below explains them in plain language:
| Parameter | Meaning |
|---|---|
irq | The interrupt number the kernel assigned to your device. |
handler | The function the kernel calls when the interrupt fires. |
flags | Behavior options such as sharing and trigger type (see below). |
name | A label shown in /proc/interrupts to identify the owner. |
dev | A cookie passed back to your handler; usually a pointer to your driver’s private data. Required for shared IRQs. |
The return value follows the usual kernel convention: 0 on success, and a negative error code on failure. Your driver must always check it.
The Handler Signature
Your handler must match the type irq_handler_t. It receives the interrupt number and the same dev cookie you registered, and it returns an irqreturn_t value:
typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);
There are three return values you will use in modern kernels:
| Return Value | When to Use It |
|---|---|
IRQ_HANDLED | Your device raised this interrupt and you handled it. |
IRQ_NONE | This was not your device (important for shared lines). |
IRQ_WAKE_THREAD | Wake the threaded (bottom-half) handler to finish the work. |
Common IRQ Flags
The flags argument controls how the kernel treats the line. A few you will meet often:
| Flag | Purpose |
|---|---|
IRQF_SHARED | Allow several devices to share one interrupt line. |
IRQF_TRIGGER_RISING / IRQF_TRIGGER_FALLING | Select the active edge for edge-triggered interrupts. |
IRQF_TRIGGER_HIGH / IRQF_TRIGGER_LOW | Select the active level for level-triggered interrupts. |
IRQF_ONESHOT | Keep the line masked until the threaded handler finishes (essential for level-triggered threaded IRQs). |
Releasing the Interrupt
Whatever you request, you must release. When your driver unloads or the device goes away, call free_irq() so the kernel reclaims the line. Pass the same dev cookie you registered so the kernel can identify your registration on a shared line:
void free_irq(unsigned int irq, void *dev);
free_irq() in your cleanup path is a classic driver bug. It can leave a dangling handler that the kernel may call after your module’s memory is gone, leading to a crash.
Where the IRQ Number Comes From
On modern systems you rarely hardcode an interrupt number. Instead the number is described by firmware and resolved for you:
- On embedded ARM/ARM64 platforms, the Device Tree describes each device’s interrupt, and the driver retrieves it with helpers such as
platform_get_irq(). - On PCI and PCIe devices, the bus subsystem assigns the number, often using MSI or MSI-X message-signaled interrupts.
- On x86 with ACPI, firmware tables supply the routing information.
The key point for this free Linux kernel development course lesson is that you request the resolved number; you do not guess it.
Best Practices for IRQ Allocation
- Always check the return value of
request_irq()and fail cleanly if it is negative. - Pair every successful
request_irq()with a matchingfree_irq()in your teardown path. - Use a meaningful
nameso/proc/interruptsis readable during debugging. - Pass your device’s private data as
dev; never passNULLon a shared line. - Prefer managed helpers such as
devm_request_irq()where appropriate, so cleanup happens automatically.
Common Mistakes and Troubleshooting
| Symptom | Likely Cause |
|---|---|
request_irq returns -EBUSY | Line already taken and you did not pass IRQF_SHARED, or another driver holds it exclusively. |
| Handler never fires | Wrong IRQ number, wrong trigger flag, or the device interrupt is not enabled in hardware. |
| “nobody cared” and the line gets disabled | Your handler returned IRQ_NONE repeatedly, or you never acknowledged the interrupt at the device. |
| Crash on module unload | Missing free_irq(), so the handler is called after cleanup. |
Key Takeaways
- A hardware interrupt is an electrical signal that tells the CPU a device needs attention, replacing wasteful polling.
- An interrupt controller (GIC on ARM, APIC on x86) routes the signal to the CPU, which checks for it after every instruction.
- The generic IRQ layer abstracts hardware differences so driver code stays portable.
- You register a handler with
request_irq()and must release it withfree_irq(). - Handlers return
IRQ_HANDLED,IRQ_NONE, orIRQ_WAKE_THREAD. - High-speed devices use NAPI to avoid interrupt livelock.
Conclusion
In this first part of our lesson on handling hardware interrupts in Linux device drivers, you built a solid mental model of what interrupts are, how the modern kernel delivers them from silicon to your handler, and how to allocate an interrupt line the right way with request_irq() and release it with free_irq(). These fundamentals are the foundation of every interrupt-driven driver you will ever write. As part of the EmbeddedPathashala free Linux device drivers course and free embedded systems course, this knowledge prepares you to write real handler code. In Part 2, we will implement the interrupt handler routine itself, learn the strict dos and don’ts of interrupt context, explore the threaded interrupt model, and understand top and bottom halves.
Frequently Asked Questions (FAQ)
Q1. What is a hardware interrupt in Linux device drivers?
It is an electrical signal a peripheral raises to tell the CPU or driver that immediate attention is required, letting software run only when needed instead of polling continuously.
Q2. What is the difference between an IRQ and an interrupt?
An interrupt is the event itself. IRQ (Interrupt ReQuest) refers to the specific line or number assigned to a device that carries that event to the controller.
Q3. Which function allocates an interrupt line in a modern kernel?
Use request_irq() from <linux/interrupt.h>, and release it with free_irq(). For handlers that must sleep, use request_threaded_irq(), covered in Part 2.
Q4. Why does my handler need to return a value?
The return value tells the kernel whether your device caused the interrupt (IRQ_HANDLED), whether it did not (IRQ_NONE), or whether a thread should finish the work (IRQ_WAKE_THREAD). This is critical on shared lines.
Q5. What is NAPI and why do fast network cards use it?
NAPI is a kernel receive framework that lets a driver switch between interrupts and polling. Under heavy traffic it polls in batches to avoid livelock, where constant interrupts would otherwise freeze the system.
Q6. Do I need to know the IRQ number in advance?
Usually not. On ARM/ARM64 the Device Tree provides it (retrieved via platform_get_irq()), and on PCI/PCIe the bus assigns it, often via MSI/MSI-X.
Q7. Is this free Linux device drivers course suitable for beginners?
Yes. This EmbeddedPathashala series explains concepts from first principles while still giving the depth needed for real embedded systems work.

2 Comments