4. 中断控制器(PLIC/APIC)适配:中断控制器驱动框架、设备树中断描述、中断号映射错误排查、中断响应延迟问题

中断控制器,说白了就是CPU的“门铃系统”。

你想想看,外设要通知CPU干活,总得有个敲门的方式。这个“敲门”的过程,就是中断。而中断控制器,就是负责管理所有门铃的那个管家。

我在做RISC-V芯片移植时,PLIC(平台级中断控制器)和APIC(高级可编程中断控制器)打交道最多。这两者虽然名字不同,但核心逻辑是相通的。今天我就把这块的坑和经验,一次性说清楚。

4.1 中断控制器驱动框架

Linux内核里,中断控制器驱动遵循一套标准框架。说白了,就是你要告诉内核三件事:

  • 怎么初始化硬件
  • 怎么屏蔽/使能某个中断
  • 中断来了怎么处理

以RISC-V的PLIC为例,驱动入口通常长这样:

static struct irq_chip plic_chip = {
    .name           = "PLIC",
    .irq_mask       = plic_irq_mask,
    .irq_unmask     = plic_irq_unmask,
    .irq_eoi        = plic_irq_eoi,
};

static int plic_init(struct device_node *node, struct device_node *parent)
{
    // 映射寄存器基地址
    // 注册中断域
    // 设置每个中断的优先级
}

我个人习惯,拿到一个新芯片,先看它的中断控制器手册。重点关注三个寄存器:中断挂起寄存器中断使能寄存器中断阈值/优先级寄存器。这三个搞明白了,驱动框架就搭起来了。

小技巧: 调试中断驱动时,我建议先写一个最简单的“轮询版”驱动——不依赖中断,直接读挂起寄存器。确认硬件能正确产生中断信号后,再上完整的中断处理流程。这样能快速隔离问题。

4.2 设备树中断描述

设备树里描述中断,其实就两个核心字段:interrupt-parentinterrupts

举个例子,一个UART设备挂到PLIC上:

uart0: serial@10000000 {
    compatible = "vendor,uart";
    reg = <0x10000000 0x1000>;
    interrupt-parent = <&plic>;
    interrupts = <10 1>;  // 中断号10,上升沿触发
};

这里有个坑,我踩过好几次:interrupts属性的编码格式,不同芯片不一样

有些芯片用 <中断号 触发类型>,有些用 <中断号 中断类型 优先级>。你得去查芯片手册里的“中断控制器”章节,看它定义的 #interrupt-cells 是多少。

注意: 设备树里中断号写错了,系统不会报编译错误。它只会静默地不响应中断。我曾经花了一整天,最后发现是设备树里中断号少写了1——因为有些芯片的中断号从0开始,有些从1开始。

4.3 中断号映射错误排查

中断号映射错误,是移植过程中最常见的坑。症状就是:外设驱动加载正常,但中断就是不触发。

排查步骤,我一般按这个顺序来:

  1. 检查设备树:确认 interrupts 属性里的中断号,和芯片手册一致。
  2. 检查中断域:用 cat /proc/interrupts 看内核识别到的中断号。
  3. 检查硬件连接:用示波器或逻辑分析仪,看中断引脚上有没有电平变化。
  4. 检查驱动注册:确认 request_irq() 返回0,没有报 -EBUSY。

我曾经遇到一个案例:设备树里写的中断号是32,但内核里实际注册的是33。为什么?因为PLIC内部把中断号0保留给了“无中断”,所以硬件中断号从1开始,而软件中断号从0开始。这个偏移量,你得在驱动里处理好。

核心原则: 硬件中断号 ≠ 软件中断号。驱动里一定要做一次映射转换。常见的做法是在 .irq_domain_opsmap 回调里做转换。

4.4 中断响应延迟问题

中断响应延迟,说白了就是“门铃响了,CPU多久才去开门”。

影响延迟的因素,主要有三个:

因素 影响程度 排查方向
中断屏蔽时间 检查驱动里 local_irq_save 的临界区长度
中断优先级设置 PLIC的阈值寄存器是否设得太高
中断嵌套 高优先级中断是否被低优先级阻塞
CPU缓存一致性 检查是否漏了 dsb / fence 指令

嗯,这里要注意:PLIC本身有一个全局阈值寄存器。只有优先级高于这个阈值的中断,才会被转发到CPU。如果你不小心把阈值设成了最大值,那所有中断都会被屏蔽——我就在一个项目里犯过这个错,排查了整整两天。

另外,RISC-V的PLIC还有一个特性:中断完成后,必须写EOI(End of Interrupt)寄存器。如果忘了写,PLIC会认为中断还在处理中,不会发送下一个中断。这个在驱动里很容易漏掉。

实测建议:trace_irq_handler_entrytrace_irq_handler_exit 这两个tracepoint,可以精确测量每个中断处理函数的执行时间。如果发现某个中断处理时间异常长,那就要检查它是不是在临界区里做了太多事情。

最后说一句:中断响应延迟,很多时候不是硬件慢,而是软件设计不合理。比如在中断处理函数里调用 printk,那延迟肯定爆炸。我建议中断处理函数里只做最必要的事,把耗时操作放到 taskletworkqueue 里去做。

中断控制器适配核心逻辑 外设 (UART/SPI) 设备树描述 interrupt-parent 中断控制器 PLIC / APIC 中断号映射 CPU 响应延迟因素 • 中断屏蔽时间 • 优先级设置 • 中断嵌套 核心流程:外设 → 设备树描述 → 中断控制器 → 中断号映射 → CPU响应 常见问题:中断号偏移、阈值设置错误、EOI遗漏

好了,中断控制器这块,核心就是这些。记住:先确认硬件能产生中断,再排查软件映射,最后优化延迟。这个顺序别搞反了,不然你会像我一样,在错误的方向上浪费大量时间。