3. 中断注册与注销
中断处理程序的注册与注销是驱动开发中最基础也最关键的操作之一。Linux 内核提供了 request_irq、free_irq 以及资源管理版本 devm_request_irq 三个核心接口。理解它们的参数语义、行为差异以及使用场景,是编写健壮中断驱动的前提。
3.1 request_irq:注册中断处理程序
request_irq 是内核中最经典的中断注册函数,它将一个中断号与一个处理函数绑定。当硬件产生该中断时,内核会调用注册的处理函数。
函数原型:
int request_irq(unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev);
| 参数 | 类型 | 说明 |
|---|---|---|
irq |
unsigned int |
中断号,通常由 platform_get_irq 或 irq_of_parse_and_map 获取 |
handler |
irq_handler_t |
中断处理函数指针,返回 irqreturn_t |
flags |
unsigned long |
中断标志位,控制触发方式、共享属性等 |
name |
const char * |
设备名称,用于 /proc/interrupts 显示 |
dev |
void * |
传递给处理函数的设备标识,共享中断时用于区分设备 |
3.1.1 flags 标志位详解
flags 参数决定了中断的触发方式和行为特性,常用标志如下:
- IRQF_TRIGGER_RISING:上升沿触发
- IRQF_TRIGGER_FALLING:下降沿触发
- IRQF_TRIGGER_HIGH:高电平触发
- IRQF_TRIGGER_LOW:低电平触发
- IRQF_SHARED:允许共享中断线,多个设备共用同一中断号
- IRQF_ONESHOT:中断处理期间屏蔽该中断线,适用于 threaded IRQ
- IRQF_NO_SUSPEND:系统休眠时不关闭该中断
最佳实践:对于共享中断(IRQF_SHARED),必须提供
dev 参数,且处理函数中应首先检查中断是否来自本设备,避免误处理其他设备的中断。
3.1.2 中断处理函数签名
irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
struct my_device *dev = (struct my_device *)dev_id;
/* 检查中断是否来自本设备 */
if (!(readl(dev->reg_base + INT_STATUS) & INT_PENDING))
return IRQ_NONE;
/* 处理中断事件 */
writel(INT_CLEAR, dev->reg_base + INT_CLEAR);
/* 调度下半部(如 tasklet 或 workqueue) */
tasklet_schedule(&dev->tasklet);
return IRQ_HANDLED;
}
注意:中断处理函数运行在中断上下文中,不可睡眠、不可使用
mutex、不可访问用户空间。耗时操作应推迟到下半部执行。
3.2 free_irq:注销中断处理程序
当驱动卸载或设备移除时,必须释放已注册的中断。使用 free_irq 可以安全地移除中断处理程序。
函数原型:
void free_irq(unsigned int irq, void *dev_id);
| 参数 | 说明 |
|---|---|
irq |
要释放的中断号 |
dev_id |
注册时传入的设备标识,用于匹配共享中断 |
3.2.1 使用注意事项
- 必须与 request_irq 配对使用:注册时传入的
dev_id必须与释放时一致,否则会导致释放失败或误释放其他设备的中断。 - 确保中断处理已完成:
free_irq会等待当前正在执行的中断处理函数返回后才返回,因此是安全的。 - 共享中断的释放:对于共享中断,
free_irq只会移除与dev_id匹配的处理程序,其他设备的中断处理不受影响。
常见错误:在中断处理函数中直接调用
free_irq 释放自身,可能导致死锁或内核崩溃。应在进程上下文中执行释放操作。
3.3 devm_request_irq:资源管理版本
devm_request_irq 是内核提供的设备资源管理(devres)版本,它自动跟踪中断的注册状态,在驱动卸载或设备移除时自动释放中断,无需手动调用 free_irq。
函数原型:
int devm_request_irq(struct device *dev,
unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev_id);
| 参数 | 说明 |
|---|---|
dev |
请求中断的设备指针,用于资源生命周期管理 |
irq |
中断号 |
handler |
中断处理函数 |
flags |
中断标志位 |
name |
设备名称 |
dev_id |
设备标识 |
3.3.1 与传统接口的对比
| 特性 | request_irq / free_irq | devm_request_irq |
|---|---|---|
| 手动释放 | 必须调用 free_irq | 自动释放 |
| 错误路径处理 | 需手动管理 goto 标签 | 自动回滚 |
| 适用场景 | 非设备模型、特殊生命周期 | 标准 platform/i2c/spi 驱动 |
| 代码简洁性 | 较低,需配对调用 | 较高,减少遗漏风险 |
| 内核版本 | 所有版本 | 2.6.32+ |
3.3.2 典型使用示例
static int my_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct my_device *mydev;
int irq, ret;
mydev = devm_kzalloc(dev, sizeof(*mydev), GFP_KERNEL);
if (!mydev)
return -ENOMEM;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* 使用 devm 版本注册中断,无需手动 free_irq */
ret = devm_request_irq(dev, irq, my_interrupt_handler,
IRQF_TRIGGER_RISING | IRQF_SHARED,
"my_device", mydev);
if (ret) {
dev_err(dev, "failed to request irq %d\n", irq);
return ret;
}
platform_set_drvdata(pdev, mydev);
return 0;
}
static int my_remove(struct platform_device *pdev)
{
/* 中断自动释放,无需显式调用 free_irq */
return 0;
}
推荐使用:在新驱动开发中,优先使用
devm_request_irq。它不仅能减少代码量,还能避免因错误路径遗漏 free_irq 导致的中断泄漏问题。
3.4 中断注册与注销的常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 中断注册失败 -EBUSY | 中断号已被占用,且未设置 IRQF_SHARED | 确认硬件中断号,或添加 IRQF_SHARED 标志 |
| 中断处理函数未调用 | 中断控制器未使能,或 flags 触发方式不匹配 | 检查设备树/ACPI 配置,确认触发方式 |
| 共享中断误处理 | 未检查中断状态寄存器,直接返回 IRQ_HANDLED | 处理函数中先检查中断源,非本设备返回 IRQ_NONE |
| 中断泄漏 | 驱动卸载时未调用 free_irq | 使用 devm_request_irq 或确保错误路径释放 |
| 中断上下文睡眠 | 在中断处理函数中调用可能导致睡眠的函数 | 使用 threaded IRQ 或将耗时操作移至下半部 |
调试技巧:通过
/proc/interrupts 可以查看当前系统中所有中断的注册情况、触发次数以及绑定的处理函数名称,是排查中断问题的第一站。