3. 中断注册与注销:request_irq、free_irq、devm_request_irq 接口详解

中断处理程序的注册与注销是驱动开发中最基础也最关键的操作之一。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、不可访问用户空间。耗时操作应推迟到下半部执行。
request_irq 注册流程 驱动调用 request_irq 内核中断子系统 创建 irqaction 注册到 irq_desc 链表 使能中断控制器 关键数据结构 irq_desc:每个中断号 对应一个描述符

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 导致的中断泄漏问题。
中断注册接口对比 request_irq 需手动 free_irq 错误路径易遗漏 free_irq 配对调用 等待处理完成 devm_request_irq 自动释放 推荐使用 devm 版本:资源生命周期绑定设备,自动回滚

3.4 中断注册与注销的常见问题

问题 原因 解决方案
中断注册失败 -EBUSY 中断号已被占用,且未设置 IRQF_SHARED 确认硬件中断号,或添加 IRQF_SHARED 标志
中断处理函数未调用 中断控制器未使能,或 flags 触发方式不匹配 检查设备树/ACPI 配置,确认触发方式
共享中断误处理 未检查中断状态寄存器,直接返回 IRQ_HANDLED 处理函数中先检查中断源,非本设备返回 IRQ_NONE
中断泄漏 驱动卸载时未调用 free_irq 使用 devm_request_irq 或确保错误路径释放
中断上下文睡眠 在中断处理函数中调用可能导致睡眠的函数 使用 threaded IRQ 或将耗时操作移至下半部
调试技巧:通过 /proc/interrupts 可以查看当前系统中所有中断的注册情况、触发次数以及绑定的处理函数名称,是排查中断问题的第一站。