3. 中断申请API:request_irq()函数原型、参数详解与返回值处理

在Linux内核驱动中,中断的申请是设备与CPU异步通信的核心环节。request_irq()是驱动开发者最常使用的中断注册接口。理解其每个参数的含义、传递方式以及返回值的处理逻辑,是编写健壮中断处理程序的基础。

3.1 函数原型与头文件

request_irq()定义在 <linux/interrupt.h> 中,其函数原型如下:

int request_irq(unsigned int irq,
                irq_handler_t handler,
                unsigned long flags,
                const char *name,
                void *dev_id);

该函数用于向内核注册一个中断处理程序。成功返回0,失败返回负的错误码。

注意:在较新的内核版本(如5.x及以后)中,request_irq() 底层已封装为 request_threaded_irq() 的简化调用,但参数语义完全一致。本课程以经典 request_irq() 为准。

3.2 参数详解

下表列出了 request_irq() 五个参数的类型、含义及典型用法:

参数 类型 含义 典型值 / 说明
irq unsigned int 中断号 硬件中断线编号,可通过 platform_get_irq() 或设备树获取
handler irq_handler_t 中断处理函数指针 类型为 irqreturn_t (*handler)(int, void *)
flags unsigned long 中断标志位 控制触发方式、共享属性等,见下方标志位表格
name const char * 中断名称 用于 /proc/interrupts 显示,建议使用驱动名称
dev_id void * 设备标识指针 共享中断时用于区分设备;通常传递设备结构体指针

3.2.1 irq — 中断号

中断号是系统全局唯一的整数标识,用于关联特定的硬件中断线。在设备树驱动的 probe() 函数中,通常通过以下方式获取:

int irq = platform_get_irq(pdev, 0);
if (irq < 0) {
    dev_err(&pdev->dev, "failed to get IRQ\n");
    return irq;
}

3.2.2 handler — 中断处理函数

处理函数必须符合 irq_handler_t 类型,返回值为 irqreturn_t 枚举:

static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_device *dev = (struct my_device *)dev_id;
    // 处理中断事件
    return IRQ_HANDLED;   // 或 IRQ_NONE
}
  • IRQ_HANDLED:中断已被本处理程序正确处理。
  • IRQ_NONE:中断不是本设备产生的,用于共享中断场景。
  • IRQ_WAKE_THREAD:唤醒中断线程(仅在线程化中断中使用)。

3.2.3 flags — 中断标志位

标志位通过位或运算组合,控制中断的触发方式、唤醒行为等。常用标志如下:

标志 值 含义
IRQF_TRIGGER_RISING 0x00000001 上升沿触发
IRQF_TRIGGER_FALLING 0x00000002 下降沿触发
IRQF_TRIGGER_HIGH 0x00000004 高电平触发
IRQF_TRIGGER_LOW 0x00000008 低电平触发
IRQF_SHARED 0x00000080 允许共享中断线
IRQF_NO_SUSPEND 0x00004000 系统休眠时不关闭该中断
常见错误:共享中断时,必须设置 IRQF_SHARED 标志,且所有共享该中断线的驱动必须传递唯一的 dev_id,否则内核无法区分中断来源。

3.2.4 name — 中断名称

该名称会出现在 /proc/interrupts 中,便于调试。建议使用驱动名称或设备名称,如 "my_i2c_driver"。

3.2.5 dev_id — 设备标识指针

该参数在共享中断时至关重要。内核在调用中断处理函数时会将 dev_id 原样传回。通常传递设备结构体指针,以便在中断处理函数中访问设备私有数据。对于非共享中断,可以传递 NULL,但不推荐。

3.3 返回值处理

request_irq() 的返回值必须检查。成功返回0,失败返回负的错误码:

错误码 含义 典型原因
-EINVAL 参数无效 中断号超出范围,或 handler 为 NULL
-EBUSY 中断已被占用 中断线已被注册且未设置 IRQF_SHARED
-ENOMEM 内存不足 内核无法分配中断描述符

标准错误处理模式:

int ret = request_irq(irq, my_irq_handler, IRQF_TRIGGER_RISING,
                      "my_device", dev);
if (ret) {
    dev_err(dev->dev, "request_irq failed: %d\n", ret);
    return ret;
}

3.4 中断申请流程SVG图

下图展示了从驱动 probe() 到中断注册完成的典型流程:

中断申请流程:从 probe() 到 request_irq() 驱动 probe() 入口 platform_get_irq() 获取中断号 检查 irq < 0 ? 返回错误 构造 flags (触发方式 + 共享) request_irq(irq, handler, flags, name, dev_id) 检查 ret != 0 ? 释放资源 / 返回 中断注册完成

3.5 实践要点与避坑指南

  • 共享中断必须传递非空 dev_id:内核通过 dev_id 区分不同驱动的中断处理程序,传递 NULL 会导致 free_irq() 无法正确注销。
  • 中断处理函数必须快速执行:在 handler 中不能调用可能导致睡眠的函数(如 kmalloc(GFP_KERNEL)、mutex_lock())。需要耗时操作时,应使用 request_threaded_irq() 配合线程化中断。
  • flags 与设备树匹配:如果设备树中指定了 interrupts 属性,驱动中 flags 应设置为 IRQF_TRIGGER_NONE(0),让内核从设备树解析触发方式,避免冲突。
  • 返回值检查不可省略:即使中断号看起来正确,也可能因资源冲突导致 -EBUSY,必须检查返回值并做错误回滚。
最佳实践:在 probe() 中申请中断后,应在 remove() 中调用 free_irq(irq, dev_id) 释放中断。如果中断是共享的,dev_id 必须与 request_irq() 时传递的指针一致。