平台:smart210
CPU:s5pv210
目标:学习s5pv210的中断体系,从一个子中断的使能等设置的流程中体会完整的中断过程。
知识储备:向量中断控制器的介绍
主要有4个VIC,4个TZIC还有一个很特殊的ARM PrimeCell PL192。主要的VIC和TZIC可以支持93个中断源。其中TZIC是为TrustZone技术所准备的,ARM TrustZone® 技术是系统范围的安全方法,针对高性能计算平台上的大量应用,包括安全支付、数字版权管理 (DRM)、企业服务和基于 Web 的服务(援引官方的介绍)。TZIC提供了安全控制的nFIQ中断并且使该这类中断从传统的不安全中断VIC中屏蔽掉,从而达到应用在数据传输时的安全性和保密性。
支持的核心功能有固定的硬件中断优先级以及可编程的软件中断优先级(包括设置与屏蔽),还有这是为合理调配中断而必须要用到的。还有对应的中断信号生成、中断请求等等。还有特权模式的访问。
VIC有四个组,其中每个组均包含有32个中断源,合计128个中断源(当然有一些是空的),所支持的中断源,从GPIO的EINT到UART等各类通讯总线再到多媒体信号等,可以说是无所不包,所以我们只要利用好向量中断控制器,才能更加高效地使用CPU的计算资源,服务于具体的工作需求。
以VIC0为例,分析具体的寄存器。
RAW INTERRUPT就是该中断组在没有设置VIC0INTSELECT和VIC0INTENABLE之前的中断状态(初值不确定),而FIQ和IRQ是设置完那两个寄存器之后的中断状态(初值确定为0)。所以我们就有了VIC0IRQSTATUS、VIC0FIQSTATUS与VIC0RAWSTATUS这三个只读状态寄存器,每一位都对应着该组(这里是VIC0组)的一个中断源。
还有一些中断优先级设置与屏蔽,软中断等寄存器可供设置。
其次,还有VIC0VECTADDR0~VICVECTADDR31用来设置各中断源的中断服务程序入口,VIC0VECTPRIORITY0~VIC0VECTPRIORITY31用来根据需要设置各中断源的优先级。还有如下相关的寄存器,我们可以根据需要做相应的读写。
VIC0VECTADDR是一个具有特殊功用的寄存器,对它的读写最好是在中断服务程序内,读的时候能读出当前中断运行程序的地址,写任何值则将清除掉现在正在运行的中断服务程序,而且要求必须在在中断服务程序的末尾对该寄存器作任意值写操作。
VIC0PERIPHID0还有VIC0PCELLID0这些只读寄存器则只是提供了一些识别信息,基本上不需要用到。
除了上面的内容之外,我们还需要理解以下几点:
1.异常向量表
我们已知,memory map标志出了Interal SRAM (即iRAM)的地址范围:0xD002_0000~0xD003_8000,其中的Exception Vector Table是异常向量所属空间,我们已知ARM有以下的特性:
ARM 系列的处理器可以工作 7 种模式下。除 User Mode 以外的其它 Mode 都叫做特权模式,除 User 和 System 以外的其它 5 种模式叫做异常模式,分别为: - 快速中断模式(fiq):高速数据传输或通道处理。 - 外部中断模式(irq):通用的中断处理。 - 管理模式(svc):操作系统使用的保护模式。 - 数据访问终止模式(abt):当数据或指令预取终止时进入该模式。 - 定义指令中止模式(und):当未定义的指令执行时进入该模式。
我们需要对这五种异常模式定义好对应的处理程序的入口地址,尤其是常见的两个中断模式:FIQ与IRQ,比如当【按键中断】发生的时候,处理器就将进入IRQ模式,首先要进行现场保护(硬件自动完成),然后从固定入口进入中断处理函数,中断处理函数首先判断是哪个中断组发生中断,然后直接执行该中断组对应的VICxINTADDR寄存器里面的函数。我们知道,一个中断组有32个中断源,为什么我们可以直接运行中断组的VICxINTADDR指定的函数而无需知道是具体哪个中断源触发了中断呢?这是由于中断组内的中断源在触发中断后硬件会自动把对应中断源的处理函数入口地址赋给所属组的VICxINTADDR,这样我们就能省去检索中断源再跳转到对应中断函数入口的时间了,当然,前提是我们要把【按键中断】的服务程序入口地址赋给某个对应的寄存器(假设是VIC0INTADDR12,所属组是VIC0组,所对应入口是VIC0INTADDR)。
对应的程序思路是这样的:
//1.设置异常模式的处理程序入口,应注意到IRQ_handle函数 void system_initexception( void) { pExceptionUNDE = (unsigned long)exceptionundef; pExceptionSWI = (unsigned long)exceptionswi; pExceptionPABORT = (unsigned long)exceptionpabort; pExceptionDABORT = (unsigned long)exceptiondabort; pExceptionIRQ = (unsigned long)IRQ_handle; pExceptionFIQ = (unsigned long)IRQ_handle; //左边都是对应的向量地址,右边是编写好的处理程序的函数名,通过赋值达到设置的目的 intc_init(); }
//2.中断控制器相关初始化 void intc_init(void) { //清除中断使能(禁止中断) VIC0INTENCLEAR = 0xffffffff; VIC1INTENCLEAR = 0xffffffff; VIC2INTENCLEAR = 0xffffffff; VIC3INTENCLEAR = 0xffffffff; //设置所有中断源为IRQ中断 VIC0INTSELECT = 0x0; VIC1INTSELECT = 0x0; VIC2INTSELECT = 0x0; VIC3INTSELECT = 0x0; //清除每个中断组服务程序入口地址,即四个VICxINTADDR寄存器 intc_clearvectaddr(); }
//3.通过该函数设置外部程序作为对应中断源服务程序入口,第一个参数为中断号(一个中断源对应一个中断号),第二个参数为函数地址。 void intc_setvectaddr(unsigned long intnum, void (*handler)(void)) { //VIC0 if(intnum<32) { *( (volatile unsigned long *)(VIC0VECTADDR + 4*intnum) ) = (unsigned)handler; } //VIC1 else if(intnum<64) { *( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler; } //VIC2 else if(intnum<96) { *( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler; } //VIC3 else { *( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler; } return; }
//4.在start.S中做好一些准备工作,程序最先开始运行start.S,然后设置好CPSR_cxsf后,允许了中断,跳转到main函数。 .global _start .global IRQ_handle _start: ldr r0, =0xE2700000 mov r1, #0 str r1, [r0] ldr sp, =0x40000000 mov r0, #0x53 msr CPSR_cxsf, r0 bl clock_init bl main IRQ_handle: ldr sp, =0xD0037F80 sub lr, lr, #4 stmfd sp!, {r0-r12, lr} bl irq_handler ldmfd sp!, {r0-r12, pc}^
//5.main函数的作用 int main(void) { int c = 0; //初始化串口 uart_init(); //初始化异常向量表 system_initexception(); printf("**************Int test *************** \r\n"); // 1111 = EXT_INT[16] 设置GPIO的H2组作为外部中断引脚,实质上就是开发板上的实体按键 GPH2CON = GPH2_0_EINT16|GPH2_1_EINT17|GPH2_2_EINT18|GPH2_3_EINT19; GPH3CON = GPH3_0_EINT24|GPH3_1_EINT25|GPH3_2_EINT26|GPH3_3_EINT27; // 010 = Falling edge triggered // EXT_INT[16]~EXT_INT[19]是属于EXT_INT_2的,通过这个寄存器设置为下降沿触发,而EXT_INT[24]~EXT_INT[27]是属于EXT_INT_3的。 EXT_INT_2_CON |= 2<<0|2<<4|2<<8|2<<12; EXT_INT_3_CON |= 2<<0|2<<4|2<<8|2<<12; //unmask,作用是允许该外部中断组的具体引脚的中断触发功能 EXT_INT_2_MASK &= (~(1<<0)) & (~(1<<1)) & (~(1<<2)) & (~(1<<3)); EXT_INT_3_MASK &= (~(1<<0)) & (~(1<<1)) & (~(1<<2)) & (~(1<<3)); //NUM_EINT16_31是一个中断源,只要EINT16到EINT31的任意一个引脚触发了中断,就能引发之前设置好的中断服务程序,这里通过 //intc_setvectaddr()函数把中断号跟对应中断服务程序联系起来。 intc_setvectaddr(NUM_EINT16_31, isr_key); //使能该中断源 intc_enable(NUM_EINT16_31); while (1) { printf("%d\r\n",c++); delay(0x100000); } } void isr_key(void) { printf("We get external interrupt:EINT16_31 (key1 to key 8)\r\n"); //下面的程序是检测是该中断源里的哪个引脚触发的中断,用来辨别是哪个KEY被按下了。 unsigned long what; what = (EXT_INT_2_PEND & 0x0f) + ((EXT_INT_3_PEND & 0xf) << 4) ; unsigned long i; for(i=0;i<8;i++) { if(what & 1<<i) printf("Now the key is key%d\n",i+1); } // clear VIC0ADDR intc_clearvectaddr(); // clear pending bit EXT_INT_2_PEND |= 1<<0|1<<1|1<<2|1<<3; EXT_INT_3_PEND |= 1<<0|1<<1|1<<2|1<<3; }void irq_handler(void) { unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR}; int i=0; void (*isr)(void) = NULL; for(; i<4; i++) { if(intc_getvicirqstatus(i) != 0) { isr = (void (*)(void)) vicaddr[i]; break; } } (*isr)(); }
首先是进入start.S(清bss,开中断,设置时钟,进main),然后是main(初始化串口,初始化异常向量表,初始化中断控制器,配置GPH2和GPH3作为外部中断引脚,配置EXT_INT2和EXT_INT3触发下降沿中断,取消EXT_INT2与EXT_INT3的中断屏蔽,设置该外部中断源的处理函数入口,使能该中断源的中断,死循环不断打印数字)。
当按键中断发生后,根据异常向量表,程序会自动跳转到IRQ_handle(在start.S内)处执行,IRQ_handle先进行了现场保护,然后跳转到irq_handler,irq_handler首先是遍历四个中断组,看看是哪个中断组发生了中断,然后就跳转到该中断组的中断服务程序里(前面已经说到,isr_key作为按键中断服务程序的入口已根据中断号存入VICxINTADDRn中,而发生中断的同时,硬件会将中断源的服务程序入口自动搬移到它所属的中断组的VICxINTADDR里),从而间接地运行了isr_key这个程序,在这个程序里,我们做了分析,找出了触发中断的具体按键,并通过串口打印出来。