aboutsummaryrefslogtreecommitdiff
path: root/libgcc/config/gthr-vxworks-thread.c
blob: 3d250e6dc2fbc619f01fa0c9ead426602909d44b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
/* 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, &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;

  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, &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;
}

#endif