2440init.s中断跳转分析
最近准备自己写一个S3C2440的启动代码。参阅了一下2440init.s这一启动代码。发现有很多人对于这个启动代码中的异常,特别是像外部中断这样的异常,到底在2440init.s中是如何实现跳转的这一问题有很大的疑惑。现在就我的理解对这个过程做一下解说。说的不一定很好,呵呵,权当消遣吧。另外,这里面的错误肯定会有,大家尽管给我提吧。
首先要大家要明白,特别是初学者,当S3C2440发生异常时,其程序指针是会被强制转换的,这个是硬件自己完成的,不需要我们的干涉。下面这个表格就是具体的强制指针转化表。
地址 | 异常名称 | 2440init.s 中的指令 |
0x00 | 复位异常 | b ResetHandler |
0x04 | 未定义指令异常 | b HandlerUndef |
0x08 | 软件中断异常 | b HandlerSWI |
0x0c | 指令预取异常 | b HandlerPabort |
0x10 | 数据预取异常 | b HandlerDabort |
0x14 | 保留 | b . |
0x18 | IRQ中断异常 | b HandlerIRQ |
0x1c | FIQ中断异常 | b HandlerFIQ |
现在可以很清楚的知道了。如果S3C2440刚上电或者是复位,那么pc指针被硬件强制转换到0x00地址处,那么按照2440init.s中的指令“b ResetHandler”,pc会跳转到ResetHandler处继续执行程序。同样,当发生外部中断时,pc指针会被强制转换到0x18处,执行“b HandlerIRQ”这条指令。
接下去,便是讨论程序跳转之后如何运行的了。首先,我们做一个设想,假设S3C 2440只有一个中断(当然这是不可能的了,就连51也有5个中断源。),那么可想而知,程序跳转到HandlerIRQ处就应该执行中断处理函数了。也就是说,我们在C语言中写的中断处理函数的地址应该就是HandlerIRQ的内容了。虽然这个假设不可能,但它可以帮助我们理解。本质上其实就是类似于这样的一个过程,只不过因为S3C2440有那么多的中断源,所以不可能把中断函数的地址直接赋给HandlerIRQ就行了。这中间应该还有一个转换。就是根据不同的、具体的中断源,让HandlerIRQ对应于不同的中断处理函数的地址。
那么接下来看看HandlerIRQ标号后面的内容吧。
HandlerIRQ HANDLER HandleIRQ
很明显,这里还有一个宏定义在里面,要知道HANDLER的内容,我们可以在2440init.s中查到:
MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
sub sp,sp,#4
stmfd sp!,{r0}
ldr r0,=$HandleLabel
ldr r0,[r0]
str r0,[sp,#4]
ldmfd sp!,{r0,pc}
MEND
下面对这个宏定义做一下说明。首先是MACRO表明了宏定义的开始,MEND则表示了宏定义的结束。这个宏的作用其实就是在不改变任何寄存器的前提下,把pc指针指向$HandleLabel。
明白了这个以后,我们就可以把这个宏去掉再来理解一遍:
首先是当S3C2440发生外部中断时,pc指针被强制指向了0x18处:
b 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处。呵呵,挺简单的吧。然后
呢。我们就得关注一下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
HandleEINT0 # 4
HandleEINT1 # 4
HandleEINT2 # 4
HandleEINT3 # 4
……
很简单,这里定义了一个文字池,首地址:_ISR_STARTADDRESS代表了地址为0x33FF_FF00的内存区域,每隔4个字节,定义一个标号。很很容易就找到了 HandleIRQ这个我们需要找的标号。那么它所代表的内存区域自然就是0x33FF_FF00+0x04*6的内存地址了。那么接下来的工作就是要把真正的、具体的中断处理函数的地址赋给HandleIRQ了。这里大家先看一下下面的两端代码:
(1):
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 ;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}
首先我们来看一下第(1)段程序,前面已经提到,此时的的pc已经指向了HandeIRQ所表示的内存了,但是现在该内存还是空的,pc跳转到这里也不能接着往下运行了。所以才有了第1段代码,它的作用就是给HandleIRQ安装句柄了,把IsrIRQ的入口地址填充到了HandeIRQ里面了。所以程序接着就会跳转到IsrIRQ处执行。也就是上面的第(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 _ISR_STARTADDRESS+0X20
看这个地址,其实就是上面那个中断表里面的:
^ _ISR_STARTADDRESS ; _ISR_STARTADDRESS=0x33FF_FF00
……
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又跳到了中断处理函数中区执行中断函数了。