/* 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"
#include <taskLib.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 ();
  #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

/* -------------------- Timed Condition Variables --------------------- */

int
__gthread_cond_signal (__gthread_cond_t *cond)
{
  if (!cond)
    return ERROR;

  return __CHECK_RESULT (semGive (*cond));
}

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, &current) == 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;

  __RETURN_ERRNO_IF_NOT_OK (semGive (*mutex));

  __RETURN_ERRNO_IF_NOT_OK (semTake (*cond, waiting_ticks));

  __RETURN_ERRNO_IF_NOT_OK (semTake (*mutex, WAIT_FOREVER));

  return OK;
}

/* --------------------------- 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, &current) == 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;
}
