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-parent 和 interrupts。
举个例子,一个UART设备挂到PLIC上:
uart0: serial@10000000 {
compatible = "vendor,uart";
reg = <0x10000000 0x1000>;
interrupt-parent = <&plic>;
interrupts = <10 1>; // 中断号10,上升沿触发
};
这里有个坑,我踩过好几次:interrupts属性的编码格式,不同芯片不一样。
有些芯片用 <中断号 触发类型>,有些用 <中断号 中断类型 优先级>。你得去查芯片手册里的“中断控制器”章节,看它定义的 #interrupt-cells 是多少。
4.3 中断号映射错误排查
中断号映射错误,是移植过程中最常见的坑。症状就是:外设驱动加载正常,但中断就是不触发。
排查步骤,我一般按这个顺序来:
- 检查设备树:确认
interrupts属性里的中断号,和芯片手册一致。 - 检查中断域:用
cat /proc/interrupts看内核识别到的中断号。 - 检查硬件连接:用示波器或逻辑分析仪,看中断引脚上有没有电平变化。
- 检查驱动注册:确认
request_irq()返回0,没有报 -EBUSY。
我曾经遇到一个案例:设备树里写的中断号是32,但内核里实际注册的是33。为什么?因为PLIC内部把中断号0保留给了“无中断”,所以硬件中断号从1开始,而软件中断号从0开始。这个偏移量,你得在驱动里处理好。
.irq_domain_ops 的 map 回调里做转换。
4.4 中断响应延迟问题
中断响应延迟,说白了就是“门铃响了,CPU多久才去开门”。
影响延迟的因素,主要有三个:
| 因素 | 影响程度 | 排查方向 |
|---|---|---|
| 中断屏蔽时间 | 高 | 检查驱动里 local_irq_save 的临界区长度 |
| 中断优先级设置 | 中 | PLIC的阈值寄存器是否设得太高 |
| 中断嵌套 | 中 | 高优先级中断是否被低优先级阻塞 |
| CPU缓存一致性 | 低 | 检查是否漏了 dsb / fence 指令 |
嗯,这里要注意:PLIC本身有一个全局阈值寄存器。只有优先级高于这个阈值的中断,才会被转发到CPU。如果你不小心把阈值设成了最大值,那所有中断都会被屏蔽——我就在一个项目里犯过这个错,排查了整整两天。
另外,RISC-V的PLIC还有一个特性:中断完成后,必须写EOI(End of Interrupt)寄存器。如果忘了写,PLIC会认为中断还在处理中,不会发送下一个中断。这个在驱动里很容易漏掉。
trace_irq_handler_entry 和 trace_irq_handler_exit 这两个tracepoint,可以精确测量每个中断处理函数的执行时间。如果发现某个中断处理时间异常长,那就要检查它是不是在临界区里做了太多事情。
最后说一句:中断响应延迟,很多时候不是硬件慢,而是软件设计不合理。比如在中断处理函数里调用 printk,那延迟肯定爆炸。我建议中断处理函数里只做最必要的事,把耗时操作放到 tasklet 或 workqueue 里去做。
好了,中断控制器这块,核心就是这些。记住:先确认硬件能产生中断,再排查软件映射,最后优化延迟。这个顺序别搞反了,不然你会像我一样,在错误的方向上浪费大量时间。