Linux Hardware Interrupts Explained: Hardirq vs Threaded IRQ Handlers-Linux Device Driver Training in Hyderabad

Linux Hardware Interrupts Explained: Hardirq vs Threaded IRQ Handlers | EmbeddedPathashala
Linux Hardware Interrupts Explained: Hardirq vs Threaded IRQ Handlers
A free Linux kernel programming course lecture on interrupt handling, real-time latency, and Linux device driver internals (updated for kernel 6.x)
Reading Time
12 min
Level
Beginner → Intermediate
Kernel Version
6.x

Every time you press a key, a network packet arrives, or a sensor toggles a GPIO line, the CPU has to stop whatever it is doing and react immediately. That reaction mechanism is called a hardware interrupt, and understanding how the Linux kernel processes it is one of the core skills taught in this free Linux kernel programming course. In this lecture we break down interrupt handling, or Linux kernel interrupt handling, in plain language, with original diagrams and driver-style code examples you can try on any modern Linux 6.x board.

What You Will Learn:

  • What a hardware interrupt actually is, from the CPU’s point of view
  • Why a burst of interrupts (an “interrupt storm”) can silently delay time-critical software
  • The difference between a plain hardirq handler and a threaded IRQ handler
  • How this affects real-time and embedded Linux applications on today’s multi-core SoCs
  • A simple, original driver-style code example using request_irq()

Prerequisites: basic C programming, a general idea of what a kernel module is, and familiarity with terms like process and CPU scheduling. No prior driver experience is required — this is part of our free Linux device drivers course track.

1. What Is a Hardware Interrupt?

A hardware interrupt is an electrical signal that a peripheral (a UART, a GPIO controller, a network card, a timer) sends to the CPU’s interrupt controller to say “I need attention right now.” The CPU immediately suspends whatever instruction stream it is executing, saves just enough context, and jumps into a piece of kernel code called the interrupt handler (also referred to as the ISR, or Interrupt Service Routine).

This is fundamentally different from a function call. Nothing in the currently running program asked for this to happen — it can occur between any two instructions, on any CPU core, at any time. That unpredictability is exactly what makes interrupt handling tricky in systems that also need to guarantee timing for other work, such as a real-time control loop or an audio pipeline.

2. The Two-Part Interrupt Model: Top Half and Bottom Half

Linux has always split interrupt work into two parts to keep the urgent reaction short:

Part Also Called Job Can It Sleep?
Top half hardirq, primary handler Acknowledge the device, grab minimal data, decide what to do next No
Bottom half tasklet, workqueue, threaded handler Do the heavier processing later, outside the interrupt context Depends on mechanism

The top half (hardirq) runs with local interrupts disabled on that CPU core and cannot be preempted by ordinary scheduling. This is great for speed but dangerous if it runs too long or too often — which brings us to the real problem.

3. The Problem: How an Interrupt Storm Breaks a Deadline

Imagine a real-time worker thread that must finish a control-loop calculation within a 10 ms deadline. It is scheduled with the real-time SCHED_FIFO policy, so on paper nothing should be able to delay it except an even higher-priority real-time thread. In practice, a plain hardirq handler can still cut in line — because on the classic model, hardirq execution is not scheduled by the normal scheduler at all; it simply preempts everything, regardless of process priority.

If the device driving that interrupt line becomes busy — for example, a network interface handling a sudden burst of packets — and fires many interrupts back to back, each hardirq execution, even if only a few hundred microseconds long, chips away at your thread’s time budget. Below is an original timeline diagram showing this effect.

Diagram: Interrupt Storm Delaying a Real-Time Thread
RT thread running
hardirq
hardirq
hardirq
RT thread resumes (late)
Green = RT thread executing Red = hardirq preempting unconditionally

Each red block preempts the thread regardless of its real-time priority. If enough of them arrive back to back, the thread’s deadline is missed even though it was the highest-priority runnable task.

Raising the thread’s real-time priority does not help here, because a plain hardirq handler is not a scheduled entity — it has no priority to compare against. This is the exact problem threaded interrupt handlers were designed to solve, which we cover in the next lecture of this free Linux kernel development course.

4. Why This Still Matters on Modern Multi-Core SoCs

It is tempting to assume this is only a problem for old, single-core embedded boards. In reality, modern quad-core and octa-core SoCs used in industrial gateways, robotics controllers, and automotive infotainment still route many interrupts to a single core by default (IRQ affinity), and high-throughput peripherals such as Gigabit Ethernet, NVMe, and USB3 controllers can generate very frequent interrupts. Since Linux kernel 6.12 (released November 2024), the long-standing PREEMPT_RT patch set was merged into the mainline kernel itself, meaning every standard kernel source tree now has the option to build a fully preemptible, low-latency kernel without applying an external patch. Threaded interrupt handlers, which we will build hands-on in the next lecture, are one of the core mechanisms that made this mainline merge possible.

