Threaded Interrupt Handlers in the Linux Kernel: request_threaded_irq Explained
Free Linux Kernel Programming Course — Interrupt Handling Series
A threaded interrupt handler in the Linux kernel lets your driver do real work — work that can sleep, take a mutex, or run for a while — in response to a hardware interrupt, without holding up the rest of the system. In this lecture of our free Linux kernel programming course, you will learn what request_threaded_irq() does, how it differs from a plain hardirq handler, and when you should actually reach for one.
What You Will Learn
- The difference between a primary (hardirq) handler and a threaded handler
- How request_threaded_irq() and devm_request_threaded_irq() work
- When you actually need a threaded interrupt handler in the Linux kernel
- A complete, modern GPIO-style code example, written for a current kernel
- Common mistakes, best practices, and a quick decision table
Prerequisites
- Comfort with basic C and pointers
- The previous lecture in this free Linux device drivers course on devm_request_irq and managed IRQ allocation
- A general idea of what a kernel thread is (we cover the essentials here too)
Why the Linux Kernel Needs Threaded Interrupt Handlers
When a hardware interrupt fires, the CPU jumps into what is called hardirq context. Code running there cannot sleep, cannot take a regular mutex, and should finish as fast as possible, because further interrupts on that CPU may be held off while it runs. That is fine for reading a status register and clearing a flag, but it is a serious problem if your driver needs to talk to the hardware over a bus like I2C or SPI, which can block.
A threaded interrupt handler in the Linux kernel solves this by splitting the work: a very short primary handler runs in hardirq context, and the actual processing runs afterwards in its own kernel thread, where sleeping is perfectly safe.
request_irq vs request_threaded_irq: The Real Difference
| Situation | Recommended API |
|---|---|
| Handler just reads/writes a register, finishes in a few microseconds | devm_request_irq() with a plain handler |
| Handler needs to talk over I2C/SPI, or may sleep | devm_request_threaded_irq() with a thread_fn |
| You need both a fast ack step and slower follow-up work | request_threaded_irq() with both handler and thread_fn set |
| Interrupt-driven GPIO button/sensor with debouncing logic | devm_request_threaded_irq() with NULL primary handler |
How Threaded Interrupts Flow Through the Kernel
Notice step 3: because the thread_fn() runs as a normal kernel thread with its own task_struct, it is free to sleep, block on an I2C transfer, or take a mutex — none of which is legal in hardirq context.
Step-by-Step: A Modern Threaded Interrupt Example
The example below models a GPIO-connected sensor whose interrupt handler needs to read a value over I2C, which can sleep. We use devm_request_threaded_irq() with a NULL primary handler, so the kernel supplies a default primary handler and goes straight to our thread_fn.
1. Registering the threaded handler
static int mysensor_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct mysensor_priv *priv;
int irq, ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->client = client;
i2c_set_clientdata(client, priv);
irq = client->irq;
if (irq <= 0)
return -EINVAL;
ret = devm_request_threaded_irq(dev, irq,
NULL, /* no primary handler */
mysensor_irq_thread, /* runs in thread context */
IRQF_ONESHOT | IRQF_TRIGGER_RISING,
"mysensor-irq", priv);
if (ret) {
dev_err(dev, "failed to request threaded IRQ %d: %d\n", irq, ret);
return ret;
}
return 0;
}
2. The threaded handler itself — sleeping is safe here
static irqreturn_t mysensor_irq_thread(int irq, void *dev_id)
{
struct mysensor_priv *priv = dev_id;
u8 value;
int ret;
/* This can sleep: I2C transfers block, and that is fine
* because we are running in a dedicated kernel thread,
* not in hardirq context. */
ret = i2c_smbus_read_byte_data(priv->client, MYSENSOR_REG_STATUS);
if (ret client->dev, "sensor read failed: %d\n", ret);
return IRQ_HANDLED;
}
value = ret;
mysensor_report_event(priv, value);
return IRQ_HANDLED;
}
3. Why IRQF_ONESHOT is required here
When the primary handler is NULL, the kernel does not unmask the interrupt line again until your thread_fn finishes. IRQF_ONESHOT tells the kernel to keep the line masked between the hardware event and the thread finishing, which prevents the same interrupt from re-firing before your thread has had a chance to service it. It is mandatory whenever you pass NULL as the primary handler.
Real-World Use Cases
- Touchscreen and sensor drivers that read data over I2C or SPI after an interrupt
- GPIO buttons that need debouncing logic with small delays
- Drivers that need to take a mutex already held by another sleeping context
- Devices where the follow-up work is too heavy for a tasklet but still latency-sensitive enough to avoid a plain workqueue
Common Mistakes with Threaded Interrupt Handlers
- Forgetting IRQF_ONESHOT when the primary handler is NULL — the kernel will refuse the registration.
- Doing blocking I/O in the primary (hardirq) handler instead of moving it into thread_fn.
- Returning IRQ_NONE from thread_fn when the interrupt was not shared — thread_fn should normally return IRQ_HANDLED.
- Assuming thread_fn runs immediately — it is scheduled like any other kernel thread and can be delayed under heavy system load.
Best Practices
- Default to devm_request_threaded_irq() so cleanup is automatic, exactly as with devm_request_irq()
- Use a real primary handler only when you need a fast acknowledgement step before the slower thread work
- Keep the primary handler limited to checking and clearing hardware status
- Always pair a NULL primary handler with IRQF_ONESHOT
Performance Considerations
Threaded handlers add a small scheduling latency compared to a plain hardirq handler, since the kernel has to wake and schedule a thread rather than run inline. For most sensor and slow-bus drivers this is negligible; for genuinely time-critical, sub-microsecond work, keep that portion in the primary handler and thread only the slower follow-up.
Security Considerations
Because thread_fn runs in normal process context, treat any data it reads from hardware (over I2C, SPI, or shared memory) as untrusted input and validate lengths and ranges before acting on it, just as you would in any other kernel code path.
Summary / Key Takeaways
- A threaded interrupt handler in the Linux kernel moves real work out of hardirq context into a schedulable kernel thread
- request_threaded_irq() takes both a primary handler and a thread_fn; either can be NULL, but not both
- IRQF_ONESHOT is mandatory when the primary handler is NULL
- Prefer the managed devm_request_threaded_irq() variant in modern drivers
Conclusion
Threaded interrupt handlers are the standard tool for any Linux driver whose interrupt-time work needs to sleep or take meaningful time. Combined with the managed IRQ allocation from the previous lecture, devm_request_threaded_irq() gives you a clean, leak-free, modern way to handle interrupts in any kernel-6.x driver. That wraps up this part of the free Linux kernel programming course — the next lecture continues with more device driver fundamentals.
Frequently Asked Questions (FAQ)
1. Can both the primary handler and thread_fn be NULL?
No, at least one of them must be a real function, otherwise request_threaded_irq() returns an error.
2. Is IRQF_ONESHOT always required?
It is required only when the primary handler is NULL. If you supply a real primary handler, IRQF_ONESHOT is optional.
3. Can thread_fn use mutexes and sleep?
Yes, that is the entire point of a threaded interrupt handler in the Linux kernel — thread_fn runs in normal process context.
4. Does devm_request_threaded_irq() also clean up automatically?
Yes, exactly like devm_request_irq(), the registration is tied to the device and released automatically on removal.
5. What does IRQ_WAKE_THREAD mean?
It is the return value a primary handler gives when it wants the kernel to schedule the associated thread_fn afterwards.
6. Is a threaded handler slower than a plain hardirq handler?
There is a small extra scheduling delay because a kernel thread has to be woken, but for most peripheral drivers this is not noticeable.
7. When should I use a plain handler instead of a threaded one?
When the interrupt-time work is tiny and never sleeps, such as reading one register and clearing a flag.
8. Do threaded interrupt handlers work the same way on all architectures?
Yes, request_threaded_irq() is part of the generic interrupt subsystem and behaves the same across architectures supported by the mainline kernel.
Continue the Free Linux Kernel Programming Course
Explore more lectures on interrupt handling, kernel modules, and device driver fundamentals.
Course Index Next Lecture
2 Comments