/* Copyright (c) 2012, Broadcom Europe Ltd All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /*#define VCOS_INLINE_BODIES */ #include "interface/vcos/vcos.h" #include "interface/vcos/vcos_msgqueue.h" #include #include #include #include #include #include /* Cygwin doesn't always have prctl.h and it doesn't have PR_SET_NAME */ #if defined( __linux__ ) # if !defined(HAVE_PRCTL) # define HAVE_PRCTL # endif #include #endif #ifdef HAVE_CMAKE_CONFIG #include "cmake_config.h" #endif #ifdef HAVE_MTRACE #include #endif #if defined(ANDROID) #include #endif #ifndef VCOS_DEFAULT_STACK_SIZE #define VCOS_DEFAULT_STACK_SIZE 4096 #endif static int vcos_argc; static const char **vcos_argv; typedef void (*LEGACY_ENTRY_FN_T)(int, void *); static VCOS_THREAD_ATTR_T default_attrs = { .ta_stacksz = VCOS_DEFAULT_STACK_SIZE, }; /** Singleton global lock used for vcos_global_lock/unlock(). */ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; #ifdef ANDROID static VCOS_MUTEX_T printf_lock; #endif /* Create a per-thread key for faking up vcos access * on non-vcos threads. */ pthread_key_t _vcos_thread_current_key; static VCOS_UNSIGNED _vcos_thread_current_key_created = 0; static VCOS_ONCE_T current_thread_key_once; /* init just once */ static void vcos_thread_cleanup(VCOS_THREAD_T *thread) { vcos_semaphore_delete(&thread->suspend); if (thread->task_timer_created) { vcos_timer_delete(&thread->task_timer); } } static void vcos_dummy_thread_cleanup(void *cxt) { VCOS_THREAD_T *thread = cxt; if (thread->dummy) { int i; /* call termination functions */ for (i=0; thread->at_exit[i].pfn != NULL; i++) { thread->at_exit[i].pfn(thread->at_exit[i].cxt); } vcos_thread_cleanup(thread); vcos_free(thread); } } static void current_thread_key_init(void) { vcos_assert(!_vcos_thread_current_key_created); pthread_key_create (&_vcos_thread_current_key, vcos_dummy_thread_cleanup); _vcos_thread_current_key_created = 1; } /* A VCOS wrapper for the thread which called vcos_init. */ static VCOS_THREAD_T vcos_thread_main; static void *vcos_thread_entry(void *arg) { int i; void *ret; VCOS_THREAD_T *thread = (VCOS_THREAD_T *)arg; vcos_assert(thread != NULL); thread->dummy = 0; pthread_setspecific(_vcos_thread_current_key, thread); #if defined( HAVE_PRCTL ) && defined( PR_SET_NAME ) /* cygwin doesn't have PR_SET_NAME */ prctl( PR_SET_NAME, (unsigned long)thread->name, 0, 0, 0 ); #endif if (thread->legacy) { LEGACY_ENTRY_FN_T fn = (LEGACY_ENTRY_FN_T)thread->entry; (*fn)(0, thread->arg); ret = 0; } else { ret = (*thread->entry)(thread->arg); } /* call termination functions */ for (i=0; thread->at_exit[i].pfn != NULL; i++) { thread->at_exit[i].pfn(thread->at_exit[i].cxt); } return ret; } static void _task_timer_expiration_routine(void *cxt) { VCOS_THREAD_T *thread = (VCOS_THREAD_T *)cxt; vcos_assert(thread->orig_task_timer_expiration_routine); thread->orig_task_timer_expiration_routine(thread->orig_task_timer_context); thread->orig_task_timer_expiration_routine = NULL; } VCOS_STATUS_T vcos_thread_create(VCOS_THREAD_T *thread, const char *name, VCOS_THREAD_ATTR_T *attrs, VCOS_THREAD_ENTRY_FN_T entry, void *arg) { VCOS_STATUS_T st; pthread_attr_t pt_attrs; VCOS_THREAD_ATTR_T *local_attrs = attrs ? attrs : &default_attrs; int rc; vcos_assert(thread); memset(thread, 0, sizeof(VCOS_THREAD_T)); rc = pthread_attr_init(&pt_attrs); if (rc < 0) return VCOS_ENOMEM; st = vcos_semaphore_create(&thread->suspend, NULL, 0); if (st != VCOS_SUCCESS) { pthread_attr_destroy(&pt_attrs); return st; } pthread_attr_setstacksize(&pt_attrs, local_attrs->ta_stacksz); #if VCOS_CAN_SET_STACK_ADDR if (local_attrs->ta_stackaddr) { pthread_attr_setstackaddr(&pt_attrs, local_attrs->ta_stackaddr); } #else vcos_demand(local_attrs->ta_stackaddr == 0); #endif /* pthread_attr_setpriority(&pt_attrs, local_attrs->ta_priority); */ vcos_assert(local_attrs->ta_stackaddr == 0); /* Not possible */ thread->entry = entry; thread->arg = arg; thread->legacy = local_attrs->legacy; strncpy(thread->name, name, sizeof(thread->name)); thread->name[sizeof(thread->name)-1] = '\0'; memset(thread->at_exit, 0, sizeof(thread->at_exit)); rc = pthread_create(&thread->thread, &pt_attrs, vcos_thread_entry, thread); pthread_attr_destroy(&pt_attrs); if (rc < 0) { vcos_semaphore_delete(&thread->suspend); return VCOS_ENOMEM; } else { return VCOS_SUCCESS; } } void vcos_thread_join(VCOS_THREAD_T *thread, void **pData) { pthread_join(thread->thread, pData); vcos_thread_cleanup(thread); } VCOSPRE_ VCOS_STATUS_T VCOSPOST_ vcos_thread_create_classic(VCOS_THREAD_T *thread, const char *name, void *(*entry)(void *arg), void *arg, void *stack, VCOS_UNSIGNED stacksz, VCOS_UNSIGNED priaff, VCOS_UNSIGNED timeslice, VCOS_UNSIGNED autostart) { VCOS_THREAD_ATTR_T attrs; vcos_thread_attr_init(&attrs); vcos_thread_attr_setstacksize(&attrs, stacksz); vcos_thread_attr_setpriority(&attrs, priaff & ~_VCOS_AFFINITY_MASK); vcos_thread_attr_setaffinity(&attrs, priaff & _VCOS_AFFINITY_MASK); (void)timeslice; (void)autostart; if (VCOS_CAN_SET_STACK_ADDR) { vcos_thread_attr_setstack(&attrs, stack, stacksz); } return vcos_thread_create(thread, name, &attrs, entry, arg); } uint64_t vcos_getmicrosecs64_internal(void) { struct timeval tv; uint64_t tm = 0; if (!gettimeofday(&tv, NULL)) { tm = (tv.tv_sec * 1000000LL) + tv.tv_usec; } return tm; } #ifdef ANDROID static int log_prio[] = { ANDROID_LOG_INFO, /* VCOS_LOG_UNINITIALIZED */ ANDROID_LOG_INFO, /* VCOS_LOG_NEVER */ ANDROID_LOG_ERROR, /* VCOS_LOG_ERROR */ ANDROID_LOG_WARN, /* VCOS_LOG_WARN */ ANDROID_LOG_INFO, /* VCOS_LOG_INFO */ ANDROID_LOG_DEBUG /* VCOS_LOG_TRACE */ }; int vcos_use_android_log = 1; int vcos_log_to_file = 0; #else int vcos_use_android_log = 0; int vcos_log_to_file = 0; #endif static FILE * log_fhandle = NULL; void vcos_vlog_default_impl(const VCOS_LOG_CAT_T *cat, VCOS_LOG_LEVEL_T _level, const char *fmt, va_list args) { (void)_level; #ifdef ANDROID if ( vcos_use_android_log ) { __android_log_vprint(log_prio[_level], cat->name, fmt, args); } else { vcos_mutex_lock(&printf_lock); #endif if(NULL != log_fhandle) { if (cat->flags.want_prefix) fprintf( log_fhandle, "%s: ", cat->name ); vfprintf(log_fhandle, fmt, args); fputs("\n", log_fhandle); fflush(log_fhandle); } #ifdef ANDROID vcos_mutex_unlock(&printf_lock); } #endif } void _vcos_log_platform_init(void) { if(vcos_log_to_file) { char log_fname[100]; #ifdef ANDROID snprintf(log_fname, 100, "/data/log/vcos_log%u.txt", vcos_process_id_current()); #else snprintf(log_fname, 100, "/var/log/vcos_log%u.txt", vcos_process_id_current()); #endif log_fhandle = fopen(log_fname, "w"); } else log_fhandle = stderr; } /* Flags for init/deinit components */ enum { VCOS_INIT_NAMED_SEM = (1 << 0), VCOS_INIT_PRINTF_LOCK = (1 << 1), VCOS_INIT_MAIN_SEM = (1 << 2), VCOS_INIT_MSGQ = (1 << 3), VCOS_INIT_ALL = 0xffffffff }; static void vcos_term(uint32_t flags) { if (flags & VCOS_INIT_MSGQ) vcos_msgq_deinit(); if (flags & VCOS_INIT_MAIN_SEM) vcos_semaphore_delete(&vcos_thread_main.suspend); #ifdef ANDROID if (flags & VCOS_INIT_PRINTF_LOCK) vcos_mutex_delete(&printf_lock); #endif if (flags & VCOS_INIT_NAMED_SEM) _vcos_named_semaphore_deinit(); } VCOS_STATUS_T vcos_platform_init(void) { VCOS_STATUS_T st; uint32_t flags = 0; int pst; st = _vcos_named_semaphore_init(); if (!vcos_verify(st == VCOS_SUCCESS)) goto end; flags |= VCOS_INIT_NAMED_SEM; #ifdef HAVE_MTRACE /* enable glibc memory debugging, if the environment * variable MALLOC_TRACE names a valid file. */ mtrace(); #endif #ifdef ANDROID st = vcos_mutex_create(&printf_lock, "printf"); if (!vcos_verify(st == VCOS_SUCCESS)) goto end; flags |= VCOS_INIT_PRINTF_LOCK; #endif st = vcos_once(¤t_thread_key_once, current_thread_key_init); if (!vcos_verify(st == VCOS_SUCCESS)) goto end; /* Initialise a VCOS wrapper for the thread which called vcos_init. */ st = vcos_semaphore_create(&vcos_thread_main.suspend, NULL, 0); if (!vcos_verify(st == VCOS_SUCCESS)) goto end; flags |= VCOS_INIT_MAIN_SEM; vcos_thread_main.thread = pthread_self(); pst = pthread_setspecific(_vcos_thread_current_key, &vcos_thread_main); if (!vcos_verify(pst == 0)) { st = VCOS_EINVAL; goto end; } st = vcos_msgq_init(); if (!vcos_verify(st == VCOS_SUCCESS)) goto end; flags |= VCOS_INIT_MSGQ; vcos_logging_init(); end: if (st != VCOS_SUCCESS) vcos_term(flags); return st; } void vcos_platform_deinit(void) { vcos_term(VCOS_INIT_ALL); } void vcos_global_lock(void) { pthread_mutex_lock(&lock); } void vcos_global_unlock(void) { pthread_mutex_unlock(&lock); } void vcos_thread_exit(void *arg) { VCOS_THREAD_T *thread = vcos_thread_current(); if ( thread && thread->dummy ) { vcos_free ( (void*) thread ); thread = NULL; } pthread_exit(arg); } void vcos_thread_attr_init(VCOS_THREAD_ATTR_T *attrs) { *attrs = default_attrs; } VCOS_STATUS_T vcos_pthreads_map_error(int error) { switch (error) { case ENOMEM: return VCOS_ENOMEM; case ENXIO: return VCOS_ENXIO; case EAGAIN: return VCOS_EAGAIN; case ENOSPC: return VCOS_ENOSPC; default: return VCOS_EINVAL; } } VCOS_STATUS_T vcos_pthreads_map_errno(void) { return vcos_pthreads_map_error(errno); } void _vcos_task_timer_set(void (*pfn)(void*), void *cxt, VCOS_UNSIGNED ms) { VCOS_THREAD_T *thread = vcos_thread_current(); if (thread == NULL) return; vcos_assert(thread->orig_task_timer_expiration_routine == NULL); if (!thread->task_timer_created) { VCOS_STATUS_T st = vcos_timer_create(&thread->task_timer, NULL, _task_timer_expiration_routine, thread); (void)st; vcos_assert(st == VCOS_SUCCESS); thread->task_timer_created = 1; } thread->orig_task_timer_expiration_routine = pfn; thread->orig_task_timer_context = cxt; vcos_timer_set(&thread->task_timer, ms); } void _vcos_task_timer_cancel(void) { VCOS_THREAD_T *thread = vcos_thread_current(); if (thread == NULL || !thread->task_timer_created) return; vcos_timer_cancel(&thread->task_timer); thread->orig_task_timer_expiration_routine = NULL; } int vcos_vsnprintf( char *buf, size_t buflen, const char *fmt, va_list ap ) { return vsnprintf( buf, buflen, fmt, ap ); } int vcos_snprintf(char *buf, size_t buflen, const char *fmt, ...) { int ret; va_list ap; va_start(ap,fmt); ret = vsnprintf(buf, buflen, fmt, ap); va_end(ap); return ret; } int vcos_have_rtos(void) { return 1; } const char * vcos_thread_get_name(const VCOS_THREAD_T *thread) { return thread->name; } #ifdef VCOS_HAVE_BACKTRACK void __attribute__((weak)) vcos_backtrace_self(void); #endif void vcos_pthreads_logging_assert(const char *file, const char *func, unsigned int line, const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "assertion failure:%s:%d:%s():", file, line, func); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); #ifdef VCOS_HAVE_BACKTRACK if (vcos_backtrace_self) vcos_backtrace_self(); #endif abort(); } extern VCOS_STATUS_T vcos_thread_at_exit(void (*pfn)(void*), void *cxt) { int i; VCOS_THREAD_T *self = vcos_thread_current(); if (!self) { vcos_assert(0); return VCOS_EINVAL; } for (i=0; iat_exit[i].pfn == NULL) { self->at_exit[i].pfn = pfn; self->at_exit[i].cxt = cxt; return VCOS_SUCCESS; } } return VCOS_ENOSPC; } void vcos_set_args(int argc, const char **argv) { vcos_argc = argc; vcos_argv = argv; } int vcos_get_argc(void) { return vcos_argc; } const char ** vcos_get_argv(void) { return vcos_argv; } /* we can't inline this, because HZ comes from sys/param.h which * dumps all sorts of junk into the global namespace, notable MIN and * MAX. */ uint32_t _vcos_get_ticks_per_second(void) { return HZ; } VCOS_STATUS_T vcos_once(VCOS_ONCE_T *once_control, void (*init_routine)(void)) { int rc = pthread_once(once_control, init_routine); if (rc != 0) { switch (errno) { case EINVAL: return VCOS_EINVAL; default: vcos_assert(0); return VCOS_EACCESS; } } else { return VCOS_SUCCESS; } } VCOS_THREAD_T *vcos_dummy_thread_create(void) { VCOS_STATUS_T st; VCOS_THREAD_T *thread_hndl = NULL; int rc; thread_hndl = (VCOS_THREAD_T *)vcos_malloc(sizeof(VCOS_THREAD_T), NULL); vcos_assert(thread_hndl != NULL); memset(thread_hndl, 0, sizeof(VCOS_THREAD_T)); thread_hndl->dummy = 1; thread_hndl->thread = pthread_self(); st = vcos_semaphore_create(&thread_hndl->suspend, NULL, 0); if (st != VCOS_SUCCESS) { vcos_free(thread_hndl); return( thread_hndl ); } vcos_once(¤t_thread_key_once, current_thread_key_init); rc = pthread_setspecific(_vcos_thread_current_key, thread_hndl); (void)rc; return( thread_hndl ); } /*********************************************************** * * Timers * ***********************************************************/ /* On Linux we could use POSIX timers with a bit of synchronization. * Unfortunately POSIX timers on Bionic are NOT POSIX compliant * what makes that option not viable. * That's why we ended up with our own implementation of timers. * NOTE: That condition variables on Bionic are also buggy and * they work incorrectly with CLOCK_MONOTONIC, so we have to * use CLOCK_REALTIME (and hope that no one will change the time * significantly after the timer has been set up */ #define NSEC_IN_SEC (1000*1000*1000) #define MSEC_IN_SEC (1000) #define NSEC_IN_MSEC (1000*1000) static int _timespec_is_zero(struct timespec *ts) { return ((ts->tv_sec == 0) && (ts->tv_nsec == 0)); } static void _timespec_set_zero(struct timespec *ts) { ts->tv_sec = ts->tv_nsec = 0; } /* Adds left to right and stores the result in left */ static void _timespec_add(struct timespec *left, struct timespec *right) { left->tv_sec += right->tv_sec; left->tv_nsec += right->tv_nsec; if (left->tv_nsec >= (NSEC_IN_SEC)) { left->tv_nsec -= NSEC_IN_SEC; left->tv_sec++; } } static int _timespec_is_larger(struct timespec *left, struct timespec *right) { if (left->tv_sec != right->tv_sec) return left->tv_sec > right->tv_sec; else return left->tv_nsec > right->tv_nsec; } static void* _timer_thread(void *arg) { VCOS_TIMER_T *timer = (VCOS_TIMER_T*)arg; pthread_mutex_lock(&timer->lock); while (!timer->quit) { struct timespec now; /* Wait until next expiry time, or until timer's settings are changed */ if (_timespec_is_zero(&timer->expires)) pthread_cond_wait(&timer->settings_changed, &timer->lock); else pthread_cond_timedwait(&timer->settings_changed, &timer->lock, &timer->expires); /* See if the timer has expired - reloop if it didn't */ clock_gettime(CLOCK_REALTIME, &now); if (_timespec_is_zero(&timer->expires) || _timespec_is_larger(&timer->expires, &now)) continue; /* The timer has expired. Clear the expiry time and call the * expiration routine */ _timespec_set_zero(&timer->expires); timer->orig_expiration_routine(timer->orig_context); } pthread_mutex_unlock(&timer->lock); return NULL; } VCOS_STATUS_T vcos_timer_init(void) { return VCOS_SUCCESS; } VCOS_STATUS_T vcos_timer_create(VCOS_TIMER_T *timer, const char *name, void (*expiration_routine)(void *context), void *context) { pthread_mutexattr_t lock_attr; VCOS_STATUS_T result = VCOS_SUCCESS; int settings_changed_initialized = 0; int lock_attr_initialized = 0; int lock_initialized = 0; (void)name; vcos_assert(timer); vcos_assert(expiration_routine); memset(timer, 0, sizeof(VCOS_TIMER_T)); timer->orig_expiration_routine = expiration_routine; timer->orig_context = context; /* Create conditional variable for notifying the timer's thread * when settings change. */ if (result == VCOS_SUCCESS) { int rc = pthread_cond_init(&timer->settings_changed, NULL); if (rc == 0) settings_changed_initialized = 1; else result = vcos_pthreads_map_error(rc); } /* Create attributes for the lock (we want it to be recursive) */ if (result == VCOS_SUCCESS) { int rc = pthread_mutexattr_init(&lock_attr); if (rc == 0) { pthread_mutexattr_settype(&lock_attr, PTHREAD_MUTEX_RECURSIVE); lock_attr_initialized = 1; } else { result = vcos_pthreads_map_error(rc); } } /* Create lock for the timer structure */ if (result == VCOS_SUCCESS) { int rc = pthread_mutex_init(&timer->lock, &lock_attr); if (rc == 0) lock_initialized = 1; else result = vcos_pthreads_map_error(rc); } /* Lock attributes are no longer needed */ if (lock_attr_initialized) pthread_mutexattr_destroy(&lock_attr); /* Create the underlying thread */ if (result == VCOS_SUCCESS) { int rc = pthread_create(&timer->thread, NULL, _timer_thread, timer); if (rc != 0) result = vcos_pthreads_map_error(rc); } /* Clean up if anything went wrong */ if (result != VCOS_SUCCESS) { if (lock_initialized) pthread_mutex_destroy(&timer->lock); if (settings_changed_initialized) pthread_cond_destroy(&timer->settings_changed); } return result; } void vcos_timer_set(VCOS_TIMER_T *timer, VCOS_UNSIGNED delay_ms) { struct timespec now; vcos_assert(timer); /* Other implementations of this function do undefined things * when delay_ms is 0. This implementation will simply assert and return */ vcos_assert(delay_ms != 0); if (delay_ms == 0) return; pthread_mutex_lock(&timer->lock); /* Calculate the new absolute expiry time */ clock_gettime(CLOCK_REALTIME, &now); timer->expires.tv_sec = delay_ms / MSEC_IN_SEC; timer->expires.tv_nsec = (delay_ms % MSEC_IN_SEC) * NSEC_IN_MSEC; _timespec_add(&timer->expires, &now); /* Notify the timer's thread about the change */ pthread_cond_signal(&timer->settings_changed); pthread_mutex_unlock(&timer->lock); } void vcos_timer_cancel(VCOS_TIMER_T *timer) { vcos_assert(timer); pthread_mutex_lock(&timer->lock); _timespec_set_zero(&timer->expires); pthread_cond_signal(&timer->settings_changed); pthread_mutex_unlock(&timer->lock); } void vcos_timer_reset(VCOS_TIMER_T *timer, VCOS_UNSIGNED delay_ms) { vcos_timer_set(timer, delay_ms); } void vcos_timer_delete(VCOS_TIMER_T *timer) { vcos_assert(timer); pthread_mutex_lock(&timer->lock); /* Other implementation of this function (e.g. ThreadX) * disallow it being called from the expiration routine */ vcos_assert(pthread_self() != timer->thread); /* Stop the timer and set flag telling the timer thread to quit */ _timespec_set_zero(&timer->expires); timer->quit = 1; /* Notify the timer's thread about the change */ pthread_cond_signal(&timer->settings_changed); /* Release the lock, so that the timer's thread can quit */ pthread_mutex_unlock(&timer->lock); /* Wait for the timer thread to finish */ pthread_join(timer->thread, NULL); /* Free resources used by the timer */ pthread_mutex_destroy(&timer->lock); pthread_cond_destroy(&timer->settings_changed); }