2. ARM Cortex-A7 中断体系:GIC 架构与中断类型
中断处理,说白了就是 CPU 被外设或软件「打断」后,去执行一段紧急代码。在 Cortex-A7 多核系统里,这个打断机制由 GIC(通用中断控制器)统一管理。我刚开始接触多核中断时,也被 GIC 的层级绕晕过,后来发现只要抓住三个核心——中断类型、中断号、分发流程——就清晰了。
2.1 GIC 架构概览
Cortex-A7 通常搭配 GIC-400 或 GIC-500。GIC 内部拆成两个逻辑单元:分发器(Distributor)和CPU 接口(CPU Interface)。每个 CPU 核有自己的 CPU 接口,分发器则全局唯一。
分发器:负责中断优先级仲裁、使能/屏蔽、目标 CPU 选择。
CPU 接口:向本核的 IRQ/FIQ 引脚发送中断信号,并管理中断的应答与结束。
我画了一张 GIC 内部结构图,帮你直观理解数据流:
2.2 三种中断类型
GIC 把中断分成三类:SGI、PPI、SPI。我当年在调试一个双核通信问题时,就是靠 SGI 在两个核之间「打招呼」定位到竞态条件的。
| 类型 | 全称 | 中断号范围 | 目标 | 典型用途 |
|---|---|---|---|---|
| SGI | Software Generated Interrupt | ID0 - ID15 | 指定 CPU 核 | 核间通信、IPI |
| PPI | Private Peripheral Interrupt | ID16 - ID31 | 私有(仅本核) | 本地定时器、看门狗 |
| SPI | Shared Peripheral Interrupt | ID32 - ID1019 | 任意核或广播 | UART、I2C、GPIO 等外设 |
2.3 中断号分配规则
中断号是 GIC 识别中断源的唯一标识。ARM 规范定了 ID0~ID1019,但实际 SoC 只用了前面一部分。我整理了一份典型分配表:
| 中断号范围 | 类型 | SoC 典型分配 |
|---|---|---|
| 0 - 15 | SGI | Linux IPI 调用(IPI_RESCHEDULE 等) |
| 16 - 31 | PPI | CPU 本地定时器、PMU 事件 |
| 32 - 95 | SPI | UART0(32)、I2C0(33)、GPIO(34~63) 等 |
| 96 - 127 | SPI | DMA 控制器、USB 控制器 |
| 128+ | SPI | GPU、视频编解码等高速外设 |
嗯,这里要注意:中断号在设备树里通常写的是 SPI 偏移号,不是 GIC 全局 ID。比如 UART0 的 SPI 偏移是 0,对应 GIC ID 32。我见过不少新手直接在设备树里写 32,结果中断始终不触发——因为内核会帮你加上 32 的基值。
2.4 中断分组与安全扩展
GIC 还支持将中断分为 Group0 和 Group1。Group0 走 FIQ,用于安全世界(TrustZone);Group1 走 IRQ,用于非安全世界(Linux 内核)。
说白了,这是 ARM 为了虚拟化和安全隔离设计的。普通驱动开发基本只碰 Group1 的 IRQ,但如果你在做 TEE(可信执行环境)相关的驱动,就得处理 Group0 的 FIQ 了。
2.5 中断处理流程速览
一个 SPI 从外设触发到 CPU 执行 ISR,大致经过这几步:
- 外设拉高中断线,GIC 分发器检测到电平/边沿事件。
- 分发器仲裁优先级,选出当前最高优先级的中断。
- 分发器查目标 CPU 掩码,把中断发给对应核的 CPU 接口。
- CPU 接口向核发送 IRQ 信号,CPU 跳转到异常向量表。
- CPU 读 GICC_IAR 寄存器,获取中断号,跳转到对应的 ISR。
- ISR 处理完成后写 GICC_EOIR,通知 GIC 中断结束。
GICD_ISENABLERn — 使能中断
GICD_IPRIORITYRn — 设置优先级
GICD_ITARGETSRn — 设置目标 CPU
GICC_IAR — 读取中断号(应答)
GICC_EOIR — 写结束标记
我个人习惯在调试时,先读 GICC_IAR 确认中断号是否和预期一致。如果号不对,多半是设备树配错了,或者 GIC 初始化阶段没把中断路由到当前核。