设备驱动-10.中断子系统-5 armv7 GIC架构解析

发布时间 2023-09-29 19:03:58作者: fuzidage

1 armv7 GIC介绍

armv7 32位 gic采用v2版本,参考手册 https://developer.arm.com/documentation/ihi0048/bb/?lang=en

image
image

GIC400 就是v2版本的中断控制器 IP 核,当 GIC 接收到外部中断信号以后就会报给 ARM 内核。框架如下:
image

GIC 架构分为了两个逻辑块:Distributor 和 CPU Interface,也就是分发器端和 CPU 接口端。
分发器用来全局中断使能控制,每一个中断使能开关,中断优先级,外部中断触发方式(边沿触发、电平触发)等。
cpu接收端用来接收中断信号汇报给cpu, 如:应答中断,通知中断处理完成,定义抢占策略,设置优先级掩码,当多个中断到来选择最高优先级的中断号。
例如:I.MX6U给了一个 core_ca7.h定义了GIC的所有寄存器。

/*
* GIC 寄存器描述结构体,
* GIC 分为分发器端和 CPU 接口端
*/
typedef struct {
	/* 分发器端寄存器 */
	int32_t RESERVED0[1024];
	_IOM uint32_t D_CTLR; /* Offset: 0x1000 (R/W) */
	_IM uint32_t D_TYPER; /* Offset: 0x1004 (R/ ) */
	_IM uint32_t D_IIDR; /* Offset: 0x1008 (R/ ) */
	int32_t RESERVED1[29];
	_IOM uint32_t D_IGROUPR[16]; /* Offset: 0x1080 - 0x0BC (R/W) */
	uint32_t RESERVED2[16];
	__IOM uint32_t D_ISENABLER[16];/* Offset: 0x1100 - 0x13C (R/W) */
	uint32_t RESERVED3[16];
	__IOM uint32_t D_ICENABLER[16];/* Offset: 0x1180 - 0x1BC (R/W) */
	uint32_t RESERVED4[16];
	__IOM uint32_t D_ISPENDR[16]; /* Offset: 0x1200 - 0x23C (R/W) */
	uint32_t RESERVED5[16];
	__IOM uint32_t D_ICPENDR[16]; /* Offset: 0x1280 - 0x2BC (R/W) */
	uint32_t RESERVED6[16];
	__IOM uint32_t D_ISACTIVER[16];/* Offset: 0x1300 - 0x33C (R/W) */
	uint32_t RESERVED7[16];
	__IOM uint32_t D_ICACTIVER[16];/* Offset: 0x1380 - 0x3BC (R/W) */
	uint32_t RESERVED8[16];
	__IOM uint8_t D_IPRIORITYR[512];/* Offset: 0x1400 - 0x5FC (R/W) */
	uint32_t RESERVED9[128];
	__IOM uint8_t D_ITARGETSR[512];/* Offset: 0x1800 - 0x9FC (R/W) */
	uint32_t RESERVED10[128];
	__IOM uint32_t D_ICFGR[32]; /* Offset: 0x1C00 - 0xC7C (R/W) */
	uint32_t RESERVED11[32];
	__IM uint32_t D_PPISR; /* Offset: 0x1D00 (R/ ) */
	__IM uint32_t D_SPISR[15]; /* Offset: 0x1D04 - 0xD3C (R/ ) */
	uint32_t RESERVED12[112];
	__OM uint32_t D_SGIR; /* Offset: 0x1F00 ( /W) */
	uint32_t RESERVED13[3];
	__IOM uint8_t D_CPENDSGIR[16];/* Offset: 0x1F10 - 0xF1C (R/W) */
	__IOM uint8_t D_SPENDSGIR[16];/* Offset: 0x1F20 - 0xF2C (R/W) */
	uint32_t RESERVED14[40];
	__IM uint32_t D_PIDR4; /* Offset: 0x1FD0 (R/ ) */
	__IM uint32_t D_PIDR5; /* Offset: 0x1FD4 (R/ ) */
	__IM uint32_t D_PIDR6; /* Offset: 0x1FD8 (R/ ) */
	__IM uint32_t D_PIDR7; /* Offset: 0x1FDC (R/ ) */
	__IM uint32_t D_PIDR0; /* Offset: 0x1FE0 (R/ ) */
	__IM uint32_t D_PIDR1; /* Offset: 0x1FE4 (R/ ) */
	__IM uint32_t D_PIDR2; /* Offset: 0x1FE8 (R/ ) */
	__IM uint32_t D_PIDR3; /* Offset: 0x1FEC (R/ ) */
	__IM uint32_t D_CIDR0; /* Offset: 0x1FF0 (R/ ) */
	__IM uint32_t D_CIDR1; /* Offset: 0x1FF4 (R/ ) */
	__IM uint32_t D_CIDR2; /* Offset: 0x1FF8 (R/ ) */
	__IM uint32_t D_CIDR3; /* Offset: 0x1FFC (R/ ) */

	/* CPU 接口端寄存器 */
	__IOM uint32_t C_CTLR; /* Offset: 0x2000 (R/W) */
	__IOM uint32_t C_PMR; /* Offset: 0x2004 (R/W) */
	__IOM uint32_t C_BPR; /* Offset: 0x2008 (R/W) */
	__IM uint32_t C_IAR; /* Offset: 0x200C (R/ ) */
	__OM uint32_t C_EOIR; /* Offset: 0x2010 ( /W) */
	__IM uint32_t C_RPR; /* Offset: 0x2014 (R/ ) */
	__IM uint32_t C_HPPIR; /* Offset: 0x2018 (R/ ) */
	__IOM uint32_t C_ABPR; /* Offset: 0x201C (R/W) */
	__IM uint32_t C_AIAR; /* Offset: 0x2020 (R/ ) */
	__OM uint32_t C_AEOIR; /* Offset: 0x2024 ( /W) */
	__IM uint32_t C_AHPPIR; /* Offset: 0x2028 (R/ ) */
	uint32_t RESERVED15[41];
	__IOM uint32_t C_APR0; /* Offset: 0x20D0 (R/W) */
	uint32_t RESERVED16[3];
	__IOM uint32_t C_NSAPR0; /* Offset: 0x20E0 (R/W) */
	uint32_t RESERVED17[6];
	__IM uint32_t C_IIDR; /* Offset: 0x20FC (R/ ) */
	uint32_t RESERVED18[960];
	__OM uint32_t C_DIR; /* Offset: 0x3000 ( /W) */
} GIC_Type;

