implemented furi from flipper zero
added cmsis_core, furi, mlib and nanobake implemented basic app structure from furi implemented basic placeholder apps
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <furi_config.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
FuriWaitForever = 0xFFFFFFFFU,
|
||||
} FuriWait;
|
||||
|
||||
typedef enum {
|
||||
FuriFlagWaitAny = 0x00000000U, ///< Wait for any flag (default).
|
||||
FuriFlagWaitAll = 0x00000001U, ///< Wait for all flags.
|
||||
FuriFlagNoClear = 0x00000002U, ///< Do not clear flags which have been specified to wait for.
|
||||
|
||||
FuriFlagError = 0x80000000U, ///< Error indicator.
|
||||
FuriFlagErrorUnknown = 0xFFFFFFFFU, ///< FuriStatusError (-1).
|
||||
FuriFlagErrorTimeout = 0xFFFFFFFEU, ///< FuriStatusErrorTimeout (-2).
|
||||
FuriFlagErrorResource = 0xFFFFFFFDU, ///< FuriStatusErrorResource (-3).
|
||||
FuriFlagErrorParameter = 0xFFFFFFFCU, ///< FuriStatusErrorParameter (-4).
|
||||
FuriFlagErrorISR = 0xFFFFFFFAU, ///< FuriStatusErrorISR (-6).
|
||||
} FuriFlag;
|
||||
|
||||
typedef enum {
|
||||
FuriStatusOk = 0, ///< Operation completed successfully.
|
||||
FuriStatusError =
|
||||
-1, ///< Unspecified RTOS error: run-time error but no other error message fits.
|
||||
FuriStatusErrorTimeout = -2, ///< Operation not completed within the timeout period.
|
||||
FuriStatusErrorResource = -3, ///< Resource not available.
|
||||
FuriStatusErrorParameter = -4, ///< Parameter error.
|
||||
FuriStatusErrorNoMemory =
|
||||
-5, ///< System is out of memory: it was impossible to allocate or reserve memory for the operation.
|
||||
FuriStatusErrorISR =
|
||||
-6, ///< Not allowed in ISR context: the function cannot be called from interrupt service routines.
|
||||
FuriStatusReserved = 0x7FFFFFFF ///< Prevents enum down-size compiler optimization.
|
||||
} FuriStatus;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,194 @@
|
||||
#include "check.h"
|
||||
#include "common_defines.h"
|
||||
|
||||
#include "furi_hal_console.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
PLACE_IN_SECTION("MB_MEM2") const char* __furi_check_message = NULL;
|
||||
PLACE_IN_SECTION("MB_MEM2") uint32_t __furi_check_registers[13] = {0};
|
||||
|
||||
/** Load r12 value to __furi_check_message and store registers to __furi_check_registers */
|
||||
/*#define GET_MESSAGE_AND_STORE_REGISTERS() \
|
||||
asm volatile("ldr r11, =__furi_check_message \n" \
|
||||
"str r12, [r11] \n" \
|
||||
"ldr r12, =__furi_check_registers \n" \
|
||||
"stm r12, {r0-r11} \n" \
|
||||
"str lr, [r12, #48] \n" \
|
||||
: \
|
||||
: \
|
||||
: "memory");*/
|
||||
|
||||
/** Restore registers and halt MCU
|
||||
*
|
||||
* - Always use it with GET_MESSAGE_AND_STORE_REGISTERS
|
||||
* - If debugger is(was) connected this routine will raise bkpt
|
||||
* - If debugger is not connected then endless loop
|
||||
*
|
||||
*/
|
||||
/*#define RESTORE_REGISTERS_AND_HALT_MCU(debug) \
|
||||
register bool a0 asm("a0") = debug; \
|
||||
asm volatile("cbnz a0, with_debugger%= \n" \
|
||||
"ldr a12, =__furi_check_registers\n" \
|
||||
"ldm a12, {a0-a11} \n" \
|
||||
"loop%=: \n" \
|
||||
"wfi \n" \
|
||||
"b loop%= \n" \
|
||||
"with_debugger%=: \n" \
|
||||
"ldr a12, =__furi_check_registers\n" \
|
||||
"ldm a12, {a0-a11} \n" \
|
||||
"debug_loop%=: \n" \
|
||||
"bkpt 0x00 \n" \
|
||||
"wfi \n" \
|
||||
"b debug_loop%= \n" \
|
||||
: \
|
||||
: "a"(a0) \
|
||||
: "memory");*/
|
||||
|
||||
extern size_t xPortGetTotalHeapSize(void);
|
||||
|
||||
static void __furi_put_uint32_as_text(uint32_t data) {
|
||||
char tmp_str[] = "-2147483648";
|
||||
itoa(data, tmp_str, 10);
|
||||
furi_hal_console_puts(tmp_str);
|
||||
}
|
||||
|
||||
static void __furi_put_uint32_as_hex(uint32_t data) {
|
||||
char tmp_str[] = "0xFFFFFFFF";
|
||||
itoa(data, tmp_str, 16);
|
||||
furi_hal_console_puts(tmp_str);
|
||||
}
|
||||
|
||||
static void __furi_print_register_info() {
|
||||
// Print registers
|
||||
for(uint8_t i = 0; i < 12; i++) {
|
||||
furi_hal_console_puts("\r\n\tr");
|
||||
__furi_put_uint32_as_text(i);
|
||||
furi_hal_console_puts(" : ");
|
||||
__furi_put_uint32_as_hex(__furi_check_registers[i]);
|
||||
}
|
||||
|
||||
furi_hal_console_puts("\r\n\tlr : ");
|
||||
__furi_put_uint32_as_hex(__furi_check_registers[12]);
|
||||
}
|
||||
|
||||
static void __furi_print_stack_info() {
|
||||
furi_hal_console_puts("\r\n\tstack watermark: ");
|
||||
__furi_put_uint32_as_text(uxTaskGetStackHighWaterMark(NULL) * 4);
|
||||
}
|
||||
|
||||
static void __furi_print_bt_stack_info() {
|
||||
// const FuriHalBtHardfaultInfo* fault_info = furi_hal_bt_get_hardfault_info();
|
||||
// if(fault_info == NULL) {
|
||||
// furi_hal_console_puts("\r\n\tcore2: not faulted");
|
||||
// } else {
|
||||
// furi_hal_console_puts("\r\n\tcore2: hardfaulted.\r\n\tPC: ");
|
||||
// __furi_put_uint32_as_hex(fault_info->source_pc);
|
||||
// furi_hal_console_puts("\r\n\tLR: ");
|
||||
// __furi_put_uint32_as_hex(fault_info->source_lr);
|
||||
// furi_hal_console_puts("\r\n\tSP: ");
|
||||
// __furi_put_uint32_as_hex(fault_info->source_sp);
|
||||
// }
|
||||
}
|
||||
|
||||
static void __furi_print_heap_info() {
|
||||
// furi_hal_console_puts("\r\n\t heap total: ");
|
||||
// __furi_put_uint32_as_text(xPortGetTotalHeapSize());
|
||||
furi_hal_console_puts("\r\n\t heap free: ");
|
||||
__furi_put_uint32_as_text(xPortGetFreeHeapSize());
|
||||
furi_hal_console_puts("\r\n\t heap watermark: ");
|
||||
__furi_put_uint32_as_text(xPortGetMinimumEverFreeHeapSize());
|
||||
}
|
||||
|
||||
static void __furi_print_name(bool isr) {
|
||||
if(isr) {
|
||||
furi_hal_console_puts("[ISR ");
|
||||
__furi_put_uint32_as_text(__get_IPSR());
|
||||
furi_hal_console_puts("] ");
|
||||
} else {
|
||||
const char* name = pcTaskGetName(NULL);
|
||||
if(name == NULL) {
|
||||
furi_hal_console_puts("[main] ");
|
||||
} else {
|
||||
furi_hal_console_puts("[");
|
||||
furi_hal_console_puts(name);
|
||||
furi_hal_console_puts("] ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FURI_NORETURN void __furi_crash_implementation() {
|
||||
__disable_irq();
|
||||
// GET_MESSAGE_AND_STORE_REGISTERS();
|
||||
|
||||
bool isr = FURI_IS_IRQ_MODE();
|
||||
|
||||
if(__furi_check_message == NULL) {
|
||||
__furi_check_message = "Fatal Error";
|
||||
} else if(__furi_check_message == (void*)__FURI_ASSERT_MESSAGE_FLAG) {
|
||||
__furi_check_message = "furi_assert failed";
|
||||
} else if(__furi_check_message == (void*)__FURI_CHECK_MESSAGE_FLAG) {
|
||||
__furi_check_message = "furi_check failed";
|
||||
}
|
||||
|
||||
furi_hal_console_puts("\r\n\033[0;31m[CRASH]");
|
||||
__furi_print_name(isr);
|
||||
furi_hal_console_puts(__furi_check_message);
|
||||
|
||||
__furi_print_register_info();
|
||||
if(!isr) {
|
||||
__furi_print_stack_info();
|
||||
}
|
||||
__furi_print_heap_info();
|
||||
__furi_print_bt_stack_info();
|
||||
|
||||
// Check if debug enabled by DAP
|
||||
// https://developer.arm.com/documentation/ddi0403/d/Debug-Architecture/ARMv7-M-Debug/Debug-register-support-in-the-SCS/Debug-Halting-Control-and-Status-Register--DHCSR?lang=en
|
||||
// bool debug = CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk;
|
||||
bool debug = true;
|
||||
#ifdef FURI_NDEBUG
|
||||
if(debug) {
|
||||
#endif
|
||||
furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n");
|
||||
furi_hal_console_puts("\033[0m\r\n");
|
||||
// furi_hal_debug_enable();
|
||||
|
||||
esp_system_abort("crash");
|
||||
#ifdef FURI_NDEBUG
|
||||
} else {
|
||||
uint32_t ptr = (uint32_t)__furi_check_message;
|
||||
if(ptr < FLASH_BASE || ptr > (FLASH_BASE + FLASH_SIZE)) {
|
||||
ptr = (uint32_t) "Check serial logs";
|
||||
}
|
||||
furi_hal_rtc_set_fault_data(ptr);
|
||||
furi_hal_console_puts("\r\nRebooting system.\r\n");
|
||||
furi_hal_console_puts("\033[0m\r\n");
|
||||
esp_system_abort("crash");
|
||||
}
|
||||
#endif
|
||||
__builtin_unreachable();
|
||||
}
|
||||
FURI_NORETURN void __furi_halt_implementation() {
|
||||
__disable_irq();
|
||||
// GET_MESSAGE_AND_STORE_REGISTERS();
|
||||
|
||||
bool isr = FURI_IS_IRQ_MODE();
|
||||
|
||||
if(__furi_check_message == NULL) {
|
||||
__furi_check_message = "System halt requested.";
|
||||
}
|
||||
|
||||
furi_hal_console_puts("\r\n\033[0;31m[HALT]");
|
||||
__furi_print_name(isr);
|
||||
furi_hal_console_puts(__furi_check_message);
|
||||
furi_hal_console_puts("\r\nSystem halted. Bye-bye!\r\n");
|
||||
furi_hal_console_puts("\033[0m\r\n");
|
||||
|
||||
// Check if debug enabled by DAP
|
||||
// https://developer.arm.com/documentation/ddi0403/d/Debug-Architecture/ARMv7-M-Debug/Debug-register-support-in-the-SCS/Debug-Halting-Control-and-Status-Register--DHCSR?lang=en
|
||||
// bool debug = CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk;
|
||||
// RESTORE_REGISTERS_AND_HALT_MCU(true);
|
||||
|
||||
__builtin_unreachable();
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* @file check.h
|
||||
*
|
||||
* Furi crash and assert functions.
|
||||
*
|
||||
* The main problem with crashing is that you can't do anything without disturbing registers,
|
||||
* and if you disturb registers, you won't be able to see the correct register values in the debugger.
|
||||
*
|
||||
* Current solution works around it by passing the message through r12 and doing some magic with registers in crash function.
|
||||
* r0-r10 are stored in the ram2 on crash routine start and restored at the end.
|
||||
* The only register that is going to be lost is r11.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <m-core.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#define FURI_NORETURN [[noreturn]]
|
||||
#else
|
||||
#include <stdnoreturn.h>
|
||||
#define FURI_NORETURN noreturn
|
||||
#endif
|
||||
|
||||
// Flags instead of pointers will save ~4 bytes on furi_assert and furi_check calls.
|
||||
#define __FURI_ASSERT_MESSAGE_FLAG (0x01)
|
||||
#define __FURI_CHECK_MESSAGE_FLAG (0x02)
|
||||
|
||||
/** Crash system */
|
||||
FURI_NORETURN void __furi_crash_implementation();
|
||||
|
||||
/** Halt system */
|
||||
FURI_NORETURN void __furi_halt_implementation();
|
||||
|
||||
/** Crash system with message. */
|
||||
#define __furi_crash(message) \
|
||||
do { \
|
||||
ESP_LOGE("crash", "%s\n\tat %s:%d", (message) ? (message) : "", __FILE__, __LINE__); \
|
||||
__furi_crash_implementation(); \
|
||||
} while(0)
|
||||
|
||||
/** Crash system
|
||||
*
|
||||
* @param optional message (const char*)
|
||||
*/
|
||||
#define furi_crash(...) M_APPLY(__furi_crash, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__)))
|
||||
|
||||
/** Halt system with message. */
|
||||
#define __furi_halt(message) \
|
||||
do { \
|
||||
ESP_LOGE("halt", "%s\n\tat %s:%d", (message) ? (message) : "", __FILE__, __LINE__); \
|
||||
__furi_halt_implementation(); \
|
||||
} while(0)
|
||||
|
||||
/** Halt system
|
||||
*
|
||||
* @param optional message (const char*)
|
||||
*/
|
||||
#define furi_halt(...) M_APPLY(__furi_halt, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__)))
|
||||
|
||||
/** Check condition and crash if check failed */
|
||||
#define __furi_check(__e, __m) \
|
||||
do { \
|
||||
if(!(__e)) { \
|
||||
ESP_LOGE("check", "%s", #__e); \
|
||||
__furi_crash(__m); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/** Check condition and crash if failed
|
||||
*
|
||||
* @param condition to check
|
||||
* @param optional message (const char*)
|
||||
*/
|
||||
#define furi_check(...) \
|
||||
M_APPLY(__furi_check, M_DEFAULT_ARGS(2, (__FURI_CHECK_MESSAGE_FLAG), __VA_ARGS__))
|
||||
|
||||
/** Only in debug build: Assert condition and crash if assert failed */
|
||||
#ifdef FURI_DEBUG
|
||||
#define __furi_assert(__e, __m) \
|
||||
do { \
|
||||
if(!(__e)) { \
|
||||
ESP_LOGE("assert", "%s", #__e); \
|
||||
__furi_crash(__m); \
|
||||
} \
|
||||
} while(0)
|
||||
#else
|
||||
#define __furi_assert(__e, __m) \
|
||||
do { \
|
||||
((void)(__e)); \
|
||||
((void)(__m)); \
|
||||
} while(0)
|
||||
#endif
|
||||
|
||||
/** Assert condition and crash if failed
|
||||
*
|
||||
* @warning only will do check if firmware compiled in debug mode
|
||||
*
|
||||
* @param condition to check
|
||||
* @param optional message (const char*)
|
||||
*/
|
||||
#define furi_assert(...) \
|
||||
M_APPLY(__furi_assert, M_DEFAULT_ARGS(2, (__FURI_ASSERT_MESSAGE_FLAG), __VA_ARGS__))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "core_defines.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <cmsis_compiler.h>
|
||||
|
||||
#ifndef FURI_WARN_UNUSED
|
||||
#define FURI_WARN_UNUSED __attribute__((warn_unused_result))
|
||||
#endif
|
||||
|
||||
#ifndef FURI_WEAK
|
||||
#define FURI_WEAK __attribute__((weak))
|
||||
#endif
|
||||
|
||||
#ifndef FURI_PACKED
|
||||
#define FURI_PACKED __attribute__((packed))
|
||||
#endif
|
||||
|
||||
#ifndef FURI_IS_IRQ_MASKED
|
||||
#define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U)
|
||||
#endif
|
||||
|
||||
#ifndef FURI_IS_IRQ_MODE
|
||||
#define FURI_IS_IRQ_MODE() (__get_IPSR() != 0U)
|
||||
#endif
|
||||
|
||||
#ifndef FURI_IS_ISR
|
||||
#define FURI_IS_ISR() (FURI_IS_IRQ_MODE() || FURI_IS_IRQ_MASKED())
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint32_t isrm;
|
||||
bool from_isr;
|
||||
bool kernel_running;
|
||||
} __FuriCriticalInfo;
|
||||
|
||||
__FuriCriticalInfo __furi_critical_enter(void);
|
||||
|
||||
void __furi_critical_exit(__FuriCriticalInfo info);
|
||||
|
||||
#ifndef FURI_CRITICAL_ENTER
|
||||
#define FURI_CRITICAL_ENTER() __FuriCriticalInfo __furi_critical_info = __furi_critical_enter();
|
||||
#endif
|
||||
|
||||
#ifndef FURI_CRITICAL_EXIT
|
||||
#define FURI_CRITICAL_EXIT() __furi_critical_exit(__furi_critical_info);
|
||||
#endif
|
||||
|
||||
#ifndef FURI_CHECK_RETURN
|
||||
#define FURI_CHECK_RETURN __attribute__((__warn_unused_result__))
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define FURI_RETURNS_NONNULL __attribute__((returns_nonnull))
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(a, b) \
|
||||
({ \
|
||||
__typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
_a > _b ? _a : _b; \
|
||||
})
|
||||
#endif
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) \
|
||||
({ \
|
||||
__typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
_a < _b ? _a : _b; \
|
||||
})
|
||||
#endif
|
||||
|
||||
#ifndef ABS
|
||||
#define ABS(a) ({ (a) < 0 ? -(a) : (a); })
|
||||
#endif
|
||||
|
||||
#ifndef ROUND_UP_TO
|
||||
#define ROUND_UP_TO(a, b) \
|
||||
({ \
|
||||
__typeof__(a) _a = (a); \
|
||||
__typeof__(b) _b = (b); \
|
||||
_a / _b + !!(_a % _b); \
|
||||
})
|
||||
#endif
|
||||
|
||||
#ifndef CLAMP
|
||||
#define CLAMP(x, upper, lower) (MIN(upper, MAX(x, lower)))
|
||||
#endif
|
||||
|
||||
#ifndef COUNT_OF
|
||||
#define COUNT_OF(x) (sizeof(x) / sizeof(x[0]))
|
||||
#endif
|
||||
|
||||
#ifndef FURI_SWAP
|
||||
#define FURI_SWAP(x, y) \
|
||||
do { \
|
||||
typeof(x) SWAP = x; \
|
||||
x = y; \
|
||||
y = SWAP; \
|
||||
} while(0)
|
||||
#endif
|
||||
|
||||
#ifndef PLACE_IN_SECTION
|
||||
#define PLACE_IN_SECTION(x) __attribute__((section(x)))
|
||||
#endif
|
||||
|
||||
#ifndef ALIGN
|
||||
#define ALIGN(n) __attribute__((aligned(n)))
|
||||
#endif
|
||||
|
||||
#ifndef __weak
|
||||
#define __weak __attribute__((weak))
|
||||
#endif
|
||||
|
||||
#ifndef UNUSED
|
||||
#define UNUSED(X) (void)(X)
|
||||
#endif
|
||||
|
||||
#ifndef STRINGIFY
|
||||
#define STRINGIFY(x) #x
|
||||
#endif
|
||||
|
||||
#ifndef TOSTRING
|
||||
#define TOSTRING(x) STRINGIFY(x)
|
||||
#endif
|
||||
|
||||
#ifndef CONCATENATE
|
||||
#define CONCATENATE(a, b) CONCATENATE_(a, b)
|
||||
#define CONCATENATE_(a, b) a##b
|
||||
#endif
|
||||
|
||||
#ifndef REVERSE_BYTES_U32
|
||||
#define REVERSE_BYTES_U32(x) \
|
||||
((((x)&0x000000FF) << 24) | (((x)&0x0000FF00) << 8) | (((x)&0x00FF0000) >> 8) | \
|
||||
(((x)&0xFF000000) >> 24))
|
||||
#endif
|
||||
|
||||
#ifndef FURI_BIT
|
||||
#define FURI_BIT(x, n) (((x) >> (n)) & 1)
|
||||
#endif
|
||||
|
||||
#ifndef FURI_BIT_SET
|
||||
#define FURI_BIT_SET(x, n) \
|
||||
({ \
|
||||
__typeof__(x) _x = (1); \
|
||||
(x) |= (_x << (n)); \
|
||||
})
|
||||
#endif
|
||||
|
||||
#ifndef FURI_BIT_CLEAR
|
||||
#define FURI_BIT_CLEAR(x, n) \
|
||||
({ \
|
||||
__typeof__(x) _x = (1); \
|
||||
(x) &= ~(_x << (n)); \
|
||||
})
|
||||
#endif
|
||||
|
||||
#define FURI_SW_MEMBARRIER() asm volatile("" : : : "memory")
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "common_defines.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
static portMUX_TYPE prv_critical_mutex;
|
||||
|
||||
__FuriCriticalInfo __furi_critical_enter(void) {
|
||||
__FuriCriticalInfo info;
|
||||
|
||||
info.isrm = 0;
|
||||
info.from_isr = FURI_IS_ISR();
|
||||
info.kernel_running = (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING);
|
||||
|
||||
if(info.from_isr) {
|
||||
info.isrm = taskENTER_CRITICAL_FROM_ISR();
|
||||
} else if(info.kernel_running) {
|
||||
taskENTER_CRITICAL(&prv_critical_mutex);
|
||||
} else {
|
||||
__disable_irq();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void __furi_critical_exit(__FuriCriticalInfo info) {
|
||||
if(info.from_isr) {
|
||||
taskEXIT_CRITICAL_FROM_ISR(info.isrm);
|
||||
} else if(info.kernel_running) {
|
||||
taskEXIT_CRITICAL(&prv_critical_mutex);
|
||||
} else {
|
||||
__enable_irq();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
#include "event_flag.h"
|
||||
#include "common_defines.h"
|
||||
#include "check.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
#define FURI_EVENT_FLAG_MAX_BITS_EVENT_GROUPS 24U
|
||||
#define FURI_EVENT_FLAG_INVALID_BITS (~((1UL << FURI_EVENT_FLAG_MAX_BITS_EVENT_GROUPS) - 1U))
|
||||
|
||||
FuriEventFlag* furi_event_flag_alloc() {
|
||||
furi_assert(!FURI_IS_IRQ_MODE());
|
||||
|
||||
EventGroupHandle_t handle = xEventGroupCreate();
|
||||
furi_check(handle);
|
||||
|
||||
return ((FuriEventFlag*)handle);
|
||||
}
|
||||
|
||||
void furi_event_flag_free(FuriEventFlag* instance) {
|
||||
furi_assert(!FURI_IS_IRQ_MODE());
|
||||
vEventGroupDelete((EventGroupHandle_t)instance);
|
||||
}
|
||||
|
||||
uint32_t furi_event_flag_set(FuriEventFlag* instance, uint32_t flags) {
|
||||
furi_assert(instance);
|
||||
furi_assert((flags & FURI_EVENT_FLAG_INVALID_BITS) == 0U);
|
||||
|
||||
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
|
||||
uint32_t rflags;
|
||||
BaseType_t yield;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
yield = pdFALSE;
|
||||
if(xEventGroupSetBitsFromISR(hEventGroup, (EventBits_t)flags, &yield) == pdFAIL) {
|
||||
rflags = (uint32_t)FuriFlagErrorResource;
|
||||
} else {
|
||||
rflags = flags;
|
||||
portYIELD_FROM_ISR(yield);
|
||||
}
|
||||
} else {
|
||||
rflags = xEventGroupSetBits(hEventGroup, (EventBits_t)flags);
|
||||
}
|
||||
|
||||
/* Return event flags after setting */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t furi_event_flag_clear(FuriEventFlag* instance, uint32_t flags) {
|
||||
furi_assert(instance);
|
||||
furi_assert((flags & FURI_EVENT_FLAG_INVALID_BITS) == 0U);
|
||||
|
||||
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
|
||||
uint32_t rflags;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
rflags = xEventGroupGetBitsFromISR(hEventGroup);
|
||||
|
||||
if(xEventGroupClearBitsFromISR(hEventGroup, (EventBits_t)flags) == pdFAIL) {
|
||||
rflags = (uint32_t)FuriStatusErrorResource;
|
||||
} else {
|
||||
/* xEventGroupClearBitsFromISR only registers clear operation in the timer command queue. */
|
||||
/* Yield is required here otherwise clear operation might not execute in the right order. */
|
||||
/* See https://github.com/FreeRTOS/FreeRTOS-Kernel/issues/93 for more info. */
|
||||
portYIELD_FROM_ISR(pdTRUE);
|
||||
}
|
||||
} else {
|
||||
rflags = xEventGroupClearBits(hEventGroup, (EventBits_t)flags);
|
||||
}
|
||||
|
||||
/* Return event flags before clearing */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t furi_event_flag_get(FuriEventFlag* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
|
||||
uint32_t rflags;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
rflags = xEventGroupGetBitsFromISR(hEventGroup);
|
||||
} else {
|
||||
rflags = xEventGroupGetBits(hEventGroup);
|
||||
}
|
||||
|
||||
/* Return current event flags */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t furi_event_flag_wait(
|
||||
FuriEventFlag* instance,
|
||||
uint32_t flags,
|
||||
uint32_t options,
|
||||
uint32_t timeout) {
|
||||
furi_assert(!FURI_IS_IRQ_MODE());
|
||||
furi_assert(instance);
|
||||
furi_assert((flags & FURI_EVENT_FLAG_INVALID_BITS) == 0U);
|
||||
|
||||
EventGroupHandle_t hEventGroup = (EventGroupHandle_t)instance;
|
||||
BaseType_t wait_all;
|
||||
BaseType_t exit_clr;
|
||||
uint32_t rflags;
|
||||
|
||||
if(options & FuriFlagWaitAll) {
|
||||
wait_all = pdTRUE;
|
||||
} else {
|
||||
wait_all = pdFAIL;
|
||||
}
|
||||
|
||||
if(options & FuriFlagNoClear) {
|
||||
exit_clr = pdFAIL;
|
||||
} else {
|
||||
exit_clr = pdTRUE;
|
||||
}
|
||||
|
||||
rflags = xEventGroupWaitBits(
|
||||
hEventGroup, (EventBits_t)flags, exit_clr, wait_all, (TickType_t)timeout);
|
||||
|
||||
if(options & FuriFlagWaitAll) {
|
||||
if((flags & rflags) != flags) {
|
||||
if(timeout > 0U) {
|
||||
rflags = (uint32_t)FuriStatusErrorTimeout;
|
||||
} else {
|
||||
rflags = (uint32_t)FuriStatusErrorResource;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if((flags & rflags) == 0U) {
|
||||
if(timeout > 0U) {
|
||||
rflags = (uint32_t)FuriStatusErrorTimeout;
|
||||
} else {
|
||||
rflags = (uint32_t)FuriStatusErrorResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return event flags before clearing */
|
||||
return (rflags);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @file event_flag.h
|
||||
* Furi Event Flag
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void FuriEventFlag;
|
||||
|
||||
/** Allocate FuriEventFlag
|
||||
*
|
||||
* @return pointer to FuriEventFlag
|
||||
*/
|
||||
FuriEventFlag* furi_event_flag_alloc();
|
||||
|
||||
/** Deallocate FuriEventFlag
|
||||
*
|
||||
* @param instance pointer to FuriEventFlag
|
||||
*/
|
||||
void furi_event_flag_free(FuriEventFlag* instance);
|
||||
|
||||
/** Set flags
|
||||
*
|
||||
* @param instance pointer to FuriEventFlag
|
||||
* @param[in] flags The flags
|
||||
*
|
||||
* @return Resulting flags or error (FuriStatus)
|
||||
*/
|
||||
uint32_t furi_event_flag_set(FuriEventFlag* instance, uint32_t flags);
|
||||
|
||||
/** Clear flags
|
||||
*
|
||||
* @param instance pointer to FuriEventFlag
|
||||
* @param[in] flags The flags
|
||||
*
|
||||
* @return Resulting flags or error (FuriStatus)
|
||||
*/
|
||||
uint32_t furi_event_flag_clear(FuriEventFlag* instance, uint32_t flags);
|
||||
|
||||
/** Get flags
|
||||
*
|
||||
* @param instance pointer to FuriEventFlag
|
||||
*
|
||||
* @return Resulting flags
|
||||
*/
|
||||
uint32_t furi_event_flag_get(FuriEventFlag* instance);
|
||||
|
||||
/** Wait flags
|
||||
*
|
||||
* @param instance pointer to FuriEventFlag
|
||||
* @param[in] flags The flags
|
||||
* @param[in] options The option flags
|
||||
* @param[in] timeout The timeout
|
||||
*
|
||||
* @return Resulting flags or error (FuriStatus)
|
||||
*/
|
||||
uint32_t furi_event_flag_wait(
|
||||
FuriEventFlag* instance,
|
||||
uint32_t flags,
|
||||
uint32_t options,
|
||||
uint32_t timeout);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define FURI_CONFIG_THREAD_MAX_PRIORITIES (32)
|
||||
@@ -0,0 +1,109 @@
|
||||
#include "furi_hal_console.h"
|
||||
#include "common_defines.h"
|
||||
#include "furi_string.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <esp_log.h>
|
||||
#include <memory.h>
|
||||
|
||||
#define TAG "FuriHalConsole"
|
||||
|
||||
#ifdef HEAP_PRINT_DEBUG
|
||||
#define CONSOLE_BAUDRATE 1843200
|
||||
#else
|
||||
#define CONSOLE_BAUDRATE 230400
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
bool alive;
|
||||
FuriHalConsoleTxCallback tx_callback;
|
||||
void* tx_callback_context;
|
||||
} FuriHalConsole;
|
||||
|
||||
FuriHalConsole furi_hal_console = {
|
||||
.alive = false,
|
||||
.tx_callback = NULL,
|
||||
.tx_callback_context = NULL,
|
||||
};
|
||||
|
||||
void furi_hal_console_init() {
|
||||
// furi_hal_uart_init(FuriHalUartIdUSART1, CONSOLE_BAUDRATE);
|
||||
furi_hal_console.alive = true;
|
||||
}
|
||||
|
||||
void furi_hal_console_enable() {
|
||||
// furi_hal_uart_set_irq_cb(FuriHalUartIdUSART1, NULL, NULL);
|
||||
// while(!LL_USART_IsActiveFlag_TC(USART1))
|
||||
// ;
|
||||
// furi_hal_uart_set_br(FuriHalUartIdUSART1, CONSOLE_BAUDRATE);
|
||||
furi_hal_console.alive = true;
|
||||
}
|
||||
|
||||
void furi_hal_console_disable() {
|
||||
// while(!LL_USART_IsActiveFlag_TC(USART1))
|
||||
// ;
|
||||
furi_hal_console.alive = false;
|
||||
}
|
||||
|
||||
void furi_hal_console_set_tx_callback(FuriHalConsoleTxCallback callback, void* context) {
|
||||
FURI_CRITICAL_ENTER();
|
||||
furi_hal_console.tx_callback = callback;
|
||||
furi_hal_console.tx_callback_context = context;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_console_tx(const uint8_t* buffer, size_t buffer_size) {
|
||||
if(!furi_hal_console.alive) return;
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
// Transmit data
|
||||
|
||||
if(furi_hal_console.tx_callback) {
|
||||
furi_hal_console.tx_callback(buffer, buffer_size, furi_hal_console.tx_callback_context);
|
||||
}
|
||||
|
||||
char safe_buffer[buffer_size + 1];
|
||||
memcpy(safe_buffer, buffer, buffer_size);
|
||||
safe_buffer[buffer_size] = 0;
|
||||
|
||||
ESP_LOGI(TAG, "%s", safe_buffer);
|
||||
// furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)buffer, buffer_size);
|
||||
//// Wait for TC flag to be raised for last char
|
||||
// while(!LL_USART_IsActiveFlag_TC(USART1))
|
||||
// ;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size) {
|
||||
if(!furi_hal_console.alive) return;
|
||||
|
||||
FURI_CRITICAL_ENTER();
|
||||
|
||||
char safe_buffer[buffer_size + 1];
|
||||
memcpy(safe_buffer, buffer, buffer_size);
|
||||
safe_buffer[buffer_size] = 0;
|
||||
ESP_LOGI(TAG, "%s", safe_buffer);
|
||||
|
||||
// Transmit data
|
||||
// furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)buffer, buffer_size);
|
||||
// Transmit new line symbols
|
||||
// furi_hal_uart_tx(FuriHalUartIdUSART1, (uint8_t*)"\r\n", 2);
|
||||
// Wait for TC flag to be raised for last char
|
||||
// while(!LL_USART_IsActiveFlag_TC(USART1))
|
||||
// ;
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
void furi_hal_console_printf(const char format[], ...) {
|
||||
FuriString* string;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
string = furi_string_alloc_vprintf(format, args);
|
||||
va_end(args);
|
||||
furi_hal_console_tx((const uint8_t*)furi_string_get_cstr(string), furi_string_size(string));
|
||||
furi_string_free(string);
|
||||
}
|
||||
|
||||
void furi_hal_console_puts(const char* data) {
|
||||
furi_hal_console_tx((const uint8_t*)data, strlen(data));
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void (*FuriHalConsoleTxCallback)(const uint8_t* buffer, size_t size, void* context);
|
||||
|
||||
void furi_hal_console_init();
|
||||
|
||||
void furi_hal_console_enable();
|
||||
|
||||
void furi_hal_console_disable();
|
||||
|
||||
void furi_hal_console_set_tx_callback(FuriHalConsoleTxCallback callback, void* context);
|
||||
|
||||
void furi_hal_console_tx(const uint8_t* buffer, size_t buffer_size);
|
||||
|
||||
void furi_hal_console_tx_with_new_line(const uint8_t* buffer, size_t buffer_size);
|
||||
|
||||
/**
|
||||
* Printf-like plain uart interface
|
||||
* @warning Will not work in ISR context
|
||||
* @param format
|
||||
* @param ...
|
||||
*/
|
||||
void furi_hal_console_printf(const char format[], ...) _ATTRIBUTE((__format__(__printf__, 1, 2)));
|
||||
|
||||
void furi_hal_console_puts(const char* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,304 @@
|
||||
#include "furi_string.h"
|
||||
#include <m-string.h>
|
||||
|
||||
struct FuriString {
|
||||
string_t string;
|
||||
};
|
||||
|
||||
#undef furi_string_alloc_set
|
||||
#undef furi_string_set
|
||||
#undef furi_string_cmp
|
||||
#undef furi_string_cmpi
|
||||
#undef furi_string_search
|
||||
#undef furi_string_search_str
|
||||
#undef furi_string_equal
|
||||
#undef furi_string_replace
|
||||
#undef furi_string_replace_str
|
||||
#undef furi_string_replace_all
|
||||
#undef furi_string_start_with
|
||||
#undef furi_string_end_with
|
||||
#undef furi_string_search_char
|
||||
#undef furi_string_search_rchar
|
||||
#undef furi_string_trim
|
||||
#undef furi_string_cat
|
||||
|
||||
FuriString* furi_string_alloc() {
|
||||
FuriString* string = malloc(sizeof(FuriString));
|
||||
string_init(string->string);
|
||||
return string;
|
||||
}
|
||||
|
||||
FuriString* furi_string_alloc_set(const FuriString* s) {
|
||||
FuriString* string = malloc(sizeof(FuriString)); //-V799
|
||||
string_init_set(string->string, s->string);
|
||||
return string;
|
||||
} //-V773
|
||||
|
||||
FuriString* furi_string_alloc_set_str(const char cstr[]) {
|
||||
FuriString* string = malloc(sizeof(FuriString)); //-V799
|
||||
string_init_set(string->string, cstr);
|
||||
return string;
|
||||
} //-V773
|
||||
|
||||
FuriString* furi_string_alloc_printf(const char format[], ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
FuriString* string = furi_string_alloc_vprintf(format, args);
|
||||
va_end(args);
|
||||
return string;
|
||||
}
|
||||
|
||||
FuriString* furi_string_alloc_vprintf(const char format[], va_list args) {
|
||||
FuriString* string = malloc(sizeof(FuriString));
|
||||
string_init_vprintf(string->string, format, args);
|
||||
return string;
|
||||
}
|
||||
|
||||
FuriString* furi_string_alloc_move(FuriString* s) {
|
||||
FuriString* string = malloc(sizeof(FuriString));
|
||||
string_init_move(string->string, s->string);
|
||||
free(s);
|
||||
return string;
|
||||
}
|
||||
|
||||
void furi_string_free(FuriString* s) {
|
||||
string_clear(s->string);
|
||||
free(s);
|
||||
}
|
||||
|
||||
void furi_string_reserve(FuriString* s, size_t alloc) {
|
||||
string_reserve(s->string, alloc);
|
||||
}
|
||||
|
||||
void furi_string_reset(FuriString* s) {
|
||||
string_reset(s->string);
|
||||
}
|
||||
|
||||
void furi_string_swap(FuriString* v1, FuriString* v2) {
|
||||
string_swap(v1->string, v2->string);
|
||||
}
|
||||
|
||||
void furi_string_move(FuriString* v1, FuriString* v2) {
|
||||
string_clear(v1->string);
|
||||
string_init_move(v1->string, v2->string);
|
||||
free(v2);
|
||||
}
|
||||
|
||||
size_t furi_string_hash(const FuriString* v) {
|
||||
return string_hash(v->string);
|
||||
}
|
||||
|
||||
char furi_string_get_char(const FuriString* v, size_t index) {
|
||||
return string_get_char(v->string, index);
|
||||
}
|
||||
|
||||
const char* furi_string_get_cstr(const FuriString* s) {
|
||||
return string_get_cstr(s->string);
|
||||
}
|
||||
|
||||
void furi_string_set(FuriString* s, FuriString* source) {
|
||||
string_set(s->string, source->string);
|
||||
}
|
||||
|
||||
void furi_string_set_str(FuriString* s, const char cstr[]) {
|
||||
string_set(s->string, cstr);
|
||||
}
|
||||
|
||||
void furi_string_set_strn(FuriString* s, const char str[], size_t n) {
|
||||
string_set_strn(s->string, str, n);
|
||||
}
|
||||
|
||||
void furi_string_set_char(FuriString* s, size_t index, const char c) {
|
||||
string_set_char(s->string, index, c);
|
||||
}
|
||||
|
||||
int furi_string_cmp(const FuriString* s1, const FuriString* s2) {
|
||||
return string_cmp(s1->string, s2->string);
|
||||
}
|
||||
|
||||
int furi_string_cmp_str(const FuriString* s1, const char str[]) {
|
||||
return string_cmp(s1->string, str);
|
||||
}
|
||||
|
||||
int furi_string_cmpi(const FuriString* v1, const FuriString* v2) {
|
||||
return string_cmpi(v1->string, v2->string);
|
||||
}
|
||||
|
||||
int furi_string_cmpi_str(const FuriString* v1, const char p2[]) {
|
||||
return string_cmpi_str(v1->string, p2);
|
||||
}
|
||||
|
||||
size_t furi_string_search(const FuriString* v, const FuriString* needle, size_t start) {
|
||||
return string_search(v->string, needle->string, start);
|
||||
}
|
||||
|
||||
size_t furi_string_search_str(const FuriString* v, const char needle[], size_t start) {
|
||||
return string_search(v->string, needle, start);
|
||||
}
|
||||
|
||||
bool furi_string_equal(const FuriString* v1, const FuriString* v2) {
|
||||
return string_equal_p(v1->string, v2->string);
|
||||
}
|
||||
|
||||
bool furi_string_equal_str(const FuriString* v1, const char v2[]) {
|
||||
return string_equal_p(v1->string, v2);
|
||||
}
|
||||
|
||||
void furi_string_push_back(FuriString* v, char c) {
|
||||
string_push_back(v->string, c);
|
||||
}
|
||||
|
||||
size_t furi_string_size(const FuriString* s) {
|
||||
return string_size(s->string);
|
||||
}
|
||||
|
||||
int furi_string_printf(FuriString* v, const char format[], ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int result = furi_string_vprintf(v, format, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
int furi_string_vprintf(FuriString* v, const char format[], va_list args) {
|
||||
return string_vprintf(v->string, format, args);
|
||||
}
|
||||
|
||||
int furi_string_cat_printf(FuriString* v, const char format[], ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int result = furi_string_cat_vprintf(v, format, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
int furi_string_cat_vprintf(FuriString* v, const char format[], va_list args) {
|
||||
FuriString* string = furi_string_alloc();
|
||||
int ret = furi_string_vprintf(string, format, args);
|
||||
furi_string_cat(v, string);
|
||||
furi_string_free(string);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool furi_string_empty(const FuriString* v) {
|
||||
return string_empty_p(v->string);
|
||||
}
|
||||
|
||||
void furi_string_replace_at(FuriString* v, size_t pos, size_t len, const char str2[]) {
|
||||
string_replace_at(v->string, pos, len, str2);
|
||||
}
|
||||
|
||||
size_t
|
||||
furi_string_replace(FuriString* string, FuriString* needle, FuriString* replace, size_t start) {
|
||||
return string_replace(string->string, needle->string, replace->string, start);
|
||||
}
|
||||
|
||||
size_t furi_string_replace_str(FuriString* v, const char str1[], const char str2[], size_t start) {
|
||||
return string_replace_str(v->string, str1, str2, start);
|
||||
}
|
||||
|
||||
void furi_string_replace_all_str(FuriString* v, const char str1[], const char str2[]) {
|
||||
string_replace_all_str(v->string, str1, str2);
|
||||
}
|
||||
|
||||
void furi_string_replace_all(FuriString* v, const FuriString* str1, const FuriString* str2) {
|
||||
string_replace_all(v->string, str1->string, str2->string);
|
||||
}
|
||||
|
||||
bool furi_string_start_with(const FuriString* v, const FuriString* v2) {
|
||||
return string_start_with_string_p(v->string, v2->string);
|
||||
}
|
||||
|
||||
bool furi_string_start_with_str(const FuriString* v, const char str[]) {
|
||||
return string_start_with_str_p(v->string, str);
|
||||
}
|
||||
|
||||
bool furi_string_end_with(const FuriString* v, const FuriString* v2) {
|
||||
return string_end_with_string_p(v->string, v2->string);
|
||||
}
|
||||
|
||||
bool furi_string_end_with_str(const FuriString* v, const char str[]) {
|
||||
return string_end_with_str_p(v->string, str);
|
||||
}
|
||||
|
||||
size_t furi_string_search_char(const FuriString* v, char c, size_t start) {
|
||||
return string_search_char(v->string, c, start);
|
||||
}
|
||||
|
||||
size_t furi_string_search_rchar(const FuriString* v, char c, size_t start) {
|
||||
return string_search_rchar(v->string, c, start);
|
||||
}
|
||||
|
||||
void furi_string_left(FuriString* v, size_t index) {
|
||||
string_left(v->string, index);
|
||||
}
|
||||
|
||||
void furi_string_right(FuriString* v, size_t index) {
|
||||
string_right(v->string, index);
|
||||
}
|
||||
|
||||
void furi_string_mid(FuriString* v, size_t index, size_t size) {
|
||||
string_mid(v->string, index, size);
|
||||
}
|
||||
|
||||
void furi_string_trim(FuriString* v, const char charac[]) {
|
||||
string_strim(v->string, charac);
|
||||
}
|
||||
|
||||
void furi_string_cat(FuriString* v, const FuriString* v2) {
|
||||
string_cat(v->string, v2->string);
|
||||
}
|
||||
|
||||
void furi_string_cat_str(FuriString* v, const char str[]) {
|
||||
string_cat(v->string, str);
|
||||
}
|
||||
|
||||
void furi_string_set_n(FuriString* v, const FuriString* ref, size_t offset, size_t length) {
|
||||
string_set_n(v->string, ref->string, offset, length);
|
||||
}
|
||||
|
||||
size_t furi_string_utf8_length(FuriString* str) {
|
||||
return string_length_u(str->string);
|
||||
}
|
||||
|
||||
void furi_string_utf8_push(FuriString* str, FuriStringUnicodeValue u) {
|
||||
string_push_u(str->string, u);
|
||||
}
|
||||
|
||||
static m_str1ng_utf8_state_e furi_state_to_state(FuriStringUTF8State state) {
|
||||
switch(state) {
|
||||
case FuriStringUTF8StateStarting:
|
||||
return M_STR1NG_UTF8_STARTING;
|
||||
case FuriStringUTF8StateDecoding1:
|
||||
return M_STR1NG_UTF8_DECODING_1;
|
||||
case FuriStringUTF8StateDecoding2:
|
||||
return M_STR1NG_UTF8_DECODING_2;
|
||||
case FuriStringUTF8StateDecoding3:
|
||||
return M_STR1NG_UTF8_DECODING_3;
|
||||
default:
|
||||
return M_STR1NG_UTF8_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static FuriStringUTF8State state_to_furi_state(m_str1ng_utf8_state_e state) {
|
||||
switch(state) {
|
||||
case M_STR1NG_UTF8_STARTING:
|
||||
return FuriStringUTF8StateStarting;
|
||||
case M_STR1NG_UTF8_DECODING_1:
|
||||
return FuriStringUTF8StateDecoding1;
|
||||
case M_STR1NG_UTF8_DECODING_2:
|
||||
return FuriStringUTF8StateDecoding2;
|
||||
case M_STR1NG_UTF8_DECODING_3:
|
||||
return FuriStringUTF8StateDecoding3;
|
||||
default:
|
||||
return FuriStringUTF8StateError;
|
||||
}
|
||||
}
|
||||
|
||||
void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnicodeValue* unicode) {
|
||||
string_unicode_t m_u = *unicode;
|
||||
m_str1ng_utf8_state_e m_state = furi_state_to_state(*state);
|
||||
m_str1ng_utf8_decode(c, &m_state, &m_u);
|
||||
*state = state_to_furi_state(m_state);
|
||||
*unicode = m_u;
|
||||
}
|
||||
@@ -0,0 +1,738 @@
|
||||
/**
|
||||
* @file string.h
|
||||
* Furi string primitive
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include <m-core.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Furi string failure constant.
|
||||
*/
|
||||
#define FURI_STRING_FAILURE ((size_t)-1)
|
||||
|
||||
/**
|
||||
* @brief Furi string primitive.
|
||||
*/
|
||||
typedef struct FuriString FuriString;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Constructors
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Allocate new FuriString.
|
||||
* @return FuriString*
|
||||
*/
|
||||
FuriString* furi_string_alloc();
|
||||
|
||||
/**
|
||||
* @brief Allocate new FuriString and set it to string.
|
||||
* Allocate & Set the string a to the string.
|
||||
* @param source
|
||||
* @return FuriString*
|
||||
*/
|
||||
FuriString* furi_string_alloc_set(const FuriString* source);
|
||||
|
||||
/**
|
||||
* @brief Allocate new FuriString and set it to C string.
|
||||
* Allocate & Set the string a to the C string.
|
||||
* @param cstr_source
|
||||
* @return FuriString*
|
||||
*/
|
||||
FuriString* furi_string_alloc_set_str(const char cstr_source[]);
|
||||
|
||||
/**
|
||||
* @brief Allocate new FuriString and printf to it.
|
||||
* Initialize and set a string to the given formatted value.
|
||||
* @param format
|
||||
* @param ...
|
||||
* @return FuriString*
|
||||
*/
|
||||
FuriString* furi_string_alloc_printf(const char format[], ...)
|
||||
_ATTRIBUTE((__format__(__printf__, 1, 2)));
|
||||
|
||||
/**
|
||||
* @brief Allocate new FuriString and printf to it.
|
||||
* Initialize and set a string to the given formatted value.
|
||||
* @param format
|
||||
* @param args
|
||||
* @return FuriString*
|
||||
*/
|
||||
FuriString* furi_string_alloc_vprintf(const char format[], va_list args);
|
||||
|
||||
/**
|
||||
* @brief Allocate new FuriString and move source string content to it.
|
||||
* Allocate the string, set it to the other one, and destroy the other one.
|
||||
* @param source
|
||||
* @return FuriString*
|
||||
*/
|
||||
FuriString* furi_string_alloc_move(FuriString* source);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Destructors
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Free FuriString.
|
||||
* @param string
|
||||
*/
|
||||
void furi_string_free(FuriString* string);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// String memory management
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Reserve memory for string.
|
||||
* Modify the string capacity to be able to handle at least 'alloc' characters (including final null char).
|
||||
* @param string
|
||||
* @param size
|
||||
*/
|
||||
void furi_string_reserve(FuriString* string, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Reset string.
|
||||
* Make the string empty.
|
||||
* @param s
|
||||
*/
|
||||
void furi_string_reset(FuriString* string);
|
||||
|
||||
/**
|
||||
* @brief Swap two strings.
|
||||
* Swap the two strings string_1 and string_2.
|
||||
* @param string_1
|
||||
* @param string_2
|
||||
*/
|
||||
void furi_string_swap(FuriString* string_1, FuriString* string_2);
|
||||
|
||||
/**
|
||||
* @brief Move string_2 content to string_1.
|
||||
* Set the string to the other one, and destroy the other one.
|
||||
* @param string_1
|
||||
* @param string_2
|
||||
*/
|
||||
void furi_string_move(FuriString* string_1, FuriString* string_2);
|
||||
|
||||
/**
|
||||
* @brief Compute a hash for the string.
|
||||
* @param string
|
||||
* @return size_t
|
||||
*/
|
||||
size_t furi_string_hash(const FuriString* string);
|
||||
|
||||
/**
|
||||
* @brief Get string size (usually length, but not for UTF-8)
|
||||
* @param string
|
||||
* @return size_t
|
||||
*/
|
||||
size_t furi_string_size(const FuriString* string);
|
||||
|
||||
/**
|
||||
* @brief Check that string is empty or not
|
||||
* @param string
|
||||
* @return bool
|
||||
*/
|
||||
bool furi_string_empty(const FuriString* string);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Getters
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Get the character at the given index.
|
||||
* Return the selected character of the string.
|
||||
* @param string
|
||||
* @param index
|
||||
* @return char
|
||||
*/
|
||||
char furi_string_get_char(const FuriString* string, size_t index);
|
||||
|
||||
/**
|
||||
* @brief Return the string view a classic C string.
|
||||
* @param string
|
||||
* @return const char*
|
||||
*/
|
||||
const char* furi_string_get_cstr(const FuriString* string);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Setters
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Set the string to the other string.
|
||||
* Set the string to the source string.
|
||||
* @param string
|
||||
* @param source
|
||||
*/
|
||||
void furi_string_set(FuriString* string, FuriString* source);
|
||||
|
||||
/**
|
||||
* @brief Set the string to the other C string.
|
||||
* Set the string to the source C string.
|
||||
* @param string
|
||||
* @param source
|
||||
*/
|
||||
void furi_string_set_str(FuriString* string, const char source[]);
|
||||
|
||||
/**
|
||||
* @brief Set the string to the n first characters of the C string.
|
||||
* @param string
|
||||
* @param source
|
||||
* @param length
|
||||
*/
|
||||
void furi_string_set_strn(FuriString* string, const char source[], size_t length);
|
||||
|
||||
/**
|
||||
* @brief Set the character at the given index.
|
||||
* @param string
|
||||
* @param index
|
||||
* @param c
|
||||
*/
|
||||
void furi_string_set_char(FuriString* string, size_t index, const char c);
|
||||
|
||||
/**
|
||||
* @brief Set the string to the n first characters of other one.
|
||||
* @param string
|
||||
* @param source
|
||||
* @param offset
|
||||
* @param length
|
||||
*/
|
||||
void furi_string_set_n(FuriString* string, const FuriString* source, size_t offset, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Format in the string the given printf format
|
||||
* @param string
|
||||
* @param format
|
||||
* @param ...
|
||||
* @return int
|
||||
*/
|
||||
int furi_string_printf(FuriString* string, const char format[], ...)
|
||||
_ATTRIBUTE((__format__(__printf__, 2, 3)));
|
||||
|
||||
/**
|
||||
* @brief Format in the string the given printf format
|
||||
* @param string
|
||||
* @param format
|
||||
* @param args
|
||||
* @return int
|
||||
*/
|
||||
int furi_string_vprintf(FuriString* string, const char format[], va_list args);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Appending
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Append a character to the string.
|
||||
* @param string
|
||||
* @param c
|
||||
*/
|
||||
void furi_string_push_back(FuriString* string, char c);
|
||||
|
||||
/**
|
||||
* @brief Append a string to the string.
|
||||
* Concatenate the string with the other string.
|
||||
* @param string_1
|
||||
* @param string_2
|
||||
*/
|
||||
void furi_string_cat(FuriString* string_1, const FuriString* string_2);
|
||||
|
||||
/**
|
||||
* @brief Append a C string to the string.
|
||||
* Concatenate the string with the C string.
|
||||
* @param string_1
|
||||
* @param cstring_2
|
||||
*/
|
||||
void furi_string_cat_str(FuriString* string_1, const char cstring_2[]);
|
||||
|
||||
/**
|
||||
* @brief Append to the string the formatted string of the given printf format.
|
||||
* @param string
|
||||
* @param format
|
||||
* @param ...
|
||||
* @return int
|
||||
*/
|
||||
int furi_string_cat_printf(FuriString* string, const char format[], ...)
|
||||
_ATTRIBUTE((__format__(__printf__, 2, 3)));
|
||||
|
||||
/**
|
||||
* @brief Append to the string the formatted string of the given printf format.
|
||||
* @param string
|
||||
* @param format
|
||||
* @param args
|
||||
* @return int
|
||||
*/
|
||||
int furi_string_cat_vprintf(FuriString* string, const char format[], va_list args);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Comparators
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Compare two strings and return the sort order.
|
||||
* @param string_1
|
||||
* @param string_2
|
||||
* @return int
|
||||
*/
|
||||
int furi_string_cmp(const FuriString* string_1, const FuriString* string_2);
|
||||
|
||||
/**
|
||||
* @brief Compare string with C string and return the sort order.
|
||||
* @param string_1
|
||||
* @param cstring_2
|
||||
* @return int
|
||||
*/
|
||||
int furi_string_cmp_str(const FuriString* string_1, const char cstring_2[]);
|
||||
|
||||
/**
|
||||
* @brief Compare two strings (case insensitive according to the current locale) and return the sort order.
|
||||
* Note: doesn't work with UTF-8 strings.
|
||||
* @param string_1
|
||||
* @param string_2
|
||||
* @return int
|
||||
*/
|
||||
int furi_string_cmpi(const FuriString* string_1, const FuriString* string_2);
|
||||
|
||||
/**
|
||||
* @brief Compare string with C string (case insensitive according to the current locale) and return the sort order.
|
||||
* Note: doesn't work with UTF-8 strings.
|
||||
* @param string_1
|
||||
* @param cstring_2
|
||||
* @return int
|
||||
*/
|
||||
int furi_string_cmpi_str(const FuriString* string_1, const char cstring_2[]);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Search
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Search the first occurrence of the needle in the string from the position start.
|
||||
* Return STRING_FAILURE if not found.
|
||||
* By default, start is zero.
|
||||
* @param string
|
||||
* @param needle
|
||||
* @param start
|
||||
* @return size_t
|
||||
*/
|
||||
size_t furi_string_search(const FuriString* string, const FuriString* needle, size_t start);
|
||||
|
||||
/**
|
||||
* @brief Search the first occurrence of the needle in the string from the position start.
|
||||
* Return STRING_FAILURE if not found.
|
||||
* @param string
|
||||
* @param needle
|
||||
* @param start
|
||||
* @return size_t
|
||||
*/
|
||||
size_t furi_string_search_str(const FuriString* string, const char needle[], size_t start);
|
||||
|
||||
/**
|
||||
* @brief Search for the position of the character c from the position start (include) in the string.
|
||||
* Return STRING_FAILURE if not found.
|
||||
* By default, start is zero.
|
||||
* @param string
|
||||
* @param c
|
||||
* @param start
|
||||
* @return size_t
|
||||
*/
|
||||
size_t furi_string_search_char(const FuriString* string, char c, size_t start);
|
||||
|
||||
/**
|
||||
* @brief Reverse search for the position of the character c from the position start (include) in the string.
|
||||
* Return STRING_FAILURE if not found.
|
||||
* By default, start is zero.
|
||||
* @param string
|
||||
* @param c
|
||||
* @param start
|
||||
* @return size_t
|
||||
*/
|
||||
size_t furi_string_search_rchar(const FuriString* string, char c, size_t start);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Equality
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Test if two strings are equal.
|
||||
* @param string_1
|
||||
* @param string_2
|
||||
* @return bool
|
||||
*/
|
||||
bool furi_string_equal(const FuriString* string_1, const FuriString* string_2);
|
||||
|
||||
/**
|
||||
* @brief Test if the string is equal to the C string.
|
||||
* @param string_1
|
||||
* @param cstring_2
|
||||
* @return bool
|
||||
*/
|
||||
bool furi_string_equal_str(const FuriString* string_1, const char cstring_2[]);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Replace
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Replace in the string the sub-string at position 'pos' for 'len' bytes into the C string 'replace'.
|
||||
* @param string
|
||||
* @param pos
|
||||
* @param len
|
||||
* @param replace
|
||||
*/
|
||||
void furi_string_replace_at(FuriString* string, size_t pos, size_t len, const char replace[]);
|
||||
|
||||
/**
|
||||
* @brief Replace a string 'needle' to string 'replace' in a string from 'start' position.
|
||||
* By default, start is zero.
|
||||
* Return STRING_FAILURE if 'needle' not found or replace position.
|
||||
* @param string
|
||||
* @param needle
|
||||
* @param replace
|
||||
* @param start
|
||||
* @return size_t
|
||||
*/
|
||||
size_t
|
||||
furi_string_replace(FuriString* string, FuriString* needle, FuriString* replace, size_t start);
|
||||
|
||||
/**
|
||||
* @brief Replace a C string 'needle' to C string 'replace' in a string from 'start' position.
|
||||
* By default, start is zero.
|
||||
* Return STRING_FAILURE if 'needle' not found or replace position.
|
||||
* @param string
|
||||
* @param needle
|
||||
* @param replace
|
||||
* @param start
|
||||
* @return size_t
|
||||
*/
|
||||
size_t furi_string_replace_str(
|
||||
FuriString* string,
|
||||
const char needle[],
|
||||
const char replace[],
|
||||
size_t start);
|
||||
|
||||
/**
|
||||
* @brief Replace all occurrences of 'needle' string into 'replace' string.
|
||||
* @param string
|
||||
* @param needle
|
||||
* @param replace
|
||||
*/
|
||||
void furi_string_replace_all(
|
||||
FuriString* string,
|
||||
const FuriString* needle,
|
||||
const FuriString* replace);
|
||||
|
||||
/**
|
||||
* @brief Replace all occurrences of 'needle' C string into 'replace' C string.
|
||||
* @param string
|
||||
* @param needle
|
||||
* @param replace
|
||||
*/
|
||||
void furi_string_replace_all_str(FuriString* string, const char needle[], const char replace[]);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Start / End tests
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Test if the string starts with the given string.
|
||||
* @param string
|
||||
* @param start
|
||||
* @return bool
|
||||
*/
|
||||
bool furi_string_start_with(const FuriString* string, const FuriString* start);
|
||||
|
||||
/**
|
||||
* @brief Test if the string starts with the given C string.
|
||||
* @param string
|
||||
* @param start
|
||||
* @return bool
|
||||
*/
|
||||
bool furi_string_start_with_str(const FuriString* string, const char start[]);
|
||||
|
||||
/**
|
||||
* @brief Test if the string ends with the given string.
|
||||
* @param string
|
||||
* @param end
|
||||
* @return bool
|
||||
*/
|
||||
bool furi_string_end_with(const FuriString* string, const FuriString* end);
|
||||
|
||||
/**
|
||||
* @brief Test if the string ends with the given C string.
|
||||
* @param string
|
||||
* @param end
|
||||
* @return bool
|
||||
*/
|
||||
bool furi_string_end_with_str(const FuriString* string, const char end[]);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Trim
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief Trim the string left to the first 'index' bytes.
|
||||
* @param string
|
||||
* @param index
|
||||
*/
|
||||
void furi_string_left(FuriString* string, size_t index);
|
||||
|
||||
/**
|
||||
* @brief Trim the string right from the 'index' position to the last position.
|
||||
* @param string
|
||||
* @param index
|
||||
*/
|
||||
void furi_string_right(FuriString* string, size_t index);
|
||||
|
||||
/**
|
||||
* @brief Trim the string from position index to size bytes.
|
||||
* See also furi_string_set_n.
|
||||
* @param string
|
||||
* @param index
|
||||
* @param size
|
||||
*/
|
||||
void furi_string_mid(FuriString* string, size_t index, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Trim a string from the given set of characters (default is " \n\r\t").
|
||||
* @param string
|
||||
* @param chars
|
||||
*/
|
||||
void furi_string_trim(FuriString* string, const char chars[]);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// UTF8
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief An unicode value.
|
||||
*/
|
||||
typedef unsigned int FuriStringUnicodeValue;
|
||||
|
||||
/**
|
||||
* @brief Compute the length in UTF8 characters in the string.
|
||||
* @param string
|
||||
* @return size_t
|
||||
*/
|
||||
size_t furi_string_utf8_length(FuriString* string);
|
||||
|
||||
/**
|
||||
* @brief Push unicode into string, encoding it in UTF8.
|
||||
* @param string
|
||||
* @param unicode
|
||||
*/
|
||||
void furi_string_utf8_push(FuriString* string, FuriStringUnicodeValue unicode);
|
||||
|
||||
/**
|
||||
* @brief State of the UTF8 decoding machine state.
|
||||
*/
|
||||
typedef enum {
|
||||
FuriStringUTF8StateStarting,
|
||||
FuriStringUTF8StateDecoding1,
|
||||
FuriStringUTF8StateDecoding2,
|
||||
FuriStringUTF8StateDecoding3,
|
||||
FuriStringUTF8StateError
|
||||
} FuriStringUTF8State;
|
||||
|
||||
/**
|
||||
* @brief Main generic UTF8 decoder.
|
||||
* It takes a character, and the previous state and the previous value of the unicode value.
|
||||
* It updates the state and the decoded unicode value.
|
||||
* A decoded unicode encoded value is valid only when the state is FuriStringUTF8StateStarting.
|
||||
* @param c
|
||||
* @param state
|
||||
* @param unicode
|
||||
*/
|
||||
void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnicodeValue* unicode);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Lasciate ogne speranza, voi ch’entrate
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
*
|
||||
* Select either the string function or the str function depending on
|
||||
* the b operand to the function.
|
||||
* func1 is the string function / func2 is the str function.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Select for 1 argument
|
||||
*/
|
||||
#define FURI_STRING_SELECT1(func1, func2, a) \
|
||||
_Generic((a), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a)
|
||||
|
||||
/**
|
||||
* @brief Select for 2 arguments
|
||||
*/
|
||||
#define FURI_STRING_SELECT2(func1, func2, a, b) \
|
||||
_Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b)
|
||||
|
||||
/**
|
||||
* @brief Select for 3 arguments
|
||||
*/
|
||||
#define FURI_STRING_SELECT3(func1, func2, a, b, c) \
|
||||
_Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b, c)
|
||||
|
||||
/**
|
||||
* @brief Select for 4 arguments
|
||||
*/
|
||||
#define FURI_STRING_SELECT4(func1, func2, a, b, c, d) \
|
||||
_Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b, c, d)
|
||||
|
||||
/**
|
||||
* @brief Allocate new FuriString and set it content to string (or C string).
|
||||
* ([c]string)
|
||||
*/
|
||||
#define furi_string_alloc_set(a) \
|
||||
FURI_STRING_SELECT1(furi_string_alloc_set, furi_string_alloc_set_str, a)
|
||||
|
||||
/**
|
||||
* @brief Set the string content to string (or C string).
|
||||
* (string, [c]string)
|
||||
*/
|
||||
#define furi_string_set(a, b) FURI_STRING_SELECT2(furi_string_set, furi_string_set_str, a, b)
|
||||
|
||||
/**
|
||||
* @brief Compare string with string (or C string) and return the sort order.
|
||||
* Note: doesn't work with UTF-8 strings.
|
||||
* (string, [c]string)
|
||||
*/
|
||||
#define furi_string_cmp(a, b) FURI_STRING_SELECT2(furi_string_cmp, furi_string_cmp_str, a, b)
|
||||
|
||||
/**
|
||||
* @brief Compare string with string (or C string) (case insensitive according to the current locale) and return the sort order.
|
||||
* Note: doesn't work with UTF-8 strings.
|
||||
* (string, [c]string)
|
||||
*/
|
||||
#define furi_string_cmpi(a, b) FURI_STRING_SELECT2(furi_string_cmpi, furi_string_cmpi_str, a, b)
|
||||
|
||||
/**
|
||||
* @brief Test if the string is equal to the string (or C string).
|
||||
* (string, [c]string)
|
||||
*/
|
||||
#define furi_string_equal(a, b) FURI_STRING_SELECT2(furi_string_equal, furi_string_equal_str, a, b)
|
||||
|
||||
/**
|
||||
* @brief Replace all occurrences of string into string (or C string to another C string) in a string.
|
||||
* (string, [c]string, [c]string)
|
||||
*/
|
||||
#define furi_string_replace_all(a, b, c) \
|
||||
FURI_STRING_SELECT3(furi_string_replace_all, furi_string_replace_all_str, a, b, c)
|
||||
|
||||
/**
|
||||
* @brief Search for a string (or C string) in a string
|
||||
* (string, [c]string[, start=0])
|
||||
*/
|
||||
#define furi_string_search(...) \
|
||||
M_APPLY( \
|
||||
FURI_STRING_SELECT3, \
|
||||
furi_string_search, \
|
||||
furi_string_search_str, \
|
||||
M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
|
||||
/**
|
||||
* @brief Search for a C string in a string
|
||||
* (string, cstring[, start=0])
|
||||
*/
|
||||
#define furi_string_search_str(...) furi_string_search_str(M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
|
||||
|
||||
/**
|
||||
* @brief Test if the string starts with the given string (or C string).
|
||||
* (string, [c]string)
|
||||
*/
|
||||
#define furi_string_start_with(a, b) \
|
||||
FURI_STRING_SELECT2(furi_string_start_with, furi_string_start_with_str, a, b)
|
||||
|
||||
/**
|
||||
* @brief Test if the string ends with the given string (or C string).
|
||||
* (string, [c]string)
|
||||
*/
|
||||
#define furi_string_end_with(a, b) \
|
||||
FURI_STRING_SELECT2(furi_string_end_with, furi_string_end_with_str, a, b)
|
||||
|
||||
/**
|
||||
* @brief Append a string (or C string) to the string.
|
||||
* (string, [c]string)
|
||||
*/
|
||||
#define furi_string_cat(a, b) FURI_STRING_SELECT2(furi_string_cat, furi_string_cat_str, a, b)
|
||||
|
||||
/**
|
||||
* @brief Trim a string from the given set of characters (default is " \n\r\t").
|
||||
* (string[, set=" \n\r\t"])
|
||||
*/
|
||||
#define furi_string_trim(...) furi_string_trim(M_DEFAULT_ARGS(2, (" \n\r\t"), __VA_ARGS__))
|
||||
|
||||
/**
|
||||
* @brief Search for a character in a string.
|
||||
* (string, character[, start=0])
|
||||
*/
|
||||
#define furi_string_search_char(...) furi_string_search_char(M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
|
||||
|
||||
/**
|
||||
* @brief Reverse Search for a character in a string.
|
||||
* (string, character[, start=0])
|
||||
*/
|
||||
#define furi_string_search_rchar(...) furi_string_search_rchar(M_DEFAULT_ARGS(3, (0), __VA_ARGS__))
|
||||
|
||||
/**
|
||||
* @brief Replace a string to another string (or C string to another C string) in a string.
|
||||
* (string, [c]string, [c]string[, start=0])
|
||||
*/
|
||||
#define furi_string_replace(...) \
|
||||
M_APPLY( \
|
||||
FURI_STRING_SELECT4, \
|
||||
furi_string_replace, \
|
||||
furi_string_replace_str, \
|
||||
M_DEFAULT_ARGS(4, (0), __VA_ARGS__))
|
||||
|
||||
/**
|
||||
* @brief Replace a C string to another C string in a string.
|
||||
* (string, cstring, cstring[, start=0])
|
||||
*/
|
||||
#define furi_string_replace_str(...) furi_string_replace_str(M_DEFAULT_ARGS(4, (0), __VA_ARGS__))
|
||||
|
||||
/**
|
||||
* @brief INIT OPLIST for FuriString.
|
||||
*/
|
||||
#define F_STR_INIT(a) ((a) = furi_string_alloc())
|
||||
|
||||
/**
|
||||
* @brief INIT SET OPLIST for FuriString.
|
||||
*/
|
||||
#define F_STR_INIT_SET(a, b) ((a) = furi_string_alloc_set(b))
|
||||
|
||||
/**
|
||||
* @brief INIT MOVE OPLIST for FuriString.
|
||||
*/
|
||||
#define F_STR_INIT_MOVE(a, b) ((a) = furi_string_alloc_move(b))
|
||||
|
||||
/**
|
||||
* @brief OPLIST for FuriString.
|
||||
*/
|
||||
#define FURI_STRING_OPLIST \
|
||||
(INIT(F_STR_INIT), \
|
||||
INIT_SET(F_STR_INIT_SET), \
|
||||
SET(furi_string_set), \
|
||||
INIT_MOVE(F_STR_INIT_MOVE), \
|
||||
MOVE(furi_string_move), \
|
||||
SWAP(furi_string_swap), \
|
||||
RESET(furi_string_reset), \
|
||||
EMPTY_P(furi_string_empty), \
|
||||
CLEAR(furi_string_free), \
|
||||
HASH(furi_string_hash), \
|
||||
EQUAL(furi_string_equal), \
|
||||
CMP(furi_string_cmp), \
|
||||
TYPE(FuriString*))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,202 @@
|
||||
#include "kernel.h"
|
||||
#include "base.h"
|
||||
#include "check.h"
|
||||
#include "common_defines.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <rom/ets_sys.h>
|
||||
|
||||
bool furi_kernel_is_irq_or_masked() {
|
||||
bool irq = false;
|
||||
BaseType_t state;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
/* Called from interrupt context */
|
||||
irq = true;
|
||||
} else {
|
||||
/* Get FreeRTOS scheduler state */
|
||||
state = xTaskGetSchedulerState();
|
||||
|
||||
if(state != taskSCHEDULER_NOT_STARTED) {
|
||||
/* Scheduler was started */
|
||||
if(FURI_IS_IRQ_MASKED()) {
|
||||
/* Interrupts are masked */
|
||||
irq = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return context, 0: thread context, 1: IRQ context */
|
||||
return (irq);
|
||||
}
|
||||
|
||||
bool furi_kernel_is_running() {
|
||||
return xTaskGetSchedulerState() != taskSCHEDULER_RUNNING;
|
||||
}
|
||||
|
||||
int32_t furi_kernel_lock() {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
|
||||
int32_t lock;
|
||||
|
||||
switch(xTaskGetSchedulerState()) {
|
||||
case taskSCHEDULER_SUSPENDED:
|
||||
lock = 1;
|
||||
break;
|
||||
|
||||
case taskSCHEDULER_RUNNING:
|
||||
vTaskSuspendAll();
|
||||
lock = 0;
|
||||
break;
|
||||
|
||||
case taskSCHEDULER_NOT_STARTED:
|
||||
default:
|
||||
lock = (int32_t)FuriStatusError;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Return previous lock state */
|
||||
return (lock);
|
||||
}
|
||||
|
||||
int32_t furi_kernel_unlock() {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
|
||||
int32_t lock;
|
||||
|
||||
switch(xTaskGetSchedulerState()) {
|
||||
case taskSCHEDULER_SUSPENDED:
|
||||
lock = 1;
|
||||
|
||||
if(xTaskResumeAll() != pdTRUE) {
|
||||
if(xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED) {
|
||||
lock = (int32_t)FuriStatusError;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case taskSCHEDULER_RUNNING:
|
||||
lock = 0;
|
||||
break;
|
||||
|
||||
case taskSCHEDULER_NOT_STARTED:
|
||||
default:
|
||||
lock = (int32_t)FuriStatusError;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Return previous lock state */
|
||||
return (lock);
|
||||
}
|
||||
|
||||
int32_t furi_kernel_restore_lock(int32_t lock) {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
|
||||
switch(xTaskGetSchedulerState()) {
|
||||
case taskSCHEDULER_SUSPENDED:
|
||||
case taskSCHEDULER_RUNNING:
|
||||
if(lock == 1) {
|
||||
vTaskSuspendAll();
|
||||
} else {
|
||||
if(lock != 0) {
|
||||
lock = (int32_t)FuriStatusError;
|
||||
} else {
|
||||
if(xTaskResumeAll() != pdTRUE) {
|
||||
if(xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) {
|
||||
lock = (int32_t)FuriStatusError;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case taskSCHEDULER_NOT_STARTED:
|
||||
default:
|
||||
lock = (int32_t)FuriStatusError;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Return new lock state */
|
||||
return (lock);
|
||||
}
|
||||
|
||||
uint32_t furi_kernel_get_tick_frequency() {
|
||||
/* Return frequency in hertz */
|
||||
return (configTICK_RATE_HZ_RAW);
|
||||
}
|
||||
|
||||
void furi_delay_tick(uint32_t ticks) {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
if(ticks == 0U) {
|
||||
taskYIELD();
|
||||
} else {
|
||||
vTaskDelay(ticks);
|
||||
}
|
||||
}
|
||||
|
||||
FuriStatus furi_delay_until_tick(uint32_t tick) {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
|
||||
TickType_t tcnt, delay;
|
||||
FuriStatus stat;
|
||||
|
||||
stat = FuriStatusOk;
|
||||
tcnt = xTaskGetTickCount();
|
||||
|
||||
/* Determine remaining number of tick to delay */
|
||||
delay = (TickType_t)tick - tcnt;
|
||||
|
||||
/* Check if target tick has not expired */
|
||||
if((delay != 0U) && (0 == (delay >> (8 * sizeof(TickType_t) - 1)))) {
|
||||
if(xTaskDelayUntil(&tcnt, delay) == pdFALSE) {
|
||||
/* Did not delay */
|
||||
stat = FuriStatusError;
|
||||
}
|
||||
} else {
|
||||
/* No delay or already expired */
|
||||
stat = FuriStatusErrorParameter;
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
uint32_t furi_get_tick() {
|
||||
TickType_t ticks;
|
||||
|
||||
if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
ticks = xTaskGetTickCountFromISR();
|
||||
} else {
|
||||
ticks = xTaskGetTickCount();
|
||||
}
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
uint32_t furi_ms_to_ticks(uint32_t milliseconds) {
|
||||
#if configTICK_RATE_HZ_RAW == 1000
|
||||
return milliseconds;
|
||||
#else
|
||||
return (uint32_t)((float)configTICK_RATE_HZ_RAW) / 1000.0f * (float)milliseconds;
|
||||
#endif
|
||||
}
|
||||
|
||||
void furi_delay_ms(uint32_t milliseconds) {
|
||||
if(!FURI_IS_ISR() && xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
|
||||
if(milliseconds > 0 && milliseconds < portMAX_DELAY - 1) {
|
||||
milliseconds += 1;
|
||||
}
|
||||
#if configTICK_RATE_HZ_RAW == 1000
|
||||
furi_delay_tick(milliseconds);
|
||||
#else
|
||||
furi_delay_tick(furi_ms_to_ticks(milliseconds));
|
||||
#endif
|
||||
} else if(milliseconds > 0) {
|
||||
furi_delay_us(milliseconds * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
void furi_delay_us(uint32_t microseconds) {
|
||||
ets_delay_us(microseconds);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @file kernel.h
|
||||
* Furi Kernel primitives
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define configTICK_RATE_HZ_RAW 1000
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Check if CPU is in IRQ or kernel running and IRQ is masked
|
||||
*
|
||||
* Originally this primitive was born as a workaround for FreeRTOS kernel primitives shenanigans with PRIMASK.
|
||||
*
|
||||
* Meaningful use cases are:
|
||||
*
|
||||
* - When kernel is started and you want to ensure that you are not in IRQ or IRQ is not masked(like in critical section)
|
||||
* - When kernel is not started and you want to make sure that you are not in IRQ mode, ignoring PRIMASK.
|
||||
*
|
||||
* As you can see there will be edge case when kernel is not started and PRIMASK is not 0 that may cause some funky behavior.
|
||||
* Most likely it will happen after kernel primitives being used, but control not yet passed to kernel.
|
||||
* It's up to you to figure out if it is safe for your code or not.
|
||||
*
|
||||
* @return true if CPU is in IRQ or kernel running and IRQ is masked
|
||||
*/
|
||||
bool furi_kernel_is_irq_or_masked();
|
||||
|
||||
/** Check if kernel is running
|
||||
*
|
||||
* @return true if running, false otherwise
|
||||
*/
|
||||
bool furi_kernel_is_running();
|
||||
|
||||
/** Lock kernel, pause process scheduling
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @return previous lock state(0 - unlocked, 1 - locked)
|
||||
*/
|
||||
int32_t furi_kernel_lock();
|
||||
|
||||
/** Unlock kernel, resume process scheduling
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @return previous lock state(0 - unlocked, 1 - locked)
|
||||
*/
|
||||
int32_t furi_kernel_unlock();
|
||||
|
||||
/** Restore kernel lock state
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @param[in] lock The lock state
|
||||
*
|
||||
* @return new lock state or error
|
||||
*/
|
||||
int32_t furi_kernel_restore_lock(int32_t lock);
|
||||
|
||||
/** Get kernel systick frequency
|
||||
*
|
||||
* @return systick counts per second
|
||||
*/
|
||||
uint32_t furi_kernel_get_tick_frequency();
|
||||
|
||||
/** Delay execution
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* Also keep in mind delay is aliased to scheduler timer intervals.
|
||||
*
|
||||
* @param[in] ticks The ticks count to pause
|
||||
*/
|
||||
void furi_delay_tick(uint32_t ticks);
|
||||
|
||||
/** Delay until tick
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @param[in] ticks The tick until which kerel should delay task execution
|
||||
*
|
||||
* @return The furi status.
|
||||
*/
|
||||
FuriStatus furi_delay_until_tick(uint32_t tick);
|
||||
|
||||
/** Get current tick counter
|
||||
*
|
||||
* System uptime, may overflow.
|
||||
*
|
||||
* @return Current ticks in milliseconds
|
||||
*/
|
||||
uint32_t furi_get_tick(void);
|
||||
|
||||
/** Convert milliseconds to ticks
|
||||
*
|
||||
* @param[in] milliseconds time in milliseconds
|
||||
* @return time in ticks
|
||||
*/
|
||||
uint32_t furi_ms_to_ticks(uint32_t milliseconds);
|
||||
|
||||
/** Delay in milliseconds
|
||||
*
|
||||
* This method uses kernel ticks on the inside, which causes delay to be aliased to scheduler timer intervals.
|
||||
* Real wait time will be between X+ milliseconds.
|
||||
* Special value: 0, will cause task yield.
|
||||
* Also if used when kernel is not running will fall back to `furi_delay_us`.
|
||||
*
|
||||
* @warning Cannot be used from ISR
|
||||
*
|
||||
* @param[in] milliseconds milliseconds to wait
|
||||
*/
|
||||
void furi_delay_ms(uint32_t milliseconds);
|
||||
|
||||
/** Delay in microseconds
|
||||
*
|
||||
* Implemented using Cortex DWT counter. Blocking and non aliased.
|
||||
*
|
||||
* @param[in] microseconds microseconds to wait
|
||||
*/
|
||||
void furi_delay_us(uint32_t microseconds);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,26 @@
|
||||
// File originated from Flipper Zero / Furi
|
||||
#pragma once
|
||||
#include <m-core.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define M_INIT_DUP(a) ((a) = strdup(""))
|
||||
#define M_INIT_SET_DUP(a, b) ((a) = strdup(b))
|
||||
#define M_SET_DUP(a, b) (free((void*)a), (a) = strdup(b))
|
||||
#define M_CLEAR_DUP(a) (free((void*)a))
|
||||
|
||||
#define M_CSTR_DUP_OPLIST \
|
||||
(INIT(M_INIT_DUP), \
|
||||
INIT_SET(M_INIT_SET_DUP), \
|
||||
SET(M_SET_DUP), \
|
||||
CLEAR(M_CLEAR_DUP), \
|
||||
HASH(m_core_cstr_hash), \
|
||||
EQUAL(M_CSTR_EQUAL), \
|
||||
CMP(strcmp), \
|
||||
TYPE(const char*))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,182 @@
|
||||
#include "kernel.h"
|
||||
#include "message_queue.h"
|
||||
#include "check.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
|
||||
FuriMessageQueue* furi_message_queue_alloc(uint32_t msg_count, uint32_t msg_size) {
|
||||
furi_assert((furi_kernel_is_irq_or_masked() == 0U) && (msg_count > 0U) && (msg_size > 0U));
|
||||
|
||||
QueueHandle_t handle = xQueueCreate(msg_count, msg_size);
|
||||
furi_check(handle);
|
||||
|
||||
return ((FuriMessageQueue*)handle);
|
||||
}
|
||||
|
||||
void furi_message_queue_free(FuriMessageQueue* instance) {
|
||||
furi_assert(furi_kernel_is_irq_or_masked() == 0U);
|
||||
furi_assert(instance);
|
||||
|
||||
vQueueDelete((QueueHandle_t)instance);
|
||||
}
|
||||
|
||||
FuriStatus
|
||||
furi_message_queue_put(FuriMessageQueue* instance, const void* msg_ptr, uint32_t timeout) {
|
||||
QueueHandle_t hQueue = (QueueHandle_t)instance;
|
||||
FuriStatus stat;
|
||||
BaseType_t yield;
|
||||
|
||||
stat = FuriStatusOk;
|
||||
|
||||
if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
if((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) {
|
||||
stat = FuriStatusErrorParameter;
|
||||
} else {
|
||||
yield = pdFALSE;
|
||||
|
||||
if(xQueueSendToBackFromISR(hQueue, msg_ptr, &yield) != pdTRUE) {
|
||||
stat = FuriStatusErrorResource;
|
||||
} else {
|
||||
portYIELD_FROM_ISR(yield);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if((hQueue == NULL) || (msg_ptr == NULL)) {
|
||||
stat = FuriStatusErrorParameter;
|
||||
} else {
|
||||
if(xQueueSendToBack(hQueue, msg_ptr, (TickType_t)timeout) != pdPASS) {
|
||||
if(timeout != 0U) {
|
||||
stat = FuriStatusErrorTimeout;
|
||||
} else {
|
||||
stat = FuriStatusErrorResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
FuriStatus furi_message_queue_get(FuriMessageQueue* instance, void* msg_ptr, uint32_t timeout) {
|
||||
QueueHandle_t hQueue = (QueueHandle_t)instance;
|
||||
FuriStatus stat;
|
||||
BaseType_t yield;
|
||||
|
||||
stat = FuriStatusOk;
|
||||
|
||||
if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
if((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) {
|
||||
stat = FuriStatusErrorParameter;
|
||||
} else {
|
||||
yield = pdFALSE;
|
||||
|
||||
if(xQueueReceiveFromISR(hQueue, msg_ptr, &yield) != pdPASS) {
|
||||
stat = FuriStatusErrorResource;
|
||||
} else {
|
||||
portYIELD_FROM_ISR(yield);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if((hQueue == NULL) || (msg_ptr == NULL)) {
|
||||
stat = FuriStatusErrorParameter;
|
||||
} else {
|
||||
if(xQueueReceive(hQueue, msg_ptr, (TickType_t)timeout) != pdPASS) {
|
||||
if(timeout != 0U) {
|
||||
stat = FuriStatusErrorTimeout;
|
||||
} else {
|
||||
stat = FuriStatusErrorResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
uint32_t furi_message_queue_get_capacity(FuriMessageQueue* instance) {
|
||||
StaticQueue_t* mq = (StaticQueue_t*)instance;
|
||||
uint32_t capacity;
|
||||
|
||||
if(mq == NULL) {
|
||||
capacity = 0U;
|
||||
} else {
|
||||
/* capacity = pxQueue->uxLength */
|
||||
capacity = mq->uxDummy4[1];
|
||||
}
|
||||
|
||||
/* Return maximum number of messages */
|
||||
return (capacity);
|
||||
}
|
||||
|
||||
uint32_t furi_message_queue_get_message_size(FuriMessageQueue* instance) {
|
||||
StaticQueue_t* mq = (StaticQueue_t*)instance;
|
||||
uint32_t size;
|
||||
|
||||
if(mq == NULL) {
|
||||
size = 0U;
|
||||
} else {
|
||||
/* size = pxQueue->uxItemSize */
|
||||
size = mq->uxDummy4[2];
|
||||
}
|
||||
|
||||
/* Return maximum message size */
|
||||
return (size);
|
||||
}
|
||||
|
||||
uint32_t furi_message_queue_get_count(FuriMessageQueue* instance) {
|
||||
QueueHandle_t hQueue = (QueueHandle_t)instance;
|
||||
UBaseType_t count;
|
||||
|
||||
if(hQueue == NULL) {
|
||||
count = 0U;
|
||||
} else if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
count = uxQueueMessagesWaitingFromISR(hQueue);
|
||||
} else {
|
||||
count = uxQueueMessagesWaiting(hQueue);
|
||||
}
|
||||
|
||||
/* Return number of queued messages */
|
||||
return ((uint32_t)count);
|
||||
}
|
||||
|
||||
uint32_t furi_message_queue_get_space(FuriMessageQueue* instance) {
|
||||
StaticQueue_t* mq = (StaticQueue_t*)instance;
|
||||
uint32_t space;
|
||||
uint32_t isrm;
|
||||
|
||||
if(mq == NULL) {
|
||||
space = 0U;
|
||||
} else if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
isrm = taskENTER_CRITICAL_FROM_ISR();
|
||||
|
||||
/* space = pxQueue->uxLength - pxQueue->uxMessagesWaiting; */
|
||||
space = mq->uxDummy4[1] - mq->uxDummy4[0];
|
||||
|
||||
taskEXIT_CRITICAL_FROM_ISR(isrm);
|
||||
} else {
|
||||
space = (uint32_t)uxQueueSpacesAvailable((QueueHandle_t)mq);
|
||||
}
|
||||
|
||||
/* Return number of available slots */
|
||||
return (space);
|
||||
}
|
||||
|
||||
FuriStatus furi_message_queue_reset(FuriMessageQueue* instance) {
|
||||
QueueHandle_t hQueue = (QueueHandle_t)instance;
|
||||
FuriStatus stat;
|
||||
|
||||
if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
stat = FuriStatusErrorISR;
|
||||
} else if(hQueue == NULL) {
|
||||
stat = FuriStatusErrorParameter;
|
||||
} else {
|
||||
stat = FuriStatusOk;
|
||||
(void)xQueueReset(hQueue);
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @file message_queue.h
|
||||
* FuriMessageQueue
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void FuriMessageQueue;
|
||||
|
||||
/** Allocate furi message queue
|
||||
*
|
||||
* @param[in] msg_count The message count
|
||||
* @param[in] msg_size The message size
|
||||
*
|
||||
* @return pointer to FuriMessageQueue instance
|
||||
*/
|
||||
FuriMessageQueue* furi_message_queue_alloc(uint32_t msg_count, uint32_t msg_size);
|
||||
|
||||
/** Free queue
|
||||
*
|
||||
* @param instance pointer to FuriMessageQueue instance
|
||||
*/
|
||||
void furi_message_queue_free(FuriMessageQueue* instance);
|
||||
|
||||
/** Put message into queue
|
||||
*
|
||||
* @param instance pointer to FuriMessageQueue instance
|
||||
* @param[in] msg_ptr The message pointer
|
||||
* @param[in] timeout The timeout
|
||||
* @param[in] msg_prio The message prio
|
||||
*
|
||||
* @return The furi status.
|
||||
*/
|
||||
FuriStatus
|
||||
furi_message_queue_put(FuriMessageQueue* instance, const void* msg_ptr, uint32_t timeout);
|
||||
|
||||
/** Get message from queue
|
||||
*
|
||||
* @param instance pointer to FuriMessageQueue instance
|
||||
* @param msg_ptr The message pointer
|
||||
* @param msg_prio The message prioority
|
||||
* @param[in] timeout The timeout
|
||||
*
|
||||
* @return The furi status.
|
||||
*/
|
||||
FuriStatus furi_message_queue_get(FuriMessageQueue* instance, void* msg_ptr, uint32_t timeout);
|
||||
|
||||
/** Get queue capacity
|
||||
*
|
||||
* @param instance pointer to FuriMessageQueue instance
|
||||
*
|
||||
* @return capacity in object count
|
||||
*/
|
||||
uint32_t furi_message_queue_get_capacity(FuriMessageQueue* instance);
|
||||
|
||||
/** Get message size
|
||||
*
|
||||
* @param instance pointer to FuriMessageQueue instance
|
||||
*
|
||||
* @return Message size in bytes
|
||||
*/
|
||||
uint32_t furi_message_queue_get_message_size(FuriMessageQueue* instance);
|
||||
|
||||
/** Get message count in queue
|
||||
*
|
||||
* @param instance pointer to FuriMessageQueue instance
|
||||
*
|
||||
* @return Message count
|
||||
*/
|
||||
uint32_t furi_message_queue_get_count(FuriMessageQueue* instance);
|
||||
|
||||
/** Get queue available space
|
||||
*
|
||||
* @param instance pointer to FuriMessageQueue instance
|
||||
*
|
||||
* @return Message count
|
||||
*/
|
||||
uint32_t furi_message_queue_get_space(FuriMessageQueue* instance);
|
||||
|
||||
/** Reset queue
|
||||
*
|
||||
* @param instance pointer to FuriMessageQueue instance
|
||||
*
|
||||
* @return The furi status.
|
||||
*/
|
||||
FuriStatus furi_message_queue_reset(FuriMessageQueue* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "mutex.h"
|
||||
#include "check.h"
|
||||
#include "common_defines.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
FuriMutex* furi_mutex_alloc(FuriMutexType type) {
|
||||
furi_assert(!FURI_IS_IRQ_MODE());
|
||||
|
||||
SemaphoreHandle_t hMutex = NULL;
|
||||
|
||||
if(type == FuriMutexTypeNormal) {
|
||||
hMutex = xSemaphoreCreateMutex();
|
||||
} else if(type == FuriMutexTypeRecursive) {
|
||||
hMutex = xSemaphoreCreateRecursiveMutex();
|
||||
} else {
|
||||
furi_crash("Programming error");
|
||||
}
|
||||
|
||||
furi_check(hMutex != NULL);
|
||||
|
||||
if(type == FuriMutexTypeRecursive) {
|
||||
/* Set LSB as 'recursive mutex flag' */
|
||||
hMutex = (SemaphoreHandle_t)((uint32_t)hMutex | 1U);
|
||||
}
|
||||
|
||||
/* Return mutex ID */
|
||||
return ((FuriMutex*)hMutex);
|
||||
}
|
||||
|
||||
void furi_mutex_free(FuriMutex* instance) {
|
||||
furi_assert(!FURI_IS_IRQ_MODE());
|
||||
furi_assert(instance);
|
||||
|
||||
vSemaphoreDelete((SemaphoreHandle_t)((uint32_t)instance & ~1U));
|
||||
}
|
||||
|
||||
FuriStatus furi_mutex_acquire(FuriMutex* instance, uint32_t timeout) {
|
||||
SemaphoreHandle_t hMutex;
|
||||
FuriStatus stat;
|
||||
uint32_t rmtx;
|
||||
|
||||
hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U);
|
||||
|
||||
/* Extract recursive mutex flag */
|
||||
rmtx = (uint32_t)instance & 1U;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
FuriStatus furi_mutex_release(FuriMutex* instance) {
|
||||
SemaphoreHandle_t hMutex;
|
||||
FuriStatus stat;
|
||||
uint32_t rmtx;
|
||||
|
||||
hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U);
|
||||
|
||||
/* Extract recursive mutex flag */
|
||||
rmtx = (uint32_t)instance & 1U;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
FuriThreadId furi_mutex_get_owner(FuriMutex* instance) {
|
||||
SemaphoreHandle_t hMutex;
|
||||
FuriThreadId owner;
|
||||
|
||||
hMutex = (SemaphoreHandle_t)((uint32_t)instance & ~1U);
|
||||
|
||||
if((FURI_IS_IRQ_MODE()) || (hMutex == NULL)) {
|
||||
owner = 0;
|
||||
} else {
|
||||
owner = (FuriThreadId)xSemaphoreGetMutexHolder(hMutex);
|
||||
}
|
||||
|
||||
/* Return owner thread ID */
|
||||
return (owner);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @file mutex.h
|
||||
* FuriMutex
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include "thread.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
FuriMutexTypeNormal,
|
||||
FuriMutexTypeRecursive,
|
||||
} FuriMutexType;
|
||||
|
||||
typedef void FuriMutex;
|
||||
|
||||
/** Allocate FuriMutex
|
||||
*
|
||||
* @param[in] type The mutex type
|
||||
*
|
||||
* @return pointer to FuriMutex instance
|
||||
*/
|
||||
FuriMutex* furi_mutex_alloc(FuriMutexType type);
|
||||
|
||||
/** Free FuriMutex
|
||||
*
|
||||
* @param instance The pointer to FuriMutex instance
|
||||
*/
|
||||
void furi_mutex_free(FuriMutex* instance);
|
||||
|
||||
/** Acquire mutex
|
||||
*
|
||||
* @param instance The pointer to FuriMutex instance
|
||||
* @param[in] timeout The timeout
|
||||
*
|
||||
* @return The furi status.
|
||||
*/
|
||||
FuriStatus furi_mutex_acquire(FuriMutex* instance, uint32_t timeout);
|
||||
|
||||
/** Release mutex
|
||||
*
|
||||
* @param instance The pointer to FuriMutex instance
|
||||
*
|
||||
* @return The furi status.
|
||||
*/
|
||||
FuriStatus furi_mutex_release(FuriMutex* instance);
|
||||
|
||||
/** Get mutex owner thread id
|
||||
*
|
||||
* @param instance The pointer to FuriMutex instance
|
||||
*
|
||||
* @return The furi thread identifier.
|
||||
*/
|
||||
FuriThreadId furi_mutex_get_owner(FuriMutex* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "pubsub.h"
|
||||
#include "check.h"
|
||||
#include "mutex.h"
|
||||
|
||||
#include <m-list.h>
|
||||
|
||||
struct FuriPubSubSubscription {
|
||||
FuriPubSubCallback callback;
|
||||
void* callback_context;
|
||||
};
|
||||
|
||||
LIST_DEF(FuriPubSubSubscriptionList, FuriPubSubSubscription, M_POD_OPLIST);
|
||||
|
||||
struct FuriPubSub {
|
||||
FuriPubSubSubscriptionList_t items;
|
||||
FuriMutex* mutex;
|
||||
};
|
||||
|
||||
FuriPubSub* furi_pubsub_alloc() {
|
||||
FuriPubSub* pubsub = malloc(sizeof(FuriPubSub));
|
||||
|
||||
pubsub->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
furi_assert(pubsub->mutex);
|
||||
|
||||
FuriPubSubSubscriptionList_init(pubsub->items);
|
||||
|
||||
return pubsub;
|
||||
}
|
||||
|
||||
void furi_pubsub_free(FuriPubSub* pubsub) {
|
||||
furi_assert(pubsub);
|
||||
|
||||
furi_check(FuriPubSubSubscriptionList_size(pubsub->items) == 0);
|
||||
|
||||
FuriPubSubSubscriptionList_clear(pubsub->items);
|
||||
|
||||
furi_mutex_free(pubsub->mutex);
|
||||
|
||||
free(pubsub);
|
||||
}
|
||||
|
||||
FuriPubSubSubscription*
|
||||
furi_pubsub_subscribe(FuriPubSub* pubsub, FuriPubSubCallback callback, void* callback_context) {
|
||||
furi_check(furi_mutex_acquire(pubsub->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
// put uninitialized item to the list
|
||||
FuriPubSubSubscription* item = FuriPubSubSubscriptionList_push_raw(pubsub->items);
|
||||
|
||||
// initialize item
|
||||
item->callback = callback;
|
||||
item->callback_context = callback_context;
|
||||
|
||||
furi_check(furi_mutex_release(pubsub->mutex) == FuriStatusOk);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void furi_pubsub_unsubscribe(FuriPubSub* pubsub, FuriPubSubSubscription* pubsub_subscription) {
|
||||
furi_assert(pubsub);
|
||||
furi_assert(pubsub_subscription);
|
||||
|
||||
furi_check(furi_mutex_acquire(pubsub->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
bool result = false;
|
||||
|
||||
// iterate over items
|
||||
FuriPubSubSubscriptionList_it_t it;
|
||||
for(FuriPubSubSubscriptionList_it(it, pubsub->items); !FuriPubSubSubscriptionList_end_p(it);
|
||||
FuriPubSubSubscriptionList_next(it)) {
|
||||
const FuriPubSubSubscription* item = FuriPubSubSubscriptionList_cref(it);
|
||||
|
||||
// if the iterator is equal to our element
|
||||
if(item == pubsub_subscription) {
|
||||
FuriPubSubSubscriptionList_remove(pubsub->items, it);
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
furi_check(furi_mutex_release(pubsub->mutex) == FuriStatusOk);
|
||||
furi_check(result);
|
||||
}
|
||||
|
||||
void furi_pubsub_publish(FuriPubSub* pubsub, void* message) {
|
||||
furi_check(furi_mutex_acquire(pubsub->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
// iterate over subscribers
|
||||
FuriPubSubSubscriptionList_it_t it;
|
||||
for(FuriPubSubSubscriptionList_it(it, pubsub->items); !FuriPubSubSubscriptionList_end_p(it);
|
||||
FuriPubSubSubscriptionList_next(it)) {
|
||||
const FuriPubSubSubscription* item = FuriPubSubSubscriptionList_cref(it);
|
||||
item->callback(message, item->callback_context);
|
||||
}
|
||||
|
||||
furi_check(furi_mutex_release(pubsub->mutex) == FuriStatusOk);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* @file pubsub.h
|
||||
* FuriPubSub
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** FuriPubSub Callback type */
|
||||
typedef void (*FuriPubSubCallback)(const void* message, void* context);
|
||||
|
||||
/** FuriPubSub type */
|
||||
typedef struct FuriPubSub FuriPubSub;
|
||||
|
||||
/** FuriPubSubSubscription type */
|
||||
typedef struct FuriPubSubSubscription FuriPubSubSubscription;
|
||||
|
||||
/** Allocate FuriPubSub
|
||||
*
|
||||
* Reentrable, Not threadsafe, one owner
|
||||
*
|
||||
* @return pointer to FuriPubSub instance
|
||||
*/
|
||||
FuriPubSub* furi_pubsub_alloc();
|
||||
|
||||
/** Free FuriPubSub
|
||||
*
|
||||
* @param pubsub FuriPubSub instance
|
||||
*/
|
||||
void furi_pubsub_free(FuriPubSub* pubsub);
|
||||
|
||||
/** Subscribe to FuriPubSub
|
||||
*
|
||||
* Threadsafe, Reentrable
|
||||
*
|
||||
* @param pubsub pointer to FuriPubSub instance
|
||||
* @param[in] callback The callback
|
||||
* @param callback_context The callback context
|
||||
*
|
||||
* @return pointer to FuriPubSubSubscription instance
|
||||
*/
|
||||
FuriPubSubSubscription*
|
||||
furi_pubsub_subscribe(FuriPubSub* pubsub, FuriPubSubCallback callback, void* callback_context);
|
||||
|
||||
/** Unsubscribe from FuriPubSub
|
||||
*
|
||||
* No use of `pubsub_subscription` allowed after call of this method
|
||||
* Threadsafe, Reentrable.
|
||||
*
|
||||
* @param pubsub pointer to FuriPubSub instance
|
||||
* @param pubsub_subscription pointer to FuriPubSubSubscription instance
|
||||
*/
|
||||
void furi_pubsub_unsubscribe(FuriPubSub* pubsub, FuriPubSubSubscription* pubsub_subscription);
|
||||
|
||||
/** Publish message to FuriPubSub
|
||||
*
|
||||
* Threadsafe, Reentrable.
|
||||
*
|
||||
* @param pubsub pointer to FuriPubSub instance
|
||||
* @param message message pointer to publish
|
||||
*/
|
||||
void furi_pubsub_publish(FuriPubSub* pubsub, void* message);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "record.h"
|
||||
#include "check.h"
|
||||
#include "mutex.h"
|
||||
#include "event_flag.h"
|
||||
|
||||
#include <m-dict.h>
|
||||
#include "m_cstr_dup.h"
|
||||
|
||||
#define FURI_RECORD_FLAG_READY (0x1)
|
||||
|
||||
typedef struct {
|
||||
FuriEventFlag* flags;
|
||||
void* data;
|
||||
size_t holders_count;
|
||||
} FuriRecordData;
|
||||
|
||||
DICT_DEF2(FuriRecordDataDict, const char*, M_CSTR_DUP_OPLIST, FuriRecordData, M_POD_OPLIST)
|
||||
|
||||
typedef struct {
|
||||
FuriMutex* mutex;
|
||||
FuriRecordDataDict_t records;
|
||||
} FuriRecord;
|
||||
|
||||
static FuriRecord* furi_record = NULL;
|
||||
|
||||
static FuriRecordData* furi_record_get(const char* name) {
|
||||
return FuriRecordDataDict_get(furi_record->records, name);
|
||||
}
|
||||
|
||||
static void furi_record_put(const char* name, FuriRecordData* record_data) {
|
||||
FuriRecordDataDict_set_at(furi_record->records, name, *record_data);
|
||||
}
|
||||
|
||||
static void furi_record_erase(const char* name, FuriRecordData* record_data) {
|
||||
furi_event_flag_free(record_data->flags);
|
||||
FuriRecordDataDict_erase(furi_record->records, name);
|
||||
}
|
||||
|
||||
void furi_record_init() {
|
||||
furi_record = malloc(sizeof(FuriRecord));
|
||||
furi_record->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
furi_check(furi_record->mutex);
|
||||
FuriRecordDataDict_init(furi_record->records);
|
||||
}
|
||||
|
||||
static FuriRecordData* furi_record_data_get_or_create(const char* name) {
|
||||
furi_assert(furi_record);
|
||||
FuriRecordData* record_data = furi_record_get(name);
|
||||
if(!record_data) {
|
||||
FuriRecordData new_record;
|
||||
new_record.flags = furi_event_flag_alloc();
|
||||
new_record.data = NULL;
|
||||
new_record.holders_count = 0;
|
||||
furi_record_put(name, &new_record);
|
||||
record_data = furi_record_get(name);
|
||||
}
|
||||
return record_data;
|
||||
}
|
||||
|
||||
static void furi_record_lock() {
|
||||
furi_check(furi_mutex_acquire(furi_record->mutex, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
static void furi_record_unlock() {
|
||||
furi_check(furi_mutex_release(furi_record->mutex) == FuriStatusOk);
|
||||
}
|
||||
|
||||
bool furi_record_exists(const char* name) {
|
||||
furi_assert(furi_record);
|
||||
furi_assert(name);
|
||||
|
||||
bool ret = false;
|
||||
|
||||
furi_record_lock();
|
||||
ret = (furi_record_get(name) != NULL);
|
||||
furi_record_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void furi_record_create(const char* name, void* data) {
|
||||
furi_assert(furi_record);
|
||||
|
||||
furi_record_lock();
|
||||
|
||||
// Get record data and fill it
|
||||
FuriRecordData* record_data = furi_record_data_get_or_create(name);
|
||||
furi_assert(record_data->data == NULL);
|
||||
record_data->data = data;
|
||||
furi_event_flag_set(record_data->flags, FURI_RECORD_FLAG_READY);
|
||||
|
||||
furi_record_unlock();
|
||||
}
|
||||
|
||||
bool furi_record_destroy(const char* name) {
|
||||
furi_assert(furi_record);
|
||||
|
||||
bool ret = false;
|
||||
|
||||
furi_record_lock();
|
||||
|
||||
FuriRecordData* record_data = furi_record_get(name);
|
||||
furi_assert(record_data);
|
||||
if(record_data->holders_count == 0) {
|
||||
furi_record_erase(name, record_data);
|
||||
ret = true;
|
||||
}
|
||||
|
||||
furi_record_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* furi_record_open(const char* name) {
|
||||
furi_assert(furi_record);
|
||||
|
||||
furi_record_lock();
|
||||
|
||||
FuriRecordData* record_data = furi_record_data_get_or_create(name);
|
||||
record_data->holders_count++;
|
||||
|
||||
furi_record_unlock();
|
||||
|
||||
// Wait for record to become ready
|
||||
furi_check(
|
||||
furi_event_flag_wait(
|
||||
record_data->flags,
|
||||
FURI_RECORD_FLAG_READY,
|
||||
FuriFlagWaitAny | FuriFlagNoClear,
|
||||
FuriWaitForever) == FURI_RECORD_FLAG_READY);
|
||||
|
||||
return record_data->data;
|
||||
}
|
||||
|
||||
void furi_record_close(const char* name) {
|
||||
furi_assert(furi_record);
|
||||
|
||||
furi_record_lock();
|
||||
|
||||
FuriRecordData* record_data = furi_record_get(name);
|
||||
furi_assert(record_data);
|
||||
record_data->holders_count--;
|
||||
|
||||
furi_record_unlock();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* @file record.h
|
||||
* Furi: record API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "core_defines.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Initialize record storage For internal use only.
|
||||
*/
|
||||
void furi_record_init();
|
||||
|
||||
/** Check if record exists
|
||||
*
|
||||
* @param name record name
|
||||
* @note Thread safe. Create and destroy must be executed from the same
|
||||
* thread.
|
||||
*/
|
||||
bool furi_record_exists(const char* name);
|
||||
|
||||
/** Create record
|
||||
*
|
||||
* @param name record name
|
||||
* @param data data pointer
|
||||
* @note Thread safe. Create and destroy must be executed from the same
|
||||
* thread.
|
||||
*/
|
||||
void furi_record_create(const char* name, void* data);
|
||||
|
||||
/** Destroy record
|
||||
*
|
||||
* @param name record name
|
||||
*
|
||||
* @return true if successful, false if still have holders or thread is not
|
||||
* owner.
|
||||
* @note Thread safe. Create and destroy must be executed from the same
|
||||
* thread.
|
||||
*/
|
||||
bool furi_record_destroy(const char* name);
|
||||
|
||||
/** Open record
|
||||
*
|
||||
* @param name record name
|
||||
*
|
||||
* @return pointer to the record
|
||||
* @note Thread safe. Open and close must be executed from the same
|
||||
* thread. Suspends caller thread till record is available
|
||||
*/
|
||||
FURI_RETURNS_NONNULL void* furi_record_open(const char* name);
|
||||
|
||||
/** Close record
|
||||
*
|
||||
* @param name record name
|
||||
* @note Thread safe. Open and close must be executed from the same
|
||||
* thread.
|
||||
*/
|
||||
void furi_record_close(const char* name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "semaphore.h"
|
||||
#include "check.h"
|
||||
#include "common_defines.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
FuriSemaphore* furi_semaphore_alloc(uint32_t max_count, uint32_t initial_count) {
|
||||
furi_assert(!FURI_IS_IRQ_MODE());
|
||||
furi_assert((max_count > 0U) && (initial_count <= max_count));
|
||||
|
||||
SemaphoreHandle_t hSemaphore = NULL;
|
||||
if(max_count == 1U) {
|
||||
hSemaphore = xSemaphoreCreateBinary();
|
||||
if((hSemaphore != NULL) && (initial_count != 0U)) {
|
||||
if(xSemaphoreGive(hSemaphore) != pdPASS) {
|
||||
vSemaphoreDelete(hSemaphore);
|
||||
hSemaphore = NULL;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hSemaphore = xSemaphoreCreateCounting(max_count, initial_count);
|
||||
}
|
||||
|
||||
furi_check(hSemaphore);
|
||||
|
||||
/* Return semaphore ID */
|
||||
return ((FuriSemaphore*)hSemaphore);
|
||||
}
|
||||
|
||||
void furi_semaphore_free(FuriSemaphore* instance) {
|
||||
furi_assert(instance);
|
||||
furi_assert(!FURI_IS_IRQ_MODE());
|
||||
|
||||
SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
|
||||
|
||||
vSemaphoreDelete(hSemaphore);
|
||||
}
|
||||
|
||||
FuriStatus furi_semaphore_acquire(FuriSemaphore* instance, uint32_t timeout) {
|
||||
furi_assert(instance);
|
||||
|
||||
SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
|
||||
FuriStatus stat;
|
||||
BaseType_t yield;
|
||||
|
||||
stat = FuriStatusOk;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
if(timeout != 0U) {
|
||||
stat = FuriStatusErrorParameter;
|
||||
} else {
|
||||
yield = pdFALSE;
|
||||
|
||||
if(xSemaphoreTakeFromISR(hSemaphore, &yield) != pdPASS) {
|
||||
stat = FuriStatusErrorResource;
|
||||
} else {
|
||||
portYIELD_FROM_ISR(yield);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(xSemaphoreTake(hSemaphore, (TickType_t)timeout) != pdPASS) {
|
||||
if(timeout != 0U) {
|
||||
stat = FuriStatusErrorTimeout;
|
||||
} else {
|
||||
stat = FuriStatusErrorResource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
FuriStatus furi_semaphore_release(FuriSemaphore* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
|
||||
FuriStatus stat;
|
||||
BaseType_t yield;
|
||||
|
||||
stat = FuriStatusOk;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
yield = pdFALSE;
|
||||
|
||||
if(xSemaphoreGiveFromISR(hSemaphore, &yield) != pdTRUE) {
|
||||
stat = FuriStatusErrorResource;
|
||||
} else {
|
||||
portYIELD_FROM_ISR(yield);
|
||||
}
|
||||
} else {
|
||||
if(xSemaphoreGive(hSemaphore) != pdPASS) {
|
||||
stat = FuriStatusErrorResource;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
//uint32_t furi_semaphore_get_count(FuriSemaphore* instance) {
|
||||
// furi_assert(instance);
|
||||
//
|
||||
// SemaphoreHandle_t hSemaphore = (SemaphoreHandle_t)instance;
|
||||
// uint32_t count;
|
||||
//
|
||||
// if(FURI_IS_IRQ_MODE()) {
|
||||
// count = (uint32_t)uxSemaphoreGetCountFromISR(hSemaphore);
|
||||
// } else {
|
||||
// count = (uint32_t)uxSemaphoreGetCount(hSemaphore);
|
||||
// }
|
||||
//
|
||||
// /* Return number of tokens */
|
||||
// return (count);
|
||||
//}
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @file semaphore.h
|
||||
* FuriSemaphore
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include "thread.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void FuriSemaphore;
|
||||
|
||||
/** Allocate semaphore
|
||||
*
|
||||
* @param[in] max_count The maximum count
|
||||
* @param[in] initial_count The initial count
|
||||
*
|
||||
* @return pointer to FuriSemaphore instance
|
||||
*/
|
||||
FuriSemaphore* furi_semaphore_alloc(uint32_t max_count, uint32_t initial_count);
|
||||
|
||||
/** Free semaphore
|
||||
*
|
||||
* @param instance The pointer to FuriSemaphore instance
|
||||
*/
|
||||
void furi_semaphore_free(FuriSemaphore* instance);
|
||||
|
||||
/** Acquire semaphore
|
||||
*
|
||||
* @param instance The pointer to FuriSemaphore instance
|
||||
* @param[in] timeout The timeout
|
||||
*
|
||||
* @return The furi status.
|
||||
*/
|
||||
FuriStatus furi_semaphore_acquire(FuriSemaphore* instance, uint32_t timeout);
|
||||
|
||||
/** Release semaphore
|
||||
*
|
||||
* @param instance The pointer to FuriSemaphore instance
|
||||
*
|
||||
* @return The furi status.
|
||||
*/
|
||||
FuriStatus furi_semaphore_release(FuriSemaphore* instance);
|
||||
|
||||
///** Get semaphore count
|
||||
// *
|
||||
// * @param instance The pointer to FuriSemaphore instance
|
||||
// *
|
||||
// * @return Semaphore count
|
||||
// */
|
||||
//uint32_t furi_semaphore_get_count(FuriSemaphore* instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,86 @@
|
||||
#include "base.h"
|
||||
#include "check.h"
|
||||
#include "stream_buffer.h"
|
||||
#include "common_defines.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/stream_buffer.h>
|
||||
|
||||
FuriStreamBuffer* furi_stream_buffer_alloc(size_t size, size_t trigger_level) {
|
||||
furi_assert(size != 0);
|
||||
|
||||
StreamBufferHandle_t handle = xStreamBufferCreate(size, trigger_level);
|
||||
furi_check(handle);
|
||||
|
||||
return handle;
|
||||
};
|
||||
|
||||
void furi_stream_buffer_free(FuriStreamBuffer* stream_buffer) {
|
||||
furi_assert(stream_buffer);
|
||||
vStreamBufferDelete(stream_buffer);
|
||||
};
|
||||
|
||||
bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigger_level) {
|
||||
furi_assert(stream_buffer);
|
||||
return xStreamBufferSetTriggerLevel(stream_buffer, trigger_level) == pdTRUE;
|
||||
};
|
||||
|
||||
size_t furi_stream_buffer_send(
|
||||
FuriStreamBuffer* stream_buffer,
|
||||
const void* data,
|
||||
size_t length,
|
||||
uint32_t timeout) {
|
||||
size_t ret;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
BaseType_t yield;
|
||||
ret = xStreamBufferSendFromISR(stream_buffer, data, length, &yield);
|
||||
portYIELD_FROM_ISR(yield);
|
||||
} else {
|
||||
ret = xStreamBufferSend(stream_buffer, data, length, timeout);
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
size_t furi_stream_buffer_receive(
|
||||
FuriStreamBuffer* stream_buffer,
|
||||
void* data,
|
||||
size_t length,
|
||||
uint32_t timeout) {
|
||||
size_t ret;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
BaseType_t yield;
|
||||
ret = xStreamBufferReceiveFromISR(stream_buffer, data, length, &yield);
|
||||
portYIELD_FROM_ISR(yield);
|
||||
} else {
|
||||
ret = xStreamBufferReceive(stream_buffer, data, length, timeout);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t furi_stream_buffer_bytes_available(FuriStreamBuffer* stream_buffer) {
|
||||
return xStreamBufferBytesAvailable(stream_buffer);
|
||||
};
|
||||
|
||||
size_t furi_stream_buffer_spaces_available(FuriStreamBuffer* stream_buffer) {
|
||||
return xStreamBufferSpacesAvailable(stream_buffer);
|
||||
};
|
||||
|
||||
bool furi_stream_buffer_is_full(FuriStreamBuffer* stream_buffer) {
|
||||
return xStreamBufferIsFull(stream_buffer) == pdTRUE;
|
||||
};
|
||||
|
||||
bool furi_stream_buffer_is_empty(FuriStreamBuffer* stream_buffer) {
|
||||
return (xStreamBufferIsEmpty(stream_buffer) == pdTRUE);
|
||||
};
|
||||
|
||||
FuriStatus furi_stream_buffer_reset(FuriStreamBuffer* stream_buffer) {
|
||||
if(xStreamBufferReset(stream_buffer) == pdPASS) {
|
||||
return FuriStatusOk;
|
||||
} else {
|
||||
return FuriStatusError;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @file stream_buffer.h
|
||||
* Furi stream buffer primitive.
|
||||
*
|
||||
* Stream buffers are used to send a continuous stream of data from one task or
|
||||
* interrupt to another. Their implementation is light weight, making them
|
||||
* particularly suited for interrupt to task and core to core communication
|
||||
* scenarios.
|
||||
*
|
||||
* ***NOTE***: Stream buffer implementation assumes there is only one task or
|
||||
* interrupt that will write to the buffer (the writer), and only one task or
|
||||
* interrupt that will read from the buffer (the reader).
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void FuriStreamBuffer;
|
||||
|
||||
/**
|
||||
* @brief Allocate stream buffer instance.
|
||||
* Stream buffer implementation assumes there is only one task or
|
||||
* interrupt that will write to the buffer (the writer), and only one task or
|
||||
* interrupt that will read from the buffer (the reader).
|
||||
*
|
||||
* @param size The total number of bytes the stream buffer will be able to hold at any one time.
|
||||
* @param trigger_level The number of bytes that must be in the stream buffer
|
||||
* before a task that is blocked on the stream buffer to wait for data is moved out of the blocked state.
|
||||
* @return The stream buffer instance.
|
||||
*/
|
||||
FuriStreamBuffer* furi_stream_buffer_alloc(size_t size, size_t trigger_level);
|
||||
|
||||
/**
|
||||
* @brief Free stream buffer instance
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
*/
|
||||
void furi_stream_buffer_free(FuriStreamBuffer* stream_buffer);
|
||||
|
||||
/**
|
||||
* @brief Set trigger level for stream buffer.
|
||||
* A stream buffer's trigger level is the number of bytes that must be in the
|
||||
* stream buffer before a task that is blocked on the stream buffer to
|
||||
* wait for data is moved out of the blocked state.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance
|
||||
* @param trigger_level The new trigger level for the stream buffer.
|
||||
* @return true if trigger level can be be updated (new trigger level was less than or equal to the stream buffer's length).
|
||||
* @return false if trigger level can't be be updated (new trigger level was greater than the stream buffer's length).
|
||||
*/
|
||||
bool furi_stream_set_trigger_level(FuriStreamBuffer* stream_buffer, size_t trigger_level);
|
||||
|
||||
/**
|
||||
* @brief Sends bytes to a stream buffer. The bytes are copied into the stream buffer.
|
||||
* Wakes up task waiting for data to become available if called from ISR.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @param data A pointer to the data that is to be copied into the stream buffer.
|
||||
* @param length The maximum number of bytes to copy from data into the stream buffer.
|
||||
* @param timeout The maximum amount of time the task should remain in the
|
||||
* Blocked state to wait for space to become available if the stream buffer is full.
|
||||
* Will return immediately if timeout is zero.
|
||||
* Setting timeout to FuriWaitForever will cause the task to wait indefinitely.
|
||||
* Ignored if called from ISR.
|
||||
* @return The number of bytes actually written to the stream buffer.
|
||||
*/
|
||||
size_t furi_stream_buffer_send(
|
||||
FuriStreamBuffer* stream_buffer,
|
||||
const void* data,
|
||||
size_t length,
|
||||
uint32_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Receives bytes from a stream buffer.
|
||||
* Wakes up task waiting for space to become available if called from ISR.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @param data A pointer to the buffer into which the received bytes will be
|
||||
* copied.
|
||||
* @param length The length of the buffer pointed to by the data parameter.
|
||||
* @param timeout The maximum amount of time the task should remain in the
|
||||
* Blocked state to wait for data to become available if the stream buffer is empty.
|
||||
* Will return immediately if timeout is zero.
|
||||
* Setting timeout to FuriWaitForever will cause the task to wait indefinitely.
|
||||
* Ignored if called from ISR.
|
||||
* @return The number of bytes read from the stream buffer, if any.
|
||||
*/
|
||||
size_t furi_stream_buffer_receive(
|
||||
FuriStreamBuffer* stream_buffer,
|
||||
void* data,
|
||||
size_t length,
|
||||
uint32_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Queries a stream buffer to see how much data it contains, which is equal to
|
||||
* the number of bytes that can be read from the stream buffer before the stream
|
||||
* buffer would be empty.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @return The number of bytes that can be read from the stream buffer before
|
||||
* the stream buffer would be empty.
|
||||
*/
|
||||
size_t furi_stream_buffer_bytes_available(FuriStreamBuffer* stream_buffer);
|
||||
|
||||
/**
|
||||
* @brief Queries a stream buffer to see how much free space it contains, which is
|
||||
* equal to the amount of data that can be sent to the stream buffer before it
|
||||
* is full.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @return The number of bytes that can be written to the stream buffer before
|
||||
* the stream buffer would be full.
|
||||
*/
|
||||
size_t furi_stream_buffer_spaces_available(FuriStreamBuffer* stream_buffer);
|
||||
|
||||
/**
|
||||
* @brief Queries a stream buffer to see if it is full.
|
||||
*
|
||||
* @param stream_buffer stream buffer instance.
|
||||
* @return true if the stream buffer is full.
|
||||
* @return false if the stream buffer is not full.
|
||||
*/
|
||||
bool furi_stream_buffer_is_full(FuriStreamBuffer* stream_buffer);
|
||||
|
||||
/**
|
||||
* @brief Queries a stream buffer to see if it is empty.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @return true if the stream buffer is empty.
|
||||
* @return false if the stream buffer is not empty.
|
||||
*/
|
||||
bool furi_stream_buffer_is_empty(FuriStreamBuffer* stream_buffer);
|
||||
|
||||
/**
|
||||
* @brief Resets a stream buffer to its initial, empty, state. Any data that was
|
||||
* in the stream buffer is discarded. A stream buffer can only be reset if there
|
||||
* are no tasks blocked waiting to either send to or receive from the stream buffer.
|
||||
*
|
||||
* @param stream_buffer The stream buffer instance.
|
||||
* @return FuriStatusOk if the stream buffer is reset.
|
||||
* @return FuriStatusError if there was a task blocked waiting to send to or read
|
||||
* from the stream buffer then the stream buffer is not reset.
|
||||
*/
|
||||
FuriStatus furi_stream_buffer_reset(FuriStreamBuffer* stream_buffer);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,655 @@
|
||||
#include "thread.h"
|
||||
#include "kernel.h"
|
||||
#include "check.h"
|
||||
#include "common_defines.h"
|
||||
#include "furi_string.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
|
||||
#include <furi_hal_console.h>
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#define TAG "FuriThread"
|
||||
|
||||
#define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers
|
||||
|
||||
typedef struct FuriThreadStdout FuriThreadStdout;
|
||||
|
||||
struct FuriThreadStdout {
|
||||
FuriThreadStdoutWriteCallback write_callback;
|
||||
FuriString* buffer;
|
||||
};
|
||||
|
||||
struct FuriThread {
|
||||
FuriThreadState state;
|
||||
int32_t ret;
|
||||
|
||||
FuriThreadCallback callback;
|
||||
void* context;
|
||||
|
||||
FuriThreadStateCallback state_callback;
|
||||
void* state_context;
|
||||
|
||||
char* name;
|
||||
char* appid;
|
||||
|
||||
FuriThreadPriority priority;
|
||||
|
||||
TaskHandle_t task_handle;
|
||||
size_t heap_size;
|
||||
|
||||
FuriThreadStdout output;
|
||||
|
||||
// Keep all non-alignable byte types in one place,
|
||||
// this ensures that the size of this structure is minimal
|
||||
bool is_service;
|
||||
bool heap_trace_enabled;
|
||||
|
||||
configSTACK_DEPTH_TYPE stack_size;
|
||||
};
|
||||
|
||||
static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size);
|
||||
static int32_t __furi_thread_stdout_flush(FuriThread* thread);
|
||||
|
||||
/** Catch threads that are trying to exit wrong way */
|
||||
__attribute__((__noreturn__)) void furi_thread_catch() { //-V1082
|
||||
// If you're here it means you're probably doing something wrong
|
||||
// with critical sections or with scheduler state
|
||||
asm volatile("nop"); // extra magic
|
||||
furi_crash("You are doing it wrong"); //-V779
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
static void furi_thread_set_state(FuriThread* thread, FuriThreadState state) {
|
||||
furi_assert(thread);
|
||||
thread->state = state;
|
||||
if(thread->state_callback) {
|
||||
thread->state_callback(state, thread->state_context);
|
||||
}
|
||||
}
|
||||
|
||||
static void furi_thread_body(void* context) {
|
||||
furi_assert(context);
|
||||
FuriThread* thread = context;
|
||||
|
||||
// store thread instance to thread local storage
|
||||
furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) == NULL);
|
||||
vTaskSetThreadLocalStoragePointer(NULL, 0, thread);
|
||||
|
||||
furi_assert(thread->state == FuriThreadStateStarting);
|
||||
furi_thread_set_state(thread, FuriThreadStateRunning);
|
||||
|
||||
TaskHandle_t task_handle = xTaskGetCurrentTaskHandle();
|
||||
// if(thread->heap_trace_enabled == true) {
|
||||
// memmgr_heap_enable_thread_trace((FuriThreadId)task_handle);
|
||||
// }
|
||||
|
||||
thread->ret = thread->callback(thread->context);
|
||||
|
||||
// if(thread->heap_trace_enabled == true) {
|
||||
// furi_delay_ms(33);
|
||||
// thread->heap_size = memmgr_heap_get_thread_memory((FuriThreadId)task_handle);
|
||||
// furi_log_print_format(
|
||||
// thread->heap_size ? FuriLogLevelError : FuriLogLevelInfo,
|
||||
// TAG,
|
||||
// "%s allocation balance: %zu",
|
||||
// thread->name ? thread->name : "Thread",
|
||||
// thread->heap_size);
|
||||
// memmgr_heap_disable_thread_trace((FuriThreadId)task_handle);
|
||||
// }
|
||||
|
||||
furi_assert(thread->state == FuriThreadStateRunning);
|
||||
|
||||
if(thread->is_service) {
|
||||
ESP_LOGI(
|
||||
TAG,
|
||||
"%s service thread TCB memory will not be reclaimed",
|
||||
thread->name ? thread->name : "<unnamed service>");
|
||||
}
|
||||
|
||||
// flush stdout
|
||||
__furi_thread_stdout_flush(thread);
|
||||
|
||||
furi_thread_set_state(thread, FuriThreadStateStopped);
|
||||
|
||||
vTaskDelete(NULL);
|
||||
furi_thread_catch();
|
||||
}
|
||||
|
||||
FuriThread* furi_thread_alloc() {
|
||||
FuriThread* thread = malloc(sizeof(FuriThread));
|
||||
// TODO: create default struct instead of using memset()
|
||||
memset(thread, 0, sizeof(FuriThread));
|
||||
thread->output.buffer = furi_string_alloc();
|
||||
thread->is_service = false;
|
||||
|
||||
FuriThread* parent = NULL;
|
||||
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
|
||||
// TLS is not available, if we called not from thread context
|
||||
parent = pvTaskGetThreadLocalStoragePointer(NULL, 0);
|
||||
|
||||
if(parent && parent->appid) {
|
||||
furi_thread_set_appid(thread, parent->appid);
|
||||
} else {
|
||||
furi_thread_set_appid(thread, "unknown");
|
||||
}
|
||||
} else {
|
||||
// if scheduler is not started, we are starting driver thread
|
||||
furi_thread_set_appid(thread, "driver");
|
||||
}
|
||||
|
||||
/*FuriHalRtcHeapTrackMode mode = furi_hal_rtc_get_heap_track_mode();
|
||||
if(mode == FuriHalRtcHeapTrackModeAll) {
|
||||
thread->heap_trace_enabled = true;
|
||||
} else if(mode == FuriHalRtcHeapTrackModeTree && furi_thread_get_current_id()) {
|
||||
if(parent) thread->heap_trace_enabled = parent->heap_trace_enabled;
|
||||
} else */{
|
||||
thread->heap_trace_enabled = false;
|
||||
}
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
FuriThread* furi_thread_alloc_ex(
|
||||
const char* name,
|
||||
uint32_t stack_size,
|
||||
FuriThreadCallback callback,
|
||||
void* context) {
|
||||
FuriThread* thread = furi_thread_alloc();
|
||||
furi_thread_set_name(thread, name);
|
||||
furi_thread_set_stack_size(thread, stack_size);
|
||||
furi_thread_set_callback(thread, callback);
|
||||
furi_thread_set_context(thread, context);
|
||||
return thread;
|
||||
}
|
||||
|
||||
void furi_thread_free(FuriThread* thread) {
|
||||
furi_assert(thread);
|
||||
|
||||
// Ensure that use join before free
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
furi_assert(thread->task_handle == NULL);
|
||||
|
||||
if(thread->name) free(thread->name);
|
||||
if(thread->appid) free(thread->appid);
|
||||
furi_string_free(thread->output.buffer);
|
||||
|
||||
free(thread);
|
||||
}
|
||||
|
||||
void furi_thread_set_name(FuriThread* thread, const char* name) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
if(thread->name) free(thread->name);
|
||||
thread->name = name ? strdup(name) : NULL;
|
||||
}
|
||||
|
||||
void furi_thread_set_appid(FuriThread* thread, const char* appid) {
|
||||
furi_assert(thread);
|
||||
furi_assert(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;
|
||||
}
|
||||
|
||||
bool furi_thread_mark_is_service(FuriThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
assert(!FURI_IS_IRQ_MODE() && (hTask != NULL));
|
||||
FuriThread* thread = (FuriThread*)pvTaskGetThreadLocalStoragePointer(hTask, 0);
|
||||
assert(thread != NULL);
|
||||
return thread->is_service;
|
||||
}
|
||||
|
||||
void furi_thread_set_stack_size(FuriThread* thread, size_t stack_size) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
furi_assert(stack_size % 4 == 0);
|
||||
thread->stack_size = stack_size;
|
||||
}
|
||||
|
||||
void furi_thread_set_callback(FuriThread* thread, FuriThreadCallback callback) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
thread->callback = callback;
|
||||
}
|
||||
|
||||
void furi_thread_set_context(FuriThread* thread, void* context) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
thread->context = context;
|
||||
}
|
||||
|
||||
void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
furi_assert(priority >= FuriThreadPriorityIdle && priority <= FuriThreadPriorityIsr);
|
||||
thread->priority = priority;
|
||||
}
|
||||
|
||||
void furi_thread_set_current_priority(FuriThreadPriority priority) {
|
||||
UBaseType_t new_priority = priority ? priority : FuriThreadPriorityNormal;
|
||||
vTaskPrioritySet(NULL, new_priority);
|
||||
}
|
||||
|
||||
FuriThreadPriority furi_thread_get_current_priority() {
|
||||
return (FuriThreadPriority)uxTaskPriorityGet(NULL);
|
||||
}
|
||||
|
||||
void furi_thread_set_state_callback(FuriThread* thread, FuriThreadStateCallback callback) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
thread->state_callback = callback;
|
||||
}
|
||||
|
||||
void furi_thread_set_state_context(FuriThread* thread, void* context) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
thread->state_context = context;
|
||||
}
|
||||
|
||||
FuriThreadState furi_thread_get_state(FuriThread* thread) {
|
||||
furi_assert(thread);
|
||||
return thread->state;
|
||||
}
|
||||
|
||||
void furi_thread_start(FuriThread* thread) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->callback);
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
furi_assert(thread->stack_size > 0 && thread->stack_size < (UINT16_MAX * sizeof(StackType_t)));
|
||||
|
||||
furi_thread_set_state(thread, FuriThreadStateStarting);
|
||||
|
||||
uint32_t stack = 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,
|
||||
malloc(sizeof(StackType_t) * stack),
|
||||
malloc(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);
|
||||
}
|
||||
|
||||
void furi_thread_cleanup_tcb_event(TaskHandle_t task) {
|
||||
FuriThread* thread = pvTaskGetThreadLocalStoragePointer(task, 0);
|
||||
if(thread) {
|
||||
// clear thread local storage
|
||||
vTaskSetThreadLocalStoragePointer(task, 0, NULL);
|
||||
furi_assert(thread->task_handle == task);
|
||||
thread->task_handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool furi_thread_join(FuriThread* thread) {
|
||||
furi_assert(thread);
|
||||
|
||||
furi_check(furi_thread_get_current() != thread);
|
||||
|
||||
// !!! IMPORTANT NOTICE !!!
|
||||
//
|
||||
// If your thread exited, but your app stuck here: some other thread uses
|
||||
// all cpu time, which delays kernel from releasing task handle
|
||||
while(thread->task_handle) {
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FuriThreadId furi_thread_get_id(FuriThread* thread) {
|
||||
furi_assert(thread);
|
||||
return thread->task_handle;
|
||||
}
|
||||
|
||||
void furi_thread_enable_heap_trace(FuriThread* thread) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
thread->heap_trace_enabled = true;
|
||||
}
|
||||
|
||||
void furi_thread_disable_heap_trace(FuriThread* thread) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
thread->heap_trace_enabled = false;
|
||||
}
|
||||
|
||||
size_t furi_thread_get_heap_size(FuriThread* thread) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->heap_trace_enabled == true);
|
||||
return thread->heap_size;
|
||||
}
|
||||
|
||||
int32_t furi_thread_get_return_code(FuriThread* thread) {
|
||||
furi_assert(thread);
|
||||
furi_assert(thread->state == FuriThreadStateStopped);
|
||||
return thread->ret;
|
||||
}
|
||||
|
||||
FuriThreadId furi_thread_get_current_id() {
|
||||
return xTaskGetCurrentTaskHandle();
|
||||
}
|
||||
|
||||
FuriThread* furi_thread_get_current() {
|
||||
FuriThread* thread = pvTaskGetThreadLocalStoragePointer(NULL, 0);
|
||||
return thread;
|
||||
}
|
||||
|
||||
void furi_thread_yield() {
|
||||
furi_assert(!FURI_IS_IRQ_MODE());
|
||||
taskYIELD();
|
||||
}
|
||||
|
||||
/* Limits */
|
||||
#define MAX_BITS_TASK_NOTIFY 31U
|
||||
#define MAX_BITS_EVENT_GROUPS 24U
|
||||
|
||||
#define THREAD_FLAGS_INVALID_BITS (~((1UL << MAX_BITS_TASK_NOTIFY) - 1U))
|
||||
#define EVENT_FLAGS_INVALID_BITS (~((1UL << MAX_BITS_EVENT_GROUPS) - 1U))
|
||||
|
||||
uint32_t furi_thread_flags_set(FuriThreadId thread_id, uint32_t flags) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
uint32_t rflags;
|
||||
BaseType_t yield;
|
||||
|
||||
if((hTask == NULL) || ((flags & THREAD_FLAGS_INVALID_BITS) != 0U)) {
|
||||
rflags = (uint32_t)FuriStatusErrorParameter;
|
||||
} else {
|
||||
rflags = (uint32_t)FuriStatusError;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
yield = pdFALSE;
|
||||
|
||||
(void)xTaskNotifyIndexedFromISR(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits, &yield);
|
||||
(void)xTaskNotifyAndQueryIndexedFromISR(
|
||||
hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags, NULL);
|
||||
|
||||
portYIELD_FROM_ISR(yield);
|
||||
} else {
|
||||
(void)xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, flags, eSetBits);
|
||||
(void)xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags);
|
||||
}
|
||||
}
|
||||
/* Return flags after setting */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t furi_thread_flags_clear(uint32_t flags) {
|
||||
TaskHandle_t hTask;
|
||||
uint32_t rflags, cflags;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
rflags = (uint32_t)FuriStatusErrorISR;
|
||||
} else if((flags & THREAD_FLAGS_INVALID_BITS) != 0U) {
|
||||
rflags = (uint32_t)FuriStatusErrorParameter;
|
||||
} else {
|
||||
hTask = xTaskGetCurrentTaskHandle();
|
||||
|
||||
if(xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &cflags) ==
|
||||
pdPASS) {
|
||||
rflags = cflags;
|
||||
cflags &= ~flags;
|
||||
|
||||
if(xTaskNotifyIndexed(hTask, THREAD_NOTIFY_INDEX, cflags, eSetValueWithOverwrite) !=
|
||||
pdPASS) {
|
||||
rflags = (uint32_t)FuriStatusError;
|
||||
}
|
||||
} else {
|
||||
rflags = (uint32_t)FuriStatusError;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return flags before clearing */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t furi_thread_flags_get(void) {
|
||||
TaskHandle_t hTask;
|
||||
uint32_t rflags;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
rflags = (uint32_t)FuriStatusErrorISR;
|
||||
} else {
|
||||
hTask = xTaskGetCurrentTaskHandle();
|
||||
|
||||
if(xTaskNotifyAndQueryIndexed(hTask, THREAD_NOTIFY_INDEX, 0, eNoAction, &rflags) !=
|
||||
pdPASS) {
|
||||
rflags = (uint32_t)FuriStatusError;
|
||||
}
|
||||
}
|
||||
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout) {
|
||||
uint32_t rflags, nval;
|
||||
uint32_t clear;
|
||||
TickType_t t0, td, tout;
|
||||
BaseType_t rval;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
rflags = (uint32_t)FuriStatusErrorISR;
|
||||
} else if((flags & THREAD_FLAGS_INVALID_BITS) != 0U) {
|
||||
rflags = (uint32_t)FuriStatusErrorParameter;
|
||||
} else {
|
||||
if((options & FuriFlagNoClear) == FuriFlagNoClear) {
|
||||
clear = 0U;
|
||||
} else {
|
||||
clear = flags;
|
||||
}
|
||||
|
||||
rflags = 0U;
|
||||
tout = timeout;
|
||||
|
||||
t0 = xTaskGetTickCount();
|
||||
do {
|
||||
rval = xTaskNotifyWaitIndexed(THREAD_NOTIFY_INDEX, 0, clear, &nval, tout);
|
||||
|
||||
if(rval == pdPASS) {
|
||||
rflags &= flags;
|
||||
rflags |= nval;
|
||||
|
||||
if((options & FuriFlagWaitAll) == FuriFlagWaitAll) {
|
||||
if((flags & rflags) == flags) {
|
||||
break;
|
||||
} else {
|
||||
if(timeout == 0U) {
|
||||
rflags = (uint32_t)FuriStatusErrorResource;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if((flags & rflags) != 0) {
|
||||
break;
|
||||
} else {
|
||||
if(timeout == 0U) {
|
||||
rflags = (uint32_t)FuriStatusErrorResource;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Update timeout */
|
||||
td = xTaskGetTickCount() - t0;
|
||||
|
||||
if(td > tout) {
|
||||
tout = 0;
|
||||
} else {
|
||||
tout -= td;
|
||||
}
|
||||
} else {
|
||||
if(timeout == 0) {
|
||||
rflags = (uint32_t)FuriStatusErrorResource;
|
||||
} else {
|
||||
rflags = (uint32_t)FuriStatusErrorTimeout;
|
||||
}
|
||||
}
|
||||
} while(rval != pdFAIL);
|
||||
}
|
||||
|
||||
/* Return flags before clearing */
|
||||
return (rflags);
|
||||
}
|
||||
|
||||
uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_items) {
|
||||
uint32_t i, count;
|
||||
TaskStatus_t* task;
|
||||
|
||||
if(FURI_IS_IRQ_MODE() || (thread_array == NULL) || (array_items == 0U)) {
|
||||
count = 0U;
|
||||
} else {
|
||||
vTaskSuspendAll();
|
||||
|
||||
count = uxTaskGetNumberOfTasks();
|
||||
task = pvPortMalloc(count * sizeof(TaskStatus_t));
|
||||
|
||||
if(task != NULL) {
|
||||
count = uxTaskGetSystemState(task, count, NULL);
|
||||
|
||||
for(i = 0U; (i < count) && (i < array_items); i++) {
|
||||
thread_array[i] = (FuriThreadId)task[i].xHandle;
|
||||
}
|
||||
count = i;
|
||||
}
|
||||
(void)xTaskResumeAll();
|
||||
|
||||
vPortFree(task);
|
||||
}
|
||||
|
||||
return (count);
|
||||
}
|
||||
|
||||
const char* furi_thread_get_name(FuriThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
const char* name;
|
||||
|
||||
if(FURI_IS_IRQ_MODE() || (hTask == NULL)) {
|
||||
name = NULL;
|
||||
} else {
|
||||
name = pcTaskGetName(hTask);
|
||||
}
|
||||
|
||||
return (name);
|
||||
}
|
||||
|
||||
const char* furi_thread_get_appid(FuriThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
const char* appid = "system";
|
||||
|
||||
if(!FURI_IS_IRQ_MODE() && (hTask != NULL)) {
|
||||
FuriThread* thread = (FuriThread*)pvTaskGetThreadLocalStoragePointer(hTask, 0);
|
||||
if(thread) {
|
||||
appid = thread->appid;
|
||||
}
|
||||
}
|
||||
|
||||
return (appid);
|
||||
}
|
||||
|
||||
uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
uint32_t sz;
|
||||
|
||||
if(FURI_IS_IRQ_MODE() || (hTask == NULL)) {
|
||||
sz = 0U;
|
||||
} else {
|
||||
sz = (uint32_t)(uxTaskGetStackHighWaterMark(hTask) * sizeof(StackType_t));
|
||||
}
|
||||
|
||||
return (sz);
|
||||
}
|
||||
|
||||
static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size) {
|
||||
if(thread->output.write_callback != NULL) {
|
||||
thread->output.write_callback(data, size);
|
||||
} else {
|
||||
furi_hal_console_tx((const uint8_t*)data, size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
static int32_t __furi_thread_stdout_flush(FuriThread* thread) {
|
||||
FuriString* buffer = thread->output.buffer;
|
||||
size_t size = furi_string_size(buffer);
|
||||
if(size > 0) {
|
||||
__furi_thread_stdout_write(thread, furi_string_get_cstr(buffer), size);
|
||||
furi_string_reset(buffer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_assert(thread);
|
||||
__furi_thread_stdout_flush(thread);
|
||||
thread->output.write_callback = callback;
|
||||
}
|
||||
|
||||
FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback() {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_assert(thread);
|
||||
return thread->output.write_callback;
|
||||
}
|
||||
|
||||
size_t furi_thread_stdout_write(const char* data, size_t size) {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_assert(thread);
|
||||
if(size == 0 || data == NULL) {
|
||||
return __furi_thread_stdout_flush(thread);
|
||||
} else {
|
||||
if(data[size - 1] == '\n') {
|
||||
// if the last character is a newline, we can flush buffer and write data as is, wo buffers
|
||||
__furi_thread_stdout_flush(thread);
|
||||
__furi_thread_stdout_write(thread, data, size);
|
||||
} else {
|
||||
// string_cat doesn't work here because we need to write the exact size data
|
||||
for(size_t i = 0; i < size; i++) {
|
||||
furi_string_push_back(thread->output.buffer, data[i]);
|
||||
if(data[i] == '\n') {
|
||||
__furi_thread_stdout_flush(thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int32_t furi_thread_stdout_flush() {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
furi_assert(thread);
|
||||
return __furi_thread_stdout_flush(thread);
|
||||
}
|
||||
|
||||
void furi_thread_suspend(FuriThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
vTaskSuspend(hTask);
|
||||
}
|
||||
|
||||
void furi_thread_resume(FuriThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
xTaskResumeFromISR(hTask);
|
||||
} else {
|
||||
vTaskResume(hTask);
|
||||
}
|
||||
}
|
||||
|
||||
bool furi_thread_is_suspended(FuriThreadId thread_id) {
|
||||
TaskHandle_t hTask = (TaskHandle_t)thread_id;
|
||||
return eTaskGetState(hTask) == eSuspended;
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
/**
|
||||
* @file thread.h
|
||||
* Furi: Furi Thread API
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include "common_defines.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** FuriThreadState */
|
||||
typedef enum {
|
||||
FuriThreadStateStopped,
|
||||
FuriThreadStateStarting,
|
||||
FuriThreadStateRunning,
|
||||
} FuriThreadState;
|
||||
|
||||
/** FuriThreadPriority */
|
||||
typedef enum {
|
||||
FuriThreadPriorityNone = 0, /**< Uninitialized, choose system default */
|
||||
FuriThreadPriorityIdle = 1, /**< Idle priority */
|
||||
FuriThreadPriorityLowest = 14, /**< Lowest */
|
||||
FuriThreadPriorityLow = 15, /**< Low */
|
||||
FuriThreadPriorityNormal = 16, /**< Normal */
|
||||
FuriThreadPriorityHigh = 17, /**< High */
|
||||
FuriThreadPriorityHighest = 18, /**< Highest */
|
||||
FuriThreadPriorityIsr =
|
||||
(FURI_CONFIG_THREAD_MAX_PRIORITIES - 1), /**< Deferred ISR (highest possible) */
|
||||
} FuriThreadPriority;
|
||||
|
||||
/** FuriThread anonymous structure */
|
||||
typedef struct FuriThread FuriThread;
|
||||
|
||||
/** FuriThreadId proxy type to OS low level functions */
|
||||
typedef void* FuriThreadId;
|
||||
|
||||
/** FuriThreadCallback Your callback to run in new thread
|
||||
* @warning never use osThreadExit in FuriThread
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
typedef void (*FuriThreadStateCallback)(FuriThreadState state, void* context);
|
||||
|
||||
/** Allocate FuriThread
|
||||
*
|
||||
* @return FuriThread instance
|
||||
*/
|
||||
FuriThread* furi_thread_alloc();
|
||||
|
||||
/** Allocate FuriThread, shortcut version
|
||||
*
|
||||
* @param name
|
||||
* @param stack_size
|
||||
* @param callback
|
||||
* @param context
|
||||
* @return FuriThread*
|
||||
*/
|
||||
FuriThread* furi_thread_alloc_ex(
|
||||
const char* name,
|
||||
uint32_t stack_size,
|
||||
FuriThreadCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Release FuriThread
|
||||
*
|
||||
* @warning see furi_thread_join
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
*/
|
||||
void furi_thread_free(FuriThread* thread);
|
||||
|
||||
/** Set FuriThread name
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
* @param name string
|
||||
*/
|
||||
void furi_thread_set_name(FuriThread* thread, const char* name);
|
||||
|
||||
/**
|
||||
* @brief Set FuriThread appid
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
* @param stack_size stack size in bytes
|
||||
*/
|
||||
void furi_thread_set_stack_size(FuriThread* thread, size_t stack_size);
|
||||
|
||||
/** Set FuriThread callback
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
* @param callback FuriThreadCallback, called upon thread run
|
||||
*/
|
||||
void furi_thread_set_callback(FuriThread* thread, FuriThreadCallback callback);
|
||||
|
||||
/** Set FuriThread context
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
* @param context pointer to context for thread callback
|
||||
*/
|
||||
void furi_thread_set_context(FuriThread* thread, void* context);
|
||||
|
||||
/** Set FuriThread priority
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
* @param priority FuriThreadPriority value
|
||||
*/
|
||||
void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority);
|
||||
|
||||
/** Set current thread priority
|
||||
*
|
||||
* @param priority FuriThreadPriority value
|
||||
*/
|
||||
void furi_thread_set_current_priority(FuriThreadPriority priority);
|
||||
|
||||
/** Get current thread priority
|
||||
*
|
||||
* @return FuriThreadPriority value
|
||||
*/
|
||||
FuriThreadPriority furi_thread_get_current_priority();
|
||||
|
||||
/** Set FuriThread state change callback
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
* @param callback state change callback
|
||||
*/
|
||||
void furi_thread_set_state_callback(FuriThread* thread, FuriThreadStateCallback callback);
|
||||
|
||||
/** Set FuriThread state change context
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
* @param context pointer to context
|
||||
*/
|
||||
void furi_thread_set_state_context(FuriThread* thread, void* context);
|
||||
|
||||
/** Get FuriThread state
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
*
|
||||
* @return thread state from FuriThreadState
|
||||
*/
|
||||
FuriThreadState furi_thread_get_state(FuriThread* thread);
|
||||
|
||||
/** Start FuriThread
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
*/
|
||||
void furi_thread_start(FuriThread* thread);
|
||||
|
||||
/** Join FuriThread
|
||||
*
|
||||
* @warning Use this method only when CPU is not busy(Idle task receives
|
||||
* control), otherwise it will wait forever.
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
bool furi_thread_join(FuriThread* thread);
|
||||
|
||||
/** Get FreeRTOS FuriThreadId for FuriThread instance
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
*
|
||||
* @return FuriThreadId or NULL
|
||||
*/
|
||||
FuriThreadId furi_thread_get_id(FuriThread* thread);
|
||||
|
||||
/** Enable heap tracing
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
*/
|
||||
void furi_thread_enable_heap_trace(FuriThread* thread);
|
||||
|
||||
/** Disable heap tracing
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
*/
|
||||
void furi_thread_disable_heap_trace(FuriThread* thread);
|
||||
|
||||
/** Get thread heap size
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
*
|
||||
* @return size in bytes
|
||||
*/
|
||||
size_t furi_thread_get_heap_size(FuriThread* thread);
|
||||
|
||||
/** Get thread return code
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
*
|
||||
* @return return code
|
||||
*/
|
||||
int32_t furi_thread_get_return_code(FuriThread* thread);
|
||||
|
||||
/** Thread related methods that doesn't involve FuriThread directly */
|
||||
|
||||
/** Get FreeRTOS FuriThreadId for current thread
|
||||
*
|
||||
* @param thread FuriThread instance
|
||||
*
|
||||
* @return FuriThreadId or NULL
|
||||
*/
|
||||
FuriThreadId furi_thread_get_current_id();
|
||||
|
||||
/** Get FuriThread instance for current thread
|
||||
*
|
||||
* @return pointer to FuriThread or NULL if this thread doesn't belongs to Furi
|
||||
*/
|
||||
FuriThread* furi_thread_get_current();
|
||||
|
||||
/** Return control to scheduler */
|
||||
void furi_thread_yield();
|
||||
|
||||
uint32_t furi_thread_flags_set(FuriThreadId thread_id, uint32_t flags);
|
||||
|
||||
uint32_t furi_thread_flags_clear(uint32_t flags);
|
||||
|
||||
uint32_t furi_thread_flags_get(void);
|
||||
|
||||
uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Enumerate threads
|
||||
*
|
||||
* @param thread_array array of FuriThreadId, where thread ids will be stored
|
||||
* @param array_items array size
|
||||
* @return uint32_t threads count
|
||||
*/
|
||||
uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_items);
|
||||
|
||||
/**
|
||||
* @brief Get thread name
|
||||
*
|
||||
* @param thread_id
|
||||
* @return const char* name or NULL
|
||||
*/
|
||||
const char* furi_thread_get_name(FuriThreadId thread_id);
|
||||
|
||||
/**
|
||||
* @brief Get thread appid
|
||||
*
|
||||
* @param thread_id
|
||||
* @return const char* appid
|
||||
*/
|
||||
const char* furi_thread_get_appid(FuriThreadId thread_id);
|
||||
|
||||
/**
|
||||
* @brief Get thread stack watermark
|
||||
*
|
||||
* @param thread_id
|
||||
* @return uint32_t
|
||||
*/
|
||||
uint32_t furi_thread_get_stack_space(FuriThreadId thread_id);
|
||||
|
||||
/** Get STDOUT callback for thead
|
||||
*
|
||||
* @return STDOUT callback
|
||||
*/
|
||||
FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback();
|
||||
|
||||
/** Set STDOUT callback for thread
|
||||
*
|
||||
* @param callback callback or NULL to clear
|
||||
*/
|
||||
void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback);
|
||||
|
||||
/** Write data to buffered STDOUT
|
||||
*
|
||||
* @param data input data
|
||||
* @param size input data size
|
||||
*
|
||||
* @return size_t written data size
|
||||
*/
|
||||
size_t furi_thread_stdout_write(const char* data, size_t size);
|
||||
|
||||
/** Flush data to STDOUT
|
||||
*
|
||||
* @return int32_t error code
|
||||
*/
|
||||
int32_t furi_thread_stdout_flush();
|
||||
|
||||
/** Suspend thread
|
||||
*
|
||||
* @param thread_id thread id
|
||||
*/
|
||||
void furi_thread_suspend(FuriThreadId thread_id);
|
||||
|
||||
/** Resume thread
|
||||
*
|
||||
* @param thread_id thread id
|
||||
*/
|
||||
void furi_thread_resume(FuriThreadId thread_id);
|
||||
|
||||
/** Get thread suspended state
|
||||
*
|
||||
* @param thread_id thread id
|
||||
* @return true if thread is suspended
|
||||
*/
|
||||
bool furi_thread_is_suspended(FuriThreadId thread_id);
|
||||
|
||||
bool furi_thread_mark_is_service(FuriThreadId thread_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,172 @@
|
||||
#include "timer.h"
|
||||
#include "check.h"
|
||||
#include "kernel.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/timers.h>
|
||||
|
||||
typedef struct {
|
||||
FuriTimerCallback func;
|
||||
void* context;
|
||||
} TimerCallback_t;
|
||||
|
||||
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* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* context) {
|
||||
furi_assert((furi_kernel_is_irq_or_masked() == 0U) && (func != NULL));
|
||||
|
||||
TimerHandle_t hTimer;
|
||||
TimerCallback_t* callb;
|
||||
UBaseType_t reload;
|
||||
|
||||
hTimer = NULL;
|
||||
|
||||
/* 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));
|
||||
|
||||
callb->func = func;
|
||||
callb->context = context;
|
||||
|
||||
if(type == FuriTimerTypeOnce) {
|
||||
reload = pdFALSE;
|
||||
} else {
|
||||
reload = pdTRUE;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
/* Return timer ID */
|
||||
return ((FuriTimer*)hTimer);
|
||||
}
|
||||
|
||||
void furi_timer_free(FuriTimer* instance) {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
TimerCallback_t* callb;
|
||||
|
||||
callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer);
|
||||
|
||||
furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS);
|
||||
|
||||
while(furi_timer_is_running(instance)) furi_delay_tick(2);
|
||||
|
||||
if((uint32_t)callb & 1U) {
|
||||
/* Callback memory was allocated from dynamic pool, clear flag */
|
||||
callb = (TimerCallback_t*)((uint32_t)callb & ~1U);
|
||||
|
||||
/* Return allocated memory to dynamic pool */
|
||||
free(callb);
|
||||
}
|
||||
}
|
||||
|
||||
FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(instance);
|
||||
furi_assert(ticks < portMAX_DELAY);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
FuriStatus stat;
|
||||
|
||||
if(xTimerChangePeriod(hTimer, ticks, portMAX_DELAY) == pdPASS) {
|
||||
stat = FuriStatusOk;
|
||||
} else {
|
||||
stat = FuriStatusErrorResource;
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks) {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(instance);
|
||||
furi_assert(ticks < portMAX_DELAY);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
FuriStatus stat;
|
||||
|
||||
if(xTimerChangePeriod(hTimer, ticks, portMAX_DELAY) == pdPASS &&
|
||||
xTimerReset(hTimer, portMAX_DELAY) == pdPASS) {
|
||||
stat = FuriStatusOk;
|
||||
} else {
|
||||
stat = FuriStatusErrorResource;
|
||||
}
|
||||
|
||||
/* Return execution status */
|
||||
return (stat);
|
||||
}
|
||||
|
||||
FuriStatus furi_timer_stop(FuriTimer* instance) {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
|
||||
furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS);
|
||||
|
||||
return FuriStatusOk;
|
||||
}
|
||||
|
||||
uint32_t furi_timer_is_running(FuriTimer* instance) {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
|
||||
/* Return 0: not running, 1: running */
|
||||
return (uint32_t)xTimerIsTimerActive(hTimer);
|
||||
}
|
||||
|
||||
uint32_t furi_timer_get_expire_time(FuriTimer* instance) {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
|
||||
return (uint32_t)xTimerGetExpiryTime(hTimer);
|
||||
}
|
||||
|
||||
void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context, uint32_t arg) {
|
||||
BaseType_t ret = pdFAIL;
|
||||
if(furi_kernel_is_irq_or_masked()) {
|
||||
ret = xTimerPendFunctionCallFromISR(callback, context, arg, NULL);
|
||||
} else {
|
||||
ret = xTimerPendFunctionCall(callback, context, arg, FuriWaitForever);
|
||||
}
|
||||
furi_check(ret == pdPASS);
|
||||
}
|
||||
|
||||
void furi_timer_set_thread_priority(FuriTimerThreadPriority priority) {
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
|
||||
TaskHandle_t task_handle = xTimerGetTimerDaemonTaskHandle();
|
||||
furi_check(task_handle); // Don't call this method before timer task start
|
||||
|
||||
if(priority == FuriTimerThreadPriorityNormal) {
|
||||
vTaskPrioritySet(task_handle, configTIMER_TASK_PRIORITY);
|
||||
} else if(priority == FuriTimerThreadPriorityElevated) {
|
||||
vTaskPrioritySet(task_handle, configMAX_PRIORITIES - 1);
|
||||
} else {
|
||||
furi_crash();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void (*FuriTimerCallback)(void* context);
|
||||
|
||||
typedef enum {
|
||||
FuriTimerTypeOnce = 0, ///< One-shot timer.
|
||||
FuriTimerTypePeriodic = 1 ///< Repeating timer.
|
||||
} FuriTimerType;
|
||||
|
||||
typedef void FuriTimer;
|
||||
|
||||
/** Allocate timer
|
||||
*
|
||||
* @param[in] func The callback function
|
||||
* @param[in] type The timer type
|
||||
* @param context The callback context
|
||||
*
|
||||
* @return The pointer to FuriTimer instance
|
||||
*/
|
||||
FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* context);
|
||||
|
||||
/** Free timer
|
||||
*
|
||||
* @param instance The pointer to FuriTimer instance
|
||||
*/
|
||||
void furi_timer_free(FuriTimer* instance);
|
||||
|
||||
/** Start timer
|
||||
*
|
||||
* @warning This is asynchronous call, real operation will happen as soon as
|
||||
* timer service process this request.
|
||||
*
|
||||
* @param instance The pointer to FuriTimer instance
|
||||
* @param[in] ticks The interval in ticks
|
||||
*
|
||||
* @return The furi status.
|
||||
*/
|
||||
FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks);
|
||||
|
||||
/** Restart timer with previous timeout value
|
||||
*
|
||||
* @warning This is asynchronous call, real operation will happen as soon as
|
||||
* timer service process this request.
|
||||
*
|
||||
* @param instance The pointer to FuriTimer instance
|
||||
* @param[in] ticks The interval in ticks
|
||||
*
|
||||
* @return The furi status.
|
||||
*/
|
||||
FuriStatus furi_timer_restart(FuriTimer* instance, uint32_t ticks);
|
||||
|
||||
/** Stop timer
|
||||
*
|
||||
* @warning This is asynchronous call, real operation will happen as soon as
|
||||
* timer service process this request.
|
||||
*
|
||||
* @param instance The pointer to FuriTimer instance
|
||||
*
|
||||
* @return The furi status.
|
||||
*/
|
||||
FuriStatus furi_timer_stop(FuriTimer* instance);
|
||||
|
||||
/** Is timer running
|
||||
*
|
||||
* @warning This cal may and will return obsolete timer state if timer
|
||||
* commands are still in the queue. Please read FreeRTOS timer
|
||||
* documentation first.
|
||||
*
|
||||
* @param instance The pointer to FuriTimer instance
|
||||
*
|
||||
* @return 0: not running, 1: running
|
||||
*/
|
||||
uint32_t furi_timer_is_running(FuriTimer* instance);
|
||||
|
||||
/** Get timer expire time
|
||||
*
|
||||
* @param instance The Timer instance
|
||||
*
|
||||
* @return expire tick
|
||||
*/
|
||||
uint32_t furi_timer_get_expire_time(FuriTimer* instance);
|
||||
|
||||
typedef void (*FuriTimerPendigCallback)(void* context, uint32_t arg);
|
||||
|
||||
void furi_timer_pending_callback(FuriTimerPendigCallback callback, void* context, uint32_t arg);
|
||||
|
||||
typedef enum {
|
||||
FuriTimerThreadPriorityNormal, /**< Lower then other threads */
|
||||
FuriTimerThreadPriorityElevated, /**< Same as other threads */
|
||||
} FuriTimerThreadPriority;
|
||||
|
||||
/** Set Timer thread priority
|
||||
*
|
||||
* @param[in] priority The priority
|
||||
*/
|
||||
void furi_timer_set_thread_priority(FuriTimerThreadPriority priority);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user