diff --git a/include/l4/generic/tcb.h b/include/l4/generic/tcb.h index 575e740..82fa4b3 100644 --- a/include/l4/generic/tcb.h +++ b/include/l4/generic/tcb.h @@ -57,8 +57,11 @@ struct ktcb { /* User context */ task_context_t context; - /* Reference to syscall saved context */ - syscall_args_t *syscall_regs; + /* + * Reference to the context on stack + * saved at the beginning of a syscall trap. + */ + syscall_context_t *syscall_regs; /* Runqueue related */ struct list_head rq_list; diff --git a/include/l4/glue/arm/syscall.h b/include/l4/glue/arm/syscall.h index a1ff969..448a98c 100644 --- a/include/l4/glue/arm/syscall.h +++ b/include/l4/glue/arm/syscall.h @@ -18,7 +18,12 @@ extern unsigned int __syscall_page_start; -typedef struct syscall_args { +/* + * This structure is saved on the kernel stack + * just after entering a system call exception. + */ +typedef struct syscall_context { + u32 spsr u32 r0; u32 r1; u32 r2; @@ -28,7 +33,9 @@ typedef struct syscall_args { u32 r6; /* MR3 */ u32 r7; /* MR4 */ u32 r8; /* MR5 */ -} syscall_args_t; + u32 sp_usr; + u32 lr_usr; +} __attribute__((__packed__)) syscall_context_t; typedef struct msg_regs { u32 mr0; diff --git a/src/api/ipc.c b/src/api/ipc.c index 849c755..ebd35a9 100644 --- a/src/api/ipc.c +++ b/src/api/ipc.c @@ -47,7 +47,7 @@ int ipc_msg_copy(struct ktcb *to, struct ktcb *from) return 0; } -int sys_ipc_control(struct syscall_args *regs) +int sys_ipc_control(syscall_context_t *regs) { return -ENOSYS; } @@ -212,7 +212,7 @@ static inline int __sys_ipc(l4id_t to, l4id_t from, unsigned int ipc_type) return ret; } -void printk_sysregs(struct syscall_args *regs) +void printk_sysregs(syscall_context_t *regs) { printk("System call registers for tid: %d\n", current->tid); printk("R0: %x\n", regs->r0); @@ -234,7 +234,7 @@ void printk_sysregs(struct syscall_args *regs) * - Can propagate messages from third party threads. * - A thread can both send and receive on the same call. */ -int sys_ipc(struct syscall_args *regs) +int sys_ipc(syscall_context_t *regs) { l4id_t to = (l4id_t)regs->r0; l4id_t from = (l4id_t)regs->r1; diff --git a/src/api/syscall.c b/src/api/syscall.c index 97bb358..2a41f82 100644 --- a/src/api/syscall.c +++ b/src/api/syscall.c @@ -17,7 +17,7 @@ #include INC_API(syscall.h) #include INC_ARCH(exception.h) -int sys_exchange_registers(struct syscall_args *regs) +int sys_exchange_registers(syscall_context_t *regs) { struct ktcb *task; unsigned int pc = regs->r0; @@ -43,7 +43,7 @@ found: return 0; } -int sys_schedule(struct syscall_args *regs) +int sys_schedule(syscall_context_t *regs) { printk("(SVC) %s called. Tid (%d)\n", __FUNCTION__, current->tid); return 0; @@ -54,7 +54,7 @@ int sys_space_control(struct syscall_args *regs) return -ENOSYS; } -int sys_getid(struct syscall_args *regs) +int sys_getid(syscall_context_t *regs) { struct task_ids *ids = (struct task_ids *)regs->r0; struct ktcb *this = current; @@ -83,7 +83,7 @@ int validate_granted_pages(unsigned long pfn, int npages) * this memory is used for thread creation and memory mapping, (e.g. new * page tables, page middle directories, per-task kernel stack etc.) */ -int sys_kmem_control(struct syscall_args *regs) +int sys_kmem_control(syscall_context_t *regs) { unsigned long pfn = (unsigned long)regs->r0; int npages = (int)regs->r1; diff --git a/src/api/thread.c b/src/api/thread.c index 89c53e9..e58184c 100644 --- a/src/api/thread.c +++ b/src/api/thread.c @@ -12,7 +12,7 @@ #include #include INC_ARCH(mm.h) -int sys_thread_switch(struct syscall_args *regs) +int sys_thread_switch(syscall_context_t *regs) { sched_yield(); return 0; @@ -60,10 +60,56 @@ int thread_start(struct task_ids *ids) return -EINVAL; } -int setup_new_ktcb(struct ktcb *new, struct ktcb *orig) +/* + * Copies the pre-syscall context of original thread into the kernel + * stack of new thread. Modifies new thread's context registers so that + * when it schedules it executes as if it is returning from a syscall, + * i.e. the syscall return path where the previous context copied to its + * stack is restored. It also modifies r0 to ensure POSIX child return + * semantics. + */ +int arch_setup_new_thread(struct ktcb *new, struct ktcb *orig) { - /* Setup new thread stack */ - new->context.sp = + /* + * Pre-syscall context is saved on the kernel stack upon + * a system call exception. We need the location where it + * is saved relative to the start of ktcb. + */ + void *syscall_context_offset = (void *)orig->syscall_regs - + (void *)orig; + + /* + * Copy the saved context from original thread's + * stack to new thread stack. + */ + memcpy((void *)new + syscall_context_offset, + (void *)orig + syscall_context_offset, + sizeof(syscall_context_t)); + + /* + * Modify the return register value with 0 to ensure new thread + * returns with that value. This is a POSIX requirement and enforces + * policy on the microkernel, but it is currently the best solution. + */ + new->syscall_regs->r0 = 0; + + /* + * Set up the stack pointer and program counter so that next time + * the new thread schedules, it executes the end part of the system + * call exception where the previous context is restored. + */ + new->context.sp = (unsigned long)((void *)new + + syscall_context_offset); + new->context.pc = (unsigned long)return_from_syscall; + + /* Copy other relevant fields from original ktcb */ + new->pagerid = orig->pagerid; + + /* Distribute original thread's ticks into two threads */ + new->ticks_left = orig->ticks_left / 2; + orig->ticks_left /= 2; + + return 0; } /* @@ -117,9 +163,13 @@ out: waitqueue_head_init(&new->wqh_send); waitqueue_head_init(&new->wqh_recv); - /* When space is copied kernel-side tcb and stack are also copied */ + /* + * When space is copied this restores the new thread's + * system call return environment so that it can safely + * return as a copy of its original thread. + */ if (flags == THREAD_CREATE_COPYSPC) - setup_new_ktcb(new, task); + arch_setup_new_thread(new, task); /* Add task to global hlist of tasks */ add_task_global(new); @@ -132,7 +182,7 @@ out: * space for a thread that doesn't already have one, or destroys it if the last * thread that uses it is destroyed. */ -int sys_thread_control(struct syscall_args *regs) +int sys_thread_control(syscall_context_t *regs) { int ret = 0; u32 *reg = (u32 *)regs; diff --git a/src/arch/arm/vectors.S b/src/arch/arm/vectors.S index 56298b3..ac053ac 100644 --- a/src/arch/arm/vectors.S +++ b/src/arch/arm/vectors.S @@ -144,11 +144,14 @@ BEGIN_PROC(arm_swi_exception) mrs r0, spsr_fc @ psr also need saving in case this context is interrupted. stmfd sp!, {r0} enable_irqs r0 - add r0, sp, #4 @ Pass sp address + 4 as a pointer to saved regs. - ktcb_ref_saved_regs r0, r1, r2 @ Save regs pointer in ktcb + mov r0, sp @ Current SP has pointer to all saved context. + ktcb_ref_saved_regs r0, r1, r2 @ Save syscall context pointer in ktcb mov r1, lr @ Pass swi instruction address in LR as arg1 mov lr, pc ldr pc, =syscall + +.global return_from_syscall; @ Newly created threads use this path to return, +return_from_syscall: @ if they duplicated another thread's address space. disable_irqs r1 @ Not disabling irqs at this point causes the SP_USR and spsr @ to get corrupt causing havoc. ldmfd sp!, {r1} diff --git a/src/glue/arm/systable.c b/src/glue/arm/systable.c index d3c648e..cdaefe7 100644 --- a/src/glue/arm/systable.c +++ b/src/glue/arm/systable.c @@ -58,7 +58,7 @@ void syscall_init() } /* Checks a syscall is legitimate and dispatches to appropriate handler. */ -int syscall(struct syscall_args *regs, unsigned long swi_addr) +int syscall(syscall_context_t *regs, unsigned long swi_addr) { /* Check if genuine system call, coming from the syscall page */ if ((swi_addr & ARM_SYSCALL_PAGE) == ARM_SYSCALL_PAGE) {