4. 注册中断:request_irq 函数详解
在 Linux 驱动中,中断处理程序的注册是设备驱动与内核中断子系统交互的核心接口。request_irq 函数负责将驱动程序的中断处理函数挂载到指定的 IRQ 线上,使内核在接收到硬件中断信号时能够正确调用驱动层的处理逻辑。本节将深入解析该函数的原型、各参数含义、返回值及典型使用模式。
4.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,失败时返回负的错误码(如 -EBUSY、-EINVAL)。
4.2 参数详解
| 参数 | 类型 | 含义 | 典型值 / 说明 |
|---|---|---|---|
irq |
unsigned int |
中断请求号,标识硬件中断线 | 通过 platform_get_irq() 获取,或直接使用硬件手册中的 IRQ 号 |
handler |
irq_handler_t |
中断处理函数指针 | 函数类型为 irqreturn_t (*)(int, void *) |
flags |
unsigned long |
中断管理标志位 | 控制触发方式、共享属性、快速/慢速处理等 |
name |
const char * |
中断名称,用于 /proc/interrupts 显示 |
通常设为驱动名称,如 "my_uart" |
dev_id |
void * |
设备标识指针,用于共享中断区分和释放时匹配 | 通常传入设备结构体指针,共享中断时必须唯一 |
4.3 flags 标志位详解
flags 参数是中断行为控制的关键,常用标志位定义在 <linux/interrupt.h> 中:
| 标志位 | 值 | 作用 |
|---|---|---|
IRQF_TRIGGER_RISING |
0x00000001 | 上升沿触发 |
IRQF_TRIGGER_FALLING |
0x00000002 | 下降沿触发 |
IRQF_TRIGGER_HIGH |
0x00000004 | 高电平触发 |
IRQF_TRIGGER_LOW |
0x00000008 | 低电平触发 |
IRQF_SHARED |
0x00000080 | 允许多个设备共享同一 IRQ 线 |
IRQF_ONESHOT |
0x00002000 | 中断处理期间屏蔽该 IRQ,处理完自动重新使能 |
IRQF_NO_SUSPEND |
0x00004000 | 系统休眠时不关闭该中断 |
实践建议:对于边缘触发的中断(如 GPIO 按键),推荐使用
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING 双沿触发;对于共享中断总线(如 PCI),必须设置 IRQF_SHARED 并确保 dev_id 唯一。
4.4 dev_id 参数的关键作用
dev_id 是 request_irq 中最容易被忽视但极其重要的参数,其核心用途包括:
- 共享中断区分:当多个驱动共享同一 IRQ 线时,内核遍历该 IRQ 上的所有处理函数,通过
dev_id匹配当前设备。处理函数内部需检查硬件状态确认是否为本设备产生的中断。 - 释放中断匹配:
free_irq(irq, dev_id)必须传入与注册时相同的dev_id,否则释放失败。 - 传递设备上下文:处理函数第二个参数即为
dev_id,驱动可将设备结构体指针传入,避免使用全局变量。
常见错误:在非共享中断中将
dev_id 设为 NULL 虽然可以工作,但会丧失设备上下文传递能力,且 free_irq 时无法精确匹配。强烈建议始终传入非空指针。
4.5 中断注册流程 SVG 图
下图展示了从驱动入口到中断注册完成的完整调用链路:
4.6 典型代码示例
以下是一个完整的 request_irq 注册示例,展示了从获取 IRQ 到注册完成的完整流程:
#include <linux/interrupt.h>
#include <linux/platform_device.h>
struct my_device {
int irq;
void __iomem *base;
/* 其他设备数据 */
};
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
struct my_device *mdev = (struct my_device *)dev_id;
/* 读取硬件状态寄存器确认中断源 */
uint32_t status = ioread32(mdev->base + REG_STATUS);
if (!(status & INT_PENDING))
return IRQ_NONE; /* 非本设备中断 */
/* 处理中断事件 */
/* ... */
return IRQ_HANDLED;
}
static int my_probe(struct platform_device *pdev)
{
struct my_device *mdev;
int ret;
mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL);
if (!mdev)
return -ENOMEM;
/* 获取中断号 */
mdev->irq = platform_get_irq(pdev, 0);
if (mdev->irq < 0)
return mdev->irq;
/* 注册中断 */
ret = request_irq(mdev->irq,
my_irq_handler,
IRQF_TRIGGER_FALLING | IRQF_SHARED,
"my_device",
mdev);
if (ret) {
dev_err(&pdev->dev, "request_irq failed: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, mdev);
return 0;
}
static int my_remove(struct platform_device *pdev)
{
struct my_device *mdev = platform_get_drvdata(pdev);
free_irq(mdev->irq, mdev); /* dev_id 必须与注册时一致 */
return 0;
}
4.7 常见错误与调试
| 错误码 | 含义 | 常见原因 |
|---|---|---|
-EBUSY |
IRQ 线已被占用且未设置 IRQF_SHARED |
非共享中断重复注册;或共享中断未正确设置标志 |
-EINVAL |
参数无效 | irq 号超出范围;handler 为 NULL;flags 包含非法组合 |
-ENOMEM |
内存不足 | 内核中断描述符分配失败,通常为系统资源紧张 |
调试技巧:注册失败后,可通过
cat /proc/interrupts 查看当前已注册的中断及触发次数;使用 cat /proc/irq/<irq_num>/smp_affinity 检查中断亲和性设置。
4.8 中断处理函数返回值规范
中断处理函数必须返回 irqreturn_t 类型,其定义如下:
| 返回值 | 含义 | 使用场景 |
|---|---|---|
IRQ_NONE |
中断不是本设备产生的 | 共享中断中,硬件状态检查后发现非本设备中断 |
IRQ_HANDLED |
中断已被正确处理 | 正常中断处理完成 |
IRQ_WAKE_THREAD |
唤醒中断线程化处理 | 配合 request_threaded_irq 使用 |
最佳实践:在共享中断的处理函数中,务必先检查硬件状态寄存器,确认中断是否来自本设备。若确认非本设备中断,立即返回
IRQ_NONE,避免错误消耗其他设备的中断。