/* * Copyright (C) 2010 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. * * Timer object abstraction. */ #include #include #include #include #include #include #include #include #include "boilerplate/list.h" #include "boilerplate/signal.h" #include "boilerplate/lock.h" #include "copperplate/threadobj.h" #include "copperplate/timerobj.h" #include "copperplate/clockobj.h" #include "copperplate/debug.h" #include "internal.h" static pthread_mutex_t svlock; static pthread_t svthread; static pid_t svpid; static DEFINE_PRIVATE_LIST(svtimers); #ifdef CONFIG_XENO_COBALT static inline void timersv_init_corespec(void) { } #else /* CONFIG_XENO_MERCURY */ static inline void timersv_init_corespec(void) { sigset_t set; sigemptyset(&set); sigaddset(&set, SIGALRM); pthread_sigmask(SIG_BLOCK, &set, NULL); } #endif /* CONFIG_XENO_MERCURY */ /* * XXX: at some point, we may consider using a timer wheel instead of * a simple linked list to index timers. The latter method is * efficient for up to ten outstanding timers or so, which should be * enough for most applications. However, there exist poorly designed * apps involving dozens of active timers, particularly in the legacy * embedded world. */ static void timerobj_enqueue(struct timerobj *tmobj) { struct timerobj *__tmobj; if (pvlist_empty(&svtimers)) { pvlist_append(&tmobj->next, &svtimers); return; } pvlist_for_each_entry_reverse(__tmobj, &svtimers, next) { if (timespec_before_or_same(&__tmobj->itspec.it_value, &tmobj->itspec.it_value)) break; } atpvh(&__tmobj->next, &tmobj->next); } static int server_prologue(void *arg) { svpid = get_thread_pid(); copperplate_set_current_name("timer-internal"); timersv_init_corespec(); threadobj_set_current(THREADOBJ_IRQCONTEXT); return 0; } static void *timerobj_server(void *arg) { void (*handler)(struct timerobj *tmobj); struct timespec now, value, interval; struct timerobj *tmobj; sigset_t set; int sig, ret; sigemptyset(&set); sigaddset(&set, SIGALRM); for (;;) { ret = __RT(sigwait(&set, &sig)); if (ret && ret != -EINTR) break; /* * We have a single server thread for now, so handlers * are fully serialized. */ write_lock_nocancel(&svlock); __RT(clock_gettime(CLOCK_COPPERPLATE, &now)); while (!pvlist_empty(&svtimers)) { tmobj = pvlist_first_entry(&svtimers, typeof(*tmobj), next); value = tmobj->itspec.it_value; interval = tmobj->itspec.it_interval; handler = tmobj->handler; if (timespec_after(&value, &now)) break; pvlist_remove_init(&tmobj->next); if (interval.tv_sec > 0 || interval.tv_nsec > 0) { timespec_add(&tmobj->itspec.it_value, &value, &interval); timerobj_enqueue(tmobj); } write_unlock(&svlock); handler(tmobj); write_lock_nocancel(&svlock); } write_unlock(&svlock); } return NULL; } static void timerobj_spawn_server(void) { struct corethread_attributes cta; cta.policy = SCHED_CORE; cta.param_ex.sched_priority = threadobj_irq_prio; cta.prologue = server_prologue; cta.run = timerobj_server; cta.arg = NULL; cta.stacksize = PTHREAD_STACK_DEFAULT; cta.detachstate = PTHREAD_CREATE_DETACHED; __bt(copperplate_create_thread(&cta, &svthread)); } int timerobj_init(struct timerobj *tmobj) { static pthread_once_t spawn_once; pthread_mutexattr_t mattr; struct sigevent sev; int ret; /* * XXX: We need a threaded handler so that we may invoke core * async-unsafe services from there (e.g. syncobj post * routines are not async-safe, but the higher layers may * invoke them from a timer handler). * * We don't rely on glibc's SIGEV_THREAD feature, because it * is unreliable with some glibc releases (2.4 -> 2.9 at the * very least), and spawning a short-lived thread at each * timeout expiration to run the handler is just overkill. */ pthread_once(&spawn_once, timerobj_spawn_server); if (!svthread) return __bt(-EAGAIN); tmobj->handler = NULL; pvholder_init(&tmobj->next); /* so we may use pvholder_linked() */ memset(&sev, 0, sizeof(sev)); sev.sigev_notify = SIGEV_THREAD_ID; sev.sigev_signo = SIGALRM; sev.sigev_notify_thread_id = svpid; ret = __RT(timer_create(CLOCK_COPPERPLATE, &sev, &tmobj->timer)); if (ret) return __bt(-errno); pthread_mutexattr_init(&mattr); pthread_mutexattr_settype(&mattr, mutex_type_attribute); pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_INHERIT); ret = pthread_mutexattr_setpshared(&mattr, mutex_scope_attribute); assert(ret == 0); ret = __bt(-__RT(pthread_mutex_init(&tmobj->lock, &mattr))); pthread_mutexattr_destroy(&mattr); return ret; } void timerobj_destroy(struct timerobj *tmobj) /* lock held, dropped */ { write_lock_nocancel(&svlock); if (pvholder_linked(&tmobj->next)) pvlist_remove_init(&tmobj->next); write_unlock(&svlock); __RT(timer_delete(tmobj->timer)); __RT(pthread_mutex_unlock(&tmobj->lock)); __RT(pthread_mutex_destroy(&tmobj->lock)); } int timerobj_start(struct timerobj *tmobj, void (*handler)(struct timerobj *tmobj), struct itimerspec *it) /* lock held, dropped */ { int ret = 0; /* * We hold the queue lock long enough to prevent the timer * from being dequeued by the carrier thread before it has * been armed, e.g. the user handler might destroy it under * our feet if so, causing timer_settime() to fail, which * would in turn lead to a double-deletion if the caller * happens to check the return code then drop the timer * (again). */ write_lock_nocancel(&svlock); if (pvholder_linked(&tmobj->next)) pvlist_remove_init(&tmobj->next); tmobj->handler = handler; tmobj->itspec = *it; if (__RT(timer_settime(tmobj->timer, TIMER_ABSTIME, it, NULL))) { ret = __bt(-errno); goto fail; } timerobj_enqueue(tmobj); fail: write_unlock(&svlock); timerobj_unlock(tmobj); return ret; } int timerobj_stop(struct timerobj *tmobj) /* lock held, dropped */ { static const struct itimerspec itimer_stop; write_lock_nocancel(&svlock); if (pvholder_linked(&tmobj->next)) pvlist_remove_init(&tmobj->next); __RT(timer_settime(tmobj->timer, 0, &itimer_stop, NULL)); tmobj->handler = NULL; write_unlock(&svlock); timerobj_unlock(tmobj); return 0; } int timerobj_pkg_init(void) { pthread_mutexattr_t mattr; int ret; pthread_mutexattr_init(&mattr); pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE); pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_INHERIT); pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_PRIVATE); ret = __bt(-__RT(pthread_mutex_init(&svlock, &mattr))); pthread_mutexattr_destroy(&mattr); return ret; }