S5PV210中断控制器详解(二):矢量和优先级

S5PV210采用了Arm公司的Prime PL192,它是一款支持可编程硬件优先级的矢量中断控制器(Vectored Interrupt Controller)。在这篇博文里,楼主会通过PL192手册的解读和一些实验,尝试挖掘其中的两个关键词:矢量和优先级,它们的深层次含义。
1 介绍
软件在收到中断后,其中必须做的一个任务是找到中断服务函数ISR(Interrupt Service Routine),并跳转过去执行。对于传统的中断控制器,软件的办法往往要先找到中断源,做法是遍历一个名字类似于IntStatus寄存器的每个bit位,找出其中不为0的位,则认为是其对于的中断发生了,然后再做一次映射,根据中断源查找到对应的ISR。这种方法,对于少量的中断源如十个以内,延迟不会太大,但像S5PV210这种有接近上百个中断,每次中断发生后,都需要遍历来查找中断源的话,效率未免也太低了,会造成很大的中断时延。要知道,中断时延在很多嵌入式系统里的重要指标之一。
为此,矢量中断控制器VIC应运而生,CPU在对VIC进行初始化的过程中,将各个中断源的ISR写入到VIC中存储起来,在中断发生以后,VIC会自动挑选出当前优先级最高的中断源,并将其ISR推送到VICADDRESS寄存器里,CPU可以直接拿到ISR(一般就是一个函数的起始地址),将PC跳转到这个地址取执行就可以了,速度大大加快。
这个过程可以用下面的图来更具体的解释:
上面是PL192里的部分模块的工作框图,Priority Logic模块是输入有VectIRQ0~31,VICVECTPRIORITY0~31和VICSWPRIORITYMASK,软件在需要先配置VICVECTPRIORITY0~31,将各个中断的优先级设置好,如果不配默认都是最低的0xF,VICSWPRIORITY可以屏蔽某写level的中断源,再将感兴趣的中断ISR配置到VICVECTADDR0~31,当中断发生时,即VectIRQ0~31中有一个或者多信号发生,Priority Logic 模块会根据前面配置仲裁出最高优先级的中断,将其输送给VICVECT模块,VICVECT模块会推送该中断的ISR到VICADDRESS寄存器,与此同时Priority Logic模块将IRQ信号输出到nVICIRQ引脚(它一般连接的就是ARM core的IRQ引脚)触发ARM CPU来响应该中断。
2 默认情况下的实验
为了更直观的观察和验证中断控制器的行为,楼主做了一系列的实验。首先将CPSR里屏蔽掉IRQ和FIQ,CPU不会响应任何中断,但中断控制器还是在正常工作,它的寄存器都也还是可以正常读写的的, 我们可以设计各种case对这些寄存器进行读和写。在这篇博文里,楼主以两个串口UART0和UART2的中断为例来分析讲解,因为楼主手上的210板子只有这两个串口。
首先来确认一个问题,默认情况下,UART0和UART2,哪个中断优先级较高?请看PL192 datasheet里的一段话
Hardware priority levels only take effect when multiple interrupts are programmed to have the same priority level, and occur at the same time. In this case, vectored interrupt0 has the highest priority, and interrupt 31 has the lowest priority
当多个中断具有相同优先级时,编号较小的一个具有较高的优先级,UART0编号为VIC1的10号,UART2为12号,所以,UART0的优先级比UART2要高,后面我们也会通过实验再验证这一点。
系统启动初始化完了之后,给UART2发生一个字符,触发一个UART2中断,此时去读取VIC1ADDRESS寄存器,得到的是UART2的ISR,并且VIC1IRQSTATUS=0x1000,这很OK。
这时,再给UART0发生一个字符,触发一个UART0中断,重新读取VIC1ADDRESS寄存器,得到的是依然是UART2的ISR,中断控制器不会改变VIC1ADDRESS。VIC1IRQSTATUS==0x1400(UART0和UART2两个中断都处在pending状态)
而如果CPU做一次清Intrrupt controller的操作(即写VIC1ADDRESS = 0),而不清UART2,(即UART2的中断源还在),然后再去读取VIC1ADDRESS寄存器,将会获取到的是UART0的ISR
反之,如果CPU清intrrupt controller(即写VIC1ADDRESS = 0)而不清UART0.,那么UART0和UART2的中断源都在,但是VIC1ADDRESS会始终显示UART0的ISR
现在可以得出两个结论:
1 CPU写VIC1ADDRESS = 0之前,即使有更高优先级的中断发生,intrrupt controller也不会改变VIC1ADDRESS
2 每次CPU写VIC1ADDRESS = 0之后,intrrupt controller会在现有的中断源中,重新找到优先级最高的一个,推送到VIC1ADDRESS
关于上面的实验结果,楼主在PL192的datasheet里找到了这样一段话。
The hardware masking is applied whenever an interrupt is being serviced,either with a read from the VICADDRESS Register. This prevents other active interrupts of an equal or lower priority generating a new IRQ while the interrupt service routine is being executed. When the interrupt routine has completed and the VICADDRESS Register has been written to, theinterrupt mask is cleared to allow all enabled interrupt sources through.
当软件读走VICADDRESS寄存器后,中断控制器硬件就会暂时屏蔽所有中断,不会再次触发CPU。
直到软件写VICADDRESS寄存器之后。这和我们的测试结果是相符的
At the end of the ISR, the VICADDRESS Register is written to, to update the priority hardware.

