ARM中断(二)

本文感谢 郑星 朋友

 

2440支持IRQ(普通中断)和FIQ(快速中断)。2440有60个中断源,不支持中断嵌套。

CPU每执行一条指令都会检查CPSR寄存器,当发现I或F位被置1时,就进行中断处理。需要两次查表过程(为什么要查两次表??没有办法,ARM把所有的中断都归纳成一个IRQ中断异常和一个FIQ中断异常;第一次查表主要是查出是什么异常,可我们总要知道是这个中断异常中的什么中断呀!没办法还需要查第二次)。第一步跳入异常向量表:

地址

异常名称

指令

0x00

复位异常

B  RestHandler

0x04

未定义指令异常

B  HandlerUndef

0x08

软件中断异常

B  HandlerSWI

0x0C

指令预取异常

B  HandlerPabort

0x10

数据预取异常

B  HandlerDabort

0x14

保留

 

0x18

IRQ中断异常

B  HandlerIRQ

0x1C

FIQ中断异常

B  HandlerFIQ

 

如果S3C2440刚上电或者是复位,那么pc指针被硬件强制转换到0x00地址处,那么按照2440init.s中的指令“B  ResetHandler”,pc会跳转到ResetHandler处继续执行程序。

我们以外部中断为例,当发生外部中断后,PC指针指向0x18,执行B  HandlerIRQ 指令,接着跳转到HandlerIRQ标号处执行,S3C2440有很多的中断源,所以不可能把中断函数的地址直接赋给HandlerIRQ。这中间应该还有一个转换。就是根据不同的、具体的中断源,HandlerIRQ对应于不同的中断处理函数的地址。那么接下来看看HandlerIRQ标号后面的内容吧:

HandlerIRQ HANDLER HandleIRQ
很明显,这里还有一个宏定义在里面,要知道HANDLER的内容,我们可以在2440init.s中查到:

