in this post, I’ll try to explain little about supervision call function on the arm architecture Armv6m.
where is SVCall function used?
the supervision call function usually used in operating system where an unpriviliged process tries to call system function to perform some task. you may relate it to Syscalls in linux where a proccess calls kernel function.
the SVC function call will lead the cpu to enter exception mode which is priviliged. the exception being called is in NVIC vector table as following:
the cpu core has the following general purpose registers:
- R0, R1, R2, R3,…,R11
- R12 also known as SP (stack pointer)
- R13 also known as LR (Link register) / used to store return address when calling functions in C/C++
- R14 is also known as PC (program counter) / loaded with reset handler on reset event
in order to manipulate the stack we have to understand first how exception entry and exit work in the architecture
in the exception entry the CPU automatically pushes the following registers to the stack in order
- XPSR
- PC / Return address
- LR
- R12
- R3
- R2
- R1
- R0
on exception exit, the cpu restores the registers values and returns to the previous instruction it was trying to execute after popping the values from the stack.
you also have to know that C compilers make function calls with the parameters of the function on R0, R1, R2, etc as the values allready pushed to stack before branching ..
and the function return value on R0.
/**
* \def SVC(code)
* calls svc function with code \param code
* */
#define SVC(code) asm volatile (“SVC %0“ : : “I“ (code))
/**
* \brief SVCall_Handler is SVC exception supervisor call instruction
* \todo the SVC number is pushed in the stack probably
* */
void SVCall_Handler(void){
__asm__ __volatile__(“MRS R0, msp \t\n“
“B svc_main_c”);
}
/**
* \brief svc function test
* */
void svc_main_c(uint32_t* svc_args){
//uint32_t svc_number = ((char*) svc_args[6])[-2];
svc_args[0] = svc_args[0] + svc_args[1];
}
int main(void)
{
board_init();
// 48MHz / 1000 = 48,000 = 1ms
SysTick_Config(RTOS_CPU_CLOCK/1000);
__enable_irq();
__asm__ (“mov r0, #5“);
__asm__ (“mov r1, #7“);
SVC(0);
uint32_t tmp;
__asm__ (“mov %0, r0“: “=r“ (tmp) /*outputs*/);
if(tmp == 12){
blinkFast();
}
//stay in
while(1){
}
}
Note: as can be seen in the code snippet above the SVC instruction is a macro that looks like C function and compiler will just convert it to assembly instruction SVC #imm
code execution as follows:
– in main:
we push #5 to R0
we push #7 to R1
we call SVC instruction .. the processor goes into exception entry and pushes the registers to the stack.
when the processor execute the SVC instruction it goes to exception entry which pushes the registers to the stack and then jump to the handler function SVCall_Handler
SVCall_handler takes current stack pointer (msp) and pushes it to the register R0 and then branches to the function svc_main_c
the way svc_main_c is defined with the pointer parameter.. remember (C parameter passing is on R0 thats why we pushed the stack pointer to R0 then we branched)
now svc_args actually holding the stack pointer address which points to R0 before the exception handler being executed.
we know the first argument is R0 the second is R1
the function now will just add them and return the value on R0 by the code:
svc_args[0] = svc_args[0] + svc_args[1];
and finally the function exits and goes back to the original code .. of course with the exception exit procedure which pops the stack to the registers in the same order it came from in the exception entry
now in main code R0 holds the added value 5+7 which is 12
i can just convert the to C variable by executing the following:
uint32_t tmp;
__asm__ (“mov %0, r0“: “=r“ (tmp) /*outputs*/);
and now since we know its 12 ..
we can happily go to our blinking program!
if(tmp == 12){
blinkFast();
}
happy stack manipulations!
Thank you!!1