swi 是 arm 的软件中断指令,大概是 software interrupt 的意思 执行完swi指令后,cpu会做几件事情:
- 将swi的下一条指令地址保存到 r14_svc 中
- 将当前 cpsr 保存到 spsr_svc 中
- 将cpu模式改为特权模式svc_mode, 即更改 cpsr 的低五位, cspr[4:0]=0b10011
- 切换到ARM状态, cspr[5]=0
- 禁止IRQ, cspr[7]=1
- 将 pc 置为 0x00000008, 即跳到中断向量的地方开始执行
其于CPU的这种行为,我们需要考虑几件事情:
- cpu最后跳到0x00000008地址执行,而且该地址处只有4个字节可以使用。
这4个字节正好可以放置一条指令。那么显然这个指令要将pc跳到另一个地方。
如何实现呢?
首先考虑当前的环境和需求。
在我的这块开发板上,SRAM的地址空间为 0x30000000 ~ 0x34000000 所以最开始要将测试程序放到 0x30000000 开始的地方。当程序从 0x30000000 地址跑 起来之后,我们再去设置中断向量表,即在在代码中去设置 0x00000000 ~ 0x0000001c 这块地址空间的指令。
于是想到可以如下来实现:
- .global _start
-
_start:
-
mov r8, #0
-
adr r9, vector_init_block
-
ldmia {r0-r7}
-
stmia {r0-r7}
-
-
vector_init_block:
-
b reset_addr
-
b undefined_addr
-
b swi_addr
-
b prefetch_addr
-
b abort_addr
-
b notused_addr
-
b irq_addr
-
b fiq_addr
程序从 _start 开始执行,第一件事情即将 vector_init_block 开始的8条指令复制到 0x00000000 开始的地方,每条指令占4字节,共 8*4=32字节。
假想现在程序从 0x30000000 开始执行,之后调用 swi 发生了软件中断,pc开始跳到 0x00000008 开始执行,这个位置的指令是 b swi_addr , 然后再跳到swi_addr去继续 执行。
这里有一个问题,跳转指令 b 只能在 32M 空间范围内跳转。 而 swi_addr 肯定是在 0x30000000 之后的,所以这里会跳转失败。
要想在32位(4G)地址空间实现跳转,可以使用 ldr 指令。 于是可以如下实现
- .global _start
-
_start:
-
mov r8, #0
-
adr r9, vector_init_block
-
ldmia {r0-r7}
-
stmia {r0-r7}
-
ldmia {r0-r7}
-
stmia {r0-r7}
-
-
vector_init_block:
-
ldr pc, reset_addr
-
ldr pc, undefined_addr
-
ldr pc, swi_addr
-
ldr pc, prefetch_addr
-
ldr pc, abort_addr
-
ldr pc, notused_addr
-
ldr pc, irq_addr
-
ldr pc, fiq_addr
-
-
reset_addr: .word reset_handler
-
undefined_addr: .word undefined_handler
-
swi_addr: .word swi_handler
-
prefetch_addr: .word prefetch_handler
-
abort_addr: .word abort_handler
-
notused_addr: .word 0
-
irq_addr: .word irq_handler
-
fiq_addr: .word fiq_handler
ldr 指令是将某个地址里的值读到寄存器中,而紧接着我们将几个中断处理函数 的地址放到内存中供 ldr 读取。
reset_addr .word reset_handler 就是很典型的指针用法, 将处理函数 reset_handler 的地址放到内存中存起来。
与上面不同的时,这里 _start 中不仅复制了指令,而且复制了后面的‘指针’
- 上面构造好中断向量表之后,swi 就可以正确跳到 swi_handler 地址处开始执行了。
那么在 swi_handler 处理函数中,需要做些什么事情呢?
swi处理通常分为两级:
- 汇编实现,保存现场,计算swi中的24位立即数
- 可以用c实现,具体实现swi各个功能
细分如下:
- 保存环境,将要用到的寄存器和返回地址保存到栈中,供退出时恢复
- 计算中断号,swi指令中包括一个24位的立即数,用于指定特定功能
- 进入swi处理程序。这部分可以用c实现
- 中断返回,恢复寄存器
- swi_handler:
-
stmfd {r0-r12, lr}
-
-
ldr r0, [lr, #-4]
-
bic r0, r0, #0xff000000
-
bl c_swi_handler
-
-
ldmfd {r0-r12, pc}^
看到 swi 是怎么返回的了么! ldmfd sp!, {r0-r12, pc}^ ^ 表示恢复 spsr_svc -> cpsr
- c语言部分。 首先要注意的是上面 bl c_swi_handler 之前计算好了 r0 的值,跳到
c_swi_handler时,r0将作为第一个实参传给c_swi_handler, 所以在c_swi_handler中
可以得到中断号
- void c_swi_handler(unsigned int nr)
-
{
-
/* print swi exception number */
-
puts("c_swi_handler\t");
-
put_dex(nr);
-
-
return;
-
}
- 如何触发 swi 中断呢? 这好像不是个问题,直接使用 swi 不就可以了么。
那在c语言里呢? 嵌入汇编!
- static inline void swi(void)
-
{
-
__asm__ __volatile__ (
-
"swi 0x1\n\t"
-
);
-
}
swi 带的参数 0x1 即为中断号
附件是本文例子的完整代码,运行于2410上,其中通过串口打印来方便看到测试结果
讲了这么多,swi倒底可以用来做什么呢? 通过上面的测试,对swi运行流程有了一个大概的了解。之后第一映像就是swi可以用来实 现系统调用。 如同在x86下linux中使用的 int 0x80
2410-exception-swi.rar
阅读(6192) | 评论(0) | 转发(0) |