ARM汇编SWI

来源:互联网 发布:注册环保工程师知乎 编辑:程序博客网 时间:2024/05/17 07:44

1.  状态切换和BX指令

AREA   ADDREG,CODE,READONLY
  ENTRY

MAIN

      ADR r0,ThunbProg + 1 
    BX  r0

      CODE16

ThunbProg
    mov r2,#2
  mov r3,#3
  add r2,r2,r3
  ADR r0,ARMProg
  BX r0

    CODE32

ARMProg
  mov r4,#4
  mov r5,#5
  add r4,r4,r5
    mov r0,#0x18
    LDR r1,=0x20026
    SWI 0x123456
    END

解释:ADR r0,ThunbProg + 1为什么要加1呢?这是因为BX指令中若目标地址的bit[0]为0,则跳转时自动将CPSR中的标志位T复位,即把目标地址的代码解释为ARM代码,若目标地址的bit[0]为1,则跳转时自动将CPSR中的标志位T置位,即把目标代码解释为Thumb代码。

    SWI,即software interrupt软件中断。该指令产生一个SWI异常。意思就是处理器模式改变为管理模式,CPSR寄存器保存到管理模式下的SPSR寄存器,并且跳转到SWI向量。其ARM指令格式如下:

SWI{cond} immed_24

Cond域:是可选的条件码 (参见 ARM汇编指令条件执行详解).

immed_24域:范围从 0 到 224-1的表达式, (即0-16777215),immed_24为软中断号(服务类型)。

使用SWI指令时,通常使用以下两种方法进行传递参数

(1).指令中的24位立即数指定了用户请求的服务类型,参数通过通用寄存器传递.

mov  r0,#34   ;设置子功能号位34

SWI   12      ;调用12号软中断

(2).指令中的24位立即数被忽略,用户请求的服务类型有寄存器R0的值决定,参数通过其他的通用寄存器传递.

 mov r0,#12     ;调用12号软中断

 mov r1,#34     ;设置子功能号位34

 SWI  0

在SWI异常中断处理程序中,取出SWI立即数的步骤为:首先确定引起软中断的SWI指令是ARM指令还是Thunb指令,这可通过对SPSR访问得到;然后取得该SWI指令的地址,这可通过访问LR寄存器得到;接着读出指令,分解出立即数.如下程序:

T_bit     EQU    0x20