5. A Simple Hardirq-Only Driver Example

Here is a minimal, original example of registering a classic (non-threaded) interrupt handler for a GPIO button on a kernel 6.x system, using the modern gpiod descriptor API instead of the deprecated legacy GPIO numbering. Keep in mind: this top half must never call any function that can sleep.

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>

static struct gpio_desc *btn_gpio;
static int btn_irq;

static irqreturn_t btn_hardirq_handler(int irq, void *dev_id)
{
    /* Top half: keep this extremely short, no sleeping allowed */
    pr_info("ep_btn: button edge detected on IRQ %d\n", irq);
    return IRQ_HANDLED;
}

static int ep_btn_probe(struct platform_device *pdev)
{
    int ret;

    btn_gpio = devm_gpiod_get(&pdev->dev, "button", GPIOD_IN);
    if (IS_ERR(btn_gpio))
        return PTR_ERR(btn_gpio);

    btn_irq = gpiod_to_irq(btn_gpio);
    if (btn_irq < 0)
        return btn_irq;

    ret = devm_request_irq(&pdev->dev, btn_irq, btn_hardirq_handler,
                            IRQF_TRIGGER_FALLING, "ep_button_irq", NULL);
    return ret;
}

static struct platform_driver ep_btn_driver = {
    .probe = ep_btn_probe,
    .driver = {
        .name = "ep_button",
    },
};
module_platform_driver(ep_btn_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala sample hardirq-only button driver");

Notice that btn_hardirq_handler() only logs a message and returns. Anything heavier — reading a sensor over I2C, updating a data structure protected by a mutex, or waking up a user-space process — should never live directly inside this function on a production driver.

6. Common Mistakes
  • Doing heavy work in the top half — copying large buffers or calling blocking APIs inside a hardirq handler will stall the whole core.
  • Assuming a high SCHED_FIFO priority protects you from hardirqs — as shown above, a plain hardirq is not a scheduled entity, so priority alone will not save your deadline.
  • Ignoring IRQ affinity — leaving every interrupt pinned to CPU0 by default can overload one core while others sit idle.
  • Forgetting to free the IRQ — always pair manual request_irq() with free_irq(), or simply use the devm_ managed variant as shown above.
7. Best Practices
  • Keep the top half to a handful of microseconds: acknowledge hardware, read a status register, decide, return.
  • Push any processing that can sleep, take a mutex, or run for more than a few microseconds into a threaded handler or workqueue.
  • Use /proc/interrupts and cat /proc/irq/<n>/smp_affinity to check which core is servicing a busy interrupt line.
  • On latency-sensitive boards, measure before optimizing — tools like rtla timerlat (built into modern kernels) give you real numbers instead of guesswork.
Key Takeaways
Hardirq preempts unconditionally Top half must stay tiny Priority does not protect against hardirq Interrupt storms delay real-time threads Threaded IRQ is the fix (next lecture)
Frequently Asked Questions

Q1. What is the difference between a hardirq and a threaded IRQ handler?
A hardirq (top half) runs immediately with interrupts disabled and cannot be scheduled or delayed by priority. A threaded IRQ handler runs as a normal kernel thread that the scheduler can prioritize, delay, or preempt like any other thread.

Q2. Why does a real-time thread miss its deadline even at the highest SCHED_FIFO priority?
Because a plain hardirq is not a scheduled task. It preempts every thread regardless of that thread’s priority, since it runs outside normal process scheduling.

Q3. Is this problem still relevant on Linux kernel 6.x?
Yes. High-speed peripherals on modern SoCs can still generate frequent interrupts, and IRQ affinity still concentrates load on specific cores by default.

Q4. What replaced the out-of-tree PREEMPT_RT patch?
PREEMPT_RT was merged into the mainline Linux kernel starting with kernel 6.12, released in November 2024, so it is now a configuration option in the standard kernel source tree.

Q5. Can I try this without real hardware?
Yes, you can build and load the example module on a Raspberry Pi, BeagleBone, or even QEMU with a virtual GPIO, as long as the platform exposes a GPIO line through the device tree.

Q6. Where can I learn threaded interrupt handlers next?
In the next lecture of this free Linux device drivers course, where we rewrite this same driver using request_threaded_irq().

Continue the Free Linux Kernel Programming Course

This lecture is part of EmbeddedPathashala’s free Linux kernel development course, covering kernel modules, device drivers, and interrupt handling from scratch.

Browse the Full Course Watch on YouTube

2 Comments

Leave a Reply

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