2. Linux中断子系统架构:中断控制器、中断描述符、中断处理流程

在Subdev驱动开发中,中断处理是连接硬件事件与软件响应的核心桥梁。Linux内核的中断子系统设计精巧,分为三个关键层次:中断控制器(Interrupt Controller)、中断描述符(Interrupt Descriptor)和中断处理流程(Interrupt Handling Flow)。理解这三层架构,是编写可靠Subdev中断驱动的基石。

核心观点:Linux中断子系统采用分层抽象,将硬件中断控制器细节与通用中断处理逻辑解耦。Subdev驱动开发者主要与中断描述符和中断处理流程的上半部/下半部交互,而中断控制器由芯片级代码管理。

2.1 中断控制器(Interrupt Controller)

中断控制器是硬件层面的中断管理单元,负责接收来自外设(如Subdev)的中断请求,进行优先级仲裁、屏蔽、使能,并最终向CPU发送中断信号。在嵌入式系统中,常见的中断控制器包括GIC(Generic Interrupt Controller,用于ARM)、APIC(用于x86)以及各种SoC内部集成的控制器。

Linux内核通过struct irq_chip结构体抽象中断控制器的硬件操作,每个中断控制器实例对应一个irq_chip。Subdev驱动通常不直接操作irq_chip,而是通过通用中断接口(如request_irq)间接使用。

中断控制器类型 典型硬件 Linux抽象结构 Subdev驱动关注点
GIC v2/v3 ARM Cortex-A系列 irq-gic.c 中的 irq_chip SPI(共享外设中断)号分配
APIC x86架构 irq_chip for IO-APIC IRQ号映射(通常无需关心)
GPIO控制器作为中断控制器 SoC内部GPIO模块 gpio_irq_chip 封装 GPIO中断号、触发类型配置
级联中断控制器 多个中断控制器串联 irq_domain 层级映射 父中断控制器与子中断控制器的级联关系
Subdev开发提示:在设备树中,Subdev节点通过interrupt-parent属性指定所属的中断控制器,通过interrupts属性指定中断号。驱动中调用platform_get_irq()即可获取Linux中断号,无需关心底层中断控制器细节。
中断控制器层级与Subdev关系 中断控制器层级与Subdev关系 CPU 核心 主中断控制器 (GIC/APIC) 子中断控制器 (GPIO) 子中断控制器 (EXINT) Subdev A (SPI设备) Subdev B (I2C设备) 说明:Subdev通过子中断控制器连接到主中断控制器,最终向CPU提交中断请求。

2.2 中断描述符(Interrupt Descriptor)

中断描述符是内核为每个中断源分配的软件管理结构,由struct irq_desc表示。它包含了中断的完整状态信息、处理函数链表、硬件抽象层指针以及统计信息。Subdev驱动通过中断号(IRQ number)索引到对应的irq_desc。

struct irq_desc的核心字段如下:

字段名 类型 作用 Subdev驱动影响
irq_data struct irq_data 包含中断号、irq_chip指针、irq_domain等硬件映射信息 驱动通过irq_get_irq_data()获取,用于配置触发类型
action struct irqaction * 指向中断处理函数链表的头节点 request_irq()将驱动处理函数挂入此链表
status_use_accessors unsigned int 中断状态标志(如IRQ_DISABLED、IRQ_INPROGRESS) 驱动可通过disable_irq()/enable_irq()间接修改
depth unsigned int 中断禁用深度计数,用于嵌套禁用/使能 驱动应避免不匹配的disable_irq()调用
kstat_irqs struct irqstat * 中断统计信息(触发次数等) 可通过/proc/interrupts查看,辅助调试
关键约束:中断描述符的action链表支持共享中断(IRQF_SHARED)。Subdev驱动在注册共享中断时,必须确保中断处理函数能正确判断中断是否来自本设备,否则会导致系统误判。
中断描述符 (irq_desc) 结构 中断描述符 (struct irq_desc) 结构 struct irq_desc (中断号 N) irq_data irq: 中断号 chip: irq_chip 指针 action (链表头) handler: Subdev处理函数 dev_id: 设备私有数据 status / depth IRQ_DISABLED / depth计数 kstat_irqs 中断统计 / proc接口

2.3 中断处理流程

当中断发生时,CPU执行从硬件到软件的完整流程。Subdev驱动开发者需要理解中断处理的上半部(Top Half)和下半部(Bottom Half)机制,以确保中断响应及时且系统稳定。

