出处:ARM9的中断体系结构
实验的目的:
当TQ2440开发板的按键按下的时候,触发中断,点亮LED灯。
实验的源程序:
/*************************************************************************************
*s3c24xx.h
*************************************************************************************/
/* WOTCH DOG register */
#define WTCON (*(volatile unsigned long *)0x53000000)
/* SDRAM regisers */
#define MEM_CTL_BASE 0x48000000
#define SDRAM_BASE 0x30000000
/* NAND Flash registers */
#define NFCONF (*(volatile unsigned int *)0x4e000000)
#define NFCMD (*(volatile unsigned char *)0x4e000004)
#define NFADDR (*(volatile unsigned char *)0x4e000008)
#define NFDATA (*(volatile unsigned char *)0x4e00000c)
#define NFSTAT (*(volatile unsigned char *)0x4e000010)
/*GPIO registers*/
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPFUP (*(volatile unsigned long *)0x56000058)
#define GPGCON (*(volatile unsigned long *)0x56000060)
#define GPGDAT (*(volatile unsigned long *)0x56000064)
#define GPGUP (*(volatile unsigned long *)0x56000068)
#define GPHCON (*(volatile unsigned long *)0x56000070)
#define GPHDAT (*(volatile unsigned long *)0x56000074)
#define GPHUP (*(volatile unsigned long *)0x56000078)
/*UART registers*/
#define ULCON0 (*(volatile unsigned long *)0x50000000)
#define UCON0 (*(volatile unsigned long *)0x50000004)
#define UFCON0 (*(volatile unsigned long *)0x50000008)
#define UMCON0 (*(volatile unsigned long *)0x5000000c)
#define UTRSTAT0 (*(volatile unsigned long *)0x50000010)
#define UTXH0 (*(volatile unsigned char *)0x50000020)
#define URXH0 (*(volatile unsigned char *)0x50000024)
#define UBRDIV0 (*(volatile unsigned long *)0x50000028)
/*interrupt registes*/
#define SRCPND (*(volatile unsigned long *)0x4A000000)
#define INTMOD (*(volatile unsigned long *)0x4A000004)
#define INTMSK (*(volatile unsigned long *)0x4A000008)
#define PRIORITY (*(volatile unsigned long *)0x4A00000c)
#define INTPND (*(volatile unsigned long *)0x4A000010)
#define INTOFFSET (*(volatile unsigned long *)0x4A000014)
#define SUBSRCPND (*(volatile unsigned long *)0x4A000018)
#define INTSUBMSK (*(volatile unsigned long *)0x4A00001c)
/*external interrupt registers*/
#define EINTMASK (*(volatile unsigned long *)0x560000a4)
#define EINTPEND (*(volatile unsigned long *)0x560000a8)
@******************************************************************************
@ File:head.S
@ 功能:初始化,设置中断模式、管理模式的栈,设置好中断处理函数
@******************************************************************************
.extern main
.text
.global _start
_start:
@******************************************************************************
@ 异常向量,本程序中,除Reset和HandleIRQ外,其它异常都没有使用
@******************************************************************************
b Reset
@ 0x04: 未定义指令中止模式的向量地址
HandleUndef:
b HandleUndef
@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式
HandleSWI:
b HandleSWI
@ 0x0c: 指令预取终止导致的异常的向量地址
HandlePrefetchAbort:
b HandlePrefetchAbort
@ 0x10: 数据访问终止导致的异常的向量地址
HandleDataAbort:
b HandleDataAbort
@ 0x14: 保留
HandleNotUsed:
b HandleNotUsed
@ 0x18: 中断模式的向量地址
b HandleIRQ
@ 0x1c: 快中断模式的向量地址
HandleFIQ:
b HandleFIQ
Reset:
ldr sp, =4096 @ 设置栈指针,以下都是C函数,调用前需要设好栈
bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启
msr cpsr_c, #0xd2 @ 进入中断模式
ldr sp, =3072 @ 设置中断模式栈指针
msr cpsr_c, #0xd3 @ 进入管理模式
ldr sp, =4096 @ 设置管理模式栈指针,
@ 其实复位之后,CPU就处于管理模式,
@ 前面的“ldr sp, =4096”完成同样的功能,此句可省略
bl init_led @ 初始化LED的GPIO管脚
bl init_irq @ 调用中断初始化函数,在init.c中
msr cpsr_c, #0x53 @ 设置I-bit=0,开IRQ中断
ldr lr, =halt_loop @ 设置返回地址
ldr pc, =main @ 调用main函数
halt_loop:
b halt_loop
HandleIRQ:
sub lr, lr, #4 @ 计算返回地址
stmdb sp!, { r0-r12,lr } @ 保存使用到的寄存器
@ 注意,此时的sp是中断模式的sp
@ 初始值是上面设置的3072
ldr lr, =int_return @ 设置调用ISR即EINT_Handle函数后的返回地址
ldr pc, =EINT_Handle @ 调用中断服务函数,在interrupt.c中
int_return:
ldmia sp!, { r0-r12,pc }^ @ 中断返回, ^表示将spsr的值复制到cpsr
======================================================
注:ARM9的异常向量表:
======================================================
/**************************************************************************************************
* init.c: 进行一些初始化
*************************************************************************************************/
#include "s3c24xx.h"
/*
* LED1,LED2,LED4对应GPB5、GPB6、GPB7、GPB8
*/
#define GPB5_out (1<<(5*2))
#define GPB6_out (1<<(6*2))
#define GPB7_out (1<<(7*2))
#define GPB8_out (1<<(8*2))
#define GPB5_msk (3<<(5*2))
#define GPB6_msk (3<<(6*2))
#define GPB7_msk (3<<(7*2))
#define GPB8_msk (3<<(8*2))
/*
* K1,K2,K3,K4对应GPF1、GPF4、GPF2、GPF0
*/
#define GPF0_int (0x2<<(0*2))
#define GPF1_int (0x2<<(1*2))
#define GPF2_int (0x2<<(2*2))
#define GPF4_int (0x2<<(4*2))
#define GPF0_msk (3<<(0*2))
#define GPF1_msk (3<<(1*2))
#define GPF2_msk (3<<(2*2))
#define GPF4_msk (3<<(4*2))
/*
* 关闭WATCHDOG,否则CPU会不断重启
*/
void disable_watch_dog(void)
{
WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可
}
void init_led(void)
{
// LED1,LED2,LED3,LED4对应的4根引脚设为输出
GPBCON &= ~(GPB5_msk " GPB6_msk | GPB7_msk | GPB8_msk);
GPBCON |= GPB5_out | GPB6_out | GPB7_out | GPB8_out;
}
/*
* 初始化GPIO引脚为外部中断
* GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD)
*/
void init_irq( )
{
// K1,K2,K3,K4对应的4根引脚设为中断功能
GPFCON &= ~(GPF0_msk | GPF1_msk | GPF2_msk | GPF4_msk);
GPFCON |= GPF0_int | GPF1_int | GPF2_int | GPF4_int;
// 对于EINT4,需要在EINTMASK寄存器中使能它
EINTMASK &= ~(1<<4);
/*
* 设定优先级:
* ARB_SEL0 = 00b, ARB_MODE0 = 0: REQ1 > REQ2 > REQ3,即EINT0 > EINT1 > EINT2
* 仲裁器1、6无需设置
* 最终:
* EINT0 > EINT1> EINT2 > EINT4 即K4 > K1 > K3 > K2
*/
PRIORITY = (PRIORITY & ((~0x01) | ~(0x3<<7)));
// EINT0、EINT1、EINT2、EINT4_7使能
INTMSK &= (~(1<<0)) & (~(1<<1)) & (~(1<<2)) & (~(1<<4));
}
/*******************************************************************************************
*interrupt.c
*******************************************************************************************/
#include "s3c24xx.h"
void EINT_Handle()
{
unsigned long oft = INTOFFSET;
unsigned long val;
/*
* K1,K2,K3,K4对应GPF1、GPF4、GPF2、GPF0
* 即 EINT1, ETIN4, EINT2, EINT0
* oft为 1, 4, 2, 0 (对应INTMSK寄存器)
*/
switch( oft )
{
// K1被按下
case 1:
{
GPBDAT |= (0xF<<5); // 所有LED熄灭
GPBDAT &= ~(1<<5); // LED1点亮
break;
}
// K2被按下
case 4:
{
GPBDAT |= (0xF<<5); // 所有LED熄灭
GPBDAT &= ~(1<<6); // LED2点亮
break;
}
// K3被按下
case 2:
{
GPBDAT |= (0xF<<5); // 所有LED熄灭
GPBDAT &= ~(1<<7); // LED3点亮
break;
}
// K4被按下
case 0:
{
GPBDAT |= (0xF<<5); // 所有LED熄灭
GPBDAT &= ~(1<<8); // LED4点亮
break;
}
default:
break;
}
//清中断
if( oft == 4 )
EINTPEND = (1<<4); // EINT4_7合用IRQ4
SRCPND = 1< INTPND = 1< }
/*****************************************************************************
*main.c
*****************************************************************************/
int main()
{
while(1);
return 0;
}
/************************************************************************************
*Makefile
************************************************************************************/
objs := head.o init.o interrupt.o main.o
int.bin: $(objs)
arm-linux-ld -Ttext 0x00000000 -o int_elf $^
arm-linux-objcopy -O binary -S int_elf $@
arm-linux-objdump -D -m arm int_elf > int.dis
%.o:%.c
arm-linux-gcc -Wall -O2 -c -o $@ $<
%.o:%.S
arm-linux-gcc -Wall -O2 -c -o $@ $<
clean:
rm -f int.bin int_elf int.dis *.o
实验的问题总结:
I.刚开始程序会执行head.S文件,第一条指令是b reset指令。在reset中,我们可以知道:
设置了reset模式下的栈,因为我们之后需要调用C语言函数,而C语言函数的局部变量是需要栈来保存的。
然后我们可以看到,执行了这样一条指令:msr cpsr_c, #0xd2
其原因可以看一下解释:
/***************************MRS 和 MSR*********************************
MRS R0, CPSR 读取CPSR的内容写入R0
MSR CPSR, R0 读取R0的内容写入CPSR
MSR CPSR_f, R0 读取R0的24-31的内容写入CPSR的24-31bit
MSR CPSR_c, R0 读取R0的0-7的内容写入CPSR的0-7bit
MSR CPSR_fc, R0
MSR CPSR_f, #0xf0000000 将0xf写入CPSR的24-31bit
所有的CPSR可以替换成SPSR
***************************MRS 和 MSR*********************************/
/****************************CPSR***********************************
CPSR格式如下所示。SPSR和CPSR格式相同。
31 30 29 28 27 26 7 6 5 4 3 2 1 0
N Z C V Q DNM(RAZ) I F T M4 M3 M2 M1 M0
***条件标志位***
N——本位设置成当前指令运算结果的bit[31]的值。当两个表示的有符号整数运算时,n=1表示运算结果为负数,n=0表示结果为正书或零。
Z——z=1表示运算的结果为零;z=0表示运算的结果不为零。对于CMP指令,Z=1表示进行比较的两个数大小相等。
C——下面分四种情况讨论C的设置方法:
在加法指令中(包括比较指令CMP),当结果产生了进位,则C=1,表示无符号运算发生上溢出;其他情况C=0。
在减法指令中(包括减法指令CMP),当运算中发生错位,则C=0,表示无符号运算数发生下溢出;其他情况下C=1。
对于包含移位操作的非加减运算指令,C中包含最后一次溢出的的位的数值。对于其他非加减运算指令,C位的值通常不受影响。
V——对于加减运算指令,当操作数和运算结果为二进制的补码表示的带符号数时,V=1表示符号为溢出;通常其他指令不影响V位。
***Q标识位***
在ARM V5的E系列处理器中,CPSR的bit[27]称为q标识位,主要用于指示增强的dsp指令是否发生了溢出。同样的spsr的bit[27]位也称为q标识位,用于在异常中断发生时保存和恢复CPSR中的Q标识位。
在ARM V5以前的版本及ARM V5的非E系列的处理器中,Q标识位没有被定义。
***CPSR中的控制位***
CPSR的低八位I、F、T、M[4:0]统称为控制位。当异常中断发生时这些位发生变化。在特权级的处理器模式下,软件可以修改这些控制位。
**中断禁止位I,F:当I=1时禁止IRQ中断,当F=1时禁止FIQ中断
**T控制位:T控制位用于控制指令执行的状态,即说明本指令是ARM指令还是Thumb指令。对于ARM V4以更高版本的T系列ARM处理器,T控制位含义如下:
T=0表示执行ARM指令
T=1表示执行Thumb指令
对于ARM V5以及更高版本的非T系列处理器,T控制位的含义如下
T=0表示执行ARM指令
T=1表示强制下一条执行的指令产生未定指令中断
***M控制位***
M控制位控制处理器模式,具体含义如下:
M[4:0] 处理器模式 可访问的寄存器
ob10000 user pc,r14~r0,CPSR
0b10001 FIQ PC,R14_FIQ-R8_FIQ,R7~R0,CPSR,SPSR_FIQ
0b10010 IRQ PC,R14_IRQ-R13_IRQ,R12~R0,CPSR,SPSR_IRQ
0B10011 SUPERVISOR PC,R14_SVC-R13_SVC,R12~R0,CPSR,SPSR_SVC
0b10111 ABORT PC,R14_ABT-R13_ABT,R12~R0,CPSR,SPSR_ABT
0b11011 UNDEFINEED PC,R14_UND-R8_UND,R12~R0,CPSR,SPSR_UND
0b11111 SYSTEM PC,R14-R0,CPSR(ARM V4以及更高版本)
*****************************CPSR*************************************/
看完这两点,你就知道为什么要设置的值为#0xd2。接下来是设置中断模式下的栈指针。同上,因为每种模式下的栈寄存器是不共用的。
II.接下来,我们重点看一下 ldr lr, =halt_loop 这条指令,为什么在调用main函数之前需要设置lr寄存器呢?不设置难道不可以么?
具体解释如下:
在跳转的时候,PC的值会赋给lr寄存器。
在汇编调用C语言的时候,不一定非得配置lr寄存器,这要看你调用完C语言后想再往下执行哪条指令,
若接下来执行的指令刚好是C语言完了之后的这条指令,那么这种情况下可以不用设置,因为CPU会自动将lr = lr - 4,
也就是接下来该执行的一条指令,若你想再调用C语言之后,是跳转到某处的一标号,此时需要设置lr。
III.接下来,若发生中断的时候,CPU会自动让PC跳转到 HandleIRQ 去执行对应的指令。
然后,我们可以看到:sub lr, lr, #4
为什么要将lr的值减4呢?具体解释如下:
/***************************************************************************
我们知道在ARM 架构里,PC值指向当前执行指令的地址加8处,也就是说, 当执行指令A(地址0x8000)时,PC 等于指令C 的地址(0x8008)。
假如指令A 是“BL”指令,则当执行该指令时,会把PC(=0x8008)保存到LR 寄存器里面,但是接下去处理器会马上对LR 进行一个自动的调整动作:
LR=LR-0x4。这样,最终保存在 LR 里面的是 B 指令的地址,所以当从 BL 返回时,LR 里面正好是正确的返回地址。同样的调整机制在所有LR
自动保存操作中都存在,比如进入中断响应时,处理器所做的LR 保存中,也进行了一次自动调整,并且调整动作都是LR=LR-0x4。
下面,我们对不同类型的异常的返回地址依次进行说明:
假设在指令A 处(地址0x8000)发生了异常,进入异常响应后,LR 上经过调整保存的地址值应该是B 的地址0x8004。
1、 如果发生的是软件中断,即A 是“SWI”指令
异常是由指令本身引起的,从 SWI 中断返回后下一条执行指令就是B,正好是LR 寄存器保存的地址, 所以只要直接把LR 恢复给PC。
MOVS pc, lr
2、 发生的是Undefined instruction异常
异常是由指令本身引起的,从异常返回后下一条执行指令就是B,正好是LR 寄存器保存的地址, 所以只要直接把LR 恢复给PC。
MOVS pc, lr
3、 发生的是IRQ或FIQ中断
因为指令不可能被中断打断,所以A指令执行完以后才能响应中断,此时PC已更新,指向指令D的地址(地址0x800C),
LR 上经过调整保存的地址值是C 的地址0x8008。中断返回后应该执行B指令,所以返回操作是:
SUBS pc, lr, #4
4、 发生的是Prefetch Abort异常
该异常并不是处理器试图从一个非法地址取指令时触发,取出的指令只是被标记为非法,按正常处理流程放在流水线上,
在执行阶段触发Prefetch Abort异常,此时LR 上经过调整保存的地址值是B 的地址0x8004。异常返回应该返回到A指令,
尝试重新取指令,所以返回操作是:
SUBS pc, lr, #4
5、 发生的是“Data Abort”
CPU访问存储器时触发该异常,此时PC指向指令D的地址(地址0x800C),LR 上经过调整保存的地址值是C 的地址0x8008。
异常返回后,应回到指令A,尝试重新操作存储器,所以返回操作是:
SUBS pc, lr, #8
****************************************************************************/
这就是为什么我们需要将lr的值减4,具体可以参考我转载的博客。
IV.然后我们可以看得这样一条指令:stmdb sp!, { r0-r12,lr },它是和 ldmia sp!, { r0-r12,pc }^ 指令对应起来的。
在跳转到中断服务程序之前,先保存用到的寄存器,在完成中断服务程序之后,又恢复寄存器。其中,lr和pc是对应起来的。
也就是说,恢复寄存器后,pc中保存的值是lr寄存器中的。所以当执行完这条指令之后,就又跳回来了。
ARM 中断状态和SVC状态的堆栈切换 (异常)
基础知识:
Arm的寄存器使用规则以及寻址指令:
R13 Sp 堆栈寄存器
R14 Lr 连接寄存器
R15 PC 程序计数器
多寄存器寻址:
LDMIA R0!,{R1-R4}
执行以后的效果
R1 <——[R0]
R2 <——[R0+4]
R3 <——[R0+8]
R4 <——[R0+12]
堆栈寻址:
STMFD入栈指令,相当于STMDB
STMFD SP!,{R2-R4} 注意这个“!”的使用,在使用和不使用的情况下会有不一样的效果,在后面的代码中具体分析。
[SP-4] <——R4
[SP-8] <——R3
[SP-12] <——R2
LDMFD出栈指令,相当于LDMIA
LDMFD SP!,{R6-R8}
R6 <——[SP]
R7 <——[SP+4]
R8 <——[SP+8]
补充说明:
LDMIA / STMIA Increment After (先操作,后增加)
LDMIB / STMIB Increment Before(先增加,后操作)
LDMDA / STMDA Decrement After (先操作,后递减)
LDMDB / STMDB Decrement Before(先递减,后操作)
•STMFD (Push) 块存储- Full Descending stack [STMDB]
•LDMFD (Pop) 块装载- Full Descending stack [LDMIA]
这些使用规则以及默认的表达方法是给编译器使用。但是在开发底层语言的同时,有必要知道这个么命名的规则和使用方法。初始化代码和部分关键代码是靠汇编实现。这些代码的理解不免和汇编打交道。因此了解一下基本的汇编规则还是很有帮助。
Arm的工作模式:
Arm的工作模式以及相关寄存器设置:
1,用户模式(usr) [10000]:ARM处理器正常的程序执行状态
2,快速中断模式(fiq) [10001]:用于高速数据传输或通道处理
3,外部中断模式(irq) [10010]:用于通用的中断处理
4,管理模式(svc) [10011]:操作系统使用的保护模式
5,中止模式(abt) [10111]:当数据或指令预取终止时进入该模式,用于虚拟存
储及存储保护
6,未定义指令模式(und)[11011]:当未定义的指令执行时进入该模式,用于支持硬件
协处理器的软件仿真
7,系统模式(sys) [11111]:运行具有特权模式的操作系统任务
设置方法:
MRS R14,CPSR 读取
MSR CPSR_c, R14 写入
以上几种模式存在的意义在于不同模式下特殊的几个寄存器使用是有区别的。再svc模式下堆栈指针为sp svc中断模式下sp指针为 sp irq等。同样lr连接寄存器的内容也是有不同的含义。再不同模式切换中,lr寄存器保存的地址是由硬件完成,但是表示的是不同模式下的下一条指令,既返模式切换后的返回地址。
每一种模式对应不同的bank register。中文官方翻译不详。但是每一种模式要拥有自己独立的寄存器组。并且每一种模式使用和可见寄存器的数量也是不相同的。
模式切换过程中其实只针对spsr进行操作,而未涉及cpsr是的操作。这个过程之所以这样主要是参考ARM cortex A8的TRM。其中这样描述离开异常的情况:
Typically the return instruction is an arithmetic orlogical operation with the S bit set to
1 and rd = r15, so the core copies the SPSR back to theCPSR.
也就是说离开异常,从异常情况返回以后会自动把spsr的内同拷贝到cpsr中。所以在执行BL Lr指令之前使用的堆栈其实并位切换。
Linux中初始化:
1, Svc模式的堆栈初始化:
堆栈的概念是给C 语言编译以后的代码使用,因此从head.S一直到C语言的执行,就是start_kernel。
__mmap_switched:
@注释 1:
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy datasegment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS(and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
@注释 2:
ldmia r3, {r4, r5, r6, r7, sp}
str r9, [r4] @ Saveprocessor ID
str r1, [r5] @ Savemachine type
str r2, [r6] @ Saveatags pointer
bic r4, r0, #CR_A @ Clear'A' bit
stmia r7, {r0, r4} @Save control register values
@注释 3:
b start_kernel
ENDPROC(__mmap_switched)
注释1:
__switch_data这是以个地址。Linker会安排这个地址具体的数值。打开Sysmap可以发现这个数值为:c0008123 t __switch_data
注释 2:
将r3所指的内容依次装入{r4– r6,sp},这个时候sp指针就有了具体的数值了。
注释 3:
跳转指令,指向C函数的start_kernel。这时候栈针开始起效。因为C语言编译出来的代码参数传递,调用变量保存等都使用sp指针。这个指针仅仅是给初始化代码所使用。在进程的概念中还有进程堆栈的概念。这时候的sp具体指向的是描述进程结构的结构体task_info。
2,irq以及其他模式的初始化:
/*
* setup stacks for re-entrant exceptionhandlers
*/
__asm__ (
"msr cpsr_c, %1\n\t"
"add r14, %0, %2\n\t"
"mov sp, r14\n\t"
"msr cpsr_c, %3\n\t"
"add r14, %0, %4\n\t"
"mov sp, r14\n\t"
"msr cpsr_c, %5\n\t"
"add r14, %0, %6\n\t"
"mov sp, r14\n\t"
"msr cpsr_c, %7"
:
: "r" (stk),
PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
"I" (offsetof(struct stack,irq[0])),
PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),
"I" (offsetof(struct stack,abt[0])),
PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),
"I" (offsetof(struct stack,und[0])),
PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
: "r14");
函数:cpu_init()文件:setup.c
通过msr设置了cpsr寄存器。然后通过mov指令把具体的参数地址写入sp寄存器。
其中offsetof(struct stack, irq[0])这个表达式表示的是偏移量。既是在结构体中的偏移量。
其实在这个函数中初始化的irq堆栈只有4 bytes x 3。这么小的堆栈空间是否可以满足中端的需求。答案是:可以。在中端进入的函数中其实并没有完全使用irq模式下sp_irq指向的堆栈空间。在进入函数中马上有利用了msr指令进行了模式切换,切换到了svc模式。并且放弃了irq的模式。从中端返回也是从svc模式返回,而非irq模式。
代码:
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
@
@ Save r0, lr_<exception>(parent PC) and spsr_<exception>
@ (parent CPSR)
@
@ 注释 1:
stmia sp, {r0, lr} @ save r0,lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE) 进入SVC模式
msr spsr_cxsf, r0
@
@ the branch table mustimmediately follow this code
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
@注释 2:
movs pc, lr @ branch tohandler in SVC mode
参照ARM的参考
ENDPROC(vector_\name)
注释 1 :
保存irq模式下的sp和lr指针到前面初始的sp_irq中。记住只有4 bytes x 3大小的空间。在后面的代码中还会看到str lr, [sp, #8]保存了最后一个参数到sp_irq的空间中。这里要注意:stmia sp, {r0, lr}这条指令。没有使用“!”号。这样一来尽管指令执行后sp指针指向的地址不会自加。因此在正式切换到SVC模式之前sp_irq所指向的地址并没有变化。这样再次进入中断模式时候,sp_irq不需要调整,可以重复使用。
注释 2:
参照ARM 的芯片设计手册可以发现,离开异常,从异常情况返回以后会自动把spsr的内同拷贝到cpsr中。所以在执行BL Lr指令之前使用的堆栈其实并位切换。
这样一来尽管是中断的模式进入系统,但是由中断模式切换至SVC模式。在SVC模式中完成了中断的后续相应和操作。
---------------------
作者:小陆zi
来源:CSDN
原文:https://blog.csdn.net/edwardlulinux/article/details/9261393
版权声明:本文为博主原创文章,转载请附上博文链接!
深入理解SP、LR和PC
深入理解ARM的这三个寄存器,对编程以及操作系统的移植都有很大的裨益。
1、堆栈指针r13(SP):每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈,也就是说五种异常模式、非异常模式(用户模式和系统模式),都有各自独立的堆栈,用不同的堆栈指针来索引。这样当ARM进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性。
2、连接寄存器r14(LR):每种模式下r14都有自身版组,它有两个特殊功能。
(1)保存子程序返回地址。使用BL或BLX时,跳转指令自动把返回地址放入r14中;子程序通过把r14复制到PC来实现返回,通常用下列指令之一:
MOV PC, LR
BX LR
通常子程序这样写,保证了子程序中还可以调用子程序。
stmfd sp!, {lr}
……
ldmfd sp!, {pc}
(2)当异常发生时,异常模式的r14用来保存异常返回地址,将r14如栈可以处理嵌套中断。
3、程序计数器r15(PC):PC是有读写限制的。当没有超过读取限制的时候,读取的值是指令的地址加上8个字节,由于ARM指令总是以字对齐的,故bit[1:0]总是00。当用str或stm存储PC的时候,偏移量有可能是8或12等其它值。在V3及以下版本中,写入bit[1:0]的值将被忽略,而在V4及以上版本写入r15的bit[1:0]必须为00,否则后果不可预测。
关于 PC指针问题
根据《ARM微控制器基础与实践》第2页对指令流水线的说明ARM指令集中:
当前"取指PC"=当前PC
当前"译码PC"=当前PC-4
当前"执行PC"=当前PC-8
很容易理解ARM指令集中:
例程(1)
BL delay ;LR=当前PC-4(即当前指令"执行PC"+8-4)
mov r0,#0 ;
delay mov PC,LR ;子函数返回;即返回到调用时"当前PC"-4位置(即当前指令("执行PC"+8)-4);即返回到函数调用指令的下一条指令(mov r0,#0;)处
例程(2)
mov r0,#1 ;当EINT1在此处发生中断,LR=当前PC(即当前指令"执行PC"+8)
mov r0,#0 ;
EINT1_Handler
subs PC,LR,#4 ;LR=LR-4中断返回;即返回到中断时"当前PC"-4位置(即当前指令("执行PC"+8)-4);即返回到子函数调用指令的下一条指令(mov r0,#0;)处
但如何理解P393页软中断的汇编接口:
例程(3)
TST R3,#T_bit ;判断是ARM指令集还是Thumb指令集
LDRNEH R0,[LR,#-2] ;是Thumb指令集,取SWI中断时执行的指令(即C语言__SWI(0x00)指令)
BICNE R0,R0,#0XFF00 ;取软中断功能号
LDREQ R0,[LR,#-4] ;是ARM指令集,取SWI中断时执行的指令(即C语言__SWI(0x00)指令)
BIC R0,R0,#0XFF000000 ;取软中断功能号
问题1:LDREQ R0,[LR,#-4];取的是执行C语言__SWI(0x00)指令时"当前PC-4";而它指向的应该是SWI中断返回的位置,而不是SWI指令本身?
问题2:BIC R0,R0,#0XFF000000 ;取软中断功能号;该指令的执行一定和生成ARM代码规则相关,但书中并无相关介绍 答 1:
re:当前"取指PC"=当前PC
当前"译码PC"=当前PC-4
当前"执行PC"=当前PC-8
=========
在三级流水线下上面的描述及其容易理解,流水线增多后似乎不太合适讲,但在ARM架构下记住这点就行了,也是最重要的:当前"执行PC"=当前PC-8。
问题1:LDREQ R0,[LR,#-4];取的是执行C语言__SWI(0x00)指令时"当前PC-4";而它指向的应该是SWI中断返回的位置,而不是SWI指令本身?
:执行C语言__SWI(0x00)指令时,指令地址=当前PC-8
:保存LR时会有个自动调整:LR=PC-4(从你的举例1来看应该以及知道这一点了),所以LR=当前PC-4=SWI指令地址+8-4=当前PC-8__SWI(0x00)指令地址+4
:然后再取LR-4=当前PC-8__SWI(0x00)指令地址+4-4=SWI指令
问题2:BIC R0,R0,#0XFF000000 ;取软中断功能号;该指令的执行一定和生成ARM代码规则相关,但书中并无相关介绍
:去看SWI指令格式编码,机器码编码中除了开头几位的条件标识和指令码之外,余下的就是软中断号了
异常中断返回的几种情况
ARM体系结构与编程 清华大学出版社 杜春雷 第九章异常中断处理
我的理解如下:
1.SWI和和未定义指令异常中断的返回:
指令地址
A PC-8 当前指令为SWI或未定义指令 此时发生中断.PC的值还没有更新.
A+4 PC-4 中断时处理器将PC-4保存到LR
A+8 PC
返回时,从发生中断的指令A(PC-8)的下一条指令A+4(PC-4)处开始执行,所以直接
把LR的值赋给PC就行了,具体指令为MOV PC,LR (PC=A+4=LR)
2,IRQ和FIQ异常中断处理的返回:
指令地址 对应于PC
A PC-8 执行此指令完成后(!)查询IRQ及FIQ,如果有中断请求
则产生中断.
A+4 PC-4
A+8 PC
(此时PC的值已经更新,指向A+12.将当前PC-4(即A+8)
保存到LR.返回时,要接着执行A+4(LR-4)处的指令,所以返回指令为
SUBS PC, LR,#4(PC=A+4=LR-4)
3,指令预取中止异常中断处理的返回:
指令地址
A PC-8 执行本指令时发生中断,
A+4 PC-4 处理器将A+4(PC-4)保存到LR.
A+8 PC
返回时,发生指令预取中止的指令A(PC-8)处重新执行,所以返回指令为
SUBS PC, LR,#4(PC=A=LR-4)
4,数据访问中止异常中断处理的返回:
指令地址
A PC-8 本指令访问有问题的数据,产生中断时,PC的值已经更新
A+4 PC-4 中断发生时PC=A+12,处理器将A+8(PC-4)保存到LR.
A+8 PC
返回时,要返回到A处继续执行,所以指令为SUBS PC, LR,#8.(PC=A=LR-8)
在ARM体系中,通常有以下3种方式控制程序的执行流程:
1、在正常执行过程中,每执行一条ARM指令,程序计数器PC的值加4个字节;每执行一条Thumb指令,程序计数器PC加2个字节。整个过程是顺序执行的;
2、跳转 B指令执行跳转操作;BL指令在执行跳转的同时,保存子程序返回地址;BX指令,执行跳转的同时,根据目标地址的最低位,可以将程序状态切换到Thumb状态;BLX指令执行上述3个操作;
3、当异常中断发生时,系统执行完当前指令后,将跳转到相应的异常中断处理程序处执行。在进入异常中断处理程序时,要保存被中断的程序的执行现场,在从异常中断处理程序退出时,要恢复被中断的程序的执行现场。当异常中断处理程序执行完成后,程序返回到发生中断的指令的下一条指令处执行。
重点:ARM处理器对异常中断的响应过程:
㈠、保存当前程序状态寄存器CPSR到对应异常中断的处理器模式下的SPSR中;
㈡、设置当前程序状态寄存器CPSR的处理器模式位M(4:0)为对应的处理器模式,并禁止IRQ中断(设置I位=1);当进入的是FIQ模式时,禁止FIQ中断(设置F位=1);
㈢、将对应异常中断的处理器模式下的LR设置成返回地址;
㈣、将程序计数器PC值,设置成该异常中断向量地址,从而跳转到相应的异常中断处理程序处执行。
上述处理器对异常中断的响应过程可以用伪代码描述如下:
R14<exception_mode>=return Link
SPSR<exception_mode>=CPSR
CPSR[4:0]=exception mde number
CPSR[5] = 0 //所有异常均在ARM状态下处理
if(<exception_mode>==Reset or FIQ )then
CPSR[6]=1 //禁止FIQ中断
CPSR[7] =1 //禁止IRQ中断
PC = exception vetor address
程序将自动跳转到对应异常中断的处理程序中。
上述过程,完全由处理器自动完成,所以,当发生一种异常中断时,寄存器R14 、CPSR、SPSR和PC的值将是上述的结果!
ARM处理器中主要有7个异常(2个中断异常):
1、复位异常;在以ARM为核的单片机中,常把下列事件作为引起复位的原因。
(1)上电复位:在上电后,复位使内部达到预定的状态,特别是程序跳到初始入口;
(2)复位引脚上的复位脉冲:这是由外部其他控制信号引起的;
(3)对系统电源检测发现过压或欠压;
(4)时钟异常复位。
ARM处理器复位后,处理器硬件将进行以下操作:
(1)强制进入管理模式;0b10011
(2)强制进入ARM状态;T=0
(3)跳转到绝对地址PC=0x00000000处执行;
(4)禁止IRQ中断和FIQ中断。I=1,F=1;
复位后,程序状态寄存器如下:
...... I F T M4 M3 M2 M1 M0
1 1 0 1 0 0 1 1
上电复位后,进入管理模式,执行操作系统程序,一般用做对系统初始化,例如开中断等;然后切换到用户模式,开始执行正常的用户程序。
切换到用户模式可使用下列程序:
MRS R0,CPSR ;读状态寄存器
BIC R0,R0,#03 ;把末两位清0
MSR CPRS_c,R0 ;把修改后的值加载给状态寄存器,切换结束
...... ;用户程序
2、未定义指令异常;由于ARM使用32位代码,包含的信息量很大,可达2的31方(4G)。ARM指令集不能用尽所有代码。当ARM处理器遇到无法译码的指令时就会发生未定义指令异常。进入中断处理程序。
ARM的未定义指令异常有以下两种情况:
(1)遇到一条无法执行的指令,此指令没有定义;
(2)执行一条对协处理器的操作指令,在正常情况下,协处理器应该应答,但协处理器没有应答。
未定义异常中断时,处理器硬件将进行以下操作:
(1)把下一条指令的地址拷贝给LR;
(2)把程序状态寄存器CPSR拷贝给SPSR_und;
(3)强制进入未定义模式; 0b11011
(4)强制进入到ARM模式;T=0
(5)跳转到绝对地址PC=0x00000004处执行;
(6)禁止IRQ中断。 I=0
(7)状态寄存器中的F位不变。
进入中断后,程序状态寄存器如下:
...... I F T M4 M3 M2 M1 M0
1 x 0 1 1 0 1 1
使用下列指令退出异常中断,返回原程序 。
MOVS PC,R14.
3、软件中断异常;是由指令SWI引起的。程序在执行这一指令后,进入异常中断。
SWI和未定义指令异常中断是由当前执行的指令自身产生的。当SWI和未定义指令异常中断产生时,程序计数器PC的值还未更新,它指向当前指令后面第2条指令(对于ARM指令来说+8字节;对于Thumb指令来说+4字节的位置)。当SWI和未定义指令异常中断产生时,处理器将值(PC-4)保存到异常模式下的寄存器LR_mode中。这时(PC-4)即指向当前指令的下一条指令地址。因此返回操作可以通过下面的指令来实现:MOV PC,LR。该指令将寄存器LR中的值复制到程序计数器PC中实现程序返回,同时将SPSR_mode寄存器内容复制到当前程序状态寄存器CPSR中。
处理器响应中断,硬件执行如下的操作。
(1)把下一条指令的地址拷贝给LR;
(2)把程序状态寄存器CPSR拷贝给SPSR_svc;
(3)强制进入管理模式;0b10011
(4)强制进入到ARM状态;T=0
(5)跳转到绝对地址PC=0x00000008处执行;
(6)禁止IRQ中断。 I=1; F保持不变;
进入中断后的程序状态寄存器如下:
...... I F T M4 M3 M2 M1 M0
1 x 0 1 0 0 1 1
软件中断处理程序完成后,使用下列指令返回到原中断处:MOVS PC,R14.
4、预取指中止异常;由程序存储器引起的中止异常叫做预取指中止异常;由数据存储器引起的中止异常叫做数据中止异常。由于ARM的指令是3级流水线结构,读取指令周期是提前进行的,因此把读取指令的过程一般称预取指。如果在取得指令的同时程序存储器发出中止信号,则ARM处理器把这一条指令标记位无效,然后等待执行。
在指令预取时,如果目标地址是非法的,该指令将被标记成有问题的指令。这时,流水线上该指令之前的指令继续执行。当执行到该被标记成有问题的指令时,处理器产生指令预取中止异常中断。
当发生指令预取中止异常中断时,程序要返回到该有问题的指令处,重新读取并执行该指令。因此指令预取中止异常中断程序应该返回到产生该指令预取中止异常中断的指令处,而不是像前面两种情况下返回到发生中断的指令的下一条指令。
指令预取中止异常中断是由当前执行的指令自身产生的,当指令预取中止异常中断产生时,程序计数器PC的值还未更新,它指向当前指令后面第2条指令(对于ARM指令来说,它指向当前指令地址加8个字节的w位置;对于Thumb指令来说,它指向当前指令地址加4个字节的位置)。当指令预取中止异常中断产生时,处理器将(PC-4)值保存到异常模式下的寄存器LR_mode中。这时(PC-4)即指向当前指令的下一条指令。因此返回操作可以通过下面的指令来实现:SUBS PC,LR,#4
该指令将寄存器LR中的值减4后,复制到程序计数器PC中,实现程序返回,同时将SPSR_mode寄存器内容复制到当前程序状态寄存器CPSR中。
有两种可能如下:
(1)当执行这条指令前程序发生跳转,则这条无效指令不引起异常中断;
(2)当执行到这条指令时,处理器会发生预取指中止异常,引起中断。
当进入预取指异常中断时,处理器硬件响应中断,执行以下的操作:
(1)把中断时PC的地址拷贝给LR;
(2)把程序状态寄存器CPSR拷贝给SPSR_abt;
(3)强制进入中止异常模式;0b10111
(4)强制进入到ARM状态;T=0;
(5)跳转到绝对地址PC=0x0000000C处执行;
(6)禁止IRQ中断。 I=1;
进入中断后,程序状态寄存器如下:
...... I F T M4 M3 M2 M1 M0
1 x 0 1 0 1 1 1
预取指中止异常中断返回时,应该执行下列指令:SUBS PC,R14,#4.
5、数据中止异常;ARM处理器访问数据存储器时,在读取数据的同时数据存储器发出了中止信号,引起数据中止异常。
当进入预取数据异常中断时,处理器硬件响应中断,执行以下的操作:
(1)把中断时的PC的地址拷贝给LR;
(2)把程序状态寄存器CPSR拷贝给SPSR_abt;
(3)强制进入中止异常模式;
(4)强制进入到ARM状态;
(5)跳转到绝对地址PC=0x00000010处执行;
(6)禁止IRQ中断。
进入中断后,程序状态寄存器如下:
...... I F T M4 M3 M2 M1 M0
1 x 0 1 0 1 1 1
数据中止异常中断服务程序返回时,使用下列指令:SUBS PC,R14,#8
上述指令是返回到中断时所执行的指令,目的是再一次从数据存储器中读取数据。如果不再一次读取数据,则执行下一条指令,此时使用下列指令返回:
SUBS PC,R14,#4
6、中断请求(IRQ)异常;例如:定时器中断、串行口通讯中断、外部信号中断和A/D处理中断等。IRQ中断是可屏蔽的。
通常当处理器执行完当前指令后,查询IRQ中断引脚及FIQ中断引脚,并且查看系统是否允许IRQ及FIQ中断。如果有中断引脚有效,并且系统允许该中断产生,处理器将产生IRQ或FIQ异常中断。这时,程序计数器PC的值已经更新,它指向当前指令后面的第3条指令(对于ARM指令来说,它指向当前指令地址+12字节位置;对于Thumb指令来说,它指向当前指令地址+6字节的位置)。处理器这时将值(PC-4)保存到异常模式下的寄存器LR_mode中。这时(PC-4)即指向当前指令后面的第2条指令。因此返回操作可以通过下面的指令实现: SUBS PC, LR, #4
该指令将寄存器LR中的值减4后,复制到程序计数器PC中,实现程序返回,同时将SPSR_mode寄存器内容复制到当前程序状态寄存器CPSR中。
在状态寄存器中的I位就是IRQ的屏蔽位。当I=1时。则屏蔽IRQ中断,当I=0时,则允许中断。处理器复位后置I为1,关闭中断。
当发生IRQ中断时,处理器硬件响应中断,执行下列操作:
(1)把中断时的PC的地址值拷贝给LR;
(2)把程序状态寄存器CPSR拷贝给SPSR_irq;
(3)制进入IRQ异常模式;
(4)强制进入到ARM状态;
(5)跳转到绝对地址PC=0x00000018处执行;
(6)禁止IRQ中断。
进入中断后,程序状态寄存器如下:
...... I F T M4 M3 M2 M1 M0
1 x 0 1 0 0 1 0
完成中断处理后,程序执行下列返回原中断处:SUBS PC.R14,#4.
7、快速中断(FIQ)请求异常;FIQ快速中断是可屏蔽的。在状态寄存器中的F位就是FIQ的屏蔽位。当F=1时。则屏蔽FIQ中断,当F=0时,则允许中断。处理器复位后置F为1,关闭中断。
当发生FIQ中断时,处理器硬件响应中断,执行下列操作:
(1)把中断时的PC的地址值拷贝给LR;
(2)程序状态寄存器CPSR拷贝给SPSR_fiq;
(3)强制进入FIQ异常模式;
(4)强制进入到ARM状态;
(5)跳转到绝对地址PC=0x0000001C处执行;
(6)禁止FIQ中断。
进入中断后,程序状态寄存器如下:
...... I F T M4 M3 M2 M1 M0
1 x 0 1 0 0 0 1
完成中断处理后,程序执行下列返回原中断处:SUBS PC.R14,#4
---------------------
作者:血染风采2018
来源:CSDN
原文:https://blog.csdn.net/wqx521/article/details/52453760
版权声明:本文为博主原创文章,转载请附上博文链接!