There are two parts of the handling of syscalls.
- From starting of the syscall and up to making
processor exception to enter into previllege mode.
For ARM, this mode would be SVC(Supervisor)mode.
This part involves filling the general purpose registers with correct system call no, arguments to be passed in the syscall,
and triggering the processor's state to privilege mode.
- Second(and a bit more complicated as compared to the first one) part involves processing the syscall in privilege mode, and returning from the privilege mode.
2a. In the exception table of ARM the control is directed by the exception vector(SVC in this case) to common handler for the exceptions.
Following is the exception vector for SVC:
[File: arch\arm\kernel\entry-armv.S]
W(ldr) pc, __vectors_start + 0x1000
Following is the entry to common handler of the exceptions:
[File: arch\arm\kernel\entry-armv.S]
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
2b. At the finish of this common handler, r0,lr and spsr are saved on the on the stack at addresses of [SP], [SP+4] and [SP+8] respectively.
[File: arch\arm\kernel\entry-armv.S]
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
2c. Then, the common handler transfers/branches the controller to SVC specific handler:
[File: arch\arm\kernel\entry-armv.S]
mrs lr, spsr
...
and lr, lr, #0x0f
...
ARM( ldr lr, [pc, lr, lsl #2] )
movs pc, lr @ branch to handler in SVC mode
...
.word vector_swi
At the end of the common handler, the controller is transferred to SVC handler vector_swi.
2d. Now let's see how syscall is processed in SVC handler.
Context of the caller is saved as following after advancing SP at new FRAME in follwing order:
[File: arch\arm\kernel\entry-common.S]
ENTRY(vector_swi)
...
(r0-r12,sp,lr)_usr_mode, lr_exp (exp=svc in this case)
This context will be used to return from the SVC mode to user mode by storing r0-r12,sp,lr,pc by the values of this context.
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0 - r12
ARM( add r8, sp, #S_PC )
ARM( stmdb r8, {sp, lr}^ ) @ Calling sp, lr
...
mrs r8, spsr @ called from non-FIQ mode, so ok.
str lr, [sp, #S_PC] @ Save calling PC
2e. syscall no. is fetched according to procedure calling standards, and utlimately it's stored in scno(r7 register).
[File: arch\arm\kernel\entry-common.S]
elif defined(CONFIG_AEABI)
/*
* Pure EABI user space always put syscall number into scno (r7).
*/
#elif defined(CONFIG_ARM_THUMB)
/* Legacy ABI only, possibly thumb mode. */
tst r8, #PSR_T_BIT @ this is SPSR from save_user_regs
addne scno, r7, #__NR_SYSCALL_BASE @ put OS number in
...
2f. Then, address of the sys_call_table is stored in tbl register(r8).
[File: arch\arm\kernel\entry-common.S]
adr tbl, sys_call_table @ load syscall table pointer
sys_call_table contains a list of syscall according to their nos.
File call.S contains the list.
[File: arch\arm\kernel\entry-common.S]
ENTRY(sys_call_table)
#include "calls.S"
#undef ABI
...
2g. Then the correct syscall handler will be involved by branching to the sys_call_table with the offset of syscall no.
The reurn address is set at label "ret_fast_syscall".
[File: arch\arm\kernel\entry-common.S]
adr lr, BSYM(ret_fast_syscall) @ return address
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
...
2h. Then, the user-mode registers are restored in such a way that (r0-r12,sp,lr,pc)_usr_mode are stored from the valued stored in step 2d.
This is done in macro "restore_user_regs".
[File: arch\arm\kernel\entry-common.S]
ret_fast_syscall:
...
restore_user_regs fast = 1, offset = S_OFF
...
[File: arch\arm\kernel\entry-header.S]
macro restore_user_regs, fast = 0, offset = 0
mov r2, sp
...
.if \fast
ldmdb sp, {r1 - r12} @ get calling r1 - r12
.else
ldmdb sp, {r0 - r12} @ get calling r0 - r12
.endif
add sp, sp, #S_FRAME_SIZE - S_SP
movs pc, lr @ return & move spsr_svc into cpsr
.endm