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() 到中断注册完成的典型流程:
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() 时传递的指针一致。