Linux Interrupt Fundamentals
Interrupt API Functions
Interrupt Identifiers
Each interrupt has a unique numerical identifier used for differentiation. The Linux kernel represents this identifier using an integer variable.
request_irq Function
Interrupts must be explicitly registered in Linux. The request_irq function activates and enables an interrupt. Note that this function may sleep and cannot be used in interrupt contexts or non-sleep-safe code segments.
int register_interrupt(unsigned int irq_num,
irq_handler_t isr,
unsigned long attributes,
const char *label,
void *device_ref)
{
/* Registers and enables interrupt handler */
}
| Flag | Description |
|---|---|
| IRQF_SHARED | Multiple devices share interrupt line |
| IRQF_ONESHOT | Single-shot interrupt |
| IRQF_TRIGGER_RISING | Rising-edge trigger |
| IRQF_TRIGGER_FALLING | Falling-edge trigger |
free_irq Function
Registered interrupts must be released using free_irq. For non-shared interrupts, this disables the interrupt and removes the handler.
void release_interrupt(unsigned int irq_num, void *device_ref);
Interrupt Service Routines
typedef irqreturn_t (*isr_func)(int, void*);
enum interrupt_status {
IRQ_UNHANDLED = 0,
IRQ_PROCESSED = 1,
IRQ_WAKE_THREAD = 2
};
Interrupt Control Functions
void activate_irq(unsigned int irq_num);
void deactivate_irq(unsigned int irq_num);
void deactivate_irq_async(unsigned int irq_num);
void local_irq_enable(void);
void local_irq_disable(void);
void save_local_irq(unsigned long *state);
void restore_local_irq(unsigned long state);
Top and Bottom Halves
Interrupt processing is divided into two segments:
- Top Half: Fast, non-blocking operations
- Bottom Half: Time-consuming processing deferred for later execution
Deferred Execution Mechanisms
Tasklets
struct tasklet_struct {
struct tasklet_struct *next_task;
unsigned long status;
atomic_t ref_count;
void (*callback)(unsigned long);
unsigned long callback_data;
};
void setup_tasklet(struct tasklet_struct *t,
void (*func)(unsigned long),
unsigned long data);
Work Queues
struct work_struct {
/* Work queue structure */
};
#define INIT_WORK(work_ptr, work_func)
#define DECLARE_WORK(work_name, func_ptr)
bool schedule_work(struct work_struct *work_item);
Device Tree Interrupt Configuration
GIC Controller
Example device tree node:
interrupt_controller@a0021000 {
compatible = "arm,cortex-a7-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xa0021000 0x1000>,
<0xa0022000 0x2000>;
};
EXTI Controller
external_interrupts: interrupt-controller@5000d000 {
compatible = "st,stm32mp1-exti", "syscon";
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x5000d000 0x400>;
};
Interrupt Handling Implementation
struct custom_gpio_device {
struct gpio_desc *gpio;
unsigned int irq_id;
struct timer_list debounce_timer;
spinlock_t lock;
};
static irqreturn_t gpio_isr(int irq, void *dev_id) {
struct custom_gpio_device *dev = dev_id;
mod_timer(&dev->debounce_timer,
jiffies + msecs_to_jiffies(15));
return IRQ_HANDLED;
}
static void debounce_handler(struct timer_list *t) {
struct custom_gpio_device *dev =
container_of(t, struct custom_gpio_device, debounce_timer);
unsigned long flags;
spin_lock_irqsave(&dev->lock, flags);
/* Read and process GPIO state */
spin_unlock_irqrestore(&dev->lock, flags);
}
static int configure_gpio_interrupt(struct device_node *node) {
struct custom_gpio_device *dev;
int gpio_num = of_get_named_gpio(node, "interrupt-gpio", 0);
unsigned int irq_id = irq_of_parse_and_map(node, 0);
/* GPIO and interrupt setup */
return request_irq(irq_id, gpio_isr, IRQF_TRIGGER_BOTH,
"gpio_irq", dev);
}
Driver Initialization
static int __init gpio_driver_init(void) {
struct device_node *node = of_find_node_by_path("/gpio_device");
/* Device tree parsing */
/* GPIO and interrupt configuration */
/* Character device setup */
timer_setup(&dev->debounce_timer, debounce_handler, 0);
return 0;
}