Furi: core refactoring and CMSIS removal part 2 (#1410)

* Furi: rename and move core
* Furi: drop CMSIS_OS header and unused api, partially refactor and cleanup the rest
* Furi: CMSIS_OS drop and refactoring.
* Furi: refactoring, remove cmsis legacy
* Furi: fix incorrect assert on queue deallocation, cleanup timer
* Furi: improve delay api, get rid of floats
* hal: dropped furi_hal_crc
* Furi: move DWT based delay to cortex HAL
* Furi: update core documentation

Co-authored-by: hedger <hedger@nanode.su>
This commit is contained in:
あく
2022-07-20 13:56:33 +03:00
committed by GitHub
parent f9c2287ea7
commit e3c7201a20
264 changed files with 2569 additions and 3883 deletions

13
furi/SConscript Normal file
View File

@@ -0,0 +1,13 @@
Import("env")
env.Append(LINT_SOURCES=["furi"])
libenv = env.Clone(FW_LIB_NAME="furi")
libenv.ApplyLibFlags()
sources = libenv.GlobRecursive("*.c")
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
libenv.Install("${LIB_DIST_DIR}", lib)
Return("lib")

43
furi/core/base.h Normal file
View File

@@ -0,0 +1,43 @@
#pragma once
#include <stdint.h>
#include <stdbool.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

112
furi/core/check.c Normal file
View File

@@ -0,0 +1,112 @@
#include "check.h"
#include "common_defines.h"
#include <furi_hal_console.h>
#include <furi_hal_power.h>
#include <furi_hal_rtc.h>
#include <stdio.h>
#include <FreeRTOS.h>
#include <task.h>
#include <stdio.h>
extern size_t xPortGetTotalHeapSize(void);
extern size_t xPortGetFreeHeapSize(void);
extern size_t xPortGetMinimumEverFreeHeapSize(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_print_stack_info() {
furi_hal_console_puts("\r\n\tstack watermark: ");
__furi_put_uint32_as_text(uxTaskGetStackHighWaterMark(NULL) * 4);
}
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("] ");
}
}
}
static FURI_NORETURN void __furi_halt() {
asm volatile(
#ifdef FURI_DEBUG
"bkpt 0x00 \n"
#endif
"loop%=: \n"
"wfi \n"
"b loop%= \n"
:
:
: "memory");
__builtin_unreachable();
}
FURI_NORETURN void furi_crash(const char* message) {
bool isr = FURI_IS_ISR();
__disable_irq();
if(message == NULL) {
message = "Fatal Error";
}
furi_hal_console_puts("\r\n\033[0;31m[CRASH]");
__furi_print_name(isr);
furi_hal_console_puts(message);
if(!isr) {
__furi_print_stack_info();
}
__furi_print_heap_info();
#ifdef FURI_DEBUG
furi_hal_console_puts("\r\nSystem halted. Connect debugger for more info\r\n");
furi_hal_console_puts("\033[0m\r\n");
__furi_halt();
#else
furi_hal_rtc_set_fault_data((uint32_t)message);
furi_hal_console_puts("\r\nRebooting system.\r\n");
furi_hal_console_puts("\033[0m\r\n");
furi_hal_power_reset();
#endif
__builtin_unreachable();
}
FURI_NORETURN void furi_halt(const char* message) {
bool isr = FURI_IS_ISR();
__disable_irq();
if(message == NULL) {
message = "System halt requested.";
}
furi_hal_console_puts("\r\n\033[0;31m[HALT]");
__furi_print_name(isr);
furi_hal_console_puts(message);
furi_hal_console_puts("\r\nSystem halted. Bye-bye!\r\n");
furi_hal_console_puts("\033[0m\r\n");
__furi_halt();
}

32
furi/core/check.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#define FURI_NORETURN [[noreturn]]
#else
#include <stdnoreturn.h>
#define FURI_NORETURN noreturn
#endif
/** Check condition and crash if check failed */
#define furi_check(__e) ((__e) ? (void)0 : furi_crash("furi_check failed\r\n"))
/** Only in debug build: Assert condition and crash if assert failed */
#ifdef FURI_DEBUG
#define furi_assert(__e) ((__e) ? (void)0 : furi_crash("furi_assert failed\r\n"))
#else
#define furi_assert(__e) \
do { \
((void)(__e)); \
} while(0)
#endif
/** Crash system */
FURI_NORETURN void furi_crash(const char* message);
/** Halt system */
FURI_NORETURN void furi_halt(const char* message);
#ifdef __cplusplus
}
#endif

154
furi/core/common_defines.h Normal file
View File

@@ -0,0 +1,154 @@
#pragma once
#include <stdbool.h>
#include <FreeRTOS.h>
#include <task.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <cmsis_compiler.h>
#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 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 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_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
#ifndef FURI_CRITICAL_ENTER
#define FURI_CRITICAL_ENTER() \
uint32_t __isrm = 0; \
bool __from_isr = FURI_IS_ISR(); \
bool __kernel_running = (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING); \
if(__from_isr) { \
__isrm = taskENTER_CRITICAL_FROM_ISR(); \
} else if(__kernel_running) { \
taskENTER_CRITICAL(); \
} else { \
__disable_irq(); \
}
#endif
#ifndef FURI_CRITICAL_EXIT
#define FURI_CRITICAL_EXIT() \
if(__from_isr) { \
taskEXIT_CRITICAL_FROM_ISR(__isrm); \
} else if(__kernel_running) { \
taskEXIT_CRITICAL(); \
} else { \
__enable_irq(); \
}
#endif
static inline bool furi_is_irq_context() {
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);
}
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,44 @@
#pragma once
/** Assign value to variable with const modifier
*
* This macros is equivalent to `const_cast` from C++
* Literally x = y, but with some magic.
* It's as dangerous as only can be.
* We don't advice you to use it unless you REALLY MUST.
* Like REALLY REALLY.
*
* @param x - const variable
* @param y - variable
*
* @return assigned variable value
*/
#ifndef FURI_CONST_ASSIGN
#define FURI_CONST_ASSIGN_(T, x, y) \
({ \
T* tmp_x = (T*)&x; \
*tmp_x = y; \
*tmp_x; \
})
#define FURI_CONST_ASSIGN_PTR(x, y) \
({ \
void** tmp_x = (void**)&x; \
*tmp_x = y; \
*tmp_x; \
})
#define FURI_CONST_ASSIGN(x, y) \
_Generic((x), signed char \
: FURI_CONST_ASSIGN_(signed char, x, y), unsigned char \
: FURI_CONST_ASSIGN_(unsigned char, x, y), short \
: FURI_CONST_ASSIGN_(short, x, y), unsigned short \
: FURI_CONST_ASSIGN_(unsigned short, x, y), int \
: FURI_CONST_ASSIGN_(int, x, y), unsigned \
: FURI_CONST_ASSIGN_(unsigned, x, y), long \
: FURI_CONST_ASSIGN_(long, x, y), unsigned long \
: FURI_CONST_ASSIGN_(unsigned long, x, y), long long \
: FURI_CONST_ASSIGN_(long long, x, y), unsigned long long \
: FURI_CONST_ASSIGN_(unsigned long long, x, y), float \
: FURI_CONST_ASSIGN_(float, x, y), double \
: FURI_CONST_ASSIGN_(double, x, y), long double \
: FURI_CONST_ASSIGN_(long double, x, y))
#endif

135
furi/core/event_flag.c Normal file
View File

@@ -0,0 +1,135 @@
#include "event_flag.h"
#include "common_defines.h"
#include "check.h"
#include <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());
return ((FuriEventFlag*)xEventGroupCreate());
}
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() != 0U) {
yield = pdFALSE;
if(xEventGroupSetBitsFromISR(hEventGroup, (EventBits_t)flags, &yield) == pdFAIL) {
rflags = (uint32_t)FuriStatusErrorResource;
} 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() != 0U) {
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() != 0U) {
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);
}

70
furi/core/event_flag.h Normal file
View File

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

174
furi/core/kernel.c Normal file
View File