;下面这个宏是用于第一次查表过程的实现中断向量的重定向,如果你比较细心的话就是发现
;在_ISR_STARTADDRESS=0x33FF_FF00里定义的第一级中断向量表是采用型如Handle***的方式的.
;而在程序的ENTRY处(程序开始处)采用的是b Handler***的方式.
;在这里Handler***就是通过HANDLER这个宏和Handle***建立联系的.
;这种方式的优点就是正真定义的向量数据在内存空间里,而不是在ENTRY处的ROM(FLASH)空间里,
;这样,我们就可以在程序里灵活的改动向量的数据了.
;======================================================= 
;这段程序用于把中断服务程序的首地址装载到pc中,有人称之为“加载程序”。
;本初始化程序定义了一个数据区(在文件最后),34个字空间,存放相应中断服务程序的首地址。每个字
;空间都有一个标号,以Handle***命名。
;在向量中断模式下使用“加载程序”来执行中断服务程序。
;这里就必须讲一下向量中断模式和非向量中断模式的概念
;向量中断模式是当cpu读取位于0x18处的IRQ中断指令的时候,系统自动读取对应于该中断源确定地址上的;
;指令取代0x18处的指令,通过跳转指令系统就直接跳转到对应地址
;函数中 节省了中断处理时间提高了中断处理速度标 例如 ADC中断的向量地址为0xC0,则在0xC0处放如下
;代码:ldr PC,=HandlerADC 当ADC中断产生的时候系统会
;自动跳转到HandlerADC函数中
;非向量中断模式处理方式是一种传统的中断处理方法,当系统产生中断的时候,系统将interrupt
;pending寄存器中对应标志位置位 然后跳转到位于0x18处的统一中断
;函数中 该函数通过读取interrupt pending寄存器中对应标志位 来判断中断源 并根据优先级关系再跳到
;对应中断源的处理代码中


 MACRO  ;宏定义的开始
 $HandlerLabel HANDLER $HandleLabel

 $HandlerLabel     ;标号
 sub sp,sp,#4      ;(1)减少sp(用于存放转跳地址)
 stmfd sp!,{r0}    ;(2)把工作寄存器压入栈(lr does not push because it return to original address)
 ldr     r0,=$HandleLabel  ;将HandleXXX的址址放入r0
 ldr     r0,[r0]           ;把HandleXXX所指向的内容(也就是中断程序的入口)放入r0
 str     r0,[sp,#4]        ;(3)把中断服务程序(ISR)压入栈
 ldmfd   sp!,{r0,pc}       ;(4)用出栈的方式恢复r0的原值和为pc设定新值(也就完成了到ISR的转跳)
 MEND    ;宏定义的结束

这个宏的作用其实就是在不改变任何寄存器的前提下,把pc指针指向$HandleLabel。这里是将PC指针直接由HandlerIRQ指向HandleIRQ。(在这里Handler***就是通过HANDLER这个宏和Handle***建立联系的.
;这种方式的优点就是正真定义的向量数据在内存空间里,而不是在ENTRY处的ROM(FLASH)空间里,
;这样,我们就可以在程序里灵活的改动向量的数据了.)

下面我们就得关注一下HandleIRQ这个标号了。它在2440init.s中设这样定义的:

^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00
HandleReset  #4
HandleUndef  #4
HandleSWI  #4
HandlePabort #4
HandleDabort  #4
HandleReserved  #4
HandleIRQ  #4
HandleFIQ  #4
………

(其中 “^”表示MAP指令,“#”表示FIELD指令)

很简单,这里定义了一个内存地址块,首地址:_ISR_STARTADDRESS代表了地址为0x33FF_FF00的内存区域,每隔4个字节,定义一个标号。很很容易就找到了 HandleIRQ这个我们需要找的标号。那么它所代表的内存区域自然就是0x33FF_FF00+0x04*6的内存地址了(跳到此地址执行,这个过程由硬件自动完成)。那么接下来的工作就是要把真正的、具体的中断处理函数的地址赋给HandleIRQ了。这里大家先看一下下面的两端代码:

1):

; 进入C语言前的最后一步了,就是把我们用说查二级向量表
; 的中断例程安装到一级向量表(异常向量表)里.

ldr r0,=HandleIRQ ;This routine is needed
ldr r1,=IsrIRQ    ;if there isn't 'subs pc,lr,#4' at 0x18, 0x1c
str r1,[r0]

(2):

这一段程序就是用来进行第二次查表的过程了.

 ;如果说第一次查表是由硬件来完成的,那这一次查表就是由软件来实现的了.
 IsrIRQ
 sub sp,sp,#4        ;给PC寄存器保留 reserved for PC
 stmfd sp!,{r8-r9}   ;把r8-r9压入栈

 ldr r9,=INTOFFSET   ;把INTOFFSET的地址装入r9  INTOFFSET是一个内部的寄存器,存着中断的偏移
 ldr r9,[r9]         ;I_ISR
 ldr r8,=HandleEINT0 ;这就是我们第二个中断向量表的入口的,先装入r8
 ;===================================================================================
 ;哈哈,这查表方法够好了吧,r8(入口)+index*4(别望了一条指令是4 bytes的喔),
 ;这不就是我们要找的那一项了吗.找到了表项,下一步做什么?肯定先装入了!
 ;==================================================================================
 add r8,r8,r9,lsl #2 ;地址对齐,因为每个中断向量占4个字节,即isr = IvectTable + Offeset * 4
 ldr r8,[r8]         ;装入中断服务程序的入口
 str r8,[sp,#8]      ;把入口也入栈,准备用旧招
 ldmfd sp!,{r8-r9,pc};施招,弹出栈,哈哈,顺便把r8弹出到PC了,跳转成功!

首先我们来看一下第(1)段程序,前面已经提到,此时的的pc已经指向了HandeIRQ所表示的内存了,但是现在该内存还是空的,pc跳转到这里也不能接着往下运行了。所以才有了第1段代码,它的作用就是给HandleIRQ安装句柄了,把IsrIRQ的入口地址填充到了HandeIRQ里面了。所以程序接着就会跳转到IsrIRQ处执行。也就是上面的第(2)段程序了。这段程序具体讲解我就不说了(2次查表),跟最上面的宏定义很类似,其实就是让PC跳转到另外一个地方(pc=HandleEINT0+INTOFFSET*4)。而那个地方正是真正的中断函数。那我们再来看看这个地址是怎么算出来的。首先HandleEINT0就是上面那个MAP定义里面的一个内存区域,有没有发现它是第一个中断源,紧接着它,就是其它各种类型的中断源了。而INTOFFSET则是S3C2440的一个特殊功能寄存器了,某个类型的中断发生了,它的值就会发生变化。而后面为什么要乘以4呢,因为文字池中定义的标号都是4字节的,其实是因为S3C2440中指针变量就是占据4个字节的,这个可以用sizeof(*p)来验证。所以此时pc指针就指向了另外一个地方,那就是刚才说的中断表了。而在c语言中,我们通常会有这样的中断函数句柄安装语句:pISR_EINTn = (unsigned int )key_interrupt;
这里的pISR_EINTn其实是有定义的,我们以pISR_EINT0为例,宏定义如下:

#define pISR_EINT0 (*(unsigned *)(_ISR_STARTADDRESS+0x20))

看这个地址,其实就是上面那个中断表里面的:

^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00
……0-7
HandleEINT0 # 4
HandleEINT1 # 4
HandleEINT2 # 4
HandleEINT3 # 4
……

大家发现了吧,HandleEINT0的地址是不是就是 _ISR_STARTADDRESS+0X20,所以说c语言中我们写的中断函数安装句柄,就是这个作用。

好的,现在让我们来总结一下吧。(以外部中断0为例)
首先是在c语言的函数中,我们已经执行了这样一条语句pISR_EINTn = (unsigned int )key_interrupt;它的作用是把中断处理函数key_interrupt的地址赋给了中断表中的HandleEINT0。
然后当某个时刻,发生了外部中断0,那么pc指针被强制指向了0x18处,执行指令: 

b HandlerIRQ

跳转到HandlerIRQ之后,执行如下代码:(已经把宏定义屏蔽)

HandlerIRQ
    sub sp,sp,#4
    stmfd sp!,{r0} 
    ldr r0,=HandleIRQ
    ldr r0,[r0] 
    str r0,[sp,#4] 
    ldmfd sp!,{r0,pc}

然后pc之争有指向了HandleIRQ这个内存区域。而又由于HandlerIRQ已经被安装了IsrIRQ的句柄,所以紧接着pc又跳转到IsrIRQ处执行如下程序:

IsrIRQ
    sub sp,sp,#4 ;reserved for PC
    stmfd sp!,{r8-r9}
    ldr r9,=INTOFFSET
    ldr r9,[r9]
    ldr r8,=HandleEINT0
    add r8,r8,r9,lsl #2
    ldr r8,[r8]
    str r8,[sp,#8]
    ldmfd sp!,{r8-r9,pc}

它的作用是让pc指针根据INTOFFSET的值跳转到中断向量表中的HandleEINT0处。而在此处已经被安装了c语言中的中断处理函数的句柄,所以pc又跳到了中断处理函数中区执行中断函数了。

posted on 2012-05-01 12:59  hicjiajia  阅读(2559)  评论(0编辑  收藏  举报