1.1 GIC 类型

GIC 将众多的中断源分为分为三类:
①、SPI(Shared Peripheral Interrupt),共享中断,顾名思义,所有 Core 共享的中断,这个是最常见的,那些外部中断都属于 SPI 中断 。
②、PPI(Private Peripheral Interrupt),私有中断,我们说了 GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。
③、SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。

1.2 中断 ID

为了区分这些不同的中断源肯定要给他们分配一个唯一 ID,这些 ID 就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,ID0~ID1019 中断使能和禁止。这 1020 个 ID 包含了 PPI、SPI 和 SGI。

ID0~ID15:这 16 个 ID 分配给 SGI。
ID16~ID31:这 16 个 ID 分配给 PPI。
ID32~ID1019:这 988 个 ID 分配给 SPI。

例如:I.MX6U 的总共使用了 128 个中断 ID,加上前面属于 PPI 和 SGI 的 32 个 ID,I.MX6U 的中断源共有 128+32=160,那么irq为0的中断ID即为32。
个。NXP 官方 SDK中的文件 MCIMX6Y2C.h定义了160个中断ID。
image

#define NUMBER_OF_INT_VECTORS 160 /* 中断源 160 个,SGI+PPI+SPI*/

typedef enum IRQn {
	/* Auxiliary constants */
	otAvail_IRQn = -128,
	/* Core interrupts */
	oftware0_IRQn = 0, 
	oftware1_IRQn = 1, 
	Software2_IRQn = 2, 
	Software3_IRQn = 3, 
	Software4_IRQn = 4, 
	Software5_IRQn = 5, 
	Software6_IRQn = 6, 
	Software7_IRQn = 7, 
	Software8_IRQn = 8, 
	Software9_IRQn = 9, 
	Software10_IRQn = 10, 
	Software11_IRQn = 11, 
	Software12_IRQn = 12, 
	Software13_IRQn = 13, 
	Software14_IRQn = 14, 
	Software15_IRQn = 15, 
	VirtualMaintenance_IRQn = 25, 
	HypervisorTimer_IRQn = 26, 
	VirtualTimer_IRQn = 27, 
	LegacyFastInt_IRQn = 28, 
	SecurePhyTimer_IRQn = 29, 
	NonSecurePhyTimer_IRQn = 30, 
	LegacyIRQ_IRQn = 31, 
	/* Device specific interrupts */
	IOMUXC_IRQn = 32, 
	DAP_IRQn = 33, 
	SDMA_IRQn = 34, 
	TSC_IRQn = 35, 
	SNVS_IRQn = 36, 
	...... ...... 
} IRQn_Type;