@@ -0,0 +1,174 @@
#include "kernel.h"
#include "base.h"
#include "check.h"
#include "common_defines.h"
#include <furi_hal.h>
#include CMSIS_device_header
int32_t furi_kernel_lock() {
furi_assert(!furi_is_irq_context());
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_is_irq_context());
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_is_irq_context());
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_is_irq_context());
if(ticks == 0U) {
taskYIELD();
} else {
vTaskDelay(ticks);
}
}
FuriStatus furi_delay_until_tick(uint32_t tick) {
furi_assert(!furi_is_irq_context());
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_is_irq_context() != 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) {
furi_hal_cortex_delay_us(microseconds);
}

93
furi/core/kernel.h Normal file
View File

@@ -0,0 +1,93 @@
/**
* @file kenrel.h
* Furi Kernel primitives
*/
#pragma once
#include <core/base.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Lock kernel, pause process scheduling
*
* @return previous lock state(0 - unlocked, 1 - locked)
*/
int32_t furi_kernel_lock();
/** Unlock kernel, resume process scheduling
*
* @return previous lock state(0 - unlocked, 1 - locked)
*/
int32_t furi_kernel_unlock();
/** Restore kernel lock state
*
* @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
*
* 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
*
* @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

66
furi/core/log.c Normal file
View File

@@ -0,0 +1,66 @@
#include "log.h"
#include "check.h"
#include "mutex.h"
#include <furi_hal.h>
#define FURI_LOG_LEVEL_DEFAULT FuriLogLevelInfo
typedef struct {
FuriLogLevel log_level;
FuriLogPuts puts;
FuriLogTimestamp timetamp;
FuriMutex* mutex;
} FuriLogParams;
static FuriLogParams furi_log;
void furi_log_init() {
// Set default logging parameters
furi_log.log_level = FURI_LOG_LEVEL_DEFAULT;
furi_log.puts = furi_hal_console_puts;
furi_log.timetamp = furi_get_tick;
furi_log.mutex = furi_mutex_alloc(FuriMutexTypeNormal);
}
void furi_log_print(FuriLogLevel level, const char* format, ...) {
if(level <= furi_log.log_level &&
furi_mutex_acquire(furi_log.mutex, FuriWaitForever) == FuriStatusOk) {
string_t string;
// Timestamp
string_init_printf(string, "%lu ", furi_log.timetamp());
furi_log.puts(string_get_cstr(string));
string_clear(string);
va_list args;
va_start(args, format);
string_init_vprintf(string, format, args);
va_end(args);
furi_log.puts(string_get_cstr(string));
string_clear(string);
furi_mutex_release(furi_log.mutex);
}
}
void furi_log_set_level(FuriLogLevel level) {
if(level == FuriLogLevelDefault) {
level = FURI_LOG_LEVEL_DEFAULT;
}
furi_log.log_level = level;
}
FuriLogLevel furi_log_get_level(void) {
return furi_log.log_level;
}
void furi_log_set_puts(FuriLogPuts puts) {
furi_assert(puts);
furi_log.puts = puts;
}
void furi_log_set_timestamp(FuriLogTimestamp timestamp) {
furi_assert(timestamp);
furi_log.timetamp = timestamp;
}

101
furi/core/log.h Normal file
View File

@@ -0,0 +1,101 @@
/**
* @file log.h
* Furi Logging system
*/
#pragma once
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
FuriLogLevelDefault = 0,
FuriLogLevelNone = 1,
FuriLogLevelError = 2,
FuriLogLevelWarn = 3,
FuriLogLevelInfo = 4,
FuriLogLevelDebug = 5,
FuriLogLevelTrace = 6,
} FuriLogLevel;
#define FURI_LOG_CLR(clr) "\033[0;" clr "m"
#define FURI_LOG_CLR_RESET "\033[0m"
#define FURI_LOG_CLR_BLACK "30"
#define FURI_LOG_CLR_RED "31"
#define FURI_LOG_CLR_GREEN "32"
#define FURI_LOG_CLR_BROWN "33"
#define FURI_LOG_CLR_BLUE "34"
#define FURI_LOG_CLR_PURPLE "35"
#define FURI_LOG_CLR_E FURI_LOG_CLR(FURI_LOG_CLR_RED)
#define FURI_LOG_CLR_W FURI_LOG_CLR(FURI_LOG_CLR_BROWN)
#define FURI_LOG_CLR_I FURI_LOG_CLR(FURI_LOG_CLR_GREEN)
#define FURI_LOG_CLR_D FURI_LOG_CLR(FURI_LOG_CLR_BLUE)
#define FURI_LOG_CLR_T FURI_LOG_CLR(FURI_LOG_CLR_PURPLE)
typedef void (*FuriLogPuts)(const char* data);
typedef uint32_t (*FuriLogTimestamp)(void);
/** Initialize logging */
void furi_log_init();
/** Log record
*
* @param[in] level The level
* @param[in] format The format
* @param[in] <unnamed> VA args
*/
void furi_log_print(FuriLogLevel level, const char* format, ...);
/** Set log level
*
* @param[in] level The level
*/
void furi_log_set_level(FuriLogLevel level);
/** Get log level
*
* @return The furi log level.
*/
FuriLogLevel furi_log_get_level();
/** Set log output callback
*
* @param[in] puts The puts callback
*/
void furi_log_set_puts(FuriLogPuts puts);
/** Set timestamp callback
*
* @param[in] timestamp The timestamp callback
*/
void furi_log_set_timestamp(FuriLogTimestamp timestamp);
#define FURI_LOG_FORMAT(log_letter, tag, format) \
FURI_LOG_CLR_##log_letter "[" #log_letter "][" tag "]: " FURI_LOG_CLR_RESET format "\r\n"
#define FURI_LOG_SHOW(tag, format, log_level, log_letter, ...) \
furi_log_print(log_level, FURI_LOG_FORMAT(log_letter, tag, format), ##__VA_ARGS__)
/** Log methods
*
* @param tag The application tag
* @param format The format
* @param ... VA Args
*/
#define FURI_LOG_E(tag, format, ...) \
FURI_LOG_SHOW(tag, format, FuriLogLevelError, E, ##__VA_ARGS__)
#define FURI_LOG_W(tag, format, ...) FURI_LOG_SHOW(tag, format, FuriLogLevelWarn, W, ##__VA_ARGS__)
#define FURI_LOG_I(tag, format, ...) FURI_LOG_SHOW(tag, format, FuriLogLevelInfo, I, ##__VA_ARGS__)
#define FURI_LOG_D(tag, format, ...) \
FURI_LOG_SHOW(tag, format, FuriLogLevelDebug, D, ##__VA_ARGS__)
#define FURI_LOG_T(tag, format, ...) \
FURI_LOG_SHOW(tag, format, FuriLogLevelTrace, T, ##__VA_ARGS__)
#ifdef __cplusplus
}
#endif

81
furi/core/memmgr.c Normal file
View File

@@ -0,0 +1,81 @@
#include "memmgr.h"
#include "common_defines.h"
#include <string.h>
extern void* pvPortMalloc(size_t xSize);
extern void vPortFree(void* pv);
extern size_t xPortGetFreeHeapSize(void);
extern size_t xPortGetTotalHeapSize(void);
extern size_t xPortGetMinimumEverFreeHeapSize(void);
void* malloc(size_t size) {
return pvPortMalloc(size);
}
void free(void* ptr) {
vPortFree(ptr);
}
void* realloc(void* ptr, size_t size) {
if(size == 0) {
vPortFree(ptr);
return NULL;
}
void* p = pvPortMalloc(size);
if(ptr != NULL) {
memcpy(p, ptr, size);
vPortFree(ptr);
}
return p;
}
void* calloc(size_t count, size_t size) {
return pvPortMalloc(count * size);
}
char* strdup(const char* s) {
const char* s_null = s;
if(s_null == NULL) {
return NULL;
}
size_t siz = strlen(s) + 1;
char* y = pvPortMalloc(siz);
memcpy(y, s, siz);
return y;
}
size_t memmgr_get_free_heap(void) {
return xPortGetFreeHeapSize();
}
size_t memmgr_get_total_heap(void) {
return xPortGetTotalHeapSize();
}
size_t memmgr_get_minimum_free_heap(void) {
return xPortGetMinimumEverFreeHeapSize();
}
void* __wrap__malloc_r(struct _reent* r, size_t size) {
UNUSED(r);
return pvPortMalloc(size);
}
void __wrap__free_r(struct _reent* r, void* ptr) {
UNUSED(r);
vPortFree(ptr);
}
void* __wrap__calloc_r(struct _reent* r, size_t count, size_t size) {
UNUSED(r);
return calloc(count, size);
}
void* __wrap__realloc_r(struct _reent* r, void* ptr, size_t size) {
UNUSED(r);
return realloc(ptr, size);
}

40
furi/core/memmgr.h Normal file
View File

@@ -0,0 +1,40 @@
/**
* @file memmgr.h
* Furi: memory managment API and glue
*/
#pragma once
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "check.h"
#ifdef __cplusplus
extern "C" {
#endif
// define for test case "link against furi memmgr"
#define FURI_MEMMGR_GUARD 1
/** Get free heap size
*
* @return free heap size in bytes
*/
size_t memmgr_get_free_heap(void);
/** Get total heap size
*
* @return total heap size in bytes
*/
size_t memmgr_get_total_heap(void);
/** Get heap watermark
*
* @return minimum heap in bytes
*/
size_t memmgr_get_minimum_free_heap(void);
#ifdef __cplusplus
}
#endif

650
furi/core/memmgr_heap.c Normal file
View File

@@ -0,0 +1,650 @@
/*
* FreeRTOS Kernel V10.2.1
* Copyright (C) 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* http://www.FreeRTOS.org
* http://aws.amazon.com/freertos
*
* 1 tab == 4 spaces!
*/
/*
* A sample implementation of pvPortMalloc() and vPortFree() that combines
* (coalescences) adjacent memory blocks as they are freed, and in so doing
* limits memory fragmentation.
*
* See heap_1.c, heap_2.c and heap_3.c for alternative implementations, and the
* memory management pages of http://www.FreeRTOS.org for more information.
*/
#include "memmgr_heap.h"
#include "check.h"
#include <stdlib.h>
#include <stdio.h>
#include <stm32wbxx.h>
#include <furi_hal_console.h>
#include <core/common_defines.h>
/* Defining MPU_WRAPPERS_INCLUDED_FROM_API_FILE prevents task.h from redefining
all the API functions to use the MPU wrappers. That should only be done when
task.h is included from an application file. */
#define MPU_WRAPPERS_INCLUDED_FROM_API_FILE
#include "FreeRTOS.h"
#include "task.h"
#undef MPU_WRAPPERS_INCLUDED_FROM_API_FILE
#if(configSUPPORT_DYNAMIC_ALLOCATION == 0)
#error This file must not be used if configSUPPORT_DYNAMIC_ALLOCATION is 0
#endif
/* Block sizes must not get too small. */
#define heapMINIMUM_BLOCK_SIZE ((size_t)(xHeapStructSize << 1))
/* Assumes 8bit bytes! */
#define heapBITS_PER_BYTE ((size_t)8)
/* Heap start end symbols provided by linker */
extern const void __heap_start__;
extern const void __heap_end__;
uint8_t* ucHeap = (uint8_t*)&__heap_start__;
/* Define the linked list structure. This is used to link free blocks in order
of their memory address. */
typedef struct A_BLOCK_LINK {
struct A_BLOCK_LINK* pxNextFreeBlock; /*<< The next free block in the list. */
size_t xBlockSize; /*<< The size of the free block. */
} BlockLink_t;
/*-----------------------------------------------------------*/
/*
* Inserts a block of memory that is being freed into the correct position in
* the list of free memory blocks. The block being freed will be merged with
* the block in front it and/or the block behind it if the memory blocks are
* adjacent to each other.
*/
static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert);
/*
* Called automatically to setup the required heap structures the first time
* pvPortMalloc() is called.
*/
static void prvHeapInit(void);
/*-----------------------------------------------------------*/
/* The size of the structure placed at the beginning of each allocated memory
block must by correctly byte aligned. */
static const size_t xHeapStructSize = (sizeof(BlockLink_t) + ((size_t)(portBYTE_ALIGNMENT - 1))) &
~((size_t)portBYTE_ALIGNMENT_MASK);
/* Create a couple of list links to mark the start and end of the list. */
static BlockLink_t xStart, *pxEnd = NULL;
/* Keeps track of the number of free bytes remaining, but says nothing about
fragmentation. */
static size_t xFreeBytesRemaining = 0U;
static size_t xMinimumEverFreeBytesRemaining = 0U;
/* Gets set to the top bit of an size_t type. When this bit in the xBlockSize
member of an BlockLink_t structure is set then the block belongs to the
application. When the bit is free the block is still part of the free heap
space. */
static size_t xBlockAllocatedBit = 0;
/* Furi heap extension */
#include <m-dict.h>
/* Allocation tracking types */
DICT_DEF2(MemmgrHeapAllocDict, uint32_t, uint32_t)
DICT_DEF2(
MemmgrHeapThreadDict,
uint32_t,
M_DEFAULT_OPLIST,
MemmgrHeapAllocDict_t,
DICT_OPLIST(MemmgrHeapAllocDict))
/* Thread allocation tracing storage */
static MemmgrHeapThreadDict_t memmgr_heap_thread_dict = {0};
static volatile uint32_t memmgr_heap_thread_trace_depth = 0;
/* Initialize tracing storage on start */
void memmgr_heap_init() {
MemmgrHeapThreadDict_init(memmgr_heap_thread_dict);
}
void memmgr_heap_enable_thread_trace(FuriThreadId thread_id) {
vTaskSuspendAll();
{
memmgr_heap_thread_trace_depth++;
furi_check(MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id) == NULL);
MemmgrHeapAllocDict_t alloc_dict;
MemmgrHeapAllocDict_init(alloc_dict);
MemmgrHeapThreadDict_set_at(memmgr_heap_thread_dict, (uint32_t)thread_id, alloc_dict);
MemmgrHeapAllocDict_clear(alloc_dict);
memmgr_heap_thread_trace_depth--;
}
(void)xTaskResumeAll();
}
void memmgr_heap_disable_thread_trace(FuriThreadId thread_id) {
vTaskSuspendAll();
{
memmgr_heap_thread_trace_depth++;
furi_check(MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id) != NULL);
MemmgrHeapThreadDict_erase(memmgr_heap_thread_dict, (uint32_t)thread_id);
memmgr_heap_thread_trace_depth--;
}
(void)xTaskResumeAll();
}
size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id) {
size_t leftovers = MEMMGR_HEAP_UNKNOWN;
vTaskSuspendAll();
{
memmgr_heap_thread_trace_depth++;
MemmgrHeapAllocDict_t* alloc_dict =
MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id);
if(alloc_dict) {
leftovers = 0;
MemmgrHeapAllocDict_it_t alloc_dict_it;
for(MemmgrHeapAllocDict_it(alloc_dict_it, *alloc_dict);
!MemmgrHeapAllocDict_end_p(alloc_dict_it);
MemmgrHeapAllocDict_next(alloc_dict_it)) {
MemmgrHeapAllocDict_itref_t* data = MemmgrHeapAllocDict_ref(alloc_dict_it);
if(data->key != 0) {
uint8_t* puc = (uint8_t*)data->key;
puc -= xHeapStructSize;
BlockLink_t* pxLink = (void*)puc;
if((pxLink->xBlockSize & xBlockAllocatedBit) != 0 &&
pxLink->pxNextFreeBlock == NULL) {
leftovers += data->value;
}
}
}
}
memmgr_heap_thread_trace_depth--;
}
(void)xTaskResumeAll();
return leftovers;
}
#undef traceMALLOC
static inline void traceMALLOC(void* pointer, size_t size) {
FuriThreadId thread_id = furi_thread_get_current_id();
if(thread_id && memmgr_heap_thread_trace_depth == 0) {
memmgr_heap_thread_trace_depth++;
MemmgrHeapAllocDict_t* alloc_dict =
MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id);
if(alloc_dict) {
MemmgrHeapAllocDict_set_at(*alloc_dict, (uint32_t)pointer, (uint32_t)size);
}
memmgr_heap_thread_trace_depth--;
}
}
#undef traceFREE
static inline void traceFREE(void* pointer, size_t size) {
UNUSED(size);
FuriThreadId thread_id = furi_thread_get_current_id();
if(thread_id && memmgr_heap_thread_trace_depth == 0) {
memmgr_heap_thread_trace_depth++;
MemmgrHeapAllocDict_t* alloc_dict =
MemmgrHeapThreadDict_get(memmgr_heap_thread_dict, (uint32_t)thread_id);
if(alloc_dict) {
MemmgrHeapAllocDict_erase(*alloc_dict, (uint32_t)pointer);
}
memmgr_heap_thread_trace_depth--;
}
}
size_t memmgr_heap_get_max_free_block() {
size_t max_free_size = 0;
BlockLink_t* pxBlock;
vTaskSuspendAll();
pxBlock = xStart.pxNextFreeBlock;
while(pxBlock->pxNextFreeBlock != NULL) {
if(pxBlock->xBlockSize > max_free_size) {
max_free_size = pxBlock->xBlockSize;
}
pxBlock = pxBlock->pxNextFreeBlock;
}
xTaskResumeAll();
return max_free_size;
}
void memmgr_heap_printf_free_blocks() {
BlockLink_t* pxBlock;
//TODO enable when we can do printf with a locked scheduler
//vTaskSuspendAll();
pxBlock = xStart.pxNextFreeBlock;
while(pxBlock->pxNextFreeBlock != NULL) {
printf("A %p S %lu\r\n", (void*)pxBlock, (uint32_t)pxBlock->xBlockSize);
pxBlock = pxBlock->pxNextFreeBlock;
}
//xTaskResumeAll();
}
#ifdef HEAP_PRINT_DEBUG
char* ultoa(unsigned long num, char* str, int radix) {
char temp[33]; // at radix 2 the string is at most 32 + 1 null long.
int temp_loc = 0;
int digit;
int str_loc = 0;
//construct a backward string of the number.
do {
digit = (unsigned long)num % ((unsigned long)radix);
if(digit < 10)
temp[temp_loc++] = digit + '0';
else
temp[temp_loc++] = digit - 10 + 'A';
num = ((unsigned long)num) / ((unsigned long)radix);
} while((unsigned long)num > 0);
temp_loc--;
//now reverse the string.
while(temp_loc >= 0) { // while there are still chars
str[str_loc++] = temp[temp_loc--];
}
str[str_loc] = 0; // add null termination.
return str;
}
static void print_heap_init() {
char tmp_str[33];
size_t heap_start = (size_t)&__heap_start__;
size_t heap_end = (size_t)&__heap_end__;
// {PHStart|heap_start|heap_end}
FURI_CRITICAL_ENTER();
furi_hal_console_puts("{PHStart|");
ultoa(heap_start, tmp_str, 16);
furi_hal_console_puts(tmp_str);
furi_hal_console_puts("|");
ultoa(heap_end, tmp_str, 16);
furi_hal_console_puts(tmp_str);
furi_hal_console_puts("}\r\n");
FURI_CRITICAL_EXIT();
}
static void print_heap_malloc(void* ptr, size_t size) {
char tmp_str[33];
const char* name = furi_thread_get_name(furi_thread_get_current_id());
if(!name) {
name = "";
}
// {thread name|m|address|size}
FURI_CRITICAL_ENTER();
furi_hal_console_puts("{");
furi_hal_console_puts(name);
furi_hal_console_puts("|m|0x");
ultoa((unsigned long)ptr, tmp_str, 16);
furi_hal_console_puts(tmp_str);
furi_hal_console_puts("|");
utoa(size, tmp_str, 10);
furi_hal_console_puts(tmp_str);
furi_hal_console_puts("}\r\n");
FURI_CRITICAL_EXIT();
}
static void print_heap_free(void* ptr) {
char tmp_str[33];
const char* name = furi_thread_get_name(furi_thread_get_current_id());
if(!name) {
name = "";
}
// {thread name|f|address}
FURI_CRITICAL_ENTER();
furi_hal_console_puts("{");
furi_hal_console_puts(name);
furi_hal_console_puts("|f|0x");
ultoa((unsigned long)ptr, tmp_str, 16);
furi_hal_console_puts(tmp_str);
furi_hal_console_puts("}\r\n");
FURI_CRITICAL_EXIT();
}
#endif
/*-----------------------------------------------------------*/
void* pvPortMalloc(size_t xWantedSize) {
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void* pvReturn = NULL;
size_t to_wipe = xWantedSize;
#ifdef HEAP_PRINT_DEBUG
BlockLink_t* print_heap_block = NULL;
#endif
/* If this is the first call to malloc then the heap will require
initialisation to setup the list of free blocks. */
if(pxEnd == NULL) {
#ifdef HEAP_PRINT_DEBUG
print_heap_init();
#endif
vTaskSuspendAll();
{
prvHeapInit();
memmgr_heap_init();
}
(void)xTaskResumeAll();
} else {
mtCOVERAGE_TEST_MARKER();
}
vTaskSuspendAll();
{
/* Check the requested block size is not so large that the top bit is
set. The top bit of the block size member of the BlockLink_t structure
is used to determine who owns the block - the application or the
kernel, so it must be free. */
if((xWantedSize & xBlockAllocatedBit) == 0) {
/* The wanted size is increased so it can contain a BlockLink_t
structure in addition to the requested amount of bytes. */
if(xWantedSize > 0) {
xWantedSize += xHeapStructSize;
/* Ensure that blocks are always aligned to the required number
of bytes. */
if((xWantedSize & portBYTE_ALIGNMENT_MASK) != 0x00) {
/* Byte alignment required. */
xWantedSize += (portBYTE_ALIGNMENT - (xWantedSize & portBYTE_ALIGNMENT_MASK));
configASSERT((xWantedSize & portBYTE_ALIGNMENT_MASK) == 0);
} else {
mtCOVERAGE_TEST_MARKER();
}
} else {
mtCOVERAGE_TEST_MARKER();
}
if((xWantedSize > 0) && (xWantedSize <= xFreeBytesRemaining)) {
/* Traverse the list from the start (lowest address) block until
one of adequate size is found. */
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
while((pxBlock->xBlockSize < xWantedSize) && (pxBlock->pxNextFreeBlock != NULL)) {
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
/* If the end marker was reached then a block of adequate size
was not found. */
if(pxBlock != pxEnd) {
/* Return the memory space pointed to - jumping over the
BlockLink_t structure at its start. */
pvReturn =
(void*)(((uint8_t*)pxPreviousBlock->pxNextFreeBlock) + xHeapStructSize);
/* This block is being returned for use so must be taken out
of the list of free blocks. */
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
/* If the block is larger than required it can be split into
two. */
if((pxBlock->xBlockSize - xWantedSize) > heapMINIMUM_BLOCK_SIZE) {
/* This block is to be split into two. Create a new
block following the number of bytes requested. The void
cast is used to prevent byte alignment warnings from the
compiler. */
pxNewBlockLink = (void*)(((uint8_t*)pxBlock) + xWantedSize);
configASSERT((((size_t)pxNewBlockLink) & portBYTE_ALIGNMENT_MASK) == 0);
/* Calculate the sizes of two blocks split from the
single block. */
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
pxBlock->xBlockSize = xWantedSize;
/* Insert the new block into the list of free blocks. */
prvInsertBlockIntoFreeList(pxNewBlockLink);
} else {
mtCOVERAGE_TEST_MARKER();
}
xFreeBytesRemaining -= pxBlock->xBlockSize;
if(xFreeBytesRemaining < xMinimumEverFreeBytesRemaining) {
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
} else {
mtCOVERAGE_TEST_MARKER();
}
/* The block is being returned - it is allocated and owned
by the application and has no "next" block. */
pxBlock->xBlockSize |= xBlockAllocatedBit;
pxBlock->pxNextFreeBlock = NULL;
#ifdef HEAP_PRINT_DEBUG
print_heap_block = pxBlock;
#endif
} else {
mtCOVERAGE_TEST_MARKER();
}
} else {
mtCOVERAGE_TEST_MARKER();
}
} else {
mtCOVERAGE_TEST_MARKER();
}
traceMALLOC(pvReturn, xWantedSize);
}
(void)xTaskResumeAll();
#ifdef HEAP_PRINT_DEBUG
print_heap_malloc(print_heap_block, print_heap_block->xBlockSize & ~xBlockAllocatedBit);
#endif
#if(configUSE_MALLOC_FAILED_HOOK == 1)
{
if(pvReturn == NULL) {
extern void vApplicationMallocFailedHook(void);
vApplicationMallocFailedHook();
} else {
mtCOVERAGE_TEST_MARKER();
}
}
#endif
configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0);
furi_check(pvReturn);
pvReturn = memset(pvReturn, 0, to_wipe);
return pvReturn;
}
/*-----------------------------------------------------------*/
void vPortFree(void* pv) {
uint8_t* puc = (uint8_t*)pv;
BlockLink_t* pxLink;
if(pv != NULL) {
/* The memory being freed will have an BlockLink_t structure immediately
before it. */
puc -= xHeapStructSize;
/* This casting is to keep the compiler from issuing warnings. */
pxLink = (void*)puc;
/* Check the block is actually allocated. */
configASSERT((pxLink->xBlockSize & xBlockAllocatedBit) != 0);
configASSERT(pxLink->pxNextFreeBlock == NULL);
if((pxLink->xBlockSize & xBlockAllocatedBit) != 0) {
if(pxLink->pxNextFreeBlock == NULL) {
/* The block is being returned to the heap - it is no longer
allocated. */
pxLink->xBlockSize &= ~xBlockAllocatedBit;
#ifdef HEAP_PRINT_DEBUG
print_heap_free(pxLink);
#endif
vTaskSuspendAll();
{
furi_assert((size_t)pv >= SRAM_BASE);
furi_assert((size_t)pv < SRAM_BASE + 1024 * 256);
furi_assert((pxLink->xBlockSize - xHeapStructSize) < 1024 * 256);
furi_assert((int32_t)(pxLink->xBlockSize - xHeapStructSize) >= 0);
/* Add this block to the list of free blocks. */
xFreeBytesRemaining += pxLink->xBlockSize;
traceFREE(pv, pxLink->xBlockSize);
memset(pv, 0, pxLink->xBlockSize - xHeapStructSize);
prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink));
}
(void)xTaskResumeAll();
} else {
mtCOVERAGE_TEST_MARKER();
}
} else {
mtCOVERAGE_TEST_MARKER();
}
} else {
#ifdef HEAP_PRINT_DEBUG
print_heap_free(pv);
#endif
}
}
/*-----------------------------------------------------------*/
size_t xPortGetTotalHeapSize(void) {
return (size_t)&__heap_end__ - (size_t)&__heap_start__;
}
/*-----------------------------------------------------------*/
size_t xPortGetFreeHeapSize(void) {
return xFreeBytesRemaining;
}
/*-----------------------------------------------------------*/
size_t xPortGetMinimumEverFreeHeapSize(void) {
return xMinimumEverFreeBytesRemaining;
}
/*-----------------------------------------------------------*/
void vPortInitialiseBlocks(void) {
/* This just exists to keep the linker quiet. */
}
/*-----------------------------------------------------------*/
static void prvHeapInit(void) {
BlockLink_t* pxFirstFreeBlock;
uint8_t* pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = (size_t)&__heap_end__ - (size_t)&__heap_start__;
/* Ensure the heap starts on a correctly aligned boundary. */
uxAddress = (size_t)ucHeap;
if((uxAddress & portBYTE_ALIGNMENT_MASK) != 0) {
uxAddress += (portBYTE_ALIGNMENT - 1);
uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK);
xTotalHeapSize -= uxAddress - (size_t)ucHeap;
}
pucAlignedHeap = (uint8_t*)uxAddress;
/* xStart is used to hold a pointer to the first item in the list of free
blocks. The void cast is used to prevent compiler warnings. */
xStart.pxNextFreeBlock = (void*)pucAlignedHeap;
xStart.xBlockSize = (size_t)0;
/* pxEnd is used to mark the end of the list of free blocks and is inserted
at the end of the heap space. */
uxAddress = ((size_t)pucAlignedHeap) + xTotalHeapSize;
uxAddress -= xHeapStructSize;
uxAddress &= ~((size_t)portBYTE_ALIGNMENT_MASK);
pxEnd = (void*)uxAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;
/* To start with there is a single free block that is sized to take up the
entire heap space, minus the space taken by pxEnd. */
pxFirstFreeBlock = (void*)pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = uxAddress - (size_t)pxFirstFreeBlock;
pxFirstFreeBlock->pxNextFreeBlock = pxEnd;
/* Only one block exists - and it covers the entire usable heap space. */
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
/* Work out the position of the top bit in a size_t variable. */
xBlockAllocatedBit = ((size_t)1) << ((sizeof(size_t) * heapBITS_PER_BYTE) - 1);
}
/*-----------------------------------------------------------*/
static void prvInsertBlockIntoFreeList(BlockLink_t* pxBlockToInsert) {
BlockLink_t* pxIterator;
uint8_t* puc;
/* Iterate through the list until a block is found that has a higher address
than the block being inserted. */
for(pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert;
pxIterator = pxIterator->pxNextFreeBlock) {
/* Nothing to do here, just iterate to the right position. */
}
/* Do the block being inserted, and the block it is being inserted after
make a contiguous block of memory? */
puc = (uint8_t*)pxIterator;
if((puc + pxIterator->xBlockSize) == (uint8_t*)pxBlockToInsert) {
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
pxBlockToInsert = pxIterator;
} else {
mtCOVERAGE_TEST_MARKER();
}
/* Do the block being inserted, and the block it is being inserted before
make a contiguous block of memory? */
puc = (uint8_t*)pxBlockToInsert;
if((puc + pxBlockToInsert->xBlockSize) == (uint8_t*)pxIterator->pxNextFreeBlock) {
if(pxIterator->pxNextFreeBlock != pxEnd) {
/* Form one big block from the two blocks. */
pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
} else {
pxBlockToInsert->pxNextFreeBlock = pxEnd;
}
} else {
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
}
/* If the block being inserted plugged a gab, so was merged with the block
before and the block after, then it's pxNextFreeBlock pointer will have
already been set, and should not be set here as that would make it point
to itself. */
if(pxIterator != pxBlockToInsert) {
pxIterator->pxNextFreeBlock = pxBlockToInsert;
} else {
mtCOVERAGE_TEST_MARKER();
}
}

