/* * Copyright (C) 2008 Philippe Gerum . * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #include #include #include #include #include #include #include #include #include "boilerplate/namegen.h" #include "copperplate/heapobj.h" #include "copperplate/threadobj.h" #include "copperplate/syncobj.h" #include "copperplate/clockobj.h" #include "copperplate/cluster.h" #include "copperplate/internal.h" #include "psos/psos.h" #include "internal.h" #include "task.h" #include "tm.h" #include "queue.h" #include "rn.h" union psos_wait_union { struct psos_queue_wait queue_wait; struct psos_rn_wait rn_wait; }; struct cluster psos_task_table; static DEFINE_NAME_GENERATOR(task_namegen, "task", struct psos_task, name); static struct psos_task *find_psos_task(u_long tid, int *err_r) { struct psos_task *task = mainheap_deref(tid, struct psos_task); unsigned int magic; /* * Best-effort to validate a TCB pointer the cheap way, * without relying on any syscall. */ if (task == NULL || ((uintptr_t)task & (sizeof(uintptr_t)-1)) != 0) goto objid_error; magic = threadobj_get_magic(&task->thobj); if (magic == task_magic) return task; if (magic == ~task_magic) { *err_r = ERR_OBJDEL; return NULL; } if ((magic >> 16) == 0x8181) { *err_r = ERR_OBJTYPE; return NULL; } objid_error: *err_r = ERR_OBJID; return NULL; } static struct psos_task *find_psos_task_or_self(u_long tid, int *err_r) { struct psos_task *current; if (tid) return find_psos_task(tid, err_r); current = psos_task_current(); if (current == NULL) { *err_r = ERR_SSFN; return NULL; } return current; } struct psos_task *get_psos_task(u_long tid, int *err_r) { struct psos_task *task = find_psos_task(tid, err_r); /* * Grab the task lock, assuming that the task might have been * deleted, and/or maybe we have been lucky, and some random * opaque pointer might lead us to something which is laid in * valid memory but certainly not to a task object. Last * chance is pthread_mutex_lock() detecting a wrong mutex kind * and bailing out. * * XXX: threadobj_lock() disables cancellability for the * caller upon success, until the lock is dropped in * threadobj_unlock(), so there is no way it may vanish while * holding the lock. Therefore we need no cleanup handler * here. */ if (task == NULL || threadobj_lock(&task->thobj) == -EINVAL) return NULL; /* Check the magic word again, while we hold the lock. */ if (threadobj_get_magic(&task->thobj) != task_magic) { threadobj_unlock(&task->thobj); *err_r = ERR_OBJDEL; return NULL; } return task; } struct psos_task *get_psos_task_or_self(u_long tid, int *err_r) { struct psos_task *current; if (tid) return get_psos_task(tid, err_r); current = psos_task_current(); if (current == NULL) { *err_r = ERR_SSFN; return NULL; } /* This one might block but can't fail, it is ours. */ threadobj_lock(¤t->thobj); return current; } void put_psos_task(struct psos_task *task) { threadobj_unlock(&task->thobj); } static void task_finalizer(struct threadobj *thobj) { struct psos_task *task = container_of(thobj, struct psos_task, thobj); struct psos_tm *tm, *tmp; struct syncstate syns; int ret; cluster_delobj(&psos_task_table, &task->cobj); if (!pvlist_empty(&task->timer_list)) { pvlist_for_each_entry_safe(tm, tmp, &task->timer_list, link) tm_cancel((u_long)tm); } /* We have to hold a lock on a syncobj to destroy it. */ ret = __bt(syncobj_lock(&task->sobj, &syns)); if (ret == 0) syncobj_destroy(&task->sobj, &syns); } static int task_prologue(void *arg) { struct psos_task *task = arg; return __bt(threadobj_prologue(&task->thobj, task->name)); } static void *task_trampoline(void *arg) { struct psos_task *task = arg; struct psos_task_args *args = &task->args; struct sched_param_ex param_ex; struct service svc; CANCEL_DEFER(svc); threadobj_wait_start(); threadobj_lock(&task->thobj); if (task->mode & T_TSLICE) { param_ex.sched_priority = threadobj_get_priority(&task->thobj); param_ex.sched_rr_quantum.tv_sec = psos_rrperiod.tv_sec; param_ex.sched_rr_quantum.tv_nsec = psos_rrperiod.tv_nsec; threadobj_set_schedparam(&task->thobj, SCHED_RR, ¶m_ex); } if (task->mode & T_NOPREEMPT) __threadobj_lock_sched(&task->thobj); threadobj_unlock(&task->thobj); threadobj_notify_entry(); CANCEL_RESTORE(svc); args->entry(args->arg0, args->arg1, args->arg2, args->arg3); return NULL; } /* * By default, pSOS priorities are mapped 1:1 to SCHED_FIFO * levels. The available priority range is [1..256] over Cobalt when * running in primary mode, and [1..99] over the regular kernel with * the POSIX interface. * * NOTE: over Cobalt, a thread transitioning to secondary mode has its * priority ceiled to 99 in the regular POSIX SCHED_FIFO class. * * The application code may override the routine doing the priority * mapping from pSOS to SCHED_FIFO (normalize). Normalized priorities * returned by this routine must be in the range [ 1 * .. threadobj_high_prio] inclusive. */ __weak int psos_task_normalize_priority(unsigned long psos_prio) { if (psos_prio > threadobj_high_prio) panic("current implementation restricts pSOS " "priority levels to range [1..%d]", threadobj_high_prio); /* Map a pSOS priority level to a SCHED_FIFO one. */ return psos_prio; } /* * Although default pSOS priorities are mapped 1:1 to SCHED_FIFO, we * do still have to use a denormalize function because these calls are * weak and application code may be override the call and implement * the mapping differently. */ __weak unsigned long psos_task_denormalize_priority(int core_prio) { /* Map a SCHED_FIFO priority level to a pSOS one. */ return core_prio; } static int check_task_priority(u_long psos_prio, int *core_prio) { if (psos_prio < 1 || psos_prio > 255) /* In theory. */ return ERR_PRIOR; *core_prio = psos_task_normalize_priority(psos_prio); return SUCCESS; } static int psos_task_get_priority(struct psos_task *task) { int prio = threadobj_get_priority(&task->thobj); return psos_task_denormalize_priority(prio); } u_long t_create(const char *name, u_long prio, u_long sstack, u_long ustack, u_long flags, u_long *tid_r) { struct corethread_attributes cta; struct threadobj_init_data idata; struct psos_task *task; struct service svc; int ret, cprio = 1; char short_name[5]; ret = check_task_priority(prio, &cprio); if (ret) return ret; CANCEL_DEFER(svc); task = threadobj_alloc(struct psos_task, thobj, union psos_wait_union); if (task == NULL) { ret = ERR_NOTCB; goto out; } ustack += sstack; /* * Make sure we are granted a minimal amount of stack space * for common usage of the Glibc. If zero, we will pick a * value based on the implementation default for such minimum. */ if (ustack > 0 && ustack < 8192) { threadobj_free(&task->thobj); ret = ERR_TINYSTK; goto out; } if (name == NULL || *name == '\0') generate_name(task->name, name, &task_namegen); else { name = psos_trunc_name(short_name, name); namecpy(task->name, name); } task->flags = flags; /* We don't do much with those. */ task->mode = 0; /* Not yet known. */ task->events = 0; ret = syncobj_init(&task->sobj, CLOCK_COPPERPLATE, 0, fnref_null); if (ret) goto fail_syncinit; memset(task->notepad, 0, sizeof(task->notepad)); pvlist_init(&task->timer_list); *tid_r = mainheap_ref(task, u_long); idata.magic = task_magic; idata.finalizer = task_finalizer; idata.policy = cprio ? SCHED_FIFO : SCHED_OTHER; idata.param_ex.sched_priority = cprio; ret = threadobj_init(&task->thobj, &idata); if (ret) goto fail_threadinit; ret = __bt(cluster_addobj_dup(&psos_task_table, task->name, &task->cobj)); if (ret) { warning("cannot register task: %s", task->name); goto fail_register; } cta.policy = idata.policy; cta.param_ex.sched_priority = cprio; cta.prologue = task_prologue; cta.run = task_trampoline; cta.arg = task; cta.stacksize = ustack; cta.detachstate = PTHREAD_CREATE_DETACHED; ret = __bt(copperplate_create_thread(&cta, &task->thobj.ptid)); if (ret) { cluster_delobj(&psos_task_table, &task->cobj); fail_register: threadobj_uninit(&task->thobj); fail_threadinit: syncobj_uninit(&task->sobj); fail_syncinit: ret = ERR_NOTCB; threadobj_free(&task->thobj); } out: CANCEL_RESTORE(svc); return ret; } u_long t_start(u_long tid, u_long mode, void (*entry)(u_long, u_long, u_long, u_long), u_long args[]) { struct psos_task *task; struct service svc; int ret; CANCEL_DEFER(svc); task = get_psos_task(tid, &ret); if (task == NULL) goto out; task->args.entry = entry; if (args) { task->args.arg0 = args[0]; task->args.arg1 = args[1]; task->args.arg2 = args[2]; task->args.arg3 = args[3]; } else { task->args.arg0 = 0; task->args.arg1 = 0; task->args.arg2 = 0; task->args.arg3 = 0; } task->mode = mode; ret = threadobj_start(&task->thobj); switch (ret) { case -EIDRM: ret = SUCCESS; break; default: ret = ERR_OBJDEL; case 0: /* == SUCCESS */ put_psos_task(task); } out: CANCEL_RESTORE(svc); return ret; } u_long t_suspend(u_long tid) { struct psos_task *task; struct service svc; int ret; CANCEL_DEFER(svc); task = get_psos_task_or_self(tid, &ret); if (task == NULL) goto out; ret = threadobj_suspend(&task->thobj); if (ret) ret = ERR_OBJDEL; put_psos_task(task); out: CANCEL_RESTORE(svc); return ret; } u_long t_resume(u_long tid) { struct psos_task *task; struct service svc; int ret; CANCEL_DEFER(svc); task = get_psos_task(tid, &ret); if (task == NULL) goto out; ret = threadobj_resume(&task->thobj); if (ret) ret = ERR_OBJDEL; put_psos_task(task); out: CANCEL_RESTORE(svc); return ret; } u_long t_setpri(u_long tid, u_long newprio, u_long *oldprio_r) { int policy, ret = SUCCESS, cprio = 1; struct sched_param_ex param_ex; struct psos_task *task; struct service svc; CANCEL_DEFER(svc); task = get_psos_task_or_self(tid, &ret); if (task == NULL) goto out; *oldprio_r = psos_task_get_priority(task); if (newprio == 0) /* Only inquires for the task priority. */ goto done; ret = check_task_priority(newprio, &cprio); if (ret) { ret = ERR_SETPRI; goto done; } policy = cprio ? SCHED_FIFO : SCHED_OTHER; param_ex.sched_priority = cprio; ret = threadobj_set_schedparam(&task->thobj, policy, ¶m_ex); switch (ret) { case -EIDRM: ret = SUCCESS; goto out; default: ret = ERR_OBJDEL; case 0: /* == SUCCESS */ break; } done: put_psos_task(task); out: CANCEL_RESTORE(svc); return ret; } u_long t_delete(u_long tid) { struct psos_task *task; struct service svc; int ret; CANCEL_DEFER(svc); task = get_psos_task_or_self(tid, &ret); if (task == NULL) goto out; ret = threadobj_cancel(&task->thobj); if (ret) ret = ERR_OBJDEL; out: CANCEL_RESTORE(svc); return ret; } u_long t_ident(const char *name, u_long node, u_long *tid_r) { struct clusterobj *cobj; struct psos_task *task; struct service svc; char short_name[5]; int ret = SUCCESS; if (node) return ERR_NODENO; CANCEL_DEFER(svc); if (name == NULL) { task = find_psos_task_or_self(0, &ret); if (task == NULL) goto out; } else { name = psos_trunc_name(short_name, name); cobj = cluster_findobj(&psos_task_table, name); if (cobj == NULL) { ret = ERR_OBJNF; goto out; } task = container_of(cobj, struct psos_task, cobj); /* * Last attempt to check whether the task is valid, in * case it is pending deletion. */ if (threadobj_get_magic(&task->thobj) != task_magic) { ret = ERR_OBJNF; goto out; } } *tid_r = mainheap_ref(task, u_long); out: CANCEL_RESTORE(svc); return ret; } u_long t_getreg(u_long tid, u_long regnum, u_long *regvalue_r) { struct psos_task *task; struct service svc; int ret = SUCCESS; if (regnum >= PSOSTASK_NR_REGS) return ERR_REGNUM; CANCEL_DEFER(svc); task = get_psos_task_or_self(tid, &ret); if (task == NULL) goto out; *regvalue_r = task->notepad[regnum]; put_psos_task(task); out: CANCEL_RESTORE(svc); return ret; } u_long t_setreg(u_long tid, u_long regnum, u_long regvalue) { struct psos_task *task; struct service svc; int ret = SUCCESS; if (regnum >= PSOSTASK_NR_REGS) return ERR_REGNUM; CANCEL_DEFER(svc); task = get_psos_task_or_self(tid, &ret); if (task == NULL) goto out; task->notepad[regnum] = regvalue; put_psos_task(task); out: CANCEL_RESTORE(svc); return ret; } u_long t_mode(u_long mask, u_long newmask, u_long *oldmode_r) { struct sched_param_ex param_ex; int policy, ret = SUCCESS; struct psos_task *task; struct service svc; CANCEL_DEFER(svc); task = get_psos_task_or_self(0, &ret); if (task == NULL) goto out; *oldmode_r = task->mode; if (mask == 0) goto done; task->mode &= ~mask; task->mode |= (newmask & mask); if (task->mode & T_NOPREEMPT) __threadobj_lock_sched_once(&task->thobj); else if (*oldmode_r & T_NOPREEMPT) __threadobj_unlock_sched(&task->thobj); param_ex.sched_priority = threadobj_get_priority(&task->thobj); if (((task->mode ^ *oldmode_r) & T_TSLICE) == 0) goto done; /* rr status not changed. */ if (task->mode & T_TSLICE) { policy = SCHED_RR; param_ex.sched_rr_quantum.tv_sec = psos_rrperiod.tv_sec; param_ex.sched_rr_quantum.tv_nsec = psos_rrperiod.tv_nsec; } else { policy = param_ex.sched_priority ? SCHED_FIFO : SCHED_OTHER; } /* Working on self, so -EIDRM can't happen. */ threadobj_set_schedparam(&task->thobj, policy, ¶m_ex); done: put_psos_task(task); out: CANCEL_RESTORE(svc); return ret; } static int collect_events(struct psos_task *task, u_long flags, u_long events, u_long *events_r) { if (((flags & EV_ANY) && (events & task->events) != 0) || (!(flags & EV_ANY) && ((events & task->events) == events))) { /* * The condition is satisfied; update the return value * with the set of matched events, and clear the * collected events from the task's mask. */ *events_r = (task->events & events); task->events &= ~events; return 1; } return 0; } u_long ev_receive(u_long events, u_long flags, u_long timeout, u_long *events_r) { struct timespec ts, *timespec; struct psos_task *current; struct syncstate syns; struct service svc; int ret; current = find_psos_task_or_self(0, &ret); if (current == NULL) return ret; CANCEL_DEFER(svc); ret = syncobj_lock(¤t->sobj, &syns); if (ret) { ret = ERR_OBJDEL; goto out; } if (events == 0) { *events_r = current->events; /* Only polling events. */ goto done; } if (collect_events(current, flags, events, events_r)) goto done; if (flags & EV_NOWAIT) { ret = ERR_NOEVS; goto done; } if (timeout != 0) { timespec = &ts; clockobj_ticks_to_timeout(&psos_clock, timeout, timespec); } else timespec = NULL; for (;;) { ret = syncobj_wait_grant(¤t->sobj, timespec, &syns); if (ret == -ETIMEDOUT) { ret = ERR_TIMEOUT; break; } if (collect_events(current, flags, events, events_r)) break; } done: syncobj_unlock(¤t->sobj, &syns); out: CANCEL_RESTORE(svc); return ret; } int __ev_send(struct psos_task *task, u_long events) { struct syncstate syns; int ret; ret = syncobj_lock(&task->sobj, &syns); if (ret) return ERR_OBJDEL; task->events |= events; /* * If the task is pending in ev_receive(), it's likely that we * are posting events the task is waiting for, so we can wake * it up immediately and let it confirm whether the condition * is now satisfied. */ syncobj_grant_one(&task->sobj); syncobj_unlock(&task->sobj, &syns); return 0; } u_long ev_send(u_long tid, u_long events) { struct psos_task *task; struct service svc; int ret = SUCCESS; task = find_psos_task_or_self(tid, &ret); if (task == NULL) return ret; CANCEL_DEFER(svc); ret = __ev_send(task, events); CANCEL_RESTORE(svc); return ret; }