/* * Copyright (C) 2005 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 "current.h" #include "internal.h" /** * @ingroup cobalt_api * @defgroup cobalt_api_cond Condition variables * * Cobalt/POSIX condition variable services * * A condition variable is a synchronization object that allows threads to * suspend execution until some predicate on shared data is satisfied. The basic * operations on conditions are: signal the condition (when the predicate * becomes true), and wait for the condition, suspending the thread execution * until another thread signals the condition. * * A condition variable must always be associated with a mutex, to avoid the * race condition where a thread prepares to wait on a condition variable and * another thread signals the condition just before the first thread actually * waits on it. * * Before it can be used, a condition variable has to be initialized with * pthread_cond_init(). An attribute object, which reference may be passed to * this service, allows to select the features of the created condition * variable, namely the @a clock used by the pthread_cond_timedwait() service * (@a CLOCK_REALTIME is used by default), and whether it may be shared between * several processes (it may not be shared by default, see * pthread_condattr_setpshared()). * * Note that pthread_cond_init() should be used to initialize a condition * variable, using the static initializer @a PTHREAD_COND_INITIALIZER will * delay the initialization to the first method called on the condition * variable and will most likely introduce switches to secondary mode. * The documentation (and specifically api-tags) of the * condition variable services assumes the condition variable was explicitly * initialised with pthread_cond_init(). * *@{ */ static pthread_condattr_t cobalt_default_condattr; static inline struct cobalt_cond_state * get_cond_state(struct cobalt_cond_shadow *shadow) { if (xnsynch_is_shared(shadow->handle)) return cobalt_umm_shared + shadow->state_offset; return cobalt_umm_private + shadow->state_offset; } static inline struct cobalt_mutex_state * get_mutex_state(struct cobalt_cond_shadow *shadow) { struct cobalt_cond_state *cond_state = get_cond_state(shadow); if (cond_state->mutex_state_offset == ~0U) return NULL; if (xnsynch_is_shared(shadow->handle)) return cobalt_umm_shared + cond_state->mutex_state_offset; return cobalt_umm_private + cond_state->mutex_state_offset; } void cobalt_default_condattr_init(void) { pthread_condattr_init(&cobalt_default_condattr); } /** * @fn int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr) * @brief Initialize a condition variable * * This service initializes the condition variable @a cond, using the * condition variable attributes object @a attr. If @a attr is @a * NULL, default attributes are used (see pthread_condattr_init()). * * @param cond the condition variable to be initialized; * * @param attr the condition variable attributes object. * * @return 0 on succes, * @return an error number if: * - EINVAL, the condition variable attributes object @a attr is invalid or * uninitialized; * - EBUSY, the condition variable @a cond was already initialized; * - ENOMEM, insufficient memory available from the system heap to initialize the * condition variable, increase CONFIG_XENO_OPT_SYS_HEAPSZ. * - EAGAIN, no registry slot available, check/raise CONFIG_XENO_OPT_REGISTRY_NRSLOTS. * * @see * * Specification. * * @apitags{thread-unrestricted} */ COBALT_IMPL(int, pthread_cond_init, (pthread_cond_t *cond, const pthread_condattr_t * attr)) { struct cobalt_cond_shadow *_cnd = &((union cobalt_cond_union *)cond)->shadow_cond; struct cobalt_cond_state *cond_state; struct cobalt_condattr kcattr; int err, tmp; if (attr == NULL) attr = &cobalt_default_condattr; err = pthread_condattr_getpshared(attr, &tmp); if (err) return err; kcattr.pshared = tmp; err = pthread_condattr_getclock(attr, &tmp); if (err) return err; kcattr.clock = tmp; err = -XENOMAI_SYSCALL2( sc_cobalt_cond_init, _cnd, &kcattr); if (err) return err; cond_state = get_cond_state(_cnd); cobalt_commit_memory(cond_state); return 0; } static int __attribute__((cold)) cobalt_cond_autoinit_type(const pthread_cond_t *cond) { static const pthread_cond_t cond_initializer = PTHREAD_COND_INITIALIZER; return memcmp(cond, &cond_initializer, sizeof(cond_initializer)) == 0 ? 0 : -1; } static int __attribute__((cold)) cobalt_cond_doautoinit(union cobalt_cond_union *ucond) { if (cobalt_cond_autoinit_type(&ucond->native_cond) < 0) return EINVAL; return __COBALT(pthread_cond_init(&ucond->native_cond, NULL)); } static inline int cobalt_cond_autoinit(union cobalt_cond_union *ucond) { if (ucond->shadow_cond.magic != COBALT_COND_MAGIC) return cobalt_cond_doautoinit(ucond); return 0; } /** * @fn int pthread_cond_destroy(pthread_cond_t *cond) * @brief Destroy a condition variable * * This service destroys the condition variable @a cond, if no thread is * currently blocked on it. The condition variable becomes invalid for all * condition variable services (they all return the EINVAL error) except * pthread_cond_init(). * * @param cond the condition variable to be destroyed. * * @return 0 on succes, * @return an error number if: * - EINVAL, the condition variable @a cond is invalid; * - EPERM, the condition variable is not process-shared and does not belong to * the current process; * - EBUSY, some thread is currently using the condition variable. * * @see * * Specification. * * @apitags{thread-unrestricted} */ COBALT_IMPL(int, pthread_cond_destroy, (pthread_cond_t *cond)) { struct cobalt_cond_shadow *_cond = &((union cobalt_cond_union *)cond)->shadow_cond; if (_cond->magic != COBALT_COND_MAGIC) return (cobalt_cond_autoinit_type(cond) < 0) ? EINVAL : 0; return -XENOMAI_SYSCALL1( sc_cobalt_cond_destroy, _cond); } struct cobalt_cond_cleanup_t { struct cobalt_cond_shadow *cond; struct cobalt_mutex_shadow *mutex; unsigned count; int err; }; static void __pthread_cond_cleanup(void *data) { struct cobalt_cond_cleanup_t *c = (struct cobalt_cond_cleanup_t *)data; int err; do { err = XENOMAI_SYSCALL2(sc_cobalt_cond_wait_epilogue, c->cond, c->mutex); } while (err == -EINTR); c->mutex->lockcnt = c->count; } /** * Wait on a condition variable. * * This service atomically unlocks the mutex @a mx, and block the calling thread * until the condition variable @a cnd is signalled using pthread_cond_signal() * or pthread_cond_broadcast(). When the condition is signaled, this service * re-acquire the mutex before returning. * * Spurious wakeups occur if a signal is delivered to the blocked thread, so, an * application should not assume that the condition changed upon successful * return from this service. * * Even if the mutex @a mx is recursive and its recursion count is greater than * one on entry, it is unlocked before blocking the caller, and the recursion * count is restored once the mutex is re-acquired by this service before * returning. * * Once a thread is blocked on a condition variable, a dynamic binding is formed * between the condition vairable @a cnd and the mutex @a mx; if another thread * calls this service specifying @a cnd as a condition variable but another * mutex than @a mx, this service returns immediately with the EINVAL status. * * This service is a cancellation point for Cobalt threads (created * with the pthread_create() service). When such a thread is cancelled * while blocked in a call to this service, the mutex @a mx is * re-acquired before the cancellation cleanup handlers are called. * * @param cond the condition variable to wait for; * * @param mutex the mutex associated with @a cnd. * * @return 0 on success, * @return an error number if: * - EPERM, the caller context is invalid; * - EINVAL, the specified condition variable or mutex is invalid; * - EPERM, the specified condition variable is not process-shared and does not * belong to the current process; * - EINVAL, another thread is currently blocked on @a cnd using another mutex * than @a mx; * - EPERM, the specified mutex is not owned by the caller. * * @see * * Specification. * * @apitags{xthread-only, switch-primary} */ COBALT_IMPL(int, pthread_cond_wait, (pthread_cond_t *cond, pthread_mutex_t *mutex)) { struct cobalt_cond_shadow *_cnd = &((union cobalt_cond_union *)cond)->shadow_cond; struct cobalt_mutex_shadow *_mx = &((union cobalt_mutex_union *)mutex)->shadow_mutex; struct cobalt_cond_cleanup_t c = { .cond = _cnd, .mutex = _mx, .err = 0, }; int err, oldtype; unsigned count; if (_mx->magic != COBALT_MUTEX_MAGIC) return EINVAL; err = cobalt_cond_autoinit((union cobalt_cond_union *)cond); if (err) return err; if (_mx->attr.type == PTHREAD_MUTEX_ERRORCHECK) { xnhandle_t cur = cobalt_get_current(); if (cur == XN_NO_HANDLE) return EPERM; if (xnsynch_fast_owner_check(mutex_get_ownerp(_mx), cur)) return EPERM; } pthread_cleanup_push(&__pthread_cond_cleanup, &c); count = _mx->lockcnt; pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); err = XENOMAI_SYSCALL5(sc_cobalt_cond_wait_prologue, _cnd, _mx, &c.err, 0, NULL); pthread_setcanceltype(oldtype, NULL); pthread_cleanup_pop(0); while (err == -EINTR) err = XENOMAI_SYSCALL2(sc_cobalt_cond_wait_epilogue, _cnd, _mx); _mx->lockcnt = count; pthread_testcancel(); return -err ?: -c.err; } /** * Wait a bounded time on a condition variable. * * This service is equivalent to pthread_cond_wait(), except that the calling * thread remains blocked on the condition variable @a cnd only until the * timeout specified by @a abstime expires. * * The timeout @a abstime is expressed as an absolute value of the @a clock * attribute passed to pthread_cond_init(). By default, @a CLOCK_REALTIME is * used. * * @param cond the condition variable to wait for; * * @param mutex the mutex associated with @a cnd; * * @param abstime the timeout, expressed as an absolute value of the clock * attribute passed to pthread_cond_init(). * * @return 0 on success, * @return an error number if: * - EPERM, the caller context is invalid; * - EPERM, the specified condition variable is not process-shared and does not * belong to the current process; * - EINVAL, the specified condition variable, mutex or timeout is invalid; * - EINVAL, another thread is currently blocked on @a cnd using another mutex * than @a mx; * - EPERM, the specified mutex is not owned by the caller; * - ETIMEDOUT, the specified timeout expired. * * @see * * Specification. * * @apitags{xthread-only, switch-primary} */ COBALT_IMPL(int, pthread_cond_timedwait, (pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)) { struct cobalt_cond_shadow *_cnd = &((union cobalt_cond_union *)cond)->shadow_cond; struct cobalt_mutex_shadow *_mx = &((union cobalt_mutex_union *)mutex)->shadow_mutex; struct cobalt_cond_cleanup_t c = { .cond = _cnd, .mutex = _mx, }; int err, oldtype; unsigned count; if (_mx->magic != COBALT_MUTEX_MAGIC) return EINVAL; err = cobalt_cond_autoinit((union cobalt_cond_union *)cond); if (err) return err; if (_mx->attr.type == PTHREAD_MUTEX_ERRORCHECK) { xnhandle_t cur = cobalt_get_current(); if (cur == XN_NO_HANDLE) return EPERM; if (xnsynch_fast_owner_check(mutex_get_ownerp(_mx), cur)) return EPERM; } pthread_cleanup_push(&__pthread_cond_cleanup, &c); count = _mx->lockcnt; pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); err = XENOMAI_SYSCALL5(sc_cobalt_cond_wait_prologue, _cnd, _mx, &c.err, 1, abstime); pthread_setcanceltype(oldtype, NULL); pthread_cleanup_pop(0); while (err == -EINTR) err = XENOMAI_SYSCALL2(sc_cobalt_cond_wait_epilogue, _cnd, _mx); _mx->lockcnt = count; pthread_testcancel(); return -err ?: -c.err; } /** * Signal a condition variable. * * This service unblocks one thread blocked on the condition variable @a cnd. * * If more than one thread is blocked on the specified condition variable, the * highest priority thread is unblocked. * * @param cond the condition variable to be signalled. * * @return 0 on succes, * @return an error number if: * - EINVAL, the condition variable is invalid; * - EPERM, the condition variable is not process-shared and does not belong to * the current process. * * @see * * Specification. * * @apitags{xthread-only} */ COBALT_IMPL(int, pthread_cond_signal, (pthread_cond_t *cond)) { struct cobalt_cond_shadow *_cnd = &((union cobalt_cond_union *)cond)->shadow_cond; struct cobalt_mutex_state *mutex_state; struct cobalt_cond_state *cond_state; __u32 pending_signals; xnhandle_t cur; __u32 flags; int err; err = cobalt_cond_autoinit((union cobalt_cond_union *)cond); if (err) return err; mutex_state = get_mutex_state(_cnd); if (mutex_state == NULL) return 0; /* Fast path, no waiter. */ flags = mutex_state->flags; if (flags & COBALT_MUTEX_ERRORCHECK) { cur = cobalt_get_current(); if (cur == XN_NO_HANDLE) return EPERM; if (xnsynch_fast_owner_check(&mutex_state->owner, cur) < 0) return EPERM; } mutex_state->flags = flags | COBALT_MUTEX_COND_SIGNAL; cond_state = get_cond_state(_cnd); pending_signals = cond_state->pending_signals; if (pending_signals != ~0U) cond_state->pending_signals = pending_signals + 1; return 0; } /** * Broadcast a condition variable. * * This service unblocks all threads blocked on the condition variable @a cnd. * * @param cond the condition variable to be signalled. * * @return 0 on succes, * @return an error number if: * - EINVAL, the condition variable is invalid; * - EPERM, the condition variable is not process-shared and does not belong to * the current process. * * @see * * Specification. * * @apitags{xthread-only} */ COBALT_IMPL(int, pthread_cond_broadcast, (pthread_cond_t *cond)) { struct cobalt_cond_shadow *_cnd = &((union cobalt_cond_union *)cond)->shadow_cond; struct cobalt_mutex_state *mutex_state; struct cobalt_cond_state *cond_state; xnhandle_t cur; __u32 flags; int err; err = cobalt_cond_autoinit((union cobalt_cond_union *)cond); if (err) return err; mutex_state = get_mutex_state(_cnd); if (mutex_state == NULL) return 0; flags = mutex_state->flags; if (flags & COBALT_MUTEX_ERRORCHECK) { cur = cobalt_get_current(); if (cur == XN_NO_HANDLE) return EPERM; if (xnsynch_fast_owner_check(&mutex_state->owner, cur) < 0) return EPERM; } mutex_state->flags = flags | COBALT_MUTEX_COND_SIGNAL; cond_state = get_cond_state(_cnd); cond_state->pending_signals = ~0U; return 0; } /** * Initialize a condition variable attributes object. * * This services initializes the condition variable attributes object @a attr * with default values for all attributes. Default value are: * - for the @a clock attribute, @a CLOCK_REALTIME; * - for the @a pshared attribute @a PTHREAD_PROCESS_PRIVATE. * * If this service is called specifying a condition variable attributes object * that was already initialized, the attributes object is reinitialized. * * @param attr the condition variable attributes object to be initialized. * * @return 0 on success; * @return an error number if: * - ENOMEM, the condition variable attribute object pointer @a attr is @a * NULL. * * @see * * Specification. * * @apitags{thread-unrestricted} */ int pthread_condattr_init(pthread_condattr_t * attr); /** * Destroy a condition variable attributes object. * * This service destroys the condition variable attributes object @a attr. The * object becomes invalid for all condition variable services (they all return * EINVAL) except pthread_condattr_init(). * * @param attr the initialized mutex attributes object to be destroyed. * * @return 0 on success; * @return an error number if: * - EINVAL, the mutex attributes object @a attr is invalid. * * @see * * Specification. * * @apitags{thread-unrestricted} */ int pthread_condattr_destroy(pthread_condattr_t * attr); /** * Get the clock selection attribute from a condition variable attributes * object. * * This service stores, at the address @a clk_id, the value of the @a clock * attribute in the condition variable attributes object @a attr. * * See pthread_cond_timedwait() for a description of the effect of * this attribute on a condition variable. The clock ID returned is @a * CLOCK_REALTIME or @a CLOCK_MONOTONIC. * * @param attr an initialized condition variable attributes object, * * @param clk_id address where the @a clock attribute value will be stored on * success. * * @return 0 on success, * @return an error number if: * - EINVAL, the attribute object @a attr is invalid. * * @see * * Specification. * * @apitags{thread-unrestricted} */ int pthread_condattr_getclock(const pthread_condattr_t * attr, clockid_t * clk_id); /** * Set the clock selection attribute of a condition variable attributes object. * * This service set the @a clock attribute of the condition variable attributes * object @a attr. * * See pthread_cond_timedwait() for a description of the effect of * this attribute on a condition variable. * * @param attr an initialized condition variable attributes object, * * @param clk_id value of the @a clock attribute, may be @a CLOCK_REALTIME or @a * CLOCK_MONOTONIC. * * @return 0 on success, * @return an error number if: * - EINVAL, the condition variable attributes object @a attr is invalid; * - EINVAL, the value of @a clk_id is invalid for the @a clock attribute. * * @see * * Specification. * * @apitags{thread-unrestricted} */ int pthread_condattr_setclock(pthread_condattr_t * attr, clockid_t clk_id); /** * Get the process-shared attribute from a condition variable attributes * object. * * This service stores, at the address @a pshared, the value of the @a pshared * attribute in the condition variable attributes object @a attr. * * The @a pshared attribute may only be one of @a PTHREAD_PROCESS_PRIVATE or @a * PTHREAD_PROCESS_SHARED. See pthread_condattr_setpshared() for the meaning of * these two constants. * * @param attr an initialized condition variable attributes object. * * @param pshared address where the value of the @a pshared attribute will be * stored on success. * * @return 0 on success, * @return an error number if: * - EINVAL, the @a pshared address is invalid; * - EINVAL, the condition variable attributes object @a attr is invalid. * * @see * * Specification. * * @apitags{thread-unrestricted} */ int pthread_condattr_getpshared(const pthread_condattr_t *attr, int *pshared); /** * Set the process-shared attribute of a condition variable attributes object. * * This service set the @a pshared attribute of the condition variable * attributes object @a attr. * * @param attr an initialized condition variable attributes object. * * @param pshared value of the @a pshared attribute, may be one of: * - PTHREAD_PROCESS_PRIVATE, meaning that a condition variable created with the * attributes object @a attr will only be accessible by threads within the * same process as the thread that initialized the condition variable; * - PTHREAD_PROCESS_SHARED, meaning that a condition variable created with the * attributes object @a attr will be accessible by any thread that has access * to the memory where the condition variable is allocated. * * @return 0 on success, * @return an error status if: * - EINVAL, the condition variable attributes object @a attr is invalid; * - EINVAL, the value of @a pshared is invalid. * * @see * * Specification. * * @apitags{thread-unrestricted} */ int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared); /** @}*/