392 lines
11 KiB
C
392 lines
11 KiB
C
/* Copyright (C) 2002-2022 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
|