1. 什么是 GIC?
🔸 GIC 全称
GIC(Generic Interrupt Controller,通用中断控制器),是 ARM 公司专门为Cortex-A 系列内核设计的一款集中式中断控制器。
🔸 为什么需要 GIC?
随着 SoC(片上系统)复杂度提升,芯片中集成了大量外设(UART、I2C、SPI、GPIO、USB、Ethernet 等),每个外设都可能产生中断。如果让 CPU 直接处理每个外设的中断:
硬件连线复杂:每个外设都需要一根独立的中断线接到 CPU
软件处理困难:中断优先级、屏蔽、使能等操作难以统一管理
多核支持差:在多核 CPU 上难以实现中断路由
GIC 解决了上述问题,它将所有外设的中断集中管理,统一向 CPU 提交中断请求。
🔸 GIC 主要功能
| 功能 | 说明 |
|---|---|
| 中断使能/屏蔽 | 控制每个中断源是否使能 |
| 中断优先级管理 | 多个中断同时到达时,按优先级仲裁 |
| 中断分发 | 多核系统中将中断路由到指定的 CPU 核 |
| 中断状态维护 | 记录中断的 pending、active 等状态 |
| 中断通知 | 通过 IRQ 或 FIQ 信号通知 CPU |
🔸 GIC 版本演进
| 版本 | 架构 | 适用 CPU | 说明 |
|---|---|---|---|
| GIC V1 | 32 位 | Cortex-A5 | 早期版本 |
| GIC V2 | 32 位 | Cortex-A7、A9、A15 | i.MX6ULL 使用此版本 |
| GIC V3 | 64 位 | Cortex-A53、A57 | 支持更大规模系统 |
| GIC V4 | 64 位 | Cortex-A53、A57 | V3 增强版,支持虚拟中断 |
| GIC V5 | 新架构 | 最新高性能芯片 | 下一代架构 |
💡i.MX6ULL内置的是GIC-400(GIC V2 架构),最多支持 160 个中断源。
2. 什么是协处理器?ARM Cortex-A7 内核配备了几个协处理器?
🔸 协处理器(Coprocessor)定义
协处理器是一种协助主处理器完成特定功能的专用处理单元。在 ARM 架构中,协处理器通过一组专用的指令(MCR/MRC)和寄存器与主 CPU 交互,用于完成主 CPU 不擅长或效率不高的任务。
🔸 ARM 协处理器编号
ARM 架构最多支持16 个协处理器,编号为cp0 ~ cp15:
| 协处理器 | 用途 |
|---|---|
| cp0 ~ cp13 | 由芯片厂商自行定义(保留) |
| cp14 | 调试协处理器(Debug) |
| cp15 | 系统控制协处理器(最常用) |
⚠️ Cortex-A 系列实际只实现了cp14和cp15,其他协处理器编号保留未用。
🔸 cp15 系统控制协处理器
cp15 是 ARM 架构中最重要的协处理器,通过它可以访问 CPU 内部的关键控制寄存器:
| 主寄存器 (CRn) | 主要功能 |
|---|---|
| c0 | ID 寄存器、Cache 类型、TRM |
| c1 | SCTLR(系统控制寄存器,控制 MMU、Cache、异常向量表位置等) |
| c2 | TTBR(转换表基址寄存器,配置页表) |
| c3 | DACR(域访问控制寄存器) |
| c5 | FSR(故障状态寄存器) |
| c6 | FAR(故障地址寄存器) |
| c7 | Cache 控制、TLB 管理 |
| c8 | TLB 控制 |
| c12 | VBAR(异常向量表基址寄存器) |
| c13 | FCSE、上下文 ID |
| c15 | CBAR(GIC 基址寄存器,Cortex-A7 特有) |
🔸 协处理器操作指令
MRC{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2> ; 读取 cp15 寄存器到 ARM 寄存器 MCR{cond} p15, <opc1>, <Rt>, <CRn>, <CRm>, <opc2> ; 将 ARM 寄存器写入 cp15 寄存器常用示例:
; 读取 SCTLR 寄存器到 r0 mrc p15, 0, r0, c1, c0, 0 ; 将 r0 写回 SCTLR 寄存器 mcr p15, 0, r0, c1, c0, 0 ; 读取 GIC 基址到 r1(CBAR 寄存器) mrc p15, 4, r1, c15, c0, 0 ; 设置异常向量表基址(VBAR 寄存器) mcr p15, 0, r0, c12, c0, 0
🔸 Cortex-A7 协处理器总结
| 项目 | 数量/内容 |
|---|---|
| 理论支持数 | 16 个(cp0 ~ cp15) |
| 实际实现数 | 2 个(cp14、cp15) |
| cp14 | 调试协处理器 |
| cp15 | 系统控制协处理器(MMU、Cache、异常向量、GIC 基址) |
3. 为什么要将异常向量表重映射?如何重映射异常向量表?
🔸 为什么要重映射异常向量表?
原因一:异常向量表位置的固定性
ARM 处理器上电复位后,异常向量表默认位于:
| 异常向量表基址 | 触发条件 |
|---|---|
| 0x00000000 | SCTLR.V = 0(默认) |
| 0xFFFF0000 | SCTLR.V = 1(高端向量) |
绝大多数 MCU/MPU 的启动代码(Boot ROM)都映射在低地址 0x00000000,所以默认情况下异常向量表位于此处。
原因二:每项向量只有 4 字节
异常向量表中每项只有 4 字节(刚好放下 1 条跳转指令),无法容纳完整的中断服务程序:
; 默认异常向量表 b reset_handler ; 0x00: 占 4 字节 b undefined_handler ; 0x04: 占 4 字节 b swi_handler ; 0x08: 占 4 字节 b prefetch_abort_handler ; 0x0C: 占 4 字节 b data_abort_handler ; 0x10: 占 4 字节 b reserved_handler ; 0x14: 占 4 字节 b irq_handler ; 0x18: 占 4 字节 b fiq_handler ; 0x1C: 占 4 字节
真正的中断服务程序往往需要几十甚至上百条指令,无法直接放在异常向量表中。
原因三:NOR Flash / ROM 中无法直接写代码
NOR Flash支持 XIP(片内执行),但写入困难
Boot ROM只能读不能写
如果异常向量表位于 NOR Flash 或 ROM 中,运行后无法修改,也难以调试。
重映射的好处
将异常向量表重映射到RAM(高速、可读写)后:
| 优势 | 说明 |
|---|---|
| 可在 RAM 中修改 | 方便调试和动态更新 |
| 访问速度快 | RAM 访问速度远高于 Flash |
| 可容纳完整程序 | 异常向量表每项可以是一个完整函数 |
🔸 如何重映射异常向量表?
方法一:通过 SCTLR.V 位切换基址
Cortex-A7 通过 cp15 的c1(SCTLR)寄存器的V 位(bit 13)控制异常向量表位置:
| SCTLR.V | 异常向量表基址 |
|---|---|
| 0 | 0x00000000(默认) |
| 1 | 0xFFFF0000 |
操作示例:
mrc p15, 0, r0, c1, c0, 0 ; 读取 SCTLR 到 r0 orr r0, r0, #(1 << 13) ; 设置 V 位(高端向量 0xFFFF0000) mcr p15, 0, r0, c1, c0, 0 ; 写回 SCTLR
⚠️ 这种方法只能切换到 0x00000000 或 0xFFFF0000 两个固定地址,灵活性差。
方法二:通过 VBAR 寄存器设置基址(Cortex-A7 推荐)
Cortex-A7 提供VBAR(Vector Base Address Register)寄存器,cp15 的c12,可以任意指定异常向量表的基址:
; 将异常向量表基址设置为 0x87800000 ldr r0, =0x87800000 mcr p15, 0, r0, c12, c0, 0 ; 写入 VBAR
这样就可以将异常向量表放到 RAM 的任意位置(如 0x87800000),方便调试和管理。
方法三:完整的异常向量表重映射流程(i.MX6ULL 示例)
; 1. 初始化 DDR bl init_ddr ; 2. 将异常向量表代码拷贝到 DDR 地址 0x87800000 ldr r0, =_start ; 源地址(Flash 中的异常向量表) ldr r1, =0x87800000 ; 目标地址(DDR) ldr r2, =__vector_end ; 结束地址 cpy: ldmia r0!, {r3 - r10} stmia r1!, {r3 - r10} cmp r0, r2 ble cpy ; 3. 通过 VBAR 寄存器重映射异常向量表 ldr r0, =0x87800000 mcr p15, 0, r0, c12, c0, 0 ; 4. 切换 ARM 状态、设置栈指针,然后跳转到 C 语言 main 函数 cps #0x1F ; 切换到 System 模式 msr cpsr_c, #0xD3 ; 切换到 SVC 模式 ldr sp, =0x80800000 ; 设置 SVC 模式栈指针 msr cpsr_c, #0xD7 ; 切换到 Abort 模式 ldr sp, =0x80800000 msr cpsr_c, #0xDB ; 切换到 Undef 模式 ldr sp, =0x80800000 msr cpsr_c, #0xD2 ; 切换到 IRQ 模式 ldr sp, =0x80800000 msr cpsr_c, #0xD1 ; 切换到 FIQ 模式 ldr sp, =0x80800000 msr cpsr_c, #0x13 ; 切换到 SVC 模式 b main ; 跳转到 main 函数🔸 异常向量表示例(在 DDR 0x87800000 处)
; 异常向量表 b reset_handler ; 0x00: 复位 b undefined_handler ; 0x04: 未定义指令 b swi_handler ; 0x08: 软中断 b prefetch_abort_handler ; 0x0C: 预取中止 b data_abort_handler ; 0x10: 数据中止 b reserved_handler ; 0x14: 保留 b irq_handler ; 0x18: IRQ 中断 b fiq_handler ; 0x1C: FIQ 中断
4. GIC 支持多少个中断,分为哪几类?
🔸 GIC 中断总数
GIC(V2 架构)最多支持1020 个中断源,编号为ID0 ~ ID1019。
🔸 中断分类
GIC 将所有中断分为3 大类:
| 类别 | 全称 | 中断 ID 范围 | 数量 | 用途 |
|---|---|---|---|---|
| SGI | Software Generated Interrupts | 0 ~ 15 | 16 | 软件生成中断,用于 CPU 核间通信 |
| PPI | Private Peripheral Interrupts | 16 ~ 31 | 16 | 私有外设中断,每个 CPU 核独享 |
| SPI | Shared Peripheral Interrupts | 32 ~ 1019 | 988 | 共享外设中断,所有 CPU 共享 |
🔸 三类中断详细说明
1. SGI(软件生成中断)
编号:0 ~ 15
触发方式:由软件写 GIC 的SGIR 寄存器主动触发
典型用途:
多核 CPU 之间的通信(IPI,Inter-Processor Interrupt)
例如 CPU0 需要 CPU1 做一些工作时,CPU0 触发一个 SGI 通知 CPU1
特点:
完全由软件控制,灵活性高
只能路由到其他 CPU 核(不能发给触发它的核自身)
2. PPI(私有外设中断)
编号:16 ~ 31
触发来源:外设,但每个外设只对应一个 CPU 核
典型外设:
本地 Timer
看门狗(Watchdog)
性能监控计数器
特点:
每个 CPU 核有自己独立的 PPI
多核系统中,每个核看到的 PPI 可能不同
3. SPI(共享外设中断)
编号:32 ~ 1019
触发来源:SoC 中的各种外设
典型外设:
GPIO中断
UART、I2C、SPI中断
USB、Ethernet、SD 卡中断
LCD、PWM、ADC中断
特点:
所有 CPU 核共享
GIC 负责决定路由到哪个 CPU 核
数量最多,是嵌入式开发中最常接触的一类中断
🔸 GIC 中断分类示意图
GIC(最多 1020 个中断 ID = 0 ~ 1019) │ ├─── SGI(0 ~ 15,共 16 个)软件中断(核间通信) │ └─ 写 SGIR 寄存器触发 │ ├─── PPI(16 ~ 31,共 16 个)私有外设中断 │ └─ 本地 Timer、Watchdog 等 │ └─── SPI(32 ~ 1019,共 988 个)共享外设中断 └─ GPIO、UART、I2C、USB、SD 等
🔸 GIC 与 CPU 交互的关键寄存器
| 寄存器 | 偏移 | 读写 | 功能 |
|---|---|---|---|
| GICD_CTLR | 0x000 | R/W | GIC 全局控制寄存器,使能/禁用 GIC |
| GICD_ISENABLER | 0x100 | R/W | 中断使能寄存器 |
| GICD_ICENABLER | 0x180 | R/W | 中断禁用寄存器 |
| GICD_IPRIORITYR | 0x400 | R/W | 中断优先级寄存器 |
| GICD_ITARGETSR | 0x800 | R/W | 中断目标 CPU 寄存器 |
| GICD_SGIR | 0xF00 | W | 软件生成中断寄存器 |
| GICC_CTLR | 0x2000 | R/W | CPU 接口控制寄存器 |
| GICC_PMR | 0x2004 | R/W | 中断优先级屏蔽寄存器 |
| GICC_IAR | 0x200C | R | 中断确认寄存器(读取得到中断 ID) |
| GICC_EOIR | 0x2010 | W | 中断结束寄存器(写入通知 GIC 中断处理完成) |
5. i.MX6ULL 中支持多少个中断?
🔸 i.MX6ULL 中断总览
i.MX6ULL 共支持160 个中断源,编号ID0 ~ ID159,其中:
| 中断类型 | ID 范围 | 数量 | 说明 |
|---|---|---|---|
| SGI | 0 ~ 15 | 16 | 保留,软件可触发 |
| PPI | 16 ~ 31 | 16 | 私有外设中断(本地 Timer、Watchdog 等) |
| SPI | 32 ~ 159 | 128 | 共享外设中断(GPIO、UART、I2C 等) |
| 合计 | 0 ~ 159 | 160 | GIC V2 架构支持 |
💡 i.MX6ULL 的 GIC 实现为GIC-400,属于GIC V2版本,采用 32 位架构,与 Cortex-A7 内核配套。
🔸 i.MX6ULL 常用中断源示例
| 中断 ID | 中断名称 | 说明 |
|---|---|---|
| 32 + 32×n | GPIO1_Combined_0_15_IRQn ~ GPIO5_Combined_0_15_IRQn | GPIO1 ~ GPIO5 中断 |
| 32 + 16 | GPIO1_Combined_16_31_IRQn | GPIO1 第 16 ~ 31 引脚中断 |
| ... | ... | ... |
| 58 | UART1_IRQn | UART1 中断 |
| 59 | UART2_IRQn | UART2 中断 |
| 60 | UART3_IRQn | UART3 中断 |
| 61 | UART4_IRQn | UART4 中断 |
| 62 + 32n | I2C1_IRQn ~ I2C4_IRQn | I2C 中断 |
| ... | ... | ... |
完整中断号可参考 NXP 官方手册i.MX6ULL Reference Manual中的Chapter 3: Interrupts章节。
🔸 i.MX6ULL 中断处理流程
i.MX6ULL 中断处理流程 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1. 中断源发出中断请求(外设产生中断信号) ↓ 2. CPU 响应中断、比较中断优先级 ↓ 3. 保护现场(CPSR → SPSR_<mode>,LR 保存返回地址) ↓ 4. 切换到 IRQ 模式,跳转到异常向量表 ↓ 5. 执行中断服务函数(irq_handler) ↓ 6. 读取 GIC 的 GICC_IAR 寄存器,得到中断 ID ↓ 7. 根据中断 ID 调用对应的中断处理函数 ↓ 8. 处理完成后,写 GIC 的 GICC_EOIR 寄存器通知 GIC ↓ 9. 恢复现场(SPSR → CPSR,返回到被打断的程序继续执行)
🔸 i.MX6ULL 中断初始化示例(伪代码)
/* 1. 初始化 GIC 控制器 */ void GIC_Init(void) { /* 关闭所有 SPI 中断 */ for (i = 32; i < 160; i += 32) { GICD_ICENABLER[i / 32] = 0xFFFFFFFF; } /* 设置所有 SPI 中断优先级为最低 */ for (i = 32; i < 160; i += 4) { GICD_IPRIORITYR[i / 4] = 0xFFFFFFFF; } /* 设置所有 SPI 中断目标为 CPU0 */ for (i = 32; i < 160; i += 4) { GICD_ITARGETSR[i / 4] = 0x01010101; } /* 使能 GIC */ GICD_CTLR = 3; GICC_CTLR = 1; } /* 2. 使能某个外设的中断(如 GPIO1_18) */ void GIC_EnableIRQ(IRQn_Type IRQn) { GICD_ISENABLER[IRQn / 32] |= (1 << (IRQn % 32)); } /* 3. 设置中断优先级 */ void GIC_SetPriority(IRQn_Type IRQn, uint8_t priority) { GICD_IPRIORITYR[IRQn / 4] |= (priority << ((IRQn % 4) * 8)); } /* 4. 中断服务函数(Cortex-A7 通用入口) */ void irq_handler(void) { uint32_t intID = GICC_IAR; /* 读取中断 ID */ /* 根据中断 ID 调用对应处理函数 */ system_irq_handler(intID); GICC_EOIR = intID; /* 通知 GIC 中断处理完成 */ }🔸 CPSR 中断相关控制位
| 位 | 名称 | 功能 |
|---|---|---|
| I | IRQ 中断禁止位 | 0 = 使能 IRQ,1 = 禁止 IRQ |
| F | FIQ 中断禁止位 | 0 = 使能 FIQ,1 = 禁止 FIQ |
相关汇编指令:
cpsie i ; 使能 IRQ 中断(CPSR.I = 0) cpsid i ; 禁止 IRQ 中断(CPSR.I = 1) cpsie f ; 使能 FIQ 中断 cpsid f ; 禁止 FIQ 中断 msr cpsr_c, r0 ; 修改 CPSR 低 8 位
📝 总结
| 序号 | 题目 | 核心要点 |
|---|---|---|
| 1 | 什么是 GIC | ARM 设计的通用中断控制器,集中管理 SoC 中所有中断 |
| 2 | 协处理器 | ARM 支持 16 个(cp0~cp15),Cortex-A7 实现 2 个(cp14 调试、cp15 系统控制) |
| 3 | 重映射异常向量表 | 原因:默认 0x00 每项只有 4 字节,且在 Flash/ROM 中不便修改;方法:通过 VBAR 寄存器(cp15.c12)任意指定基址 |
| 4 | GIC 中断分类 | 共 1020 个:SGI(0~15,软件中断)、PPI(16~31,私有外设)、SPI(32~1019,共享外设) |
| 5 | i.MX6ULL 中断数 | 160 个(0~159),GIC V2 架构,配套 Cortex-A7 处理器 |
🔗 中断相关关键寄存器速查
| 寄存器 | 含义 | 操作 |
|---|---|---|
| SCTLR.V(cp15.c1.bit13) | 异常向量表位置 | 0: 0x00000000;1: 0xFFFF0000 |
| VBAR(cp15.c12) | 异常向量表基址(推荐使用) | 可设置为任意地址 |
| CBAR(cp15.c15) | GIC 基址寄存器 | 用于获取 GIC 控制器基地址 |
| GICC_IAR(GIC 偏移 0x200C) | 中断确认寄存器 | 读取得到中断 ID |
| GICC_EOIR(GIC 偏移 0x2010) | 中断结束寄存器 | 通知 GIC 中断处理完成 |