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

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:

  1. Primary Handler (Top Half): Runs in atomic context (interrupts disabled). It performs minimal work (e.g., acknowledging the interrupt) and schedules the threaded handler.
  2. Threaded Handler (Bottom Half): Runs in a dedicated kernel thread (process context), where sleeping and blocking operations are allowed.

Key Benefits:

  • Simplified Code: No need for separate workqueues or tasklets.
  • Sleep-Safe Operations: Threaded handlers can use blocking APIs.
  • Priority Control: Kernel threads can be assigned real-time priorities.


Why Use Threaded IRQs?

  1. Avoid Workqueue Overhead: Threaded IRQs eliminate the need to manage a separate workqueue.
  2. Atomic-to-Process Context Transition: Handled automatically by the kernel.
  3. Edge Cases Simplified: No risk of missing flags like IRQF_ONESHOT with proper configuration.


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)

  • Purpose: Minimal work in atomic context.
  • Action: Prints a log and returns IRQ_WAKE_THREAD to schedule the threaded handler.
  • Constraints: Cannot sleep or call blocking functions.

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)

  • Purpose: Perform actual work in process context.
  • Action: Toggles the LED using gpio_set_value().
  • Safety: Can sleep or call blocking APIs (use gpio_set_value_cansleep() for slow GPIOs).

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)

  • GPIO Setup: Configures the button as input and LED as output.
  • IRQ Mapping: Converts the GPIO number to an IRQ using gpio_to_irq().
  • Threaded IRQ Registration: Links the primary and threaded handlers via request_threaded_irq().

ret = request_threaded_irq(irq_number, 
                          gpio_irq_handler_quick,
                          gpio_irq_handler_thread,
                          IRQF_TRIGGER_RISING | IRQF_ONESHOT, 
                          "gpio16_irq", NULL);
        

Flags Explained:

  • IRQF_TRIGGER_RISING: Interrupt triggers on rising edge (button press).
  • IRQF_ONESHOT: Keeps the interrupt disabled until the threaded handler completes.

4. Cleanup (gpio_irq_exit)

  • Frees the IRQ and GPIOs.


Key Takeaways

  1. Use IRQF_ONESHOT for Level-Triggered Interrupts: Prevents interrupt storms by keeping the IRQ disabled until the threaded handler finishes. For edge-triggered interrupts (like this example), it’s optional but still good practice.
  2. GPIO Context Matters:
  3. Debugging Threaded IRQs:


Threaded IRQs vs. Workqueues

Article content

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!

Nikunj Savani

Senior Software Engineer @ Infineon Technologies | Advance Embedded System

3mo

Thanks for sharing in detais article, David

Like
Reply

To view or add a comment, sign in

Others also viewed

Explore topics