ARM微处理器的编程模型之:异常中断处理
4.在特权模式下使用SWI异常处理
在特权模式下使用SWI异常处理,和IRQ/FIQ中断嵌套基本类似。当执行SWI指令后,处理器执行下面操作。
① 处理器进入特权模式。
② 将程序状态字内容CPSR保存到SPSR_svc。
③ 返回地址放入LR_svc。
如果处理器已经处于特权模式,再发生SWI异常,则LR_svc和SPSR_svc寄存器的值将丢失。
所以在特权模式下,调用SWI软中断异常,必须先将LR_svc和SPSR_svc寄存器的值压栈保护。下面的例子显示了一个可以在特权模式下调用的SWI处理函数。
AREA SWI_Area, CODE, READONLY
PRESERVE8
EXPORT SWI_Handler
IMPORT C_SWI_Handler
T_bit EQU 0x20
SWI_Handler
STMFD sp!,{r0-r3,r12,lr} ;寄存器压栈保护
MOV r1, sp ;堆栈指针放r1作为参数传递.
MRS r0, spsr ;读取spsr.
STMFD sp!, {r0, r3} ;将spsr压栈保护
;
;
LDR r0,[lr,#-4] ;计算SWI指令地址.
BIC r0,r0,#0xFF000000 ;读取SWI中断向量号.
; r0存放中断向量号
; r1 堆栈指针
BL C_SWI_Handler ;调用C程序的SWI处理函数.
LDMFD sp!, {r0, r3} ;从堆栈中读取spsr.
MSR spsr_cf, r0 ;恢复spcr
LDMFD sp!, {r0-r3,r12,pc}^ ;恢复其他寄存器并返回.
END
5.从应用程序中调用SWI
可从汇编语言或 C/C++ 中调用 SWI。
(1)从汇编应用程序中调用SWI
从汇编语言程序中调用SWI,只要遵循AAPCS标准即可。调用前,设定所有必须的值并发出相关的 SWI。例如:
MOV r0, #65 ; 将软中断的子功能号放到r0中
SWI 0x0
注意 | SWI指令和其他所以ARM指令一样,可以被条件执行。 |
(2)从C应用程序中调用SWI
在C或C++应用程序中调用SWI,要将C语言的子程序用编译器扩展_swi声明,例如:
__swi(0) void my_swi(int);
……
……
……
my_swi(65);
编译器扩展_swi确保了SWI以内联方式进行编译,而没有额外的开销。但有如下的AAPCS限制。
· 函数调用参数只能使用r0~r3传递。
· 函数返回值只能通过r0~r3传递。
向内联的SWI函数传递参数和向实际的子函数传递参数基本类似。但返回值的情况比较复杂。如果有两到四个返回值,则必须告诉编译程序返回值是以结构形式返回的,并使用__value_in_regs 伪操作声明。这是因为基于结构值的函数通常被处理为一个void(空)型函数,且第一个自变量必须为存放结果结构的地址。
下面的例子显示了对编号为0x0、0x1、0x2和0x3的SWI软中断的调用。其中,SWI0x0和SWI0x1传递两个整型参数并返回一个单一结果;SWI0x2传递4个参数并返回一个单一结果;而SWI0x3传递4个参数并通过结构体返回4个结果。
#include stdio.h>
#include swi.h
unsigned *swi_vec = (unsigned *)0x08;
extern void SWI_Handler(void);
int main( void )
{
int result1, result2;
struct four_results res_3;
Install_Handler( (unsigned) SWI_Handler, swi_vec );
printf(result1 = multiply_two(2,4) = %dn, result1 = multiply_two(2,4));
printf(result2 = multiply_two(3,6) = %dn, result2 = multiply_two(3,6));
printf(add_two( result1, result2 ) = %dn, add_two( result1, result2 ));
printf(add_multiply_two(2,4,3,6) = %dn, add_multiply_two(2,4,3,6));
res_3 = many_operations( 12, 4, 3, 1 );
printf(res_3.a = %dn, res_3.a );
printf(res_3.b = %dn, res_3.b );
printf(res_3.c = %dn, res_3.c );
printf(res_3.d = %dn, res_3.d );
return 0;
}
__swi(0) int multiply_two(int, int);
__swi(1) int add_two(int, int);
__swi(2) int add_multiply_two(int, int, int, int);
struct four_results
{
int a;
int b;
int c;
int d;
};
__swi(3) __value_in_regs struct four_results many_operations(int, int, int, int);
(3)应用程序中动态调用SWI
在某些情形下,需要调用直到运行时才会知道其编号的 SWI。例如,当有很多相关操作可在同一目标上执行,并且每一个操作都有其自己的 SWI 时,就会发生这种情况。在此情况下,上一小节的方法不适用。
解决的方法有两种。
· 在运行时得到SWI功能号,然后构造出相应的SWI指令的编码,将该编码保存在某个存储单元中,将PC指针指向该单元,执行指令。
· 使用一个通用的SWI异常中断处理程序,将运行时需要调用的SWI功能号作为参数传递给该通用的SWI异常处理程序,通用的SWI异常中断处理程序根据参数值调用相应的SWI处理程序完成需要的操作。
通过汇编语言可以实现第二种解决办法:通过寄存器(通常为r0或r12)传递所需要的操作数,这样可以重新编写SWI处理程序,对相应寄存器中的值进行处理。
但有些情况下,为了节省程序开销,需要直接使用SWI中断号对程序调用。例如,操作系统可能会使用单一的一条SWI指令并用寄存器来传递所需运算的编号。这使得其他SWI空间可用于特定应用程序的SWI。在一个特定的应用程序中,如果从指令中提取SWI编号的开销太大,就可使用这个方法。ARM(0x123456)和Thumb(0xAB)半主机方式的SWI就是这样实现的。
评论