目录
S3C2440 中断体系结构
ARM的7种工作模式
- 用户模式(usr):ARM处理器正常的程序执行状态
- 快速中断模式(fiq):用于高速数据传输或通道处理
- 中断模式(irq):用于通用的中断处理
- 管理模式(svc):操作系统使用的保护模式
- 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护
- 系统模式(sys):运行具有特权的操作系统任务
- 未定义指令终止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真
除了用户模式之外,其他的6种工作模式都属于特权模式。大多数程序运行于用户模式,进入特权模式是为了处理中断、异常或者访问被保护的系统资源。
当发生各类中断、异常时CPU自动进入相应的模式。当在特权模式时,可以通过软件来进行模式切换。
ARM的CPU有两种工作状态:ARM状态和Thumb状态。ARM状态下CPU执行32位的字对齐的ARM指令;Thumb状态下CPU执行16位的半字对齐的Thumb指令。CPU上电复位后处于ARM状态。
目前我们只关心ARM状态下的操作。在ARM状态下,ARM920T有31个通用寄存器和6个程序状态寄存器,这些寄存器都是32位的。这37个寄存器分为7组,如下图所示:
图中的Banked register,即备份寄存器,专属于相应的工作模式。不同的工作模式下都有自己的副本,当切换到另一个工作模式时,那个工作模式的寄存器副本就将被使用。
ARM状态下,每种工作模式都有16个通用寄存器和1~2个程序状态寄存器。除了R15寄存器之外,R0~R14都是通用寄存器,这些寄存器既可以用于保存数据,也可以用于保存地址。R15又叫PC,就是我们所熟知的程序计数器。需要指出,通常R13~R15都有特殊用途。其中,R13又被称为栈指针寄存器,通常被用于保存栈指针,简记为sp。R14又被称为程序连接寄存器(link register),当执行BL指令调用子程序时,R14中得到跳转前要执行的下一条指令的地址。当发生中断或异常时,对应的相应模式R14中保存返回值。
快速中断模式中有7个备份寄存器R8~R14,这使得进入快速中断模式执行时,不需要保存R8~R14寄存器。其他特权模式只有两个备份寄存器R13、R14。
CPSR(Current Program Status Register,程序状态寄存器)是一个很重要的寄存器,具体如下图所示:
各比特位的含义如下所示: - 第0到第4比特位 M[4:0]:表明CPU当前处于什么工作模式,设置后使CPU进入指定的工作模式。除了sys模式之外,其他特权模式下都有SPSR寄存器(Saved Program Status Registers,程序状态保存寄存器)。当切换到这些工作模式时,在SPSR中保存前一个工作模式的CPSR值。当返回到之前的工作模式时,可以将SPSR的值恢复到CPSR中。
- 第5比特位T位:置1时表明CPU处于Thumb状态,置0时表示处于ARM状态。
- 第6比特位F位、第7比特位I位:这两比特位都属于中断禁止位。当置1时,对应的FIQ中断和IRQ中断分别被禁止。
发生异常或中断
当发生异常或中断时,ARM920T CPU核将自动完成以下工作:
- 在该特权模式的lr寄存器中保存之前工作模式的将要执行的下一条指令的地址。对于ARM状态,这个值可能是PC值+4或者+8。具体情况如下表所示:
- 将CPSR的值复制到对应的特权模式的SPSR中;
- 将CPSR的工作模式位设置为新的特权模式对应的值;
- 令PC等于该异常或中断对应的异常向量表中的地址。异常向量表和对应的工作模式如下图所示:
退出异常工作模式
- 在特权模式下,将lr减去一个适当的值,之后赋给PC
- 将SPSR的值赋值给CPSR
S3C2440中断控制器
CPU在运行过程中,对于如何检测到各类外设发生的随机事件,主要有以下两种方法:
- 查询方式: 程序循环地查询各设备的状态。这样做效率太低,不适合并发。
- 中断方式:当某事件发生时,硬件会设置某个寄存器。CPU在每执行完一条指令后,都会区检测这个寄存器,如果发生了事件,则CPU中断当前流程,跳转到一个固定的地址去处理该事件,之后返回原来被中断的程序继续执行。
无论是何种CPU,中断的处理过程是相似的。
- 首先由中断控制器汇集 各类外设发出的中断信号,之后通知CPU;
- CPU保存当前程序的运行上下文,然后调用中断服务程序ISR(Interrupt Service Routine)来处理这些中断;
- 在ISR中通过读取中断控制器、外设相关寄存器来识别中断并进行相应的处理;
- 清除中断
- 恢复被中断程序的运行上下文,然后继续执行。
我们通过S3C2440的用户手册可以得到其中断控制器的结构如下图所示:
如上图所示,SUBSRCPND和SRCPND寄存器表明有哪些中断被触发了,正在等待处理。而SUBMASK和MASK寄存器则起到开关的作用,用于屏蔽某些中断。
- 当Request sources(without sub-register)中的中断源被触发之后,SUBSRCPND寄存器中相应位被置1,如果此中断没有被SUBMASK寄存器屏蔽的话,它在SRCPND寄存器中的相应位也会被置1,之后处理过程和2一样;
- 当Request sources(without sub-register)中的中断源被触发后,SRCPND寄存器中相应位被置1,如果此中断没有被INTMASK寄存器屏蔽的话,或者此中断是FIQ,那么它将被路由至下一步。
- 如果被触发的中断是FIQ,那么CPU进入快速中断处理模式;如果是一般中断IRQ,那么可能同时有几个中断被触发,未被屏蔽的中断会全部进入到优先级比较器,优先级最高的中断胜出,并将INTPND寄存器中相应的位置1,然后CPU进入IRQ中断模式进行处理。中断服务程序会结合INTOFFSET和INTPND寄存器来确定中断源。
中断源和子中断源
对于某些中断来说,比如INT_UART0中断源,其需要细分为三个子中断源:INT_ERR0、INT_TXD0、INT_RXD0,用以分别表示UART0的出错中断、发送中断和接收中断。
S3C2440的中断源和子中断源如下图所示:
中断源:
子中断源:
相关寄存器
与中断控制器相关的寄存器有8个。其中,SUBSRCPND和INTSUBMSK这两个寄存器中相同的位对应相同的中断;SRCPND、INTMSK、INTMOD、INTPND这四个寄存器相同的位对应相同的中断。
- SUBSRCPND寄存器
SUBSRCPND用来标志INT_RXD0、INT_TXD0等中断是否已经发生。在S3C2440中,这类子中断共有15个。每位对应一个中断。当中断发生时,对应位被置1,之后如果INTSUBMSK中对应的位没有被置1屏蔽,那么它们中的若干位将汇集到SRCPND寄存器中的某一位上。如SUBSRCPND寄存器中的INT_ERR0、INT_TXD0和INT_RXD0这3个中断,只要有一个子中断发生且没有被屏蔽,那么SRCPND寄存器中的INT_UART0位将被置1。SUBSRCPND寄存器中具体哪几位对应SRCPND寄存器中的一位,如下图所示:
对于SUBSRCPND寄存器,往其中某一位写1,则即可将该为清零,写0无效果,寄存器中原数据保持不变。 - INTSUBMSK寄存器
INTSUBMSK寄存器被用来屏蔽SUBSRCPND寄存器中所标志的中断。置1表示屏蔽对应位上的中断。
- SRCPND寄存器
SRCPND寄存器中每一位被用来表示一个或者一类中断是否已经发生。“一类中断”自然指的是那些对应有SUBSRCPND的中断。
和SUBSRCPND类似,想要清除SRCPND寄存器中的某一位,往此位写1即可。 - INTMSK寄存器
INTMSK寄存器被用来屏蔽SRCPND寄存器中所标志的中断。置1屏蔽。需要说明的是,INTMSK寄存器只能屏蔽被设置为IRQ的中断,而不能屏蔽被设置为FIQ的中断。怎么将中断设置为FIQ,详见下面的INTMOD寄存器。
- INTMOD寄存器
当INTMOD寄存器中的某一位被设置为1时,它对应的中断被设置为FIQ。之后,若此发生此中断,那么CPU将进入快速中断模式,这通常用来处理特别紧急的中断。
需要说明的是,在同一时刻,INTMOD寄存器中只能有一位被设置为1。
- PRIORITY寄存器
优先级寄存器我们接下来用一节专门讲解。这里强调一点,优先级比较只针对于IRQ中断。因为有之前的框图也可知,FIQ直接到达CPU了。我们把IRQ中断叫做普通中断,简称中断。 - INTPND寄存器
经过中断优先级仲裁器选出的优先级最高的中断,将被设置进INTPND寄存器。即INTPND寄存器中对应的位被置1。此后CPU将进入中断模式处理它。显然,同一时刻,INTPND寄存器只能有一位被置1。在ISR中,我们可以根据这一位来断定是哪一个中断发生了。
同样,写1清零。
- INTOFFSET寄存器
INTOFFSET寄存器被用来表示INTPND寄存器中哪一位被置1了,即当INTPND寄存器中位[x]被置1了,那么此时INTOFFSET寄存器的值就是x。x的取值范围是闭区间[0, 31]。
当清除SRCPND、INTPND寄存器时,INTOFFSET寄存器被自动清零。
优先级比较器
S3C2440将不同的中断分为几组,每一组内先进行比较,之后每一组的胜出者再进行比较,最终决出优先级最高的一个中断,如下图所示:
关于优先级设置,我们先看一下优先级设置寄存器:
每一个仲裁器都含有6个输入引脚,PRIORITY寄存器使用三位来控制其行为,一位是ARB_MODE,用于选择工作模式;另外两位是ARB_SEL,被用于控制输入信号的优先级。
由于ARB_SEL是两位,所以其优先级设置有四种方式:
我们可以看到,无论ARB_SEL设置为任何值,REQ0的优先级始终是最高的,REQ5的优先级始终是最低的。
当我们将工作模式位ARB_MODE设置为0时,对应的ARB_SEL位是不会自动变化的,也就是说该仲裁器的6个输入引脚的优先级是固定不变的。当ARB_MODE位被置1时,ARB_SEL会随着已经被服务过的REQ自动变化,服务过谁,那么就设置为谁优先级最低,如下图所示:
异常实验
如上图所示,我们先实现异常向量表,并进行reset、und和swi异常的实验。当系统复位或上电时,我们打印出booting;当执行未定义机器指令时,打印出Undefiend instruction;当进行软中断调用时,我们打印出调用号。
我们知道,SVC、Und、Abt、IRQ、FIQ模式都有自己的堆栈,而Usr和Sys模式共用一个堆栈。所以我们在它们各自的处理过程中首先就要初始化栈指针。由之前的博客介绍可以知道,JZ2440开发板外接了两片EM63A165TS-6G,每片大小为32MB,所以JZ2440外接内存大小一共为64MB。由于ARM的堆栈是满减栈,所以我们将各个模式的堆栈指针依次设置在内存最高地址处。SDRAM接在BANK6,对应的基址是0x30000000,故最高内存地址为0x34000000。设置每个栈大小为4KB。所以:
sp_svc = 0x34000000
sp_und = 0x33FFF000
sp_abt = 0x33FFE000
sp_irq = 0x33FFD000
sp_fiq = 0x33FFC000
sp_usr_sys = 0x33FFB000
如下所示,我们先构造中断向量表:
.text
.global _start
_start:
b reset //SVC
b undEXP //Und
b swiEXP //SVC
b AbortPrefetch //Abort
b AbortData //Abort
b reset
b IRQINT //IRQ
b FIQINT //FIQ
其中reset标号对应的就是以前正常启动的代码,而其他标号则对应各个异常的处理程序。需要特别注意的是,这里的跳转指令需要使用位置无关跳转,同时不能使用bl指令,因为它会修改lr寄存器,使得异常处理之后需要返回的地址被覆盖掉。
下面我们分别来介绍各个异常处理过程。
未定义指令异常
我们将und模式下的栈指针设置为0x33FFF000。
当pc指向的地址中存储的是一条非法指令时,cpu将其取指到指令寄存器中,执行时就会产生未定义指令异常。由上面的异常退出表可知,此时lr寄存器中保存的就是待返回的用户程序指令地址。所以我们直接将r0~r12寄存器,还有lr_und保存到栈中。之后直接调用C函数进行相应的异常处理即可。
由于此时lr寄存器中保存的是用户程序中的下一条指令的地址。所以导致未定义指令异常的指令就是之前地址的内容。故lr_und - 4就是未定义指令的地址。我们将该地址作为实参传递给异常处理函数,方便其进行异常处理。
在C处理函数返回之后,我们弹栈并恢复CPSR寄存器即可。代码如下所示:
undEXP:
ldr sp, =0x33FFF000
/*因为lr是异常处理后的返回地址,所以不用修正lr */
/* 保存r0-r12以及lr*/
//使用满减栈
stmdb sp!, {r0-r12, lr}
//lr减4,即为导致异常的指令地址
sub r0, lr, #4
bl undExpHandler
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
void undExpHandler(unsigned int* undAddr) {
printf("Undefined Instruction(0x%08x) at address(0x%08x) is found!\n\r",
*undAddr, undAddr);
}
软件中断异常
由于swi模式对应的是SVC管理员模式,所以我们将swi模式下的栈指针设置为0x34000000。
同样在开头需要将r0~r12以及lr寄存器压栈。
当我们调用swi xx的时候,则会进入管理员模式,此时通常是为了调用一个特殊的管理员模式下的功能函数。由于此时lr保存的也是程序要返回的地址,所以lr_svc - 4就是swi指令所在的地址。我们取出swi指令的二进制值,同时结合swi指令格式:
我们可知swi指令中的数字序号就存放在低24位中,故我们可以取出该软件中断号,并传给C处理函数。
在C处理函数返回之后,我们弹栈并恢复CPSR寄存器即可。代码如下所示:
swiEXP:
ldr sp, =0x34000000
/*因为lr是异常处理后的返回地址,所以不用修正lr */
/* 保存r0-r12以及lr*/
//使用满减栈
stmdb sp!, {r0-r12,lr}
//lr减4,即为导致异常的指令地址
ldr r0, [lr, #-4]
//只保留低24位的软件号
bic r0, r0, #0xFF000000
bl swiExpHandler
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
void swiExpHandler(unsigned int swiNum) {
printf("Software Exception is Checked with Number %d(0x%x)\n\r", swiNum, swiNum);
}
指令预取异常和数据预取异常
这两个异常对应的模式都是abt模式,所以我们设置其栈指针为0x33FFE000。和前两个处理流程几乎一样,需要特别注意的是,数据预取异常需要在开头修正lr,令lr = lr - 8;指令预取异常修正lr = lr - 4。其他的没有什么区别。这两个异常的处理代码如下所示:
AbortPrefetch:
ldr sp, =0x33FFE000
subs lr, lr, #4
//使用满减栈
stmdb sp!, {r0-r12, lr}
sub r0, lr, #4
bl abortPrefetchExpHandler
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
AbortData:
ldr sp, =0x33FFE000
subs lr, lr, #8
//使用满减栈
stmdb sp!, {r0-r12, lr}
mov r0, lr
bl abortDataExpHandler
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
void abortPrefetchExpHandler(unsigned int* abtPrefetchAddr) {
printf("Fetch instruction from address(0x%08x) failed.\n\r", abtPrefetchAddr);
}
void abortDataExpHandler(unsigned int* abtDataAddr) {
printf("Fetch data failed at address(0x%08x).\n\r",
abtDataAddr);
}
IRQ中断和FIQ中断
按照我们之前的设定,我们将IRQ的栈指针初始化为0x33FFD000,我们将FIQ的栈指针初始化为0x33FFC000。同时还要将二者的lr寄存器进行修正, lr = lr - 4才是程序真正应该返回的地址。在本文中,对于irq普通中断和fiq快速中断,我们调用C处理函数,并处理中断返回工作。在C函数中只先简单打印一行提示。具体任务将在下一篇文章中详述,下一篇文章将进行按键中断点灯和按键fiq快速中断点灯,同时采用串口中断进行收发数据。
IRQINT:
ldr sp, =0x33FFD000
subs lr, lr, #4
//使用满减栈
stmdb sp!, {r0-r12, lr}
bl irqExpHandler
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
FIQINT:
ldr sp, =0x33FFC000
subs lr, lr, #4
//使用满减栈
stmdb sp!, {r0-r12, lr}
bl fiqExpHandler
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
void irqExpHandler(void) {
printf("IRQ is Enter!\n\r");
}
void fiqExpHandler(void) {
printf("FIQ is Enter!\n\r");
}
添加触发代码
为了在实验中触发未定义指令异常和软件中断异常,我们在代码拷贝到内存之后添加未定义指令0xdeadC0de和swi软件中断调用,如下所示:
ldr r0, =bootMsg
bl uart0_puts
swi 0x2020
.word 0xdeadC0de
ldr lr, =halt
ldr pc, =main //这才是真正跳转到内存中开始运行
实验结果如下图所示: