diff --git a/components/esp_rom/esp32/ld/esp32.rom.newlib-data.ld b/components/esp_rom/esp32/ld/esp32.rom.newlib-data.ld index 642bd6a072..ed5795c86c 100644 --- a/components/esp_rom/esp32/ld/esp32.rom.newlib-data.ld +++ b/components/esp_rom/esp32/ld/esp32.rom.newlib-data.ld @@ -17,3 +17,7 @@ __sf_fake_stderr = 0x3ff96458; __sf_fake_stdin = 0x3ff96498; __sf_fake_stdout = 0x3ff96478; __wctomb = 0x3ff96540; +__sfp_lock = 0x3ffae0ac; +__sinit_lock = 0x3ffae0a8; +__env_lock_object = 0x3ffae0b8; +__tz_lock_object = 0x3ffae080; diff --git a/components/esp_rom/esp32s2/ld/esp32s2.rom.newlib-data.ld b/components/esp_rom/esp32s2/ld/esp32s2.rom.newlib-data.ld index 33eda7e057..681c34b568 100644 --- a/components/esp_rom/esp32s2/ld/esp32s2.rom.newlib-data.ld +++ b/components/esp_rom/esp32s2/ld/esp32s2.rom.newlib-data.ld @@ -13,3 +13,5 @@ _PathLocale = 0x3ffffd80; __sf_fake_stderr = 0x3ffaf08c; __sf_fake_stdin = 0x3ffaf0cc; __sf_fake_stdout = 0x3ffaf0ac; +__sfp_recursive_mutex = 0x3ffffd88; +__sinit_recursive_mutex = 0x3ffffd84; diff --git a/components/esp_rom/esp32s3/ld/esp32s3.rom.newlib-data.ld b/components/esp_rom/esp32s3/ld/esp32s3.rom.newlib-data.ld index adc8a3d8df..91a07e18d0 100644 --- a/components/esp_rom/esp32s3/ld/esp32s3.rom.newlib-data.ld +++ b/components/esp_rom/esp32s3/ld/esp32s3.rom.newlib-data.ld @@ -15,3 +15,5 @@ _PathLocale = 0x3fcefcd0; __sf_fake_stderr = 0x3ff0c524; __sf_fake_stdin = 0x3ff0c564; __sf_fake_stdout = 0x3ff0c544; +__sinit_recursive_mutex = 0x3fcefcd4; +__sfp_recursive_mutex = 0x3fcefcd8; diff --git a/components/esp_system/startup.c b/components/esp_system/startup.c index 09cd07ef8b..0c4ad2d685 100644 --- a/components/esp_system/startup.c +++ b/components/esp_system/startup.c @@ -199,6 +199,7 @@ static void do_core_init(void) fail initializing it properly. */ heap_caps_init(); esp_setup_syscall_table(); + esp_newlib_locks_init(); esp_newlib_time_init(); if (g_spiram_ok) { diff --git a/components/newlib/CMakeLists.txt b/components/newlib/CMakeLists.txt index b9055dbc45..670be02dbc 100644 --- a/components/newlib/CMakeLists.txt +++ b/components/newlib/CMakeLists.txt @@ -30,9 +30,8 @@ target_link_libraries(${COMPONENT_LIB} INTERFACE c m gcc "$= sizeof(StaticSemaphore_t), + "Incorrect size of struct __lock"); + +/* FreeRTOS configuration check */ +_Static_assert(configSUPPORT_STATIC_ALLOCATION, + "FreeRTOS should be configured with static allocation support"); + +/* These 2 locks are used instead of 9 distinct newlib static locks, + * as most of the locks are required for lesser-used features, so + * the chance of performance degradation due to lock contention is low. + */ +static StaticSemaphore_t s_common_mutex; +static StaticSemaphore_t s_common_recursive_mutex; + + +#ifdef CONFIG_IDF_TARGET_ESP32C3 +/* C3 ROM is built without Newlib static lock symbols exported, and + * with an extra level of _LOCK_T indirection in mind. + * The following is a workaround for this: + * - on startup, we call esp_rom_newlib_init_common_mutexes to set + * the two mutex pointers to magic values. + * - if in __retarget_lock_acquire*, we check if the argument dereferences + * to the magic value. If yes, we lock the correct mutex defined in the app, + * instead. + * Casts from &StaticSemaphore_t to _LOCK_T are okay because _LOCK_T + * (which is SemaphoreHandle_t) is a pointer to the corresponding + * StaticSemaphore_t structure. This is ensured by asserts below. + */ + +#define ROM_NEEDS_MUTEX_OVERRIDE +#endif // CONFIG_IDF_TARGET_ESP32C3 + +#ifdef ROM_NEEDS_MUTEX_OVERRIDE +#define ROM_MUTEX_MAGIC 0xbb10c433 +/* This is a macro, since we are overwriting the argument */ +#define MAYBE_OVERRIDE_LOCK(_lock, _lock_to_use_instead) \ + if (*(int*)_lock == ROM_MUTEX_MAGIC) { \ + (_lock) = (_LOCK_T) (_lock_to_use_instead); \ + } +#else // ROM_NEEDS_MUTEX_OVERRIDE +#define MAYBE_OVERRIDE_LOCK(_lock, _lock_to_use_instead) +#endif // ROM_NEEDS_MUTEX_OVERRIDE + + +void IRAM_ATTR __retarget_lock_init(_LOCK_T *lock) +{ + *lock = NULL; /* In case lock's memory is uninitialized */ + lock_init_generic(lock, queueQUEUE_TYPE_MUTEX); +} + +void IRAM_ATTR __retarget_lock_init_recursive(_LOCK_T *lock) +{ + *lock = NULL; /* In case lock's memory is uninitialized */ + lock_init_generic(lock, queueQUEUE_TYPE_RECURSIVE_MUTEX); +} + +void IRAM_ATTR __retarget_lock_close(_LOCK_T lock) +{ + _lock_close(&lock); +} + +void IRAM_ATTR __retarget_lock_close_recursive(_LOCK_T lock) +{ + _lock_close_recursive(&lock); +} + +/* Separate function, to prevent generating multiple assert strings */ +static void IRAM_ATTR check_lock_nonzero(_LOCK_T lock) +{ + assert(lock != NULL && "Uninitialized lock used"); +} + +void IRAM_ATTR __retarget_lock_acquire(_LOCK_T lock) +{ + check_lock_nonzero(lock); + MAYBE_OVERRIDE_LOCK(lock, &s_common_mutex); + _lock_acquire(&lock); +} + +void IRAM_ATTR __retarget_lock_acquire_recursive(_LOCK_T lock) +{ + check_lock_nonzero(lock); + MAYBE_OVERRIDE_LOCK(lock, &s_common_recursive_mutex); + _lock_acquire_recursive(&lock); +} + +int IRAM_ATTR __retarget_lock_try_acquire(_LOCK_T lock) +{ + check_lock_nonzero(lock); + MAYBE_OVERRIDE_LOCK(lock, &s_common_mutex); + return _lock_try_acquire(&lock); +} + +int IRAM_ATTR __retarget_lock_try_acquire_recursive(_LOCK_T lock) +{ + check_lock_nonzero(lock); + MAYBE_OVERRIDE_LOCK(lock, &s_common_recursive_mutex); + return _lock_try_acquire_recursive(&lock); +} + +void IRAM_ATTR __retarget_lock_release(_LOCK_T lock) +{ + check_lock_nonzero(lock); + _lock_release(&lock); +} + +void IRAM_ATTR __retarget_lock_release_recursive(_LOCK_T lock) +{ + check_lock_nonzero(lock); + _lock_release_recursive(&lock); +} + +/* When _RETARGETABLE_LOCKING is enabled, newlib expects the following locks to be provided: */ + +extern StaticSemaphore_t __attribute__((alias("s_common_recursive_mutex"))) __lock___sinit_recursive_mutex; +extern StaticSemaphore_t __attribute__((alias("s_common_recursive_mutex"))) __lock___malloc_recursive_mutex; +extern StaticSemaphore_t __attribute__((alias("s_common_recursive_mutex"))) __lock___env_recursive_mutex; +extern StaticSemaphore_t __attribute__((alias("s_common_recursive_mutex"))) __lock___sfp_recursive_mutex; +extern StaticSemaphore_t __attribute__((alias("s_common_recursive_mutex"))) __lock___atexit_recursive_mutex; +extern StaticSemaphore_t __attribute__((alias("s_common_mutex"))) __lock___at_quick_exit_mutex; +extern StaticSemaphore_t __attribute__((alias("s_common_mutex"))) __lock___tz_mutex; +extern StaticSemaphore_t __attribute__((alias("s_common_mutex"))) __lock___dd_hash_mutex; +extern StaticSemaphore_t __attribute__((alias("s_common_mutex"))) __lock___arc4random_mutex; + +void esp_newlib_locks_init(void) +{ + /* Initialize the two mutexes used for the locks above. + * Asserts below check our assumption that SemaphoreHandle_t will always + * point to the corresponding StaticSemaphore_t structure. + */ + SemaphoreHandle_t handle; + handle = xSemaphoreCreateMutexStatic(&s_common_mutex); + assert(handle == (SemaphoreHandle_t) &s_common_mutex); + handle = xSemaphoreCreateRecursiveMutexStatic(&s_common_recursive_mutex); + assert(handle == (SemaphoreHandle_t) &s_common_recursive_mutex); + (void) handle; + + /* Chip ROMs are built with older versions of newlib, and rely on different lock variables. + * Initialize these locks to the same values. + */ +#ifdef CONFIG_IDF_TARGET_ESP32 + /* Newlib 2.2.0 is used in ROM, the following lock symbols are defined: */ + extern _lock_t __sfp_lock; + __sfp_lock = (_lock_t) &s_common_recursive_mutex; + extern _lock_t __sinit_lock; + __sinit_lock = (_lock_t) &s_common_recursive_mutex; + extern _lock_t __env_lock_object; + __env_lock_object = (_lock_t) &s_common_mutex; + extern _lock_t __tz_lock_object; + __tz_lock_object = (_lock_t) &s_common_recursive_mutex; +#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) + /* Newlib 3.0.0 is used in ROM, the following lock symbols are defined: */ + extern _lock_t __sinit_recursive_mutex; + __sinit_recursive_mutex = (_lock_t) &s_common_recursive_mutex; + extern _lock_t __sfp_recursive_mutex; + __sfp_recursive_mutex = (_lock_t) &s_common_recursive_mutex; +#elif defined(CONFIG_IDF_TARGET_ESP32C3) + /* Newlib 3.3.0 is used in ROM, built with _RETARGETABLE_LOCKING. + * No access to lock variables for the purpose of ECO forward compatibility, + * however we have an API to initialize lock variables used in the ROM. + */ + extern void esp_rom_newlib_init_common_mutexes(_LOCK_T, _LOCK_T); + /* See notes about ROM_NEEDS_MUTEX_OVERRIDE above */ + int magic_val = ROM_MUTEX_MAGIC; + _LOCK_T magic_mutex = (_LOCK_T) &magic_val; + esp_rom_newlib_init_common_mutexes(magic_mutex, magic_mutex); +#else // other target +#error Unsupported target +#endif +} + +#else // _RETARGETABLE_LOCKING + +void esp_newlib_locks_init(void) { } + +#endif // _RETARGETABLE_LOCKING diff --git a/components/newlib/platform_include/esp_newlib.h b/components/newlib/platform_include/esp_newlib.h index 988f2b7e66..b4aa54e41a 100644 --- a/components/newlib/platform_include/esp_newlib.h +++ b/components/newlib/platform_include/esp_newlib.h @@ -53,4 +53,9 @@ void esp_set_time_from_rtc(void); */ void esp_sync_counters_rtc_and_frc(void); +/** + * Initialize newlib static locks + */ +void esp_newlib_locks_init(void); + #endif //__ESP_NEWLIB_H__ diff --git a/components/newlib/platform_include/sys/lock.h b/components/newlib/platform_include/sys/lock.h new file mode 100644 index 0000000000..1581a293e8 --- /dev/null +++ b/components/newlib/platform_include/sys/lock.h @@ -0,0 +1,41 @@ +#pragma once + +#include_next + +#ifdef _RETARGETABLE_LOCKING + +/* Actual platfrom-specific definition of struct __lock. + * The size here should be sufficient for a FreeRTOS mutex. + * This is checked by a static assertion in locks.c + * + * Note 1: this might need to be made dependent on whether FreeRTOS + * is included in the build. + * + * Note 2: the size is made sufficient for the case when + * configUSE_TRACE_FACILITY is enabled. If it is disabled, + * this definition wastes 8 bytes. + */ +struct __lock { + int reserved[23]; +}; + +/* Compatibility definitions for the legacy ESP-specific locking implementation. + * These used to be provided by libc/sys/xtensa/sys/lock.h in newlib. + * Newer versions of newlib don't have this ESP-specific lock.h header, and are + * built with _RETARGETABLE_LOCKING enabled, instead. + */ + +typedef _LOCK_T _lock_t; + +void _lock_init(_lock_t *plock); +void _lock_init_recursive(_lock_t *plock); +void _lock_close(_lock_t *plock); +void _lock_close_recursive(_lock_t *plock); +void _lock_acquire(_lock_t *plock); +void _lock_acquire_recursive(_lock_t *plock); +int _lock_try_acquire(_lock_t *plock); +int _lock_try_acquire_recursive(_lock_t *plock); +void _lock_release(_lock_t *plock); +void _lock_release_recursive(_lock_t *plock); + +#endif // _RETARGETABLE_LOCKING diff --git a/components/newlib/test/test_locks.c b/components/newlib/test/test_locks.c new file mode 100644 index 0000000000..7418370001 --- /dev/null +++ b/components/newlib/test/test_locks.c @@ -0,0 +1,109 @@ +#include +#include +#include +#include +#include +#include "unity.h" +#include "test_utils.h" +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#if defined(_RETARGETABLE_LOCKING) + +static void locking_task(void* arg) +{ + _LOCK_T lock = (_LOCK_T) arg; + __lock_acquire(lock); + __lock_release(lock); + vTaskSuspend(NULL); +} + +static void recursive_locking_task(void* arg) +{ + _LOCK_T lock = (_LOCK_T) arg; + __lock_acquire_recursive(lock); + __lock_release_recursive(lock); + vTaskSuspend(NULL); +} + +static void test_inner_normal(_LOCK_T lock) +{ + /* Acquire the lock */ + __lock_acquire(lock); + + /* Create another task to try acquire same lock */ + TaskHandle_t task_hdl; + TEST_ASSERT(xTaskCreate(&locking_task, "locking_task", 2048, lock, UNITY_FREERTOS_PRIORITY, &task_hdl)); + vTaskDelay(2); + /* It should get blocked */ + TEST_ASSERT_EQUAL(eBlocked, eTaskGetState(task_hdl)); + + /* Once we release the lock, the task should succeed and suspend itself */ + __lock_release(lock); + vTaskDelay(2); + TEST_ASSERT_EQUAL(eSuspended, eTaskGetState(task_hdl)); + vTaskDelete(task_hdl); + + /* Can not recursively acquire the lock from same task */ + TEST_ASSERT_EQUAL(0, __lock_try_acquire(lock)); + TEST_ASSERT_EQUAL(-1, __lock_try_acquire(lock)); + __lock_release(lock); +} + +static void test_inner_recursive(_LOCK_T lock) +{ + /* Acquire the lock */ + __lock_acquire_recursive(lock); + + /* Create another task to try acquire same lock */ + TaskHandle_t task_hdl; + TEST_ASSERT(xTaskCreate(&recursive_locking_task, "locking_task", 2048, lock, UNITY_FREERTOS_PRIORITY, &task_hdl)); + vTaskDelay(2); + /* It should get blocked */ + TEST_ASSERT_EQUAL(eBlocked, eTaskGetState(task_hdl)); + + /* Once we release the lock, the task should succeed and suspend itself */ + __lock_release_recursive(lock); + vTaskDelay(2); + TEST_ASSERT_EQUAL(eSuspended, eTaskGetState(task_hdl)); + vTaskDelete(task_hdl); + + /* Try recursively acquiring the lock */ + TEST_ASSERT_EQUAL(0, __lock_try_acquire_recursive(lock)); + TEST_ASSERT_EQUAL(0, __lock_try_acquire_recursive(lock)); + __lock_release_recursive(lock); + __lock_release_recursive(lock); +} + +TEST_CASE("Retargetable static locks", "[newlib_locks]") +{ + StaticSemaphore_t semaphore; + _LOCK_T lock = (_LOCK_T) xSemaphoreCreateMutexStatic(&semaphore); + test_inner_normal(lock); +} + +TEST_CASE("Retargetable static recursive locks", "[newlib_locks]") +{ + StaticSemaphore_t semaphore; + _LOCK_T lock = (_LOCK_T) xSemaphoreCreateRecursiveMutexStatic(&semaphore); + test_inner_recursive(lock); +} + +TEST_CASE("Retargetable dynamic locks", "[newlib_locks]") +{ + _LOCK_T lock; + __lock_init(lock); + test_inner_normal(lock); + __lock_close(lock); +} + +TEST_CASE("Retargetable dynamic recursive locks", "[newlib_locks]") +{ + _LOCK_T lock; + __lock_init_recursive(lock); + test_inner_recursive(lock); + __lock_close_recursive(lock); +} + +#endif // _RETARGETABLE_LOCKING