2. Linux中断子系统架构:中断控制器、中断描述符、中断处理流程
在Subdev驱动开发中,中断处理是连接硬件事件与软件响应的核心桥梁。Linux内核的中断子系统设计精巧,分为三个关键层次:中断控制器(Interrupt Controller)、中断描述符(Interrupt Descriptor)和中断处理流程(Interrupt Handling Flow)。理解这三层架构,是编写可靠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 层级映射 |
父中断控制器与子中断控制器的级联关系 |
interrupt-parent属性指定所属的中断控制器,通过interrupts属性指定中断号。驱动中调用platform_get_irq()即可获取Linux中断号,无需关心底层中断控制器细节。
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驱动在注册共享中断时,必须确保中断处理函数能正确判断中断是否来自本设备,否则会导致系统误判。
2.3 中断处理流程
当中断发生时,CPU执行从硬件到软件的完整流程。Subdev驱动开发者需要理解中断处理的上半部(Top Half)和下半部(Bottom Half)机制,以确保中断响应及时且系统稳定。
2.3.1 中断处理流程步骤
- 硬件中断触发:Subdev设备拉高/拉低中断引脚,中断控制器检测到有效信号。
- 中断控制器仲裁:中断控制器根据优先级选择最高优先级中断,向CPU发送IRQ信号。
- CPU异常向量跳转:CPU保存当前上下文(寄存器、PC等),跳转到中断向量表(Vector Table)对应的入口地址。
- 内核通用中断处理:执行
handle_arch_irq()或do_IRQ(),根据中断号查找irq_desc,调用irq_chip的irq_ack()确认中断。 - 执行中断处理函数:遍历
irq_desc->action链表,调用Subdev注册的handler(上半部)。 - 触发下半部:上半部返回
IRQ_WAKE_THREAD或调度软中断/工作队列,执行下半部处理。 - 中断返回:恢复CPU上下文,继续执行被中断的任务。
request_threaded_irq()可以方便地将上半部和线程化下半部分离。
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()间接调用 |
与硬件绑定,常驻 |
interrupt-parent和interrupts属性是否正确;② 查看/proc/interrupts确认中断号是否注册;③ 使用cat /sys/kernel/debug/irq/irqs/<N>查看中断描述符的详细状态;④ 确认中断控制器是否使能了该中断源。