[FL-140] Core api dynamic records (#296)
* SYSTEM: tickless mode with deep sleep. * Move FreeRTOS ticks to lptim2 * API: move all sumbodules init routines to one place. Timebase: working lptim2 at tick source. * API Timebase: lp-timer routines, timer access safe zones prediction and synchronization. FreeRTOS: adjust configuration for tickless mode. * NFC: support for tickless mode. * API Timebase: improve tick error handling in IRQ. Apploader: use insomnia mode to run applications. * BLE: prevent sleep while core2 starting * HAL: nap while in insomnia mode * init records work * try to implement record delete * tests and flapp * flapp subsystem * new core functions to get app stat, simplify core code * fix thread termination * add strdup to core * fix tests * Refactoring: remove all unusued parts, update API usage, aggreagate API sources and headers, new record storage * Refactoring: update furi record api usage, cleanup code * Fix broken merge for freertos apps * Core, Target: fix compilation warnings * Drop firmware target local * HAL Timebase, Power, Clock: semaphore guarded access to clock and power modes, better sleep mode. * SD-Filesystem: wait for all deps to arrive before adding widget. Core, BLE: disable debug dump to serial. * delete old app example-ipc * delete old app fatfs list * fix strobe app, add input header * delete old display driver * comment old app qr-code * fix sd-card test, add forced widget update * remove unused new core test * increase heap to 128k * comment and assert old core tests * fix syntax Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
This commit is contained in:
43
core/furi/check.c
Normal file
43
core/furi/check.c
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "check.h"
|
||||
#include "api-hal-task.h"
|
||||
#include <stdio.h>
|
||||
|
||||
void __furi_abort(void);
|
||||
|
||||
// TODO printf doesnt work in ISR context
|
||||
void __furi_check(void) {
|
||||
printf("assertion failed in release mode, switch to debug mode to see full assert info");
|
||||
__furi_abort();
|
||||
}
|
||||
|
||||
// TODO printf doesnt work in ISR context
|
||||
void __furi_check_debug(const char* file, int line, const char* function, const char* condition) {
|
||||
printf(
|
||||
"assertion \"%s\" failed: file \"%s\", line %d%s%s",
|
||||
condition,
|
||||
file,
|
||||
line,
|
||||
function ? ", function: " : "",
|
||||
function ? function : "");
|
||||
|
||||
if(task_is_isr_context()) {
|
||||
printf(" in [ISR] context");
|
||||
} else {
|
||||
// FuriApp* app = find_task(xTaskGetCurrentTaskHandle());
|
||||
|
||||
// if(app == NULL) {
|
||||
// printf(", in [main] context");
|
||||
// } else {
|
||||
// printf(", in [%s] app context", app->name);
|
||||
// }
|
||||
}
|
||||
|
||||
__furi_abort();
|
||||
}
|
||||
|
||||
void __furi_abort(void) {
|
||||
__disable_irq();
|
||||
asm("bkpt 1");
|
||||
while(1) {
|
||||
}
|
||||
}
|
47
core/furi/check.h
Normal file
47
core/furi/check.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Find how to how get function's pretty name
|
||||
#ifndef __FURI_CHECK_FUNC
|
||||
// Use g++'s demangled names in C++
|
||||
#if defined __cplusplus && defined __GNUC__
|
||||
#define __FURI_CHECK_FUNC __PRETTY_FUNCTION__
|
||||
|
||||
// C99 requires the use of __func__
|
||||
#elif __STDC_VERSION__ >= 199901L
|
||||
#define __FURI_CHECK_FUNC __func__
|
||||
|
||||
// Older versions of gcc don't have __func__ but can use __FUNCTION__
|
||||
#elif __GNUC__ >= 2
|
||||
#define __FURI_CHECK_FUNC __FUNCTION__
|
||||
|
||||
// failed to detect __func__ support
|
||||
#else
|
||||
#define __FURI_CHECK_FUNC ((char*)0)
|
||||
#endif
|
||||
#endif
|
||||
// !__FURI_CHECK_FUNC
|
||||
|
||||
// We have two levels of assertion
|
||||
// One - furi_check, which always runs, the only difference is in the level of debug information
|
||||
// The second is furi_assert, which doesn't compile in release mode
|
||||
#ifdef NDEBUG
|
||||
#define furi_check(__e) ((__e) ? (void)0 : __furi_check())
|
||||
#define furi_assert(__e) ((void)0)
|
||||
#else
|
||||
#define furi_check(__e) \
|
||||
((__e) ? (void)0 : __furi_check_debug(__FILE__, __LINE__, __FURI_CHECK_FUNC, #__e))
|
||||
#define furi_assert(__e) \
|
||||
((__e) ? (void)0 : __furi_check_debug(__FILE__, __LINE__, __FURI_CHECK_FUNC, #__e))
|
||||
#endif
|
||||
// !NDEBUG
|
||||
|
||||
void __furi_check(void);
|
||||
void __furi_check_debug(const char* file, int line, const char* function, const char* condition);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
24
core/furi/event.c
Normal file
24
core/furi/event.c
Normal file
@@ -0,0 +1,24 @@
|
||||
#include "event.h"
|
||||
#include <string.h>
|
||||
|
||||
bool init_event(Event* event) {
|
||||
event->semaphore_id = osSemaphoreNew(1, 0, NULL);
|
||||
return event->semaphore_id != NULL;
|
||||
}
|
||||
|
||||
bool delete_event(Event* event) {
|
||||
return osSemaphoreDelete(event->semaphore_id) == osOK;
|
||||
}
|
||||
|
||||
void signal_event(Event* event) {
|
||||
// Ignore the result, as we do not care about repeated event signalling.
|
||||
osSemaphoreRelease(event->semaphore_id);
|
||||
}
|
||||
|
||||
void wait_event(Event* event) {
|
||||
wait_event_with_timeout(event, osWaitForever);
|
||||
}
|
||||
|
||||
bool wait_event_with_timeout(Event* event, uint32_t timeout_ms) {
|
||||
return osSemaphoreAcquire(event->semaphore_id, timeout_ms) == osOK;
|
||||
}
|
44
core/furi/event.h
Normal file
44
core/furi/event.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <cmsis_os2.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
osSemaphoreId_t semaphore_id;
|
||||
} Event;
|
||||
|
||||
/*
|
||||
Creates Event.
|
||||
*/
|
||||
bool init_event(Event* event);
|
||||
|
||||
/*
|
||||
Free resources allocated by `init_event`.
|
||||
This function doesn't free the memory occupied by `Event` itself.
|
||||
*/
|
||||
bool delete_event(Event* event);
|
||||
|
||||
/*
|
||||
Signals the event.
|
||||
If the event is already in "signalled" state, nothing happens.
|
||||
*/
|
||||
void signal_event(Event* event);
|
||||
|
||||
/*
|
||||
Waits until the event is signalled.
|
||||
*/
|
||||
void wait_event(Event* event);
|
||||
|
||||
/*
|
||||
Waits with a timeout until the event is signalled.
|
||||
*/
|
||||
bool wait_event_with_timeout(Event* event, uint32_t timeout_ms);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
68
core/furi/memmgr.c
Normal file
68
core/furi/memmgr.c
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "memmgr.h"
|
||||
#include <string.h>
|
||||
|
||||
extern void* pvPortMalloc(size_t xSize);
|
||||
extern void vPortFree(void* pv);
|
||||
extern size_t xPortGetFreeHeapSize(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;
|
||||
p = pvPortMalloc(size);
|
||||
if(p) {
|
||||
// TODO implement secure realloc
|
||||
// insecure, but will do job in our case
|
||||
if(ptr != NULL) {
|
||||
memcpy(p, ptr, size);
|
||||
vPortFree(ptr);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void* calloc(size_t count, size_t size) {
|
||||
void* ptr = pvPortMalloc(count * size);
|
||||
if(ptr) {
|
||||
// zero the memory
|
||||
memset(ptr, 0, count * size);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
char* strdup(const char* s) {
|
||||
if(s == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t siz = strlen(s) + 1;
|
||||
char* y = malloc(siz);
|
||||
|
||||
if(y != NULL) {
|
||||
memcpy(y, s, siz);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
size_t memmgr_get_free_heap(void) {
|
||||
return xPortGetFreeHeapSize();
|
||||
}
|
||||
|
||||
size_t memmgr_get_minimum_free_heap(void) {
|
||||
return xPortGetMinimumEverFreeHeapSize();
|
||||
}
|
30
core/furi/memmgr.h
Normal file
30
core/furi/memmgr.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.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
|
||||
|
||||
void* malloc(size_t size);
|
||||
void free(void* ptr);
|
||||
void* realloc(void* ptr, size_t size);
|
||||
void* calloc(size_t count, size_t size);
|
||||
|
||||
size_t memmgr_get_free_heap(void);
|
||||
size_t memmgr_get_minimum_free_heap(void);
|
||||
|
||||
inline static void* furi_alloc(size_t size) {
|
||||
void* p = malloc(size);
|
||||
furi_check(p);
|
||||
return memset(p, 0, size);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
91
core/furi/pubsub.c
Normal file
91
core/furi/pubsub.c
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "pubsub.h"
|
||||
#include <furi.h>
|
||||
|
||||
bool init_pubsub(PubSub* pubsub) {
|
||||
// mutex without name,
|
||||
// no attributes (unfortunatly robust mutex is not supported by FreeRTOS),
|
||||
// with dynamic memory allocation
|
||||
const osMutexAttr_t value_mutex_attr = {
|
||||
.name = NULL, .attr_bits = 0, .cb_mem = NULL, .cb_size = 0U};
|
||||
|
||||
pubsub->mutex = osMutexNew(&value_mutex_attr);
|
||||
if(pubsub->mutex == NULL) return false;
|
||||
|
||||
// construct list
|
||||
list_pubsub_cb_init(pubsub->items);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool delete_pubsub(PubSub* pubsub) {
|
||||
if(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK) {
|
||||
bool result = osMutexDelete(pubsub->mutex) == osOK;
|
||||
list_pubsub_cb_clear(pubsub->items);
|
||||
return result;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
PubSubItem* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx) {
|
||||
if(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK) {
|
||||
// put uninitialized item to the list
|
||||
PubSubItem* item = list_pubsub_cb_push_raw(pubsub->items);
|
||||
|
||||
// initialize item
|
||||
item->cb = cb;
|
||||
item->ctx = ctx;
|
||||
item->self = pubsub;
|
||||
|
||||
// TODO unsubscribe pubsub on app exit
|
||||
//flapp_on_exit(unsubscribe_pubsub, item);
|
||||
|
||||
osMutexRelease(pubsub->mutex);
|
||||
|
||||
return item;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool unsubscribe_pubsub(PubSubItem* pubsub_id) {
|
||||
if(osMutexAcquire(pubsub_id->self->mutex, osWaitForever) == osOK) {
|
||||
bool result = false;
|
||||
|
||||
// iterate over items
|
||||
list_pubsub_cb_it_t it;
|
||||
for(list_pubsub_cb_it(it, pubsub_id->self->items); !list_pubsub_cb_end_p(it);
|
||||
list_pubsub_cb_next(it)) {
|
||||
const PubSubItem* item = list_pubsub_cb_cref(it);
|
||||
|
||||
// if the iterator is equal to our element
|
||||
if(item == pubsub_id) {
|
||||
list_pubsub_cb_remove(pubsub_id->self->items, it);
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
osMutexRelease(pubsub_id->self->mutex);
|
||||
return result;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool notify_pubsub(PubSub* pubsub, void* arg) {
|
||||
if(osMutexAcquire(pubsub->mutex, osWaitForever) == osOK) {
|
||||
// iterate over subscribers
|
||||
list_pubsub_cb_it_t it;
|
||||
for(list_pubsub_cb_it(it, pubsub->items); !list_pubsub_cb_end_p(it);
|
||||
list_pubsub_cb_next(it)) {
|
||||
const PubSubItem* item = list_pubsub_cb_cref(it);
|
||||
item->cb(arg, item->ctx);
|
||||
}
|
||||
|
||||
osMutexRelease(pubsub->mutex);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
95
core/furi/pubsub.h
Normal file
95
core/furi/pubsub.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include "cmsis_os.h"
|
||||
#include "m-list.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
== PubSub ==
|
||||
|
||||
PubSub allows users to subscribe on notifies and notify subscribers.
|
||||
Notifier side can pass `void*` arg to subscriber callback,
|
||||
and also subscriber can set `void*` context pointer that pass into
|
||||
callback (you can see callback signature below).
|
||||
*/
|
||||
|
||||
typedef void (*PubSubCallback)(const void*, void*);
|
||||
typedef struct PubSubType PubSub;
|
||||
|
||||
typedef struct {
|
||||
PubSubCallback cb;
|
||||
void* ctx;
|
||||
PubSub* self;
|
||||
} PubSubItem;
|
||||
|
||||
LIST_DEF(list_pubsub_cb, PubSubItem, M_POD_OPLIST);
|
||||
|
||||
struct PubSubType {
|
||||
list_pubsub_cb_t items;
|
||||
osMutexId_t mutex;
|
||||
};
|
||||
|
||||
/*
|
||||
To create PubSub you should create PubSub instance and call `init_pubsub`.
|
||||
*/
|
||||
bool init_pubsub(PubSub* pubsub);
|
||||
|
||||
/*
|
||||
Since we use dynamic memory - we must explicity delete pubsub
|
||||
*/
|
||||
bool delete_pubsub(PubSub* pubsub);
|
||||
|
||||
/*
|
||||
Use `subscribe_pubsub` to register your callback.
|
||||
*/
|
||||
PubSubItem* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx);
|
||||
|
||||
/*
|
||||
Use `unsubscribe_pubsub` to unregister callback.
|
||||
*/
|
||||
bool unsubscribe_pubsub(PubSubItem* pubsub_id);
|
||||
|
||||
/*
|
||||
Use `notify_pubsub` to notify subscribers.
|
||||
*/
|
||||
bool notify_pubsub(PubSub* pubsub, void* arg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
|
||||
```C
|
||||
// MANIFEST
|
||||
// name="test"
|
||||
// stack=128
|
||||
|
||||
void example_pubsub_handler(void* arg, void* ctx) {
|
||||
printf("get %d from %s\n", *(uint32_t*)arg, (const char*)ctx);
|
||||
}
|
||||
|
||||
void pubsub_test() {
|
||||
const char* app_name = "test app";
|
||||
|
||||
PubSub example_pubsub;
|
||||
init_pubsub(&example_pubsub);
|
||||
|
||||
if(!subscribe_pubsub(&example_pubsub, example_pubsub_handler, (void*)app_name)) {
|
||||
printf("critical error\n");
|
||||
flapp_exit(NULL);
|
||||
}
|
||||
|
||||
uint32_t counter = 0;
|
||||
while(1) {
|
||||
notify_pubsub(&example_pubsub, (void*)&counter);
|
||||
counter++;
|
||||
|
||||
osDelay(100);
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
123
core/furi/record.c
Normal file
123
core/furi/record.c
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "record.h"
|
||||
#include "check.h"
|
||||
|
||||
#include <cmsis_os2.h>
|
||||
#include <m-string.h>
|
||||
#include <m-dict.h>
|
||||
|
||||
#define FURI_RECORD_FLAG_UPDATED 0x00000001U
|
||||
|
||||
DICT_SET_DEF(osThreadIdSet, uint32_t)
|
||||
|
||||
typedef struct {
|
||||
void* data;
|
||||
osThreadId_t owner;
|
||||
osThreadIdSet_t holders;
|
||||
} FuriRecord;
|
||||
|
||||
DICT_DEF2(FuriRecordDict, string_t, STRING_OPLIST, FuriRecord, M_POD_OPLIST)
|
||||
|
||||
typedef struct {
|
||||
osMutexId_t records_mutex;
|
||||
FuriRecordDict_t records;
|
||||
} FuriRecordData;
|
||||
|
||||
FuriRecordData furi_record_data;
|
||||
|
||||
void furi_record_init() {
|
||||
furi_record_data.records_mutex = osMutexNew(NULL);
|
||||
FuriRecordDict_init(furi_record_data.records);
|
||||
}
|
||||
|
||||
FuriRecord* furi_record_get_or_create(string_t name_str) {
|
||||
FuriRecord* record = FuriRecordDict_get(furi_record_data.records, name_str);
|
||||
if(!record) {
|
||||
FuriRecord new_record;
|
||||
new_record.data = NULL;
|
||||
new_record.owner = NULL;
|
||||
osThreadIdSet_init(new_record.holders);
|
||||
FuriRecordDict_set_at(furi_record_data.records, name_str, new_record);
|
||||
record = FuriRecordDict_get(furi_record_data.records, name_str);
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
void furi_record_create(const char* name, void* data) {
|
||||
osThreadId_t thread_id = osThreadGetId();
|
||||
|
||||
string_t name_str;
|
||||
string_init_set_str(name_str, name);
|
||||
|
||||
// Acquire mutex
|
||||
furi_check(osMutexAcquire(furi_record_data.records_mutex, osWaitForever) == osOK);
|
||||
FuriRecord* record = furi_record_get_or_create(name_str);
|
||||
record->data = data;
|
||||
record->owner = thread_id;
|
||||
|
||||
// For each holder set event flag
|
||||
osThreadIdSet_it_t it;
|
||||
for(osThreadIdSet_it(it, record->holders); !osThreadIdSet_end_p(it); osThreadIdSet_next(it)) {
|
||||
osThreadFlagsSet((osThreadId_t)*osThreadIdSet_ref(it), FURI_RECORD_FLAG_UPDATED);
|
||||
}
|
||||
// Release mutex
|
||||
furi_check(osMutexRelease(furi_record_data.records_mutex) == osOK);
|
||||
|
||||
string_clear(name_str);
|
||||
}
|
||||
|
||||
bool furi_record_destroy(const char* name) {
|
||||
osThreadId_t thread_id = osThreadGetId();
|
||||
|
||||
string_t name_str;
|
||||
string_init_set_str(name_str, name);
|
||||
|
||||
bool destroyed = false;
|
||||
furi_check(osMutexAcquire(furi_record_data.records_mutex, osWaitForever) == osOK);
|
||||
FuriRecord* record = FuriRecordDict_get(furi_record_data.records, name_str);
|
||||
if(record && record->owner == thread_id && osThreadIdSet_size(record->holders) == 0) {
|
||||
osThreadIdSet_clear(record->holders);
|
||||
FuriRecordDict_erase(furi_record_data.records, name_str);
|
||||
}
|
||||
furi_check(osMutexRelease(furi_record_data.records_mutex) == osOK);
|
||||
|
||||
string_clear(name_str);
|
||||
return destroyed;
|
||||
}
|
||||
|
||||
void* furi_record_open(const char* name) {
|
||||
osThreadId_t thread_id = osThreadGetId();
|
||||
|
||||
string_t name_str;
|
||||
string_init_set_str(name_str, name);
|
||||
|
||||
FuriRecord* record = NULL;
|
||||
while(1) {
|
||||
furi_check(osMutexAcquire(furi_record_data.records_mutex, osWaitForever) == osOK);
|
||||
record = furi_record_get_or_create(name_str);
|
||||
osThreadIdSet_push(record->holders, (uint32_t)thread_id);
|
||||
furi_check(osMutexRelease(furi_record_data.records_mutex) == osOK);
|
||||
// Check if owner is already arrived
|
||||
if(record->owner) {
|
||||
break;
|
||||
}
|
||||
// Wait for thread flag to appear
|
||||
osThreadFlagsWait(FURI_RECORD_FLAG_UPDATED, osFlagsWaitAny, osWaitForever);
|
||||
}
|
||||
|
||||
string_clear(name_str);
|
||||
return record->data;
|
||||
}
|
||||
|
||||
void furi_record_close(const char* name) {
|
||||
osThreadId_t thread_id = osThreadGetId();
|
||||
|
||||
string_t name_str;
|
||||
string_init_set_str(name_str, name);
|
||||
|
||||
furi_check(osMutexAcquire(furi_record_data.records_mutex, osWaitForever) == osOK);
|
||||
FuriRecord* record = FuriRecordDict_get(furi_record_data.records, name_str);
|
||||
osThreadIdSet_erase(record->holders, (uint32_t)thread_id);
|
||||
furi_check(osMutexRelease(furi_record_data.records_mutex) == osOK);
|
||||
|
||||
string_clear(name_str);
|
||||
}
|
44
core/furi/record.h
Normal file
44
core/furi/record.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Initialize record storage
|
||||
* For internal use only.
|
||||
*/
|
||||
void furi_record_init();
|
||||
|
||||
/* 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 appear
|
||||
*/
|
||||
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
|
38
core/furi/stdglue.c
Normal file
38
core/furi/stdglue.c
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "stdglue.h"
|
||||
#include <main.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
extern UART_HandleTypeDef DEBUG_UART;
|
||||
|
||||
static ssize_t stdout_write(void* _cookie, const char* data, size_t size) {
|
||||
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;
|
||||
}
|
||||
|
||||
HAL_UART_Transmit(&DEBUG_UART, (uint8_t*)data, (uint16_t)size, HAL_MAX_DELAY);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
bool furi_stdglue_init() {
|
||||
FILE* fp = fopencookie(
|
||||
NULL,
|
||||
"w",
|
||||
(cookie_io_functions_t){
|
||||
.read = NULL,
|
||||
.write = stdout_write,
|
||||
.seek = NULL,
|
||||
.close = NULL,
|
||||
});
|
||||
setvbuf(fp, NULL, _IONBF, 0);
|
||||
stdout = fp;
|
||||
|
||||
return true;
|
||||
}
|
13
core/furi/stdglue.h
Normal file
13
core/furi/stdglue.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bool furi_stdglue_init();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
177
core/furi/value-expanders.c
Normal file
177
core/furi/value-expanders.c
Normal file
@@ -0,0 +1,177 @@
|
||||
#include "value-expanders.h"
|
||||
|
||||
bool init_composer(ValueComposer* composer, void* value) {
|
||||
if(!init_mutex(&composer->value, value, 0)) return false;
|
||||
|
||||
for(size_t i = 0; i < sizeof(composer->layers) / sizeof(composer->layers[0]); i++) {
|
||||
list_composer_cb_init(composer->layers[i]);
|
||||
}
|
||||
|
||||
// mutex without name,
|
||||
// no attributes (unfortunatly robust mutex is not supported by FreeRTOS),
|
||||
// with dynamic memory allocation
|
||||
const osMutexAttr_t value_mutex_attr = {
|
||||
.name = NULL, .attr_bits = 0, .cb_mem = NULL, .cb_size = 0U};
|
||||
|
||||
composer->mutex = osMutexNew(&value_mutex_attr);
|
||||
if(composer->mutex == NULL) return false;
|
||||
|
||||
if(!init_event(&composer->request)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool delete_composer(ValueComposer* composer) {
|
||||
if(osMutexAcquire(composer->mutex, osWaitForever) == osOK) {
|
||||
bool result = true;
|
||||
result &= delete_mutex(&composer->value);
|
||||
|
||||
for(size_t i = 0; i < sizeof(composer->layers) / sizeof(composer->layers[0]); i++) {
|
||||
list_composer_cb_clear(composer->layers[i]);
|
||||
}
|
||||
|
||||
result &= osMutexDelete(composer->mutex) == osOK;
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ValueComposerHandle*
|
||||
add_compose_layer(ValueComposer* composer, ValueComposerCallback cb, void* ctx, UiLayer layer) {
|
||||
if(osMutexAcquire(composer->mutex, osWaitForever) == osOK) {
|
||||
// put uninitialized item to the list
|
||||
ValueComposerHandle* handle = list_composer_cb_push_raw(composer->layers[layer]);
|
||||
|
||||
handle->cb = cb;
|
||||
handle->ctx = ctx;
|
||||
handle->layer = layer;
|
||||
handle->composer = composer;
|
||||
|
||||
// TODO unregister handle on app exit
|
||||
//flapp_on_exit(remove_compose_layer, handle);
|
||||
|
||||
osMutexRelease(composer->mutex);
|
||||
|
||||
// Layers changed, request composition
|
||||
signal_event(&composer->request);
|
||||
|
||||
return handle;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool remove_compose_layer(ValueComposerHandle* handle) {
|
||||
ValueComposer* composer = handle->composer;
|
||||
|
||||
if(osMutexAcquire(composer->mutex, osWaitForever) == osOK) {
|
||||
bool result = false;
|
||||
|
||||
// iterate over items
|
||||
list_composer_cb_it_t it;
|
||||
for(list_composer_cb_it(it, composer->layers[handle->layer]); !list_composer_cb_end_p(it);
|
||||
list_composer_cb_next(it)) {
|
||||
const ValueComposerHandle* item = list_composer_cb_cref(it);
|
||||
|
||||
// if the iterator is equal to our element
|
||||
if(item == handle) {
|
||||
list_composer_cb_remove(composer->layers[handle->layer], it);
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
osMutexRelease(composer->mutex);
|
||||
|
||||
// Layers changed, request composition
|
||||
signal_event(&composer->request);
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void request_compose(ValueComposerHandle* handle) {
|
||||
ValueComposer* composer = handle->composer;
|
||||
signal_event(&composer->request);
|
||||
}
|
||||
|
||||
void perform_compose(
|
||||
ValueComposer* composer,
|
||||
ValueComposerCallback start_cb,
|
||||
ValueComposerCallback end_cb,
|
||||
void* ctx) {
|
||||
if(!wait_event_with_timeout(&composer->request, 0)) return;
|
||||
|
||||
void* state = acquire_mutex(&composer->value, 0);
|
||||
if(state == NULL) return;
|
||||
|
||||
if(start_cb != NULL) start_cb(ctx, state);
|
||||
perform_compose_internal(composer, state);
|
||||
if(end_cb != NULL) end_cb(ctx, state);
|
||||
|
||||
release_mutex(&composer->value, state);
|
||||
}
|
||||
|
||||
void perform_compose_internal(ValueComposer* composer, void* state) {
|
||||
if(osMutexAcquire(composer->mutex, osWaitForever) == osOK) {
|
||||
// Compose all levels for now
|
||||
for(size_t i = 0; i < sizeof(composer->layers) / sizeof(composer->layers[0]); i++) {
|
||||
// iterate over items
|
||||
list_composer_cb_it_t it;
|
||||
for(list_composer_cb_it(it, composer->layers[i]); !list_composer_cb_end_p(it);
|
||||
list_composer_cb_next(it)) {
|
||||
const ValueComposerHandle* h = list_composer_cb_cref(it);
|
||||
h->cb(h->ctx, state);
|
||||
}
|
||||
}
|
||||
|
||||
osMutexRelease(composer->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void COPY_COMPOSE(void* ctx, void* state) {
|
||||
read_mutex((ValueMutex*)ctx, state, 0, osWaitForever);
|
||||
}
|
||||
|
||||
bool init_managed(ValueManager* managed, void* value, size_t size) {
|
||||
if(!init_pubsub(&managed->pubsub)) return false;
|
||||
if(!init_mutex(&managed->value, value, size)) {
|
||||
delete_pubsub(&managed->pubsub);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool delete_managed(ValueManager* managed) {
|
||||
bool result = true;
|
||||
result &= delete_mutex(&managed->value);
|
||||
result &= delete_pubsub(&managed->pubsub);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout) {
|
||||
void* value = acquire_mutex(&managed->value, timeout);
|
||||
if(value == NULL) return false;
|
||||
|
||||
memcpy(value, data, len);
|
||||
|
||||
notify_pubsub(&managed->pubsub, value);
|
||||
|
||||
if(!release_mutex(&managed->value, value)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool commit_managed(ValueManager* managed, void* value) {
|
||||
if(value != managed->value.value) return false;
|
||||
|
||||
notify_pubsub(&managed->pubsub, value);
|
||||
|
||||
if(!release_mutex(&managed->value, value)) return false;
|
||||
|
||||
return true;
|
||||
}
|
115
core/furi/value-expanders.h
Normal file
115
core/furi/value-expanders.h
Normal file
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include "valuemutex.h"
|
||||
#include "pubsub.h"
|
||||
#include "event.h"
|
||||
#include <m-list.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
== Value composer ==
|
||||
*/
|
||||
|
||||
typedef struct ValueComposer ValueComposer;
|
||||
|
||||
typedef void (*ValueComposerCallback)(void* ctx, void* state);
|
||||
|
||||
typedef enum { UiLayerBelowNotify, UiLayerNotify, UiLayerAboveNotify } UiLayer;
|
||||
|
||||
typedef struct {
|
||||
ValueComposerCallback cb;
|
||||
void* ctx;
|
||||
UiLayer layer;
|
||||
ValueComposer* composer;
|
||||
} ValueComposerHandle;
|
||||
|
||||
LIST_DEF(list_composer_cb, ValueComposerHandle, M_POD_OPLIST);
|
||||
|
||||
struct ValueComposer {
|
||||
ValueMutex value;
|
||||
list_composer_cb_t layers[3];
|
||||
osMutexId_t mutex;
|
||||
Event request;
|
||||
};
|
||||
|
||||
void COPY_COMPOSE(void* ctx, void* state);
|
||||
|
||||
bool init_composer(ValueComposer* composer, void* value);
|
||||
|
||||
/*
|
||||
Free resources allocated by `init_composer`.
|
||||
This function doesn't free the memory occupied by `ValueComposer` itself.
|
||||
*/
|
||||
bool delete_composer(ValueComposer* composer);
|
||||
|
||||
ValueComposerHandle*
|
||||
add_compose_layer(ValueComposer* composer, ValueComposerCallback cb, void* ctx, UiLayer layer);
|
||||
|
||||
bool remove_compose_layer(ValueComposerHandle* handle);
|
||||
|
||||
void request_compose(ValueComposerHandle* handle);
|
||||
|
||||
/*
|
||||
Perform composition if requested.
|
||||
|
||||
`start_cb` and `end_cb` will be called before and after all layer callbacks, respectively.
|
||||
Both `start_cb` and `end_cb` can be NULL. They can be used to set initial state (e.g. clear screen)
|
||||
and commit the final state.
|
||||
*/
|
||||
void perform_compose(
|
||||
ValueComposer* composer,
|
||||
ValueComposerCallback start_cb,
|
||||
ValueComposerCallback end_cb,
|
||||
void* ctx);
|
||||
|
||||
/*
|
||||
Perform composition.
|
||||
|
||||
This function should be called with value mutex acquired.
|
||||
This function is here for convenience, so that developers can write their own compose loops.
|
||||
See `perform_compose` function body for an example.
|
||||
*/
|
||||
void perform_compose_internal(ValueComposer* composer, void* state);
|
||||
|
||||
// See [LED](LED-API) or [Display](Display-API) API for examples.
|
||||
|
||||
/*
|
||||
== ValueManager ==
|
||||
|
||||
More complicated concept is ValueManager.
|
||||
It is like ValueMutex, but user can subscribe to value updates.
|
||||
|
||||
First of all you can use value and pubsub part as showing above:
|
||||
aquire/release mutex, read value, subscribe/unsubscribe pubsub.
|
||||
There are two specific methods for ValueManager: write_managed, commit_managed
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
ValueMutex value;
|
||||
PubSub pubsub;
|
||||
} ValueManager;
|
||||
|
||||
bool init_managed(ValueManager* managed, void* value, size_t size);
|
||||
|
||||
/*
|
||||
Free resources allocated by `init_managed`.
|
||||
This function doesn't free the memory occupied by `ValueManager` itself.
|
||||
*/
|
||||
bool delete_managed(ValueManager* managed);
|
||||
|
||||
/*
|
||||
acquire value, changes it and send notify with current value.
|
||||
*/
|
||||
bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout);
|
||||
|
||||
/*
|
||||
commit_managed works as `release_mutex` but send notify with current value.
|
||||
*/
|
||||
bool commit_managed(ValueManager* managed, void* value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
61
core/furi/valuemutex.c
Normal file
61
core/furi/valuemutex.c
Normal file
@@ -0,0 +1,61 @@
|
||||
#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
|
||||
const osMutexAttr_t value_mutext_attr = {
|
||||
.name = NULL, .attr_bits = 0, .cb_mem = NULL, .cb_size = 0U};
|
||||
|
||||
valuemutex->mutex = osMutexNew(&value_mutext_attr);
|
||||
if(valuemutex->mutex == NULL) return false;
|
||||
|
||||
valuemutex->value = value;
|
||||
valuemutex->size = size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool delete_mutex(ValueMutex* valuemutex) {
|
||||
if(osMutexAcquire(valuemutex->mutex, osWaitForever) == osOK) {
|
||||
return osMutexDelete(valuemutex->mutex) == osOK;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) {
|
||||
if(osMutexAcquire(valuemutex->mutex, timeout) == osOK) {
|
||||
return valuemutex->value;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool release_mutex(ValueMutex* valuemutex, const void* value) {
|
||||
if(value != valuemutex->value) return false;
|
||||
|
||||
if(osMutexRelease(valuemutex->mutex) != osOK) 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
core/furi/valuemutex.h
Normal file
149
core/furi/valuemutex.h
Normal file
@@ -0,0 +1,149 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmsis_os2.h>
|
||||
#include <stdbool.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;
|
||||
osMutexId_t 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.
|
||||
Returns 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, osWaitForever);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, osWaitForever);
|
||||
}
|
||||
|
||||
inline static bool read_mutex_block(ValueMutex* valuemutex, void* data, size_t len) {
|
||||
return read_mutex(valuemutex, data, len, osWaitForever);
|
||||
}
|
||||
|
||||
#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);
|
||||
|
||||
osDelay(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);
|
||||
}
|
||||
|
||||
osDelay(1000);
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
Reference in New Issue
Block a user