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、应答中断、完成中断。

我画了张图,帮你理清这三者的关系:

GIC-500/600 中断控制器架构 Distributor 中断使能/优先级 触发方式配置 SPI路由 Redistributor 0 PPI/SGI管理 CPU0 私有中断 Redistributor 1 PPI/SGI管理 CPU1 私有中断 CPU Interface 0 中断应答/完成 CPU0 CPU Interface 1 中断应答/完成 CPU1 外设中断源 SPI (共享外设中断) 说明:SPI由Distributor路由到任意Redistributor;PPI和SGI由Redistributor直接管理,不经过Distributor。
我的经验:调试GIC时,先看Distributor的使能位,再看Redistributor的掩码位。很多新手只配了Distributor,忘了Redistributor,结果中断死活不触发。我曾经在这个坑里蹲了整整一个下午。

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;
}
注意:向量表必须4KB对齐,否则CPU死活不认。我见过有人把.align 12写成了.align 11,结果中断一触发就跳飞了。这种bug用JTAG都难抓,因为PC直接跑到了非法地址。

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默认支持嵌套,但我不建议在座舱场景里随便开嵌套。为什么?因为嵌套会导致栈溢出、优先级反转、调试困难。

我个人的做法是:

  1. 中断处理分上半部和下半部:上半部只做最紧急的事(比如读FIFO),下半部做耗时操作(比如协议解析)。上半部关中断,下半部开中断。
  2. 关键中断用FIQ:FIQ有独立的寄存器组,上下文切换开销小。我把安全相关的中断(如刹车、气囊)配成FIQ。
  3. 减少中断处理时间:每个中断处理函数控制在100us以内。超了就拆成任务。

延迟优化的几个关键点:

优化手段 效果 代价
中断亲和性绑定 减少Cache Miss CPU负载不均
使用FIQ 减少上下文切换 FIQ资源有限
中断合并 减少中断次数 增加单次延迟
轮询替代中断 消除中断抖动 CPU占用率高
实战案例:我在R-Car H3上优化音频中断时,发现中断延迟有200us的抖动。排查后发现是另一个核上的网络中断抢占了CPU总线。解决方案:把音频中断绑定到CPU0,网络中断绑定到CPU1,并给音频中断提了一级优先级。抖动降到了20us以内。

最后说一句:中断调试没有银弹。我建议你在开发阶段就把GIC的寄存器dump功能做好,出问题时先看GICC_IAR(当前活跃中断ID)和GICC_PMR(优先级掩码)。这两个寄存器能告诉你80%的问题。