• 首页
  • 中国
  • 11.S3C2440 中断实验(一)und和swi实验

11.S3C2440 中断实验(一)und和swi实验

2023-11-01 191浏览
百检网是一家专业的第三方检测平台,汇聚众多拥有权威资质的第三方检测机构为你提供一站式的检测服务,做检测就上百检网。百检网让检测从此检测,一份报告全国通用,专业值得信赖。

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 = 0x‭33FFF000‬

sp_abt = 0x‭33FFE000‬

sp_irq = 0x‭33FFD000‬

sp_fiq = 0x33FFC000

sp_usr_sys = 0x33FFB000

如下所示,我们先构造中断向量表:

.text

.global _start

_start:

breset//SVC

bundEXP//Und

b swiEXP//SVC

bAbortPrefetch//Abort

b AbortData//Abort

breset

b IRQINT//IRQ

bFIQINT//FIQ

其中reset标号对应的就是以前正常启动的代码,而其他标号则对应各个异常的处理程序。需要特别注意的是,这里的跳转指令需要使用位置无关跳转,同时不能使用bl指令,因为它会修改lr寄存器,使得异常处理之后需要返回的地址被覆盖掉。

下面我们分别来介绍各个异常处理过程。

未定义指令异常

我们将und模式下的栈指针设置为0x‭33FFF000‬。

当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!nr",

*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)nr", swiNum, swiNum);

}

指令预取异常和数据预取异常

这两个异常对应的模式都是abt模式,所以我们设置其栈指针为0x‭33FFE000。和前两个处理流程几乎一样,需要特别注意的是,数据预取异常需要在开头修正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.nr", abtPrefetchAddr);

}

void abortDataExpHandler(unsigned int* abtDataAddr) {

printf("Fetch data failed at address(0x%08x).nr",

abtDataAddr);

}

IRQ中断和FIQ中断

按照我们之前的设定,我们将IRQ的栈指针初始化为0x‭33FFD000,我们将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!nr");

}

void fiqExpHandler(void) {

printf("FIQ is Enter!nr");

}

添加触发代码

为了在实验中触发未定义指令异常和软件中断异常,我们在代码拷贝到内存之后添加未定义指令0xdeadC0de和swi软件中断调用,如下所示:

ldr r0, =bootMsg

bl uart0_puts

swi 0x2020

.word 0xdeadC0de

ldr lr, =halt

ldr pc, =main //这才是真正跳转到内存中开始运行

实验结果如下图所示:


百检网秉承“客户至上,服务为先,精诚合作,以人为本”的经营理念,始终站在用户的角度解决问题,为客户提供“一站购物式”的新奇检测体验,打开网站,像挑选商品一样简单,方便。打破行业信息壁垒,建构消费和检测机构之间高效的沟通平台