49
furi/core/memmgr_heap.h Normal file
View File

@@ -0,0 +1,49 @@
/**
* @file memmgr_heap.h
* Furi: heap memory managment API and allocator
*/
#pragma once
#include <stdint.h>
#include <core/thread.h>
#ifdef __cplusplus
extern "C" {
#endif
#define MEMMGR_HEAP_UNKNOWN 0xFFFFFFFF
/** Memmgr heap enable thread allocation tracking
*
* @param thread_id - thread id to track
*/
void memmgr_heap_enable_thread_trace(FuriThreadId taks_handle);
/** Memmgr heap disable thread allocation tracking
*
* @param thread_id - thread id to track
*/
void memmgr_heap_disable_thread_trace(FuriThreadId taks_handle);
/** Memmgr heap get allocatred thread memory
*
* @param thread_id - thread id to track
*
* @return bytes allocated right now
*/
size_t memmgr_heap_get_thread_memory(FuriThreadId taks_handle);
/** Memmgr heap get the max contiguous block size on the heap
*
* @return size_t max contiguous block size
*/
size_t memmgr_heap_get_max_free_block();
/** Print the address and size of all free blocks to stdout
*/
void memmgr_heap_printf_free_blocks();
#ifdef __cplusplus
}
#endif

178
furi/core/message_queue.c Normal file
View File

