| /* |
| * Copyright (c) 2018 Intel Corporation |
| * Copyright (c) 2023 Meta |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #include "posix_internal.h" |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/posix/pthread.h> |
| #include <zephyr/sys/bitarray.h> |
| #include <zephyr/sys/__assert.h> |
| |
| struct pthread_key_data { |
| sys_snode_t node; |
| pthread_thread_data thread_data; |
| }; |
| |
| LOG_MODULE_REGISTER(pthread_key, CONFIG_PTHREAD_KEY_LOG_LEVEL); |
| |
| static struct k_spinlock pthread_key_lock; |
| |
| /* This is non-standard (i.e. an implementation detail) */ |
| #define PTHREAD_KEY_INITIALIZER (-1) |
| |
| /* |
| * We reserve the MSB to mark a pthread_key_t as initialized (from the |
| * perspective of the application). With a linear space, this means that |
| * the theoretical pthread_key_t range is [0,2147483647]. |
| */ |
| BUILD_ASSERT(CONFIG_MAX_PTHREAD_KEY_COUNT < PTHREAD_OBJ_MASK_INIT, |
| "CONFIG_MAX_PTHREAD_KEY_COUNT is too high"); |
| |
| static pthread_key_obj posix_key_pool[CONFIG_MAX_PTHREAD_KEY_COUNT]; |
| SYS_BITARRAY_DEFINE_STATIC(posix_key_bitarray, CONFIG_MAX_PTHREAD_KEY_COUNT); |
| |
| static inline size_t posix_key_to_offset(pthread_key_obj *k) |
| { |
| return k - posix_key_pool; |
| } |
| |
| static inline size_t to_posix_key_idx(pthread_key_t key) |
| { |
| return mark_pthread_obj_uninitialized(key); |
| } |
| |
| static pthread_key_obj *get_posix_key(pthread_key_t key) |
| { |
| int actually_initialized; |
| size_t bit = to_posix_key_idx(key); |
| |
| /* if the provided cond does not claim to be initialized, its invalid */ |
| if (!is_pthread_obj_initialized(key)) { |
| LOG_DBG("Key is uninitialized (%x)", key); |
| return NULL; |
| } |
| |
| /* Mask off the MSB to get the actual bit index */ |
| if (sys_bitarray_test_bit(&posix_key_bitarray, bit, &actually_initialized) < 0) { |
| LOG_DBG("Key is invalid (%x)", key); |
| return NULL; |
| } |
| |
| if (actually_initialized == 0) { |
| /* The cond claims to be initialized but is actually not */ |
| LOG_DBG("Key claims to be initialized (%x)", key); |
| return NULL; |
| } |
| |
| return &posix_key_pool[bit]; |
| } |
| |
| static pthread_key_obj *to_posix_key(pthread_key_t *key) |
| { |
| size_t bit; |
| pthread_key_obj *k; |
| |
| if (*key != PTHREAD_KEY_INITIALIZER) { |
| return get_posix_key(*key); |
| } |
| |
| /* Try and automatically associate a pthread_key_obj */ |
| if (sys_bitarray_alloc(&posix_key_bitarray, 1, &bit) < 0) { |
| /* No keys left to allocate */ |
| return NULL; |
| } |
| |
| /* Record the associated posix_cond in mu and mark as initialized */ |
| *key = mark_pthread_obj_initialized(bit); |
| k = &posix_key_pool[bit]; |
| |
| /* Initialize the condition variable here */ |
| memset(k, 0, sizeof(*k)); |
| |
| return k; |
| } |
| |
| /** |
| * @brief Create a key for thread-specific data |
| * |
| * See IEEE 1003.1 |
| */ |
| int pthread_key_create(pthread_key_t *key, |
| void (*destructor)(void *)) |
| { |
| pthread_key_obj *new_key; |
| |
| *key = PTHREAD_KEY_INITIALIZER; |
| new_key = to_posix_key(key); |
| if (new_key == NULL) { |
| return ENOMEM; |
| } |
| |
| sys_slist_init(&(new_key->key_data_l)); |
| |
| new_key->destructor = destructor; |
| LOG_DBG("Initialized key %p (%x)", new_key, *key); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Delete a key for thread-specific data |
| * |
| * See IEEE 1003.1 |
| */ |
| int pthread_key_delete(pthread_key_t key) |
| { |
| size_t bit; |
| __unused int ret; |
| pthread_key_obj *key_obj; |
| struct pthread_key_data *key_data; |
| sys_snode_t *node_l, *next_node_l; |
| k_spinlock_key_t key_key; |
| |
| key_key = k_spin_lock(&pthread_key_lock); |
| |
| key_obj = get_posix_key(key); |
| if (key_obj == NULL) { |
| k_spin_unlock(&pthread_key_lock, key_key); |
| return EINVAL; |
| } |
| |
| /* Delete thread-specific elements associated with the key */ |
| SYS_SLIST_FOR_EACH_NODE_SAFE(&(key_obj->key_data_l), |
| node_l, next_node_l) { |
| |
| /* Remove the object from the list key_data_l */ |
| key_data = (struct pthread_key_data *) |
| sys_slist_get(&(key_obj->key_data_l)); |
| |
| /* Deallocate the object's memory */ |
| k_free((void *)key_data); |
| LOG_DBG("Freed key data %p for key %x in thread %x", key_data, key, pthread_self()); |
| } |
| |
| bit = posix_key_to_offset(key_obj); |
| ret = sys_bitarray_free(&posix_key_bitarray, 1, bit); |
| __ASSERT_NO_MSG(ret == 0); |
| |
| k_spin_unlock(&pthread_key_lock, key_key); |
| |
| LOG_DBG("Deleted key %p (%x)", key_obj, key); |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Associate a thread-specific value with a key |
| * |
| * See IEEE 1003.1 |
| */ |
| int pthread_setspecific(pthread_key_t key, const void *value) |
| { |
| pthread_key_obj *key_obj; |
| struct posix_thread *thread; |
| struct pthread_key_data *key_data; |
| pthread_thread_data *thread_spec_data; |
| k_spinlock_key_t key_key; |
| sys_snode_t *node_l; |
| int retval = 0; |
| |
| thread = to_posix_thread(pthread_self()); |
| if (thread == NULL) { |
| return EINVAL; |
| } |
| |
| /* Traverse the list of keys set by the thread, looking for key. |
| * If the key is already in the list, re-assign its value. |
| * Else add the key to the thread's list. |
| */ |
| key_key = k_spin_lock(&pthread_key_lock); |
| |
| key_obj = get_posix_key(key); |
| if (key_obj == NULL) { |
| k_spin_unlock(&pthread_key_lock, key_key); |
| return EINVAL; |
| } |
| |
| SYS_SLIST_FOR_EACH_NODE(&(thread->key_list), node_l) { |
| |
| thread_spec_data = (pthread_thread_data *)node_l; |
| |
| if (thread_spec_data->key == key_obj) { |
| |
| /* Key is already present so |
| * associate thread specific data |
| */ |
| thread_spec_data->spec_data = (void *)value; |
| LOG_DBG("Paired key %x to value %p for thread %x", key, value, |
| pthread_self()); |
| goto out; |
| } |
| } |
| |
| if (node_l == NULL) { |
| key_data = k_malloc(sizeof(struct pthread_key_data)); |
| |
| if (key_data == NULL) { |
| LOG_DBG("Failed to allocate key data for key %x", key); |
| retval = ENOMEM; |
| goto out; |
| } |
| |
| LOG_DBG("Allocated key data %p for key %x in thread %x", key_data, key, |
| pthread_self()); |
| |
| /* Associate thread specific data, initialize new key */ |
| key_data->thread_data.key = key_obj; |
| key_data->thread_data.spec_data = (void *)value; |
| |
| /* Append new thread key data to thread's key list */ |
| sys_slist_append((&thread->key_list), (sys_snode_t *)(&key_data->thread_data)); |
| |
| /* Append new key data to the key object's list */ |
| sys_slist_append(&(key_obj->key_data_l), (sys_snode_t *)key_data); |
| |
| LOG_DBG("Paired key %x to value %p for thread %x", key, value, pthread_self()); |
| } |
| |
| out: |
| k_spin_unlock(&pthread_key_lock, key_key); |
| |
| return retval; |
| } |
| |
| /** |
| * @brief Get the thread-specific value associated with the key |
| * |
| * See IEEE 1003.1 |
| */ |
| void *pthread_getspecific(pthread_key_t key) |
| { |
| pthread_key_obj *key_obj; |
| struct posix_thread *thread; |
| pthread_thread_data *thread_spec_data; |
| void *value = NULL; |
| sys_snode_t *node_l; |
| k_spinlock_key_t key_key; |
| |
| thread = to_posix_thread(pthread_self()); |
| if (thread == NULL) { |
| return NULL; |
| } |
| |
| key_key = k_spin_lock(&pthread_key_lock); |
| |
| key_obj = get_posix_key(key); |
| if (key_obj == NULL) { |
| k_spin_unlock(&pthread_key_lock, key_key); |
| return NULL; |
| } |
| |
| /* Traverse the list of keys set by the thread, looking for key */ |
| |
| SYS_SLIST_FOR_EACH_NODE(&(thread->key_list), node_l) { |
| thread_spec_data = (pthread_thread_data *)node_l; |
| if (thread_spec_data->key == key_obj) { |
| /* Key is present, so get the set thread data */ |
| value = thread_spec_data->spec_data; |
| break; |
| } |
| } |
| |
| k_spin_unlock(&pthread_key_lock, key_key); |
| |
| return value; |
| } |