| /* | 
 |  * Copyright (c) 2012 The Regents of the University of California | 
 |  * Kevin Klues <klueska@cs.berkeley.edu> | 
 |  * | 
 |  * This file is part of Parlib. | 
 |  * | 
 |  * Parlib is free software: you can redistribute it and/or modify | 
 |  * it under the terms of the Lesser GNU General Public License as published by | 
 |  * the Free Software Foundation, either version 3 of the License, or | 
 |  * (at your option) any later version. | 
 |  *  | 
 |  * Parlib 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 | 
 |  * Lesser GNU General Public License for more details. | 
 |  *  | 
 |  * See COPYING.LESSER for details on the GNU Lesser General Public License. | 
 |  * See COPYING for details on the GNU General Public License. | 
 |  */ | 
 |  | 
 | #include <stddef.h> | 
 | #include <assert.h> | 
 | #include <spinlock.h> | 
 | #include <dtls.h> | 
 | #include <slab.h> | 
 |  | 
 | /* The current dymamic tls implementation uses a locked linked list | 
 |  * to find the key for a given thread. We should probably find a better way to | 
 |  * do this based on a custom lock-free hash table or something. */ | 
 | #include <sys/queue.h> | 
 | #include <spinlock.h> | 
 |  | 
 | /* The dynamic tls key structure */ | 
 | struct dtls_key { | 
 |   spinpdrlock_t lock; | 
 |   int ref_count; | 
 |   bool valid; | 
 |   void (*dtor)(void*); | 
 | }; | 
 |  | 
 | /* The definition of a dtls_key list and its elements */ | 
 | struct dtls_value { | 
 |   TAILQ_ENTRY(dtls_value) link; | 
 |   struct dtls_key *key; | 
 |   void *dtls; | 
 | };  | 
 | TAILQ_HEAD(dtls_list, dtls_value); | 
 |  | 
 | /* A struct containing all of the per thread (i.e. vcore or uthread) data | 
 |  * associated with dtls */ | 
 | typedef struct dtls_data { | 
 |   /* A per-thread list of dtls regions */ | 
 |   struct dtls_list list; | 
 |  | 
 | } dtls_data_t; | 
 |  | 
 | /* A slab of dtls keys (global to all threads) */ | 
 | static struct kmem_cache *__dtls_keys_cache; | 
 |  | 
 | /* A slab of values for use when mapping a dtls_key to its per-thread value */ | 
 | struct kmem_cache *__dtls_values_cache; | 
 |    | 
 | /* A lock protecting access to the caches above */ | 
 | static spinpdrlock_t __dtls_lock; | 
 |  | 
 | static __thread dtls_data_t __dtls_data; | 
 | static __thread bool __dtls_initialized = false; | 
 |  | 
 | static dtls_key_t __allocate_dtls_key()  | 
 | { | 
 |   spin_pdr_lock(&__dtls_lock); | 
 |   dtls_key_t key = kmem_cache_alloc(__dtls_keys_cache, 0); | 
 |   assert(key); | 
 |   key->ref_count = 1; | 
 |   spin_pdr_unlock(&__dtls_lock); | 
 |   return key; | 
 | } | 
 |  | 
 | static void __maybe_free_dtls_key(dtls_key_t key) | 
 | { | 
 |   if(key->ref_count == 0) { | 
 |     spin_pdr_lock(&__dtls_lock); | 
 |     kmem_cache_free(__dtls_keys_cache, key); | 
 |     spin_pdr_unlock(&__dtls_lock); | 
 |   } | 
 | } | 
 |  | 
 | /* Constructor to get a reference to the main thread's TLS descriptor */ | 
 | int dtls_lib_init() | 
 | { | 
 |   /* Make sure this only runs once */ | 
 |   static bool initialized = false; | 
 |   if (initialized) | 
 |       return 0; | 
 |   initialized = true; | 
 |    | 
 |   /* Initialize the global cache of dtls_keys */ | 
 |   __dtls_keys_cache = kmem_cache_create("dtls_keys_cache",  | 
 |     sizeof(struct dtls_key), __alignof__(struct dtls_key), 0, NULL, NULL); | 
 |    | 
 |   __dtls_values_cache = kmem_cache_create("dtls_values_cache",  | 
 |     sizeof(struct dtls_value), __alignof__(struct dtls_value), 0, NULL, NULL); | 
 |    | 
 |   /* Initialize the lock that protects the cache */ | 
 |   spin_pdr_init(&__dtls_lock); | 
 |   return 0; | 
 | } | 
 |  | 
 | dtls_key_t dtls_key_create(dtls_dtor_t dtor) | 
 | { | 
 |   dtls_lib_init(); | 
 |   dtls_key_t key = __allocate_dtls_key(); | 
 |   spin_pdr_init(&key->lock); | 
 |   key->valid = true; | 
 |   key->dtor = dtor; | 
 |   return key; | 
 | } | 
 |  | 
 | void dtls_key_delete(dtls_key_t key) | 
 | { | 
 |   assert(key); | 
 |  | 
 |   spin_pdr_lock(&key->lock); | 
 |   key->valid = false; | 
 |   key->ref_count--; | 
 |   spin_pdr_unlock(&key->lock); | 
 |   __maybe_free_dtls_key(key); | 
 | } | 
 |  | 
 | static inline void __set_dtls(dtls_data_t *dtls_data, dtls_key_t key, void *dtls) | 
 | { | 
 |   assert(key); | 
 |  | 
 |   spin_pdr_lock(&key->lock); | 
 |   key->ref_count++; | 
 |   spin_pdr_unlock(&key->lock); | 
 |  | 
 |   struct dtls_value *v = NULL; | 
 |   TAILQ_FOREACH(v, &dtls_data->list, link) | 
 |     if(v->key == key) break; | 
 |  | 
 |   if(!v) { | 
 |     spin_pdr_lock(&__dtls_lock); | 
 |     v = kmem_cache_alloc(__dtls_values_cache, 0); | 
 |     spin_pdr_unlock(&__dtls_lock); | 
 |     assert(v); | 
 |     v->key = key; | 
 |     TAILQ_INSERT_HEAD(&dtls_data->list, v, link); | 
 |   } | 
 |   v->dtls = dtls; | 
 | } | 
 |  | 
 | static inline void *__get_dtls(dtls_data_t *dtls_data, dtls_key_t key) | 
 | { | 
 |   assert(key); | 
 |  | 
 |   struct dtls_value *v = NULL; | 
 |   TAILQ_FOREACH(v, &dtls_data->list, link) | 
 |     if(v->key == key) return v->dtls; | 
 |   return v; | 
 | } | 
 |  | 
 | static inline void __destroy_dtls(dtls_data_t *dtls_data) | 
 | { | 
 |  struct dtls_value *v,*n; | 
 |   v = TAILQ_FIRST(&dtls_data->list); | 
 |   while(v != NULL) { | 
 |     dtls_key_t key = v->key; | 
 |     bool run_dtor = false; | 
 |    | 
 |     spin_pdr_lock(&key->lock); | 
 |     if(key->valid) | 
 |       if(key->dtor) | 
 |         run_dtor = true; | 
 |     spin_pdr_unlock(&key->lock); | 
 |  | 
 | 	// MUST run the dtor outside the spinlock if we want it to be able to call | 
 | 	// code that may deschedule it for a while (i.e. a mutex). Probably a | 
 | 	// good idea anyway since it can be arbitrarily long and is written by the | 
 | 	// user. Note, there is a small race here on the valid field, whereby we | 
 | 	// may run a destructor on an invalid key. At least the keys memory wont | 
 | 	// be deleted though, as protected by the ref count. Any reasonable usage | 
 | 	// of this interface should safeguard that a key is never destroyed before | 
 | 	// all of the threads that use it have exited anyway. | 
 |     if(run_dtor) { | 
 | 	  void *dtls = v->dtls; | 
 |       v->dtls = NULL; | 
 |       key->dtor(dtls); | 
 |     } | 
 |  | 
 |     spin_pdr_lock(&key->lock); | 
 |     key->ref_count--; | 
 |     spin_pdr_unlock(&key->lock); | 
 |     __maybe_free_dtls_key(key); | 
 |  | 
 |     n = TAILQ_NEXT(v, link); | 
 |     TAILQ_REMOVE(&dtls_data->list, v, link); | 
 |     spin_pdr_lock(&__dtls_lock); | 
 |     kmem_cache_free(__dtls_values_cache, v); | 
 |     spin_pdr_unlock(&__dtls_lock); | 
 |     v = n; | 
 |   } | 
 | } | 
 |  | 
 | void set_dtls(dtls_key_t key, void *dtls) | 
 | { | 
 |   bool initialized = true; | 
 |   dtls_data_t *dtls_data = NULL; | 
 |   if(!__dtls_initialized) { | 
 |     initialized = false; | 
 |     __dtls_initialized  = true; | 
 |   } | 
 |   dtls_data = &__dtls_data; | 
 |   if(!initialized) { | 
 |     TAILQ_INIT(&dtls_data->list); | 
 |   } | 
 |   __set_dtls(dtls_data, key, dtls); | 
 | } | 
 |  | 
 | void *get_dtls(dtls_key_t key) | 
 | { | 
 |   dtls_data_t *dtls_data = NULL; | 
 |   if(!__dtls_initialized) | 
 |     return NULL; | 
 |   dtls_data = &__dtls_data; | 
 |   return __get_dtls(dtls_data, key); | 
 | } | 
 |  | 
 | void destroy_dtls() | 
 | { | 
 |   dtls_data_t *dtls_data = NULL; | 
 |   if(!__dtls_initialized) | 
 |     return; | 
 |   dtls_data = &__dtls_data; | 
 |   __destroy_dtls(dtls_data); | 
 | } | 
 |  |