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 图

下图展示了从驱动入口到中断注册完成的完整调用链路:

request_irq 注册流程 request_irq 注册流程 驱动 probe 函数 platform_get_irq() 获取 IRQ 构造 flags 与 dev_id request_irq(irq, handler, flags, name, dev_id) 成功? 注册成功,中断使能 返回错误码,清理资源 注意:共享中断时 dev_id 必须唯一,否则 free_irq 无法正确释放

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,避免错误消耗其他设备的中断。