diff --git a/components/esp8266/source/startup.c b/components/esp8266/source/startup.c index 36ceddb6..cf0c7d9d 100644 --- a/components/esp8266/source/startup.c +++ b/components/esp8266/source/startup.c @@ -39,6 +39,7 @@ extern int base_gpio_init(void); extern int watchdog_init(void); extern int wifi_timer_init(void); extern int wifi_nvs_init(void); +extern esp_err_t esp_pthread_init(void); static void user_init_entry(void *param) { @@ -67,6 +68,10 @@ static void user_init_entry(void *param) esp_task_wdt_init(); #endif +#ifdef CONFIG_ENABLE_PTHREAD + assert(esp_pthread_init() == 0); +#endif + app_main(); wifi_task_delete(NULL); diff --git a/components/freertos/port/esp8266/include/freertos/FreeRTOSConfig.h b/components/freertos/port/esp8266/include/freertos/FreeRTOSConfig.h index 2d728385..78d22eeb 100644 --- a/components/freertos/port/esp8266/include/freertos/FreeRTOSConfig.h +++ b/components/freertos/port/esp8266/include/freertos/FreeRTOSConfig.h @@ -152,7 +152,11 @@ NVIC value of 255. */ #define configUSE_NEWLIB_REENTRANT 1 #endif +#ifdef CONFIG_ENABLE_PTHREAD +#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 2 +#else #define configNUM_THREAD_LOCAL_STORAGE_POINTERS 1 +#endif #define configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS 1 /* add this to dump task stack information */ diff --git a/components/newlib/Makefile.projbuild b/components/newlib/Makefile.projbuild index 733676d0..90b99fcb 100644 --- a/components/newlib/Makefile.projbuild +++ b/components/newlib/Makefile.projbuild @@ -1 +1,4 @@ -CFLAGS += -D_CLOCKS_PER_SEC_=CONFIG_FREERTOS_HZ +CFLAGS += -D_CLOCKS_PER_SEC_=CONFIG_FREERTOS_HZ -D_POSIX_THREADS=1 -D_UNIX98_THREAD_MUTEX_ATTRIBUTES=1 +ifdef CONFIG_ENABLE_PTHREAD +CFLAGS += -D_POSIX_THREADS=1 -D_UNIX98_THREAD_MUTEX_AT +endif diff --git a/components/newlib/newlib/include/sys/time.h b/components/newlib/newlib/include/sys/time.h index be16497f..8e3ef808 100644 --- a/components/newlib/newlib/include/sys/time.h +++ b/components/newlib/newlib/include/sys/time.h @@ -20,7 +20,7 @@ struct timeval { }; /* BSD time macros used by RTEMS code */ -#if defined (__rtems__) || defined (__CYGWIN__) +#if defined (__rtems__) || defined (__CYGWIN__) || defined(__XTENSA__) /* Convenience macros for operations on timevals. NOTE: `timercmp' does not work for >= or <=. */ diff --git a/components/newlib/newlib/port/syscall.c b/components/newlib/newlib/port/syscall.c index f289b1df..a7fe205d 100644 --- a/components/newlib/newlib/port/syscall.c +++ b/components/newlib/newlib/port/syscall.c @@ -32,7 +32,7 @@ _ssize_t _read_r(struct _reent *r, int fd, void *buf, size_t len) return 0; } -_ssize_t _write_r(struct _reent *r, int fd, void *buf, size_t len) +_ssize_t _write_r(struct _reent *r, int fd, const void *buf, size_t len) { int i; const char *cbuf = buf; diff --git a/components/pthread/Kconfig b/components/pthread/Kconfig new file mode 100644 index 00000000..6c6d8fa1 --- /dev/null +++ b/components/pthread/Kconfig @@ -0,0 +1,22 @@ +menu "PThreads" + +config ENABLE_PTHREAD + bool "Enable pthread" + default n + help + Enable this option and then pthread is to be used. + +config ESP32_PTHREAD_TASK_PRIO_DEFAULT + int "Default task priority" + range 0 255 + default 5 + help + Priority used to create new tasks with default pthread parameters. + +config ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT + int "Default task stack size" + default 3072 + help + Stack size used to create new tasks with default pthread parameters. + +endmenu diff --git a/components/pthread/component.mk b/components/pthread/component.mk new file mode 100644 index 00000000..7528b1da --- /dev/null +++ b/components/pthread/component.mk @@ -0,0 +1,15 @@ +# +# Component Makefile +# + +ifdef CONFIG_ENABLE_PTHREAD +COMPONENT_SRCDIRS := src +endif + +COMPONENT_ADD_INCLUDEDIRS := include + +COMPONENT_ADD_LDFLAGS := -lpthread + +ifdef CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK +COMPONENT_ADD_LDFLAGS += -Wl,--wrap=vPortCleanUpTCB +endif diff --git a/components/pthread/include/esp_pthread.h b/components/pthread/include/esp_pthread.h new file mode 100644 index 00000000..56849328 --- /dev/null +++ b/components/pthread/include/esp_pthread.h @@ -0,0 +1,67 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** pthread configuration structure that influences pthread creation */ +typedef struct { + size_t stack_size; ///< the stack size of the pthread + size_t prio; ///< the thread's priority + bool inherit_cfg; ///< inherit this configuration further +} esp_pthread_cfg_t; + +/** + * @brief Configure parameters for creating pthread + * + * This API allows you to configure how the subsequent + * pthread_create() call will behave. This call can be used to setup + * configuration parameters like stack size, priority, configuration + * inheritance etc. + * + * If the 'inherit' flag in the configuration structure is enabled, + * then the same configuration is also inherited in the thread + * subtree. + * + * @param cfg The pthread config parameters + * + * @return + * - ESP_OK if configuration was successfully set + * - ESP_ERR_NO_MEM if out of memory + */ +esp_err_t esp_pthread_set_cfg(const esp_pthread_cfg_t *cfg); + +/** + * @brief Get current pthread creation configuration + * + * This will retrieve the current configuration that will be used for + * creating threads. + * + * @param p Pointer to the pthread config structure that will be + * updated with the currently configured parameters + * + * @return + * - ESP_OK if the configuration was available + * - ESP_ERR_NOT_FOUND if a configuration wasn't previously set + */ +esp_err_t esp_pthread_get_cfg(esp_pthread_cfg_t *p); + +#ifdef __cplusplus +} +#endif diff --git a/components/pthread/src/pthread.c b/components/pthread/src/pthread.c new file mode 100644 index 00000000..c9fec073 --- /dev/null +++ b/components/pthread/src/pthread.c @@ -0,0 +1,612 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// This module implements pthread API on top of FreeRTOS. API is implemented to the level allowing +// libstdcxx threading framework to operate correctly. So not all original pthread routines are supported. +// Moreover some implemened functions do not provide full functionality, e.g. pthread_create does not support +// thread's attributes customization (prio, stack size and so on). So if you are not satisfied with default +// behavior use native FreeRTOS API. +// +#include +#include +#include +#include +#include "esp_err.h" +#include "esp_attr.h" +#include "sys/queue.h" +#include "sys/types.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include "pthread_internal.h" +#include "esp_pthread.h" + +#ifdef CONFIG_ENABLE_PTHREAD + +#if portNUM_PROCESSORS == 1 +#undef portENTER_CRITICAL +#undef portEXIT_CRITICAL + +#define portENTER_CRITICAL(l) vPortEnterCritical() +#define portEXIT_CRITICAL(l) vPortExitCritical() +#endif + +#define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL +#include "esp_log.h" +const static char *TAG = "pthread"; + +/** task state */ +enum esp_pthread_task_state { + PTHREAD_TASK_STATE_RUN, + PTHREAD_TASK_STATE_EXIT +}; + +/** pthread thread FreeRTOS wrapper */ +typedef struct esp_pthread_entry { + SLIST_ENTRY(esp_pthread_entry) list_node; ///< Tasks list node struct. + TaskHandle_t handle; ///< FreeRTOS task handle + TaskHandle_t join_task; ///< Handle of the task waiting to join + enum esp_pthread_task_state state; ///< pthread task state + bool detached; ///< True if pthread is detached +} esp_pthread_t; + +/** pthread wrapper task arg */ +typedef struct { + void *(*func)(void *); ///< user task entry + void *arg; ///< user task argument + esp_pthread_cfg_t cfg; ///< pthread configuration +} esp_pthread_task_arg_t; + +/** pthread mutex FreeRTOS wrapper */ +typedef struct { + SemaphoreHandle_t sem; ///< Handle of the task waiting to join + int type; ///< Mutex type. Currently supported PTHREAD_MUTEX_NORMAL and PTHREAD_MUTEX_RECURSIVE +} esp_pthread_mutex_t; + + +static SemaphoreHandle_t s_threads_mux = NULL; +#if portNUM_PROCESSORS > 1 +static portMUX_TYPE s_mutex_init_lock = portMUX_INITIALIZER_UNLOCKED; +#endif +static SLIST_HEAD(esp_thread_list_head, esp_pthread_entry) s_threads_list + = SLIST_HEAD_INITIALIZER(s_threads_list); +static pthread_key_t s_pthread_cfg_key; + + +static int IRAM_ATTR pthread_mutex_lock_internal(esp_pthread_mutex_t *mux, TickType_t tmo); + +static inline void uxPortCompareSet(volatile uint32_t *addr, uint32_t compare, uint32_t *set) +{ + portENTER_CRITICAL(&s_mutex_init_lock); + *addr = compare; + *set = 0; + portEXIT_CRITICAL(&s_mutex_init_lock); +} + +static void esp_pthread_cfg_key_destructor(void *value) +{ + free(value); +} + +esp_err_t esp_pthread_init(void) +{ + if (pthread_key_create(&s_pthread_cfg_key, esp_pthread_cfg_key_destructor) != 0) { + return ESP_ERR_NO_MEM; + } + s_threads_mux = xSemaphoreCreateMutex(); + if (s_threads_mux == NULL) { + pthread_key_delete(s_pthread_cfg_key); + return ESP_ERR_NO_MEM; + } + return ESP_OK; +} + +static void *pthread_list_find_item(void *(*item_check)(esp_pthread_t *, void *arg), void *check_arg) +{ + esp_pthread_t *it; + SLIST_FOREACH(it, &s_threads_list, list_node) { + void *val = item_check(it, check_arg); + if (val) { + return val; + } + } + return NULL; +} + +static void *pthread_get_handle_by_desc(esp_pthread_t *item, void *desc) +{ + if (item == desc) { + return item->handle; + } + return NULL; +} + +static void *pthread_get_desc_by_handle(esp_pthread_t *item, void *hnd) +{ + if (hnd == item->handle) { + return item; + } + return NULL; +} + +static inline TaskHandle_t pthread_find_handle(pthread_t thread) +{ + return pthread_list_find_item(pthread_get_handle_by_desc, (void *)thread); +} + +static esp_pthread_t *pthread_find(TaskHandle_t task_handle) +{ + return pthread_list_find_item(pthread_get_desc_by_handle, task_handle); +} + +static void pthread_delete(esp_pthread_t *pthread) +{ + SLIST_REMOVE(&s_threads_list, pthread, esp_pthread_entry, list_node); + free(pthread); +} + + +/* Call this function to configure pthread stacks in Pthreads */ +esp_err_t esp_pthread_set_cfg(const esp_pthread_cfg_t *cfg) +{ + /* If a value is already set, update that value */ + esp_pthread_cfg_t *p = pthread_getspecific(s_pthread_cfg_key); + if (!p) { + p = malloc(sizeof(esp_pthread_cfg_t)); + if (!p) { + return ESP_ERR_NO_MEM; + } + } + *p = *cfg; + pthread_setspecific(s_pthread_cfg_key, p); + return 0; +} + +esp_err_t esp_pthread_get_cfg(esp_pthread_cfg_t *p) +{ + esp_pthread_cfg_t *cfg = pthread_getspecific(s_pthread_cfg_key); + if (cfg) { + *p = *cfg; + return ESP_OK; + } + memset(p, 0, sizeof(*p)); + return ESP_ERR_NOT_FOUND; +} + +static void pthread_task_func(void *arg) +{ + esp_pthread_task_arg_t *task_arg = (esp_pthread_task_arg_t *)arg; + + ESP_LOGV(TAG, "%s ENTER %p", __FUNCTION__, task_arg->func); + // wait for start + xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); + + if (task_arg->cfg.inherit_cfg) { + /* If inherit option is set, then do a set_cfg() ourselves for future forks */ + esp_pthread_set_cfg(&task_arg->cfg); + } + ESP_LOGV(TAG, "%s START %p", __FUNCTION__, task_arg->func); + task_arg->func(task_arg->arg); + ESP_LOGV(TAG, "%s END %p", __FUNCTION__, task_arg->func); + free(task_arg); + + /* preemptively clean up thread local storage, rather than + waiting for the idle task to clean up the thread */ + pthread_internal_local_storage_destructor_callback(); + + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + esp_pthread_t *pthread = pthread_find(xTaskGetCurrentTaskHandle()); + if (!pthread) { + assert(false && "Failed to find pthread for current task!"); + } + if (pthread->detached) { + // auto-free for detached threads + pthread_delete(pthread); + } else { + // Remove from list, it indicates that task has exited + if (pthread->join_task) { + // notify join + xTaskNotify(pthread->join_task, 0, eNoAction); + } else { + pthread->state = PTHREAD_TASK_STATE_EXIT; + } + } + xSemaphoreGive(s_threads_mux); + + ESP_LOGD(TAG, "Task stk_wm = %lu", uxTaskGetStackHighWaterMark(NULL)); + vTaskDelete(NULL); + + ESP_LOGV(TAG, "%s EXIT", __FUNCTION__); +} + +int pthread_create(pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine) (void *), void *arg) +{ + TaskHandle_t xHandle = NULL; + + ESP_LOGV(TAG, "%s", __FUNCTION__); + if (attr) { + ESP_LOGE(TAG, "%s: attrs not supported!", __FUNCTION__); + return ENOSYS; + } + esp_pthread_task_arg_t *task_arg = malloc(sizeof(esp_pthread_task_arg_t)); + if (task_arg == NULL) { + ESP_LOGE(TAG, "Failed to allocate task args!"); + return ENOMEM; + } + memset(task_arg, 0, sizeof(esp_pthread_task_arg_t)); + esp_pthread_t *pthread = malloc(sizeof(esp_pthread_t)); + if (pthread == NULL) { + ESP_LOGE(TAG, "Failed to allocate pthread data!"); + free(task_arg); + return ENOMEM; + } + uint32_t stack_size = CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT; + BaseType_t prio = CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT; + esp_pthread_cfg_t *pthread_cfg = pthread_getspecific(s_pthread_cfg_key); + if (pthread_cfg) { + if (pthread_cfg->stack_size) { + stack_size = pthread_cfg->stack_size; + } + if (pthread_cfg->prio && pthread_cfg->prio < configMAX_PRIORITIES) { + prio = pthread_cfg->prio; + } + task_arg->cfg = *pthread_cfg; + } + memset(pthread, 0, sizeof(esp_pthread_t)); + task_arg->func = start_routine; + task_arg->arg = arg; + BaseType_t res = xTaskCreate(&pthread_task_func, "pthread", stack_size, + task_arg, prio, &xHandle); + if(res != pdPASS) { + ESP_LOGE(TAG, "Failed to create task!"); + free(pthread); + free(task_arg); + if (res == errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY) { + return ENOMEM; + } else { + return EAGAIN; + } + } + pthread->handle = xHandle; + + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + SLIST_INSERT_HEAD(&s_threads_list, pthread, list_node); + xSemaphoreGive(s_threads_mux); + + // start task + xTaskNotify(xHandle, 0, eNoAction); + + *thread = (pthread_t)pthread; // pointer value fit into pthread_t (uint32_t) + + ESP_LOGV(TAG, "Created task %x", (uint32_t)xHandle); + + return 0; +} + +int pthread_join(pthread_t thread, void **retval) +{ + esp_pthread_t *pthread = (esp_pthread_t *)thread; + int ret = 0; + bool wait = false; + + ESP_LOGV(TAG, "%s %p", __FUNCTION__, pthread); + + // find task + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + TaskHandle_t handle = pthread_find_handle(thread); + if (!handle) { + // not found + ret = ESRCH; + } else if (pthread->join_task) { + // already have waiting task to join + ret = EINVAL; + } else if (handle == xTaskGetCurrentTaskHandle()) { + // join to self not allowed + ret = EDEADLK; + } else { + esp_pthread_t *cur_pthread = pthread_find(xTaskGetCurrentTaskHandle()); + if (cur_pthread && cur_pthread->join_task == handle) { + // join to each other not allowed + ret = EDEADLK; + } else { + if (pthread->state == PTHREAD_TASK_STATE_RUN) { + pthread->join_task = xTaskGetCurrentTaskHandle(); + wait = true; + } else { + pthread_delete(pthread); + } + } + } + xSemaphoreGive(s_threads_mux); + + if (ret == 0 && wait) { + xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + pthread_delete(pthread); + xSemaphoreGive(s_threads_mux); + } + + if (retval) { + *retval = 0; // no exit code in FreeRTOS + } + + ESP_LOGV(TAG, "%s %p EXIT %d", __FUNCTION__, pthread, ret); + return ret; +} + +int pthread_detach(pthread_t thread) +{ + esp_pthread_t *pthread = (esp_pthread_t *)thread; + int ret = 0; + + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + TaskHandle_t handle = pthread_find_handle(thread); + if (!handle) { + ret = ESRCH; + } else { + pthread->detached = true; + } + xSemaphoreGive(s_threads_mux); + ESP_LOGV(TAG, "%s %p EXIT %d", __FUNCTION__, pthread, ret); + return ret; +} + +int pthread_cancel(pthread_t thread) +{ + ESP_LOGE(TAG, "%s: not supported!", __FUNCTION__); + return ENOSYS; +} + +int sched_yield( void ) +{ + vTaskDelay(0); + return 0; +} + +pthread_t pthread_self(void) +{ + if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) { + assert(false && "Failed to lock threads list!"); + } + esp_pthread_t *pthread = pthread_find(xTaskGetCurrentTaskHandle()); + if (!pthread) { + assert(false && "Failed to find current thread ID!"); + } + xSemaphoreGive(s_threads_mux); + return (pthread_t)pthread; +} + +int pthread_equal(pthread_t t1, pthread_t t2) +{ + return t1 == t2 ? 1 : 0; +} + +/***************** ONCE ******************/ +int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)) +{ + if (once_control == NULL || init_routine == NULL || !once_control->is_initialized) { + ESP_LOGE(TAG, "%s: Invalid args!", __FUNCTION__); + return EINVAL; + } + + uint32_t res = 1; +#if defined(CONFIG_SPIRAM_SUPPORT) + if (esp_ptr_external_ram(once_control)) { + uxPortCompareSetExtram((uint32_t *) &once_control->init_executed, 0, &res); + } else { +#endif + uxPortCompareSet((uint32_t *) &once_control->init_executed, 0, &res); +#if defined(CONFIG_SPIRAM_SUPPORT) + } +#endif + // Check if compare and set was successful + if (res == 0) { + ESP_LOGV(TAG, "%s: call init_routine %p", __FUNCTION__, once_control); + init_routine(); + } + + return 0; +} + +/***************** MUTEX ******************/ +static int mutexattr_check(const pthread_mutexattr_t *attr) +{ + if (attr->type < PTHREAD_MUTEX_NORMAL || attr->type > PTHREAD_MUTEX_RECURSIVE) { + return EINVAL; + } + return 0; +} + +int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) +{ + int type = PTHREAD_MUTEX_NORMAL; + + if (!mutex) { + return EINVAL; + } + + if (attr) { + if (!attr->is_initialized) { + return EINVAL; + } + int res = mutexattr_check(attr); + if (res) { + return res; + } + type = attr->type; + } + + esp_pthread_mutex_t *mux = (esp_pthread_mutex_t *)malloc(sizeof(esp_pthread_mutex_t)); + if (!mux) { + return ENOMEM; + } + mux->type = type; + + if (mux->type == PTHREAD_MUTEX_RECURSIVE) { + mux->sem = xSemaphoreCreateRecursiveMutex(); + } else { + mux->sem = xSemaphoreCreateMutex(); + } + if (!mux->sem) { + free(mux); + return EAGAIN; + } + + *mutex = (pthread_mutex_t)mux; // pointer value fit into pthread_mutex_t (uint32_t) + + return 0; +} + +int pthread_mutex_destroy(pthread_mutex_t *mutex) +{ + esp_pthread_mutex_t *mux; + + ESP_LOGV(TAG, "%s %p", __FUNCTION__, mutex); + + if (!mutex) { + return EINVAL; + } + mux = (esp_pthread_mutex_t *)*mutex; + + // check if mux is busy + int res = pthread_mutex_lock_internal(mux, 0); + if (res == EBUSY) { + return EBUSY; + } + + vSemaphoreDelete(mux->sem); + free(mux); + + return 0; +} + +static int IRAM_ATTR pthread_mutex_lock_internal(esp_pthread_mutex_t *mux, TickType_t tmo) +{ + if (mux->type == PTHREAD_MUTEX_RECURSIVE) { + if (xSemaphoreTakeRecursive(mux->sem, tmo) != pdTRUE) { + return EBUSY; + } + } else { + if (xSemaphoreTake(mux->sem, tmo) != pdTRUE) { + return EBUSY; + } + } + + return 0; +} + +static int pthread_mutex_init_if_static(pthread_mutex_t *mutex) { + int res = 0; + if ((intptr_t) *mutex == PTHREAD_MUTEX_INITIALIZER) { + portENTER_CRITICAL(&s_mutex_init_lock); + if ((intptr_t) *mutex == PTHREAD_MUTEX_INITIALIZER) { + res = pthread_mutex_init(mutex, NULL); + } + portEXIT_CRITICAL(&s_mutex_init_lock); + } + return res; +} + +int IRAM_ATTR pthread_mutex_lock(pthread_mutex_t *mutex) +{ + if (!mutex) { + return EINVAL; + } + int res = pthread_mutex_init_if_static(mutex); + if (res != 0) { + return res; + } + return pthread_mutex_lock_internal((esp_pthread_mutex_t *)*mutex, portMAX_DELAY); +} + +int IRAM_ATTR pthread_mutex_trylock(pthread_mutex_t *mutex) +{ + if (!mutex) { + return EINVAL; + } + int res = pthread_mutex_init_if_static(mutex); + if (res != 0) { + return res; + } + return pthread_mutex_lock_internal((esp_pthread_mutex_t *)*mutex, 0); +} + +int IRAM_ATTR pthread_mutex_unlock(pthread_mutex_t *mutex) +{ + esp_pthread_mutex_t *mux; + + if (!mutex) { + return EINVAL; + } + mux = (esp_pthread_mutex_t *)*mutex; + + if (mux->type == PTHREAD_MUTEX_RECURSIVE) { + xSemaphoreGiveRecursive(mux->sem); + } else { + xSemaphoreGive(mux->sem); + } + return 0; +} + +int pthread_mutexattr_init(pthread_mutexattr_t *attr) +{ + if (!attr) { + return EINVAL; + } + attr->type = PTHREAD_MUTEX_NORMAL; + attr->is_initialized = 1; + return 0; +} + +int pthread_mutexattr_destroy(pthread_mutexattr_t *attr) +{ + if (!attr) { + return EINVAL; + } + attr->is_initialized = 0; + return 0; +} + +int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type) +{ + ESP_LOGE(TAG, "%s: not supported!", __FUNCTION__); + return ENOSYS; +} + +int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) +{ + if (!attr) { + return EINVAL; + } + pthread_mutexattr_t tmp_attr = {.type = type}; + int res = mutexattr_check(&tmp_attr); + if (!res) { + attr->type = type; + } + return res; +} + +#endif diff --git a/components/pthread/src/pthread_cond_var.c b/components/pthread/src/pthread_cond_var.c new file mode 100644 index 00000000..98f53fda --- /dev/null +++ b/components/pthread/src/pthread_cond_var.c @@ -0,0 +1,205 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This is a simple implementation of pthread condition variables. In essence, +// the waiter creates its own semaphore to wait on and pushes it in the cond var +// specific list. Upon notify and broadcast, all the waiters for the given cond +// var are woken up. + +#include +#include +#include +#include +#include "esp_err.h" +#include "esp_attr.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "freertos/private/list.h" + +#include +#include + +#ifdef CONFIG_ENABLE_PTHREAD + +#define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL +#include "esp_log.h" +const static char *TAG = "esp_pthread"; + +typedef struct esp_pthread_cond_waiter { + SemaphoreHandle_t wait_sem; ///< task specific semaphore to wait on + TAILQ_ENTRY(esp_pthread_cond_waiter) link; ///< stash on the list of semaphores to be notified +} esp_pthread_cond_waiter_t; + +typedef struct esp_pthread_cond { + _lock_t lock; ///< lock that protects the list of semaphores + TAILQ_HEAD(, esp_pthread_cond_waiter) waiter_list; ///< head of the list of semaphores +} esp_pthread_cond_t; + +int pthread_cond_signal(pthread_cond_t *cv) +{ + if (cv == NULL || *cv == (pthread_cond_t) 0) { + return EINVAL; + } + + esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv; + + _lock_acquire_recursive(&cond->lock); + esp_pthread_cond_waiter_t *entry; + entry = TAILQ_FIRST(&cond->waiter_list); + if (entry) { + xSemaphoreGive(entry->wait_sem); + } + _lock_release_recursive(&cond->lock); + + return 0; +} + +int pthread_cond_broadcast(pthread_cond_t *cv) +{ + if (cv == NULL || *cv == (pthread_cond_t) 0) { + return EINVAL; + } + + esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv; + + _lock_acquire_recursive(&cond->lock); + esp_pthread_cond_waiter_t *entry; + TAILQ_FOREACH(entry, &cond->waiter_list, link) { + xSemaphoreGive(entry->wait_sem); + } + _lock_release_recursive(&cond->lock); + + return 0; +} + +int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mut) +{ + return pthread_cond_timedwait(cv, mut, NULL); +} + +int pthread_cond_timedwait(pthread_cond_t *cv, pthread_mutex_t *mut, const struct timespec *to) +{ + int ret; + TickType_t timeout_ticks; + + if (cv == NULL || *cv == (pthread_cond_t) 0) { + return EINVAL; + } + + esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv; + + if (to == NULL) { + timeout_ticks = portMAX_DELAY; + } else { + struct timeval abs_time, cur_time, diff_time; + long timeout_msec; + + gettimeofday(&cur_time, NULL); + + abs_time.tv_sec = to->tv_sec; + abs_time.tv_usec = to->tv_nsec / 1000; + + if (timercmp(&abs_time, &cur_time, <)) { + /* As per the pthread spec, if the time has already + * passed, no sleep is required. + */ + timeout_msec = 0; + } else { + timersub(&abs_time, &cur_time, &diff_time); + timeout_msec = (diff_time.tv_sec * 1000) + (diff_time.tv_usec / 1000); + } + + if (timeout_msec <= 0) { + return ETIMEDOUT; + } + + timeout_ticks = timeout_msec / portTICK_PERIOD_MS; + } + + esp_pthread_cond_waiter_t w; + w.wait_sem = xSemaphoreCreateCounting(1, 0); /* First get will block */ + + _lock_acquire_recursive(&cond->lock); + TAILQ_INSERT_TAIL(&cond->waiter_list, &w, link); + _lock_release_recursive(&cond->lock); + pthread_mutex_unlock(mut); + + if (xSemaphoreTake(w.wait_sem, timeout_ticks) == pdTRUE) { + ret = 0; + } else { + ret = ETIMEDOUT; + } + + _lock_acquire_recursive(&cond->lock); + TAILQ_REMOVE(&cond->waiter_list, &w, link); + _lock_release_recursive(&cond->lock); + vSemaphoreDelete(w.wait_sem); + + pthread_mutex_lock(mut); + return ret; +} + +int pthread_condattr_init(pthread_condattr_t *attr) +{ + ESP_LOGV(TAG, "%s not yet implemented (%p)", __FUNCTION__, attr); + return ENOSYS; +} + +int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *att) +{ + (void) att; /* Unused argument as of now */ + + if (cv == NULL) { + return EINVAL; + } + + esp_pthread_cond_t *cond = (esp_pthread_cond_t *) calloc(1, sizeof(esp_pthread_cond_t)); + if (cond == NULL) { + return ENOMEM; + } + + _lock_init_recursive(&cond->lock); + TAILQ_INIT(&cond->waiter_list); + + *cv = (pthread_cond_t) cond; + return 0; +} + +int pthread_cond_destroy(pthread_cond_t *cv) +{ + int ret = 0; + + if (cv == NULL || *cv == (pthread_cond_t) 0) { + return EINVAL; + } + + esp_pthread_cond_t *cond = (esp_pthread_cond_t *) *cv; + + _lock_acquire_recursive(&cond->lock); + if (!TAILQ_EMPTY(&cond->waiter_list)) { + ret = EBUSY; + } + _lock_release_recursive(&cond->lock); + + if (ret == 0) { + *cv = (pthread_cond_t) 0; + _lock_close_recursive(&cond->lock); + free(cond); + } + + return ret; +} + +#endif diff --git a/components/pthread/src/pthread_internal.h b/components/pthread/src/pthread_internal.h new file mode 100644 index 00000000..6ed2fe45 --- /dev/null +++ b/components/pthread/src/pthread_internal.h @@ -0,0 +1,16 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#pragma once + +void pthread_internal_local_storage_destructor_callback(); diff --git a/components/pthread/src/pthread_local_storage.c b/components/pthread/src/pthread_local_storage.c new file mode 100644 index 00000000..dd10d50e --- /dev/null +++ b/components/pthread/src/pthread_local_storage.c @@ -0,0 +1,272 @@ +// Copyright 2017 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include +#include +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sys/lock.h" +#include "sys/queue.h" + +#include "pthread_internal.h" + +#ifdef CONFIG_ENABLE_PTHREAD + +#define PTHREAD_TLS_INDEX 1 + +#if portNUM_PROCESSORS == 1 +#undef portENTER_CRITICAL +#undef portEXIT_CRITICAL + +#define portENTER_CRITICAL(l) vPortEnterCritical() +#define portEXIT_CRITICAL(l) vPortExitCritical() +#endif + +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); + +#if portNUM_PROCESSORS > 1 +static portMUX_TYPE s_keys_lock = portMUX_INITIALIZER_UNLOCKED; +#endif + +// 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; + } + + portENTER_CRITICAL(&s_keys_lock); + + 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); + + portEXIT_CRITICAL(&s_keys_lock); + return 0; +} + +static key_entry_t *find_key(pthread_key_t key) +{ + portENTER_CRITICAL(&s_keys_lock); + key_entry_t *result = NULL;; + SLIST_FOREACH(result, &s_keys, next) { + if(result->key == key) { + break; + } + } + portEXIT_CRITICAL(&s_keys_lock); + return result; +} + +int pthread_key_delete(pthread_key_t key) +{ + + portENTER_CRITICAL(&s_keys_lock); + + /* 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); + } + + portEXIT_CRITICAL(&s_keys_lock); + + 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, + and the FreeRTOS thread-local-storage is removed before the FreeRTOS task is deleted. + + For other tasks, this is called when the FreeRTOS idle task performs its task cleanup after the task is deleted. + + (The reason for calling it early for pthreads is 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.) +*/ +static void pthread_local_storage_thread_deleted_callback(int index, void *v_tls) +{ + 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 */ + value_entry_t *entry = SLIST_FIRST(tls); + while(entry != NULL) { + // 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); + } + value_entry_t *next_entry = SLIST_NEXT(entry, next); + free(entry); + entry = next_entry; + } + free(tls); +} + +#if defined(CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK) +/* Called from FreeRTOS task delete hook */ +void pthread_local_storage_cleanup(TaskHandle_t task) +{ + void *tls = pvTaskGetThreadLocalStoragePointer(task, PTHREAD_TLS_INDEX); + if (tls != NULL) { + pthread_local_storage_thread_deleted_callback(PTHREAD_TLS_INDEX, tls); + vTaskSetThreadLocalStoragePointer(task, PTHREAD_TLS_INDEX, NULL); + } +} + +void __real_vPortCleanUpTCB(void *tcb); + +/* If static task cleanup hook is defined then its applications responsibility to define `vPortCleanUpTCB`. + Here we are wrapping it, so that we can do pthread specific TLS cleanup and then invoke application + real specific `vPortCleanUpTCB` */ +void __wrap_vPortCleanUpTCB(void *tcb) +{ + pthread_local_storage_cleanup(tcb); + __real_vPortCleanUpTCB(tcb); +} +#endif + +/* this function called from pthread_task_func for "early" cleanup of TLS in a pthread */ +void pthread_internal_local_storage_destructor_callback() +{ + void *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX); + if (tls != NULL) { + pthread_local_storage_thread_deleted_callback(PTHREAD_TLS_INDEX, tls); + /* remove the thread-local-storage pointer to avoid the idle task cleanup + calling it again... + */ +#if defined(CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK) + vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, NULL); +#else + vTaskSetThreadLocalStoragePointerAndDelCallback(NULL, + PTHREAD_TLS_INDEX, + NULL, + NULL); +#endif + } +} + +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) { + if(result->key == key) { + 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); + if(entry != NULL) { + 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; + } +#if defined(CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK) + vTaskSetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX, tls); +#else + vTaskSetThreadLocalStoragePointerAndDelCallback(NULL, + PTHREAD_TLS_INDEX, + tls, + pthread_local_storage_thread_deleted_callback); +#endif + } + + 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 + SLIST_INSERT_HEAD(tls, entry, next); + } + + return 0; +} + +#endif diff --git a/components/pthread/test/component.mk b/components/pthread/test/component.mk new file mode 100644 index 00000000..af9fbb83 --- /dev/null +++ b/components/pthread/test/component.mk @@ -0,0 +1,9 @@ +# +#Component Makefile +# + +COMPONENT_SRCDIRS := . + +#CXXFLAGS += -H + +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/pthread/test/test_pthread_local_storage.c b/components/pthread/test/test_pthread_local_storage.c new file mode 100644 index 00000000..0b7fe96a --- /dev/null +++ b/components/pthread/test/test_pthread_local_storage.c @@ -0,0 +1,131 @@ +// Test pthread_create_key, pthread_delete_key, pthread_setspecific, pthread_getspecific +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +TEST_CASE("pthread local storage basics", "[pthread]") +{ + pthread_key_t key; + TEST_ASSERT_EQUAL(0, pthread_key_create(&key, NULL)); + + TEST_ASSERT_NULL(pthread_getspecific(key)); + int val = 3; + + printf("Setting to %p...\n", &val); + TEST_ASSERT_EQUAL(0, pthread_setspecific(key, &val)); + + printf("Reading back...\n"); + TEST_ASSERT_EQUAL_PTR(&val, pthread_getspecific(key)); + + printf("Setting to NULL...\n"); + TEST_ASSERT_EQUAL(0, pthread_setspecific(key, NULL)); + + printf("Reading back...\n"); + TEST_ASSERT_NULL(pthread_getspecific(key)); + + TEST_ASSERT_EQUAL(0, pthread_key_delete(key)); +} + +TEST_CASE("pthread local storage unique keys", "[pthread]") +{ + const int NUM_KEYS = 10; + pthread_key_t keys[NUM_KEYS]; + + for (int i = 0; i < NUM_KEYS; i++) { + TEST_ASSERT_EQUAL(0, pthread_key_create(&keys[i], NULL)); + printf("New key %d = %d\n", i, keys[i]); + } + + for (int i = 0; i < NUM_KEYS; i++) { + for (int j = 0; j < NUM_KEYS; j++) { + if (i != j) { + TEST_ASSERT_NOT_EQUAL(keys[i], keys[j]); + } + } + } + + for (int i = 0; i < NUM_KEYS; i++) { + TEST_ASSERT_EQUAL(0, pthread_key_delete(keys[i])); + } +} + +static void test_pthread_destructor(void *); +static void *expected_destructor_ptr; +static void *actual_destructor_ptr; +static void *thread_test_pthread_destructor(void *); + +TEST_CASE("pthread local storage destructor", "[pthread]") +{ + pthread_t thread; + pthread_key_t key = -1; + + expected_destructor_ptr = NULL; + actual_destructor_ptr = NULL; + + TEST_ASSERT_EQUAL(0, pthread_key_create(&key, test_pthread_destructor)); + + TEST_ASSERT_EQUAL(0, pthread_create(&thread, NULL, thread_test_pthread_destructor, (void *)key)); + TEST_ASSERT_EQUAL(0, pthread_join(thread, NULL)); + + printf("Joined...\n"); + TEST_ASSERT_NOT_NULL(expected_destructor_ptr); + TEST_ASSERT_NOT_NULL(actual_destructor_ptr); + TEST_ASSERT_EQUAL_PTR(expected_destructor_ptr, actual_destructor_ptr); + + TEST_ASSERT_EQUAL(0, pthread_key_delete(key)); +} + +static void task_test_pthread_destructor(void *v_key); + +TEST_CASE("pthread local storage destructor in FreeRTOS task", "[pthread]") +{ + // Same as previous test case, but doesn't use pthread APIs therefore must wait + // for the idle task to call the destructor + pthread_key_t key = -1; + + expected_destructor_ptr = NULL; + actual_destructor_ptr = NULL; + + TEST_ASSERT_EQUAL(0, pthread_key_create(&key, test_pthread_destructor)); + + xTaskCreate(task_test_pthread_destructor, + "ptdest", 8192, (void *)key, UNITY_FREERTOS_PRIORITY+1, + NULL); + + // Above task has higher priority to us, so should run immediately + // but we need to wait for the idle task cleanup to run + vTaskDelay(20); + + TEST_ASSERT_NOT_NULL(expected_destructor_ptr); + TEST_ASSERT_NOT_NULL(actual_destructor_ptr); + TEST_ASSERT_EQUAL_PTR(expected_destructor_ptr, actual_destructor_ptr); + + TEST_ASSERT_EQUAL(0, pthread_key_delete(key)); +} + + + +static void *thread_test_pthread_destructor(void *v_key) +{ + printf("Local storage thread running...\n"); + pthread_key_t key = (pthread_key_t) v_key; + expected_destructor_ptr = &key; // address of stack variable in the task... + pthread_setspecific(key, expected_destructor_ptr); + printf("Local storage thread done.\n"); + return NULL; +} + +static void test_pthread_destructor(void *value) +{ + printf("Destructor called...\n"); + actual_destructor_ptr = value; +} + +static void task_test_pthread_destructor(void *v_key) +{ + /* call the pthread main routine, then delete ourselves... */ + thread_test_pthread_destructor(v_key); + vTaskDelete(NULL); +}