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:
13
furi/SConscript
Normal file
13
furi/SConscript
Normal 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
43
furi/core/base.h
Normal 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
112
furi/core/check.c
Normal 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
32
furi/core/check.h
Normal 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
154
furi/core/common_defines.h
Normal 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
|
44
furi/core/dangerous_defines.h
Normal file
44
furi/core/dangerous_defines.h
Normal 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
135
furi/core/event_flag.c
Normal 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
70
furi/core/event_flag.h
Normal 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
174
furi/core/kernel.c
Normal 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
93
furi/core/kernel.h
Normal 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
66
furi/core/log.c
Normal 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
101
furi/core/log.h
Normal 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
81
furi/core/memmgr.c
Normal 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
40
furi/core/memmgr.h
Normal 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
650
furi/core/memmgr_heap.c
Normal 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
49
furi/core/memmgr_heap.h
Normal 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
178
furi/core/message_queue.c
Normal 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
95
furi/core/message_queue.h
Normal 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
122
furi/core/mutex.c
Normal 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
62
furi/core/mutex.h
Normal 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
95
furi/core/pubsub.c
Normal 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
68
furi/core/pubsub.h
Normal 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
159
furi/core/record.c
Normal 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
66
furi/core/record.h
Normal 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
115
furi/core/semaphore.c
Normal 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
58
furi/core/semaphore.h
Normal 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
102
furi/core/stdglue.c
Normal 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
36
furi/core/stdglue.h
Normal 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
410
furi/core/thread.c
Normal 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
199
furi/core/thread.h
Normal 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
142
furi/core/timer.c
Normal 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
61
furi/core/timer.h
Normal 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
59
furi/core/valuemutex.c
Normal 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
149
furi/core/valuemutex.h
Normal 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
46
furi/flipper.c
Executable 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
3
furi/flipper.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void flipper_init();
|
27
furi/furi.c
Normal file
27
furi/furi.c
Normal 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
37
furi/furi.h
Normal 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
|
Reference in New Issue
Block a user