另外,楼主还做了一个比较无聊的实验,在写完VIC1ADDRESS和清掉UART的中断后,再次去读VIC1ADDRESS,看里面是什么值,是楼主写的0吗?答案是否定的,而是“上次处理的那个中断的ISR”
也就是说,写完VIC1ADDRESS之后,该寄存器并不会被改成0,而是保存着上一次推送的那个ISR,此时该值无意义。
PL192的datasheet里对于这个现象也有对应的描述:
The VICADDRESS Register provides the location of the interrupt service routine forthe currently active interrupt, which is also made available on theVICVECTADDROUT output ports. This value reflects the relevant programmed valuefrom one of the 32 VICVECTADDR Registers, or the address input from thedaisy-chain interface. If no interrupt is currently active, the VICADDRESS Register holds the previous active interrupt address . This means that the address of the now non-active interrupt is the one presented to the CPU when:
• an interrupt has occurred
• the CPU has acknowledged the interrupt
• the interrupt source has gone inactive
• no other interrupt is active.

3 怎么将UART2的优先级设置的比UART0高
最直接的办法就是设置中断的优先级,对应的寄存器为VICVECTPRIORITY,默认情况下,都中断优先级都被设置为0xF,这是最低的优先级
我们把VIC1的UART2设置为0xe,即 (*(volatile unsigned int *)0xF2100230)=0xe;重复上面关于两个UART中断推送ISR到VICADDRESS寄存器的实验,可以很清楚的知道此时UART2的优先级要高于UART0。

另外提一下Software Mask寄存器,配置它可以屏蔽某些level的中断源。
寄存器初始值是0xFFFF,即不屏蔽任何一个level, 如果我们想屏蔽15和14两个level的中断源,应该这些书写。
(*(volatile unsigned int *)0xF2100024)=0x3fff;
被屏蔽level的中断不会触发CPU去跳转到异常向量表,VIC1ADDRESS寄存器给出的是旧的、上次处理过的的ISR,也就是说,配置该寄存器后的level中断,不会推送发生中断的ISR到VIC1ADDRESS。但需要注意的是VICISRSTATUS寄存器里可以看到这个bit的中断已发生,这个看起来有点奇怪设计可能会让软件工程师感到困惑,在极端的情况,软件真的是屏蔽了中断后去轮询VICISRSTATUS寄存器,再由里面为1的bit位映射到对应的ISR再执行的话,就会导致software mask 失败而出错。

另外还有一个有点跑题的办法,将优先级低的那个中断源改成FIQ,对应的寄存器为VICINTSELECT
前面提到,默认情况下,所有的中断源都设置为IRQ模式,如果要将UART2中断设为FIQ,做法为
*((volitile unsigned int*)0xF210000C) = 0x1000;
当FIQ发生时,PL192会拉低ARM核的FIQ引脚,触发CPU立即处理FIQ,ARM在设计的时候就是把FIQ的的优先级比IRQ要高。但是前面提到过,FIQ是Non-Vectored,PL192在FIQ发生时,不会将其ISR推送到VICADDRESS,所以ISR不能通过VIC1ADDRESS获得,它需要软件工程师自己记住是哪个ISR,或者可以直接在存放中断向量表后面的内存空间直接写FIQ的ISR(FIQ位于中断向量表的结尾处),可以再省掉一次函数的跳转,将实时响应做到极致。
0x14 ldr   pc, _data_abort_Entry
0x18 ldr   pc, IRQ_Entry
0x1c FIQ Interrupt service routine //Handle FIQ immediately
Clear interrupt request
subs pc, r14, #4 //Return
那么可以设置多个FIQ吗?硬件是支持的,但是如果设置多个,那么软件工程师需要去遍历VICFIQSTATUS(注意,不是VICIRQSTATUS)里的每个可能为1的bit位,找到之后再映射到对应的ISR,这样效率很低,甚至比普通IRQ的速度还要慢

你可能感兴趣的:(处理器相关)