I would like to know the proper way of writing SVC calls on an ARM-based microcontroller.
My understanding so far is that ARM has an exception vector table, meaning that the first instructions in any program have to be branches to the appropriate handlers:
RESET ;Handles reset
UNDEFINED ;Undefined instructions
SVC BL SVC_Entry
PRE_ABORT ;Prefetch abort
DAT_ABORT ;Data abort
Then, every time an SVC instruction is ran, the mode is switched to supervisor, the number provided with the SVC is stored in R0 and the program would branch to an appropriate handler:
;== Handling SVC calls ========================================================
Max_SVC EQU 1
SVC_Entry CMP R0, #Max_SVC ;Check upper limit
BHI SVC_end ;Does nothing if unknown
ADD R0, PC, R0, LSL #2 ;Calculate table address
LDR PC, [R0, #0]
Jump_table DEFW SVC_0 ;Halt
DEFW SVC_1 ;Print string
;== SVC calls ================================================================
SVC_1 B SVC_end
SVC_end MOVS PC, LR ;Exiting
So, if we have these instructions:
ADR R1, string ;R1 points to the string
SVC 1 ;SVC_1 handles the printing
The program would have to switch to supervisor mode, have the number "1" stored in R0, and following the jump table branch to SVC_1, run the code and switch back to user mode.
Is this correct? Am I doing it right?
The problems I have so far is that my compiler says "operator expected" for this line:
SVC BL SVC_Entry
Information on this topic is hard to find on the internet and I just want to know how to properly use SVC calls on an ARM microcontroller.
Thank you very much.
EDIT: The underlying processor is an ARM9 clocking at around 240 MHz. This lives in an AT91 microcontroller. The lab board on which it resides has been modified to fit my University's needs.
The code is loaded on the board using a custom-made program through a serial port. The program also allows debugging.
As said, don't use BL to jump to the SVC entry, use B. Have a routine first to decide determine the SVC number. (Mine is called SVC_dispatcher). What university are you at? I'll try and explain this thoroughly, making no assumptions on how much you do or don't know. I've put in the correct terms so you can google it for more info if my comments are unclear or you want more depth. I'm unsure of the labelling methods with the colon, I'm used to an older instruction set.
Good luck
SVC_dispatcher
PUSH {LR} ; always save your LR
LDR R14, [LR, #-4] ; its been stacked, so we can it (LR is R14)
; the link register is the line after the SVC instruction
; above, we load the instruction that is one before the
; link register (#-4 preindexed load) means instruction (SVC 1) into R14.
; Use bit clear to remove the mnemonic from the 32 bit instruction,
; leaving the data (1)
BIC R14, R14, #&FF000000
SVC_entry
CMP R14, #Max_SVC
BHI SVC_unknown
ADR R1, Jump_Table ; use this format, never add to the PC
LDR PC, [R1, R14, LSL #2]
; Beware: PC can be changed by IRQs **AT ANY TIME**
Jump_Table ; you know the drill here
DEFW SVC_0
DEFW SVC_1
SVC_0 B SVC_end
SVC_1 BL printString ; consider stacking registers that printString
B SVC_end ; will corrupt
SVC_end POP {LR} ; restore link register
MOV PC, LR
Just complementing Yoker's answer above for clarifications:
As the example of Yoker's answer shows, traditionally the number from the SVC instruction is NOT stored in the R0 (as you stated in your question), you should retrieve it from the code, reading the immediate value directly from the svc instruction.
To do that, you need to get the pointer to the instruction just before the return address, for example, if you have these instructions:
0x800010: ADR R1, string ;R1 points to the string
0x800014: SVC 1 ;SVC_1 handles the printing
0x800018: ADD R1, R2 ;The next instruction after the SVC where LR will point to
The return address will be LR=0x800018, then we can retrieve the address of the SVC instruction LR-4=0x800014, read that address content (which is the SVC 1 instruction) and get only the first byte of it (we ignore the svc opcode and only get the immediate value).
So, in the Yoker's example, we can see the following instructions doing exactly that:
LDR R14, [LR, #-4]
BIC R14, R14, #&FF000000
Here is another example using C code in a cortex M0 for instance (thumb instruction):
void SVC_Handler(void)
{
// Get stack pointer, assuming we the thread that generated
// the svc call was using the psp stack instead of msp
unsigned int *stack;
asm volatile ("MRS %0, psp\n\t" : "=rm" (stack) );
// Stack frame contains:
// r0, r1, r2, r3, r12, r14, the return address and xPSR
// - Stacked R0 = stack[0]
// - Stacked R1 = stack[1]
// - Stacked R2 = stack[2]
// - Stacked R3 = stack[3]
// - Stacked R12 = stack[4]
// - Stacked LR = stack[5]
// - Stacked PC = stack[6]
// - Stacked xPSR= stack[7]
// Thumb instructions have 2 bytes instead of 4, then get
// the first byte of the instruction right before the
// instruction pointed by the stacked PC
unsigned int svc_number = ((char *)svc_args[6])[-2];
switch(svc_number) {
case 0:
...
break;
case 1:
...
break;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With