进入中断和异常的练习正题前,需要通过阅读S3C2440芯片或者ARM参考手册来了解一下ARM的状态和异常机制。
首先需要知道的是ARM存在ARM模式和Thumb模式,其中Thumb模式指令长度为16bits,为了节省程序存储空间而存在的,而ARM模式指令为32bits,是平常我们使用的模式,现在因为存储器容量都比较大,能够满足存储需求,因此使用ARM模式居多,由ARM模式转为Thumb模式需要程序中使用BX跳转指令,当BX后目标地址操作数最低位为1时,CPU就会转为Thumb模式,如下,当然也要配合编译加入Thumb参数 -mthumb。
adr r0, thumb_func
add r0, r0, #1 /* bit0=1时, bx就会切换CPU State到thumb state */
bx r0
.code 16
thumb_func:
bl main
上述ARM和Thumb模式只是和指令长度相关,ARM还存在一套操作模式,对应着异常,同时也是限制对资源的访问。
上图中自上而下分别为用户模式、快中断模式、中断模式、管理模式、终止模式、系统模式和未定义模式,其中用户模式为非特权模式,限制访问一些资源且不能直接通过修改相关寄存器达到转换模式的目的。终止模式分为指令预取终止和读数据终止,会在这两个操作异常时(如读取不到)进入该模式。未定义模式是指读取到的指令不符合ARM指令集,ARM不认识,则进入该模式。FIQ和IRQ为中断,需要明确的是,中断属于异常,FIQ用于处理数据传输的中断,所用时间相对于IRQ较少,这里一部分原因在于FIQ存在较多专用寄存器,可以不用对较多寄存器进行现场保存。如下图,三角形标志的是该模式下专用寄存器。
个人认为异常模式存在最大的意义在于能够进行一定的操作来恢复系统正常,因此需要在进入异常之后跳转到相应的处理函数。这里ARM做了这方面的工作,ARM设置一个异常向量表,当异常发生的时候,就会将PC指向异常向量表中对应异常的地址,而我们所需要做的就是在相应地址中建立该异常向量表。
ARM在进入异常模式时做的事不仅仅上述跳转异常向量表,在描述ARM所做的其他事情前,需要先了解下CPSR和SPSR寄存器。CPSR,Current Program Status Register,程序当前状态寄存器,SPSR,Saved Program Status Registers,程序状态保存寄存器。SPSR很简单,就是CPSR的一个存档点,需要用到或者修改CPSR时,如果后续需要使用CPSR原值,则CPSR会将值保存到SPSR中,这一步应该是ARM自动做了的。而CPSR用途就比较多了,0-4bit是模式位,设置为对应值则进入相应的模式,可看下第二个表。第5位是上文中提到的ARM和Thumb模式状态位,必须注意的是,手册中提醒了不能手动设置这一位,否则将会进入未知状态。6、7位是中断的使能位,设置为1则禁止响应中断。8-27位是保留位,应该置位0,估计是ARM后续想要拓展功能使用的。28-31位则为运算的结果状态位,这个比较常见就不解释了。
现在可以讲下ARM在切换异常时所做的事了:
1.将返回值( PC +4或者 PC+8 )保存到R14(LR)寄存器中(注意:这里LR已经是对应模式的专属寄存器了)
2.将CPSR保存到SPSR中(同样也是对应模式下的专属SPSR)
3.改变CPSR的模式位(0-4)为对应模式的模式值
4.将PC指向异常向量表中相应异常的地址
至此,程序进入异常处理函数中。在异常处理函数中,我们的程序需要做的有:
1.因为R13(SP)寄存器为每个模式专属寄存器,因此在对应的模式中,我们需要重新设置栈,因为处理函数我们打算使用C语言。
2.进入真正的处理函数之前,需要将现场保存,即非专属寄存器需要入栈保存,保证异常处理函数结束返回后,原来的程序能够正常进行。
3.程序结束返回正常流程之前,需要将保存的现场恢复,且将SPSR中的值恢复到CPSR中,同时将PC指向之前保存的LR返回地址。如果是中断,还需要清除相应的中断位,防止重复触发。
需要注意的是,返回地址不同模式之间那是有差异的,需要查表进行相应的处理。
另外,除用户模式外,其他模式都可以通过直接修改CPSR中的mode位来改变当前模式。
有了上述的预备知识后,操作起来已经变得很简单了。需要的步骤有:
1.建立异常向量表
2.异常处理函数前后的设置栈和保存现场恢复现场
3.异常处理函数
由于需要看到现象,再在原本程序中,加入很够引发异常的代码即可。
由此输出未定义异常的代码如下:
Start.S
---------------------------------
.text
.global _start
_start:
B RESET //异常向量表,参考芯片手册
LDR pc,UNDEFIE //LDR取汇编的标号即取该地址内容,若为伪指令=,则为直接取地址
LDR pc,SW_INTERRUPT
B ABORT_PREFETCH
B ABORT_DATA
B halt //reserve
B IRQ_HANDLE
B FIQ_HANDLE
UNDEFIE: //将目标地址放置在此处,防止编译器将地址放置在4k以外及其他
.word DO_UND //因为硬件原因不能取到内容的地方
SW_INTERRUPT:
.word DO_SWI
ABORT_PREFETCH: //此处未做实现,因此全部调到halt
ABORT_DATA:
IRQ_HANDLE:
FIQ_HANDLE:
B halt
DO_UND:
//1.设置栈
LDR SP,=0x34000000
//2.保存现场
STMDB SP!,{R0-R12,LR}
//处理函数
MRS R0,CPSR
LDR R1,=UND_TEST_STRING
BL ExecptionHandle
//3.恢复现场,跳转回原来的位置
LDMIA SP!,{R0-R12,PC}^ //^ 表示将SPSR恢复到CPSR中
DO_SWI:
//1.设置栈
LDR SP,=0x33e00000
//2.保存现场
STMDB SP!,{R0-R12,LR}
//处理函数
MRS R0,CPSR
LDR R1,=SWI_TEST_STRING
BL ExecptionHandle
//3.恢复现场,跳转回原来的位置
LDMIA SP!,{R0-R12,PC}^ //^ 表示将SPSR恢复到CPSR中
UND_TEST_STRING:
.string "enter undefin mode!\n" //汇编中定义带'\0'的字符串为.string
SWI_TEST_STRING:
.string "enter swi mode!\n"
.align 4 //4字节对齐。防止string的不对齐而取地址自动对齐造成的异常执行
RESET:
MOV R0,#0
LDR R1,[R0]
STR R0,[R0]
LDR R2,[R0]
CMP R2,R0
LDR SP,=0x40000000+4096
MOVEQ SP,#4096
STREQ R1,[R0]
BL HardwareInitAll
BL UartInit
TEST_UND:
bl testPrint //调试时发现这里必须存在一个语句下面的未定义指令才会生效,
.word 0xdeadc0de //原因未知,已排除未对齐原因
//bl testPrint
TEST_SWI:
MRS R0,CPSR
BIC R0,R0,#0x0F //切换到usr模式
MSR CPSR,R0
SWI 0x123
LDR pc,=main
halt:
B halt
execption.h
----------------------------
#ifndef __EXCEPTION_H
#define __EXCEPTION_H
#include
void UndExecptionHandle(uint32_t CPSR,char* string);
#endif
execption.c
------------------------
#include "execption.h"
#include "uart.h"
void ExecptionHandle(uint32_t CPSR,char* string)
{
printHex(CPSR);
puts(string);
}
uart.h
-----------------------------
#ifndef __UART_H
#define __UART_H
#include
void UartInit(void);
void putc(uint8_t character);
uint8_t getc(void);
void printHex(uint32_t value);
void puts(char* string);
void testPrint();
#endif
#include "uart.h"
#include "s3c2440.h"
void UartInit(void)
{
//GPIO init
GPHCON &= ~((3<<6) | (3<<4));
GPHCON |= (2<<6) | (2<<4);
//UART init
ULCON0 = 3; //8n1
UCON0 = (1<<2) | (1<<0);
UBRDIV0 = (int32_t)( 50000000/ (115200 * 16) ) -1;
}
static uint8_t IsReadyToSend(void)
{
return UTRSTAT0 & (1<<1);
}
static uint8_t IsReadyToReceive(void)
{
return UTRSTAT0 & (1<<0);
}
void putc(uint8_t character)
{
if(character==(uint8_t)('\n'))
{
while(!IsReadyToSend());
UTXH0 = (uint8_t)'\r';
}
while(!IsReadyToSend());
UTXH0 = character;
}
uint8_t getc(void)
{
while(!IsReadyToReceive());
return URXH0;
}
void printHex(uint32_t value)
{
int i=0;
char output[8]={0};
for(i = 0;i<8;i++)
{
output[i] = value & 0xf;
value >>= 4;
}
puts("0x");
for(i = 7; i>=0; i--)
{
if(output[i]>=0 && output[i]<=9)
output[i] += '0';
else
output[i] = output[i] - 10 + 'A';
putc(output[i]);
}
putc('\n');
}
void puts(char* string)
{
while(*string)
{
putc(*string++);
}
}
void testPrint()
{
puts("111\n");
}
#include
#include "s3c2440.h"
#include "led.h"
#include "uart.h"
int main()
{
//HardwareInitAll();
//UartInit();
puts("main function!\n");
while(1);
return 0;
}
现象为依次打印未定义模式的CPSR、未定义的字符串、软件中断的CPSR、软件中断的字符串和主函数字符串。
还有一个可以玩的是SWI软中断主动触发时,可以带一个操作数,让我们的处理函数可以根据该操作数判断需要执行什么操作。这里的思路是通过LR拿到使用SWI的地址,取出该地址的值,根据ARM指令参考手册取出操作数部分,完成操作。
可以看到SWI的操作数是0-23bit。一个指令是4Bytes,直接用LR-4即可得到SWI调用的地址。
Start.S
.text
.global _start
_start:
B RESET
LDR pc,UNDEFIE
LDR pc,SW_INTERRUPT
...//省略重复代码
DO_SWI:
//1.设置栈
LDR SP,=0x33e00000
//2.保存现场
STMDB SP!,{R0-R12,LR}
//处理函数
MRS R0,CPSR
LDR R1,=SWI_TEST_STRING
SUB R2,LR,#4 //减去4,得到SWI地址
BL SWIHandler
//3.恢复现场,跳转回原来的位置
LDMIA SP!,{R0-R12,PC}^ //^ 表示将SPSR恢复到CPSR中
UND_TEST_STRING:
.string "enter undefin mode!\n"
SWI_TEST_STRING:
.string "enter swi mode!\n"
...
TEST_SWI:
MRS R0,CPSR
BIC R0,R0,#0x0F
MSR CPSR,R0
SWI 0x123
LDR pc,=main
halt:
B halt
execption.c
-----------------------
#include "execption.h"
#include "uart.h"
void ExecptionHandle(uint32_t CPSR,char* string)
{
printHex(CPSR);
puts(string);
}
void SWIHandler(uint32_t CPSR,char* string,uint32_t* opera_address)
{
ExecptionHandle(CPSR,string);
printHex((*opera_address) & 0xFFFFFF);
}
输出打印为0x123。