ARM Cortex M4 SVC指令作用

(1)SVC指令:

摘自 http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0203ic/Cacdfeci.html

与更早版本的 ARM 处理器一样,有一条 SVC 指令可生成 SVC。 SVC 通常用于在操作系统上请求特权操作或访问系统资源。

SVC 指令中嵌入了一个数字,这个数字通常称为 SVC 编号。 在大多数 ARM 处理器上,此编号用于指示要请求的服务。在微控制器架构上,处理器在最初进入异常时,将参数寄存器保存到堆栈中。

在 SVC 处理程序执行第一个指令之前较晚处理的异常可能会损坏 R0 到 R3 中仍保存的参数副本。 这表示参数的堆栈副本必须由 SVC 处理程序使用。 还必须通过修改堆栈中的寄存器值将任何返回值传递回调用方。 为此,必须在 SVC 处理程序的开头实现一小段汇编代码。这样可以确定寄存器的保存位置,从指令中提取 SVC 编号,并将编号和参数指针传递到用 C 编写的处理程序主体中。

Example 6.21 是一个 SVC 处理程序示例。 这段代码测试处理器所设置的 EXC_RETURN 值,确定在调用 SVC 时,使用的是哪个堆栈指针。这对于重入 SVC 可能很有用,但在大多数系统上是不必要的,因为在典型系统设计中,只会从使用进程堆栈的用户代码中调用 SVC。在这种情况下,汇编代码可以包含单条 MSR 指令,后跟指向处理程序 C 程序体的尾调用跳转(B 指令)。

Example 6.21. SVC 处理程序示例

__asm void SVCHandler(void)
{
    IMPORT SVCHandler_main
    TST lr, #4
    ITE EQ
    MRSEQ R0, MSP
    MRSNE R0, PSP
    B SVCHandler_main
}
void SVCHandler_main(unsigned int * svc_args)
{
    unsigned int svc_number;
    /*
    * Stack contains:
    * R0, R1, R2, R3, R12, R14, the return address and xPSR
    * First argument (R0) is svc_args[0]
    */
    svc_number = ((char *)svc_args[6])[-2];
    switch(svc_number)
    {
        case SVC_00:
            /* Handle SVC 00 */
            break;
        case SVC_01:
            /* Handle SVC 01 */
            break;
        default:
            /* Unknown SVC */
            break;
    }
}


Example 6.22 演示如何为很多 SVC 进行不同声明。 __svc 是编译器关键字,该关键字将函数调用替换为包含指定编号的 SVC 指令。

Example 6.22. 在 C 代码中调用 SVC 的示例