1.3 中断使能

1.3.1 IRQ 和 FIQ 总中断使能

"CPSR程序状态寄存器”已经讲过了,寄存器 CPSR 的 I=1 禁止 IRQ,当 I=0 使
能 IRQ;F=1 禁止 FIQ,F=0 使能 FIQ。我们还有更简单的指令来完成 IRQ 或者 FIQ 的使能和
禁止:

指令 描述
cpsid i 禁止 IRQ 中断。
cpsie i 使能 IRQ 中断。
cpsid f 禁止 FIQ 中断。
cpsie f 使能 FIQ 中断。

1.3.2 ID0~ID1019 中断使能和禁止

前面讲到中断ID有 ID0~ID1019, GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 用来完成外部中断的使能和禁止,对于 Cortex-A7 内核来说中断 ID 只使用了 512 个。
一共16组GICD_ISENABLER和GICD_ISENABLER,其中GICD_ISENABLER0 的 bit[15:0]对应ID15~0 的 SGI 中断,GICD_ISENABLER0 的 bit[31:16]对应 ID31~16 的 PPI 中断。剩下的GICD_ISENABLER1~GICD_ISENABLER15 就是控制 SPI 中断的。

1.3.3 中断优先级数量 GICC_PMR

Cortex-A7 GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高!Cortex-A7 选择了 32 个优先级,GICC_PMR 寄存器,此寄存器用来决定使用几级优先级,GICC_PMR 寄存器只有低 8 位有效,这 8 位最多可以设置 256 个优先级:
image
I.MX6U 为例 Cortex-A7内核,所以支持 32 个优先级,因此 GICC_PMR 要设置为 0b11111000。

1.3.4 中断抢占优先级和子优先级位数 GICC_BPR

寄存器 GICC_BPR 只有低 3 位有效,其值不同,抢占优先级和子优先级占用的位数也不同:
image
比如 I.MX6U 的优先级位数为 5(32 个优先级),所以可以设置 Binary point 为 2,表示 5 个优先级位全部为抢占优先级。

1.3.5 中断priority D_IPRIORITYR

Cortex-A7 使用了 512 个中断 ID,每个中断 ID 配有一个优先级寄存器,所以一共有 512 个 D_IPRIORITYR 寄存器。如果优先级个数为 32 的话,使用寄存器 D_IPRIORITYR 的 bit7:4 来设置优先级,也就是说实际的优先级要左移 3 位。比如要设置ID40 中断的优先级为 5,示例代码如下:

GICD_IPRIORITYR[40] = 5 << 3;

1.4 GIC控制器寄存器介绍

前面讲了GIC 架构分为了两个逻辑块:Distributor 和 CPU Interface,也就是分发器端和 CPU 接口端。

1.4.1 GIC的内存映射

image
GIC基地址偏移0x1000是分发器 block, 偏移0x2000是CPU 接口端 block。

1.4.1.1 分发器寄存器

image

1.4.1.2 cpu接口端寄存器

image
GICC_IAR寄存器描述来自《ARM Generic Interrupt Controller Architecture Specification.pdf》,它用来表示中断ID号。
image
处理完具体的中断处理函数,需要将GICC_IAR寄存器的值写入GICC_EOIR寄存器中。
For every read of a valid Interrupt ID from the GICC_IAR, the connected processor must perform a matching write to the GICC_EOIR. The value written to the GICC_EOIR must be the interrupt ID read from the GICC_IAR。
image

2. 中断示例start.s分析

以nxp的IMX6UL为例,SDK中core_ca7.h定了了GIC相关函数:

函数 描述
GIC_Init 初始化 GIC。
GIC_EnableIRQ 使能指定的外设中断。
GIC_DisableIRQ 关闭指定的外设中断。
GIC_AcknowledgeIRQ 返回中断号。
GIC_DeactivateIRQ 无效化指定中断。
GIC_GetRunningPriority 获取当前正在运行的中断优先级。
GIC_SetPriorityGrouping 设置抢占优先级位数。
GIC_GetPriorityGrouping 获取抢占优先级位数。
GIC_SetPriority 设置指定中断的优先级。
GIC_GetPriority 获取指定中断的优先级。
点击查看代码
.global _start  				/* 全局标号 */
_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(快速中断)未定义中断 			*/