SWI_Handler

          STMFD    SP!,{R0-R3,R12,LR} ;现场保护

          MRS      R0,SPSR            ;读取SPSR

          STMFD    SP!,{R0}           ;保存SPSR

          TST      R0,#T_bit           

          LDRNEH   R0,[LR,#-2]    ;若是Thunb指令,读取指令码(16位)

       BICNE    R0,R0,#0xFF00     ;取得Thunb指令的8位立即数

       LDREQ    R0,[LR,#-4]       ;若是ARM指令,读取指令码(32位)

       BICEQ    R0,R0,#0xFF000000     ;取得ARM指令的24位立即数

       ....

       LDMFD     SP!,{R0-R3,R12,PC}^    ;SWI异常中断返回

对于两条红色指令的解释:就是对连接寄存器LR(R14的)理解。

寄存器R14(LR寄存器)有两种特殊功能:

·在任何一种处理器模式下,该模式对应的R14寄存器用来保存子程序的返回地址。当执行BL或BLX指令进行子程序调用时,子程序的返回地址被放置在R14中。这样,只要把R14内容拷贝到PC中,就实现了子程序的返回(具体的子程序返回操作,这里不作详细介绍)。

·当某异常发生时,相应异常模式下的R14被设置成异常返回的地址(对于某些异常,可能是一个偏移量,一个较小的常量)。异常返回类似于子程序返回,但有小小的不同(这里不作详细介绍)。

所谓的子程序的返回地址,实际就是调用指令的下一条指令的地址,也就是BL或BLX指令的下一条指令的地址。所谓的异常的返回的地址,就是异常发生前,CPU执行的最后一条指令的下一条指令的地址。

例如:(子程序返回地址示例)

指令                       指令所在地址

ADD     R2,R1,R3             ;0x300000

BL      subC                   ;0x300004

MOV     R1,#2                  ;0x300008

BL指令执行后,R14中保存的子程序subC的返回地址是0x300008。

再例如:(异常返回地址示例)

指令                       指令所在地址

ADD     R2,R1,R3             ;0x300000

SWI     0x98                   ;0x300004

MOV     R1,#2                  ;0x300008

SWI指令执行后,进入SWI异常处理程序,此时R14中保存的返回地址为0x300008。

所以,在SWI异常处理子程序中执行 LDR  R0,[LR,#-4]语句,实际就是把产生本次SWI异常的SWI指令的内容(如:SWI   0x98)装进R0寄存器。又因为SWI指令的低24位保存了指令的操作数(如:0x98),所以再执行

BIC   R0,R0,#0xFF000000语句,就可以获得immed_24操作数的实际内容。

软件中断SWI的实现:

实际上,在SWI异常处理子程序的实现时,还可以绕开immed_24操作数的获取操作,这就是说,我们可以不去获取immed_24操作数的实际内容,也能实现SWI异常的分支处理。这就需要使用R0-R4寄存器,遵从ATPCS原则。

    具体方法就是,在执行SWI指令之前,给R0赋予某个数值,然后在SWI异常处理子程序中根据R0值实现不同的分支处理。例如:

指令                       指令所在地址

MOV     R0,#1              ;#1给R0

SWI     0x98         ; 产生SWI中断,执行异常处理程序SoftwareInterrupt

ADD     R2,R1,R3        

;SWI异常处理子程序如下

SoftwareInterrupt

        CMP     R0, #6      ; if R0 < 6

        LDRLO   PC, [PC, R0, LSL #2] ; if R0 < 6,PC = PC + R0*4,else next

        MOVS    PC, LR

SwiFunction

        DCD     function0     ;0

        DCD     function1     ;1

        DCD     function2     ;2

        DCD     function3     ;3

        DCD     function4     ;4

        DCD     function5     ;5

Function0

    异常处理分支0代码

Function1

    异常处理分支1代码

function2

    异常处理分支2代码

function3

    异常处理分支3代码

function4

    异常处理分支4代码

function5

    异常处理分支5代码

    在ARM体系结构中,当正确读取了PC的值时,该值为当前指令地址值加8字节,也就是说,对于ARM指令集来说,读出的PC值指向当前指令的下两条指令的地址,本例中就是指向SwiFunction 表头DCD  function0 这个地址,在该地址中保存了异常处理子分支function0的入口地址。所以,当进入SWI异常处理子程序SoftwareInterrupt时,如果R0=0,执行LDRLO   PC, [PC, R0, LSL #2]语句后,PC的内容即为function0的入口地址,即程序跳转到了function0执行。在本例中,因为R0=1,所以,实际程序是跳转到了function1执行。R0左移2位(LDRLO   PC, [PC, R0, LSL #2]),即R0*4,是因为ARM指令是字(4个字节)对齐的DCD  function0等伪指令也是按4字节对齐的。

在本方法的实现中,实际指令中的24位立即数(immed_24域)被忽略了, 就是说immed_24域可以为任意合法的值。如在本例中,不一定使用SWI 0x98,还可以为SWI 0x00或者SWI 0x01等等,程序还是会进入SWI异常处理子程序SoftwareInterrupt,然后根据R0的内容跳转到相应的子分支。

__swi(0x00) void SwiHandle(int Handle);
#define IRQDisable()    SwiHandle(0)
#define IRQEnable()     SwiHandle(1)
#define FIQDisable()    SwiHandle(2)
#define FIQEnable()     SwiHandle(3)

__swi是ADS编译器的关键字,用它做前缀可以声明一个软中断调用,格式为:
__swi(功能号)   返回值  名称 (参数列表)
功能号:即软中断指令中的24位立即数,软中断号
名  称:即调用软中断时用于描述软中断的函数名称
参  数:软中断函数的参数,根据ATPCS规则,如果软中断函数有不超过4个参数时,通过R0~R3传递,超过4个参数时用堆栈来传递。

__swi(0x00) void SwiHandle1(int Handle)。其中0x00为软中断功能号(软中断号);软中断函数名称为SwiHandle1;只有一个参数,则使用R0来传递;函数没有返回值。紧接着这句代码的是定义了4个宏,分别表示禁能IRQ函数、使能IRQ函数、禁能FIQ函数、使能IFQ函数,其实调用的软中断函数是一样的,只是参数不同而已。例如在用户程序中调用“IRQEnable( );”时,处理器会产生软中断。位于启动代码中的那些是软中断处理函数,当发生软中断时,PC被强制指向0x00000008,这个地址中存放的是软中断异常的处理函数的地址,所以程序会跳转至标号“SoftwareInterrupt ”处执行。SoftwareInterrupt 函数的功能是判断R0的值(R0的值为软中断函数传递过来的参数)是否小于4,如果小于4则跳转至标号“SwiFunction”执行,如果不是则函数返回。SwiFunction函数是一个散转函数,它的功能是根据R0的值跳转至对应的函数处执行,即如果参数为1,则函数会跳转至IRQEnable处执行,将IRQ中断使能。

C代码中内嵌ARM汇编时,如果需要通过C函数调用SWI中断,通常的形式是:__SWI (<number>) Function(type, type, type);

在实际项目中,可能需要很多SWI处理函数,那么一种方法是通过<number>参数来实现针对不同的应用进行不同距离的跳转。但是这种办法的可读性不强,代码都是在ARM汇编部分,而且扩展性也不好。

一种比较好的解决方法是:通过函数的第一个参数,传递一个操作符,用来指示进行什么操作,使用C语言的宏定义,来隐藏这一层调用。这样就相当于将底层代码和用户代码隔离开,可读性很强。举例如下:

__swi(0) char __ReadCharacter (unsigned op);
__swi(0) void __WriteCharacter (unsigned op, char c);

其中op参数是用来指示是哪种操作的。再针对不同的操作码,使用宏定义:

#define ReadCharacter () __ReadCharacter (0);
#define WriteCharacter (c) __WriteCharacter (1, c);

这样,用户就可以像正常的C函数一样,调用这两个函数,而底层的中断调用就变得透明了。

0 0