| 
									
										
										
										
											2022-06-17 21:57:11 +08:00
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2024-02-27 10:00:06 +08:00
										 |  |  |  * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD | 
					
						
							| 
									
										
										
										
											2022-06-17 21:57:11 +08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * SPDX-License-Identifier: Apache-2.0 | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  | #include <errno.h>
 | 
					
						
							|  |  |  | #include <pthread.h>
 | 
					
						
							|  |  |  | #include <string.h>
 | 
					
						
							|  |  |  | #include "esp_err.h"
 | 
					
						
							|  |  |  | #include "esp_log.h"
 | 
					
						
							|  |  |  | #include "freertos/FreeRTOS.h"
 | 
					
						
							|  |  |  | #include "freertos/task.h"
 | 
					
						
							|  |  |  | #include "sys/lock.h"
 | 
					
						
							| 
									
										
										
										
											2019-03-14 17:29:32 +08:00
										 |  |  | #include "sys/queue.h"
 | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include "pthread_internal.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-31 10:02:03 +01:00
										 |  |  | /* Sanity check to ensure that the number of FreeRTOS TLSPs is at least 1 */ | 
					
						
							|  |  |  | #if (CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS < 1)
 | 
					
						
							| 
									
										
										
										
											2024-02-27 10:00:06 +08:00
										 |  |  | #error "CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS cannot be 0 for pthread TLS"
 | 
					
						
							| 
									
										
										
										
											2022-10-31 10:02:03 +01:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  | #define PTHREAD_TLS_INDEX 0
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | typedef void (*pthread_destructor_t)(void*); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* This is a very naive implementation of key-indexed thread local storage, using two linked lists
 | 
					
						
							|  |  |  |    (one is a global list of registered keys, one per thread for thread local storage values). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |    It won't work well if lots of keys & thread-local values are stored (O(n) lookup for both), | 
					
						
							|  |  |  |    but it should work for small amounts of data. | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | typedef struct key_entry_t_ { | 
					
						
							|  |  |  |     pthread_key_t key; | 
					
						
							|  |  |  |     pthread_destructor_t destructor; | 
					
						
							|  |  |  |     SLIST_ENTRY(key_entry_t_) next; | 
					
						
							|  |  |  | } key_entry_t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // List of all keys created with pthread_key_create()
 | 
					
						
							|  |  |  | SLIST_HEAD(key_list_t, key_entry_t_) s_keys = SLIST_HEAD_INITIALIZER(s_keys); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-01 14:28:07 +11:00
										 |  |  | static portMUX_TYPE s_keys_lock = portMUX_INITIALIZER_UNLOCKED; | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  | 
 | 
					
						
							|  |  |  | // List of all value entries associated with a thread via pthread_setspecific()
 | 
					
						
							|  |  |  | typedef struct value_entry_t_ { | 
					
						
							|  |  |  |     pthread_key_t key; | 
					
						
							|  |  |  |     void *value; | 
					
						
							|  |  |  |     SLIST_ENTRY(value_entry_t_) next; | 
					
						
							|  |  |  | } value_entry_t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Type for the head of the list, as saved as a FreeRTOS thread local storage pointer
 | 
					
						
							|  |  |  | SLIST_HEAD(values_list_t_, value_entry_t_); | 
					
						
							|  |  |  | typedef struct values_list_t_ values_list_t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int pthread_key_create(pthread_key_t *key, pthread_destructor_t destructor) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     key_entry_t *new_key = malloc(sizeof(key_entry_t)); | 
					
						
							|  |  |  |     if (new_key == NULL) { | 
					
						
							|  |  |  |         return ENOMEM; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-01 14:28:07 +11:00
										 |  |  |     portENTER_CRITICAL(&s_keys_lock); | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const key_entry_t *head = SLIST_FIRST(&s_keys); | 
					
						
							|  |  |  |     new_key->key = (head == NULL) ? 1 : (head->key + 1); | 
					
						
							|  |  |  |     new_key->destructor = destructor; | 
					
						
							|  |  |  |     *key = new_key->key; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     SLIST_INSERT_HEAD(&s_keys, new_key, next); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-01 14:28:07 +11:00
										 |  |  |     portEXIT_CRITICAL(&s_keys_lock); | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static key_entry_t *find_key(pthread_key_t key) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2017-12-01 14:28:07 +11:00
										 |  |  |     portENTER_CRITICAL(&s_keys_lock); | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |     key_entry_t *result = NULL;; | 
					
						
							|  |  |  |     SLIST_FOREACH(result, &s_keys, next) { | 
					
						
							| 
									
										
										
										
											2024-02-27 10:00:06 +08:00
										 |  |  |         if (result->key == key) { | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2017-12-01 14:28:07 +11:00
										 |  |  |     portEXIT_CRITICAL(&s_keys_lock); | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |     return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int pthread_key_delete(pthread_key_t key) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-01 14:28:07 +11:00
										 |  |  |     portENTER_CRITICAL(&s_keys_lock); | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /* Ideally, we would also walk all tasks' thread local storage value_list here
 | 
					
						
							|  |  |  |        and delete any values associated with this key. We do not do this... | 
					
						
							|  |  |  |     */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     key_entry_t *entry = find_key(key); | 
					
						
							|  |  |  |     if (entry != NULL) { | 
					
						
							|  |  |  |         SLIST_REMOVE(&s_keys, entry, key_entry_t_, next); | 
					
						
							|  |  |  |         free(entry); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-12-01 14:28:07 +11:00
										 |  |  |     portEXIT_CRITICAL(&s_keys_lock); | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Clean up callback for deleted tasks.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |    This is called from one of two places: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |    If the thread was created via pthread_create() then it's called by pthread_task_func() when that thread ends, | 
					
						
							| 
									
										
										
										
											2021-05-13 17:06:21 +10:00
										 |  |  |    or calls pthread_exit(), and the FreeRTOS thread-local-storage is removed before the FreeRTOS task is deleted. | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  | 
 | 
					
						
							|  |  |  |    For other tasks, this is called when the FreeRTOS idle task performs its task cleanup after the task is deleted. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 17:06:21 +10:00
										 |  |  |    There are two reasons for calling it early for pthreads: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |    - To keep the timing consistent with "normal" pthreads, so after pthread_join() the task's destructors have all | 
					
						
							|  |  |  |      been called even if the idle task hasn't run cleanup yet. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |    - The destructor is always called in the context of the thread itself - which is important if the task then calls | 
					
						
							|  |  |  |      pthread_getspecific() or pthread_setspecific() to update the state further, as allowed for in the spec. | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  | */ | 
					
						
							| 
									
										
										
										
											2022-10-31 10:02:03 +01:00
										 |  |  | static void pthread_cleanup_thread_specific_data_callback(int index, void *v_tls) | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  | { | 
					
						
							|  |  |  |     values_list_t *tls = (values_list_t *)v_tls; | 
					
						
							|  |  |  |     assert(tls != NULL); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /* Walk the list, freeing all entries and calling destructors if they are registered */ | 
					
						
							| 
									
										
										
										
											2021-05-13 17:06:21 +10:00
										 |  |  |     while (1) { | 
					
						
							|  |  |  |         value_entry_t *entry = SLIST_FIRST(tls); | 
					
						
							|  |  |  |         if (entry == NULL) { | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         SLIST_REMOVE_HEAD(tls, next); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |         // This is a little slow, walking the linked list of keys once per value,
 | 
					
						
							|  |  |  |         // but assumes that the thread's value list will have less entries
 | 
					
						
							|  |  |  |         // than the keys list
 | 
					
						
							|  |  |  |         key_entry_t *key = find_key(entry->key); | 
					
						
							|  |  |  |         if (key != NULL && key->destructor != NULL) { | 
					
						
							|  |  |  |             key->destructor(entry->value); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         free(entry); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     free(tls); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* this function called from pthread_task_func for "early" cleanup of TLS in a pthread */ | 
					
						
							| 
									
										
										
										
											2022-10-31 10:02:03 +01:00
										 |  |  | void pthread_internal_local_storage_destructor_callback(TaskHandle_t handle) | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2022-10-31 10:02:03 +01:00
										 |  |  |     void *tls = pvTaskGetThreadLocalStoragePointer(handle, PTHREAD_TLS_INDEX); | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |     if (tls != NULL) { | 
					
						
							| 
									
										
										
										
											2022-10-31 10:02:03 +01:00
										 |  |  |         pthread_cleanup_thread_specific_data_callback(PTHREAD_TLS_INDEX, tls); | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |         /* remove the thread-local-storage pointer to avoid the idle task cleanup
 | 
					
						
							|  |  |  |            calling it again... | 
					
						
							|  |  |  |         */ | 
					
						
							| 
									
										
										
										
											2022-10-31 10:02:03 +01:00
										 |  |  | #if !defined(CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS)
 | 
					
						
							| 
									
										
										
										
											2018-02-02 00:17:38 +08:00
										 |  |  |         vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, NULL); | 
					
						
							|  |  |  | #else
 | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |         vTaskSetThreadLocalStoragePointerAndDelCallback(NULL, | 
					
						
							|  |  |  |                                                         PTHREAD_TLS_INDEX, | 
					
						
							|  |  |  |                                                         NULL, | 
					
						
							|  |  |  |                                                         NULL); | 
					
						
							| 
									
										
										
										
											2022-10-31 10:02:03 +01:00
										 |  |  | #endif /* CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS */
 | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static value_entry_t *find_value(const values_list_t *list, pthread_key_t key) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     value_entry_t *result = NULL;; | 
					
						
							|  |  |  |     SLIST_FOREACH(result, list, next) { | 
					
						
							| 
									
										
										
										
											2024-02-27 10:00:06 +08:00
										 |  |  |         if (result->key == key) { | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void *pthread_getspecific(pthread_key_t key) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     values_list_t *tls = (values_list_t *) pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX); | 
					
						
							|  |  |  |     if (tls == NULL) { | 
					
						
							|  |  |  |         return NULL; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     value_entry_t *entry = find_value(tls, key); | 
					
						
							| 
									
										
										
										
											2024-02-27 10:00:06 +08:00
										 |  |  |     if (entry != NULL) { | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |         return entry->value; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int pthread_setspecific(pthread_key_t key, const void *value) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     key_entry_t *key_entry = find_key(key); | 
					
						
							|  |  |  |     if (key_entry == NULL) { | 
					
						
							|  |  |  |         return ENOENT; // this situation is undefined by pthreads standard
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     values_list_t *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX); | 
					
						
							|  |  |  |     if (tls == NULL) { | 
					
						
							|  |  |  |         tls = calloc(1, sizeof(values_list_t)); | 
					
						
							|  |  |  |         if (tls == NULL) { | 
					
						
							|  |  |  |             return ENOMEM; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-10-31 10:02:03 +01:00
										 |  |  | #if !defined(CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS)
 | 
					
						
							| 
									
										
										
										
											2018-02-02 00:17:38 +08:00
										 |  |  |         vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, tls); | 
					
						
							|  |  |  | #else
 | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |         vTaskSetThreadLocalStoragePointerAndDelCallback(NULL, | 
					
						
							|  |  |  |                                                         PTHREAD_TLS_INDEX, | 
					
						
							|  |  |  |                                                         tls, | 
					
						
							| 
									
										
										
										
											2022-10-31 10:02:03 +01:00
										 |  |  |                                                         pthread_cleanup_thread_specific_data_callback); | 
					
						
							|  |  |  | #endif /* CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS */
 | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     value_entry_t *entry = find_value(tls, key); | 
					
						
							|  |  |  |     if (entry != NULL) { | 
					
						
							|  |  |  |         if (value != NULL) { | 
					
						
							|  |  |  |             // cast on next line is necessary as pthreads API uses
 | 
					
						
							|  |  |  |             // 'const void *' here but elsewhere uses 'void *'
 | 
					
						
							|  |  |  |             entry->value = (void *) value; | 
					
						
							|  |  |  |         } else { // value == NULL, remove the entry
 | 
					
						
							|  |  |  |             SLIST_REMOVE(tls, entry, value_entry_t_, next); | 
					
						
							|  |  |  |             free(entry); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } else if (value != NULL) { | 
					
						
							|  |  |  |         entry = malloc(sizeof(value_entry_t)); | 
					
						
							|  |  |  |         if (entry == NULL) { | 
					
						
							|  |  |  |             return ENOMEM; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         entry->key = key; | 
					
						
							|  |  |  |         entry->value = (void *) value; // see note above about cast
 | 
					
						
							| 
									
										
										
										
											2021-05-13 17:06:21 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // insert the new entry at the end of the list. this is important because
 | 
					
						
							|  |  |  |         // a destructor may call pthread_setspecific() to add a new non-NULL value
 | 
					
						
							|  |  |  |         // to the list, and this should be processed after all other entries.
 | 
					
						
							|  |  |  |         //
 | 
					
						
							| 
									
										
										
										
											2022-10-31 10:02:03 +01:00
										 |  |  |         // See pthread_cleanup_thread_specific_data_callback()
 | 
					
						
							| 
									
										
										
										
											2021-05-13 17:06:21 +10:00
										 |  |  |         value_entry_t *last_entry = NULL; | 
					
						
							|  |  |  |         value_entry_t *it; | 
					
						
							|  |  |  |         SLIST_FOREACH(it, tls, next) { | 
					
						
							|  |  |  |             last_entry = it; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (last_entry == NULL) { | 
					
						
							|  |  |  |             SLIST_INSERT_HEAD(tls, entry, next); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             SLIST_INSERT_AFTER(last_entry, entry, next); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2017-10-04 15:52:19 +11:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2019-07-28 11:22:08 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | /* Hook function to force linking this file */ | 
					
						
							| 
									
										
										
										
											2019-07-16 16:33:30 +07:00
										 |  |  | void pthread_include_pthread_local_storage_impl(void) | 
					
						
							| 
									
										
										
										
											2019-07-28 11:22:08 +02:00
										 |  |  | { | 
					
						
							|  |  |  | } |