@@ -0,0 +1,178 @@
#include "message_queue.h"
#include "core/common_defines.h"
#include <FreeRTOS.h>
#include <queue.h>
#include "check.h"
FuriMessageQueue* furi_message_queue_alloc(uint32_t msg_count, uint32_t msg_size) {
furi_assert((furi_is_irq_context() == 0U) && (msg_count > 0U) && (msg_size > 0U));
return ((FuriMessageQueue*)xQueueCreate(msg_count, msg_size));
}
void furi_message_queue_free(FuriMessageQueue* instance) {
furi_assert(furi_is_irq_context() == 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_is_irq_context() != 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_is_irq_context() != 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_is_irq_context() != 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_is_irq_context() != 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_is_irq_context() != 0U) {
stat = FuriStatusErrorISR;
} else if(hQueue == NULL) {
stat = FuriStatusErrorParameter;
} else {
stat = FuriStatusOk;
(void)xQueueReset(hQueue);
}
/* Return execution status */
return (stat);
}

95
furi/core/message_queue.h Normal file
View File

@@ -0,0 +1,95 @@
/**
* @file message_queue.h
* FuriMessageQueue
*/
#pragma once
#include "core/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

122
furi/core/mutex.c Normal file
View File

@@ -0,0 +1,122 @@
#include "mutex.h"
#include "check.h"
#include "common_defines.h"
#include <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());
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() != 0U) {
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() != 0U) {
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() != 0U) || (hMutex == NULL)) {
owner = 0;
} else {
owner = (FuriThreadId)xSemaphoreGetMutexHolder(hMutex);
}
/* Return owner thread ID */
return (owner);
}

