3. 中断注册与注销:request_irq()、free_irq()、中断标志(IRQF_*)详解

中断处理程序的注册与注销是驱动开发中最基础也最关键的环节。正确使用 request_irq() 和 free_irq(),并理解各类中断标志的语义,直接决定了中断响应延迟、系统稳定性和调试效率。本节将深入剖析这两个核心 API 的用法、参数细节以及常见陷阱。

3.1 request_irq():中断处理程序的注册入口

request_irq() 是 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 函数必须使用 __irq 注解,且不能睡眠
flags unsigned long 中断标志位,通过 IRQF_* 宏组合 标志选择错误会导致中断丢失或性能下降
name const char * 设备名称,用于 /proc/interrupts 显示 名称应唯一且具有辨识度
dev void * 传递给处理函数的设备标识,通常为设备结构体指针 共享中断时必须提供非 NULL 值用于区分
最佳实践: 始终使用设备树或 ACPI 获取中断号,避免硬编码。在 probe 函数中尽早注册中断,在 remove 函数中及时注销。

3.2 中断标志 IRQF_* 详解

中断标志是 request_irq() 的核心参数,决定了中断的触发方式、共享策略和电源管理行为。以下是常用标志的详细对比:

标志宏 值 含义 适用场景
IRQF_TRIGGER_NONE 0x00000000 无触发方式,由硬件默认决定 极少使用,仅用于特殊硬件
IRQF_TRIGGER_RISING 0x00000001 上升沿触发 GPIO 按键、边缘敏感设备
IRQF_TRIGGER_FALLING 0x00000002 下降沿触发 中断控制器、下降沿信号设备
IRQF_TRIGGER_HIGH 0x00000004 高电平触发 电平敏感设备、共享中断
IRQF_TRIGGER_LOW 0x00000008 低电平触发 低电平有效设备
IRQF_SHARED 0x00000080 允许共享中断线 多个设备共用同一中断线
IRQF_PROBE_SHARED 0x00000100 中断共享探测标志 驱动探测阶段使用
IRQF_NO_SUSPEND 0x00004000 系统休眠时不关闭中断 唤醒源设备、实时时钟
IRQF_FORCE_RESUME 0x00008000 系统唤醒时强制恢复中断 与 IRQF_NO_SUSPEND 配合使用
IRQF_NO_THREAD 0x00020000 禁止中断线程化 高实时性要求的中断
IRQF_EARLY_RESUME 0x00040000 在早期恢复阶段启用中断 需要提前恢复中断的设备
IRQF_ONESHOT 0x00002000 中断处理期间屏蔽同线中断 线程化中断、电平触发中断
关键警告: 使用 IRQF_SHARED 时,必须确保所有共享该中断线的驱动都正确实现了中断处理函数的返回值检查。如果某个驱动返回 IRQ_NONE 但实际并未处理,会导致内核误判为“伪中断”,进而触发错误处理机制。

3.3 中断注册流程与标志组合策略

下图展示了典型的中断注册流程以及标志组合的决策路径:

中断注册流程与标志组合决策 获取中断号 irq 选择触发方式:沿触发 / 电平触发 是否共享中断线? 添加 IRQF_SHARED 无需 IRQF_SHARED 组合其他标志(NO_SUSPEND 等) 调用 request_irq() 注册 提示:电平触发中断建议配合 IRQF_ONESHOT 使用,防止中断处理期间重复触发

3.4 free_irq():中断注销的正确姿势

free_irq() 用于在驱动卸载或设备移除时释放中断资源。其原型如下:

void free_irq(unsigned int irq, void *dev);

注销中断时需注意以下关键点:

  • 参数一致性: irq 和 dev 必须与 request_irq() 时传入的值完全一致。
  • 调用时机: 必须在所有可能触发中断的硬件操作停止之后调用,否则可能导致野指针访问。
  • 共享中断处理: 对于共享中断,free_irq() 仅移除当前设备的处理函数,其他设备不受影响。
  • 同步保证: free_irq() 会等待当前正在执行的中断处理函数完成,但不会等待线程化中断的线程退出。
典型注销顺序:
  1. 屏蔽设备中断(通过设备寄存器或 disable_irq())
  2. 确保所有 DMA 和硬件操作已停止
  3. 调用 free_irq() 释放中断线
  4. 释放设备数据结构

3.5 中断注册与注销的常见陷阱

陷阱 现象 解决方案
中断号错误 注册失败,返回 -EINVAL 使用 platform_get_irq() 动态获取
标志冲突 中断行为异常,如丢失中断 检查设备树中 interrupts 属性与标志一致性
共享中断 dev 参数为 NULL 无法区分中断来源,导致误处理 始终传入设备结构体指针
注销顺序错误 内核崩溃或设备仍在产生中断 先屏蔽硬件中断,再调用 free_irq()
线程化中断未同步 中断线程仍在运行,访问已释放内存 使用 synchronize_irq() 确保线程退出

3.6 实战代码示例:完整的中断注册与注销

以下代码展示了一个典型的中断注册与注销实现:

#include <linux/interrupt.h>
#include <linux/device.h>

struct my_device {
    void __iomem *base;
    int irq;
    struct device *dev;
};

/* 中断处理函数 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    struct my_device *mydev = (struct my_device *)dev_id;
    u32 status;

    status = ioread32(mydev->base + REG_STATUS);
    if (!(status & INT_PENDING))
        return IRQ_NONE;  /* 非本设备中断 */

    /* 处理中断事件 */
    iowrite32(INT_CLEAR, mydev->base + REG_CLEAR);

    /* 调度 tasklet 或 workqueue 处理数据 */
    return IRQ_HANDLED;
}

/* 中断注册 */
static int my_device_request_irq(struct my_device *mydev)
{
    unsigned long flags = IRQF_TRIGGER_HIGH | IRQF_SHARED;
    int ret;

    ret = request_irq(mydev->irq, my_irq_handler,
                      flags, "my_device", mydev);
    if (ret) {
        dev_err(mydev->dev, "Failed to request IRQ %d: %d\n",
                mydev->irq, ret);
        return ret;
    }

    return 0;
}

/* 中断注销 */
static void my_device_free_irq(struct my_device *mydev)
{
    /* 1. 屏蔽设备中断 */
    iowrite32(0, mydev->base + REG_INT_ENABLE);

    /* 2. 确保硬件不再产生中断 */
    mb();  /* 内存屏障 */

    /* 3. 释放中断线 */
    free_irq(mydev->irq, mydev);
}
调试技巧: 通过 /proc/interrupts 可以查看当前系统中所有中断的注册情况、触发次数以及对应的驱动名称。这是排查中断注册问题的第一站。

3.7 中断标志组合速查表

以下表格列出了最常见的标志组合及其典型应用场景:

组合 典型场景 说明
IRQF_TRIGGER_RISING GPIO 按键 简单边缘触发,无需共享
IRQF_TRIGGER_HIGH | IRQF_SHARED PCI 设备共享中断 电平触发,多个设备共用中断线
IRQF_TRIGGER_LOW | IRQF_ONESHOT 线程化中断 电平触发,防止处理期间重复触发
IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND 唤醒源设备 系统休眠时仍可唤醒
IRQF_TRIGGER_HIGH | IRQF_SHARED | IRQF_NO_THREAD 高实时性共享中断 禁止线程化,保证最低延迟