4. 中断注册与注销:request_irq()、free_irq()、devm_request_irq() 的用法与参数详解

中断处理程序的注册与注销是中断管理的核心操作。Linux 内核提供了 request_irq()、free_irq() 以及资源管理版本 devm_request_irq() 等 API,用于将驱动程序的中断处理函数绑定到指定的 IRQ 线路上。理解这些函数的参数含义、返回值及使用场景,是编写可靠中断驱动的基础。

4.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() 或 pci_irq_vector() 获取 17、irq_of_parse_and_map() 返回值
handler irq_handler_t 中断处理函数指针,原型为 irqreturn_t (*handler)(int irq, void *dev_id) my_irq_handler
flags unsigned long 中断标志位,控制触发方式、共享属性等 IRQF_TRIGGER_RISING、IRQF_SHARED
name const char * 设备名称,用于 /proc/interrupts 标识 "my_device"
dev void * 传递给处理函数的设备 ID,共享中断时用于区分设备 &my_device、NULL
返回值:成功返回 0;失败返回负的错误码,常见为 -EBUSY(中断已被独占注册)或 -EINVAL(无效参数)。

4.2 flags 标志位详解

flags 参数通过位掩码组合控制中断行为,以下是常用标志位:

  • 触发方式标志(三选一):
    • IRQF_TRIGGER_RISING — 上升沿触发
    • IRQF_TRIGGER_FALLING — 下降沿触发
    • IRQF_TRIGGER_HIGH — 高电平触发
    • IRQF_TRIGGER_LOW — 低电平触发
  • 共享中断标志:
    • IRQF_SHARED — 允许多个设备共享同一 IRQ 线,必须配合 dev 参数使用
  • 性能与调度标志:
    • IRQF_NO_SUSPEND — 系统休眠时不禁用该中断
    • IRQF_NO_THREAD — 强制在中断上下文执行,不转为线程化
    • IRQF_ONESHOT — 中断处理完成前不重新使能中断线,常用于线程化中断
实践建议:对于边缘触发中断,通常使用 IRQF_TRIGGER_RISING | IRQF_SHARED;对于电平触发中断,优先使用 IRQF_TRIGGER_LOW | IRQF_ONESHOT 以避免中断风暴。

4.3 free_irq():中断注销函数

当驱动程序卸载或设备移除时,必须释放已注册的中断。函数原型如下:

void free_irq(unsigned int irq, void *dev_id);

参数说明:

  • irq — 要释放的中断号
  • dev_id — 注册时传入的设备 ID,用于共享中断时精确匹配
关键注意点:
  • 必须在中断处理函数可能仍在执行的上下文中调用 free_irq(),该函数会同步等待所有正在运行的处理函数完成。
  • 对于共享中断,dev_id 必须与注册时一致,否则会导致释放失败或误释放其他设备的中断。
  • 在 free_irq() 返回后,不能再访问与该中断相关的硬件资源。

4.4 devm_request_irq():资源管理版本

devm_request_irq() 是 request_irq() 的设备资源管理(devres)版本,由内核自动管理中断的释放。其原型为:

int devm_request_irq(struct device *dev,
                     unsigned int irq,
                     irq_handler_t handler,
                     unsigned long flags,
                     const char *name,
                     void *dev_id);

相比 request_irq(),多了一个 struct device *dev 参数,用于关联设备生命周期。当设备被移除或驱动卸载时,内核自动调用 free_irq(),无需手动处理。

特性 request_irq() devm_request_irq()
手动释放 必须调用 free_irq() 自动释放
适用场景 非标准设备、需要精细控制生命周期 大多数平台设备、PCI 设备
错误路径处理 需在错误处理中手动释放 自动回滚
代码简洁性 较低 较高
推荐实践:在编写新的驱动程序时,优先使用 devm_request_irq(),可以显著减少错误路径处理代码,避免遗漏 free_irq() 导致的内存泄漏或资源泄漏。

4.5 中断注册与注销流程

下图展示了标准的中断注册与注销生命周期,以及 devm 版本如何简化流程:

中断注册与注销生命周期对比 标准流程 request_irq(irq, handler, flags, name, dev) 中断处理函数执行 free_irq(irq, dev_id) 同步等待处理完成 devm 自动管理流程 devm_request_irq(dev, irq, handler, flags, name, dev_id) 中断处理函数执行 设备移除 / 驱动卸载 内核自动调用 free_irq() devm 版本省去手动释放步骤,降低遗漏风险,推荐在新驱动中使用

4.6 中断处理函数原型与返回值

中断处理函数必须遵循内核定义的签名:

irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    /* 处理中断事件 */
    return IRQ_HANDLED;
}

返回值类型 irqreturn_t 为枚举,定义如下:

  • IRQ_NONE (0) — 中断不是本设备产生的,用于共享中断链
  • IRQ_HANDLED (1) — 中断已被正确处理
  • IRQ_WAKE_THREAD (2) — 唤醒线程化中断处理函数
共享中断处理要点:当使用 IRQF_SHARED 时,处理函数必须检查硬件状态寄存器确认中断是否来自本设备。如果不是,应立即返回 IRQ_NONE,让链中的下一个处理函数继续处理。

4.7 典型注册代码示例

以下是一个使用 devm_request_irq() 的完整注册示例:

#include <linux/interrupt.h>
#include <linux/platform_device.h>

static irqreturn_t my_device_irq_handler(int irq, void *dev_id)
{
    struct my_device *mydev = (struct my_device *)dev_id;
    u32 status;

    status = readl(mydev->regs + STATUS_REG);
    if (!(status & IRQ_PENDING))
        return IRQ_NONE;

    /* 处理中断 */
    writel(status, mydev->regs + CLEAR_REG);
    mydev->irq_count++;

    return IRQ_HANDLED;
}

static int my_device_probe(struct platform_device *pdev)
{
    struct my_device *mydev;
    int irq, ret;

    mydev = devm_kzalloc(&pdev->dev, sizeof(*mydev), GFP_KERNEL);
    if (!mydev)
        return -ENOMEM;

    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    ret = devm_request_irq(&pdev->dev, irq,
                           my_device_irq_handler,
                           IRQF_TRIGGER_RISING | IRQF_SHARED,
                           "my_device", mydev);
    if (ret) {
        dev_err(&pdev->dev, "failed to request IRQ %d\n", irq);
        return ret;
    }

    platform_set_drvdata(pdev, mydev);
    return 0;
}
代码要点:
  • 使用 platform_get_irq() 获取设备树或 ACPI 中定义的中断号
  • devm_request_irq() 绑定到设备生命周期,无需在 remove 中调用 free_irq()
  • 共享中断时,处理函数通过 dev_id 获取设备私有数据,并检查硬件状态

4.8 常见错误与调试

错误场景 现象 解决方案
中断号无效 request_irq() 返回 -EINVAL 检查 platform_get_irq() 返回值,确认中断控制器配置正确
中断已被独占注册 返回 -EBUSY 确认是否使用了 IRQF_SHARED,或检查是否有其他驱动占用了该 IRQ
共享中断未检查硬件 系统卡死或中断风暴 在处理函数开头检查硬件状态寄存器,非本设备中断立即返回 IRQ_NONE
dev_id 不匹配 free_irq() 无法释放 确保注册和注销时传入相同的 dev_id 指针
调试技巧:通过 /proc/interrupts 查看当前系统中断注册情况,确认中断号、触发方式、处理函数名称及中断计数是否正常。使用 cat /proc/interrupts | grep my_device 快速过滤目标设备信息。