3. 中断处理程序(ISR)编写:request_irq()函数详解、中断标志位(IRQF_SHARED等)
中断处理程序(Interrupt Service Routine, ISR)是内核响应硬件中断的核心执行单元。在 Linux 驱动中,注册 ISR 的标准接口是 request_irq()。本章节将深入剖析该函数的原型、参数含义、返回值处理,并重点解析中断标志位(尤其是 IRQF_SHARED)的实战用法与设计考量。
核心要点: 正确使用
request_irq() 是驱动稳定性的基石。理解中断标志位的组合语义,特别是共享中断的处理流程,是编写可靠 ISR 的关键。
3.1 request_irq() 函数原型与参数详解
request_irq() 是 Linux 内核为驱动程序提供的标准中断注册接口,其原型定义在 <linux/interrupt.h> 中。该函数将给定的中断号与一个 ISR 绑定,并配置中断的触发方式和共享属性。
// 函数原型 (Linux 内核)
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() 或设备树解析获得。 |
不要使用硬编码的中断号,应始终从硬件抽象层获取。 |
handler |
irq_handler_t |
指向中断处理函数的指针。原型为 irqreturn_t (*handler)(int irq, void *dev_id)。 |
ISR 必须快速执行(通常为毫秒级),耗时操作应推迟到 tasklet 或工作队列。 |
flags |
unsigned long |
中断标志位掩码,用于控制中断的触发方式、共享属性、电源管理等行为。 | 标志位的组合直接影响中断行为,是本节重点。 |
name |
const char * |
设备名称,用于 /proc/interrupts 和内核日志中标识中断源。 |
使用有意义的名称(如驱动名+实例),便于调试。 |
dev |
void * |
传递给 ISR 的 dev_id 参数。对于共享中断,此参数是区分不同设备的唯一标识。 |
共享中断时必须提供唯一的 dev 指针,通常指向设备结构体实例。 |
常见错误: 在共享中断场景下,如果
dev 参数为 NULL,内核将无法在释放中断时正确匹配处理程序,导致 free_irq() 失败或系统崩溃。
3.2 中断标志位(Flags)深度解析
flags 参数是 request_irq() 的灵魂,它通过位掩码组合定义了中断的多种行为。以下是最常用的标志位及其含义:
| 标志位 | 值(典型) | 含义 | 适用场景 |
|---|---|---|---|
IRQF_SHARED |
0x00000080 |
允许该中断线被多个设备共享。所有注册了该中断号的驱动在中断触发时都会被调用。 | 硬件中断线资源有限的嵌入式系统、PCI 设备。 |
IRQF_TRIGGER_RISING |
0x00000001 |
上升沿触发中断。 | 边沿触发型设备(如 GPIO 按键)。 |
IRQF_TRIGGER_FALLING |
0x00000002 |
下降沿触发中断。 | 边沿触发型设备。 |
IRQF_TRIGGER_HIGH |
0x00000004 |
高电平触发中断。 | 电平触发型设备(如某些网络控制器)。 |
IRQF_TRIGGER_LOW |
0x00000008 |
低电平触发中断。 | 电平触发型设备。 |
IRQF_ONESHOT |
0x00002000 |
在 ISR 执行期间,硬件中断线被屏蔽,直到 ISR 返回后才重新使能。常用于线程化中断。 | 配合 request_threaded_irq() 使用,防止中断嵌套。 |
IRQF_NO_SUSPEND |
0x00004000 |
系统休眠时该中断不会被禁用,可用于唤醒源。 | 电源管理、唤醒设备。 |
最佳实践: 对于大多数设备,推荐使用
IRQF_TRIGGER_HIGH | IRQF_SHARED 或 IRQF_TRIGGER_RISING | IRQF_SHARED 组合。触发方式应与硬件数据手册严格一致。
3.3 共享中断(IRQF_SHARED)实战机制
共享中断是现代硬件设计中常见的特性,允许多个设备共用一条物理中断线。当共享中断触发时,内核会遍历该中断线上所有注册的 ISR,依次调用它们,直到某个 ISR 返回 IRQ_HANDLED 或所有 ISR 都被调用。
下图展示了共享中断的完整处理流程:
共享中断 ISR 编写规则:
- 必须检查中断源: ISR 首先应读取设备的状态寄存器,确认是否是本设备产生了中断。如果不是,应立即返回
IRQ_NONE。 - 必须提供唯一的
dev_id: 在调用request_irq()时,dev参数应指向一个唯一的设备实例指针(如struct my_device *)。 - 返回值语义: 如果 ISR 处理了本设备的中断,返回
IRQ_HANDLED;否则返回IRQ_NONE。
3.4 返回值与错误处理
request_irq() 的返回值指示了注册是否成功。常见的返回值如下:
| 返回值 | 含义 | 处理方式 |
|---|---|---|
0 |
注册成功。 | 正常继续驱动初始化流程。 |
-EINVAL |
无效参数,如 irq 号无效、handler 为 NULL、或标志位组合错误。 |
检查中断号获取逻辑和标志位配置。 |
-EBUSY |
中断号已被占用,且未设置 IRQF_SHARED 标志。 |
确认硬件是否支持共享中断,或检查是否有其他驱动已注册该中断。 |
-ENOMEM |
内核内存不足,无法分配中断描述符。 | 检查系统内存状态,或减少并发注册的中断数量。 |
调试提示: 当
request_irq() 返回 -EBUSY 时,可以通过 cat /proc/interrupts 查看当前已注册的中断处理程序,确认是哪个驱动占用了该中断线。
3.5 完整注册示例
以下代码展示了一个典型的共享中断注册流程,包含错误处理和 ISR 编写规范:
#include <linux/interrupt.h>
#include <linux/device.h>
struct my_device {
void __iomem *base;
int irq;
struct device *dev;
};
// 中断处理函数
static irqreturn_t my_isr(int irq, void *dev_id)
{
struct my_device *mydev = (struct my_device *)dev_id;
u32 status;
// 1. 读取硬件状态寄存器,确认中断是否来自本设备
status = ioread32(mydev->base + REG_STATUS);
if (!(status & INT_PENDING)) {
// 不是本设备的中断,返回 IRQ_NONE
return IRQ_NONE;
}
// 2. 清除中断标志位
iowrite32(INT_CLEAR, mydev->base + REG_CLEAR);
// 3. 处理中断事件(快速处理,耗时操作请推迟)
dev_info(mydev->dev, "Interrupt handled!\n");
// 4. 返回 IRQ_HANDLED 表示已处理
return IRQ_HANDLED;
}
// 驱动初始化函数
static int my_probe(struct platform_device *pdev)
{
struct my_device *mydev;
int ret;
mydev = devm_kzalloc(&pdev->dev, sizeof(*mydev), GFP_KERNEL);
if (!mydev)
return -ENOMEM;
// ... 获取硬件资源、映射 IO 内存等 ...
// 获取中断号
mydev->irq = platform_get_irq(pdev, 0);
if (mydev->irq < 0)
return mydev->irq;
// 注册共享中断
ret = request_irq(mydev->irq, my_isr,
IRQF_TRIGGER_HIGH | IRQF_SHARED,
"my_driver", mydev);
if (ret) {
dev_err(&pdev->dev, "Failed to request IRQ %d, ret=%d\n",
mydev->irq, ret);
return ret;
}
// ... 其他初始化 ...
return 0;
}
// 驱动移除函数
static int my_remove(struct platform_device *pdev)
{
struct my_device *mydev = platform_get_drvdata(pdev);
// 释放中断,必须传入相同的 dev_id
free_irq(mydev->irq, mydev);
// ... 其他清理 ...
return 0;
}
关键点回顾:
- ISR 中必须检查中断源,避免误处理其他设备的中断。
free_irq()的dev_id参数必须与request_irq()时传入的完全一致。- 对于共享中断,
dev_id不能为NULL。