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 都被调用。

下图展示了共享中断的完整处理流程:

共享中断处理流程 (IRQF_SHARED) 硬件中断触发 内核中断子系统分发 遍历共享中断 ISR 链表 调用 ISR(irq, dev_id) 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。