3. 中断注册与注销:request_irq()、free_irq()、中断标志(IRQF_*)详解
中断处理程序的注册与注销是驱动开发中最基础也最关键的环节。正确使用 request_irq() 和 free_irq(),并理解各类中断标志的语义,直接决定了中断响应延迟、系统稳定性和调试效率。本节将深入剖析这两个核心 API 的用法、参数细节以及常见陷阱。
3.1 request_irq():中断处理程序的注册入口
request_irq() 是 Linux 内核提供给驱动程序注册中断处理函数的标准接口。其函数原型如下:
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev);
各参数含义与注意事项:
| 参数 | 类型 | 说明 | 常见陷阱 |
|---|---|---|---|
| irq | unsigned int | 中断号,通常由 platform_get_irq() 或 pci_irq_vector() 获取 |
硬编码中断号会导致移植性灾难 |
| handler | irq_handler_t | 中断处理函数指针,返回 irqreturn_t |
函数必须使用 __irq 注解,且不能睡眠 |
| flags | unsigned long | 中断标志位,通过 IRQF_* 宏组合 |
标志选择错误会导致中断丢失或性能下降 |
| name | const char * | 设备名称,用于 /proc/interrupts 显示 |
名称应唯一且具有辨识度 |
| dev | void * | 传递给处理函数的设备标识,通常为设备结构体指针 | 共享中断时必须提供非 NULL 值用于区分 |
最佳实践: 始终使用设备树或 ACPI 获取中断号,避免硬编码。在 probe 函数中尽早注册中断,在 remove 函数中及时注销。
3.2 中断标志 IRQF_* 详解
中断标志是 request_irq() 的核心参数,决定了中断的触发方式、共享策略和电源管理行为。以下是常用标志的详细对比:
| 标志宏 | 值 | 含义 | 适用场景 |
|---|---|---|---|
| IRQF_TRIGGER_NONE | 0x00000000 | 无触发方式,由硬件默认决定 | 极少使用,仅用于特殊硬件 |
| IRQF_TRIGGER_RISING | 0x00000001 | 上升沿触发 | GPIO 按键、边缘敏感设备 |
| IRQF_TRIGGER_FALLING | 0x00000002 | 下降沿触发 | 中断控制器、下降沿信号设备 |
| IRQF_TRIGGER_HIGH | 0x00000004 | 高电平触发 | 电平敏感设备、共享中断 |
| IRQF_TRIGGER_LOW | 0x00000008 | 低电平触发 | 低电平有效设备 |
| IRQF_SHARED | 0x00000080 | 允许共享中断线 | 多个设备共用同一中断线 |
| IRQF_PROBE_SHARED | 0x00000100 | 中断共享探测标志 | 驱动探测阶段使用 |
| IRQF_NO_SUSPEND | 0x00004000 | 系统休眠时不关闭中断 | 唤醒源设备、实时时钟 |
| IRQF_FORCE_RESUME | 0x00008000 | 系统唤醒时强制恢复中断 | 与 IRQF_NO_SUSPEND 配合使用 |
| IRQF_NO_THREAD | 0x00020000 | 禁止中断线程化 | 高实时性要求的中断 |
| IRQF_EARLY_RESUME | 0x00040000 | 在早期恢复阶段启用中断 | 需要提前恢复中断的设备 |
| IRQF_ONESHOT | 0x00002000 | 中断处理期间屏蔽同线中断 | 线程化中断、电平触发中断 |
关键警告: 使用
IRQF_SHARED 时,必须确保所有共享该中断线的驱动都正确实现了中断处理函数的返回值检查。如果某个驱动返回 IRQ_NONE 但实际并未处理,会导致内核误判为“伪中断”,进而触发错误处理机制。
3.3 中断注册流程与标志组合策略
下图展示了典型的中断注册流程以及标志组合的决策路径:
3.4 free_irq():中断注销的正确姿势
free_irq() 用于在驱动卸载或设备移除时释放中断资源。其原型如下:
void free_irq(unsigned int irq, void *dev);
注销中断时需注意以下关键点:
- 参数一致性:
irq和dev必须与request_irq()时传入的值完全一致。 - 调用时机: 必须在所有可能触发中断的硬件操作停止之后调用,否则可能导致野指针访问。
- 共享中断处理: 对于共享中断,
free_irq()仅移除当前设备的处理函数,其他设备不受影响。 - 同步保证:
free_irq()会等待当前正在执行的中断处理函数完成,但不会等待线程化中断的线程退出。
典型注销顺序:
- 屏蔽设备中断(通过设备寄存器或
disable_irq()) - 确保所有 DMA 和硬件操作已停止
- 调用
free_irq()释放中断线 - 释放设备数据结构
3.5 中断注册与注销的常见陷阱
| 陷阱 | 现象 | 解决方案 |
|---|---|---|
| 中断号错误 | 注册失败,返回 -EINVAL | 使用 platform_get_irq() 动态获取 |
| 标志冲突 | 中断行为异常,如丢失中断 | 检查设备树中 interrupts 属性与标志一致性 |
| 共享中断 dev 参数为 NULL | 无法区分中断来源,导致误处理 | 始终传入设备结构体指针 |
| 注销顺序错误 | 内核崩溃或设备仍在产生中断 | 先屏蔽硬件中断,再调用 free_irq() |
| 线程化中断未同步 | 中断线程仍在运行,访问已释放内存 | 使用 synchronize_irq() 确保线程退出 |
3.6 实战代码示例:完整的中断注册与注销
以下代码展示了一个典型的中断注册与注销实现:
#include <linux/interrupt.h>
#include <linux/device.h>
struct my_device {
void __iomem *base;
int irq;
struct device *dev;
};
/* 中断处理函数 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
struct my_device *mydev = (struct my_device *)dev_id;
u32 status;
status = ioread32(mydev->base + REG_STATUS);
if (!(status & INT_PENDING))
return IRQ_NONE; /* 非本设备中断 */
/* 处理中断事件 */
iowrite32(INT_CLEAR, mydev->base + REG_CLEAR);
/* 调度 tasklet 或 workqueue 处理数据 */
return IRQ_HANDLED;
}
/* 中断注册 */
static int my_device_request_irq(struct my_device *mydev)
{
unsigned long flags = IRQF_TRIGGER_HIGH | IRQF_SHARED;
int ret;
ret = request_irq(mydev->irq, my_irq_handler,
flags, "my_device", mydev);
if (ret) {
dev_err(mydev->dev, "Failed to request IRQ %d: %d\n",
mydev->irq, ret);
return ret;
}
return 0;
}
/* 中断注销 */
static void my_device_free_irq(struct my_device *mydev)
{
/* 1. 屏蔽设备中断 */
iowrite32(0, mydev->base + REG_INT_ENABLE);
/* 2. 确保硬件不再产生中断 */
mb(); /* 内存屏障 */
/* 3. 释放中断线 */
free_irq(mydev->irq, mydev);
}
调试技巧: 通过
/proc/interrupts 可以查看当前系统中所有中断的注册情况、触发次数以及对应的驱动名称。这是排查中断注册问题的第一站。
3.7 中断标志组合速查表
以下表格列出了最常见的标志组合及其典型应用场景:
| 组合 | 典型场景 | 说明 |
|---|---|---|
IRQF_TRIGGER_RISING |
GPIO 按键 | 简单边缘触发,无需共享 |
IRQF_TRIGGER_HIGH | IRQF_SHARED |
PCI 设备共享中断 | 电平触发,多个设备共用中断线 |
IRQF_TRIGGER_LOW | IRQF_ONESHOT |
线程化中断 | 电平触发,防止处理期间重复触发 |
IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND |
唤醒源设备 | 系统休眠时仍可唤醒 |
IRQF_TRIGGER_HIGH | IRQF_SHARED | IRQF_NO_THREAD |
高实时性共享中断 | 禁止线程化,保证最低延迟 |