2.3.1 中断处理流程步骤

  1. 硬件中断触发:Subdev设备拉高/拉低中断引脚,中断控制器检测到有效信号。
  2. 中断控制器仲裁:中断控制器根据优先级选择最高优先级中断,向CPU发送IRQ信号。
  3. CPU异常向量跳转:CPU保存当前上下文(寄存器、PC等),跳转到中断向量表(Vector Table)对应的入口地址。
  4. 内核通用中断处理:执行handle_arch_irq()或do_IRQ(),根据中断号查找irq_desc,调用irq_chip的irq_ack()确认中断。
  5. 执行中断处理函数:遍历irq_desc->action链表,调用Subdev注册的handler(上半部)。
  6. 触发下半部:上半部返回IRQ_WAKE_THREAD或调度软中断/工作队列,执行下半部处理。
  7. 中断返回:恢复CPU上下文,继续执行被中断的任务。
Subdev驱动设计原则:上半部必须快速完成(通常只做硬件寄存器读取、清除中断标志、将数据推入缓冲区),耗时操作(如协议解析、数据上报)应推迟到下半部执行。使用request_threaded_irq()可以方便地将上半部和线程化下半部分离。
Linux中断处理完整流程 Linux中断处理完整流程 ① Subdev硬件中断触发 ② 中断控制器仲裁与转发 ③ CPU异常向量跳转 ④ 内核通用中断处理 (do_IRQ) ⑤ 执行上半部 (Subdev handler) ⑥ 触发下半部 (软中断/工作队列) ⑦ 中断返回,恢复上下文

2.3.2 上半部与下半部的典型代码模式

Subdev驱动中,使用request_threaded_irq()是实现中断分离的推荐方式。以下是一个典型示例:

/* Subdev中断上半部:快速处理硬件状态 */
static irqreturn_t subdev_irq_handler(int irq, void *dev_id)
{
    struct subdev_device *subdev = (struct subdev_device *)dev_id;

    /* 1. 读取硬件中断状态寄存器,确认中断源 */
    uint32_t status = ioread32(subdev->regs + INT_STATUS);

    /* 2. 清除硬件中断标志,防止重复触发 */
    iowrite32(status, subdev->regs + INT_CLEAR);

    /* 3. 将状态保存到设备结构体,供下半部使用 */
    subdev->pending_status = status;

    /* 4. 返回 IRQ_WAKE_THREAD,唤醒下半部线程 */
    return IRQ_WAKE_THREAD;
}

/* Subdev中断下半部:线程化处理,可睡眠 */
static irqreturn_t subdev_thread_fn(int irq, void *dev_id)
{
    struct subdev_device *subdev = (struct subdev_device *)dev_id;

    /* 执行耗时操作:数据解析、协议处理、上报应用层 */
    process_subdev_data(subdev, subdev->pending_status);

    return IRQ_HANDLED;
}

/* 驱动初始化时注册中断 */
static int subdev_probe(struct platform_device *pdev)
{
    struct subdev_device *subdev;
    int irq, ret;

    /* ... 分配设备结构体、映射寄存器等 ... */

    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    /* 注册线程化中断:上半部 + 下半部 */
    ret = request_threaded_irq(irq, subdev_irq_handler,
                               subdev_thread_fn,
                               IRQF_TRIGGER_RISING,
                               dev_name(&pdev->dev), subdev);
    if (ret) {
        dev_err(&pdev->dev, "Failed to request threaded irq\n");
        return ret;
    }

    return 0;
}
最佳实践:使用request_threaded_irq()时,如果上半部可以完全处理中断(无需下半部),则返回IRQ_HANDLED;如果需要下半部,则返回IRQ_WAKE_THREAD。上半部中禁止调用可能导致睡眠的函数(如mutex_lock、kmalloc(GFP_KERNEL))。

2.4 中断处理流程中的关键数据结构关系

为了更清晰地展示中断号、中断描述符、中断控制器和Subdev驱动之间的关联,下表总结了核心数据结构的关系:

数据结构 作用域 与Subdev驱动的接口 生命周期
irq_domain 中断控制器 设备树映射(硬件中断号 → Linux中断号) 系统启动时创建
irq_desc 每个中断源 通过中断号索引,驱动不直接访问 中断号注册后存在
irqaction 每个注册者 request_irq() / free_irq() 驱动加载/卸载
irq_chip 中断控制器硬件 驱动通过irq_set_irq_type()间接调用 与硬件绑定,常驻
中断子系统核心数据结构关系 中断子系统核心数据结构关系 irq_domain 硬件→Linux中断号映射 irq_desc 中断描述符(状态+链表) irqaction Subdev处理函数+私有数据 irq_chip 中断控制器硬件操作
调试提示:当Subdev中断不触发时,按以下顺序排查:① 检查设备树中interrupt-parent和interrupts属性是否正确;② 查看/proc/interrupts确认中断号是否注册;③ 使用cat /sys/kernel/debug/irq/irqs/<N>查看中断描述符的详细状态;④ 确认中断控制器是否使能了该中断源。