Cortex-M3 / M4 SVC Handler

http://www.coactionos.com/embedded-design/133-effective-use-of-arm-cortex-m3-svcall.html

The ARM Cortex-M3 service call (SVCall) can be a tricky feature to integrate in to your system software.
It is useful for two things:

  • Allowing a piece of code to execute without interruption
  • Jumping to privileged mode from unprivileged mode]

SVCall Introduction

The SVCall (contraction of service call) is a software triggered interrupt. It is useful for several reasons.
First, depending on interrupt priorities, the handler can be uninterruptible by one interrupt but interruptible by another.
For example, if you have a piece of code that should not be interrupted by the timer but can be interrupted by the UART,
you can set the interrupt priorities on the ARM Cortex-M3 in such a fashion to have the SVCall interrupt priority
be higher than the timer but lower than the UART.
Second, if you are using the MPU or privileged mode on the ARM Cortex-M3,
the SVCall provides the code executing in unprivileged mode a way to access privileged resources.

The SVC instruction invokes the service call interrupt.
The bottom 8-bits of the SVC instruction can be set to any value and then interpreted by the interrupt handler.
This essentially allows the user a way to execute 256 different types of service calls.
Alternatively, parameters can be placed on the stack and then extracted by the interrupt handler
to provide unlimited service calls with up to three parameters.

The sample code below shows how to do this using two parameters.

SVCall Sample Code

To effectively use the service call interrupt, we pass two arguments to a function
which immediately invokes the SVC instruction.
The arguments are a pointer to a function to execute in privileged mode and a pointer to a data structure
that the function can use to read/write data in the caller's context.

The following code shows the prototype and body of the function.

