[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 <alleteam@gmail.com>
This commit is contained in:
Georgii Surkov 2024-06-05 20:04:03 +03:00 committed by GitHub
parent 03196fa110
commit 20c4121f25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 584 additions and 427 deletions

View file

@ -1,11 +1,6 @@
#include <core/check.h>
#include <core/record.h>
#include <furi.h>
#include <stdint.h>
#include <FreeRTOS.h>
#include <semphr.h>
#include <rpc/rpc.h>
#include <rpc/rpc_i.h>
#include <cli/cli.h>
@ -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);

View file

@ -19,9 +19,9 @@ static constexpr auto unit_tests_api_table = sort(create_array_t<sym_entry>(
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,

View file

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

View file

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

View file

@ -1,5 +1,4 @@
#include <furi.h>
#include <FreeRTOS.h>
#include <gui/scene_manager.h>
#include "../desktop_i.h"

View file

@ -2,7 +2,6 @@
#include <furi.h>
#include <stdint.h>
#include <stdio.h>
#include <FreeRTOS.h>
#include <projdefs.h>
#include <input/input.h>
#include <gui/canvas.h>

View file

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

View file

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

View file

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

View file

@ -10,7 +10,7 @@
extern "C" {
#endif
typedef void FuriEventFlag;
typedef struct FuriEventFlag FuriEventFlag;
/** Allocate FuriEventFlag
*

View file

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

View file

@ -5,14 +5,21 @@
#include <FreeRTOS.h>
#include <queue.h>
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;
}

View file

@ -5,129 +5,120 @@
#include <FreeRTOS.h>
#include <semphr.h>
// 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;
}

View file

@ -16,7 +16,7 @@ typedef enum {
FuriMutexTypeRecursive,
} FuriMutexType;
typedef void FuriMutex;
typedef struct FuriMutex FuriMutex;
/** Allocate FuriMutex
*

View file

@ -5,36 +5,43 @@
#include <FreeRTOS.h>
#include <semphr.h>
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;
}

View file

@ -11,7 +11,7 @@
extern "C" {
#endif
typedef void FuriSemaphore;
typedef struct FuriSemaphore FuriSemaphore;
/** Allocate semaphore
*

View file

@ -6,24 +6,42 @@
#include <FreeRTOS.h>
#include <FreeRTOS-Kernel/include/stream_buffer.h>
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;
}
}
}

View file

@ -19,7 +19,7 @@
extern "C" {
#endif
typedef void FuriStreamBuffer;
typedef struct FuriStreamBuffer FuriStreamBuffer;
/**
* @brief Allocate stream buffer instance.

View file

@ -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 : "<unknown service>");
}
// 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) {

View file

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

View file

@ -5,56 +5,46 @@
#include <FreeRTOS.h>
#include <timers.h>
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) {

View file

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

View file

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

View file

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

1 entry status name type params
2 Version + 63.0 64.0
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/bt/bt_service/bt_keys_storage.h
5 Header + applications/services/cli/cli.h
1583 Function + furi_string_vprintf int FuriString*, const char[], va_list
1584 Function + furi_thread_alloc FuriThread*
1585 Function + furi_thread_alloc_ex FuriThread* const char*, uint32_t, FuriThreadCallback, void*
1586 Function - furi_thread_alloc_service FuriThread* const char*, uint32_t, FuriThreadCallback, void*
1587 Function - furi_thread_disable_heap_trace void FuriThread*
1588 Function + furi_thread_enable_heap_trace void FuriThread*
1589 Function + furi_thread_enumerate uint32_t FuriThreadId*, uint32_t
1606 Function + furi_thread_get_stdout_callback FuriThreadStdoutWriteCallback
1607 Function + furi_thread_is_suspended _Bool FuriThreadId
1608 Function + furi_thread_join _Bool FuriThread*
Function + furi_thread_mark_as_service void FuriThread*
1609 Function + furi_thread_resume void FuriThreadId
1610 Function + furi_thread_set_appid void FuriThread*, const char*
1611 Function + furi_thread_set_callback void FuriThread*, FuriThreadCallback

View file

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

1 entry status name type params
2 Version + 63.0 64.0
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/bt/bt_service/bt_keys_storage.h
1791 Function + furi_string_vprintf int FuriString*, const char[], va_list
1792 Function + furi_thread_alloc FuriThread*
1793 Function + furi_thread_alloc_ex FuriThread* const char*, uint32_t, FuriThreadCallback, void*
1794 Function - furi_thread_alloc_service FuriThread* const char*, uint32_t, FuriThreadCallback, void*
1795 Function - furi_thread_disable_heap_trace void FuriThread*
1796 Function + furi_thread_enable_heap_trace void FuriThread*
1797 Function + furi_thread_enumerate uint32_t FuriThreadId*, uint32_t
1814 Function + furi_thread_get_stdout_callback FuriThreadStdoutWriteCallback
1815 Function + furi_thread_is_suspended _Bool FuriThreadId
1816 Function + furi_thread_join _Bool FuriThread*
Function + furi_thread_mark_as_service void FuriThread*
1817 Function + furi_thread_resume void FuriThreadId
1818 Function + furi_thread_set_appid void FuriThread*, const char*
1819 Function + furi_thread_set_callback void FuriThread*, FuriThreadCallback

View file

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

View file

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

View file

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