291d920395
This fixes the name of the macro used to condition the inclusion of an actual implementation of some of the gthread support services for VxWorks, to agree with the side defining that macro based on tests against the targetted VxWorks version major. 2020-10-28 Olivier Hainque <hainque@adacore.com> libgcc/ * config/gthr-vxworks-thread.c: Fix name of macro used to condition the inclusion of an actual implementation.
392 lines
11 KiB
C
392 lines
11 KiB
C
/* Copyright (C) 2002-2020 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
|