| /* Copyright (C) 2002-2023 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC is free software; you can redistribute it and/or modify it under |
| the terms of the GNU General Public License as published by the Free |
| Software Foundation; either version 3, or (at your option) any later |
| version. |
| |
| GCC 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 General Public License |
| for more details. |
| |
| Under Section 7 of GPL version 3, you are granted additional |
| permissions described in the GCC Runtime Library Exception, version |
| 3.1, as published by the Free Software Foundation. |
| |
| You should have received a copy of the GNU General Public License and |
| a copy of the GCC Runtime Library Exception along with this program; |
| see the files COPYING3 and COPYING.RUNTIME respectively. If not, see |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* Threads compatibility routines for libgcc2 for VxWorks. |
| |
| This file implements the GTHREAD_CXX0X part of the interface |
| exposed by gthr-vxworks.h, using APIs exposed by regular (!AE/653) |
| VxWorks kernels. */ |
| |
| #include "gthr.h" |
| |
| #if __GTHREADS_CXX0X |
| |
| #include <taskLib.h> |
| #include <stdlib.h> |
| |
| #define __TIMESPEC_TO_NSEC(timespec) \ |
| ((long long)timespec.tv_sec * 1000000000 + (long long)timespec.tv_nsec) |
| |
| #define __TIMESPEC_TO_TICKS(timespec) \ |
| ((long long)(sysClkRateGet() * __TIMESPEC_TO_NSEC(timespec) + 999999999) \ |
| / 1000000000) |
| |
| #ifdef __RTP__ |
| void tls_delete_hook (void); |
| #define __CALL_DELETE_HOOK(tcb) tls_delete_hook() |
| #else |
| /* In kernel mode, we need to pass the TCB to task_delete_hook. The TCB is |
| the pointer to the WIND_TCB structure and is the ID of the task. */ |
| void tls_delete_hook (void *TCB); |
| #define __CALL_DELETE_HOOK(tcb) tls_delete_hook((WIND_TCB *) ((tcb)->task_id)) |
| #endif |
| |
| int |
| __gthread_cond_signal (__gthread_cond_t *cond) |
| { |
| if (!cond) |
| return ERROR; |
| |
| /* If nobody is waiting, skip the semGive altogether: no one can get |
| in line while we hold the mutex associated with *COND. We could |
| skip this test altogether, but it's presumed cheaper than going |
| through the give and take below, and that a signal without a |
| waiter occurs often enough for the test to be worth it. */ |
| SEM_INFO info; |
| memset (&info, 0, sizeof (info)); |
| __RETURN_ERRNO_IF_NOT_OK (semInfoGet (*cond, &info)); |
| if (info.numTasks == 0) |
| return OK; |
| |
| int ret = __CHECK_RESULT (semGive (*cond)); |
| |
| /* It might be the case, however, that when we called semInfo, there |
| was a waiter just about to timeout, and by the time we called |
| semGive, it had already timed out, so our semGive would leave the |
| *cond semaphore full, so the next caller of wait would pass |
| through. We don't want that. So, make sure we leave the |
| semaphore empty. Despite the window in which the semaphore will |
| be full, this works because: |
| |
| - we're holding the mutex, so nobody else can semGive, and any |
| pending semTakes are actually within semExchange. there might |
| be others blocked to acquire the mutex, but those are not |
| relevant for the analysis. |
| |
| - if there was another non-timed out waiter, semGive will wake it |
| up immediately instead of leaving the semaphore full, so the |
| semTake below will time out, and the semantics are as expected |
| |
| - otherwise, if all waiters timed out before the semGive (or if |
| there weren't any to begin with), our semGive completed leaving |
| the semaphore full, and our semTake below will consume it |
| before any other waiter has a change to reach the semExchange, |
| because we're holding the mutex. */ |
| if (ret == OK) |
| semTake (*cond, NO_WAIT); |
| |
| return ret; |
| } |
| |
| /* -------------------- Timed Condition Variables --------------------- */ |
| |
| int |
| __gthread_cond_timedwait (__gthread_cond_t *cond, |
| __gthread_mutex_t *mutex, |
| const __gthread_time_t *abs_timeout) |
| { |
| if (!cond) |
| return ERROR; |
| |
| if (!mutex) |
| return ERROR; |
| |
| if (!abs_timeout) |
| return ERROR; |
| |
| struct timespec current; |
| if (clock_gettime (CLOCK_REALTIME, ¤t) == ERROR) |
| /* CLOCK_REALTIME is not supported. */ |
| return ERROR; |
| |
| const long long abs_timeout_ticks = __TIMESPEC_TO_TICKS ((*abs_timeout)); |
| const long long current_ticks = __TIMESPEC_TO_TICKS (current); |
| |
| long long waiting_ticks; |
| |
| if (current_ticks < abs_timeout_ticks) |
| waiting_ticks = abs_timeout_ticks - current_ticks; |
| else |
| /* The point until we would need to wait is in the past, |
| no need to wait at all. */ |
| waiting_ticks = 0; |
| |
| /* We check that waiting_ticks can be safely casted as an int. */ |
| if (waiting_ticks > INT_MAX) |
| waiting_ticks = INT_MAX; |
| |
| int ret = __CHECK_RESULT (semExchange (*mutex, *cond, waiting_ticks)); |
| |
| __RETURN_ERRNO_IF_NOT_OK (semTake (*mutex, WAIT_FOREVER)); |
| |
| return ret; |
| } |
| |
| /* --------------------------- Timed Mutexes ------------------------------ */ |
| |
| int |
| __gthread_mutex_timedlock (__gthread_mutex_t *m, |
| const __gthread_time_t *abs_time) |
| { |
| if (!m) |
| return ERROR; |
| |
| if (!abs_time) |
| return ERROR; |
| |
| struct timespec current; |
| if (clock_gettime (CLOCK_REALTIME, ¤t) == ERROR) |
| /* CLOCK_REALTIME is not supported. */ |
| return ERROR; |
| |
| const long long abs_timeout_ticks = __TIMESPEC_TO_TICKS ((*abs_time)); |
| const long long current_ticks = __TIMESPEC_TO_TICKS (current); |
| long long waiting_ticks; |
| |
| if (current_ticks < abs_timeout_ticks) |
| waiting_ticks = abs_timeout_ticks - current_ticks; |
| else |
| /* The point until we would need to wait is in the past, |
| no need to wait at all. */ |
| waiting_ticks = 0; |
| |
| /* Make sure that waiting_ticks can be safely casted as an int. */ |
| if (waiting_ticks > INT_MAX) |
| waiting_ticks = INT_MAX; |
| |
| return __CHECK_RESULT (semTake (*m, waiting_ticks)); |
| } |
| |
| int |
| __gthread_recursive_mutex_timedlock (__gthread_recursive_mutex_t *mutex, |
| const __gthread_time_t *abs_timeout) |
| { |
| return __gthread_mutex_timedlock ((__gthread_mutex_t *)mutex, abs_timeout); |
| } |
| |
| /* ------------------------------ Threads --------------------------------- */ |
| |
| /* Task control block initialization and destruction functions. */ |
| |
| int |
| __init_gthread_tcb (__gthread_t __tcb) |
| { |
| if (!__tcb) |
| return ERROR; |
| |
| __gthread_mutex_init (&(__tcb->return_value_available)); |
| if (__tcb->return_value_available == SEM_ID_NULL) |
| return ERROR; |
| |
| __gthread_mutex_init (&(__tcb->delete_ok)); |
| if (__tcb->delete_ok == SEM_ID_NULL) |
| goto return_sem_delete; |
| |
| /* We lock the two mutexes used for signaling. */ |
| if (__gthread_mutex_lock (&(__tcb->delete_ok)) != OK) |
| goto delete_sem_delete; |
| |
| if (__gthread_mutex_lock (&(__tcb->return_value_available)) != OK) |
| goto delete_sem_delete; |
| |
| __tcb->task_id = TASK_ID_NULL; |
| return OK; |
| |
| delete_sem_delete: |
| semDelete (__tcb->delete_ok); |
| return_sem_delete: |
| semDelete (__tcb->return_value_available); |
| return ERROR; |
| } |
| |
| /* Here, we pass a pointer to a tcb to allow calls from |
| cleanup attributes. */ |
| void |
| __delete_gthread_tcb (__gthread_t* __tcb) |
| { |
| semDelete ((*__tcb)->return_value_available); |
| semDelete ((*__tcb)->delete_ok); |
| free (*__tcb); |
| } |
| |
| /* This __gthread_t stores the address of the TCB malloc'ed in |
| __gthread_create. It is then accessible via __gthread_self(). */ |
| __thread __gthread_t __local_tcb = NULL; |
| |
| __gthread_t |
| __gthread_self (void) |
| { |
| if (!__local_tcb) |
| { |
| /* We are in the initial thread, we need to initialize the TCB. */ |
| __local_tcb = malloc (sizeof (*__local_tcb)); |
| if (!__local_tcb) |
| return NULL; |
| |
| if (__init_gthread_tcb (__local_tcb) != OK) |
| { |
| __delete_gthread_tcb (&__local_tcb); |
| return NULL; |
| } |
| /* We do not set the mutexes in the structure as a thread is not supposed |
| to join or detach himself. */ |
| __local_tcb->task_id = taskIdSelf (); |
| } |
| return __local_tcb; |
| } |
| |
| int |
| __task_wrapper (__gthread_t tcb, FUNCPTR __func, _Vx_usr_arg_t __args) |
| { |
| if (!tcb) |
| return ERROR; |
| |
| __local_tcb = tcb; |
| |
| /* We use this variable to avoid memory leaks in the case where |
| the underlying function throws an exception. */ |
| __attribute__ ((cleanup (__delete_gthread_tcb))) __gthread_t __tmp = tcb; |
| |
| void *return_value = (void *) __func (__args); |
| tcb->return_value = return_value; |
| |
| /* Call the destructors. */ |
| __CALL_DELETE_HOOK (tcb); |
| |
| /* Future calls of join() will be able to retrieve the return value. */ |
| __gthread_mutex_unlock (&tcb->return_value_available); |
| |
| /* We wait for the thread to be joined or detached. */ |
| __gthread_mutex_lock (&(tcb->delete_ok)); |
| __gthread_mutex_unlock (&(tcb->delete_ok)); |
| |
| /* Memory deallocation is done by the cleanup attribute of the tmp variable. */ |
| |
| return OK; |
| } |
| |
| /* Proper gthreads API. */ |
| |
| int |
| __gthread_create (__gthread_t * __threadid, void *(*__func) (void *), |
| void *__args) |
| { |
| if (!__threadid) |
| return ERROR; |
| |
| int priority; |
| __RETURN_ERRNO_IF_NOT_OK (taskPriorityGet (taskIdSelf (), &priority)); |
| |
| int options; |
| __RETURN_ERRNO_IF_NOT_OK (taskOptionsGet (taskIdSelf (), &options)); |
| |
| #if defined (__SPE__) |
| options |= VX_SPE_TASK; |
| #else |
| options |= VX_FP_TASK; |
| #endif |
| options &= VX_USR_TASK_OPTIONS; |
| |
| int stacksize = 20 * 1024; |
| |
| __gthread_t tcb = malloc (sizeof (*tcb)); |
| if (!tcb) |
| return ERROR; |
| |
| if (__init_gthread_tcb (tcb) != OK) |
| { |
| free (tcb); |
| return ERROR; |
| } |
| |
| TASK_ID task_id = taskCreate (NULL, |
| priority, options, stacksize, |
| (FUNCPTR) & __task_wrapper, |
| (_Vx_usr_arg_t) tcb, |
| (_Vx_usr_arg_t) __func, |
| (_Vx_usr_arg_t) __args, |
| 0, 0, 0, 0, 0, 0, 0); |
| |
| /* If taskCreate succeeds, task_id will be a valid TASK_ID and not zero. */ |
| __RETURN_ERRNO_IF_NOT_OK (!task_id); |
| |
| tcb->task_id = task_id; |
| *__threadid = tcb; |
| |
| return __CHECK_RESULT (taskActivate (task_id)); |
| } |
| |
| int |
| __gthread_equal (__gthread_t __t1, __gthread_t __t2) |
| { |
| return (__t1 == __t2) ? OK : ERROR; |
| } |
| |
| int |
| __gthread_yield (void) |
| { |
| return taskDelay (0); |
| } |
| |
| int |
| __gthread_join (__gthread_t __threadid, void **__value_ptr) |
| { |
| if (!__threadid) |
| return ERROR; |
| |
| /* A thread cannot join itself. */ |
| if (__threadid->task_id == taskIdSelf ()) |
| return ERROR; |
| |
| /* Waiting for the task to set the return value. */ |
| __gthread_mutex_lock (&__threadid->return_value_available); |
| __gthread_mutex_unlock (&__threadid->return_value_available); |
| |
| if (__value_ptr) |
| *__value_ptr = __threadid->return_value; |
| |
| /* The task will be safely be deleted. */ |
| __gthread_mutex_unlock (&(__threadid->delete_ok)); |
| |
| __RETURN_ERRNO_IF_NOT_OK (taskWait (__threadid->task_id, WAIT_FOREVER)); |
| |
| return OK; |
| } |
| |
| int |
| __gthread_detach (__gthread_t __threadid) |
| { |
| if (!__threadid) |
| return ERROR; |
| |
| if (taskIdVerify (__threadid->task_id) != OK) |
| return ERROR; |
| |
| /* The task will be safely be deleted. */ |
| __gthread_mutex_unlock (&(__threadid->delete_ok)); |
| |
| return OK; |
| } |
| |
| #endif |