From 20c4121f25125a3e85820c45037602a7b1648a67 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:04:03 +0300 Subject: [PATCH] [FL-3832] Use static synchronisation primitives (#3679) * Use static mutex * Add static_assert checks * Use static semaphore * Fix formatting * Use static stream buffer * Use static timer * Use static event group * Increase allocation size for stream buffer * Remove recursive bit from the mutex before freeing * Prevent service tasks from ever returning * Use static threads * Do not realloc memory when changing stack size * Use FuriSemaphore instead of raw FreeRTOS one in rpc_test * Remove redundant includes * Abolish FreeRTOS dynamic allocation * Improve FuriMutex * Improve FuriMessageQueue * Remove redundant comments and parentheses * Clean up code more * Create service threads via a dedicated constructor * Minor code improvements * Update docs for FuriThread, FuriTimer * Fix doxygen typo * Use a bigger buffer for static StreamBuffer * Furi: remove timer control block only when timer thread have completed all operations --------- Co-authored-by: Aleksandr Kutuzov --- .../debug/unit_tests/tests/rpc/rpc_test.c | 43 +- .../debug/unit_tests/unit_test_api_table_i.h | 4 +- applications/services/bt/bt_service/bt.c | 2 + applications/services/desktop/desktop.c | 2 + .../scenes/desktop_scene_pin_timeout.c | 1 - .../desktop/views/desktop_view_pin_timeout.c | 1 - applications/services/dolphin/dolphin.c | 2 + .../services/power/power_service/power.c | 2 + furi/core/event_flag.c | 23 +- furi/core/event_flag.h | 2 +- furi/core/memmgr_heap.c | 4 - furi/core/message_queue.c | 48 +-- furi/core/mutex.c | 133 +++--- furi/core/mutex.h | 2 +- furi/core/semaphore.c | 48 ++- furi/core/semaphore.h | 2 +- furi/core/stream_buffer.c | 49 ++- furi/core/stream_buffer.h | 2 +- furi/core/thread.c | 137 ++++--- furi/core/thread.h | 383 ++++++++++++------ furi/core/timer.c | 92 ++--- furi/core/timer.h | 6 +- furi/flipper.c | 5 +- targets/f18/api_symbols.csv | 4 +- targets/f7/api_symbols.csv | 4 +- targets/f7/furi_hal/furi_hal_serial_control.c | 5 +- targets/f7/furi_hal/furi_hal_usb.c | 3 +- targets/f7/inc/FreeRTOSConfig.h | 2 +- 28 files changed, 584 insertions(+), 427 deletions(-) diff --git a/applications/debug/unit_tests/tests/rpc/rpc_test.c b/applications/debug/unit_tests/tests/rpc/rpc_test.c index fdbfb6c7a..5bc2cc1f8 100644 --- a/applications/debug/unit_tests/tests/rpc/rpc_test.c +++ b/applications/debug/unit_tests/tests/rpc/rpc_test.c @@ -1,11 +1,6 @@ -#include -#include #include #include -#include -#include - #include #include #include @@ -40,8 +35,8 @@ static uint32_t command_id = 0; typedef struct { RpcSession* session; FuriStreamBuffer* output_stream; - SemaphoreHandle_t close_session_semaphore; - SemaphoreHandle_t terminate_semaphore; + FuriSemaphore* close_session_semaphore; + FuriSemaphore* terminate_semaphore; uint32_t timeout; } RpcSessionContext; @@ -96,8 +91,8 @@ static void test_rpc_setup(void) { rpc_session[0].output_stream = furi_stream_buffer_alloc(4096, 1); rpc_session_set_send_bytes_callback(rpc_session[0].session, output_bytes_callback); - rpc_session[0].close_session_semaphore = xSemaphoreCreateBinary(); - rpc_session[0].terminate_semaphore = xSemaphoreCreateBinary(); + rpc_session[0].close_session_semaphore = furi_semaphore_alloc(1, 0); + rpc_session[0].terminate_semaphore = furi_semaphore_alloc(1, 0); rpc_session_set_close_callback(rpc_session[0].session, test_rpc_session_close_callback); rpc_session_set_terminated_callback( rpc_session[0].session, test_rpc_session_terminated_callback); @@ -116,8 +111,8 @@ static void test_rpc_setup_second_session(void) { rpc_session[1].output_stream = furi_stream_buffer_alloc(1000, 1); rpc_session_set_send_bytes_callback(rpc_session[1].session, output_bytes_callback); - rpc_session[1].close_session_semaphore = xSemaphoreCreateBinary(); - rpc_session[1].terminate_semaphore = xSemaphoreCreateBinary(); + rpc_session[1].close_session_semaphore = furi_semaphore_alloc(1, 0); + rpc_session[1].terminate_semaphore = furi_semaphore_alloc(1, 0); rpc_session_set_close_callback(rpc_session[1].session, test_rpc_session_close_callback); rpc_session_set_terminated_callback( rpc_session[1].session, test_rpc_session_terminated_callback); @@ -126,13 +121,15 @@ static void test_rpc_setup_second_session(void) { static void test_rpc_teardown(void) { furi_check(rpc_session[0].close_session_semaphore); - xSemaphoreTake(rpc_session[0].terminate_semaphore, 0); + furi_semaphore_acquire(rpc_session[0].terminate_semaphore, 0); rpc_session_close(rpc_session[0].session); - furi_check(xSemaphoreTake(rpc_session[0].terminate_semaphore, portMAX_DELAY)); + furi_check( + furi_semaphore_acquire(rpc_session[0].terminate_semaphore, FuriWaitForever) == + FuriStatusOk); furi_record_close(RECORD_RPC); furi_stream_buffer_free(rpc_session[0].output_stream); - vSemaphoreDelete(rpc_session[0].close_session_semaphore); - vSemaphoreDelete(rpc_session[0].terminate_semaphore); + furi_semaphore_free(rpc_session[0].close_session_semaphore); + furi_semaphore_free(rpc_session[0].terminate_semaphore); ++command_id; rpc_session[0].output_stream = NULL; rpc_session[0].close_session_semaphore = NULL; @@ -142,12 +139,14 @@ static void test_rpc_teardown(void) { static void test_rpc_teardown_second_session(void) { furi_check(rpc_session[1].close_session_semaphore); - xSemaphoreTake(rpc_session[1].terminate_semaphore, 0); + furi_semaphore_acquire(rpc_session[1].terminate_semaphore, 0); rpc_session_close(rpc_session[1].session); - furi_check(xSemaphoreTake(rpc_session[1].terminate_semaphore, portMAX_DELAY)); + furi_check( + furi_semaphore_acquire(rpc_session[1].terminate_semaphore, FuriWaitForever) == + FuriStatusOk); furi_stream_buffer_free(rpc_session[1].output_stream); - vSemaphoreDelete(rpc_session[1].close_session_semaphore); - vSemaphoreDelete(rpc_session[1].terminate_semaphore); + furi_semaphore_free(rpc_session[1].close_session_semaphore); + furi_semaphore_free(rpc_session[1].terminate_semaphore); ++command_id; rpc_session[1].output_stream = NULL; rpc_session[1].close_session_semaphore = NULL; @@ -204,14 +203,14 @@ static void test_rpc_session_close_callback(void* context) { furi_check(context); RpcSessionContext* callbacks_context = context; - xSemaphoreGive(callbacks_context->close_session_semaphore); + furi_check(furi_semaphore_release(callbacks_context->close_session_semaphore) == FuriStatusOk); } static void test_rpc_session_terminated_callback(void* context) { furi_check(context); RpcSessionContext* callbacks_context = context; - xSemaphoreGive(callbacks_context->terminate_semaphore); + furi_check(furi_semaphore_release(callbacks_context->terminate_semaphore) == FuriStatusOk); } static void test_rpc_print_message_list(MsgList_t msg_list) { @@ -1645,7 +1644,7 @@ static void test_rpc_feed_rubbish_run( test_rpc_add_empty_to_list(expected, PB_CommandStatus_ERROR_DECODE, 0); - furi_check(!xSemaphoreTake(rpc_session[0].close_session_semaphore, 0)); + furi_check(furi_semaphore_acquire(rpc_session[0].close_session_semaphore, 0) != FuriStatusOk); test_rpc_encode_and_feed(input_before, 0); test_send_rubbish(rpc_session[0].session, pattern, pattern_size, size); test_rpc_encode_and_feed(input_after, 0); diff --git a/applications/debug/unit_tests/unit_test_api_table_i.h b/applications/debug/unit_tests/unit_test_api_table_i.h index 8c2fa4687..e6409f3ac 100644 --- a/applications/debug/unit_tests/unit_test_api_table_i.h +++ b/applications/debug/unit_tests/unit_test_api_table_i.h @@ -19,9 +19,9 @@ static constexpr auto unit_tests_api_table = sort(create_array_t( API_METHOD(xQueueSemaphoreTake, BaseType_t, (QueueHandle_t, TickType_t)), API_METHOD(vQueueDelete, void, (QueueHandle_t)), API_METHOD( - xQueueGenericCreate, + xQueueGenericCreateStatic, QueueHandle_t, - (const UBaseType_t, const UBaseType_t, const uint8_t)), + (const UBaseType_t, const UBaseType_t, uint8_t*, StaticQueue_t*, const uint8_t)), API_METHOD( xQueueGenericSend, BaseType_t, diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 389275b86..59eb86388 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -435,6 +435,8 @@ int32_t bt_srv(void* p) { FURI_LOG_W(TAG, "Skipping start in special boot mode"); ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT); furi_record_create(RECORD_BT, bt); + + furi_thread_suspend(furi_thread_get_current_id()); return 0; } diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 748f9a555..8b0c6d753 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -441,6 +441,8 @@ int32_t desktop_srv(void* p) { if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { FURI_LOG_W(TAG, "Skipping start in special boot mode"); + + furi_thread_suspend(furi_thread_get_current_id()); return 0; } diff --git a/applications/services/desktop/scenes/desktop_scene_pin_timeout.c b/applications/services/desktop/scenes/desktop_scene_pin_timeout.c index 82543a944..ed6f9f12e 100644 --- a/applications/services/desktop/scenes/desktop_scene_pin_timeout.c +++ b/applications/services/desktop/scenes/desktop_scene_pin_timeout.c @@ -1,5 +1,4 @@ #include -#include #include #include "../desktop_i.h" diff --git a/applications/services/desktop/views/desktop_view_pin_timeout.c b/applications/services/desktop/views/desktop_view_pin_timeout.c index f24ecc8ea..2811ba7d2 100644 --- a/applications/services/desktop/views/desktop_view_pin_timeout.c +++ b/applications/services/desktop/views/desktop_view_pin_timeout.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index a838edc60..28c17ec16 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -148,6 +148,8 @@ int32_t dolphin_srv(void* p) { if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { FURI_LOG_W(TAG, "Skipping start in special boot mode"); + + furi_thread_suspend(furi_thread_get_current_id()); return 0; } diff --git a/applications/services/power/power_service/power.c b/applications/services/power/power_service/power.c index 557beaf64..278854e13 100644 --- a/applications/services/power/power_service/power.c +++ b/applications/services/power/power_service/power.c @@ -198,6 +198,8 @@ int32_t power_srv(void* p) { if(furi_hal_rtc_get_boot_mode() != FuriHalRtcBootModeNormal) { FURI_LOG_W(TAG, "Skipping start in special boot mode"); + + furi_thread_suspend(furi_thread_get_current_id()); return 0; } diff --git a/furi/core/event_flag.c b/furi/core/event_flag.c index ccbee93d6..c2e04e2fd 100644 --- a/furi/core/event_flag.c +++ b/furi/core/event_flag.c @@ -8,18 +8,27 @@ #define FURI_EVENT_FLAG_MAX_BITS_EVENT_GROUPS 24U #define FURI_EVENT_FLAG_INVALID_BITS (~((1UL << FURI_EVENT_FLAG_MAX_BITS_EVENT_GROUPS) - 1U)) +struct FuriEventFlag { + StaticEventGroup_t container; +}; + +// IMPORTANT: container MUST be the FIRST struct member +static_assert(offsetof(FuriEventFlag, container) == 0); + FuriEventFlag* furi_event_flag_alloc(void) { furi_check(!FURI_IS_IRQ_MODE()); - EventGroupHandle_t handle = xEventGroupCreate(); - furi_check(handle); + FuriEventFlag* instance = malloc(sizeof(FuriEventFlag)); - return ((FuriEventFlag*)handle); + furi_check(xEventGroupCreateStatic(&instance->container) == (EventGroupHandle_t)instance); + + return instance; } void furi_event_flag_free(FuriEventFlag* instance) { furi_check(!FURI_IS_IRQ_MODE()); vEventGroupDelete((EventGroupHandle_t)instance); + free(instance); } uint32_t furi_event_flag_set(FuriEventFlag* instance, uint32_t flags) { @@ -43,7 +52,7 @@ uint32_t furi_event_flag_set(FuriEventFlag* instance, uint32_t flags) { } /* Return event flags after setting */ - return (rflags); + return rflags; } uint32_t furi_event_flag_clear(FuriEventFlag* instance, uint32_t flags) { @@ -69,7 +78,7 @@ uint32_t furi_event_flag_clear(FuriEventFlag* instance, uint32_t flags) { } /* Return event flags before clearing */ - return (rflags); + return rflags; } uint32_t furi_event_flag_get(FuriEventFlag* instance) { @@ -85,7 +94,7 @@ uint32_t furi_event_flag_get(FuriEventFlag* instance) { } /* Return current event flags */ - return (rflags); + return rflags; } uint32_t furi_event_flag_wait( @@ -136,5 +145,5 @@ uint32_t furi_event_flag_wait( } /* Return event flags before clearing */ - return (rflags); + return rflags; } diff --git a/furi/core/event_flag.h b/furi/core/event_flag.h index 7200144cd..fbc3bb004 100644 --- a/furi/core/event_flag.h +++ b/furi/core/event_flag.h @@ -10,7 +10,7 @@ extern "C" { #endif -typedef void FuriEventFlag; +typedef struct FuriEventFlag FuriEventFlag; /** Allocate FuriEventFlag * diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 3cee0d377..3a0e2c378 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -56,10 +56,6 @@ task.h is included from an application file. */ #error This feature is broken, logging transport must be replaced with RTT #endif -#if(configSUPPORT_DYNAMIC_ALLOCATION == 0) -#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0 -#endif - /* Block sizes must not get too small. */ #define heapMINIMUM_BLOCK_SIZE ((size_t)(xHeapStructSize << 1)) diff --git a/furi/core/message_queue.c b/furi/core/message_queue.c index 6a4e8c324..0454e289b 100644 --- a/furi/core/message_queue.c +++ b/furi/core/message_queue.c @@ -5,14 +5,21 @@ #include #include -struct FuriMessageQueue { - // !!! Semi-Opaque type inheritance, Very Fragile, DO NOT MOVE !!! - StaticQueue_t container; +// Internal FreeRTOS member names +#define uxMessagesWaiting uxDummy4[0] +#define uxLength uxDummy4[1] +#define uxItemSize uxDummy4[2] - // !!! Data buffer, must be last in the structure, DO NOT MOVE !!! +struct FuriMessageQueue { + StaticQueue_t container; uint8_t buffer[]; }; +// IMPORTANT: container MUST be the FIRST struct member +static_assert(offsetof(FuriMessageQueue, container) == 0); +// IMPORTANT: buffer MUST be the LAST struct member +static_assert(offsetof(FuriMessageQueue, buffer) == sizeof(FuriMessageQueue)); + FuriMessageQueue* furi_message_queue_alloc(uint32_t msg_count, uint32_t msg_size) { furi_check((furi_kernel_is_irq_or_masked() == 0U) && (msg_count > 0U) && (msg_size > 0U)); @@ -75,8 +82,7 @@ FuriStatus } } - /* Return execution status */ - return (stat); + return stat; } FuriStatus furi_message_queue_get(FuriMessageQueue* instance, void* msg_ptr, uint32_t timeout) { @@ -114,31 +120,19 @@ FuriStatus furi_message_queue_get(FuriMessageQueue* instance, void* msg_ptr, uin } } - return (stat); + return stat; } uint32_t furi_message_queue_get_capacity(FuriMessageQueue* instance) { furi_check(instance); - uint32_t capacity; - - /* capacity = pxQueue->uxLength */ - capacity = instance->container.uxDummy4[1]; - - /* Return maximum number of messages */ - return (capacity); + return instance->container.uxLength; } uint32_t furi_message_queue_get_message_size(FuriMessageQueue* instance) { furi_check(instance); - uint32_t size; - - /* size = pxQueue->uxItemSize */ - size = instance->container.uxDummy4[2]; - - /* Return maximum message size */ - return (size); + return instance->container.uxItemSize; } uint32_t furi_message_queue_get_count(FuriMessageQueue* instance) { @@ -153,8 +147,7 @@ uint32_t furi_message_queue_get_count(FuriMessageQueue* instance) { count = uxQueueMessagesWaiting(hQueue); } - /* Return number of queued messages */ - return ((uint32_t)count); + return (uint32_t)count; } uint32_t furi_message_queue_get_space(FuriMessageQueue* instance) { @@ -166,16 +159,14 @@ uint32_t furi_message_queue_get_space(FuriMessageQueue* instance) { if(furi_kernel_is_irq_or_masked() != 0U) { isrm = taskENTER_CRITICAL_FROM_ISR(); - /* space = pxQueue->uxLength - pxQueue->uxMessagesWaiting; */ - space = instance->container.uxDummy4[1] - instance->container.uxDummy4[0]; + space = instance->container.uxLength - instance->container.uxMessagesWaiting; taskEXIT_CRITICAL_FROM_ISR(isrm); } else { space = (uint32_t)uxQueueSpacesAvailable((QueueHandle_t)instance); } - /* Return number of available slots */ - return (space); + return space; } FuriStatus furi_message_queue_reset(FuriMessageQueue* instance) { @@ -191,6 +182,5 @@ FuriStatus furi_message_queue_reset(FuriMessageQueue* instance) { (void)xQueueReset(hQueue); } - /* Return execution status */ - return (stat); + return stat; } diff --git a/furi/core/mutex.c b/furi/core/mutex.c index 4fc3099fe..f59ae83ad 100644 --- a/furi/core/mutex.c +++ b/furi/core/mutex.c @@ -5,129 +5,120 @@ #include #include +// Internal FreeRTOS member names +#define ucQueueType ucDummy9 + +struct FuriMutex { + StaticSemaphore_t container; +}; + +// IMPORTANT: container MUST be the FIRST struct member +static_assert(offsetof(FuriMutex, container) == 0); + FuriMutex* furi_mutex_alloc(FuriMutexType type) { furi_check(!FURI_IS_IRQ_MODE()); - SemaphoreHandle_t hMutex = NULL; + FuriMutex* instance = malloc(sizeof(FuriMutex)); + + SemaphoreHandle_t hMutex; if(type == FuriMutexTypeNormal) { - hMutex = xSemaphoreCreateMutex(); + hMutex = xSemaphoreCreateMutexStatic(&instance->container); } else if(type == FuriMutexTypeRecursive) { - hMutex = xSemaphoreCreateRecursiveMutex(); + hMutex = xSemaphoreCreateRecursiveMutexStatic(&instance->container); } else { furi_crash(); } - furi_check(hMutex != NULL); + furi_check(hMutex == (SemaphoreHandle_t)instance); - if(type == FuriMutexTypeRecursive) { - /* Set LSB as 'recursive mutex flag' */ - hMutex = (SemaphoreHandle_t)((uint32_t)hMutex | 1U); - } - - /* Return mutex ID */ - return ((FuriMutex*)hMutex); + return instance; } void furi_mutex_free(FuriMutex* instance) { furi_check(!FURI_IS_IRQ_MODE()); furi_check(instance); - vSemaphoreDelete((SemaphoreHandle_t)((uint32_t)instance & ~1U)); + vSemaphoreDelete((SemaphoreHandle_t)instance); + free(instance); } FuriStatus furi_mutex_acquire(FuriMutex* instance, uint32_t timeout) { furi_check(instance); - SemaphoreHandle_t hMutex; - FuriStatus stat; - uint32_t rmtx; + SemaphoreHandle_t hMutex = (SemaphoreHandle_t)(instance); + const uint8_t mutex_type = instance->container.ucQueueType; - hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U); - - /* Extract recursive mutex flag */ - rmtx = (uint32_t)instance & 1U; - - stat = FuriStatusOk; + FuriStatus stat = FuriStatusOk; if(FURI_IS_IRQ_MODE()) { stat = FuriStatusErrorISR; - } else if(hMutex == NULL) { - stat = FuriStatusErrorParameter; - } else { - if(rmtx != 0U) { - if(xSemaphoreTakeRecursive(hMutex, timeout) != pdPASS) { - if(timeout != 0U) { - stat = FuriStatusErrorTimeout; - } else { - stat = FuriStatusErrorResource; - } - } - } else { - if(xSemaphoreTake(hMutex, timeout) != pdPASS) { - if(timeout != 0U) { - stat = FuriStatusErrorTimeout; - } else { - stat = FuriStatusErrorResource; - } + + } else if(mutex_type == queueQUEUE_TYPE_RECURSIVE_MUTEX) { + if(xSemaphoreTakeRecursive(hMutex, timeout) != pdPASS) { + if(timeout != 0U) { + stat = FuriStatusErrorTimeout; + } else { + stat = FuriStatusErrorResource; } } + + } else if(mutex_type == queueQUEUE_TYPE_MUTEX) { + if(xSemaphoreTake(hMutex, timeout) != pdPASS) { + if(timeout != 0U) { + stat = FuriStatusErrorTimeout; + } else { + stat = FuriStatusErrorResource; + } + } + + } else { + furi_crash(); } - /* Return execution status */ - return (stat); + return stat; } FuriStatus furi_mutex_release(FuriMutex* instance) { furi_check(instance); - SemaphoreHandle_t hMutex; - FuriStatus stat; - uint32_t rmtx; + SemaphoreHandle_t hMutex = (SemaphoreHandle_t)(instance); + const uint8_t mutex_type = instance->container.ucQueueType; - hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U); - - /* Extract recursive mutex flag */ - rmtx = (uint32_t)instance & 1U; - - stat = FuriStatusOk; + FuriStatus stat = FuriStatusOk; if(FURI_IS_IRQ_MODE()) { stat = FuriStatusErrorISR; - } else if(hMutex == NULL) { - stat = FuriStatusErrorParameter; - } else { - if(rmtx != 0U) { - if(xSemaphoreGiveRecursive(hMutex) != pdPASS) { - stat = FuriStatusErrorResource; - } - } else { - if(xSemaphoreGive(hMutex) != pdPASS) { - stat = FuriStatusErrorResource; - } + + } else if(mutex_type == queueQUEUE_TYPE_RECURSIVE_MUTEX) { + if(xSemaphoreGiveRecursive(hMutex) != pdPASS) { + stat = FuriStatusErrorResource; } + + } else if(mutex_type == queueQUEUE_TYPE_MUTEX) { + if(xSemaphoreGive(hMutex) != pdPASS) { + stat = FuriStatusErrorResource; + } + + } else { + furi_crash(); } - /* Return execution status */ - return (stat); + return stat; } FuriThreadId furi_mutex_get_owner(FuriMutex* instance) { furi_check(instance); - SemaphoreHandle_t hMutex; + SemaphoreHandle_t hMutex = (SemaphoreHandle_t)instance; + FuriThreadId owner; - hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U); - - if(hMutex == NULL) { - owner = 0; - } else if(FURI_IS_IRQ_MODE()) { + if(FURI_IS_IRQ_MODE()) { owner = (FuriThreadId)xSemaphoreGetMutexHolderFromISR(hMutex); } else { owner = (FuriThreadId)xSemaphoreGetMutexHolder(hMutex); } - /* Return owner thread ID */ - return (owner); + return owner; } diff --git a/furi/core/mutex.h b/furi/core/mutex.h index aa55fa7bc..60372a292 100644 --- a/furi/core/mutex.h +++ b/furi/core/mutex.h @@ -16,7 +16,7 @@ typedef enum { FuriMutexTypeRecursive, } FuriMutexType; -typedef void FuriMutex; +typedef struct FuriMutex FuriMutex; /** Allocate FuriMutex * diff --git a/furi/core/semaphore.c b/furi/core/semaphore.c index 503eec472..6413eb65f 100644 --- a/furi/core/semaphore.c +++ b/furi/core/semaphore.c @@ -5,36 +5,43 @@ #include #include +struct FuriSemaphore { + StaticSemaphore_t container; +}; + +// IMPORTANT: container MUST be the FIRST struct member +static_assert(offsetof(FuriSemaphore, container) == 0); + FuriSemaphore* furi_semaphore_alloc(uint32_t max_count, uint32_t initial_count) { furi_check(!FURI_IS_IRQ_MODE()); furi_check((max_count > 0U) && (initial_count <= max_count)); - SemaphoreHandle_t hSemaphore = NULL; + FuriSemaphore* instance = malloc(sizeof(FuriSemaphore)); + + SemaphoreHandle_t hSemaphore; + if(max_count == 1U) { - hSemaphore = xSemaphoreCreateBinary(); - if((hSemaphore != NULL) && (initial_count != 0U)) { - if(xSemaphoreGive(hSemaphore) != pdPASS) { - vSemaphoreDelete(hSemaphore); - hSemaphore = NULL; - } - } + hSemaphore = xSemaphoreCreateBinaryStatic(&instance->container); } else { - hSemaphore = xSemaphoreCreateCounting(max_count, initial_count); + hSemaphore = + xSemaphoreCreateCountingStatic(max_count, initial_count, &instance->container); } - furi_check(hSemaphore); + furi_check(hSemaphore == (SemaphoreHandle_t)instance); - /* Return semaphore ID */ - return ((FuriSemaphore*)hSemaphore); + if(max_count == 1U && initial_count != 0U) { + furi_check(xSemaphoreGive(hSemaphore) == pdPASS); + } + + return instance; } void furi_semaphore_free(FuriSemaphore* instance) { furi_check(instance); furi_check(!FURI_IS_IRQ_MODE()); - SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance; - - vSemaphoreDelete(hSemaphore); + vSemaphoreDelete((SemaphoreHandle_t)instance); + free(instance); } FuriStatus furi_semaphore_acquire(FuriSemaphore* instance, uint32_t timeout) { @@ -58,6 +65,7 @@ FuriStatus furi_semaphore_acquire(FuriSemaphore* instance, uint32_t timeout) { portYIELD_FROM_ISR(yield); } } + } else { if(xSemaphoreTake(hSemaphore, (TickType_t)timeout) != pdPASS) { if(timeout != 0U) { @@ -68,8 +76,7 @@ FuriStatus furi_semaphore_acquire(FuriSemaphore* instance, uint32_t timeout) { } } - /* Return execution status */ - return (stat); + return stat; } FuriStatus furi_semaphore_release(FuriSemaphore* instance) { @@ -89,14 +96,14 @@ FuriStatus furi_semaphore_release(FuriSemaphore* instance) { } else { portYIELD_FROM_ISR(yield); } + } else { if(xSemaphoreGive(hSemaphore) != pdPASS) { stat = FuriStatusErrorResource; } } - /* Return execution status */ - return (stat); + return stat; } uint32_t furi_semaphore_get_count(FuriSemaphore* instance) { @@ -111,6 +118,5 @@ uint32_t furi_semaphore_get_count(FuriSemaphore* instance) { count = (uint32_t)uxSemaphoreGetCount(hSemaphore); } - /* Return number of tokens */ - return (count); + return count; } diff --git a/furi/core/semaphore.h b/furi/core/semaphore.h index 19d056bfe..c6b9a1176 100644 --- a/furi/core/semaphore.h +++ b/furi/core/semaphore.h @@ -11,7 +11,7 @@ extern "C" { #endif -typedef void FuriSemaphore; +typedef struct FuriSemaphore FuriSemaphore; /** Allocate semaphore * diff --git a/furi/core/stream_buffer.c b/furi/core/stream_buffer.c index 8bd00d91c..eefda0e79 100644 --- a/furi/core/stream_buffer.c +++ b/furi/core/stream_buffer.c @@ -6,24 +6,42 @@ #include #include +struct FuriStreamBuffer { + StaticStreamBuffer_t container; + uint8_t buffer[]; +}; + +// IMPORTANT: container MUST be the FIRST struct member +static_assert(offsetof(FuriStreamBuffer, container) == 0); +// IMPORTANT: buffer MUST be the LAST struct member +static_assert(offsetof(FuriStreamBuffer, buffer) == sizeof(FuriStreamBuffer)); + FuriStreamBuffer* furi_stream_buffer_alloc(size_t size, size_t trigger_level) { furi_check(size != 0); - StreamBufferHandle_t handle = xStreamBufferCreate(size, trigger_level); - furi_check(handle); + // Actual FreeRTOS usable buffer size seems to be one less + const size_t buffer_size = size + 1; - return handle; + FuriStreamBuffer* stream_buffer = malloc(sizeof(FuriStreamBuffer) + buffer_size); + StreamBufferHandle_t hStreamBuffer = xStreamBufferCreateStatic( + buffer_size, trigger_level, stream_buffer->buffer, &stream_buffer->container); + + furi_check(hStreamBuffer == (StreamBufferHandle_t)stream_buffer); + + return stream_buffer; }; void furi_stream_buffer_free(FuriStreamBuffer* stream_buffer) { furi_check(stream_buffer); - vStreamBufferDelete(stream_buffer); + vStreamBufferDelete((StreamBufferHandle_t)stream_buffer); + free(stream_buffer); }; bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigger_level) { furi_check(stream_buffer); - return xStreamBufferSetTriggerLevel(stream_buffer, trigger_level) == pdTRUE; + return xStreamBufferSetTriggerLevel((StreamBufferHandle_t)stream_buffer, trigger_level) == + pdTRUE; }; size_t furi_stream_buffer_send( @@ -37,10 +55,10 @@ size_t furi_stream_buffer_send( if(FURI_IS_IRQ_MODE()) { BaseType_t yield; - ret = xStreamBufferSendFromISR(stream_buffer, data, length, &yield); + ret = xStreamBufferSendFromISR((StreamBufferHandle_t)stream_buffer, data, length, &yield); portYIELD_FROM_ISR(yield); } else { - ret = xStreamBufferSend(stream_buffer, data, length, timeout); + ret = xStreamBufferSend((StreamBufferHandle_t)stream_buffer, data, length, timeout); } return ret; @@ -57,10 +75,11 @@ size_t furi_stream_buffer_receive( if(FURI_IS_IRQ_MODE()) { BaseType_t yield; - ret = xStreamBufferReceiveFromISR(stream_buffer, data, length, &yield); + ret = + xStreamBufferReceiveFromISR((StreamBufferHandle_t)stream_buffer, data, length, &yield); portYIELD_FROM_ISR(yield); } else { - ret = xStreamBufferReceive(stream_buffer, data, length, timeout); + ret = xStreamBufferReceive((StreamBufferHandle_t)stream_buffer, data, length, timeout); } return ret; @@ -69,33 +88,33 @@ size_t furi_stream_buffer_receive( size_t furi_stream_buffer_bytes_available(FuriStreamBuffer* stream_buffer) { furi_check(stream_buffer); - return xStreamBufferBytesAvailable(stream_buffer); + return xStreamBufferBytesAvailable((StreamBufferHandle_t)stream_buffer); }; size_t furi_stream_buffer_spaces_available(FuriStreamBuffer* stream_buffer) { furi_check(stream_buffer); - return xStreamBufferSpacesAvailable(stream_buffer); + return xStreamBufferSpacesAvailable((StreamBufferHandle_t)stream_buffer); }; bool furi_stream_buffer_is_full(FuriStreamBuffer* stream_buffer) { furi_check(stream_buffer); - return xStreamBufferIsFull(stream_buffer) == pdTRUE; + return xStreamBufferIsFull((StreamBufferHandle_t)stream_buffer) == pdTRUE; }; bool furi_stream_buffer_is_empty(FuriStreamBuffer* stream_buffer) { furi_check(stream_buffer); - return (xStreamBufferIsEmpty(stream_buffer) == pdTRUE); + return (xStreamBufferIsEmpty((StreamBufferHandle_t)stream_buffer) == pdTRUE); }; FuriStatus furi_stream_buffer_reset(FuriStreamBuffer* stream_buffer) { furi_check(stream_buffer); - if(xStreamBufferReset(stream_buffer) == pdPASS) { + if(xStreamBufferReset((StreamBufferHandle_t)stream_buffer) == pdPASS) { return FuriStatusOk; } else { return FuriStatusError; } -} \ No newline at end of file +} diff --git a/furi/core/stream_buffer.h b/furi/core/stream_buffer.h index 5ddc49416..3cc9c1b67 100644 --- a/furi/core/stream_buffer.h +++ b/furi/core/stream_buffer.h @@ -19,7 +19,7 @@ extern "C" { #endif -typedef void FuriStreamBuffer; +typedef struct FuriStreamBuffer FuriStreamBuffer; /** * @brief Allocate stream buffer instance. diff --git a/furi/core/thread.c b/furi/core/thread.c index 3ae0b8c25..db4b7bc80 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -27,6 +27,10 @@ struct FuriThreadStdout { }; struct FuriThread { + StaticTask_t container; + TaskHandle_t task_handle; + StackType_t* stack_buffer; + FuriThreadState state; int32_t ret; @@ -41,7 +45,7 @@ struct FuriThread { FuriThreadPriority priority; - TaskHandle_t task_handle; + size_t stack_size; size_t heap_size; FuriThreadStdout output; @@ -50,10 +54,11 @@ struct FuriThread { // this ensures that the size of this structure is minimal bool is_service; bool heap_trace_enabled; - - size_t stack_size; }; +// IMPORTANT: container MUST be the FIRST struct member +static_assert(offsetof(FuriThread, container) == 0); + static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size); static int32_t __furi_thread_stdout_flush(FuriThread* thread); @@ -92,6 +97,8 @@ static void furi_thread_body(void* context) { thread->ret = thread->callback(thread->context); + furi_check(!thread->is_service, "Service threads MUST NOT return"); + if(thread->heap_trace_enabled == true) { furi_delay_ms(33); thread->heap_size = memmgr_heap_get_thread_memory((FuriThreadId)task_handle); @@ -106,13 +113,6 @@ static void furi_thread_body(void* context) { furi_check(thread->state == FuriThreadStateRunning); - if(thread->is_service) { - FURI_LOG_W( - TAG, - "%s service thread TCB memory will not be reclaimed", - thread->name ? thread->name : ""); - } - // flush stdout __furi_thread_stdout_flush(thread); @@ -122,10 +122,8 @@ static void furi_thread_body(void* context) { furi_thread_catch(); } -FuriThread* furi_thread_alloc(void) { - FuriThread* thread = malloc(sizeof(FuriThread)); +static void furi_thread_init_common(FuriThread* thread) { thread->output.buffer = furi_string_alloc(); - thread->is_service = false; FuriThread* parent = NULL; if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) { @@ -150,6 +148,32 @@ FuriThread* furi_thread_alloc(void) { } else { thread->heap_trace_enabled = false; } +} + +FuriThread* furi_thread_alloc(void) { + FuriThread* thread = malloc(sizeof(FuriThread)); + + furi_thread_init_common(thread); + + return thread; +} + +FuriThread* furi_thread_alloc_service( + const char* name, + uint32_t stack_size, + FuriThreadCallback callback, + void* context) { + FuriThread* thread = memmgr_alloc_from_pool(sizeof(FuriThread)); + + furi_thread_init_common(thread); + + thread->stack_buffer = memmgr_alloc_from_pool(stack_size); + thread->stack_size = stack_size; + thread->is_service = true; + + furi_thread_set_name(thread, name); + furi_thread_set_callback(thread, callback); + furi_thread_set_context(thread, context); return thread; } @@ -169,15 +193,20 @@ FuriThread* furi_thread_alloc_ex( void furi_thread_free(FuriThread* thread) { furi_check(thread); - - // Ensure that use join before free + // Cannot free a service thread + furi_check(thread->is_service == false); + // Cannot free a non-joined thread furi_check(thread->state == FuriThreadStateStopped); furi_check(thread->task_handle == NULL); - if(thread->name) free(thread->name); - if(thread->appid) free(thread->appid); - furi_string_free(thread->output.buffer); + furi_thread_set_name(thread, NULL); + furi_thread_set_appid(thread, NULL); + if(thread->stack_buffer) { + free(thread->stack_buffer); + } + + furi_string_free(thread->output.buffer); free(thread); } @@ -185,7 +214,9 @@ void furi_thread_set_name(FuriThread* thread, const char* name) { furi_check(thread); furi_check(thread->state == FuriThreadStateStopped); - if(thread->name) free(thread->name); + if(thread->name) { + free(thread->name); + } thread->name = name ? strdup(name) : NULL; } @@ -193,19 +224,28 @@ void furi_thread_set_name(FuriThread* thread, const char* name) { void furi_thread_set_appid(FuriThread* thread, const char* appid) { furi_check(thread); furi_check(thread->state == FuriThreadStateStopped); - if(thread->appid) free(thread->appid); - thread->appid = appid ? strdup(appid) : NULL; -} -void furi_thread_mark_as_service(FuriThread* thread) { - thread->is_service = true; + if(thread->appid) { + free(thread->appid); + } + + thread->appid = appid ? strdup(appid) : NULL; } void furi_thread_set_stack_size(FuriThread* thread, size_t stack_size) { furi_check(thread); furi_check(thread->state == FuriThreadStateStopped); - furi_check(stack_size % 4 == 0); + furi_check(stack_size); furi_check(stack_size <= THREAD_MAX_STACK_SIZE); + furi_check(stack_size % sizeof(StackType_t) == 0); + // Stack size cannot be configured for a thread that has been marked as a service + furi_check(thread->is_service == false); + + if(thread->stack_buffer) { + free(thread->stack_buffer); + } + + thread->stack_buffer = malloc(stack_size); thread->stack_size = stack_size; } @@ -270,24 +310,19 @@ void furi_thread_start(FuriThread* thread) { furi_thread_set_state(thread, FuriThreadStateStarting); - uint32_t stack = thread->stack_size / sizeof(StackType_t); + uint32_t stack_depth = thread->stack_size / sizeof(StackType_t); UBaseType_t priority = thread->priority ? thread->priority : FuriThreadPriorityNormal; - if(thread->is_service) { - thread->task_handle = xTaskCreateStatic( - furi_thread_body, - thread->name, - stack, - thread, - priority, - memmgr_alloc_from_pool(sizeof(StackType_t) * stack), - memmgr_alloc_from_pool(sizeof(StaticTask_t))); - } else { - BaseType_t ret = xTaskCreate( - furi_thread_body, thread->name, stack, thread, priority, &thread->task_handle); - furi_check(ret == pdPASS); - } - furi_check(thread->task_handle); + thread->task_handle = xTaskCreateStatic( + furi_thread_body, + thread->name, + stack_depth, + thread, + priority, + thread->stack_buffer, + &thread->container); + + furi_check(thread->task_handle == (TaskHandle_t)&thread->container); } void furi_thread_cleanup_tcb_event(TaskHandle_t task) { @@ -302,7 +337,9 @@ void furi_thread_cleanup_tcb_event(TaskHandle_t task) { bool furi_thread_join(FuriThread* thread) { furi_check(thread); - + // Cannot join a service thread + furi_check(!thread->is_service); + // Cannot join a thread to itself furi_check(furi_thread_get_current() != thread); // !!! IMPORTANT NOTICE !!! @@ -390,7 +427,7 @@ uint32_t furi_thread_flags_set(FuriThreadId thread_id, uint32_t flags) { } } /* Return flags after setting */ - return (rflags); + return rflags; } uint32_t furi_thread_flags_clear(uint32_t flags) { @@ -419,7 +456,7 @@ uint32_t furi_thread_flags_clear(uint32_t flags) { } /* Return flags before clearing */ - return (rflags); + return rflags; } uint32_t furi_thread_flags_get(void) { @@ -437,7 +474,7 @@ uint32_t furi_thread_flags_get(void) { } } - return (rflags); + return rflags; } uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout) { @@ -507,7 +544,7 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo } /* Return flags before clearing */ - return (rflags); + return rflags; } uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count) { @@ -536,7 +573,7 @@ uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_c vPortFree(task); } - return (count); + return count; } const char* furi_thread_get_name(FuriThreadId thread_id) { @@ -549,7 +586,7 @@ const char* furi_thread_get_name(FuriThreadId thread_id) { name = pcTaskGetName(hTask); } - return (name); + return name; } const char* furi_thread_get_appid(FuriThreadId thread_id) { @@ -563,7 +600,7 @@ const char* furi_thread_get_appid(FuriThreadId thread_id) { } } - return (appid); + return appid; } uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) { @@ -576,7 +613,7 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) { sz = (uint32_t)(uxTaskGetStackHighWaterMark(hTask) * sizeof(StackType_t)); } - return (sz); + return sz; } static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size) { diff --git a/furi/core/thread.h b/furi/core/thread.h index f21ee9df3..d78272a4d 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -1,6 +1,6 @@ /** * @file thread.h - * Furi: Furi Thread API + * @brief Furi: Furi Thread API */ #pragma once @@ -15,14 +15,20 @@ extern "C" { #endif -/** FuriThreadState */ +/** + * @brief Enumeration of possible FuriThread states. + * + * Many of the FuriThread functions MUST ONLY be called when the thread is STOPPED. + */ typedef enum { - FuriThreadStateStopped, - FuriThreadStateStarting, - FuriThreadStateRunning, + FuriThreadStateStopped, /**< Thread is stopped */ + FuriThreadStateStarting, /**< Thread is starting */ + FuriThreadStateRunning, /**< Thread is running */ } FuriThreadState; -/** FuriThreadPriority */ +/** + * @brief Enumeration of possible FuriThread priorities. + */ typedef enum { FuriThreadPriorityNone = 0, /**< Uninitialized, choose system default */ FuriThreadPriorityIdle = 1, /**< Idle priority */ @@ -35,42 +41,85 @@ typedef enum { (FURI_CONFIG_THREAD_MAX_PRIORITIES - 1), /**< Deferred ISR (highest possible) */ } FuriThreadPriority; -/** FuriThread anonymous structure */ +/** + * @brief FuriThread opaque type. + */ typedef struct FuriThread FuriThread; -/** FuriThreadId proxy type to OS low level functions */ +/** + * @brief Unique thread identifier type (used by the OS kernel). + */ typedef void* FuriThreadId; -/** FuriThreadCallback Your callback to run in new thread - * @warning never use osThreadExit in FuriThread +/** + * @brief Thread callback function pointer type. + * + * The function to be used as a thread callback MUST follow this signature. + * + * @param[in,out] context pointer to a user-specified object + * @return value to be used as the thread return code */ typedef int32_t (*FuriThreadCallback)(void* context); -/** Write to stdout callback - * @param data pointer to data - * @param size data size @warning your handler must consume everything +/** + * @brief Standard output callback function pointer type. + * + * The function to be used as a standard output callback MUST follow this signature. + * + * @warning The handler MUST process ALL of the provided data before returning. + * + * @param[in] data pointer to the data to be written to the standard out + * @param[in] size size of the data in bytes */ typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); -/** FuriThread state change callback called upon thread state change - * @param state new thread state - * @param context callback context +/** + * @brief State change callback function pointer type. + * + * The function to be used as a state callback MUST follow this signature. + * + * @param[in] state identifier of the state the thread has transitioned to + * @param[in,out] context pointer to a user-specified object */ typedef void (*FuriThreadStateCallback)(FuriThreadState state, void* context); -/** Allocate FuriThread +/** + * @brief Create a FuriThread instance. * - * @return FuriThread instance + * @return pointer to the created FuriThread instance */ FuriThread* furi_thread_alloc(void); -/** Allocate FuriThread, shortcut version +/** + * @brief Create a FuriThread instance (service mode). + * + * Service threads are more memory efficient, but have + * the following limitations: + * + * - Cannot return from the callback + * - Cannot be joined or freed + * - Stack size cannot be altered + * + * @param[in] name human-readable thread name (can be NULL) + * @param[in] stack_size stack size in bytes (cannot be changed later) + * @param[in] callback pointer to a function to be executed in this thread + * @param[in] context pointer to a user-specified object (will be passed to the callback) + * @return pointer to the created FuriThread instance + */ +FuriThread* furi_thread_alloc_service( + const char* name, + uint32_t stack_size, + FuriThreadCallback callback, + void* context); + +/** + * @brief Create a FuriThread instance w/ extra parameters. * - * @param name - * @param stack_size - * @param callback - * @param context - * @return FuriThread* + * @param[in] name human-readable thread name (can be NULL) + * @param[in] stack_size stack size in bytes (can be changed later) + * @param[in] callback pointer to a function to be executed in this thread + * @param[in] context pointer to a user-specified object (will be passed to the callback) + * @return pointer to the created FuriThread instance */ FuriThread* furi_thread_alloc_ex( const char* name, @@ -78,261 +127,339 @@ FuriThread* furi_thread_alloc_ex( FuriThreadCallback callback, void* context); -/** Release FuriThread +/** + * @brief Delete a FuriThread instance. * - * @warning see furi_thread_join + * The thread MUST be stopped when calling this function. * - * @param thread FuriThread instance + * @warning see furi_thread_join for caveats on stopping a thread. + * + * @param[in,out] thread pointer to the FuriThread instance to be deleted */ void furi_thread_free(FuriThread* thread); -/** Set FuriThread name +/** + * @brief Set the name of a FuriThread instance. * - * @param thread FuriThread instance - * @param name string + * The thread MUST be stopped when calling this function. + * + * @param[in,out] thread pointer to the FuriThread instance to be modified + * @param[in] name human-readable thread name (can be NULL) */ void furi_thread_set_name(FuriThread* thread, const char* name); /** - * @brief Set FuriThread appid + * @brief Set the application ID of a FuriThread instance. + * + * The thread MUST be stopped when calling this function. + * * Technically, it is like a "process id", but it is not a system-wide unique identifier. * All threads spawned by the same app will have the same appid. * - * @param thread - * @param appid + * @param[in,out] thread pointer to the FuriThread instance to be modified + * @param[in] appid thread application ID (can be NULL) */ void furi_thread_set_appid(FuriThread* thread, const char* appid); -/** Mark thread as service - * The service cannot be stopped or removed, and cannot exit from the thread body - * - * @param thread - */ -void furi_thread_mark_as_service(FuriThread* thread); - -/** Set FuriThread stack size +/** + * @brief Set the stack size of a FuriThread instance. * - * @param thread FuriThread instance - * @param stack_size stack size in bytes + * The thread MUST be stopped when calling this function. Additionally, it is NOT possible + * to change the stack size of a service thread under any circumstances. + * + * @param[in,out] thread pointer to the FuriThread instance to be modified + * @param[in] stack_size stack size in bytes */ void furi_thread_set_stack_size(FuriThread* thread, size_t stack_size); -/** Set FuriThread callback +/** + * @brief Set the user callback function to be executed in a FuriThread. * - * @param thread FuriThread instance - * @param callback FuriThreadCallback, called upon thread run + * The thread MUST be stopped when calling this function. + * + * @param[in,out] thread pointer to the FuriThread instance to be modified + * @param[in] callback pointer to a user-specified function to be executed in this thread */ void furi_thread_set_callback(FuriThread* thread, FuriThreadCallback callback); -/** Set FuriThread context +/** + * @brief Set the callback function context. * - * @param thread FuriThread instance - * @param context pointer to context for thread callback + * The thread MUST be stopped when calling this function. + * + * @param[in,out] thread pointer to the FuriThread instance to be modified + * @param[in] context pointer to a user-specified object (will be passed to the callback, can be NULL) */ void furi_thread_set_context(FuriThread* thread, void* context); -/** Set FuriThread priority +/** + * @brief Set the priority of a FuriThread. * - * @param thread FuriThread instance - * @param priority FuriThreadPriority value + * The thread MUST be stopped when calling this function. + * + * @param[in,out] thread pointer to the FuriThread instance to be modified + * @param[in] priority priority level value */ void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority); -/** Get FuriThread priority +/** + * @brief Get the priority of a FuriThread. * - * @param thread FuriThread instance - * @return FuriThreadPriority value + * @param[in] thread pointer to the FuriThread instance to be queried + * @return priority level value */ FuriThreadPriority furi_thread_get_priority(FuriThread* thread); -/** Set current thread priority +/** + * @brief Set the priority of the current FuriThread. * - * @param priority FuriThreadPriority value + * @param priority priority level value */ void furi_thread_set_current_priority(FuriThreadPriority priority); -/** Get current thread priority +/** + * @brief Get the priority of the current FuriThread. * - * @return FuriThreadPriority value + * @return priority level value */ FuriThreadPriority furi_thread_get_current_priority(void); -/** Set FuriThread state change callback +/** + * Set the callback function to be executed upon a state thransition of a FuriThread. * - * @param thread FuriThread instance - * @param callback state change callback + * The thread MUST be stopped when calling this function. + * + * @param[in,out] thread pointer to the FuriThread instance to be modified + * @param[in] callback pointer to a user-specified callback function */ void furi_thread_set_state_callback(FuriThread* thread, FuriThreadStateCallback callback); -/** Set FuriThread state change context +/** + * @brief Set the state change callback context. * - * @param thread FuriThread instance - * @param context pointer to context + * The thread MUST be stopped when calling this function. + * + * @param[in,out] thread pointer to the FuriThread instance to be modified + * @param[in] context pointer to a user-specified object (will be passed to the callback, can be NULL) */ void furi_thread_set_state_context(FuriThread* thread, void* context); -/** Get FuriThread state +/** + * @brief Get the state of a FuriThread isntance. * - * @param thread FuriThread instance - * - * @return thread state from FuriThreadState + * @param[in] thread pointer to the FuriThread instance to be queried + * @return thread state value */ FuriThreadState furi_thread_get_state(FuriThread* thread); -/** Start FuriThread +/** + * @brief Start a FuriThread instance. * - * @param thread FuriThread instance + * The thread MUST be stopped when calling this function. + * + * @param[in,out] thread pointer to the FuriThread instance to be started */ void furi_thread_start(FuriThread* thread); -/** Join FuriThread +/** + * @brief Wait for a FuriThread to exit. * - * @warning Use this method only when CPU is not busy(Idle task receives - * control), otherwise it will wait forever. + * The thread callback function must return in order for the FuriThread instance to become joinable. * - * @param thread FuriThread instance + * @warning Use this method only when the CPU is not busy (i.e. when the + * Idle task receives control), otherwise it will wait forever. * - * @return bool + * @param[in] thread pointer to the FuriThread instance to be joined + * @return always true */ bool furi_thread_join(FuriThread* thread); -/** Get FreeRTOS FuriThreadId for FuriThread instance +/** + * @brief Get the unique identifier of a FuriThread instance. * - * @param thread FuriThread instance - * - * @return FuriThreadId or NULL + * @param[in] thread pointer to the FuriThread instance to be queried + * @return unique identifier value or NULL if thread is not running */ FuriThreadId furi_thread_get_id(FuriThread* thread); -/** Enable heap tracing +/** + * @brief Enable heap usage tracing for a FuriThread. * - * @param thread FuriThread instance + * The thread MUST be stopped when calling this function. + * + * @param[in,out] thread pointer to the FuriThread instance to be modified */ void furi_thread_enable_heap_trace(FuriThread* thread); -/** Disable heap tracing +/** + * @brief Disable heap usage tracing for a FuriThread. * - * @param thread FuriThread instance + * The thread MUST be stopped when calling this function. + * + * @param[in,out] thread pointer to the FuriThread instance to be modified */ void furi_thread_disable_heap_trace(FuriThread* thread); -/** Get thread heap size +/** + * @brief Get heap usage by a FuriThread instance. * - * @param thread FuriThread instance + * The heap trace MUST be enabled before callgin this function. * - * @return size in bytes + * @param[in] thread pointer to the FuriThread instance to be queried + * @return heap usage in bytes */ size_t furi_thread_get_heap_size(FuriThread* thread); -/** Get thread return code +/** + * @brief Get the return code of a FuriThread instance. * - * @param thread FuriThread instance + * This value is equal to the return value of the thread callback function. * - * @return return code + * The thread MUST be stopped when calling this function. + * + * @param[in] thread pointer to the FuriThread instance to be queried + * @return return code value */ int32_t furi_thread_get_return_code(FuriThread* thread); -/** Thread related methods that doesn't involve FuriThread directly */ - -/** Get FreeRTOS FuriThreadId for current thread +/** + * @brief Get the unique identifier of the current FuriThread. * - * @return FuriThreadId or NULL + * @return unique identifier value */ FuriThreadId furi_thread_get_current_id(void); -/** Get FuriThread instance for current thread +/** + * @brief Get the FuriThread instance associated with the current thread. * - * @return pointer to FuriThread or NULL if this thread doesn't belongs to Furi + * @return pointer to a FuriThread instance or NULL if this thread does not belong to Furi */ FuriThread* furi_thread_get_current(void); -/** Return control to scheduler */ +/** + * @brief Return control to the scheduler. + */ void furi_thread_yield(void); +/** + * @brief Set the thread flags of a FuriThread. + * + * Can be used as a simple inter-thread communication mechanism. + * + * @param[in] thread_id unique identifier of the thread to be notified + * @param[in] flags bitmask of thread flags to set + * @return bitmask combination of previous and newly set flags + */ uint32_t furi_thread_flags_set(FuriThreadId thread_id, uint32_t flags); +/** + * @brief Clear the thread flags of the current FuriThread. + * + * @param[in] flags bitmask of thread flags to clear + * @return bitmask of thread flags before clearing + */ uint32_t furi_thread_flags_clear(uint32_t flags); +/** + * @brief Get the thread flags of the current FuriThread. + * @return current bitmask of thread flags + */ uint32_t furi_thread_flags_get(void); +/** + * @brief Wait for some thread flags to be set. + * + * @see FuriFlag for option and error flags. + * + * @param[in] flags bitmask of thread flags to wait for + * @param[in] options combination of option flags determining the behavior of the function + * @param[in] timeout maximum time to wait in milliseconds (use FuriWaitForever to wait forever) + * @return bitmask combination of received thread and error flags + */ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout); /** - * @brief Enumerate threads + * @brief Enumerate all threads. * - * @param thread_array array of FuriThreadId, where thread ids will be stored - * @param array_item_count array size - * @return uint32_t threads count + * @param[out] thread_array pointer to the output array (must be properly allocated) + * @param[in] array_item_count output array capacity in elements (NOT bytes) + * @return total thread count (array_item_count or less) */ uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count); /** - * @brief Get thread name + * @brief Get the name of a thread based on its unique identifier. * - * @param thread_id - * @return const char* name or NULL + * @param[in] thread_id unique identifier of the thread to be queried + * @return pointer to a zero-terminated string or NULL */ const char* furi_thread_get_name(FuriThreadId thread_id); /** - * @brief Get thread appid + * @brief Get the application id of a thread based on its unique identifier. * - * @param thread_id - * @return const char* appid + * @param[in] thread_id unique identifier of the thread to be queried + * @return pointer to a zero-terminated string */ const char* furi_thread_get_appid(FuriThreadId thread_id); /** - * @brief Get thread stack watermark + * @brief Get thread stack watermark. * - * @param thread_id - * @return uint32_t + * @param[in] thread_id unique identifier of the thread to be queried + * @return stack watermark value */ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); -/** Get STDOUT callback for thead +/** + * @brief Get the standard output callback for the current thead. * - * @return STDOUT callback + * @return pointer to the standard out callback function */ FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void); -/** Set STDOUT callback for thread +/** Set standard output callback for the current thread. * - * @param callback callback or NULL to clear + * @param[in] callback pointer to the callback function or NULL to clear */ void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback); -/** Write data to buffered STDOUT +/** Write data to buffered standard output. * - * @param data input data - * @param size input data size - * - * @return size_t written data size + * @param[in] data pointer to the data to be written + * @param[in] size data size in bytes + * @return number of bytes that was actually written */ size_t furi_thread_stdout_write(const char* data, size_t size); -/** Flush data to STDOUT +/** + * @brief Flush buffered data to standard output. * - * @return int32_t error code + * @return error code value */ int32_t furi_thread_stdout_flush(void); -/** Suspend thread +/** + * @brief Suspend a thread. + * + * Suspended threads are no more receiving any of the processor time. * - * @param thread_id thread id + * @param[in] thread_id unique identifier of the thread to be suspended */ void furi_thread_suspend(FuriThreadId thread_id); -/** Resume thread +/** + * @brief Resume a thread. * - * @param thread_id thread id + * @param[in] thread_id unique identifier of the thread to be resumed */ void furi_thread_resume(FuriThreadId thread_id); -/** Get thread suspended state +/** + * @brief Test if a thread is suspended. * - * @param thread_id thread id - * @return true if thread is suspended + * @param[in] thread_id unique identifier of the thread to be queried + * @return true if thread is suspended, false otherwise */ bool furi_thread_is_suspended(FuriThreadId thread_id); diff --git a/furi/core/timer.c b/furi/core/timer.c index 671761fca..1ca56f0fa 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -5,56 +5,46 @@ #include #include -typedef struct { - FuriTimerCallback func; - void* context; -} TimerCallback_t; +struct FuriTimer { + StaticTimer_t container; + FuriTimerCallback cb_func; + void* cb_context; + volatile bool can_be_removed; +}; + +// IMPORTANT: container MUST be the FIRST struct member +static_assert(offsetof(FuriTimer, container) == 0); static void TimerCallback(TimerHandle_t hTimer) { - TimerCallback_t* callb; - - /* Retrieve pointer to callback function and context */ - callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer); - - /* Remove dynamic allocation flag */ - callb = (TimerCallback_t*)((uint32_t)callb & ~1U); - - if(callb != NULL) { - callb->func(callb->context); - } + FuriTimer* instance = pvTimerGetTimerID(hTimer); + furi_check(instance); + instance->cb_func(instance->cb_context); } FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* context) { furi_check((furi_kernel_is_irq_or_masked() == 0U) && (func != NULL)); - TimerHandle_t hTimer; - TimerCallback_t* callb; - UBaseType_t reload; + FuriTimer* instance = malloc(sizeof(FuriTimer)); - hTimer = NULL; + instance->cb_func = func; + instance->cb_context = context; - /* Dynamic memory allocation is available: if memory for callback and */ - /* its context is not provided, allocate it from dynamic memory pool */ - callb = (TimerCallback_t*)malloc(sizeof(TimerCallback_t)); + const UBaseType_t reload = (type == FuriTimerTypeOnce ? pdFALSE : pdTRUE); + const TimerHandle_t hTimer = xTimerCreateStatic( + NULL, portMAX_DELAY, reload, instance, TimerCallback, &instance->container); - callb->func = func; - callb->context = context; + furi_check(hTimer == (TimerHandle_t)instance); - if(type == FuriTimerTypeOnce) { - reload = pdFALSE; - } else { - reload = pdTRUE; - } + return instance; +} - /* Store callback memory dynamic allocation flag */ - callb = (TimerCallback_t*)((uint32_t)callb | 1U); - // TimerCallback function is always provided as a callback and is used to call application - // specified function with its context both stored in structure callb. - hTimer = xTimerCreate(NULL, portMAX_DELAY, reload, callb, TimerCallback); - furi_check(hTimer); +static void furi_timer_epilogue(void* context, uint32_t arg) { + furi_assert(context); + UNUSED(arg); - /* Return timer ID */ - return ((FuriTimer*)hTimer); + FuriTimer* instance = context; + + instance->can_be_removed = true; } void furi_timer_free(FuriTimer* instance) { @@ -62,26 +52,14 @@ void furi_timer_free(FuriTimer* instance) { furi_check(instance); TimerHandle_t hTimer = (TimerHandle_t)instance; - TimerCallback_t* callb; + furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); + furi_check(xTimerPendFunctionCall(furi_timer_epilogue, instance, 0, portMAX_DELAY) == pdPASS); - callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer); - - if((uint32_t)callb & 1U) { - /* If callback memory was allocated, it is only safe to free it with - * the timer inactive. Send a stop command and wait for the timer to - * be in an inactive state. - */ - furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); - while(furi_timer_is_running(instance)) furi_delay_tick(2); - - /* Callback memory was allocated from dynamic pool, clear flag */ - callb = (TimerCallback_t*)((uint32_t)callb & ~1U); - - /* Return allocated memory to dynamic pool */ - free(callb); + while(!instance->can_be_removed) { + furi_delay_tick(2); } - furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); + free(instance); } FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { @@ -98,8 +76,7 @@ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { stat = FuriStatusErrorResource; } - /* Return execution status */ - return (stat); + return stat; } FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks) { @@ -117,8 +94,7 @@ FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks) { stat = FuriStatusErrorResource; } - /* Return execution status */ - return (stat); + return stat; } FuriStatus furi_timer_stop(FuriTimer* instance) { diff --git a/furi/core/timer.h b/furi/core/timer.h index f8f40c562..2f55001f7 100644 --- a/furi/core/timer.h +++ b/furi/core/timer.h @@ -1,3 +1,7 @@ +/** + * @file timer.h + * @brief Furi software Timer API. + */ #pragma once #include "core/base.h" @@ -13,7 +17,7 @@ typedef enum { FuriTimerTypePeriodic = 1 ///< Repeating timer. } FuriTimerType; -typedef void FuriTimer; +typedef struct FuriTimer FuriTimer; /** Allocate timer * diff --git a/furi/flipper.c b/furi/flipper.c index c7ba3b4fb..6d6215a9d 100644 --- a/furi/flipper.c +++ b/furi/flipper.c @@ -37,12 +37,11 @@ void flipper_init(void) { for(size_t i = 0; i < FLIPPER_SERVICES_COUNT; i++) { FURI_LOG_D(TAG, "Starting service %s", FLIPPER_SERVICES[i].name); - FuriThread* thread = furi_thread_alloc_ex( + FuriThread* thread = furi_thread_alloc_service( FLIPPER_SERVICES[i].name, FLIPPER_SERVICES[i].stack_size, FLIPPER_SERVICES[i].app, NULL); - furi_thread_mark_as_service(thread); furi_thread_set_appid(thread, FLIPPER_SERVICES[i].appid); furi_thread_start(thread); @@ -67,4 +66,4 @@ void vApplicationGetTimerTaskMemory( *tcb_ptr = memmgr_alloc_from_pool(sizeof(StaticTask_t)); *stack_ptr = memmgr_alloc_from_pool(sizeof(StackType_t) * configTIMER_TASK_STACK_DEPTH); *stack_size = configTIMER_TASK_STACK_DEPTH; -} \ No newline at end of file +} diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7eda31675..b8f77266c 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,63.0,, +Version,+,64.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -1583,6 +1583,7 @@ Function,+,furi_string_utf8_push,void,"FuriString*, FuriStringUnicodeValue" Function,+,furi_string_vprintf,int,"FuriString*, const char[], va_list" Function,+,furi_thread_alloc,FuriThread*, Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" +Function,-,furi_thread_alloc_service,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" @@ -1605,7 +1606,6 @@ Function,+,furi_thread_get_state,FuriThreadState,FuriThread* Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* -Function,+,furi_thread_mark_as_service,void,FuriThread* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e2e94d063..a2924c3fc 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,63.0,, +Version,+,64.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -1791,6 +1791,7 @@ Function,+,furi_string_utf8_push,void,"FuriString*, FuriStringUnicodeValue" Function,+,furi_string_vprintf,int,"FuriString*, const char[], va_list" Function,+,furi_thread_alloc,FuriThread*, Function,+,furi_thread_alloc_ex,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" +Function,-,furi_thread_alloc_service,FuriThread*,"const char*, uint32_t, FuriThreadCallback, void*" Function,-,furi_thread_disable_heap_trace,void,FuriThread* Function,+,furi_thread_enable_heap_trace,void,FuriThread* Function,+,furi_thread_enumerate,uint32_t,"FuriThreadId*, uint32_t" @@ -1813,7 +1814,6 @@ Function,+,furi_thread_get_state,FuriThreadState,FuriThread* Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback, Function,+,furi_thread_is_suspended,_Bool,FuriThreadId Function,+,furi_thread_join,_Bool,FuriThread* -Function,+,furi_thread_mark_as_service,void,FuriThread* Function,+,furi_thread_resume,void,FuriThreadId Function,+,furi_thread_set_appid,void,"FuriThread*, const char*" Function,+,furi_thread_set_callback,void,"FuriThread*, FuriThreadCallback" diff --git a/targets/f7/furi_hal/furi_hal_serial_control.c b/targets/f7/furi_hal/furi_hal_serial_control.c index d2ab414c2..241716107 100644 --- a/targets/f7/furi_hal/furi_hal_serial_control.c +++ b/targets/f7/furi_hal/furi_hal_serial_control.c @@ -268,9 +268,8 @@ void furi_hal_serial_control_init(void) { furi_hal_serial_control->handles[FuriHalSerialIdLpuart].id = FuriHalSerialIdLpuart; furi_hal_serial_control->queue = furi_message_queue_alloc(8, sizeof(FuriHalSerialControlMessage)); - furi_hal_serial_control->thread = - furi_thread_alloc_ex("SerialControlDriver", 512, furi_hal_serial_control_thread, NULL); - furi_thread_mark_as_service(furi_hal_serial_control->thread); + furi_hal_serial_control->thread = furi_thread_alloc_service( + "SerialControlDriver", 512, furi_hal_serial_control_thread, NULL); furi_thread_set_priority(furi_hal_serial_control->thread, FuriThreadPriorityHighest); furi_hal_serial_control->log_config_serial_id = FuriHalSerialIdMax; // Start control plane thread diff --git a/targets/f7/furi_hal/furi_hal_usb.c b/targets/f7/furi_hal/furi_hal_usb.c index a482940e2..22d1523b6 100644 --- a/targets/f7/furi_hal/furi_hal_usb.c +++ b/targets/f7/furi_hal/furi_hal_usb.c @@ -120,8 +120,7 @@ void furi_hal_usb_init(void) { NVIC_EnableIRQ(USB_HP_IRQn); usb.queue = furi_message_queue_alloc(1, sizeof(UsbApiEventMessage)); - usb.thread = furi_thread_alloc_ex("UsbDriver", 1024, furi_hal_usb_thread, NULL); - furi_thread_mark_as_service(usb.thread); + usb.thread = furi_thread_alloc_service("UsbDriver", 1024, furi_hal_usb_thread, NULL); furi_thread_start(usb.thread); FURI_LOG_I(TAG, "Init OK"); diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 36800565c..0abce558f 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -16,7 +16,7 @@ #define configUSE_PREEMPTION 1 #define configSUPPORT_STATIC_ALLOCATION 1 -#define configSUPPORT_DYNAMIC_ALLOCATION 1 +#define configSUPPORT_DYNAMIC_ALLOCATION 0 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 #define configCPU_CLOCK_HZ (SystemCoreClock)