62
furi/core/mutex.h Normal file
View File

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

95
furi/core/pubsub.c Normal file
View File

@@ -0,0 +1,95 @@
#include "pubsub.h"
#include "memmgr.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);
}

68
furi/core/pubsub.h Normal file
View File

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

159
furi/core/record.c Normal file
View File

@@ -0,0 +1,159 @@
#include "record.h"
#include "check.h"
#include "memmgr.h"
#include "mutex.h"
#include "event_flag.h"
#include <m-string.h>
#include <m-dict.h>
#define FURI_RECORD_FLAG_READY (0x1)
typedef struct {
FuriEventFlag* flags;
void* data;
size_t holders_count;
} FuriRecordData;
DICT_DEF2(FuriRecordDataDict, string_t, STRING_OPLIST, FuriRecordData, M_POD_OPLIST)
typedef struct {
FuriMutex* mutex;
FuriRecordDataDict_t records;
} FuriRecord;
static FuriRecord* furi_record = NULL;
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(string_t name_str) {
furi_assert(furi_record);
FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str);
if(!record_data) {
FuriRecordData new_record;
new_record.flags = furi_event_flag_alloc();
new_record.data = NULL;
new_record.holders_count = 0;
FuriRecordDataDict_set_at(furi_record->records, name_str, new_record);
record_data = FuriRecordDataDict_get(furi_record->records, name_str);
}
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;
string_t name_str;
string_init_set_str(name_str, name);
furi_record_lock();
ret = (FuriRecordDataDict_get(furi_record->records, name_str) != NULL);
furi_record_unlock();
string_clear(name_str);
return ret;
}
void furi_record_create(const char* name, void* data) {
furi_assert(furi_record);
string_t name_str;
string_init_set_str(name_str, name);
furi_record_lock();
// Get record data and fill it
FuriRecordData* record_data = furi_record_data_get_or_create(name_str);
furi_assert(record_data->data == NULL);
record_data->data = data;
furi_event_flag_set(record_data->flags, FURI_RECORD_FLAG_READY);
furi_record_unlock();
string_clear(name_str);
}
bool furi_record_destroy(const char* name) {
furi_assert(furi_record);
bool ret = false;
string_t name_str;
string_init_set_str(name_str, name);
furi_record_lock();
FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str);
furi_assert(record_data);
if(record_data->holders_count == 0) {
furi_event_flag_free(record_data->flags);
FuriRecordDataDict_erase(furi_record->records, name_str);
ret = true;
}
furi_record_unlock();
string_clear(name_str);
return ret;
}
void* furi_record_open(const char* name) {
furi_assert(furi_record);
string_t name_str;
string_init_set_str(name_str, name);
furi_record_lock();
FuriRecordData* record_data = furi_record_data_get_or_create(name_str);
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);
string_clear(name_str);
return record_data->data;
}
void furi_record_close(const char* name) {
furi_assert(furi_record);
string_t name_str;
string_init_set_str(name_str, name);
furi_record_lock();
FuriRecordData* record_data = FuriRecordDataDict_get(furi_record->records, name_str);
furi_assert(record_data);
record_data->holders_count--;
furi_record_unlock();
string_clear(name_str);
}

