/* * Thread related system calls. * * Copyright (C) 2007 Bahadir Balban */ #include #include #include #include #include #include #include #include #include #include #include #include INC_ARCH(asm.h) #include INC_SUBARCH(mm.h) int sys_thread_switch(void) { schedule(); return 0; } /* * This signals a thread so that the thread stops what it is * doing, and take action on the signal provided. Currently this * may be a suspension or an exit signal. */ int thread_signal(struct ktcb *task, unsigned int flags, unsigned int task_state) { int ret = 0; if (task->state == task_state) return 0; /* Signify we want to suspend the thread */ task->flags |= flags; /* Wake it up if it's sleeping */ wake_up_task(task, WAKEUP_INTERRUPT | WAKEUP_SYNC); /* Wait until task switches to desired state */ WAIT_EVENT(&task->wqh_pager, task->state == task_state, ret); return ret; } int thread_suspend(struct ktcb *task) { return thread_signal(task, TASK_SUSPENDING, TASK_INACTIVE); } int thread_exit(struct ktcb *task) { return thread_signal(task, TASK_EXITING, TASK_DEAD); } static inline int TASK_IS_CHILD(struct ktcb *task) { return (((task) != current) && ((task)->pagerid == current->tid)); } int thread_destroy_child(struct ktcb *task) { thread_exit(task); tcb_remove(task); /* Wake up waiters */ wake_up_all(&task->wqh_send, WAKEUP_INTERRUPT); wake_up_all(&task->wqh_recv, WAKEUP_INTERRUPT); BUG_ON(task->wqh_pager.sleepers > 0); BUG_ON(task->state != TASK_DEAD); tcb_delete(task); return 0; } int thread_destroy_children(void) { struct ktcb *task, *n; spin_lock(&curcont->ktcb_list.list_lock); list_foreach_removable_struct(task, n, &curcont->ktcb_list.list, task_list) { if (TASK_IS_CHILD(task)) { spin_unlock(&curcont->ktcb_list.list_lock); thread_destroy_child(task); spin_lock(&curcont->ktcb_list.list_lock); } } spin_unlock(&curcont->ktcb_list.list_lock); return 0; } void thread_destroy_self() { thread_destroy_children(); sched_exit_sync(); } int thread_wait(struct ktcb *task) { return 0; } int thread_destroy(struct ktcb *task) { if (TASK_IS_CHILD(task)) return thread_destroy_child(task); else if (task == current) thread_destroy_self(); return 0; } int arch_clear_thread(struct ktcb *tcb) { /* Remove from the global list */ tcb_remove(tcb); /* Sanity checks */ BUG_ON(!is_page_aligned(tcb)); BUG_ON(tcb->wqh_pager.sleepers > 0); BUG_ON(tcb->wqh_send.sleepers > 0); BUG_ON(tcb->wqh_recv.sleepers > 0); BUG_ON(!list_empty(&tcb->task_list)); BUG_ON(!list_empty(&tcb->rq_list)); BUG_ON(tcb->rq); BUG_ON(tcb->nlocks); BUG_ON(tcb->waiting_on); BUG_ON(tcb->wq); /* Reinitialise the context */ memset(&tcb->context, 0, sizeof(tcb->context)); tcb->context.spsr = ARM_MODE_USR; /* Clear the page tables */ remove_mapping_pgd_all_user(TASK_PGD(tcb)); /* Reinitialize all other fields */ tcb_init(tcb); /* Add back to global list */ tcb_add(tcb); return 0; } int thread_recycle(struct ktcb *task) { int ret; if ((ret = thread_suspend(task)) < 0) return ret; /* * If there are any sleepers on any of the task's * waitqueues, we need to wake those tasks up. */ wake_up_all(&task->wqh_send, 0); wake_up_all(&task->wqh_recv, 0); /* * The thread cannot have a pager waiting for it * since we ought to be the pager. */ BUG_ON(task->wqh_pager.sleepers > 0); /* Clear the task's tcb */ arch_clear_thread(task); return 0; } /* Runs a thread for the first time */ int thread_start(struct ktcb *task) { if (!mutex_trylock(&task->thread_control_lock)) return -EAGAIN; /* FIXME: Refuse to run dead tasks */ /* Notify scheduler of task resume */ sched_resume_async(task); /* Release lock and return */ mutex_unlock(&task->thread_control_lock); return 0; } int arch_setup_new_thread(struct ktcb *new, struct ktcb *orig, unsigned int flags) { /* New threads just need their mode set up */ if (flags & TC_NEW_SPACE) { BUG_ON(orig); new->context.spsr = ARM_MODE_USR; return 0; } BUG_ON(!orig); /* * For duplicated threads pre-syscall context is saved on * the kernel stack. We copy this context of original * into the duplicate thread's current context structure * * No locks needed as the thread is not known to the system yet. */ BUG_ON(!(new->context.spsr = orig->syscall_regs->spsr)); /* User mode */ new->context.r0 = orig->syscall_regs->r0; new->context.r1 = orig->syscall_regs->r1; new->context.r2 = orig->syscall_regs->r2; new->context.r3 = orig->syscall_regs->r3; new->context.r4 = orig->syscall_regs->r4; new->context.r5 = orig->syscall_regs->r5; new->context.r6 = orig->syscall_regs->r6; new->context.r7 = orig->syscall_regs->r7; new->context.r8 = orig->syscall_regs->r8; new->context.r9 = orig->syscall_regs->r9; new->context.r10 = orig->syscall_regs->r10; new->context.r11 = orig->syscall_regs->r11; new->context.r12 = orig->syscall_regs->r12; new->context.sp = orig->syscall_regs->sp_usr; /* Skip lr_svc since it's not going to be used */ new->context.pc = orig->syscall_regs->lr_usr; /* Distribute original thread's ticks into two threads */ new->ticks_left = (orig->ticks_left + 1) >> 1; if (!(orig->ticks_left >>= 1)) orig->ticks_left = 1; return 0; } static inline void thread_setup_new_ids(struct task_ids *ids, unsigned int flags, struct ktcb *new, struct ktcb *orig) { if (flags & TC_SHARE_GROUP) new->tgid = orig->tgid; else new->tgid = new->tid; /* Update ids to be returned back to caller */ ids->tid = new->tid; ids->tgid = new->tgid; } int thread_setup_space(struct ktcb *tcb, struct task_ids *ids, unsigned int flags) { struct address_space *space, *new; int ret = 0; address_space_reference_lock(); if (flags & TC_SHARE_SPACE) { if (!(space = address_space_find(ids->spid))) { ret = -ESRCH; goto out; } address_space_attach(tcb, space); } if (flags & TC_COPY_SPACE) { if (!(space = address_space_find(ids->spid))) { ret = -ESRCH; goto out; } if (IS_ERR(new = address_space_create(space))) { ret = (int)new; goto out; } /* New space id to be returned back to caller */ ids->spid = new->spid; address_space_attach(tcb, new); address_space_add(new); } if (flags & TC_NEW_SPACE) { if (IS_ERR(new = address_space_create(0))) { ret = (int)new; goto out; } /* New space id to be returned back to caller */ ids->spid = new->spid; address_space_attach(tcb, new); address_space_add(new); } out: address_space_reference_unlock(); return ret; } int thread_create(struct task_ids *ids, unsigned int flags) { struct ktcb *new; struct ktcb *orig = 0; int err; /* Clear flags to just include creation flags */ flags &= THREAD_CREATE_MASK; /* Can't have multiple space directives in flags */ if ((flags & TC_SHARE_SPACE & TC_COPY_SPACE & TC_NEW_SPACE) || !flags) return -EINVAL; /* Can't have multiple pager specifiers */ if (flags & TC_SHARE_PAGER & TC_AS_PAGER) return -EINVAL; /* Can't request shared utcb or tgid without shared space */ if (!(flags & TC_SHARE_SPACE)) { if ((flags & TC_SHARE_UTCB) || (flags & TC_SHARE_GROUP)) { return -EINVAL; } } if (!(new = tcb_alloc_init())) return -ENOMEM; /* Set up new thread space by using space id and flags */ if ((err = thread_setup_space(new, ids, flags)) < 0) goto out_err; /* Obtain parent thread if there is one */ if (flags & TC_SHARE_SPACE || flags & TC_COPY_SPACE) { if (!(orig = tcb_find(ids->tid))) { err = -EINVAL; goto out_err; } } /* * Note this is a kernel-level relationship * between the creator and the new thread. * * Any higher layer may define parent/child * relationships between orig and new separately. */ if (flags & TC_AS_PAGER) new->pagerid = current->tid; else if (flags & TC_SHARE_PAGER) new->pagerid = current->pagerid; else new->pagerid = new->tid; //printk("Thread (%d) pager set as (%d)\n", new->tid, new->pagerid); /* * Setup container-generic fields from current task */ new->container = current->container; /* Set up new thread context by using parent ids and flags */ thread_setup_new_ids(ids, flags, new, orig); arch_setup_new_thread(new, orig, flags); tcb_add(new); //printk("%s: %d created: %d, %d, %d \n", // __FUNCTION__, current->tid, ids->tid, // ids->tgid, ids->spid); return 0; out_err: /* Pre-mature tcb needs freeing by free_ktcb */ free_ktcb(new); return err; } /* * Creates, destroys and modifies threads. Also implicitly creates an address * 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(unsigned int flags, struct task_ids *ids) { struct ktcb *task = 0; int err, ret = 0; if ((err = check_access((unsigned long)ids, sizeof(*ids), MAP_USR_RW_FLAGS, 1)) < 0) return err; if ((flags & THREAD_ACTION_MASK) != THREAD_CREATE) if (!(task = tcb_find(ids->tid))) return -ESRCH; if ((err = cap_thread_check(task, flags, ids)) < 0) return err; switch (flags & THREAD_ACTION_MASK) { case THREAD_CREATE: ret = thread_create(ids, flags); break; case THREAD_RUN: ret = thread_start(task); break; case THREAD_SUSPEND: ret = thread_suspend(task); break; case THREAD_DESTROY: ret = thread_destroy(task); break; case THREAD_RECYCLE: ret = thread_recycle(task); break; case THREAD_WAIT: ret = thread_wait(task); break; default: ret = -EINVAL; } return ret; }