注:以下内容学习于韦东山老师arm裸机第一期视频教程
一.arm架构的异常与中断的处理
1.1 ARM对异常的处理流程
1.1.1 软件初始化
a. 设置中断源,让他可以产生中断
b. 设置中断优先级
c. 设置中断总开关
1.1.2 正常执行程序
对于不同的异常跳去不同的地址执行程序(这些地址一般是排在一起的,叫做异常向量),
这些地址上只是一条跳转指令,跳去执行其它函数,如下表
1.1.6 这些函数做什么事情?
a.保存现场(各种寄存器)
b.调用不同的处理函数
分辨中断源
调用不同处理函数
c.恢复现场
1.2 ARM异常种类
在uboot 1.1.6中有如下代码
-
_start: b reset /* 0地址,复位异常 */
-
ldr pc, _undefined_instruction /* 4地址,未定义指令 */
-
ldr pc, _software_interrupt /* 8地址,软中断指令 */
-
ldr pc, _prefetch_abort /* 12地址,指令预取异常,如果处理器预取的指令不存在,或者该地址不允许当前指令访问 */
-
ldr pc, _data_abort /* 14地址,数据访问终止,如果数据访问指令的目标地址不存在,或者该地址不允许当前指令访问 */
-
ldr pc, _not_used
-
ldr pc, _irq /* 24地址,中断异常 */
-
ldr pc, _fiq /* 28地址,快中断异常 */
-
_undefined_instruction: .word undefined_instruction
-
_software_interrupt: .word software_interrupt
-
_prefetch_abort: .word prefetch_abort
-
_data_abort: .word data_abort
-
_not_used: .word not_used
-
_irq: .word irq
-
_fiq: .word fiq
中断发生时CPU会强制调到0x18(24)的地方,然后跳转到其它函数进行处理
二.CPU模式与状态
2.1 CPU的State
2.1.1 ARM State
在这种状态下,使用arm指令集,每个指令占据4字节(被编译成的机器码占据4个字节)
2.1.2 Thumb State
在这种状态下,使用thumb指令集,每个指令占据2字节(被编译成的机器码占据2个字节)
thumb指令集是arm指令集压缩形式的子集,thumb更加高效,可以减少存储空间,对于嵌入式系统nor,sdram很大,不需要节省这点空间
Thumb 不是一个完整的体系结构,不能指望处理只执行Thumb 指令而不支持 ARM 指令集.因此,Thumb 指令只需要支持通用功能,必要时可以借助于完善的 ARM 指令集
2.1.3 将一个程序使用Thumb编译示例
对于C程序,在编译时添加-mthumb即可
修改Makefile如下:
-
objs = uart.o main.o start.o SetTacc.o my_printf.o lib1funcs.o init.o
-
A = test
-
all:$(objs)
-
#arm-linux-ld -Ttext 0 -Tdata 0x800 $^ -o $(A).elf
-
arm-linux-ld -T sdram.lds $^ -o $(A).elf
-
arm-linux-objcopy -O binary -S $(A).elf $(A).bin
-
arm-linux-objdump -D $(A).elf > $(A).dis
-
%.o:%.c
-
arm-linux-gcc -mthumb -c -o $@ $<
-
%.o:%.S
-
arm-linux-gcc -c -o $@ $<
-
clean:
-
rm *.o *.elf *.bin
对于.S程序,需要在代码中指定,在前面添加.code 32表示后续的指令使用arm指令集,在sdram_init前面添加.code 16表示下面的都使用thumb指令集,在前面需要使用bx指令进行状态切换,bx跳转的地址bit0等于1时就会进行状态的切换
代码如下:
-
.text
-
.code 32 /* 表示下面的代码使用arm指令集 */
-
.global _start
-
_start:
-
/* 省略一些初始化代码 */
-
/* 获得thumb_func地址 */
-
adr r0, thumb_func
-
/* 由于地址是4字节对齐,最后一位等于0,因此设置最后一位等于1,切换到thumb指令集 */
-
add r0, r0,#1
-
bx r0
-
.code 16 /* 表示下面的代码使用thumb指令集 */
-
thumb_func:
-
bl SdramInit
-
mov r0, #0
-
ldr r1, =_start
-
ldr r2, =bss_start
-
sub r2, r2, r1
-
bl Copy2Sdram
-
ldr r0, =bss_start
-
ldr r1, =bss_end
-
bl CleanBss
-
//bl main
-
//ldr pc, =main
-
/* 对thumb指令无法直接执行ldr pc, =main */
-
ldr r0, =main
-
mov pc, r0
-
halt:
-
b halt
编译出来可以发现bin文件变小了很多
2.2 CPU的工作模式
2.2.1 用户模式->正常程序执行的模式
2.2.2 系统模式
2.2.3 异常模式
a.未定义指令模式
b.管理模式
c.中止模式
c.1 指令预取终止(CPU是流水线操作的,在执行当前指令的时候读取第二条指令,执行第三条指令,第三条指令的读取叫做预取,可能会出错)
c.2 数据访问终止
d.中断模式
e.快中断模式
2.2.4 快速中断模式
其中,除了用户模式的其它六种模式又称为特权模式(Privileged Modes),在这六种模式下可以操作CPSR寄存器任意切换其它模式,
但是在用户模式下不可以直接进入其它模式
2.3 每种工作模式的差别
3.3.1 主要是寄存器之间的差别,见下图
3.3.2 未备份寄存器: r0-r7
备份寄存器: r8-14
程序计数器: r15
每个模式下都有自己的r13,r14寄存器sp与lr
对于快中断模式,有自己的备份寄存器r8-r14,在中断发生时不需要保存r8-r14这些寄存器,因此可以加快处理速度
在linux系统中并不会使用快中断模式
3.3.3 程序状态寄存器CPSR,SPSR,格式如下图
CPSR寄存器
a.M0-M4表示CPU当前处于哪种工作模式,可以读取进行修改(如果处于用户模式则无法修改),见下图
b.bit5-T->表示当前使用的指令集是ARM还是thmub
c.bit6-F->表示快中断使能
d.bit7-I->表示中断使能
e.bit8-bit27->保留位
f.bit28-bit31->状态位
例如 cmp R0,R1 (如果相等会将bit30位置1)
beq xxx (会去判断bit30是不是为1,是1的话则会跳转)
SPSR寄存器
发生异常时,这个寄存器会来保存被中断模式下的CPSR寄存器
2.4 异常的处理流程
2.4.1 进入异常时硬件完成:
1.将下一条指令的地址保存在lr寄存器中,即lr_异常 = 被中断模式的下一条指令的地址(pc + 4 / pc + 8)
2.将CPSR寄存器保存在异常模式下的SPSR,即SPSR_异常 = CPSR
3.修改CPSR的模式位(M0-M4),进入异常模式
4.调到向量表
2.4.2 退出异常时硬件完成:
1.PC = lr_异常 - offset,具体见下表
例如发生swi异常时,可以PC = lr_svc返回
发生fiq异常时,可以执行PC = lr_fiq - 4返回
2. 恢复CPSR的值,CPSR = SPSR_异常
3. 清中断(如果发生中断)
三.und异常模式程序示例
-
.text
-
.global _start
-
_start:
-
b reset /* vector 0 : reset */
-
b do_und /* vector 4 : und */
-
do_und:
-
/* 执行到这里之前:
-
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
-
* 2. SPSR_und保存有被中断模式的CPSR
-
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
-
* 4. 跳到0x4的地方执行程序
-
*/
-
/* sp_und未设置, 先设置它 */
-
ldr sp, =0x34000000
-
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
-
/* lr是异常处理完后的返回地址, 也要保存 */
-
stmdb sp!, {r0-r12, lr}
-
/* 保存现场 */
-
/* 处理und异常 */
-
mrs r0, cpsr
-
ldr r1, =und_string
-
bl printException
-
/* 恢复现场 */
-
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
-
und_string:
-
.string "undefined instruction exception"
-
reset:
-
/* 关闭看门狗 */
-
ldr r0, =0x53000000
-
ldr r1, =0
-
str r1, [r0]
-
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
-
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
-
ldr r0, =0x4C000000
-
ldr r1, =0xFFFFFFFF
-
str r1, [r0]
-
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
-
ldr r0, =0x4C000014
-
ldr r1, =0x5
-
str r1, [r0]
-
/* 设置CPU工作于异步模式 */
-
mrc p15,0,r0,c1,c0,0
-
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
-
mcr p15,0,r0,c1,c0,0
-
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
-
* m = MDIV+8 = 92+8=100
-
* p = PDIV+2 = 1+2 = 3
-
* s = SDIV = 1
-
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
-
*/
-
ldr r0, =0x4C000004
-
ldr r1, =(92<<12)|(1<<4)|(1<<0)
-
str r1, [r0]
-
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
-
* 然后CPU工作于新的频率FCLK
-
*/
-
/* 设置内存: sp 栈 */
-
/* 分辨是nor/nand启动
-
* 写0到0地址, 再读出来
-
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
-
* 否则就是nor启动
-
*/
-
mov r1, #0
-
ldr r0, [r1] /* 读出原来的值备份 */
-
str r1, [r1] /* 0->[0] */
-
ldr r2, [r1] /* r2=[0] */
-
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
-
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
-
moveq sp, #4096 /* nand启动 */
-
streq r0, [r1] /* 恢复原来的值 */
-
bl sdram_init
-
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
-
/* 重定位text, rodata, data段整个程序 */
-
bl copy2sdram
-
/* 清除BSS段 */
-
bl clean_bss
-
bl uart0_init
-
/* 故意加入一条未定义指令 */
-
und_code:
-
/* 注意,必须是未定义指令,已定义指令见下表 */
-
.word 0xdeadc0de /* 未定义指令 */
-
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
-
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
-
halt:
-
b halt
三.und异常模式程序的改进
3.1 问题引入及改进
a. 在上面的代码中,未定义指令放在了代码重定位之后,如果执行到异常处理函数时,一旦发生未定义异常,程序将会跳转(相对跳转)
反汇编代码如下,由于使用的的b相对跳转,程序并不会调到30000008的地址,而是会跳转到0x8的地址,一旦前面的程序过多,跳转地址的偏移较高,超过4096时,
若是NAND启动便会出错,因为RAM只有4K
-
30000000 <_start>:
-
30000000: ea00000e b 30000040 <reset>
-
30000004: eaffffff b 30000008 <do_und>
-
30000008 <do_und>:
改进方法:使用绝对跳转,跳转到sdram中去,相关代码如下:
-
.text
-
.global _start
-
_start:
-
b reset /* vector 0 : reset */
-
/* 注意,不可以使用ldr pc, =und_addr,这样会将und_addr的值存放在某个内存再去读出来,有可能存放的地方超过4K
-
* 而且,一般放在汇编文件的最后边去,然后去读取,如果汇编文件超过4K也会出错
-
ldr pc, und_addr /* vector 4 : und */ /* 从und_addr地址处读值赋给PC,会读到do_und的地址 */
-
und_addr:
-
.word do_und
-
do_und:
对应反汇编如下:
-
30000000 <_start>:
-
30000000: ea000012 b 30000050 <reset>
-
30000004: e51ff004 ldr pc, [pc, #-4] ; 30000008 <und_addr>
-
30000008 <und_addr>:
-
30000008: 3000000c andcc r0, r0, ip
-
3000000c <do_und>:
b.在重定位完之后,后面的代码都可能超过4K,因此要跳到sdram中去执行,代码如下
-
ldr pc, =sdram
-
sdram:
-
bl uart0_init
-
bl print1
-
/* 故意加入一条未定义指令 */
-
und_code:
-
.word 0xdeadc0de /* 未定义指令 */
-
bl print2
-
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
-
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
-
halt:
-
b halt
c.在定义字符串时有可能字符串的长度不是4字节对齐,因此在后面要调价.align = 4,如下:
-
und_string:
-
.string "undefined instruction exception"
-
.text
-
.global _start
-
_start:
-
b reset /* vector 0 : reset */
-
ldr pc, und_addr /* vector 4 : und */
-
und_addr:
-
.word do_und
-
do_und:
-
/* 执行到这里之前:
-
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
-
* 2. SPSR_und保存有被中断模式的CPSR
-
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
-
* 4. 跳到0x4的地方执行程序
-
*/
-
/* sp_und未设置, 先设置它 */
-
ldr sp, =0x34000000
-
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
-
/* lr是异常处理完后的返回地址, 也要保存 */
-
stmdb sp!, {r0-r12, lr}
-
/* 保存现场 */
-
/* 处理und异常 */
-
mrs r0, cpsr
-
ldr r1, =und_string
-
bl printException
-
/* 恢复现场 */
-
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
-
und_string:
-
.string "undefined instruction exception"
-
.align 4
-
reset:
-
/* 关闭看门狗 */
-
ldr r0, =0x53000000
-
ldr r1, =0
-
str r1, [r0]
-
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
-
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
-
ldr r0, =0x4C000000
-
ldr r1, =0xFFFFFFFF
-
str r1, [r0]
-
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
-
ldr r0, =0x4C000014
-
ldr r1, =0x5
-
str r1, [r0]
-
/* 设置CPU工作于异步模式 */
-
mrc p15,0,r0,c1,c0,0
-
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
-
mcr p15,0,r0,c1,c0,0
-
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
-
* m = MDIV+8 = 92+8=100
-
* p = PDIV+2 = 1+2 = 3
-
* s = SDIV = 1
-
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
-
*/
-
ldr r0, =0x4C000004
-
ldr r1, =(92<<12)|(1<<4)|(1<<0)
-
str r1, [r0]
-
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
-
* 然后CPU工作于新的频率FCLK
-
*/
-
/* 设置内存: sp 栈 */
-
/* 分辨是nor/nand启动
-
* 写0到0地址, 再读出来
-
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
-
* 否则就是nor启动
-
*/
-
mov r1, #0
-
ldr r0, [r1] /* 读出原来的值备份 */
-
str r1, [r1] /* 0->[0] */
-
ldr r2, [r1] /* r2=[0] */
-
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
-
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
-
moveq sp, #4096 /* nand启动 */
-
streq r0, [r1] /* 恢复原来的值 */
-
bl sdram_init
-
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
-
/* 重定位text, rodata, data段整个程序 */
-
bl copy2sdram
-
/* 清除BSS段 */
-
bl clean_bss
-
ldr pc, =sdram
-
sdram:
-
bl uart0_init
-
bl print1
-
/* 故意加入一条未定义指令 */
-
und_code:
-
.word 0xdeadc0de /* 未定义指令 */
-
bl print2
-
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
-
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
-
halt:
-
b halt
四.swi异常模式程序示例
4.1 swi异常介绍
在linux系统中,app运行于用户模式,这种模式受限制,不可访问硬件,当应用程序想要访问硬件时,必须切换模式,怎么切换?
swi就是软件中断,由于用户模式不能够修改cpsr寄存器来切换模式.因此,执行swi指令 + val来发生异常,可以根据val值判断为什么发生异常
4.2 程序示例
-
.text
-
.global _start
-
_start:
-
b reset /* vector 0 : reset */
-
ldr pc, und_addr /* vector 4 : und */
-
ldr pc, swi_addr /* vector 8 : swi */
-
und_addr:
-
.word do_und
-
swi_addr:
-
.word do_swi
-
do_und:
-
/* 执行到这里之前:
-
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
-
* 2. SPSR_und保存有被中断模式的CPSR
-
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
-
* 4. 跳到0x4的地方执行程序
-
*/
-
/* sp_und未设置, 先设置它 */
-
ldr sp, =0x34000000
-
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
-
/* lr是异常处理完后的返回地址, 也要保存 */
-
stmdb sp!, {r0-r12, lr}
-
/* 保存现场 */
-
/* 处理und异常 */
-
mrs r0, cpsr
-
ldr r1, =und_string
-
bl printException
-
/* 恢复现场 */
-
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
-
und_string:
-
.string "undefined instruction exception"
-
.align 4
-
do_swi:
-
/* 执行到这里之前:
-
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
-
* 2. SPSR_svc保存有被中断模式的CPSR
-
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
-
* 4. 跳到0x08的地方执行程序
-
*/
-
/* sp_svc未设置, 先设置它 */
-
ldr sp, =0x33e00000
-
/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
-
/* lr是异常处理完后的返回地址, 也要保存 */
-
stmdb sp!, {r0-r12, lr}
-
/* 保存现场 */
-
/* 处理swi异常 */
-
mrs r0, cpsr
-
ldr r1, =swi_string
-
bl printException
-
/* 恢复现场 */
-
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
-
swi_string:
-
.string "swi exception"
-
.align 4
-
reset:
-
/* 关闭看门狗 */
-
ldr r0, =0x53000000
-
ldr r1, =0
-
str r1, [r0]
-
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
-
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
-
ldr r0, =0x4C000000
-
ldr r1, =0xFFFFFFFF
-
str r1, [r0]
-
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
-
ldr r0, =0x4C000014
-
ldr r1, =0x5
-
str r1, [r0]
-
/* 设置CPU工作于异步模式 */
-
mrc p15,0,r0,c1,c0,0
-
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
-
mcr p15,0,r0,c1,c0,0
-
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
-
* m = MDIV+8 = 92+8=100
-
* p = PDIV+2 = 1+2 = 3
-
* s = SDIV = 1
-
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
-
*/
-
ldr r0, =0x4C000004
-
ldr r1, =(92<<12)|(1<<4)|(1<<0)
-
str r1, [r0]
-
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
-
* 然后CPU工作于新的频率FCLK
-
*/
-
/* 设置内存: sp 栈 */
-
/* 分辨是nor/nand启动
-
* 写0到0地址, 再读出来
-
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
-
* 否则就是nor启动
-
*/
-
mov r1, #0
-
ldr r0, [r1] /* 读出原来的值备份 */
-
str r1, [r1] /* 0->[0] */
-
ldr r2, [r1] /* r2=[0] */
-
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
-
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
-
moveq sp, #4096 /* nand启动 */
-
streq r0, [r1] /* 恢复原来的值 */
-
bl sdram_init
-
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
-
/* 重定位text, rodata, data段整个程序 */
-
bl copy2sdram
-
/* 清除BSS段 */
-
bl clean_bss
-
/* 复位之后, cpu处于svc模式
-
* 现在, 切换到usr模式
-
*/
-
mrs r0, cpsr /* 读出cpsr */
-
bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */
-
msr cpsr, r0
-
/* 设置 sp_usr */
-
ldr sp, =0x33f00000
-
ldr pc, =sdram
-
sdram:
-
bl uart0_init
-
bl print1
-
/* 故意加入一条未定义指令 */
-
und_code:
-
.word 0xdeadc0de /* 未定义指令 */
-
bl print2
-
swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
-
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
-
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
-
halt:
-
b halt
注意,CPU一上电处于管理模式,切换到用户模式再来执行SWI指令(在别的模式也可以执行,这么做只是为了测试用户模式),修改CPSR寄存器的M4-M0为0b10000即可进入用户模式
读出swi + val的val值,由于在发生异常时,lr寄存器会保存异常的下一条指令的地址,因此发生swi异常指令的地址就是lr-4,根据地址及机器码格式便可以找出val值
代码如下:
-
.text
-
.global _start
-
_start:
-
b reset /* vector 0 : reset */
-
ldr pc, und_addr /* vector 4 : und */
-
ldr pc, swi_addr /* vector 8 : swi */
-
und_addr:
-
.word do_und
-
swi_addr:
-
.word do_swi
-
do_und:
-
/* 执行到这里之前:
-
* 1. lr_und保存有被中断模式中的下一条即将执行的指令的地址
-
* 2. SPSR_und保存有被中断模式的CPSR
-
* 3. CPSR中的M4-M0被设置为11011, 进入到und模式
-
* 4. 跳到0x4的地方执行程序
-
*/
-
/* sp_und未设置, 先设置它 */
-
ldr sp, =0x34000000
-
/* 在und异常处理函数中有可能会修改r0-r12, 所以先保存 */
-
/* lr是异常处理完后的返回地址, 也要保存 */
-
stmdb sp!, {r0-r12, lr}
-
/* 保存现场 */
-
/* 处理und异常 */
-
mrs r0, cpsr
-
ldr r1, =und_string
-
bl printException
-
/* 恢复现场 */
-
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
-
und_string:
-
.string "undefined instruction exception"
-
.align 4
-
do_swi:
-
/* 执行到这里之前:
-
* 1. lr_svc保存有被中断模式中的下一条即将执行的指令的地址
-
* 2. SPSR_svc保存有被中断模式的CPSR
-
* 3. CPSR中的M4-M0被设置为10011, 进入到svc模式
-
* 4. 跳到0x08的地方执行程序
-
*/
-
/* sp_svc未设置, 先设置它 */
-
ldr sp, =0x33e00000
-
/* 保存现场 */
-
/* 在swi异常处理函数中有可能会修改r0-r12, 所以先保存 */
-
/* lr是异常处理完后的返回地址, 也要保存 */
-
stmdb sp!, {r0-r12, lr}
-
/* 由于先调用了printException函数,会使用r0-r3来传递参数,并且使用完不会恢复,因此不能用r0-r3保存lr寄存器的值,r4-r8也会被使用,但是使用完后寄存器的值被恢复原来的值 */
-
mov r4, lr
-
/* 处理swi异常 */
-
mrs r0, cpsr
-
ldr r1, =swi_string
-
bl printException
-
sub r0, r4, #4
-
bl printSWIVal
-
/* 恢复现场 */
-
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
-
swi_string:
-
.string "swi exception"
-
.align 4
-
reset:
-
/* 关闭看门狗 */
-
ldr r0, =0x53000000
-
ldr r1, =0
-
str r1, [r0]
-
/* 设置MPLL, FCLK : HCLK : PCLK = 400m : 100m : 50m */
-
/* LOCKTIME(0x4C000000) = 0xFFFFFFFF */
-
ldr r0, =0x4C000000
-
ldr r1, =0xFFFFFFFF
-
str r1, [r0]
-
/* CLKDIVN(0x4C000014) = 0X5, tFCLK:tHCLK:tPCLK = 1:4:8 */
-
ldr r0, =0x4C000014
-
ldr r1, =0x5
-
str r1, [r0]
-
/* 设置CPU工作于异步模式 */
-
mrc p15,0,r0,c1,c0,0
-
orr r0,r0,#0xc0000000 //R1_nF:OR:R1_iA
-
mcr p15,0,r0,c1,c0,0
-
/* 设置MPLLCON(0x4C000004) = (92<<12)|(1<<4)|(1<<0)
-
* m = MDIV+8 = 92+8=100
-
* p = PDIV+2 = 1+2 = 3
-
* s = SDIV = 1
-
* FCLK = 2*m*Fin/(p*2^s) = 2*100*12/(3*2^1)=400M
-
*/
-
ldr r0, =0x4C000004
-
ldr r1, =(92<<12)|(1<<4)|(1<<0)
-
str r1, [r0]
-
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
-
* 然后CPU工作于新的频率FCLK
-
*/
-
/* 设置内存: sp 栈 */
-
/* 分辨是nor/nand启动
-
* 写0到0地址, 再读出来
-
* 如果得到0, 表示0地址上的内容被修改了, 它对应ram, 这就是nand启动
-
* 否则就是nor启动
-
*/
-
mov r1, #0
-
ldr r0, [r1] /* 读出原来的值备份 */
-
str r1, [r1] /* 0->[0] */
-
ldr r2, [r1] /* r2=[0] */
-
cmp r1, r2 /* r1==r2? 如果相等表示是NAND启动 */
-
ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
-
moveq sp, #4096 /* nand启动 */
-
streq r0, [r1] /* 恢复原来的值 */
-
bl sdram_init
-
//bl sdram_init2 /* 用到有初始值的数组, 不是位置无关码 */
-
/* 重定位text, rodata, data段整个程序 */
-
bl copy2sdram
-
/* 清除BSS段 */
-
bl clean_bss
-
/* 复位之后, cpu处于svc模式
-
* 现在, 切换到usr模式
-
*/
-
mrs r0, cpsr /* 读出cpsr */
-
bic r0, r0, #0xf /* 修改M4-M0为0b10000, 进入usr模式 */
-
msr cpsr, r0
-
/* 设置 sp_usr */
-
ldr sp, =0x33f00000
-
ldr pc, =sdram
-
sdram:
-
bl uart0_init
-
bl print1
-
/* 故意加入一条未定义指令 */
-
und_code:
-
.word 0xdeadc0de /* 未定义指令 */
-
bl print2
-
swi 0x123 /* 执行此命令, 触发SWI异常, 进入0x8执行 */
-
//bl main /* 使用BL命令相对跳转, 程序仍然在NOR/sram执行 */
-
ldr pc, =main /* 绝对跳转, 跳到SDRAM */
-
halt:
-
b halt
-
printSWIVal函数如下,其中swi指令格式见下图,取出低24位即可:
-
void printSWIVal(unsigned int *pSWI)
-
{
-
puts("SWI val = ");
-
printHex(*pSWI & ~0xff000000);
-
puts("\n\r");