66
furi/core/record.h Normal file
View File

@@ -0,0 +1,66 @@
/**
* @file record.h
* Furi: record API
*/
#pragma once
#include <stdbool.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
*/
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

115
furi/core/semaphore.c Normal file
View File

@@ -0,0 +1,115 @@
#include "semaphore.h"
#include "check.h"
#include "common_defines.h"
#include <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() != 0U) {
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() != 0U) {
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() != 0U) {
count = (uint32_t)uxSemaphoreGetCountFromISR(hSemaphore);
} else {
count = (uint32_t)uxSemaphoreGetCount(hSemaphore);
}
/* Return number of tokens */
return (count);
}

58
furi/core/semaphore.h Normal file
View File

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

102
furi/core/stdglue.c Normal file
View File

@@ -0,0 +1,102 @@
#include "stdglue.h"
#include "check.h"
#include "memmgr.h"
#include <FreeRTOS.h>
#include <task.h>
#include <furi_hal.h>
#include <m-dict.h>
DICT_DEF2(
FuriStdglueCallbackDict,
uint32_t,
M_DEFAULT_OPLIST,
FuriStdglueWriteCallback,
M_PTR_OPLIST)
typedef struct {
FuriMutex* mutex;
FuriStdglueCallbackDict_t thread_outputs;
} FuriStdglue;
static FuriStdglue* furi_stdglue = NULL;
static ssize_t stdout_write(void* _cookie, const char* data, size_t size) {
furi_assert(furi_stdglue);
bool consumed = false;
FuriThreadId task_id = furi_thread_get_current_id();
if(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING && task_id &&
furi_mutex_acquire(furi_stdglue->mutex, FuriWaitForever) == FuriStatusOk) {
// We are in the thread context
// Handle thread callbacks
FuriStdglueWriteCallback* callback_ptr =
FuriStdglueCallbackDict_get(furi_stdglue->thread_outputs, (uint32_t)task_id);
if(callback_ptr) {
(*callback_ptr)(_cookie, data, size);
consumed = true;
}
furi_check(furi_mutex_release(furi_stdglue->mutex) == FuriStatusOk);
}
// Flush
if(data == 0) {
/*
* This means that we should flush internal buffers. Since we
* don't we just return. (Remember, "handle" == -1 means that all
* handles should be flushed.)
*/
return 0;
}
// Debug uart
if(!consumed) furi_hal_console_tx((const uint8_t*)data, size);
// All data consumed
return size;
}
void furi_stdglue_init() {
furi_stdglue = malloc(sizeof(FuriStdglue));
// Init outputs structures
furi_stdglue->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
furi_check(furi_stdglue->mutex);
FuriStdglueCallbackDict_init(furi_stdglue->thread_outputs);
// Prepare and set stdout descriptor
FILE* fp = fopencookie(
NULL,
"w",
(cookie_io_functions_t){
.read = NULL,
.write = stdout_write,
.seek = NULL,
.close = NULL,
});
setvbuf(fp, NULL, _IOLBF, 0);
stdout = fp;
}
bool furi_stdglue_set_thread_stdout_callback(FuriStdglueWriteCallback callback) {
furi_assert(furi_stdglue);
FuriThreadId task_id = furi_thread_get_current_id();
if(task_id) {
furi_check(furi_mutex_acquire(furi_stdglue->mutex, FuriWaitForever) == FuriStatusOk);
if(callback) {
FuriStdglueCallbackDict_set_at(
furi_stdglue->thread_outputs, (uint32_t)task_id, callback);
} else {
FuriStdglueCallbackDict_erase(furi_stdglue->thread_outputs, (uint32_t)task_id);
}
furi_check(furi_mutex_release(furi_stdglue->mutex) == FuriStatusOk);
return true;
} else {
return false;
}
}
void __malloc_lock(struct _reent* REENT) {
UNUSED(REENT);
vTaskSuspendAll();
}
void __malloc_unlock(struct _reent* REENT) {
UNUSED(REENT);
xTaskResumeAll();
}

