1 // Copyright 2017 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 #include <errno.h>
15 #include <pthread.h>
16 #include <string.h>
17 #include "esp_err.h"
18 #include "esp_log.h"
19 #include "freertos/FreeRTOS.h"
20 #include "freertos/task.h"
21 #include "sys/lock.h"
22 #include "sys/queue.h"
23 
24 #include "pthread_internal.h"
25 
26 #define PTHREAD_TLS_INDEX 0
27 
28 typedef void (*pthread_destructor_t)(void*);
29 
30 /* This is a very naive implementation of key-indexed thread local storage, using two linked lists
31    (one is a global list of registered keys, one per thread for thread local storage values).
32 
33    It won't work well if lots of keys & thread-local values are stored (O(n) lookup for both),
34    but it should work for small amounts of data.
35 */
36 typedef struct key_entry_t_ {
37     pthread_key_t key;
38     pthread_destructor_t destructor;
39     SLIST_ENTRY(key_entry_t_) next;
40 } key_entry_t;
41 
42 // List of all keys created with pthread_key_create()
43 SLIST_HEAD(key_list_t, key_entry_t_) s_keys = SLIST_HEAD_INITIALIZER(s_keys);
44 
45 static portMUX_TYPE s_keys_lock = portMUX_INITIALIZER_UNLOCKED;
46 
47 // List of all value entries associated with a thread via pthread_setspecific()
48 typedef struct value_entry_t_ {
49     pthread_key_t key;
50     void *value;
51     SLIST_ENTRY(value_entry_t_) next;
52 } value_entry_t;
53 
54 // Type for the head of the list, as saved as a FreeRTOS thread local storage pointer
55 SLIST_HEAD(values_list_t_, value_entry_t_);
56 typedef struct values_list_t_ values_list_t;
57 
pthread_key_create(pthread_key_t * key,pthread_destructor_t destructor)58 int pthread_key_create(pthread_key_t *key, pthread_destructor_t destructor)
59 {
60     key_entry_t *new_key = malloc(sizeof(key_entry_t));
61     if (new_key == NULL) {
62         return ENOMEM;
63     }
64 
65     portENTER_CRITICAL(&s_keys_lock);
66 
67     const key_entry_t *head = SLIST_FIRST(&s_keys);
68     new_key->key = (head == NULL) ? 1 : (head->key + 1);
69     new_key->destructor = destructor;
70     *key = new_key->key;
71 
72     SLIST_INSERT_HEAD(&s_keys, new_key, next);
73 
74     portEXIT_CRITICAL(&s_keys_lock);
75     return 0;
76 }
77 
find_key(pthread_key_t key)78 static key_entry_t *find_key(pthread_key_t key)
79 {
80     portENTER_CRITICAL(&s_keys_lock);
81     key_entry_t *result = NULL;;
82     SLIST_FOREACH(result, &s_keys, next) {
83         if(result->key == key) {
84             break;
85         }
86     }
87     portEXIT_CRITICAL(&s_keys_lock);
88     return result;
89 }
90 
pthread_key_delete(pthread_key_t key)91 int pthread_key_delete(pthread_key_t key)
92 {
93 
94     portENTER_CRITICAL(&s_keys_lock);
95 
96     /* Ideally, we would also walk all tasks' thread local storage value_list here
97        and delete any values associated with this key. We do not do this...
98     */
99 
100     key_entry_t *entry = find_key(key);
101     if (entry != NULL) {
102         SLIST_REMOVE(&s_keys, entry, key_entry_t_, next);
103         free(entry);
104     }
105 
106     portEXIT_CRITICAL(&s_keys_lock);
107 
108     return 0;
109 }
110 
111 /* Clean up callback for deleted tasks.
112 
113    This is called from one of two places:
114 
115    If the thread was created via pthread_create() then it's called by pthread_task_func() when that thread ends,
116    or calls pthread_exit(), and the FreeRTOS thread-local-storage is removed before the FreeRTOS task is deleted.
117 
118    For other tasks, this is called when the FreeRTOS idle task performs its task cleanup after the task is deleted.
119 
120    There are two reasons for calling it early for pthreads:
121 
122    - To keep the timing consistent with "normal" pthreads, so after pthread_join() the task's destructors have all
123      been called even if the idle task hasn't run cleanup yet.
124 
125    - The destructor is always called in the context of the thread itself - which is important if the task then calls
126      pthread_getspecific() or pthread_setspecific() to update the state further, as allowed for in the spec.
127 */
pthread_local_storage_thread_deleted_callback(int index,void * v_tls)128 static void pthread_local_storage_thread_deleted_callback(int index, void *v_tls)
129 {
130     values_list_t *tls = (values_list_t *)v_tls;
131     assert(tls != NULL);
132 
133     /* Walk the list, freeing all entries and calling destructors if they are registered */
134     while (1) {
135         value_entry_t *entry = SLIST_FIRST(tls);
136         if (entry == NULL) {
137             break;
138         }
139         SLIST_REMOVE_HEAD(tls, next);
140 
141         // This is a little slow, walking the linked list of keys once per value,
142         // but assumes that the thread's value list will have less entries
143         // than the keys list
144         key_entry_t *key = find_key(entry->key);
145         if (key != NULL && key->destructor != NULL) {
146             key->destructor(entry->value);
147         }
148         free(entry);
149     }
150     free(tls);
151 }
152 
153 #if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
154 /* Called from FreeRTOS task delete hook */
pthread_local_storage_cleanup(TaskHandle_t task)155 void pthread_local_storage_cleanup(TaskHandle_t task)
156 {
157     void *tls = pvTaskGetThreadLocalStoragePointer(task, PTHREAD_TLS_INDEX);
158     if (tls != NULL) {
159         pthread_local_storage_thread_deleted_callback(PTHREAD_TLS_INDEX, tls);
160         vTaskSetThreadLocalStoragePointer(task, PTHREAD_TLS_INDEX, NULL);
161     }
162 }
163 
164 void __real_vPortCleanUpTCB(void *tcb);
165 
166 /* If static task cleanup hook is defined then its applications responsibility to define `vPortCleanUpTCB`.
167    Here we are wrapping it, so that we can do pthread specific TLS cleanup and then invoke application
168    real specific `vPortCleanUpTCB` */
__wrap_vPortCleanUpTCB(void * tcb)169 void __wrap_vPortCleanUpTCB(void *tcb)
170 {
171     pthread_local_storage_cleanup(tcb);
172     __real_vPortCleanUpTCB(tcb);
173 }
174 #endif
175 
176 /* this function called from pthread_task_func for "early" cleanup of TLS in a pthread */
pthread_internal_local_storage_destructor_callback(void)177 void pthread_internal_local_storage_destructor_callback(void)
178 {
179     void *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
180     if (tls != NULL) {
181         pthread_local_storage_thread_deleted_callback(PTHREAD_TLS_INDEX, tls);
182         /* remove the thread-local-storage pointer to avoid the idle task cleanup
183            calling it again...
184         */
185 #if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
186         vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, NULL);
187 #else
188         vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
189                                                         PTHREAD_TLS_INDEX,
190                                                         NULL,
191                                                         NULL);
192 #endif
193     }
194 }
195 
find_value(const values_list_t * list,pthread_key_t key)196 static value_entry_t *find_value(const values_list_t *list, pthread_key_t key)
197 {
198     value_entry_t *result = NULL;;
199     SLIST_FOREACH(result, list, next) {
200         if(result->key == key) {
201             break;
202         }
203     }
204     return result;
205 }
206 
pthread_getspecific(pthread_key_t key)207 void *pthread_getspecific(pthread_key_t key)
208 {
209     values_list_t *tls = (values_list_t *) pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
210     if (tls == NULL) {
211         return NULL;
212     }
213 
214     value_entry_t *entry = find_value(tls, key);
215     if(entry != NULL) {
216         return entry->value;
217     }
218     return NULL;
219 }
220 
pthread_setspecific(pthread_key_t key,const void * value)221 int pthread_setspecific(pthread_key_t key, const void *value)
222 {
223     key_entry_t *key_entry = find_key(key);
224     if (key_entry == NULL) {
225         return ENOENT; // this situation is undefined by pthreads standard
226     }
227 
228     values_list_t *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
229     if (tls == NULL) {
230         tls = calloc(1, sizeof(values_list_t));
231         if (tls == NULL) {
232             return ENOMEM;
233         }
234 #if defined(CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP)
235         vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, tls);
236 #else
237         vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
238                                                         PTHREAD_TLS_INDEX,
239                                                         tls,
240                                                         pthread_local_storage_thread_deleted_callback);
241 #endif
242     }
243 
244     value_entry_t *entry = find_value(tls, key);
245     if (entry != NULL) {
246         if (value != NULL) {
247             // cast on next line is necessary as pthreads API uses
248             // 'const void *' here but elsewhere uses 'void *'
249             entry->value = (void *) value;
250         } else { // value == NULL, remove the entry
251             SLIST_REMOVE(tls, entry, value_entry_t_, next);
252             free(entry);
253         }
254     } else if (value != NULL) {
255         entry = malloc(sizeof(value_entry_t));
256         if (entry == NULL) {
257             return ENOMEM;
258         }
259         entry->key = key;
260         entry->value = (void *) value; // see note above about cast
261 
262         // insert the new entry at the end of the list. this is important because
263         // a destructor may call pthread_setspecific() to add a new non-NULL value
264         // to the list, and this should be processed after all other entries.
265         //
266         // See pthread_local_storage_thread_deleted_callback()
267         value_entry_t *last_entry = NULL;
268         value_entry_t *it;
269         SLIST_FOREACH(it, tls, next) {
270             last_entry = it;
271         }
272         if (last_entry == NULL) {
273             SLIST_INSERT_HEAD(tls, entry, next);
274         } else {
275             SLIST_INSERT_AFTER(last_entry, entry, next);
276         }
277     }
278 
279     return 0;
280 }
281 
282 /* Hook function to force linking this file */
pthread_include_pthread_local_storage_impl(void)283 void pthread_include_pthread_local_storage_impl(void)
284 {
285 }
286