以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
参考博客
s5pv210的中断体系 - biaohc - 博客园
我的RTOS 之一 --S5PV210 异常向量表基址和软中断测试_liujia2100的博客-CSDN博客
从0开始学ARM-异常及中断处理、异常向量表、swi_一口Linux的技术博客_51CTO博客
一、异常及中断的简介
1、异常的介绍
在ARM体系结构中,存在以下7种异常情况。
异常情况 描述 FIQ 快速中断 IRQ 一般的中断 保留 …… Data Abort 如果一个预取指令试图存取一个非法的内存单元,这时异常产生 Prefetch Abort 当一个指令被从内存中预取时,由于某种原因而失败,如果它能到达执行状态这个异常才会产生 Software Interrupt 当一个软中断指令被执行完的时候执行 Undefined Instruction 当流水线中的某个非法指令到达执行状态时执行 Reset 上电时执行 (1)复位异常(Reset)
当CPU刚上电时或按下reset重启键之后,会进入该异常。该异常在管理模式下处理。(2)未定义指令异常(Undefined Instruction)
在流水线技术里的译码阶段,如果当前指令不能被识别为有效指令,则产生未定义指令异常。该异常在未定义异常模式下处理。(3)软件中断指令异常(Software Interrupt)
该异常是应用程序自己调用时产生的,用于用户程序申请访问硬件资源时。例如:printf()打印函数,要将用户数据打印到显示器上,用户程序要想实现打印必须申请使用显示器,而用户程序又没有外设硬件的使用权,只能通过使用软件中断指令切换到内核态,通过操作系统内核代码来访问外设硬件,内核态是工作在特权模式下,操作系统在特权模式下完成将用户数据打印到显示器上。
这样做是为了保护操作系统的安全和硬件资源的合理使用。该异常在管理模式下处理。
(4)预取指令中止异常(Prefetch Abort)
该异常发生在CPU流水线取指阶段,如果目标指令地址是非法地址进入该异常。该异常在中止异常模式下处理。(5)数据中止访问异常(Data Abort)
该异常发生在要访问数据地址不存在或者为非法地址时。该异常在中止异常模式下处理。(6)一般中断请求异常(IRQ)、快速中断请求异常(FRQ)
CPU与外部设备相对独立,CPU对全部设备进行管理和资源调度处理,CPU要想知道外部设备的运行状态,可以让CPU定时查看外部设备的特定寄存器(轮询),或者在需要CPU干涉处理时让外部设备打断CPU(中断),让CPU来处理外部设备的请求。
显然第二种方式更合理,可以让CPU专心工作,这里的“打断”操作就叫做中断请求。
根据请求的紧急情况,中断请求分一般中断和快速中断。绝大部分外设使用一般中断请求。快速中断具有最高中断优先级和最小的中断延迟,通常用于处理高速数据传输及通道的中数据恢复处理,如DMA等。
2、中断的介绍
从上面关于“异常”的描述可知,中断属于异常的一种,分为“快速中断”和“一般中断”。
中断主要用来解决宏观上的并行需求。微观上的并行,指的是真正的并行,即使精确到每一秒甚至每一刻,多个事情还是在同时进行。宏观上的并行,并不等于微观上的并行,有时候宏观上是并行的,微观上是串行的。
单核CPU无法实现微观上的并行,但是通过中断机制,可以实现宏观的并行。
衡量一个操作系统的实时性的指标,包括最短响应中断时间、单位时间内响应中断次数。
3、异常向量表
3.1 异常向量表的定义
CPU事先定义了一些特定地址作为特定异常的入口地址。异常发生时,CPU会根据实际情况把PC的值设置为这些特定地址中的某一个,则CPU接下来会取出并执行这个地址所对应的存储单元里的指令。这指令一般是跳转指令,以跳转到专门处理某个异常的子程序。这些特定异常及其入口地址的对应关键系,就称为异常向量表。
比如定义0x00000000这个地址为复位异常向量地址,当发生复位异常时,CPU会自动跳转到0x00000000地址去执行指令;比如定义0x30000008这个地址为外部中断对应的异常向量地址,当发生外部中断时,CPU会自动跳转到0x30000008地址去执行指令。
所有架构(比如51单片机、PIC单片机)的CPU的中断,都是通过异常向量表实现的,但是不同CPU异常向量表的构造(比如有哪些异常向量、这些异常向量的相对位置)和异常向量表的基地址往往不同。复杂的ARM还可以软件设置异常向量表的基地址。
一个常见的异常向量表设置如下:
相对于基地址的偏移量 异常情况 0x1C FIQ 0x18 IRQ 0x14 保留 0x10 Data Abort 0x0C Prefetch Abort 0x08 Software Interrupt 0x04 Undefined Instruction 0x00 Reset 3.2 异常向量表的优先级
优先Reset→Data abort→FIQ→IRQ→Prefetch abort→Undefined instruction / SWI。
3.3 s5pv210的异常向量表
上面所列的常见异常向量表,也是s5pv210的异常向量表,不过要注意以下几点内容。
(1)s5pv210 的异常向量表的基地址
系统刚启动时,SDRAM 还没有初始化,程序都在SRAM中运行,因此s5pv210在SRAM中设置了异常向量表,以供暂时性使用。如下图所示,S5PV210在SRAM中设置的异常向量表的起始地址为0xD003_7400。
(2)s5pv210 的异常向量表的修改s5pv210 的异常向量表可以修改,以适应操作系统的需求。具体见参考博文二。
二、S5PV210的中断处理流程
1、中断处理的2个阶段
第一个阶段是异常向量表跳转,第二个阶段是进入异常处理程序。
2、中断处理的第一阶段
第一个阶段依赖于CPU设计时提供的异常向量表机制。
主要任务是从异常发生到响应异常并且保存现场,然后跳转到真正的异常处理程序处。
3、中断处理的第二阶段
第二个阶段,主要任务是识别哪个中断源发生了中断,然后调用相应的中断处理程序。
(1)S3C2440的第二阶段处理过程
怎么找到具体是哪个中断?
S3C2440的中断控制器中有一个32位的寄存器,寄存器的每一个位对应一个中断源。为了解决支持更多的中断源,2440又设计了一个子中断机制。在一级中断寄存器中有一些中断共用一个bit位,比如AC97和WDT。对于共用中断,用子中断来区分哪一个发生中断。
怎么找到对应的ISR?
首先给每个中断做了个编号,进入isr_handler之后先通过查阅中断源寄存器和子中断寄存器(中哪一位为1)确定中断的编号,然后用这个编号去isr数组(isr数组是中断初始化时事先设定好的,就是把各个中断的isr的函数名组成一个数组,用中断对应的编号作为索引来查询这个数组)中查阅得到isr地址。
评价好不好?
上面第一个过程中使用子中断搞成2级的很麻烦;第二个过程中计算中断编号也耗费时间,而中断处理的时间是很宝贵的。系统有一个性能指标,叫实时性。实时性就是中断发生到响应的时间,这个时间越短越好。
(2)S5PV210的第二阶段处理过程
怎么找到具体是哪个中断?
S5PV210因为支持的中断源很多,所以直接设计了4个中断寄存器,每个32位,每位对应一个中断源。理论上210最多支持128个中断,实际支持不足128个,因为有些位是空的。S5PV210没有子中断寄存器,每个中断源都是并列的。当中断发生时,在irq_handler中依次去查询4个中断源寄存器,看哪一个中断源寄存器的哪一位被置1,则这个位对应的寄存器就发生了中断,即找到了中断编号。
怎么找到对应的isr?
S5PV210中支持的中断源很多,如果使用2440的那一套来寻找isr的地址,实时性就不好了。S5PV210提供了很多寄存器来解决每个中断源对应isr的寻找问题。当发生相应中断时,硬件会自动的将相应isr推入一定的寄存器中,我们软件只要去这个寄存器中执行函数就行了。
(3)总结对比
第一阶段2440和210几乎完全相同,实际上几乎所有的CPU在第一阶段都是相同的。
第二阶段不同。各个SoC根据自身对实时性的要求、中断源的多少,各自发明了寻找中断编号、寻找对应isr地址的方式。
三、S5PV210的中断控制器
这里所说的中断控制器,是指与中断有关的寄存器所组成的集合。
1、 中断使能与禁止寄存器:VICnINTENABLE、VICnINTENCLEAR
(1)VICnINTENABLE 和 VICnINTENCLEAR寄存器,分别负责中断使能与中断禁止。
(2)n=0,1,2,3,对应着四个寄存器,每个寄存器都是32bit,每个bit对应一个中断源是否使能 。
- VIC0INTENABLE VIC0INTCLEAR
- VIC1INTENABLE VIC1INTCLEAR
- VIC2INTENABLE VIC2INTCLEAR
- VIC3INTENABLE VIC3INTCLEAR
如果想启用某个中断,则需要在此中断编号所对应的VICnINTENABLE寄存器的相应的位上写1;如果想禁止某个中断源,则需要在VICnINTENCLEAR寄存器的相应的位写1。
注意,有些CPU可以在同一个寄存器位进行中断使能和禁止的设置,写1使能写0禁止(或者反过来);而有些CPU的中断使能和禁止分开为2个寄存器,要使能中断就写使能寄存器,要禁止中断就写禁止寄存器,这里就是这种情况。
(3)代码示例
//使能中断 //传参intnum表示要启动的某个具体的中断源,中断号在int.h中定义,是物理中断号 void intc_enable(unsigned long intnum) {unsigned long temp;// 确定intnum在哪个寄存器的哪一位// <32就是0~31,必然在VIC0if(intnum<32){temp = VIC0INTENABLE;temp |= (1<<intnum); VIC0INTENABLE = temp;}else if(intnum<64){temp = VIC1INTENABLE;temp |= (1<<(intnum-32));VIC1INTENABLE = temp;}else if(intnum<96){temp = VIC2INTENABLE;temp |= (1<<(intnum-64));VIC2INTENABLE = temp;}else if(intnum<NUM_ALL){temp = VIC3INTENABLE;temp |= (1<<(intnum-96));VIC3INTENABLE = temp;}// NUM_ALL : enable all interruptelse{VIC0INTENABLE = 0xFFFFFFFF;VIC1INTENABLE = 0xFFFFFFFF;VIC2INTENABLE = 0xFFFFFFFF;VIC3INTENABLE = 0xFFFFFFFF;} }
2、中断模式选择寄存器:VICnINTSELECT
(1)VICnINTSELECT寄存器,用来设置各个中断的模式为IRQ还是FIQ。一般都设置成IRQ。
(2)n=0,1,2,3,对应着四个寄存器,可以设置各个中断的模式。
- VIC0INTSELECT
- VIC1INTSELECT
- VIC2INTSELECT
- VIC3INTSELECT
(3)IRQ和FIQ的区别
s5pv210支持2种中断,IRQ和FIQ,IRQ是普通中断,FIQ是快速中断。快速中断提供一种更快响应处理的中断通道,用于对实时性要求很高的中断源。CPU提供了一些机制保证FIQ可以被快速处理,从而保证实时性。但是只能有一个中断源被设置为FIQ,其他都是IRQ。
(4)FIQ比IRQ快的原因
FIQ模式有专用的r8~r12,因此中断服务程序可以直接使用r8-r12而不用保存,这就能节省时间。
另外,异常向量表中FIQ是最后一个异常向量入口,因此FIQ模式的中断服务程序不需要跳转,可以直接写在原地,这样就比其他异常少跳转一次,节省一些时间。
(5)代码示例
// 清除需要处理的中断的中断处理函数的地址 void intc_clearvectaddr(void) {// VICxADDR:当前正在处理的中断的中断处理函数的地址(即isr的入口地址)VIC0ADDR = 0;VIC1ADDR = 0;VIC2ADDR = 0;VIC3ADDR = 0; }// 初始化中断控制器 void intc_init(void) {// 禁止所有中断// 为什么在中断初始化之初要禁止所有中断?// 因为中断一旦打开,因为外部或者硬件自己的原因产生中断后一定就会寻找isr// 而我们可能认为自己用不到这个中断就没有提供isr,这时它自动拿到的就是乱码// 则程序很可能跑飞,所以不用的中断一定要关掉。// 一般的做法是先全部关掉,然后再逐一打开自己感兴趣的中断。一旦打开就必须// 给这个中断提供相应的isr并绑定好。VIC0INTENCLEAR = 0xffffffff;VIC1INTENCLEAR = 0xffffffff;VIC2INTENCLEAR = 0xffffffff;VIC3INTENCLEAR = 0xffffffff;// 这里把所有的中断源的中断类型设置为IRQ;其实可以具体到设置每一个中断的中断模式 VIC0INTSELECT = 0x0;VIC1INTSELECT = 0x0;VIC2INTSELECT = 0x0;VIC3INTSELECT = 0x0;// 清VICxADDRintc_clearvectaddr(); }
3、 中断状态寄存器:VICnIRQSTATUS、VICnFIQSTATUS
(1)当发生中断时,硬件会自动将该寄存器的对应位设置为1,以表示发生了中断发。软件在处理中断第二阶段的第一阶段,就是通过查询这个寄存器来得到中断编号的。
(2)n=0,1,2,3,对应着四个寄存器,每个寄存器的每个位表征否发生中断。
- VIC0IRQSTATUS
- VIC1IRQSTATUS
- VIC2IRQSTATUS
- VIC3IRQSTATUS
(3)代码示例
// 通过读取VICnIRQSTATUS寄存器,判断其中哪个有一位为1,来得知哪个VIC发生中断了 unsigned long intc_getvicirqstatus(unsigned long ucontroller) {if(ucontroller == 0)return VIC0IRQSTATUS;else if(ucontroller == 1)return VIC1IRQSTATUS;else if(ucontroller == 2)return VIC2IRQSTATUS;else if(ucontroller == 3)return VIC3IRQSTATUS;else{}return 0; }// 真正的中断处理程序。意思就是说这里只考虑中断处理,不考虑保护/恢复现场 void irq_handler(void) {//printf("irq_handler.\n");// SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断// 这么多中断irq在第一个阶段走的是一条路,都会进入到irq_handler来// 我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断// 对应的isr。// 虽然硬件已经自动帮我们把isr放入了VICnADDRESS中,但是因为有4个,所以我们必须// 先去软件的检查出来到底哪个VIC中断了,也就是说isr到底在哪个VICnADDRESS寄存器中unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};int i=0;void (*isr)(void) = NULL;for(i=0; i<4; i++){// 发生一个中断时,4个VIC中有3个是全0,1个的其中一位不是0if(intc_getvicirqstatus(i) != 0){isr = (void (*)(void)) vicaddr[i];break;}}(*isr)(); // 通过函数指针来调用函数 }
4、中断优先级设置寄存器:VICnVECTPRIORITYm
(1)中断优先级设置寄存器,可以设置多个中断同时发生时先处理谁后处理谁的问题。一般来说高优先级的中断可以打断低优先级的中断,从而嵌套处理中断。
(2)n=0,1,2,3;m=0,1,…,31
- VIC0VECTPRIORITY0,VIC0VECTPRIORITY1,…,VIC0VECTPRIORITY31
- VIC1VECTPRIORITY0,VIC1VECTPRIORITY1,…,VIC1VECTPRIORITY31
- VIC2VECTPRIORITY0,VIC2VECTPRIORITY1,…,VIC2VECTPRIORITY31
- VIC3VECTPRIORITY0,VIC3VECTPRIORITY1,…,VIC3VECTPRIORITY31
5、存放中断处理函数首地址的寄存器:VICnVECTADDRm
(1)这些寄存器用来存放(各个中断所对应的)中断处理函数的首地址。
(2)每一个中断源都有一个VECTADDR寄存器,程序员在设置中断的时候,把这个中断的中断处理函数的首地址直接放入这个中断所对应的VECTADDR寄存器即可。
(3)n=0,1,2,3;m=0,1,…,31
- VIC0VECTADDR0,VIC0VECTADDR1,…,VIC0VECTADDR31
- VIC1VECTADDR0,VIC1VECTADDR1,…,VIC1VECTADDR31
- VIC2VECTADDR0,VIC2VECTADDR1,…,VIC2VECTADDR31
- VIC3VECTADDR0,VIC3VECTADDR1,…,VIC3VECTADDR31
(4)代码示例
// 绑定我们写的isr到VICnVECTADDR寄存器 // 绑定过之后我们就把isr地址交给硬件了,剩下的我们不用管了,硬件自己会处理 // 等发生相应中断的时候,我们直接到相应的VICnADDR中去取isr地址即可。 // 参数:intnum是int.h定义的物理中断号,handler是函数指针,就是我们写的isr// VIC0VECTADDR定义为VIC0VECTADDR0寄存器的地址,就相当于是VIC0VECTADDR0~31这个 // 数组(这个数组就是一个函数指针数组)的首地址,然后具体计算每一个中断的时候 // 只需要首地址+偏移量即可。 void intc_setvectaddr(unsigned long intnum, void (*handler)(void)) {//VIC0if(intnum<32){*( (volatile unsigned long *)(VIC0VECTADDR + 4*(intnum-0)) ) = (unsigned)handler;}//VIC1else if(intnum<64){*( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;}//VIC2else if(intnum<96){*( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler;}//VIC3else{*( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler;}return; }
6、VICnADDRESS寄存器
(1)当发生中断时,硬件自动识别中断编号,并且自动找到这个中断所对应的VECTADDR寄存器,然后把寄存器的内容复制到VICADDRESS中,以供后续使用。这样的设计避免了软件查找中断源和中断处理函数,节省了时间,提高了s5pv210的中断响应速度。
(2)代码示例
// 真正的中断处理程序。意思就是说这里只考虑中断处理,不考虑保护/恢复现场 void irq_handler(void) {//printf("irq_handler.\n");// SoC支持很多个(在低端CPU例如2440中有30多个,在210中有100多个)中断// 这么多中断irq在第一个阶段走的是一条路,都会进入到irq_handler来// 我们在irq_handler中要去区分究竟是哪个中断发生了,然后再去调用该中断// 对应的isr。(函数指针)// 虽然硬件已经自动帮我们把isr放入了VICnADDR中,但是因为有4个,所以我们必须// 先去软件的检查出来到底哪个VIC中断了,也就是说isr到底在哪个VICADDR寄存器中unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};int i=0;void (*isr)(void) = NULL;for(i=0; i<4; i++){// 发生一个中断时,4个VIC中有3个是全0,1个的其中一位不是0if(intc_getvicirqstatus(i) != 0){isr = (void (*)(void)) vicaddr[i];break;}}(*isr)(); // 通过函数指针来调用函数 }
7、总结
以上介绍的寄存器中,能表征中断流程的寄存器有:
(1)VICnIRQSTATUS(4个)
- VIC0IRQSTATUS
- VIC1IRQSTATUS
- VIC2IRQSTATUS
- VIC3IRQSTATUS
(2)VICnVECTADDRm寄存器(4*32=128个)
- VIC0VECTADDR0,VIC0VECTADDR1,……VIC0VECTADDR31
- VIC1VECTADDR0,VIC1VECTADDR1,……VIC1VECTADDR31
- VIC2VECTADDR0,VIC2VECTADDR1,……VIC2VECTADDR31
- VIC3VECTADDR0,VIC3VECTADDR1,……VIC3VECTADDR31
(3) VICnADDRESS寄存器(4个)
- VIC0ADDRESS
- VIC1ADDRESS
- VIC2ADDRESS
- VIC3ADDRESS
它们之间的关系如下: