vxlib.c revision 132718
1/* Copyright (C) 2002 Free Software Foundation, Inc. 2 Contributed by Zack Weinberg <zack@codesourcery.com> 3 4This file is part of GCC. 5 6GCC is free software; you can redistribute it and/or modify it under 7the terms of the GNU General Public License as published by the Free 8Software Foundation; either version 2, or (at your option) any later 9version. 10 11GCC is distributed in the hope that it will be useful, but WITHOUT ANY 12WARRANTY; without even the implied warranty of MERCHANTABILITY or 13FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14for more details. 15 16You should have received a copy of the GNU General Public License 17along with GCC; see the file COPYING. If not, write to the Free 18Software Foundation, 59 Temple Place - Suite 330, Boston, MA 1902111-1307, USA. */ 20 21/* Threads compatibility routines for libgcc2 for VxWorks. 22 These are out-of-line routines called from gthr-vxworks.h. */ 23 24/* As a special exception, if you link this library with other files, 25 some of which are compiled with GCC, to produce an executable, 26 this library does not by itself cause the resulting executable 27 to be covered by the GNU General Public License. 28 This exception does not however invalidate any other reasons why 29 the executable file might be covered by the GNU General Public License. */ 30 31#include "tconfig.h" 32#include "tsystem.h" 33#include "gthr.h" 34 35#include <vxWorks.h> 36#include <vxLib.h> 37#include <taskLib.h> 38#include <taskHookLib.h> 39 40/* Init-once operation. 41 42 This would be a clone of the implementation from gthr-solaris.h, 43 except that we have a bootstrap problem - the whole point of this 44 exercise is to prevent double initialization, but if two threads 45 are racing with each other, once->mutex is liable to be initialized 46 by both. Then each thread will lock its own mutex, and proceed to 47 call the initialization routine. 48 49 So instead we use a bare atomic primitive (vxTas()) to handle 50 mutual exclusion. Threads losing the race then busy-wait, calling 51 taskDelay() to yield the processor, until the initialization is 52 completed. Inefficient, but reliable. */ 53 54int 55__gthread_once (__gthread_once_t *guard, void (*func)(void)) 56{ 57 if (guard->done) 58 return 0; 59 60 while (!vxTas ((void *)&guard->busy)) 61 taskDelay (1); 62 63 /* Only one thread at a time gets here. Check ->done again, then 64 go ahead and call func() if no one has done it yet. */ 65 if (!guard->done) 66 { 67 func (); 68 guard->done = 1; 69 } 70 71 guard->busy = 0; 72 return 0; 73} 74 75/* Thread-specific data. 76 77 We reserve a field in the TCB to point to a dynamically allocated 78 array which is used to store TSD values. A TSD key is simply an 79 offset in this array. The exact location of the TCB field is not 80 known to this code nor to vxlib.c -- all access to it indirects 81 through the routines __gthread_get_tsd_data and 82 __gthread_set_tsd_data, which are provided by the VxWorks kernel. 83 84 There is also a global array which records which keys are valid and 85 which have destructors. 86 87 A task delete hook is installed to execute key destructors. The 88 routines __gthread_enter_tsd_dtor_context and 89 __gthread_leave_tsd_dtor_context, which are also provided by the 90 kernel, ensure that it is safe to call free() on memory allocated 91 by the task being deleted. (This is a no-op on VxWorks 5, but 92 a major undertaking on AE.) 93 94 Since this interface is used to allocate only a small number of 95 keys, the table size is small and static, which simplifies the 96 code quite a bit. Revisit this if and when it becomes necessary. */ 97 98#define MAX_KEYS 4 99 100/* This is the structure pointed to by the pointer returned 101 by __gthread_get_tsd_data. */ 102struct tsd_data 103{ 104 void *values[MAX_KEYS]; 105 unsigned int generation[MAX_KEYS]; 106}; 107 108 109/* kernel provided routines */ 110extern void *__gthread_get_tsd_data (WIND_TCB *tcb); 111extern void __gthread_set_tsd_data (WIND_TCB *tcb, void *data); 112 113extern void __gthread_enter_tsd_dtor_context (WIND_TCB *tcb); 114extern void __gthread_leave_tsd_dtor_context (WIND_TCB *tcb); 115 116typedef void (*fet_callback_t) (WIND_TCB *, unsigned int); 117extern void __gthread_for_all_tasks (fet_callback_t fun, unsigned int number); 118 119/* This is a global structure which records all of the active keys. 120 121 A key is potentially valid (i.e. has been handed out by 122 __gthread_key_create) iff its generation count in this structure is 123 even. In that case, the matching entry in the dtors array is a 124 routine to be called when a thread terminates with a valid, 125 non-NULL specific value for that key. 126 127 A key is actually valid in a thread T iff the generation count 128 stored in this structure is equal to the generation count stored in 129 T's specific-value structure. */ 130 131typedef void (*tsd_dtor) (void *); 132 133struct tsd_keys 134{ 135 tsd_dtor dtor[MAX_KEYS]; 136 unsigned int generation[MAX_KEYS]; 137}; 138 139#define KEY_VALID_P(key) !(tsd_keys.generation[key] & 1) 140 141/* Note: if MAX_KEYS is increased, this initializer must be updated 142 to match. All the generation counts begin at 1, which means no 143 key is valid. */ 144static struct tsd_keys tsd_keys = 145{ 146 { 0, 0, 0, 0 }, 147 { 1, 1, 1, 1 } 148}; 149 150/* This lock protects the tsd_keys structure. */ 151static __gthread_mutex_t tsd_lock; 152 153static __gthread_once_t tsd_init_guard = __GTHREAD_ONCE_INIT; 154 155/* Internal routines. */ 156 157/* The task TCB has just been deleted. Call the destructor 158 function for each TSD key that has both a destructor and 159 a non-NULL specific value in this thread. 160 161 This routine does not need to take tsd_lock; the generation 162 count protects us from calling a stale destructor. It does 163 need to read tsd_keys.dtor[key] atomically. */ 164 165static void 166tsd_delete_hook (WIND_TCB *tcb) 167{ 168 struct tsd_data *data = __gthread_get_tsd_data (tcb); 169 __gthread_key_t key; 170 171 if (data) 172 { 173 __gthread_enter_tsd_dtor_context (tcb); 174 for (key = 0; key < MAX_KEYS; key++) 175 { 176 if (data->generation[key] == tsd_keys.generation[key]) 177 { 178 tsd_dtor dtor = tsd_keys.dtor[key]; 179 180 if (dtor) 181 dtor (data->values[key]); 182 } 183 } 184 free (data); 185 __gthread_set_tsd_data (tcb, 0); 186 __gthread_leave_tsd_dtor_context (tcb); 187 } 188} 189 190/* Initialize global data used by the TSD system. */ 191static void 192tsd_init (void) 193{ 194 taskDeleteHookAdd ((FUNCPTR)tsd_delete_hook); 195 __GTHREAD_MUTEX_INIT_FUNCTION (&tsd_lock); 196} 197 198/* External interface */ 199 200/* Store in KEYP a value which can be passed to __gthread_setspecific/ 201 __gthread_getspecific to store and retrieve a value which is 202 specific to each calling thread. If DTOR is not NULL, it will be 203 called when a thread terminates with a non-NULL specific value for 204 this key, with the value as its sole argument. */ 205 206int 207__gthread_key_create (__gthread_key_t *keyp, tsd_dtor dtor) 208{ 209 __gthread_key_t key; 210 211 __gthread_once (&tsd_init_guard, tsd_init); 212 213 if (__gthread_mutex_lock (&tsd_lock) == ERROR) 214 return errno; 215 216 for (key = 0; key < MAX_KEYS; key++) 217 if (!KEY_VALID_P (key)) 218 goto found_slot; 219 220 /* no room */ 221 __gthread_mutex_unlock (&tsd_lock); 222 return EAGAIN; 223 224 found_slot: 225 tsd_keys.generation[key]++; /* making it even */ 226 tsd_keys.dtor[key] = dtor; 227 *keyp = key; 228 __gthread_mutex_unlock (&tsd_lock); 229 return 0; 230} 231 232/* Invalidate KEY; it can no longer be used as an argument to 233 setspecific/getspecific. Note that this does NOT call destructor 234 functions for any live values for this key. */ 235int 236__gthread_key_delete (__gthread_key_t key) 237{ 238 if (key >= MAX_KEYS) 239 return EINVAL; 240 241 __gthread_once (&tsd_init_guard, tsd_init); 242 243 if (__gthread_mutex_lock (&tsd_lock) == ERROR) 244 return errno; 245 246 if (!KEY_VALID_P (key)) 247 { 248 __gthread_mutex_unlock (&tsd_lock); 249 return EINVAL; 250 } 251 252 tsd_keys.generation[key]++; /* making it odd */ 253 tsd_keys.dtor[key] = 0; 254 255 __gthread_mutex_unlock (&tsd_lock); 256 return 0; 257} 258 259/* Retrieve the thread-specific value for KEY. If it has never been 260 set in this thread, or KEY is invalid, returns NULL. 261 262 It does not matter if this function races with key_create or 263 key_delete; the worst that can happen is you get a value other than 264 the one that a serialized implementation would have provided. */ 265 266void * 267__gthread_getspecific (__gthread_key_t key) 268{ 269 struct tsd_data *data; 270 271 if (key >= MAX_KEYS) 272 return 0; 273 274 data = __gthread_get_tsd_data (taskTcb (taskIdSelf ())); 275 276 if (!data) 277 return 0; 278 279 if (data->generation[key] != tsd_keys.generation[key]) 280 return 0; 281 282 return data->values[key]; 283} 284 285/* Set the thread-specific value for KEY. If KEY is invalid, or 286 memory allocation fails, returns -1, otherwise 0. 287 288 The generation count protects this function against races with 289 key_create/key_delete; the worst thing that can happen is that a 290 value is successfully stored into a dead generation (and then 291 immediately becomes invalid). However, we do have to make sure 292 to read tsd_keys.generation[key] atomically. */ 293 294int 295__gthread_setspecific (__gthread_key_t key, void *value) 296{ 297 struct tsd_data *data; 298 WIND_TCB *tcb; 299 unsigned int generation; 300 301 if (key >= MAX_KEYS) 302 return EINVAL; 303 304 tcb = taskTcb (taskIdSelf ()); 305 data = __gthread_get_tsd_data (tcb); 306 if (!data) 307 { 308 data = malloc (sizeof (struct tsd_data)); 309 if (!data) 310 return ENOMEM; 311 312 memset (data, 0, sizeof (struct tsd_data)); 313 __gthread_set_tsd_data (tcb, data); 314 } 315 316 generation = tsd_keys.generation[key]; 317 318 if (generation & 1) 319 return EINVAL; 320 321 data->generation[key] = generation; 322 data->values[key] = value; 323 324 return 0; 325} 326