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的异常向量表:
======================================================
/**************************************************************************************************
*%20init.c:%20进行一些初始化
*************************************************************************************************/
#include%20"s3c24xx.h"
/*
*%20LED1,LED2,LED4对应GPB5、GPB6、GPB7、GPB8
*/
#define%20GPB5_out%20(1<<(5*2))
#define%20GPB6_out%20(1<<(6*2))
#define%20GPB7_out%20(1<<(7*2))
#define%20GPB8_out%20(1<<(8*2))
#define%20GPB5_msk%20(3<<(5*2))
#define%20GPB6_msk%20(3<<(6*2))
#define%20GPB7_msk%20(3<<(7*2))
#define%20GPB8_msk%20(3<<(8*2))
/*
*%20K1,K2,K3,K4对应GPF1、GPF4、GPF2、GPF0
*/
#define%20GPF0_int%20%20%20(0x2<<(0*2))
#define%20GPF1_int%20%20%20(0x2<<(1*2))
#define%20GPF2_int%20%20%20(0x2<<(2*2))
#define%20GPF4_int%20%20%20(0x2<<(4*2))
#define%20GPF0_msk%20%20(3<<(0*2))
#define%20GPF1_msk%20%20(3<<(1*2))
#define%20GPF2_msk%20%20(3<<(2*2))
#define%20GPF4_msk%20%20(3<<(4*2))
/*
*%20关闭WATCHDOG,否则CPU会不断重启
*/
void%20disable_watch_dog(void)
{
%20%20WTCON%20=%200;%20//%20关闭WATCHDOG很简单,往这个寄存器写0即可
}
void%20init_led(void)
{
//%20LED1,LED2,LED3,LED4对应的4根引脚设为输出
GPBCON%20&=%20~(GPB5_msk%20"%20GPB6_msk%20|%20GPB7_msk%20|%20GPB8_msk);
GPBCON%20|=%20GPB5_out%20|%20GPB6_out%20|%20GPB7_out%20|%20GPB8_out;
}
/*
*%20初始化GPIO引脚为外部中断
*%20GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD)
*/
void%20init_irq(%20)
{
//%20K1,K2,K3,K4对应的4根引脚设为中断功能
GPFCON%20&=%20~(GPF0_msk%20|%20GPF1_msk%20|%20GPF2_msk%20|%20GPF4_msk);
GPFCON%20|=%20GPF0_int%20|%20GPF1_int%20|%20GPF2_int%20|%20GPF4_int;
%20%20//%20对于EINT4,需要在EINTMASK寄存器中使能它
%20%20EINTMASK%20&=%20~(1<<4);
%20%20%20%20%20
%20%20/*
%20%20*%20设定优先级:
%20%20*%20ARB_SEL0%20=%2000b,%20ARB_MODE0%20=%200:%20REQ1%20>%20REQ2%20>%20REQ3,即EINT0%20>%20EINT1%20>%20EINT2
%20%20*%20仲裁器1、6无需设置
%20%20*%20最终:
%20%20*%20EINT0%20>%20EINT1>%20EINT2%20>%20EINT4%20即K4%20>%20K1%20>%20K3%20>%20K2
%20%20*/
%20%20PRIORITY%20=%20(PRIORITY%20&%20((~0x01)%20|%20~(0x3<<7)));
%20%20//%20EINT0、EINT1、EINT2、EINT4_7使能
%20%20INTMSK%20%20&=%20(~(1<<0))%20&%20(~(1<<1))%20&%20(~(1<<2))%20&%20(~(1<<4));
}
/*******************************************************************************************
*interrupt.c
*******************************************************************************************/
#include%20"s3c24xx.h"
void%20EINT_Handle()
{
%20%20unsigned%20long%20oft%20=%20INTOFFSET;
%20%20unsigned%20long%20val;
/*
*%20K1,K2,K3,K4对应GPF1、GPF4、GPF2、GPF0
*%20%20%20%20%20%20即%20EINT1,%20ETIN4,%20EINT2,%20EINT0
*%20%20%20%20%20%20oft为%201,%204,%202,%200%20(对应INTMSK寄存器)
*/
%20
%20%20switch(%20oft%20)
%20%20{
%20%20%20%20//%20K1被按下
%20%20%20%20case%201:
%20%20%20%20{%20
%20%20%20%20%20%20GPBDAT%20|=%20(0xF<<5);%20%20//%20所有LED熄灭
%20%20%20%20%20%20GPBDAT%20&=%20~(1<<5);%20%20%20//%20LED1点亮
%20%20%20%20%20%20break;
%20%20%20%20}
%20%20%20
%20%20%20%20//%20K2被按下
%20%20%20%20case%204:
%20%20%20%20{%20
%20%20%20%20%20%20GPBDAT%20|=%20(0xF<<5);%20%20//%20所有LED熄灭
%20%20%20%20%20%20GPBDAT%20&=%20~(1<<6);%20%20%20//%20LED2点亮
%20%20%20%20%20%20break;
%20%20%20%20}
%20%20%20%20//%20K3被按下
%20%20%20%20case%202:
%20%20%20%20{%20
%20%20%20%20%20%20GPBDAT%20|=%20(0xF<<5);%20%20//%20所有LED熄灭
%20%20%20%20%20%20GPBDAT%20&=%20~(1<<7);%20%20%20//%20LED3点亮
%20%20%20%20%20%20break;
%20%20%20%20}
%20%20%20%20//%20K4被按下
%20%20%20%20case%200:
%20%20%20%20{%20
%20%20%20%20%20%20GPBDAT%20|=%20(0xF<<5);%20%20//%20所有LED熄灭
%20%20%20%20%20%20GPBDAT%20&=%20~(1<<8);%20%20%20//%20LED4点亮
%20%20%20%20%20%20break;
%20%20%20%20}
%20%20%20%20default:
%20%20%20%20%20%20break;
%20%20}
%20%20//清中断
%20%20if(%20oft%20==%204%20)
%20%20%20%20EINTPEND%20=%20(1<<4);%20%20//%20EINT4_7合用IRQ4
%20%20SRCPND%20=%201<
/*****************************************************************************
*main.c
*****************************************************************************/
int%20main()
{
%20%20while(1);
%20%20return%200;
}
/************************************************************************************
*Makefile
************************************************************************************/
objs%20:=%20head.o%20init.o%20interrupt.o%20main.o
int.bin:%20$(objs)
arm-linux-ld%20-Ttext%200x00000000%20-o%20int_elf%20$^
arm-linux-objcopy%20-O%20binary%20-S%20int_elf%20$@
arm-linux-objdump%20-D%20-m%20arm%20int_elf%20>%20int.dis
%.o:%.c
arm-linux-gcc%20-Wall%20-O2%20-c%20-o%20$@%20$<
%.o:%.S
arm-linux-gcc%20-Wall%20-O2%20-c%20-o%20$@%20$<
clean:
rm%20-f%20int.bin%20int_elf%20int.dis%20*.o
实验的问题总结:
I.刚开始程序会执行head.S文件,第一条指令是b%20reset指令。在reset中,我们可以知道:
设置了reset模式下的栈,因为我们之后需要调用C语言函数,而C语言函数的局部变量是需要栈来保存的。
然后我们可以看到,执行了这样一条指令:msr%20cpsr_c,%20#0xd2
其原因可以看一下解释:
/***************************MRS%20和%20MSR*********************************
%20%20MRS%20R0,%20CPSR%20%20%20%20%20%20%20%20读取CPSR的内容写入R0
%20%20MSR%20CPSR,%20R0%20%20%20%20%20%20%20%20读取R0的内容写入CPSR
%20%20MSR%20CPSR_f,%20R0%20%20%20%20%20%20%20读取R0的24-31的内容写入CPSR的24-31bit
%20%20MSR%20CPSR_c,%20R0%20%20%20%20%20%20%20读取R0的0-7的内容写入CPSR的0-7bit
%20%20MSR%20CPSR_fc,%20R0%20
%20%20MSR%20CPSR_f,%20#0xf0000000%20%20将0xf写入CPSR的24-31bit
%20%20所有的CPSR可以替换成SPSR
***************************MRS%20和%20MSR*********************************/
/****************************CPSR***********************************
CPSR格式如下所示。SPSR和CPSR格式相同。
31%20%20%2030%20%20%2029%20%2028%20%20%2027%20%20%2026%20%20%20%20%20%20%20%207%20%20%206%20%20%205%20%20%204%20%20%203%20%20%202%20%20%201%20%20%200
N%20%20%20Z%20%20%20C%20%20%20V%20%20%20Q%20%20%20DNM(RAZ)%20%20%20%20%20I%20%20%20F%20%20%20T%20%20%20M4%20%20M3%20%20M2%20%20M1%20%20M0
***条件标志位***
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%20V5的E系列处理器中,CPSR的bit[27]称为q标识位,主要用于指示增强的dsp指令是否发生了溢出。同样的spsr的bit[27]位也称为q标识位,用于在异常中断发生时保存和恢复CPSR中的Q标识位。
在ARM%20V5以前的版本及ARM%20V5的非E系列的处理器中,Q标识位没有被定义。
***CPSR中的控制位***
CPSR的低八位I、F、T、M[4:0]统称为控制位。当异常中断发生时这些位发生变化。在特权级的处理器模式下,软件可以修改这些控制位。
**中断禁止位I,F:当I=1时禁止IRQ中断,当F=1时禁止FIQ中断
**T控制位:T控制位用于控制指令执行的状态,即说明本指令是ARM指令还是Thumb指令。对于ARM%20V4以更高版本的T系列ARM处理器,T控制位含义如下:
T=0表示执行ARM指令
T=1表示执行Thumb指令
对于ARM%20V5以及更高版本的非T系列处理器,T控制位的含义如下
T=0表示执行ARM指令
T=1表示强制下一条执行的指令产生未定指令中断
***M控制位***
M控制位控制处理器模式,具体含义如下:
M[4:0]%20处理器模式%20%20可访问的寄存器
ob10000%20user%20%20%20pc,r14~r0,CPSR
0b10001%20FIQ%20%20%20PC,R14_FIQ-R8_FIQ,R7~R0,CPSR,SPSR_FIQ
0b10010%20IRQ%20%20%20PC,R14_IRQ-R13_IRQ,R12~R0,CPSR,SPSR_IRQ
0B10011%20SUPERVISOR%20%20PC,R14_SVC-R13_SVC,R12~R0,CPSR,SPSR_SVC
0b10111%20ABORT%20%20%20PC,R14_ABT-R13_ABT,R12~R0,CPSR,SPSR_ABT
0b11011%20UNDEFINEED%20%20PC,R14_UND-R8_UND,R12~R0,CPSR,SPSR_UND
0b11111%20SYSTEM%20%20%20PC,R14-R0,CPSR(ARM%20V4以及更高版本)
*****************************CPSR*************************************/
看完这两点,你就知道为什么要设置的值为#0xd2。接下来是设置中断模式下的栈指针。同上,因为每种模式下的栈寄存器是不共用的。
II.接下来,我们重点看一下ldr%20lr,%20=halt_loop%20这条指令,为什么在调用main函数之前需要设置lr寄存器呢?不设置难道不可以么?
具体解释如下:
在跳转的时候,PC的值会赋给lr寄存器。
在汇编调用C语言的时候,不一定非得配置lr寄存器,这要看你调用完C语言后想再往下执行哪条指令,
若接下来执行的指令刚好是C语言完了之后的这条指令,那么这种情况下可以不用设置,因为CPU会自动将lr%20=%20lr%20-%204,
也就是接下来该执行的一条指令,若你想再调用C语言之后,是跳转到某处的一标号,此时需要设置lr。
III.接下来,若发生中断的时候,CPU会自动让PC跳转到HandleIRQ%20去执行对应的指令。
然后,我们可以看到:sub%20lr,%20lr,%20#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寄存器中的。所以当执行完这条指令之后,就又跳回来了。