36
furi/core/stdglue.h Normal file
View File

@@ -0,0 +1,36 @@
/**
* @file stdglue.h
* Furi: stdlibc glue
*/
#pragma once
#include <stdbool.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Write callback
* @param _cookie pointer to cookie (see stdio gnu extension)
* @param data pointer to data
* @param size data size @warnign your handler must consume everything
*/
typedef void (*FuriStdglueWriteCallback)(void* _cookie, const char* data, size_t size);
/** Initialized std library glue code */
void furi_stdglue_init();
/** Set STDOUT callback for your thread
*
* @param callback callback or NULL to clear
*
* @return true on success, otherwise fail
* @warning function is thread aware, use this API from the same thread
*/
bool furi_stdglue_set_thread_stdout_callback(FuriStdglueWriteCallback callback);
#ifdef __cplusplus
}
#endif

410
furi/core/thread.c Normal file
View File

@@ -0,0 +1,410 @@
#include "thread.h"
#include "kernel.h"
#include "memmgr.h"
#include "memmgr_heap.h"
#include "check.h"
#include "common_defines.h"
#include <task.h>
#include <m-string.h>
#define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers
struct FuriThread {
FuriThreadState state;
int32_t ret;
FuriThreadCallback callback;
void* context;
FuriThreadStateCallback state_callback;
void* state_context;
char* name;
configSTACK_DEPTH_TYPE stack_size;
FuriThreadPriority priority;
TaskHandle_t task_handle;
bool heap_trace_enabled;
size_t heap_size;
};
/** Catch threads that are trying to exit wrong way */
__attribute__((__noreturn__)) void furi_thread_catch() {
asm volatile("nop"); // extra magic
furi_crash("You are doing it wrong");
}
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;
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);
memmgr_heap_disable_thread_trace((FuriThreadId)task_handle);
}
furi_assert(thread->state == FuriThreadStateRunning);
furi_thread_set_state(thread, FuriThreadStateStopped);
vTaskDelete(thread->task_handle);
furi_thread_catch();
}
FuriThread* furi_thread_alloc() {
FuriThread* thread = malloc(sizeof(FuriThread));
return thread;
}
void furi_thread_free(FuriThread* thread) {
furi_assert(thread);
furi_assert(thread->state == FuriThreadStateStopped);
if(thread->name) free((void*)thread->name);
free(thread);
}
void furi_thread_set_name(FuriThread* thread, const char* name) {
furi_assert(thread);
furi_assert(thread->state == FuriThreadStateStopped);
if(thread->name) free((void*)thread->name);
thread->name = strdup(name);
}
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_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 < 0xFFFF * 4);
furi_thread_set_state(thread, FuriThreadStateStarting);
BaseType_t ret = xTaskCreate(
furi_thread_body,
thread->name,
thread->stack_size / 4,
thread,
thread->priority ? thread->priority : FuriThreadPriorityNormal,
&thread->task_handle);
furi_check(ret == pdPASS);
furi_check(thread->task_handle);
}
bool furi_thread_join(FuriThread* thread) {
furi_assert(thread);
while(thread->state != FuriThreadStateStopped) {
furi_delay_ms(10);
}
return FuriStatusOk;
}
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);
furi_assert(thread->heap_trace_enabled == false);
thread->heap_trace_enabled = true;
}
void furi_thread_disable_heap_trace(FuriThread* thread) {
furi_assert(thread);
furi_assert(thread->state == FuriThreadStateStopped);
furi_assert(thread->heap_trace_enabled == true);
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();
}
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);
}
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);
}

199
furi/core/thread.h Normal file
View File

@@ -0,0 +1,199 @@
/**
* @file thread.h
* Furi: Furi Thread API
*/
#pragma once
#include "base.h"
#include "common_defines.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 = 32, /**< Deffered 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);
/** FuriThread state change calback 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();
/** Release FuriThread
*
* @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);
/** 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 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
*
* @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 releated 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();
/** 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);
uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_items);
const char* furi_thread_get_name(FuriThreadId thread_id);
uint32_t furi_thread_get_stack_space(FuriThreadId thread_id);
#ifdef __cplusplus
}
#endif

142
furi/core/timer.c Normal file
View File

@@ -0,0 +1,142 @@
#include "timer.h"
#include "check.h"
#include "core/common_defines.h"
#include <FreeRTOS.h>
#include <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_is_irq_context() == 0U) && (func != NULL));
TimerHandle_t hTimer;
TimerCallback_t* callb;
UBaseType_t reload;
uint32_t callb_dyn;
hTimer = NULL;
callb = NULL;
callb_dyn = 0U;
/* Dynamic memory allocation is available: if memory for callback and */
/* its context is not provided, allocate it from dynamic memory pool */
if(callb == NULL) {
callb = (TimerCallback_t*)pvPortMalloc(sizeof(TimerCallback_t));
if(callb != NULL) {
/* Callback memory was allocated from dynamic pool, set flag */
callb_dyn = 1U;
}
}
if(callb != NULL) {
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 | callb_dyn);
// 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, 1, reload, callb, TimerCallback);
if((hTimer == NULL) && (callb != NULL) && (callb_dyn == 1U)) {
/* Failed to create a timer, release allocated resources */
callb = (TimerCallback_t*)((uint32_t)callb & ~1U);
vPortFree(callb);
}
}
/* Return timer ID */
return ((FuriTimer*)hTimer);
}
void furi_timer_free(FuriTimer* instance) {
furi_assert(!furi_is_irq_context());
furi_assert(instance);
TimerHandle_t hTimer = (TimerHandle_t)instance;
TimerCallback_t* callb;
callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer);
if(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS) {
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 */
vPortFree(callb);
}
}
}
FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) {
furi_assert(!furi_is_irq_context());
furi_assert(instance);
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_stop(FuriTimer* instance) {
furi_assert(!furi_is_irq_context());
furi_assert(instance);
TimerHandle_t hTimer = (TimerHandle_t)instance;
FuriStatus stat;
if(xTimerIsTimerActive(hTimer) == pdFALSE) {
stat = FuriStatusErrorResource;
} else {
if(xTimerStop(hTimer, portMAX_DELAY) == pdPASS) {
stat = FuriStatusOk;
} else {
stat = FuriStatusError;
}
}
/* Return execution status */
return (stat);
}
uint32_t furi_timer_is_running(FuriTimer* instance) {
furi_assert(!furi_is_irq_context());
furi_assert(instance);
TimerHandle_t hTimer = (TimerHandle_t)instance;
/* Return 0: not running, 1: running */
return (uint32_t)xTimerIsTimerActive(hTimer);
}

61
furi/core/timer.h Normal file
View File

@@ -0,0 +1,61 @@
#pragma once
#include "core/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
*
* @param instance The pointer to FuriTimer instance
* @param[in] ticks The ticks
*
* @return The furi status.
*/
FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks);
/** Stop timer
*
* @param instance The pointer to FuriTimer instance
*
* @return The furi status.
*/
FuriStatus furi_timer_stop(FuriTimer* instance);
/** Is timer running
*
* @param instance The pointer to FuriTimer instance
*
* @return 0: not running, 1: running
*/
uint32_t furi_timer_is_running(FuriTimer* instance);
#ifdef __cplusplus
}
#endif

59
furi/core/valuemutex.c Normal file
View File

