嵌入式Linux之中断向量表

中断向量表

    • 一、回顾STM32中断系统
    • 二、Cortex-A7中断系统
    • 三、中断实验编写

参考文档 【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.2.pdf

一、回顾STM32中断系统

1、STM32中断向量表

ARM芯片从0X00000000开始运行,执行指令。在程序开始的地方存放着中断向量表。中断向量表主要功能是描述中断对应的中断服务函数。
对于STM32来说代码最开始的地址存放堆栈栈顶指针。

2、中断向量偏移

一般ARM从0X000000000地址开始运行,对于STM32我们设置连接首地址为0X8000000。
如果代码一定要从0X8000000开始运行,那么需要告诉一下soc内核。也就是设置中断向量偏移。设置SCB的VTOR寄存器为新的中断向量表起始地址即可。

3、NVIC中断控制器。

 NVIC就是中断管理机构。使能和关闭指定的中断、设置中断优先级。

4、中断服务函数的编写

中断服务函数就是中断要做的事情。当中断发生以后中断服务函数就会被调 用,我们要处理的工作就可以放到中断服务函数中去完成。
同样以 STM32F103 的 PE2 为例, 其中断服务函数如下所示:
/* 外部中断 2 服务程序 */ 
void EXTI2_IRQHandler(void) 
{
  /* 中断处理代码 */   
}  

当 PE2 引脚的中断触发以后就会调用其对应的中断处理函数 EXTI2_IRQHandler,我们可 以在函数 EXTI2_IRQHandler 中添加中断处理代码。

二、Cortex-A7中断系统

1、Cortex-A中断向量表

Cortex-A中断向量表有8个中断,其中重点关注IRQ。Cortex-A的中断向量表需要用户自己去定义。
嵌入式Linux之中断向量表_第1张图片
Cortex-A 内核 CPU 的所有外部中 断都属于这个 IQR 中断,当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服 务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,进而根据具体的中断做出 相应的处理。这些外部中断和 IQR 中断的关系如下图所示:
嵌入式Linux之中断向量表_第2张图片

、复位中断(Rest),CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面做 一些初始化工作,比如初始化 SP 指针、DDR 等等。  
、未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。  
、软中断(Software Interrupt,SWI),由 SWI 指令引起的中断,Linux 的系统调用会用 SWI 指 令来引起软中断,通过软中断来陷入到内核空间。  
、指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。 
、数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。  
、IRQ 中断(IRQ Interrupt),外部中断,前面已经说了,芯片内部的外设中断都会引起此中 断的发生。 
、FIQ 中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中。  

在上面的 7 个中断中,我们常用的就是复位中断和 IRQ 中断,所以我们需要编写这两个中断的中断服务函数

2、中断向量偏移

我们的裸机历程都是从0X87800000开始的,因此要设置中断向量偏移。

3、GIC中断控制器。

同NVIC一样,GIC用于管理Cortex-A的中断。GIC提供了开关中断,设置中断优先级。

4、IMX6U中断号

为了区分不同的中断,引入了中断号。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。这 1020 个 ID 包 含了 PPI、SPI 和 SGI
ID0-ID15是给SGI,ID16-ID31是给PPI。剩下的ID32-1019给SPI,也就是按键中断、串口中断。。。。
6ULL支持128个中断。

4、中断服务函数的编写

一个是IRQ中断服务函数的编写,另一个就是在IRQ中断服务函数里面去查找并运行的具体的外设中断服务函数,

三、中断实验编写

.global _start  				/* 全局标号 */
/*描述:	_start函数,首先是中断向量表的创建
 * 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器)
 * 		 	ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常)
 */
_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									


第 6 到 14 行是中断向量表

第 17 到 81 行是复位中断服务函数 Reset_Handler,

第 19 行先调用指令“cpsid i”关闭 IRQ,

第 24 到 30 行是关闭 I/D Cache、MMU、对齐检测和分支预测。

第 33 行到 42 行是汇编版本的 中断向量表重映射。

第 50 到 68 行是设置不同模式下的 sp 指针,分别设置 IRQ 模式、SYS 模 式和 SVC 模式的栈指针,每种模式的栈大小都是 2MB。第 70 行调用指令“cpsie i”重新打开 IRQ 中断,

第 72 到 79 行是操作 CPSR 寄存器来打开 IRQ 中断。当初始化工作都完成以后就可 以进入到 main 函数了,

第 81 行就是跳转到 main 函数。

第 110 到 144 行是中断服务函数 IRQ_Handler,这个是本章的重点,因为所有的外部中断 最终都会触发 IRQ 中断,所以 IRQ 中断服务函数主要的工作就是区分去当前发生的什么中断 (中断 ID)?然后针对不同的外部中断做出不同的处理。

第 111 到 115 行是保存现场,

第 117 到 122 行是获取当前中断号,中断号被保存到了 r0 寄存器中。

第 131 和 132 行才是中断处理的重点,这两行相当于调用了函数 system_irqhandler,函数 system_irqhandler 是一个 C 语言函数, 此函数有一个参数,这个参数中断号,所以我们需要传递一个参数。汇编中调用 C 函数如何实现参数传递呢?根据 ATPCS(ARM-Thumb Procedure Call Standard)定义的函数参数传递规则,在 汇编调用 C 函数的时候建议形参不要超过 4 个,形参可以由 r0~r3 这四个寄存器来传递,如果 形参大于 4 个,那么大于 4 个的部分要使用堆栈进行传递。所以给 r0 寄存器写入中断号就可以了函数 system_irqhandler 的参数传递,在 136 行已经向 r0 寄存器写入了中断号了。中断的真正 处理过程其实是在函数 system_irqhandler 中完成,稍后需要编写函数 system_irqhandler。 这里先注释掉,#是汇编的行注释。

你可能感兴趣的:(Linux)