Threaded IRQs in Linux: Simplifying Interrupt Handling with the Raspberry Pi GPIO Example
Threaded IRQs in Linux: Simplifying Interrupt Handling with the Raspberry Pi GPIO Example
Interrupt handling in the Linux kernel requires careful coordination between hardware responsiveness and software safety. Traditional interrupt handling splits work into a "top half" (atomic context) and a "bottom half" (deferred work). While mechanisms like workqueues or tasklets are common for deferred work, threaded IRQs offer a streamlined alternative by integrating the bottom half into a dedicated kernel thread. This article explores threaded IRQs through a practical example: toggling an LED on a Raspberry Pi using a GPIO button interrupt.
What Are Threaded IRQs?
A threaded IRQ is an interrupt handling mechanism that splits processing into two parts:
Key Benefits:
Why Use Threaded IRQs?
Full Code Example: Toggling an LED with Threaded IRQs
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#define GPIO_BUTTON_PIN 587
#define GPIO_LED_PIN 625
static int irq_number;
static int led_state = 0; // Track LED state
// Primary handler (top half) - runs in interrupt context
static irqreturn_t gpio_irq_handler_quick(int irq, void *dev_id) {
printk(KERN_INFO "gpiochip pinctrl-rp1 GPIO16 interrupt triggered!\n");
return IRQ_WAKE_THREAD; // Schedule the threaded handler
}
// Threaded handler (bottom half) - runs in process context
static irqreturn_t gpio_irq_handler_thread(int irq, void *dev_id) {
// Toggle LED state
led_state = !led_state;
gpio_set_value(GPIO_LED_PIN, led_state);
printk(KERN_INFO "LED toggled to %s\n", led_state ? "ON" : "OFF");
return IRQ_HANDLED;
}
// Module initialization
static int __init gpio_irq_init(void) {
int ret;
printk(KERN_INFO "----------------------------------\n gpio_irq_init start\n");
// Request GPIO for button
if (gpio_request(GPIO_BUTTON_PIN, "gpio_irq")) {
printk(KERN_ERR "Failed to request GPIO%d\n", GPIO_BUTTON_PIN);
return -1;
}
printk(KERN_INFO "Request GPIO%d\n", GPIO_BUTTON_PIN);
// Configure button as input
gpio_direction_input(GPIO_BUTTON_PIN);
printk(KERN_INFO "Set GPIO%d as input\n", GPIO_BUTTON_PIN);
// Request GPIO for LED
if (gpio_request(GPIO_LED_PIN, "gpio_led")) {
printk(KERN_ERR "Failed to request GPIO%d for LED\n", GPIO_LED_PIN);
gpio_free(GPIO_BUTTON_PIN);
return -1;
}
printk(KERN_INFO "Request GPIO%d for LED\n", GPIO_LED_PIN);
// Configure LED as output (initialized to OFF)
gpio_direction_output(GPIO_LED_PIN, 0);
printk(KERN_INFO "Set GPIO%d as output for LED\n", GPIO_LED_PIN);
// Map GPIO button to IRQ number
irq_number = gpio_to_irq(GPIO_BUTTON_PIN);
if (irq_number < 0) {
printk(KERN_ERR "Failed to map GPIO to IRQ\n");
gpio_free(GPIO_BUTTON_PIN);
gpio_free(GPIO_LED_PIN);
return -1;
}
printk(KERN_INFO "Map GPIO%d to IRQ%d\n", GPIO_BUTTON_PIN, irq_number);
// Request threaded IRQ
ret = request_threaded_irq(irq_number,
gpio_irq_handler_quick, // Primary handler
gpio_irq_handler_thread, // Threaded handler
IRQF_TRIGGER_RISING | IRQF_ONESHOT, // Flags
"gpio16_irq", NULL);
if (ret) {
printk(KERN_ERR "Failed to request IRQ %d\n", irq_number);
gpio_free(GPIO_BUTTON_PIN);
gpio_free(GPIO_LED_PIN);
return -1;
}
printk(KERN_INFO "Request IRQ%d\n", irq_number);
printk(KERN_INFO "----------------------------------\n gpio_irq_init end\n");
return 0;
}
// Module cleanup
static void __exit gpio_irq_exit(void) {
printk(KERN_INFO "----------------------------------\n gpio_irq_exit start\n");
free_irq(irq_number, NULL);
gpio_free(GPIO_BUTTON_PIN);
gpio_free(GPIO_LED_PIN);
printk(KERN_INFO "----------------------------------\n gpio_irq_exit end\n");
}
module_init(gpio_irq_init);
module_exit(gpio_irq_exit);
MODULE_LICENSE("GPL");
Code Walkthrough
1. Primary Handler (gpio_irq_handler_quick)
static irqreturn_t gpio_irq_handler_quick(int irq, void *dev_id) {
printk(KERN_INFO "Interrupt triggered!\n");
return IRQ_WAKE_THREAD; // Wake the threaded handler
}
2. Threaded Handler (gpio_irq_handler_thread)
static irqreturn_t gpio_irq_handler_thread(int irq, void *dev_id) {
led_state = !led_state;
gpio_set_value(GPIO_LED_PIN, led_state);
printk(KERN_INFO "LED toggled\n");
return IRQ_HANDLED;
}
3. Initialization (gpio_irq_init)
ret = request_threaded_irq(irq_number,
gpio_irq_handler_quick,
gpio_irq_handler_thread,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"gpio16_irq", NULL);
Flags Explained:
4. Cleanup (gpio_irq_exit)
Key Takeaways
Threaded IRQs vs. Workqueues
Conclusion
Threaded IRQs simplify interrupt handling by unifying the top and bottom halves under a single API. The Raspberry Pi GPIO example demonstrates how to safely toggle an LED in response to a button press, leveraging the threaded handler’s ability to sleep and perform complex operations. By adopting threaded IRQs, developers reduce boilerplate code, avoid workqueue overhead, and ensure robust hardware interaction.
Next time you need to handle interrupts with deferred work, consider threaded IRQs as your go-to solution!
Senior Software Engineer @ Infineon Technologies | Advance Embedded System
3moThanks for sharing in detais article, David