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 版本如何简化流程:
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 快速过滤目标设备信息。