@@ -0,0 +1,59 @@
#include "valuemutex.h"
#include <string.h>
bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) {
// mutex without name,
// no attributes (unfortunatly robust mutex is not supported by FreeRTOS),
// with dynamic memory allocation
valuemutex->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
if(valuemutex->mutex == NULL) return false;
valuemutex->value = value;
valuemutex->size = size;
return true;
}
bool delete_mutex(ValueMutex* valuemutex) {
if(furi_mutex_acquire(valuemutex->mutex, FuriWaitForever) == FuriStatusOk) {
furi_mutex_free(valuemutex->mutex);
return true;
} else {
return false;
}
}
void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) {
if(furi_mutex_acquire(valuemutex->mutex, timeout) == FuriStatusOk) {
return valuemutex->value;
} else {
return NULL;
}
}
bool release_mutex(ValueMutex* valuemutex, const void* value) {
if(value != valuemutex->value) return false;
if(furi_mutex_release(valuemutex->mutex) != FuriStatusOk) return false;
return true;
}
bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) {
void* value = acquire_mutex(valuemutex, timeout);
if(value == NULL || len > valuemutex->size) return false;
memcpy(data, value, len > 0 ? len : valuemutex->size);
if(!release_mutex(valuemutex, value)) return false;
return true;
}
bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) {
void* value = acquire_mutex(valuemutex, timeout);
if(value == NULL || len > valuemutex->size) return false;
memcpy(value, data, len > 0 ? len : valuemutex->size);
if(!release_mutex(valuemutex, value)) return false;
return true;
}

149
furi/core/valuemutex.h Normal file
View File

@@ -0,0 +1,149 @@
#pragma once
#include <stdbool.h>
#include "mutex.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* == ValueMutex ==
* The most simple concept is ValueMutex.
* It is wrapper around mutex and value pointer.
* You can take and give mutex to work with value and read and write value.
*/
typedef struct {
void* value;
size_t size;
FuriMutex* mutex;
} ValueMutex;
/**
* Creates ValueMutex.
*/
bool init_mutex(ValueMutex* valuemutex, void* value, size_t size);
/**
* Free resources allocated by `init_mutex`.
* This function doesn't free the memory occupied by `ValueMutex` itself.
*/
bool delete_mutex(ValueMutex* valuemutex);
/**
* Call for work with data stored in mutex.
* @return pointer to data if success, NULL otherwise.
*/
void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout);
/**
* Helper: infinitly wait for mutex
*/
static inline void* acquire_mutex_block(ValueMutex* valuemutex) {
return acquire_mutex(valuemutex, FuriWaitForever);
}
/**
* With statement for value mutex, acts as lambda
* @param name a resource name, const char*
* @param function_body a (){} lambda declaration,
* executed within you parent function context.
*/
#define with_value_mutex(value_mutex, function_body) \
{ \
void* p = acquire_mutex_block(value_mutex); \
furi_check(p); \
({ void __fn__ function_body __fn__; })(p); \
release_mutex(value_mutex, p); \
}
/**
* Release mutex after end of work with data.
* Call `release_mutex` and pass ValueData instance and pointer to data.
*/
bool release_mutex(ValueMutex* valuemutex, const void* value);
/**
* Instead of take-access-give sequence you can use `read_mutex` and `write_mutex` functions.
* Both functions return true in case of success, false otherwise.
*/
bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout);
bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout);
inline static bool write_mutex_block(ValueMutex* valuemutex, void* data, size_t len) {
return write_mutex(valuemutex, data, len, FuriWaitForever);
}
inline static bool read_mutex_block(ValueMutex* valuemutex, void* data, size_t len) {
return read_mutex(valuemutex, data, len, FuriWaitForever);
}
#ifdef __cplusplus
}
#endif
/*
Usage example
```C
// MANIFEST
// name="example-provider-app"
// stack=128
void provider_app(void* _p) {
// create record with mutex
uint32_t example_value = 0;
ValueMutex example_mutex;
// call `init_mutex`.
if(!init_mutex(&example_mutex, (void*)&example_value, sizeof(uint32_t))) {
printf("critical error\n");
flapp_exit(NULL);
}
furi_record_create("provider/example", (void*)&example_mutex);
// we are ready to provide record to other apps
flapp_ready();
// get value and increment it
while(1) {
uint32_t* value = acquire_mutex(&example_mutex, OsWaitForever);
if(value != NULL) {
value++;
}
release_mutex(&example_mutex, value);
furi_delay_ms(100);
}
}
// MANIFEST
// name="example-consumer-app"
// stack=128
// require="example-provider-app"
void consumer_app(void* _p) {
// this app run after flapp_ready call in all requirements app
// open mutex value
ValueMutex* counter_mutex = furi_record_open("provider/example");
if(counter_mutex == NULL) {
printf("critical error\n");
flapp_exit(NULL);
}
// continously read value every 1s
uint32_t counter;
while(1) {
if(read_mutex(counter_mutex, &counter, sizeof(counter), OsWaitForever)) {
printf("counter value: %d\n", counter);
}
furi_delay_ms(1000);
}
}
```
*/

46
furi/flipper.c Executable file
View File

@@ -0,0 +1,46 @@
#include "flipper.h"
#include <applications.h>
#include <furi.h>
#include <furi_hal_version.h>
#define TAG "Flipper"
static void flipper_print_version(const char* target, const Version* version) {
if(version) {
FURI_LOG_I(
TAG,
"\r\n\t%s version:\t%s\r\n"
"\tBuild date:\t\t%s\r\n"
"\tGit Commit:\t\t%s (%s)%s\r\n"
"\tGit Branch:\t\t%s",
target,
version_get_version(version),
version_get_builddate(version),
version_get_githash(version),
version_get_gitbranchnum(version),
version_get_dirty_flag(version) ? " (dirty)" : "",
version_get_gitbranch(version));
} else {
FURI_LOG_I(TAG, "No build info for %s", target);
}
}
void flipper_init() {
flipper_print_version("Firmware", furi_hal_version_get_firmware_version());
FURI_LOG_I(TAG, "starting services");
for(size_t i = 0; i < FLIPPER_SERVICES_COUNT; i++) {
FURI_LOG_I(TAG, "starting service %s", FLIPPER_SERVICES[i].name);
FuriThread* thread = furi_thread_alloc();
furi_thread_set_name(thread, FLIPPER_SERVICES[i].name);
furi_thread_set_stack_size(thread, FLIPPER_SERVICES[i].stack_size);
furi_thread_set_callback(thread, FLIPPER_SERVICES[i].app);
furi_thread_start(thread);
}
FURI_LOG_I(TAG, "services startup complete");
}

3
furi/flipper.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
void flipper_init();

27
furi/furi.c Normal file
View File

@@ -0,0 +1,27 @@
#include "furi.h"
#include <string.h>
#include "queue.h"
void furi_init() {
furi_assert(!furi_is_irq_context());
furi_assert(xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED);
furi_log_init();
furi_record_init();
furi_stdglue_init();
}
void furi_run() {
furi_assert(!furi_is_irq_context());
furi_assert(xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED);
#if(__ARM_ARCH_7A__ == 0U)
/* Service Call interrupt might be configured before kernel start */
/* and when its priority is lower or equal to BASEPRI, svc intruction */
/* causes a Hard Fault. */
NVIC_SetPriority(SVCall_IRQn, 0U);
#endif
/* Start the kernel scheduler */
vTaskStartScheduler();
}

37
furi/furi.h Normal file
View File

@@ -0,0 +1,37 @@
#pragma once
#include <stdlib.h>
#include <core/check.h>
#include <core/common_defines.h>
#include <core/event_flag.h>
#include <core/kernel.h>
#include <core/log.h>
#include <core/memmgr.h>
#include <core/memmgr_heap.h>
#include <core/message_queue.h>
#include <core/mutex.h>
#include <core/pubsub.h>
#include <core/record.h>
#include <core/semaphore.h>
#include <core/stdglue.h>
#include <core/thread.h>
#include <core/timer.h>
#include <core/valuemutex.h>
#include <furi_hal_gpio.h>
// FreeRTOS timer, REMOVE AFTER REFACTORING
#include <timers.h>
#ifdef __cplusplus
extern "C" {
#endif
void furi_init();
void furi_run();
#ifdef __cplusplus
}
#endif