4、中断子系统深度解析:GIC-500/600中断控制器、中断向量表配置、中断优先级与亲和性、中断嵌套与延迟优化
中断,说白了就是CPU的“紧急通道”。没有它,CPU就得轮询所有外设,效率低得吓人。在R-Car智能座舱里,中断更是命脉——你按一下触摸屏,音频解码完成,摄像头捕捉到一帧画面,全都靠中断来通知CPU。
今天我们就来拆解R-Car的中断子系统。我个人习惯把中断比作“快递系统”:GIC就是快递分拣中心,中断向量表是地址簿,优先级决定了哪个包裹先处理,亲和性则决定了哪个快递员(CPU核)去送。
4.1 GIC-500/600中断控制器架构
R-Car H3/M3/N3等主流芯片用的都是ARM GIC-500或GIC-600。这两兄弟核心逻辑差不多,GIC-600在延迟和功耗上做了优化。我当年从GIC-500迁移到GIC-600时,发现寄存器接口几乎兼容,但GIC-600多了个直通模式,对虚拟化场景特别友好。
GIC的架构分三块:
- Distributor(分发器):负责中断的使能、优先级分组、触发方式配置。它像个“总闸”,控制所有中断源。
- Redistributor(重分发器):每个CPU核配一个。它负责把中断“推”给对应的CPU,还管着PPI(私有外设中断)和SGI(软件触发中断)。
- CPU Interface(CPU接口):直接和CPU核交互。CPU通过它读取中断ID、应答中断、完成中断。
我画了张图,帮你理清这三者的关系:
4.2 中断向量表配置
中断向量表,就是CPU收到中断后该跳去哪里的“地图”。ARMv8架构下,向量表分四张:
| 异常类型 | 偏移地址 | 说明 |
|---|---|---|
| Synchronous | 0x000 | 同步异常(数据中止、未定义指令等) |
| IRQ | 0x080 | 普通中断(我们最常用的) |
| FIQ | 0x100 | 快速中断(优先级更高) |
| SError | 0x180 | 系统错误(硬件致命错误) |
每张表又分四种异常等级(EL0/EL1/EL2/EL3)和两种栈指针(SP0/SPx)。说白了,一共16个入口点。配置时,把向量表基址写入VBAR_ELn寄存器就行。
我贴一段实际项目中用的向量表配置代码:
// 汇编代码:设置中断向量表
.section .vectors, "ax"
.align 12 // 向量表必须4KB对齐
.global vector_table
vector_table:
// EL1t 模式(使用SP_EL0)
.align 7
b sync_el1t
.align 7
b irq_el1t
.align 7
b fiq_el1t
.align 7
b serror_el1t
// EL1h 模式(使用SP_EL1)
.align 7
b sync_el1h
.align 7
b irq_el1h // 我们主要处理这个
.align 7
b fiq_el1h
.align 7
b serror_el1h
// ... 后面还有EL2和EL3的入口
// C语言中注册中断处理函数
void gic_register_isr(uint32_t irq_num, void (*handler)(void))
{
// 将handler地址写入中断向量表对应位置
// 实际项目中用函数指针数组
isr_table[irq_num] = handler;
}
4.3 中断优先级与亲和性
R-Car的GIC支持16级优先级(0最高,15最低)。配置优先级时,写GICD_IPRIORITYR寄存器。每个中断占8位,但实际只用了高4位。嗯,这里有个坑:低4位读回来总是0,别以为寄存器坏了。
亲和性(Affinity)决定了中断发给哪个CPU核。配置GICD_ITARGETSR寄存器,每个CPU核对应一个bit。比如想让中断只发给CPU0,就写0x01;发给所有核,就写0xFF。
我建议这样分配:
- 实时性要求高的中断(如音频、触摸):绑定到CPU0,优先级设高(0-3)
- 吞吐量型中断(如摄像头、网络):轮询分配给多个核,优先级中等(4-7)
- 后台服务中断(如定时器、看门狗):绑定到CPU1,优先级低(8-15)
为什么这么分?你想想看,音频中断如果延迟超过10ms,人耳就能听出爆音。而网络中断偶尔延迟几十毫秒,用户根本感觉不到。
4.4 中断嵌套与延迟优化
中断嵌套,就是处理一个中断时,又来一个更高优先级的中断。GIC默认支持嵌套,但我不建议在座舱场景里随便开嵌套。为什么?因为嵌套会导致栈溢出、优先级反转、调试困难。
我个人的做法是:
- 中断处理分上半部和下半部:上半部只做最紧急的事(比如读FIFO),下半部做耗时操作(比如协议解析)。上半部关中断,下半部开中断。
- 关键中断用FIQ:FIQ有独立的寄存器组,上下文切换开销小。我把安全相关的中断(如刹车、气囊)配成FIQ。
- 减少中断处理时间:每个中断处理函数控制在100us以内。超了就拆成任务。
延迟优化的几个关键点:
| 优化手段 | 效果 | 代价 |
|---|---|---|
| 中断亲和性绑定 | 减少Cache Miss | CPU负载不均 |
| 使用FIQ | 减少上下文切换 | FIQ资源有限 |
| 中断合并 | 减少中断次数 | 增加单次延迟 |
| 轮询替代中断 | 消除中断抖动 | CPU占用率高 |
最后说一句:中断调试没有银弹。我建议你在开发阶段就把GIC的寄存器dump功能做好,出问题时先看GICC_IAR(当前活跃中断ID)和GICC_PMR(优先级掩码)。这两个寄存器能告诉你80%的问题。