1. 中断向量表
(1)Cortex A7 中断系统
- 复位中断,CPU 复位以后就会进入复位中断,系统初始化。
- 未定义指令中断,如果指令不能识别的话就会产生此中断。
- 软中断: 由 SWI 指令引起的中断,Linux 的系统调用通过软中断来陷入到内核空间。
- 指令预取中止中断,预取指令的出错的时候会产生此中断。
- 数据访问中止中断,访问数据出错的时候会产生此中断。
- IRQ 中断,外部中断
- FIQ中断,快速中断,如果需要快速处理中断的话就可以使用此中断
(2)IRQ中断
Cortex-A 内核 CPU 的所有外部中断都属于这个 IRQ 中断,当任意一个外部中断发生的时候都会触发 IRQ 中断。
IRQ 中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,进而根据具体的中断做出相应的处理。
(3)IRQ 和 FIQ 中断使能
cpsid i 禁止 IRQ 中断。
cpsie i 使能 IRQ 中断。
cpsid f 禁止 FIQ 中断。
cpsie f 使能 FIQ 中断
对应的行为:
寄存器 CPSR :
I=1 禁止 IRQ,当 I=0 使能 IRQ;
F=1 禁止 FIQ, F=0 使能 FIQ。
(4)Cortex-A 向量表模板
<1> 创建中断向量表:
中断发生后,进入相应的中断处理函数。
_start:
ldr pc, =Reset_Handler /* 复位中断 */
ldr pc, =Undefined_Handler /* 未定义指令中断 */
ldr pc, =SVC_Handler /* SVC(Supervisor)中断 */
ldr pc, =PrefAbort_Handler /* 预取终止中断 */
ldr pc, =DataAbort_Handler /* 数据终止中断 */
ldr pc, =NotUsed_Handler /* 未使用中断 */
ldr pc, =IRQ_Handler /* IRQ 中断 */
ldr pc, =FIQ_Handler /* FIQ(快速中断)未定义中断 */
<2> 复位中断
初始化工作,比如初始化 SP 指针、 DDR 等
禁止IRQ中断:寄存器 CPSR 的 I 位为1
Reset_Handler:
cpsid i /* 关闭全局中断 */
关闭 I,DCache 和 MMU
/* 关闭 I,DCache 和 MMU
* 采取读-改-写的方式。
*/
mrc p15, 0, r0, c1, c0, 0 /* 读取 CP15 的 C1 寄存器到 R0 中 */
bic r0, r0, #(0x1 << 12) /* 清除 C1 的 I 位,关闭 I Cache */
bic r0, r0, #(0x1 << 2) /* 清除 C1 的 C 位,关闭 D Cache */
bic r0, r0, #0x2 /* 清除 C1 的 A 位,关闭对齐检查 */
bic r0, r0, #(0x1 << 11) /* 清除 C1 的 Z 位,关闭分支预测 */
bic r0, r0, #0x1 /* 清除 C1 的 M 位,关闭 MMU */
mcr p15, 0, r0, c1, c0, 0 /* 将 r0 的值写入到 CP15 的 C1 中 */
设置IRQ模式下的SP指针,每种模式下都有各自的SP指针。
需要给各个模式设置不同的SP指针
/* 设置各个模式下的栈指针,
* 注意: IMX6UL 的堆栈是向下增长的!
* 堆栈指针地址一定要是 4 字节地址对齐的!!!
* DDR 范围:0X80000000~0X9FFFFFFF 或者 0X8FFFFFFF
*/
/* 进入 IRQ 模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4 */
orr r0, r0, #0x12 /* r0 或上 0x12,表示使用 IRQ 模式 */
msr cpsr, r0 /* 将 r0 的数据写入到 cpsr 中 */
ldr sp, =0x80600000 /* IRQ 模式栈首地址为 0X80600000,大小为 2MB */
设置sys和user模式下的SP指针。
开中断,进入main函数
/* 进入 SYS 模式 */
mrs r0, cpsr
bic r0, r0, #0x1f /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4 */
orr r0, r0, #0x1f /* r0 或上 0x1f,表示使用 SYS 模式 */
msr cpsr, r0 /* 将 r0 的数据写入到 cpsr 中 */
ldr sp, =0x80400000 /* SYS 模式栈首地址为 0X80400000,大小为 2MB */
cpsie i /* 打开全局中断 */
b main
<3> IRQ中断
- 备份lr,spsr和共用的寄存器
- 从CP15的C0寄存器获取GIC的基地址
- 从GICC_IAR寄存器读取外部中断号
- 调用C语言的中断处理函数,传入中断号为参数
- 中断返回,恢复寄存器
IRQ_Handler:
push {lr} /* 保存lr地址 */
push {r0-r3, r12} /* 保存r0-r3,r12寄存器 */
mrs r0, spsr /* 读取spsr寄存器 */
push {r0} /* 保存spsr寄存器 */
mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中*/
add r1, r1, #0X2000 /* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */
ldr r0, [r1, #0XC] /* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,
* GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据
* 这个中断号来绝对调用哪个中断服务函数
*/
push {r0, r1} /* 保存r0,r1 */
cps #0x13 /* 进入SVC模式,允许其他中断再次进去 */
push {lr} /* 保存SVC模式的lr寄存器 */
ldr r2, =system_irqhandler /* 加载C语言中断处理函数到r2寄存器中*/
blx r2 /* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */
pop {lr} /* 执行完C语言中断服务函数,lr出栈 */
cps #0x12 /* 进入IRQ模式 */
pop {r0, r1}
str r0, [r1, #0X10] /* 中断执行完成,写EOIR */
pop {r0}
msr spsr_cxsf, r0 /* 恢复spsr */
pop {r0-r3, r12} /* r0-r3,r12出栈 */
pop {lr} /* lr出栈 */
subs pc, lr, #4 /* 将lr-4赋给pc */
2. GIC 控制器
(1)GIC 控制器总览
ARM 公司给 Cortex-A/R 内核提供的一个中断控制器。
I.MX6U 是 GIC V2。 GIC V2 最多支持 8 个核。
GIC 接收到外部中断信号以后就会报给 ARM 内核
VFIQ:虚拟快速 FIQ。VIRQ:虚拟外部 IRQ。FIQ:快速中断 IRQ。IRQ:外部中断 IRQ。
GIC V2总体框图:
左侧部分就是中断源
中间部分就是 GIC 控制器
右侧就是中断控制器向处理器内核发送中断信息
(2)中断源
- SPI 共享中断:所有 Core 共享的中断,外部中断都属于 SPI 中断。
- PPI 私有中断:每个核自己独有的中断。
- SGI 软件中断,由软件触发引起的中断,GICD_SGIR 写入数据来触发, 使用 SGI 中断来完成多核之间的通信
(3)中断ID
每一个 CPU 最多支持 1020 个中断 ID
ID0~ID15:这 16 个 ID 分配给 SGI
ID16~ID31:这 16 个 ID 分配给 PPI
ID32~ID1019:这 988 个 ID 分配给 SPI
某个 ID 对应哪个中断那就由半导体厂商根据实际情况去定义。
I.MX6U 的总共使用了 128 个SPI中断 ID。
typedef enum IRQn {
/* Auxiliary constants */
NotAvail_IRQn = -128, /**< Not available device specific interrupt */
/* Core interrupts */
Software0_IRQn = 0, /**< Cortex-A7 Software Generated Interrupt 0 */
// .... SGI
Software15_IRQn = 15, /**< Cortex-A7 Software Generated Interrupt 15 */
// ..... PPI
LegacyIRQ_IRQn = 31, /**< Cortex-A7 Legacy nIRQ Interrupt */
/* Device specific interrupts */
IOMUXC_IRQn = 32, /**< General Purpose Register 1 from IOMUXC. Used to notify cores on exception condition while boot. */
DAP_IRQn = 33, /**< Debug Access Port interrupt request. */
SDMA_IRQn = 34, /**< SDMA interrupt request from all channels. */
..........................
GPIO1_INT7_IRQn = 90, /**< INT7 interrupt request. */
GPIO1_INT6_IRQn = 91, /**< INT6 interrupt request. */
GPIO1_INT5_IRQn = 92, /**< INT5 interrupt request. */
GPIO1_INT4_IRQn = 93, /**< INT4 interrupt request. */
GPIO1_INT3_IRQn = 94, /**< INT3 interrupt request. */
GPIO1_INT2_IRQn = 95, /**< INT2 interrupt request. */
GPIO1_INT1_IRQn = 96, /**< INT1 interrupt request. */
GPIO1_INT0_IRQn = 97, /**< INT0 interrupt request. */
GPIO1_Combined_0_15_IRQn = 98, /**< Combined interrupt indication for GPIO1 signals 0 - 15. */
GPIO1_Combined_16_31_IRQn = 99, /**< Combined interrupt indication for GPIO1 signals 16 - 31. */
GPIO2_Combined_0_15_IRQn = 100, /**< Combined interrupt indication for GPIO2 signals 0 - 15. */
.......................................
Reserved158_IRQn = 158, /**< Reserved */
PMU_IRQ2_IRQn = 159 /**< Brown-out event on either core, gpu or soc regulators. */
} IRQn_Type;
(4)GIC 逻辑分块
两个逻辑块: Distributor 和 CPU Interface
<1> Distributor(分发器端)
中断事件应该发送到哪个 CPU Interface 上去
①、全局中断使能控制。
②、控制每一个中断的使能或者关闭。
③、设置每个中断的优先级。
④、设置每个中断的目标处理器列表。
⑤、设置每个外部中断的触发模式:电平触发或边沿触发。
⑥、设置每个中断属于组 0 还是组 1
<2> CPU Interface(CPU 接口端)
①、使能或者关闭发送到 CPU Core 的中断请求信号。
②、应答中断。
③、通知中断处理完成。
④、设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU core。
⑤、定义抢占策略。
⑥、当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core。
3. CP15协处理器
CP15 协处理器一般用于存储系统管理,CP15 协处理器一共有16 个 32 位寄存器。
MRC: 将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中。
MCR: 将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中。
MCR{cond} p15, , , , ,
(1) SCTLR 寄存器
系统控制寄存器,这个是 c1 的基本作用
完成控制功能的,比如使能或者禁止 MMU、 I/D Cache 等
MRC p15, 0, Rt, c1, c0, 0 ;读取 SCTLR 寄存器,数据保存到 Rt 中。
(2) VBAR 寄存器
向量表基地址寄存器:
代码段的起始地址,因外部RAM的起始地址一般不是0.
需要把代码再RAM中的 线性地址 传给 CP15.
ldr r0, =0X87800000 ; r0=0X87800000
MCR p15, 0, r0, c12, c0, 0 ;将 r0 里面的数据写入到 c12 中,即 c12=0X87800000
(3)CBAR 寄存器
GIC 基地址
MRC p15, 4, r1, c15, c0, 0 ;获取 GIC 基地址
ADD r1, r1, #0X2000 ;GIC 基地址加 0X2000 得到 CPU 接口端寄存器起始地址
4. 寄存器操作
(1)寄存器概述
GIC的寄存器被映射为内存中(Memory-mapped)。
GICD_* 为 Distributor相关的寄存器
GICC_* 为 CPU Interface相关的寄存器
GICV_, GICH_, 虚拟/管理相关的寄存器
CIG的寄存器基地址有协处理器 CP15 管理。存放在C0寄存器。
(2)ID0~ID1019 中断使能和禁止
GICD_ISENABLERn 外部中断的使能
GICD_ ICENABLERn 外部中断的禁止
上述寄存器各有16个,每一位对应一个中断(512/32=16)
SGI 中断:GICD_ISENABLER0 的 bit[15:0]
PPI 中断:GICD_ISENABLER0 的 bit[31:16]
SPI 中断:GICD_ISENABLER1~GICD_ISENABLER15
(3)中断优先级设置
<1> 优先级数配置
GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高
Cortex-A7 选择了 32 个优先级。
需要设置GICC_PMR 寄存器, 用 0~7 位决定几级优先级.
<2> 抢占优先级和子优先级位数
抢占优先级比子优先级的优先权更高, 抢占优先级更高的中断会先执行,而不管子优先级的优先权,数值越低优先级越高。
由寄存器 GICC_BPR 来决定, 由 0~2 位设置.
一般将所有的中断优先级位都配置为抢占优先级 为 32级.
<3> 优先级设置
优先级设置由寄存器 D_IPRIORITYR 来完成, 共有 512 个 D_IPRIORITYR 寄存器。
D_IPRIORITYR 的 bit7:4 来设置优先级
<4> 总述
- 设置寄存器 GICC_PMR,配置优先级个数,比如 I.MX6U 支持 32 级优先级。
- 设置抢占优先级和子优先级位数,一般为了简单起见,会将所有的位数都设置为抢占优先级。
- 设置指定中断 ID 的优先级,也就是设置外设优先级。
#define __GIC_PRIO_BITS 5
/* Make all interrupts have higher priority */
gic->C_PMR = (0xFFUL << (8 - __GIC_PRIO_BITS)) & 0xFFUL;
/* No subpriority, all priority level allows preemption */
gic->C_BPR = 7 - __GIC_PRIO_BITS;
/* 设置优先级 */
GIC_Type *gic = (GIC_Type *)(__get_CBAR() & 0xFFFF0000UL);
gic->D_IPRIORITYR[((uint32_t)(int32_t)IRQn)] = (uint8_t)((priority << (8UL - __GIC_PRIO_BITS)) & (uint32_t)0xFFUL);