//we need to decrease the optimization so the the compiler
//does not ignore func and args
void service_call(void (*func)(void*), void* args) __attribute__((optimize("1"));
 
void service_call(void (*func)(void*), void* args){
     //by convention func is in r0 and args is in r1
     asm volatile("svc 0");
}

When SVC is executed, the NVIC immediately stacks various registers including r0 and r1 and
then executes the interrupt handler. The interrupt handler then needs to grab the values of r0 and r1from the stack.
The value in r0 is the function pointer while r1 is a pointer to some data in the caller's context.
The r0 value is type casted as a function and executed with a single argument, the value of r1.

typedef void (*svcall_t)(void*);
 
void svcall_handler(void){
  register uint32_t * frame;
  register svcall_t call;
  register void * args;
  asm volatile ("MRS %0, psp\n\t" : "=r" (frame) ); //assumes PSP in use when service_call() invoked
  call = (svcall_t)frame[0];
  args = (void*)(frame[1]);
  call(args);
}

Finally, to make a privileged call: 

void my_priv_func(void * data){
    int * my_int;
    my_int = data;
    if (*my_int == 10 ){
        *my_int = 0;
    }
}
 
int main(void){
    int var;
    var = 10;
    service_call(my_priv_func, &var); //executes my_priv_func() in interrupt mode
    if( var == 0 ){
        //if this is true, that means everything worked
    }
    return 0;
}

Conclusion

This very simple code sample provides a powerful and effective mechanism for using the ARM Cortex-CM3 SVCall (service call) interrupt.
It allows an arbitrary function to be executed uninterrupted in privileged mode.
The code is both re-entrant and thread-safe (as long as the context-switch can't interrupt the service call handler).

 

http://falstaff.agner.ch/2013/02/18/cortex-m3-supervisor-call-svc-using-gcc/

The Cortex-M3 has a new assembler instruction SVC to call the supervisor (usually the operating system).
The ARM7TDMI used to call this interrupt SWI, but since this interrupt works differently on Cortex-M3,
ARM renamed the instruction to make sure people recognize the difference and implement those calls correctly.
The machine opcode however is still the same (bits 0-23 are user defined, bits 24-27 are ones).

On the Cortex-M3, other interrupts can interrupt the processor during state saving of the SVC interrupt
(late arrival interrupt handling).
Those late arriving interrupts most certainly leave the registers corrupted after execution.
Therefor we cannot read the parameters form registers r0 to r4 directly as we could on the ARM7TDMI using SWI interrupts.
Fortunately, the Cortex-M3 saves all registers used in standard C procedure call specification (ABI) on the stack.
So the SVC handler can get the parameters directly from the stack. 

 

GCC doesn’t have a built-in way to create SVC call function defines as IAR or RealView do
(they support prototypes decorated with “#pragma _swi”, or “__svc”, respectively).
But such calls can easily be created by using normal C functions and some inline assembler.
We can use a standard C-function to let the compiler generate a standard C call.
This C function calls SVC, which triggers the SVC interrupt.
The interrupt handler saves the current registers to the stack and
calls the corresponding handler (sv_call_handler in our case).
We get the pointer to the stack used by the caller in assembler.
We can then use this pointer to extract the arguments of the original call
from the saved stack frame and can handle the supervisor call.

/*
 * SVC sample for GCC-Toolchain on Cortex-M3/M4 or M4F
 */
 
/*
 * Inline assembler helper directive: call SVC with the given immediate
 */
#define svc(code) asm volatile ("svc %[immediate]"::[immediate] "I" (code))
 
#define SVC_WRITE_DATA 1
/*
 * Handler function definition, same parameters as the SVC function below
 */
void sv_call_write_data_handler(char *string, int length);
 
/*
 * Use a normal C function, the compiler will make sure that this is going
 * to be called using the standard C ABI which ends in a correct stack
 * frame for our SVC call
 */
__attribute__ ((noinline)) void sv_call_write_data(char *string, int length)
{
    svc(SVC_WRITE_DATA);
}
 
/*
 * SVC handler
 * In this function svc_args points to the stack frame of the SVC caller
 * function. Up to four 32-Bit sized arguments can be mapped easily:
 * The first argument (r0) is in svc_args[0],
 * The second argument (r1) in svc_args[1] and so on..
 */
void sv_call_handler_main(unsigned int *svc_args)
{
    unsigned int svc_number;
 
    /*
     * We can extract the SVC number from the SVC instruction. svc_args[6]
     * points to the program counter (the code executed just before the svc
     * call). We need to add an offset of -2 to get to the upper byte of
     * the SVC instruction (the immediate value).
     */
    svc_number = ((char *)svc_args[6])[-2];
    switch(svc_number)
    {
        case SVC_WRITE_DATA:
            /* Handle SVC write data */
            sv_call_write_data_handler((const char *)svc_args[0],
                                       (int)svc_args[1]);
            break;
...
        default:
            /* Unknown SVC */
            break;
    }
}
 
/*
 * SVC Handler entry, put a pointer to this function into the vector table
 */
void __attribute__ (( naked )) sv_call_handler(void)
{
    /*
     * Get the pointer to the stack frame which was saved before the SVC
     * call and use it as first parameter for the C-function (r0)
     * All relevant registers (r0 to r3, r12 (scratch register), r14 or lr
     * (link register), r15 or pc (programm counter) and xPSR (program
     * status register) are saved by hardware.
     */
    asm volatile(
        "tst lr, #4\t\n" /* Check EXC_RETURN[2] */
        "ite eq\t\n"
        "mrseq r0, msp\t\n"
        "mrsne r0, psp\t\n"
        "b %[sv_call_handler_main]\t\n"
        : /* no output */
        : [sv_call_handler_main] "i" (sv_call_handler_main) /* input */
        : "r0" /* clobber */
    );
}

 

 You can call the sv_call_write_data function from your code, which will execute the corresponding handler:

void main(void)
{
    printf("Going to call the supervisor.\r\n");
    sv_call_write_data("Hello World!", 12);
}
 
void sv_call_write_data_handler(char *string, int length)
{
    printf("Supervisor call \"%s\", length %d.\r\n", string, length);
}

 The output looks like this:

Going to call the supervisor.
Supervisor call "Hello World!", length 12.

Lets make this example a bit more interesting.

This time with return values and 64-bit arguments.
To address the 64-bit argument I added a 64-bit pointer to the stack frame (svc_args_ll).
For return values to work, we need to alter the stack frame in memory directly.
Since r0 (and r1 in the 64-bit case) are used for return values,
we can simply write our value to svc_args[0] (svc_args_ll[0] for 64-bit return values respectively).

/*
 * SVC Handler entry, put a pointer to this function into the vector table
 */
void __attribute__ (( naked )) sv_call_handler(void)
{
    /*
     * Get the pointer to the stack frame which was saved before the SVC
     * call and use it as first parameter for the C-function (r0)
     * All relevant registers (r0 to r3, r12 (scratch register), r14 or lr
     * (link register), r15 or pc (programm counter) and xPSR (program
     * status register) are saved by hardware.
     */
    asm volatile(
        "tst lr, #4\t\n" /* Check EXC_RETURN[2] */
        "ite eq\t\n"
        "mrseq r0, msp\t\n"
        "mrsne r0, psp\t\n"
        "b %[sv_call_handler_main]\t\n"
        : /* no output */
        : [sv_call_handler_main] "i" (sv_call_handler_main) /* input */
        : "r0" /* clobber */
    );
}

 

#define SVC_READ_DATA 2
/*
 * Use GCC pragma to suppress warning about unreturned value...
 */
#pragma GCC diagnostic ignored "-Wreturn-type"
__attribute__ ((noinline)) unsigned long long sv_call_read_data(unsigned long long input)
{
  svc(SVC_READ_DATA);
}
 
unsigned long long sv_call_read_data_handler(unsigned long long input);
 
void sv_call_handler_main(unsigned int *svc_args)
{
    unsigned long long *svc_args_ll = (unsigned long long *)svc_args; // R0 R1
...
        case SVC_READ_DATA:
            /* Handle SVC read data */
            svc_args_ll[0] = sv_call_read_data_handler(svc_args_ll[0]);
...
} // R0 R1 be changed in stack
void main(void)
{
    unsigned long long data;
    printf("Going to call the supervisor.\r\n");
    data = sv_call_read_data(0xc0ffee01c0ffee02ull);
    printf("Read data: 0x%llx\r\n", data);
}
 
unsigned long long sv_call_read_data_handler(unsigned long long input)
{
   return input + 0x20;
}

Your console should show the altered 64-bit input which was returned to the caller using a 64-bit return value:

Going to call the supervisor.
 Read data: 0xc0ffee01c0ffee22

Note 1: I tested those code on Cortex-M4F.
Still I cannot provide any warranty on that code (and so on and so fourth…)

Note 2: The compiler might generate a prologue inside the function sv_call_write_data (saving registers to stack).
This is unnecessary, since the SVC interrupt will save all registers anyway.
But when compiled with optimizations (tested with -O2), this stack saving will be omitted.

Note 3: I tried to combine the functions sv_call_handler and sv_call_handler_main
in one function but it didn’t worked out well. I think there was a problem with the link register when doing so:
The naked attribute omits the prologue in sv_call_handler, the link register is then lost after the first function call.
But when using a simple branch and a second function, GCC generates a correct prologue for the second function,
which returns then correctly to the SVC caller.

Note 4: On ARM7TDMI the GCC attribute “__attribute__ ((interrupt(“SWI”)))” generates code
which stores the registers on the stack (in software right on interrupt entry).
So all relevant registers then ends in a similar stack frame as it does on Cortex-M3 (regarding registers r0 to r3).

Update 21.02.2013: Added noinline, this makes sure the function call is eliminated by the compiler

#define SVC_0 0
#define SVC_1 1

void SVC_Handler( void )
{
  asm("TST LR, #4");
  asm("ITE EQ");
  asm("MRSEQ R0, MSP");
  asm("MRSNE R0, PSP");
  asm("B SVC_Handler_main");
}

void Dummy0( void )
{
}

void Dummy1( void )
{
}

void Dummy2( void )
{
}

void SVC_Demo( void )
{
  asm("SVC #0");
  Dummy0();   // <---- PC, skipped
  Dummy1();
  asm("SVC #1");
  Dummy0();   // <---- PC, skipped
  Dummy1();   // <-------- skipped
  Dummy2();   //
}

void SVC_0_Handler( unsigned int * svc_args )
{
  svc_args[6] += 4; // skip Dummy0()
}

void SVC_1_Handler( unsigned int * svc_args )
{
  svc_args[6] += 8; // skip Dummy0(), Dummy1()
}

// xSP : R0 R1 R2 R3 R12 R14 PC xPSR
//       |svc_args[0]        |svc_args[6]
//                      00 DF|XX XX XX XX <-- code will be executed after reti
//                      -2 -1|0  1  2  3
//                           |
void SVC_Handler_main( unsigned int * svc_args )  // R0 = ( PSP or MSP )
{
  /*
   * Stack contains:
   * r0, r1, r2, r3, r12, r14, the return address and xPSR
   * First argument (r0) is svc_args[0]
   */
    unsigned int svc_number;
    svc_number = ((char *)svc_args[6])[-2];
  // ( SVC #0 ) 00 DF : XX XX XX XX <-- code will be executed after reti
  // ( SVC #1 ) 01 DF : \______________ PC : (char *)svc_args[6])
  switch(svc_number)
  {
    case SVC_0:
      SVC_0_Handler( svc_args );
      break;

    case SVC_1:
      SVC_1_Handler( svc_args );
      break;

    default:
    break;
  }
}

 

posted @ 2013-04-30 23:05  IAmAProgrammer  阅读(6034)  评论(0编辑  收藏  举报