kconfig: use pkg-config for ncurses detection
[akaros.git] / user / parlib / dtls.c
1 /* Copyright (c) 2012 The Regents of the University of California
2  * Kevin Klues <klueska@cs.berkeley.edu>
3  *
4  * See LICENSE for details. */
5
6 #include <parlib/assert.h>
7 #include <parlib/dtls.h>
8 #include <parlib/slab.h>
9 #include <parlib/spinlock.h>
10 #include <stddef.h>
11
12 /* The current dymamic tls implementation uses a locked linked list
13  * to find the key for a given thread. We should probably find a better way to
14  * do this based on a custom lock-free hash table or something. */
15 #include <parlib/spinlock.h>
16 #include <sys/queue.h>
17
18 /* Define some number of static keys, for which the memory containing the keys
19  * and the per-thread memory for the values associated with those keys is
20  * allocated statically. This is adapted from glibc's notion of the
21  * "specific_1stblock" field embedded directly into its pthread structure for
22  * pthread_get/specific() calls. */
23 #define NUM_STATIC_KEYS 32
24
25 /* The dynamic tls key structure */
26 struct dtls_key {
27         int id;
28         int ref_count;
29         bool valid;
30         void (*dtor)(void *);
31 };
32
33 /* The definition of a dtls_key list and its elements */
34 struct dtls_value {
35         TAILQ_ENTRY(dtls_value) link;
36         struct dtls_key *key;
37         const void *dtls;
38 };
39 TAILQ_HEAD(dtls_list, dtls_value);
40
41 /* A struct containing all of the per thread (i.e. vcore or uthread) data
42  * associated with dtls */
43 typedef struct dtls_data {
44         /* A per-thread list of dtls regions */
45         struct dtls_list list;
46         /* Memory to hold dtls values for the first NUM_STATIC_KEYS keys */
47         struct dtls_value early_values[NUM_STATIC_KEYS];
48 } dtls_data_t;
49
50 /* A slab of dtls keys (global to all threads) */
51 static struct kmem_cache *__dtls_keys_cache;
52
53 /* A slab of values for use when mapping a dtls_key to its per-thread value */
54 struct kmem_cache *__dtls_values_cache;
55
56 static __thread dtls_data_t __dtls_data;
57 static __thread bool __dtls_initialized;
58 static struct dtls_key static_dtls_keys[NUM_STATIC_KEYS];
59 static int num_dtls_keys;
60
61 /* Initialize the slab caches for allocating dtls keys and values. */
62 int dtls_cache_init(void)
63 {
64         /* Make sure this only runs once */
65         static bool initialized;
66
67         if (initialized)
68                 return 0;
69         initialized = true;
70
71         /* Initialize the global cache of dtls_keys */
72         __dtls_keys_cache =
73             kmem_cache_create("dtls_keys_cache", sizeof(struct dtls_key),
74                               __alignof__(struct dtls_key), 0, NULL, NULL,
75                               NULL);
76
77         /* Initialize the global cache of dtls_values */
78         __dtls_values_cache =
79             kmem_cache_create("dtls_values_cache", sizeof(struct dtls_value),
80                               __alignof__(struct dtls_value), 0, NULL, NULL,
81                               NULL);
82
83         return 0;
84 }
85
86 static dtls_key_t __allocate_dtls_key(void)
87 {
88         dtls_key_t key;
89         int keyid = __sync_fetch_and_add(&num_dtls_keys, 1);
90
91         if (keyid < NUM_STATIC_KEYS) {
92                 key = &static_dtls_keys[keyid];
93         } else {
94                 dtls_cache_init();
95                 key = kmem_cache_alloc(__dtls_keys_cache, 0);
96         }
97         assert(key);
98         key->id = keyid;
99         key->ref_count = 1;
100         return key;
101 }
102
103 static void __maybe_free_dtls_key(dtls_key_t key)
104 {
105         int ref_count = __sync_add_and_fetch(&key->ref_count, -1);
106
107         if (ref_count == 0 && key->id >= NUM_STATIC_KEYS)
108                 kmem_cache_free(__dtls_keys_cache, key);
109 }
110
111 static struct dtls_value *__allocate_dtls_value(struct dtls_data *dtls_data,
112                                                 struct dtls_key *key)
113 {
114         struct dtls_value *v;
115
116         if (key->id < NUM_STATIC_KEYS)
117                 v = &dtls_data->early_values[key->id];
118         else
119                 v = kmem_cache_alloc(__dtls_values_cache, 0);
120         assert(v);
121         return v;
122 }
123
124 static void __free_dtls_value(struct dtls_value *v)
125 {
126         if (v->key->id >= NUM_STATIC_KEYS)
127                 kmem_cache_free(__dtls_values_cache, v);
128 }
129
130 dtls_key_t dtls_key_create(dtls_dtor_t dtor)
131 {
132         dtls_key_t key = __allocate_dtls_key();
133
134         key->valid = true;
135         key->dtor = dtor;
136         return key;
137 }
138
139 void dtls_key_delete(dtls_key_t key)
140 {
141         assert(key);
142
143         key->valid = false;
144         __maybe_free_dtls_key(key);
145 }
146
147 static inline struct dtls_value *__get_dtls(dtls_data_t *dtls_data,
148                                             dtls_key_t key)
149 {
150         struct dtls_value *v;
151
152         assert(key);
153         if (key->id < NUM_STATIC_KEYS) {
154                 v = &dtls_data->early_values[key->id];
155                 if (v->key != NULL)
156                         return v;
157         } else {
158                 TAILQ_FOREACH(v, &dtls_data->list, link)
159                         if (v->key == key)
160                                 return v;
161         }
162         return NULL;
163 }
164
165 static inline void __set_dtls(dtls_data_t *dtls_data, dtls_key_t key,
166                               const void *dtls)
167 {
168         struct dtls_value *v;
169
170         assert(key);
171         v = __get_dtls(dtls_data, key);
172         if (!v) {
173                 v = __allocate_dtls_value(dtls_data, key);
174                 __sync_fetch_and_add(&key->ref_count, 1);
175                 v->key = key;
176                 TAILQ_INSERT_HEAD(&dtls_data->list, v, link);
177         }
178         v->dtls = dtls;
179 }
180
181 static inline void __destroy_dtls(dtls_data_t *dtls_data)
182 {
183         struct dtls_value *v, *n;
184         dtls_key_t key;
185         const void *dtls;
186
187         v = TAILQ_FIRST(&dtls_data->list);
188         while (v != NULL) {
189                 key = v->key;
190                 /* The dtor must be called outside of a spinlock so that it can
191                  * call code that may deschedule it for a while (i.e. a mutex).
192                  * Probably a good idea anyway since it can be arbitrarily long
193                  * and is written by the user. Note, there is a small race here
194                  * on the valid field, whereby we may run a destructor on an
195                  * invalid key. At least the keys memory wont be deleted though,
196                  * as protected by the ref count. Any reasonable usage of this
197                  * interface should safeguard that a key is never destroyed
198                  * before all of the threads that use it have exited anyway. */
199                 if (key->valid && key->dtor) {
200                         dtls = v->dtls;
201                         v->dtls = NULL;
202                         key->dtor((void*)dtls);
203                 }
204                 n = TAILQ_NEXT(v, link);
205                 TAILQ_REMOVE(&dtls_data->list, v, link);
206                 /* Free both the key (which is v->key) and v *after* removing v
207                  * from the list.  It's possible that free() will call back into
208                  * the DTLS (e.g.  pthread_getspecific()), and v must be off the
209                  * list by then.
210                  *
211                  * For a similar, hilarious bug in glibc, check out:
212                  * https://sourceware.org/bugzilla/show_bug.cgi?id=3317 */
213                 __maybe_free_dtls_key(key);
214                 __free_dtls_value(v);
215                 v = n;
216         }
217 }
218
219 void set_dtls(dtls_key_t key, const void *dtls)
220 {
221         bool initialized = true;
222         dtls_data_t *dtls_data = NULL;
223
224         if (!__dtls_initialized) {
225                 initialized = false;
226                 __dtls_initialized = true;
227         }
228         dtls_data = &__dtls_data;
229         if (!initialized)
230                 TAILQ_INIT(&dtls_data->list);
231         __set_dtls(dtls_data, key, dtls);
232 }
233
234 void *get_dtls(dtls_key_t key)
235 {
236         dtls_data_t *dtls_data = NULL;
237         struct dtls_value *v;
238
239         if (!__dtls_initialized)
240                 return NULL;
241         dtls_data = &__dtls_data;
242         v = __get_dtls(dtls_data, key);
243         return v ? (void*)v->dtls : NULL;
244 }
245
246 void destroy_dtls(void)
247 {
248         dtls_data_t *dtls_data = NULL;
249
250         if (!__dtls_initialized)
251                 return;
252         dtls_data = &__dtls_data;
253         __destroy_dtls(dtls_data);
254 }