#define SVC_00 0x00
#define SVC_01 0x01
void __svc(SVC_00) svc_zero(const char *string);
void __svc(SVC_01) svc_one(const char *string);
int call_system_func(void)
{
    svc_zero("String to pass to SVC handler zero");
    svc_one("String to pass to a different OS function");

 SVC 处理程序
发生异常时,异常处理程序可能需要确定处理器是在 ARM 状态还是在 Thumb 状态。

特别是 SVC 处理程序,更需要读取处理器状态。 通过检查 SPSR 的 T 位可确定处理器状态。 该位在 Thumb 状态时为 1,在 ARM 状态时为 0。

ARM 和 Thumb 指令集均有 SVC 指令。 在 Thumb 状态下调用 SVC 时,必须考虑以下情况:

指令的地址在 lr–2,而不在 lr–4。

该指令本身为 16 位,因而需要半字加载,请参阅Figure 6.3。

在 ARM 状态下,SVC 编号以 8 位存储,而不是 24 位。

Figure 6.3. Thumb SVC 指令
Example 6.8 演示处理 SVC 异常的 ARM 代码。 通过动态调用 SVC,可以增大 Thumb 状态下可访问的 SVC 编号的范围。
Example 6.8. SVC 处理程序
    PRESERVE8
    AREA SVC_Area, CODE, READONLY
    EXPORT SVC_Handler    IMPORT C_SVC_Handler
T_bit   EQU    0x20                    ; Thumb bit (5) of CPSR/SPSR.
SVC_Handler
    STMFD   sp!, {r0-r3, r12, lr}  ; Store registers
    MOV     r1, sp                 ; Set pointer to parameters
    MRS     r0, spsr               ; Get spsr
    STMFD   sp!, {r0, r3}          ; Store spsr onto stack and another
                                   ; register to maintain 8-byte-aligned stack
    TST     r0, #T_bit             ; Occurred in Thumb state?
    LDRNEH  r0, [lr,#-2]           ; Yes: Load halfword and...
    BICNE   r0, r0, #0xFF00        ; ...extract comment field
    LDREQ   r0, [lr,#-4]           ; No: Load word and...
    BICEQ   r0, r0, #0xFF000000    ; ...extract comment field

    ; r0 now contains SVC number
    ; r1 now contains pointer to stacked registers

    BL      C_SVC_Handler          ; Call main part of handler
    LDMFD   sp!, {r0, r3}          ; Get spsr from stack
    MSR     SPSR_cxsf, r0               ; Restore spsr
    LDMFD   sp!, {r0-r3, r12, pc}^ ; Restore registers and return
    END


确定要调用的 SVC
进入 SVC 处理程序后,必须确定要调用哪个 SVC。 此信息可存储在指令本身的 0-23 位,如Figure 6.4 所示,或将其传递给某个整数寄存器,通常为 R0-R3 中的一个。

Figure 6.4. ARM SVC 指令

顶层 SVC 处理程序可以相对于 LR 加载 SVC 指令 请用汇编语言、C/C++ 内联或嵌入式汇编器编写。
处理程序必须首先将导致异常的 SVC 指令加载到寄存器中。 此时,SVC LR 保存 SVC 指令的下一个指令的地址,这样 SVC 加载到了寄存器(本例中为 R0)中,代码如下:
    LDR R0, [lr,#-4]
然后,处理程序可检查注释字段位,以决定所需的操作。 通过清除操作码的前八位来提取 SVC 编号:

    BIC R0, R0, #0xFF000000
Example 6.9 演示如何用这些指令编写顶层 SVC 处理程序。 有关在 ARM 状态和 Thumb 状态下处理 SVC 指令的处理程序示例,请参阅Example 6.8。

Example 6.9. 顶层 SVC 处理程序

    PRESERVE8
    AREA TopLevelSVC, CODE, READONLY   ; Name this block of code.
    EXPORT     SVC_Handler
SVC_Handler
    PUSH       {R0-R12,lr}             ; Store registers.
    LDR        R0,[lr,#-4]             ; Calculate address of SVC instruction
                                       ; and load it into R0.
    BIC        R0,R0,#0xFF000000       ; Mask off top 8 bits of instruction
                                       ; to give SVC number.
    ;
    ; Use value in R0 to determine which SVC routine to execute.
    ;
    LDM         sp!, {R0-R12,pc}^      ; Restore registers and return.
    END

汇编语言编写的 SVC 处理程序
要调用请求的 SVC 编号的处理程序,最简单的方法是使用跳转表。 如果 R0 包含 SVC 编号,则Example 6.10 中的代码可以插入到Example 6.9 提供的顶层处理程序中,插入位置在 BIC 指令之后。

Example 6.10. SVC 跳转表

    AREA SVC_Area, CODE, READONLY
    PRESERVE8
    IMPORT SVCOutOfRange
    IMPORT MaxSVC
    CMP    R0,#MaxSVC          ; Range check
    LDRLS  pc, [pc,R0,LSL #2]
    B      SVCOutOfRange
SVCJumpTable
    DCD    SVCnum0
    DCD    SVCnum1
                               ; DCD for each of other SVC routines
SVCnum0                        ; SVC number 0 code
    B    EndofSVC
SVCnum1                        ; SVC number 1 code
    B    EndofSVC
                               ; Rest of SVC handling code
EndofSVC
                               ; Return execution to top level
                               ; SVC handler so as to restore
                               ; registers and return to program.
    END

C 语言和汇编语言编写的 SVC 处理程序
尽管顶层处理程序始终必须用 ARM 汇编语言编写,处理每个 SVC 的例程既可用汇编语言编写,也可用 C 语言编写。有关限制的说明,请参阅在超级用户模式下使用 SVC。

顶层处理程序使用 BL 指令可跳转到相应的 C 函数。 因为 SVC 编号是由汇编例程加载到 R0 中的,所以作为第一个参数传递给 C 函数。 举例来说,函数可以将此参数值用在 switch() 语句中,请参阅Example 6.11。

若要调用此 C 函数,可以向Example 6.9 中 SVC_Handler 例程添加下面的代码行:

    BL    C_SVC_Handler     ; Call C routine to handle the SVC
Example 6.11. C 函数中的 SVC 处理程序

void C_SVC_handler (unsigned number)
{
    switch (number)
    {
        case 0 :                 /* SVC number 0 code */
            ...
            break;
        case 1 :                 /* SVC number 1 code */
            ...
            break;
        ...
        default :                /* Unknown SVC - report error */
    }
}


超级用户模式堆栈空间可能是有限的,因此要避免使用需要大量堆栈空间的函数。

    MOV     R1, sp        ; Second parameter to C routine...
                          ; ...is pointer to register values.
    BL    C_SVC_Handler   ; Call C routine to handle the SVC.
用 C 语言编写的 SVC 处理程序可以传入和传出值,前提是顶层处理程序将堆栈指针值作为第二个参数(在 R1 中)传递给 C 函数,并且更新 C 函数以访问该值:

void C_SVC_handler(unsigned number, unsigned *reg)
现在,C 函数在主应用程序代码中遇到 SVC 指令时就可访问存储在寄存器中的值了,请参阅 Figure 6.5。 它可从其中读取:

    value_in_reg_0 = reg [0];
    value_in_reg_1 = reg [1];
    value_in_reg_2 = reg [2];
    value_in_reg_3 = reg [3];
也可向其中回写:

    reg [0] = updated_value_0;
    reg [1] = updated_value_1;
    reg [2] = updated_value_2;
    reg [3] = updated_value_3;
这使已更新的值写入到堆栈的相应位置,然后通过顶层处理程序恢复到寄存器中。

Figure 6.5. 访问超级用户模式堆栈
在超级用户模式下使用 SVC
执行 SVC 指令时:

处理器进入超级用户模式。

CPSR 存储到 SVC SPSR 中。

返回地址存储在 SVC LR 中,请参阅处理器对异常的响应。

如果处理器已经处在超级用户模式下,则会损坏 SVC LR 和 SPSR。

如果在超级用户模式下调用 SVC,则必须存储 SVC LR 和 SPSR,以确保 LR 和 SPSR 的原始值不会丢失。例如,如果某个特定 SVC 编号的处理程序例程调用另一个 SVC,则必须确保该处理程序例程将 SVC LR 和 SPSR 都存储在堆栈中。 这可确保处理程序的每一次调用都保存返回到调用它的 SVC 后面的指令所需要的信息。Example 6.12 演示如何实现这一点。

Example 6.12. SVC 处理程序

    AREA SVC_Area, CODE, READONLY
    PRESERVE8
    EXPORT SVC_Handler
    IMPORT C_SVC_Handler
T_bit EQU 0x20
SVC_Handler
    PUSH     {R0-R3,R12,lr}       ; Store registers.
    MOV      R1, sp               ; Set pointer to parameters.
    MRS      R0, SPSR             ; Get SPSR.
    PUSH     {R0,R3}              ; Store SPSR onto stack and another register to maintain
                                  ; 8-byte-aligned stack. Only required for nested SVCs.
    TST      R0,#0x20             ; Occurred in Thumb state?
    LDRHNE   R0,[lr,#-2]          ; Yes: load halfword and...
    BICNE    R0,R0,#0xFF00        ; ...extract comment field.
    LDREQ    R0,[lr,#-4]          ; No: load word and...
    BICEQ    R0,R0,#0xFF000000    ; ...extract comment field.
                                  ; R0 now contains SVC number
                                  ; R1 now contains pointer to stacked registers
    BL       C_SVC_Handler        ; Call C routine to handle the SVC.
    POP      {R0,R3}              ; Get SPSR from stack.
    MSR      SPSR_cf, R0          ; Restore SPSR.
    LDM      sp!, {R0-R3,R12,pc}^ ; Restore registers and return.
    END


用 C 和 C++ 编写的嵌套 SVC
可用 C 或 C++ 语言编写嵌套的 SVC。 由 ARM 编译器生成的代码根据需要存储和重新加载 lr_SVC。

从应用程序调用 SVC
可用汇编语言或 C/C++ 调用 SVC。

用汇编语言设置所有必需的寄存器值并发出相关的 SVC。 例如:

    MOV    R0, #65    ; load R0 with the value 65
    SVC    0x0        ; Call SVC 0x0 with parameter value in R0
几乎像所有的 ARM 指令那样,SVC 指令可被有条件地执行。

在 C/C++ 中,将 SVC 声明为一个 __SVC 函数并调用它。 例如:

    __svc(0) void my_svc(int);
    .
    .
    .
    my_svc(65);
这确保了 SVC 以内联方式进行编译,无需额外的调用开销,其前提是:

所有参数都只传入 R0-R3

所有结果都只返回到 R0-R3 中。

参数被传递给 SVC,如同 SVC 是一个真正的函数调用。 但是,如果有二到四个返回值,则必须告诉编译器返回值在一个结构中,并使用 __value_in_regs 命令。这是因为基于 struct 值的函数通常被视为一个 void 函数,它的第一个参数必须是存放结果结构的地址。

Example 6.13 和 Example 6.14 演示一个 SVC 处理程序,该处理程序提供 SVC 编号 0x0、0x1、0x2 和 0x3。 SVC 0x0 和 SVC 0x1 都采用两个整数参数并返回一个结果。SVC 0x2 采用四个参数并返回一个结果。SVC 0x3 采用四个参数并返回四个结果。 此示例位于示例目录(...\svc\main.c 和 ...\svc\svc.h)中。

Example 6.13. main.c

#include <stdio.h>
#include "svc.h"
unsigned *svc_vec = (unsigned *)0x08;
extern void SVC_Handler(void);
int main( void )
{
    int result1, result2;
    struct four_results res_3;
    Install_Handler( (unsigned) SVC_Handler, svc_vec );
    printf("result1 = multiply_two(2,4) = %d\n", result1 = multiply_two(2,4));
    printf("result2 = multiply_two(3,6) = %d\n", result2 = multiply_two(3,6));
    printf("add_two( result1, result2 ) = %d\n", add_two( result1, result2 ));
    printf("add_multiply_two(2,4,3,6) = %d\n", add_multiply_two(2,4,3,6));
    res_3 = many_operations( 12, 4, 3, 1 );
    printf("res_3.a = %d\n", res_3.a );
    printf("res_3.b = %d\n", res_3.b );
    printf("res_3.c = %d\n", res_3.c );
    printf("res_3.d = %d\n", res_3.d );
    return 0;
}


Example 6.14. svc.h

__svc(0) int multiply_two(int, int);
__svc(1) int add_two(int, int);
__svc(2) int add_multiply_two(int, int, int, int);
struct four_results
{
    int a;
    int b;
    int c;
    int d;
};
__svc(3) __value_in_regs struct four_results
    many_operations(int, int, int, int);


从应用程序动态调用 SVC
在某些情况下,需要调用直到运行时才会知道其编号的 SVC。 例如,当有很多相关操作可对同一目标执行,并且每个操作都有自己的 SVC 时,就会发生这种情况。 在这种情况下,前几节中介绍的方法不适用。

对此,有几种解决方法,例如:

通过 SVC 编号构建 SVC 指令,将它存储在某处,然后再执行该指令。

使用通用的 SVC(作为一个额外的参数)将一个代码作为对其参数执行的实际操作。 通用 SVC 对该操作进行解码并予以执行。

第二种机制可使用汇编语言将所需的操作数传递到寄存器(通常为 R0 或 R12)中来实现。然后可重新编写 SVC 处理程序,对相应寄存器中的值进行处理。

因为有些值必须用注释字段传递给 SVC,所以有可能将这两种方法结合起来使用。

例如,操作系统可能会只用一条 SVC 指令和一个寄存器来传递所需的操作数。 这使得其他 SVC 空间可用于应用程序特定的 SVC。 在一个特定的应用程序中,如果从指令提取操作数的开销太大,则可使用这个方法。 ARM 和 Thumb 半主机指令就是这样实现的。

Example 6.15 演示如何使用 __svc 将 C 函数调用映射到半主机调用。 该示例是根据示例目录中的 retarget.c 编写的,该文件路径为 ...\emb_sw_dev\source\retarget.c。

Example 6.15. 将 C 函数映射到半主机调用

#ifdef __thumb
/* Thumb Semihosting */
#define SemiSVC 0xAB
#else
/* ARM Semihosting */
#define SemiSVC 0x123456
#endif
/* Semihosting call to write a character */
__svc(SemiSVC) void Semihosting(unsigned op, char *c);
#define WriteC(c) Semihosting (0x3,c)
void write_a_character(int ch)
{
    char tempch = ch;
    WriteC( &tempch );
}


编译器含有一个机制,支持使用 R12 来传递所需运算的值。 在 AAPCS 下,R12 为 ip 寄存器,并且专用于函数调用。其他时间内可将其用作暂存寄存器。 通用 SVC 的参数被传递到 R0-R3 寄存器中,如前面所述,还可以选择在 R0-R3 中返回值,请参阅 从应用程序调用 SVC。 在 R12 中传递的操作数可以是通用 SVC 调用的 SVC 的编号。 但这不是必需的。

Example 6.16 演示使用通用或间接 SVC 的 C 程序段。

Example 6.16. 使用间接 SVC

__svc_indirect(0x80)
    unsigned SVC_ManipulateObject(unsigned operationNumber,
                                  unsigned object,unsigned parameter);
unsigned DoSelectedManipulation(unsigned object,
                                unsigned parameter, unsigned operation)
{ return SVC_ManipulateObject(operation, object, parameter);
}


它生成以下代码:

DoSelectedManipulation
        PUSH     {R4,lr}
        MOV      R12,R2
        SVC      #0x80
        POP      {R4,pc}
        END
还可使用 __svc 机制从 C 中传递 R0 中的 SVC 编号。 例如,如果将 SVC 0x0 用作通用 SVC,操作 0 为字符读,操作 1 为字符写,则可以进行如下设置:

__svc (0) char __ReadCharacter (unsigned op);
__svc (0) void __WriteCharacter (unsigned op, char c);
可通过如下定义使其具有更好的可读性风格:

#define ReadCharacter () __ReadCharacter (0);
#define WriteCharacter (c) __WriteCharacter (1, c);
但是,如果以这种方式使用 R0,则仅有三个寄存器可用于向 SVC 传递参数。通常,在不得不将除 R0-R3 之外的更多参数传递给子例程时,可通过使用堆栈来完成但是,SVC 处理程序不容易访问堆栈参数,因为这些参数通常在用户模式堆栈中,而不是在 SVC 处理程序使用的超级用户模式堆栈中。

作为另一种选择,其中一个寄存器(通常是 R1)可用来指向存储其他参数的内存块。
  • 6
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ARM Cortex-M3与Cortex-M4权威指南高清版 姚文祥、吴常玉、曹孟娟、王丽红编写的《ARM Cortex-M3与Cortex-M4权威指南(第3版)》经过了全 新修订:增加了ARM CortexM4l处理器的信息;对 ARM cortex一M3处理器的讲解进行了*新;对于ARM Cortex—M3和ARM CortexM4进行了比较,方便其 他多种处理器架构移植到ARMCortex—M3和ARM cortexM4。本版的其他修订细节如下: 新增了论述。DSP特性和CMSIS一DSP软件库的两 章内容,介绍了DSP的基础知识以及如何编写CortexM4 的DSP软件,其中包括使用CMSIS—DsP库的实例 以及cortexM4的DsP陛能方面的知识。   新增了介绍cortexM4浮点单元及其使用的一章 内容。   新增了介绍嵌入式操作系统的使用(基于CMSIs— RTOS)以及支持嵌入式操作系统的处理器特性的一章 内容。   多种调试技术以及疑难解答。   从其他处理器进行软件移植的内容。   此外,本书介绍了ARM架构的背景知识以及指令 集、中断处理等处理器特性,并描述了如何设置并利 用存储器保护单元(MPu)等可用的高级特性。书中论 述Keil MDK、IAR EWARM、gcc以及CooCoxCoIDE工具 入门的章节可以给初学者在编写程序代码方面提供一 些帮助,其中也包括一些重要的软件开发问题,比如 低功耗特性的使用、信息输入/输出的处理、汇编和 c语言的混合编程及其他高级技术话题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值