Merge branch 'feature/add_pthread' into 'master'

Add pthread component

See merge request sdk/ESP8266_RTOS_SDK!434
This commit is contained in:
Dong Heng
2018-09-13 15:37:08 +08:00
14 changed files with 1364 additions and 3 deletions

View File

@ -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);

View File

@ -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 */

View File

@ -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

View File

@ -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 <=. */

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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.h>
/** 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

View File

@ -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 <errno.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@ -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 <errno.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#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 <sys/queue.h>
#include <sys/time.h>
#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

View File

@ -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();

View File

@ -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 <errno.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#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

View File

@ -0,0 +1,9 @@
#
#Component Makefile
#
COMPONENT_SRCDIRS := .
#CXXFLAGS += -H
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View File

@ -0,0 +1,131 @@
// Test pthread_create_key, pthread_delete_key, pthread_setspecific, pthread_getspecific
#include <pthread.h>
#include <stdio.h>
#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);
}