/* 复位中断 */	
Reset_Handler:

	cpsid i						/* 关闭全局中断 */

	/* 关闭I,DCache和MMU 
	 * 采取读-改-写的方式。
	 */
	mrc     p15, 0, r0, c1, c0, 0     /* 读取CP15的C1寄存器到R0中       		        	*/
    bic     r0,  r0, #(0x1 << 12)     /* 清除C1寄存器的bit12位(I位),关闭I Cache            	*/
    bic     r0,  r0, #(0x1 <<  2)     /* 清除C1寄存器的bit2(C位),关闭D Cache    				*/
    bic     r0,  r0, #0x2             /* 清除C1寄存器的bit1(A位),关闭对齐						*/
    bic     r0,  r0, #(0x1 << 11)     /* 清除C1寄存器的bit11(Z位),关闭分支预测					*/
    bic     r0,  r0, #0x1             /* 清除C1寄存器的bit0(M位),关闭MMU				       	*/
    mcr     p15, 0, r0, c1, c0, 0     /* 将r0寄存器中的值写入到CP15的C1寄存器中	 				*/

	
#if 0
	/* 汇编版本设置中断向量表偏移 */
	ldr r0, =0X87800000

	dsb
	isb
	mcr p15, 0, r0, c12, c0, 0
	dsb
	isb
#endif
    
	/* 设置各个模式下的栈指针,
	 * 注意:IMX6UL的堆栈是向下增长的!
	 * 堆栈指针地址一定要是4字节地址对齐的!!!
	 * DDR范围:0X80000000~0X9FFFFFFF
	 */
	/* 进入IRQ模式 */
	mrs r0, cpsr
	bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
	orr r0, r0, #0x12 	/* r0或上0x13,表示使用IRQ模式					*/
	msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/
	ldr sp, =0x80600000	/* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB */

	/* 进入SYS模式 */
	mrs r0, cpsr
	bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
	orr r0, r0, #0x1f 	/* r0或上0x13,表示使用SYS模式					*/
	msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/
	ldr sp, =0x80400000	/* 设置SYS模式下的栈首地址为0X80400000,大小为2MB */

	/* 进入SVC模式 */
	mrs r0, cpsr
	bic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/
	orr r0, r0, #0x13 	/* r0或上0x13,表示使用SVC模式					*/
	msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/
	ldr sp, =0X80200000	/* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */

	cpsie i				/* 打开全局中断 */
#if 0
	/* 使能IRQ中断 */
	mrs r0, cpsr		/* 读取cpsr寄存器值到r0中 			*/
	bic r0, r0, #0x80	/* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */
	msr cpsr, r0		/* 将r0重新写入到cpsr中 			*/
#endif

	b main				/* 跳转到main函数 			 	*/

/* 未定义中断 */
Undefined_Handler:
	ldr r0, =Undefined_Handler
	bx r0

/* SVC中断 */
SVC_Handler:
	ldr r0, =SVC_Handler
	bx r0

/* 预取终止中断 */
PrefAbort_Handler:
	ldr r0, =PrefAbort_Handler	
	bx r0

/* 数据终止中断 */
DataAbort_Handler:
	ldr r0, =DataAbort_Handler
	bx r0

/* 未使用的中断 */
NotUsed_Handler:

	ldr r0, =NotUsed_Handler
	bx r0

/* IRQ中断!重点!!!!! */
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寄存器中
								* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49
								* Cortex-A7 Technical ReferenceManua.pdf P68 P138
								*/							
	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 */

/* FIQ中断 */
FIQ_Handler:

	ldr r0, =FIQ_Handler	
	bx r0

2.1 启动流程

1.进入_start,初始化异常向量表。进入复位中断,初始化时钟,关闭看门狗,关闭MMU和ICACHE DCACHE,关闭总中断
2.设置各个模式的SP指针
3.代码段重定位到DDR上并且清bss段
4.开启总中断
5.跳转到C语言main函数执行

这里很多流程如代码重定位,清除bss,关闭看门狗等没有列举出来。