aanper 5 лет назад
Родитель
Сommit
05d704fd54
54 измененных файлов с 2498 добавлено и 322 удалено
  1. 1 1
      .github/workflows/ci.yml
  2. 6 1
      .gitignore
  3. 120 0
      applications/app-loader/app-loader.c
  4. 25 3
      applications/applications.mk
  5. 30 0
      applications/backlight-control/backlight-control.c
  6. 119 0
      applications/gui/canvas.c
  7. 27 0
      applications/gui/canvas.h
  8. 14 0
      applications/gui/canvas_i.h
  9. 172 0
      applications/gui/gui.c
  10. 18 0
      applications/gui/gui.h
  11. 73 0
      applications/gui/gui_event.c
  12. 29 0
      applications/gui/gui_event.h
  13. 5 0
      applications/gui/gui_i.h
  14. 114 0
      applications/gui/u8g2_periphery.c
  15. 83 0
      applications/gui/widget.c
  16. 21 0
      applications/gui/widget.h
  17. 9 0
      applications/gui/widget_i.h
  18. 217 0
      applications/menu/menu.c
  19. 19 0
      applications/menu/menu.h
  20. 79 0
      applications/menu/menu_event.c
  21. 32 0
      applications/menu/menu_event.h
  22. 107 0
      applications/menu/menu_item.c
  23. 37 0
      applications/menu/menu_item.h
  24. 13 14
      applications/startup.h
  25. 99 0
      applications/tests/furi_memmgr_test.c
  26. 56 0
      applications/tests/furi_pubsub_test.c
  27. 0 194
      applications/tests/furi_record_test.c
  28. 16 9
      applications/tests/minunit_test.c
  29. 51 0
      core/api-basic/memmgr.c
  30. 13 0
      core/api-basic/memmgr.h
  31. 90 0
      core/api-basic/pubsub.c
  32. 0 48
      core/api-basic/pubsub.c.unimplemented
  33. 19 15
      core/api-basic/pubsub.h
  34. 4 0
      core/flipper.h
  35. 5 1
      core/flipper_v2.h
  36. 11 0
      core/furi-deprecated.h
  37. 6 5
      docker/Dockerfile
  38. 2 2
      docker/syntax_check.sh
  39. 14 1
      firmware/targets/f2/Inc/FreeRTOSConfig.h
  40. 2 2
      firmware/targets/f2/Inc/usbd_conf.h
  41. 204 0
      firmware/targets/f2/STM32L476RGTx_FLASH_NO_BOOT.ld
  42. 27 1
      firmware/targets/f2/Src/freertos.c
  43. 3 1
      firmware/targets/f2/Src/stm32l4xx_it.c
  44. 14 7
      firmware/targets/f2/Src/system_stm32l4xx.c
  45. 6 3
      firmware/targets/f2/cube.ioc
  46. 15 1
      firmware/targets/f2/target.mk
  47. 1 0
      firmware/targets/local/Inc/cmsis_os.h
  48. 389 0
      firmware/targets/local/Src/heap_4.c
  49. 12 0
      firmware/targets/local/Src/lo_os.c
  50. 8 0
      firmware/targets/local/Src/main.c
  51. 37 0
      firmware/targets/local/fatfs/heap.h
  52. 3 0
      firmware/targets/local/target.mk
  53. 1 0
      lib/lib.mk
  54. 20 13
      make/rules.mk

+ 1 - 1
.github/workflows/ci.yml

@@ -27,7 +27,7 @@ jobs:
 
       - name: Check syntax
         uses: ./.github/actions/docker
-        continue-on-error: true
+        continue-on-error: false
         with:
           run: /syntax_check.sh
 

+ 6 - 1
.gitignore

@@ -7,4 +7,9 @@ bindings/
 .mxproject
 
 # Visual Studio Code
-.vscode/
+.vscode/
+
+# legendary cmake's
+build
+CMakeLists.txt
+firmware/targets/f2/CMakeLists.txt

+ 120 - 0
applications/app-loader/app-loader.c

@@ -0,0 +1,120 @@
+#include "flipper_v2.h"
+#include <gui/gui.h>
+#include "menu/menu.h"
+
+typedef struct {
+    FuriApp* handler;
+    Widget* widget;
+    FlipperStartupApp* current_app;
+} AppLoaderState;
+
+typedef struct {
+    AppLoaderState* state;
+    FlipperStartupApp* app;
+} AppLoaderContext;
+
+void render_callback(CanvasApi* canvas, void* _ctx) {
+    AppLoaderState* ctx = (AppLoaderState*)_ctx;
+
+    canvas->clear(canvas);
+    canvas->set_color(canvas, ColorBlack);
+    canvas->set_font(canvas, FontPrimary);
+    canvas->draw_str(canvas, 2, 32, ctx->current_app->name);
+
+    canvas->set_font(canvas, FontSecondary);
+    canvas->draw_str(canvas, 2, 44, "press back to exit");
+}
+
+void input_callback(InputEvent* input_event, void* _ctx) {
+    AppLoaderState* ctx = (AppLoaderState*)_ctx;
+
+    if(input_event->state && input_event->input == InputBack) {
+        furiac_kill(ctx->handler);
+        widget_enabled_set(ctx->widget, false);
+    }
+}
+
+void handle_menu(void* _ctx) {
+    AppLoaderContext* ctx = (AppLoaderContext*)_ctx;
+
+    widget_enabled_set(ctx->state->widget, true);
+
+    // TODO how to call this?
+    // furiac_wait_libs(&FLIPPER_STARTUP[i].libs);
+
+    ctx->state->current_app = ctx->app;
+    ctx->state->handler = furiac_start(ctx->app->app, ctx->app->name, NULL);
+}
+
+void application_blink(void* p);
+void application_uart_write(void* p);
+void application_input_dump(void* p);
+
+const FlipperStartupApp FLIPPER_APPS[] = {
+    {.app = application_blink, .name = "blink", .libs = {0}},
+    {.app = application_uart_write, .name = "uart write", .libs = {0}},
+    {.app = application_input_dump, .name = "input dump", .libs = {1, FURI_LIB{"input_task"}}},
+};
+
+void app_loader(void* p) {
+    osThreadId_t self_id = osThreadGetId();
+    assert(self_id);
+
+    AppLoaderState state;
+    state.handler = NULL;
+
+    state.widget = widget_alloc();
+    assert(state.widget);
+    widget_enabled_set(state.widget, false);
+    widget_draw_callback_set(state.widget, render_callback, &state);
+    widget_input_callback_set(state.widget, input_callback, &state);
+
+    ValueMutex* menu_mutex = furi_open("menu");
+    if(menu_mutex == NULL) {
+        printf("menu is not available\n");
+        furiac_exit(NULL);
+    }
+
+    // Open GUI and register widget
+    GuiApi* gui = furi_open("gui");
+    if(gui == NULL) {
+        printf("gui is not available\n");
+        furiac_exit(NULL);
+    }
+    gui->add_widget(gui, state.widget, WidgetLayerFullscreen);
+
+    {
+        Menu* menu = acquire_mutex_block(menu_mutex);
+
+        // FURI startup
+        const size_t flipper_app_count = sizeof(FLIPPER_APPS) / sizeof(FLIPPER_APPS[0]);
+
+        for(size_t i = 0; i < flipper_app_count; i++) {
+            AppLoaderContext* ctx = furi_alloc(sizeof(AppLoaderContext));
+            ctx->state = &state;
+            ctx->app = &FLIPPER_APPS[i];
+
+            menu_item_add(
+                menu, menu_item_alloc_function(FLIPPER_APPS[i].name, NULL, handle_menu, ctx));
+        }
+
+        /*
+        menu_item_add(menu, menu_item_alloc_function("Sub 1 gHz", NULL, NULL, NULL));
+        menu_item_add(menu, menu_item_alloc_function("125 kHz RFID", NULL, NULL, NULL));
+        menu_item_add(menu, menu_item_alloc_function("Infrared", NULL, NULL, NULL));
+        menu_item_add(menu, menu_item_alloc_function("I-Button", NULL, NULL, NULL));
+        menu_item_add(menu, menu_item_alloc_function("USB", NULL, NULL, NULL));
+        menu_item_add(menu, menu_item_alloc_function("Bluetooth", NULL, NULL, NULL));
+        menu_item_add(menu, menu_item_alloc_function("GPIO / HW", NULL, NULL, NULL));
+        menu_item_add(menu, menu_item_alloc_function("U2F", NULL, NULL, NULL));
+        menu_item_add(menu, menu_item_alloc_function("Tamagotchi", NULL, NULL, NULL));
+        menu_item_add(menu, menu_item_alloc_function("Plugins", NULL, NULL, NULL));
+        */
+
+        release_mutex(menu_mutex, menu);
+    }
+
+    printf("[app loader] start\n");
+
+    osThreadSuspend(self_id);
+}

+ 25 - 3
applications/applications.mk

@@ -5,8 +5,22 @@ CFLAGS		+= -I$(APP_DIR)
 
 APP_RELEASE ?= 0
 ifeq ($(APP_RELEASE), 1)
-APP_DISPLAY = 1
-APP_INPUT = 1
+APP_GUI		= 1
+APP_INPUT	= 1
+APP_MENU = 1
+endif
+
+APP_MENU ?= 0
+ifeq ($(APP_MENU), 1)
+APP_INPUT	= 1
+APP_GUI		= 1
+CFLAGS		+= -DAPP_MENU
+C_SOURCES	+= $(wildcard $(APP_DIR)/menu/*.c)
+C_SOURCES	+= $(wildcard $(APP_DIR)/app-loader/*.c)
+
+APP_EXAMPLE_BLINK = 1
+APP_EXAMPLE_UART_WRITE = 1
+APP_EXAMPLE_INPUT_DUMP = 1
 endif
 
 APP_TEST	?= 0
@@ -17,6 +31,8 @@ C_SOURCES	+= $(APP_DIR)/tests/furi_record_test.c
 C_SOURCES	+= $(APP_DIR)/tests/test_index.c
 C_SOURCES	+= $(APP_DIR)/tests/minunit_test.c
 C_SOURCES	+= $(APP_DIR)/tests/furi_valuemutex_test.c
+C_SOURCES	+= $(APP_DIR)/tests/furi_pubsub_test.c
+C_SOURCES	+= $(APP_DIR)/tests/furi_memmgr_test.c
 endif
 
 APP_EXAMPLE_BLINK ?= 0
@@ -77,6 +93,12 @@ APP_DISPLAY = 1
 endif
 
 # device drivers
+APP_GUI	?= 0
+ifeq ($(APP_GUI), 1)
+CFLAGS		+= -DAPP_GUI
+C_SOURCES	+= $(wildcard $(APP_DIR)/gui/*.c)
+C_SOURCES	+= $(wildcard $(APP_DIR)/backlight-control/*.c)
+endif
 
 ifeq ($(APP_DISPLAY), 1)
 CFLAGS		+= -DAPP_DISPLAY
@@ -87,4 +109,4 @@ APP_INPUT	?= 0
 ifeq ($(APP_INPUT), 1)
 CFLAGS		+= -DAPP_INPUT
 C_SOURCES	+= $(APP_DIR)/input/input.c
-endif
+endif

+ 30 - 0
applications/backlight-control/backlight-control.c

@@ -0,0 +1,30 @@
+#include "flipper.h"
+
+static void event_cb(const void* value, size_t size, void* ctx) {
+    xSemaphoreGive((SemaphoreHandle_t*)ctx);
+}
+
+const uint32_t BACKLIGHT_TIME = 10000;
+
+void backlight_control(void* p) {
+    // TODO use FURI
+    HAL_GPIO_WritePin(DISPLAY_BACKLIGHT_GPIO_Port, DISPLAY_BACKLIGHT_Pin, GPIO_PIN_SET);
+
+    StaticSemaphore_t event_descriptor;
+    SemaphoreHandle_t update = xSemaphoreCreateCountingStatic(255, 0, &event_descriptor);
+
+    // open record
+    furi_open_deprecated("input_events", false, false, event_cb, NULL, (void*)update);
+
+    // we ready to work
+    furiac_ready();
+
+    while(1) {
+        // wait for event
+        if(xSemaphoreTake(update, BACKLIGHT_TIME) == pdTRUE) {
+            HAL_GPIO_WritePin(DISPLAY_BACKLIGHT_GPIO_Port, DISPLAY_BACKLIGHT_Pin, GPIO_PIN_SET);
+        } else {
+            HAL_GPIO_WritePin(DISPLAY_BACKLIGHT_GPIO_Port, DISPLAY_BACKLIGHT_Pin, GPIO_PIN_RESET);
+        }
+    }
+}

+ 119 - 0
applications/gui/canvas.c

@@ -0,0 +1,119 @@
+#include "canvas.h"
+#include "canvas_i.h"
+
+#include <assert.h>
+#include <flipper.h>
+
+typedef struct {
+    CanvasApi api;
+
+    u8g2_t fb;
+    uint8_t offset_x;
+    uint8_t offset_y;
+    uint8_t width;
+    uint8_t height;
+} Canvas;
+
+uint8_t canvas_width(CanvasApi* api);
+uint8_t canvas_height(CanvasApi* api);
+void canvas_clear(CanvasApi* api);
+void canvas_color_set(CanvasApi* api, uint8_t color);
+void canvas_font_set(CanvasApi* api, Font font);
+void canvas_str_draw(CanvasApi* api, uint8_t x, uint8_t y, const char* str);
+
+uint8_t u8g2_gpio_and_delay_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr);
+uint8_t u8x8_hw_spi_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr);
+
+CanvasApi* canvas_api_init() {
+    Canvas* canvas = furi_alloc(sizeof(Canvas));
+
+    u8g2_Setup_st7565_erc12864_alt_f(
+        &canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32);
+
+    // send init sequence to the display, display is in sleep mode after this
+    u8g2_InitDisplay(&canvas->fb);
+    u8g2_SetContrast(&canvas->fb, 36);
+
+    u8g2_SetPowerSave(&canvas->fb, 0); // wake up display
+    u8g2_SendBuffer(&canvas->fb);
+
+    canvas->api.width = canvas_width;
+    canvas->api.height = canvas_height;
+    canvas->api.clear = canvas_clear;
+    canvas->api.set_color = canvas_color_set;
+    canvas->api.set_font = canvas_font_set;
+    canvas->api.draw_str = canvas_str_draw;
+
+    return (CanvasApi*)canvas;
+}
+
+void canvas_api_free(CanvasApi* api) {
+    assert(api);
+    free(api);
+}
+
+void canvas_commit(CanvasApi* api) {
+    assert(api);
+    Canvas* canvas = (Canvas*)api;
+    u8g2_SetPowerSave(&canvas->fb, 0); // wake up display
+    u8g2_SendBuffer(&canvas->fb);
+}
+
+void canvas_frame_set(
+    CanvasApi* api,
+    uint8_t offset_x,
+    uint8_t offset_y,
+    uint8_t width,
+    uint8_t height) {
+    assert(api);
+    Canvas* canvas = (Canvas*)api;
+    canvas->offset_x = offset_x;
+    canvas->offset_y = offset_y;
+    canvas->width = width;
+    canvas->height = height;
+}
+
+uint8_t canvas_width(CanvasApi* api) {
+    assert(api);
+    Canvas* canvas = (Canvas*)api;
+    return canvas->width;
+}
+
+uint8_t canvas_height(CanvasApi* api) {
+    assert(api);
+    Canvas* canvas = (Canvas*)api;
+    return canvas->height;
+}
+
+void canvas_clear(CanvasApi* api) {
+    assert(api);
+    Canvas* canvas = (Canvas*)api;
+    u8g2_ClearBuffer(&canvas->fb);
+}
+
+void canvas_color_set(CanvasApi* api, Color color) {
+    assert(api);
+    Canvas* canvas = (Canvas*)api;
+    u8g2_SetDrawColor(&canvas->fb, color);
+}
+
+void canvas_font_set(CanvasApi* api, Font font) {
+    assert(api);
+    Canvas* canvas = (Canvas*)api;
+    u8g2_SetFontMode(&canvas->fb, 1);
+    if(font == FontPrimary) {
+        u8g2_SetFont(&canvas->fb, u8g2_font_Born2bSportyV2_tr);
+    } else if(font == FontSecondary) {
+        u8g2_SetFont(&canvas->fb, u8g2_font_HelvetiPixel_tr);
+    } else {
+        assert(0);
+    }
+}
+
+void canvas_str_draw(CanvasApi* api, uint8_t x, uint8_t y, const char* str) {
+    assert(api);
+    Canvas* canvas = (Canvas*)api;
+    x += canvas->offset_x;
+    y += canvas->offset_y;
+    u8g2_DrawStr(&canvas->fb, x, y, str);
+}

+ 27 - 0
applications/gui/canvas.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include <stdint.h>
+#include <u8g2.h>
+
+typedef enum {
+    ColorWhite = 0x00,
+    ColorBlack = 0x01,
+} Color;
+
+typedef enum {
+    FontPrimary = 0x00,
+    FontSecondary = 0x01,
+} Font;
+
+typedef struct CanvasApi CanvasApi;
+struct CanvasApi {
+    uint8_t (*width)(CanvasApi* canvas);
+    uint8_t (*height)(CanvasApi* canvas);
+
+    void (*clear)(CanvasApi* canvas);
+
+    void (*set_color)(CanvasApi* canvas, Color color);
+    void (*set_font)(CanvasApi* canvas, Font font);
+
+    void (*draw_str)(CanvasApi* canvas, uint8_t x, uint8_t y, const char* str);
+};

+ 14 - 0
applications/gui/canvas_i.h

@@ -0,0 +1,14 @@
+#pragma once
+
+CanvasApi* canvas_api_init();
+
+void canvas_api_free(CanvasApi* api);
+
+void canvas_commit(CanvasApi* api);
+
+void canvas_frame_set(
+    CanvasApi* api,
+    uint8_t offset_x,
+    uint8_t offset_y,
+    uint8_t width,
+    uint8_t height);

+ 172 - 0
applications/gui/gui.c

@@ -0,0 +1,172 @@
+#include "gui.h"
+#include "gui_i.h"
+
+#include <flipper.h>
+#include <flipper_v2.h>
+#include <stdio.h>
+#include <m-array.h>
+
+#include "gui_event.h"
+#include "canvas.h"
+#include "canvas_i.h"
+#include "widget.h"
+#include "widget_i.h"
+
+ARRAY_DEF(WidgetArray, Widget*, M_PTR_OPLIST);
+
+struct Gui {
+    GuiApi api;
+    GuiEvent* event;
+    CanvasApi* canvas_api;
+    WidgetArray_t widgets_status_bar;
+    WidgetArray_t widgets;
+    WidgetArray_t widgets_fs;
+    WidgetArray_t widgets_dialog;
+};
+
+void gui_add_widget(GuiApi* gui_api, Widget* widget, WidgetLayer layer) {
+    assert(gui_api);
+    assert(widget);
+    Gui* gui = (Gui*)gui_api;
+
+    // TODO add mutex on widget array
+    WidgetArray_t* widget_array = NULL;
+
+    switch(layer) {
+    case WidgetLayerStatusBar:
+        widget_array = &gui->widgets_status_bar;
+        break;
+    case WidgetLayerMain:
+        widget_array = &gui->widgets;
+        break;
+    case WidgetLayerFullscreen:
+        widget_array = &gui->widgets_fs;
+        break;
+    case WidgetLayerDialog:
+        widget_array = &gui->widgets_dialog;
+        break;
+    default:
+        break;
+    }
+
+    assert(widget_array);
+
+    gui_event_lock(gui->event);
+    WidgetArray_push_back(*widget_array, widget);
+    widget_gui_set(widget, gui);
+    gui_event_unlock(gui->event);
+
+    gui_update(gui);
+}
+
+void gui_update(Gui* gui) {
+    assert(gui);
+    GuiMessage message;
+    message.type = GuiMessageTypeRedraw;
+    gui_event_messsage_send(gui->event, &message);
+}
+
+Widget* gui_widget_find_enabled(WidgetArray_t array) {
+    size_t widgets_count = WidgetArray_size(array);
+    for(size_t i = 0; i < widgets_count; i++) {
+        Widget* widget = *WidgetArray_get(array, widgets_count - i - 1);
+        if(widget_is_enabled(widget)) {
+            return widget;
+        }
+    }
+    return NULL;
+}
+
+bool gui_redraw_fs(Gui* gui) {
+    canvas_frame_set(gui->canvas_api, 0, 0, 128, 64);
+    Widget* widget = gui_widget_find_enabled(gui->widgets_fs);
+    if(widget) {
+        widget_draw(widget, gui->canvas_api);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+void gui_redraw_status_bar(Gui* gui) {
+    canvas_frame_set(gui->canvas_api, 0, 0, 128, 64);
+    Widget* widget = gui_widget_find_enabled(gui->widgets_status_bar);
+    if(widget) widget_draw(widget, gui->canvas_api);
+}
+
+void gui_redraw_normal(Gui* gui) {
+    canvas_frame_set(gui->canvas_api, 0, 9, 128, 55);
+    Widget* widget = gui_widget_find_enabled(gui->widgets);
+    if(widget) widget_draw(widget, gui->canvas_api);
+}
+
+void gui_redraw_dialogs(Gui* gui) {
+    canvas_frame_set(gui->canvas_api, 10, 20, 118, 44);
+    Widget* widget = gui_widget_find_enabled(gui->widgets_dialog);
+    if(widget) widget_draw(widget, gui->canvas_api);
+}
+
+void gui_redraw(Gui* gui) {
+    assert(gui);
+
+    if(!gui_redraw_fs(gui)) {
+        gui_redraw_status_bar(gui);
+        gui_redraw_normal(gui);
+    }
+    gui_redraw_dialogs(gui);
+
+    canvas_commit(gui->canvas_api);
+}
+
+void gui_input(Gui* gui, InputEvent* input_event) {
+    assert(gui);
+
+    Widget* widget = gui_widget_find_enabled(gui->widgets_dialog);
+    if(!widget) widget = gui_widget_find_enabled(gui->widgets_fs);
+    if(!widget) widget = gui_widget_find_enabled(gui->widgets);
+
+    if(widget) {
+        widget_input(widget, input_event);
+    }
+}
+
+Gui* gui_alloc() {
+    Gui* gui = furi_alloc(sizeof(Gui));
+
+    // Initialize widget arrays
+    WidgetArray_init(gui->widgets_status_bar);
+    WidgetArray_init(gui->widgets);
+    WidgetArray_init(gui->widgets_fs);
+    WidgetArray_init(gui->widgets_dialog);
+
+    // Event dispatcher
+    gui->event = gui_event_alloc();
+
+    // Drawing canvas api
+    gui->canvas_api = canvas_api_init();
+
+    gui->api.add_widget = gui_add_widget;
+
+    return gui;
+}
+
+void gui_task(void* p) {
+    Gui* gui = gui_alloc();
+    // Create FURI record
+    if(!furi_create("gui", gui)) {
+        printf("[gui_task] cannot create the gui record\n");
+        furiac_exit(NULL);
+    }
+
+    furiac_ready();
+
+    // Forever dispatch
+    while(1) {
+        GuiMessage message = gui_event_message_next(gui->event);
+        if(message.type == GuiMessageTypeRedraw) {
+            gui_redraw(gui);
+        } else if(message.type == GuiMessageTypeInput) {
+            gui_input(gui, &message.input);
+        }
+    }
+}

+ 18 - 0
applications/gui/gui.h

@@ -0,0 +1,18 @@
+#pragma once
+
+#include "widget.h"
+#include "canvas.h"
+
+typedef enum {
+    WidgetLayerStatusBar,
+    WidgetLayerMain,
+    WidgetLayerFullscreen,
+    WidgetLayerDialog
+} WidgetLayer;
+
+typedef struct Widget Widget;
+
+typedef struct GuiApi GuiApi;
+struct GuiApi {
+    void (*add_widget)(GuiApi* gui_api, Widget* widget, WidgetLayer layer);
+};

+ 73 - 0
applications/gui/gui_event.c

@@ -0,0 +1,73 @@
+#include "gui_event.h"
+
+#include <flipper.h>
+#include <assert.h>
+
+#define GUI_EVENT_MQUEUE_SIZE 8
+
+struct GuiEvent {
+    FuriRecordSubscriber* input_event_record;
+    osMessageQueueId_t mqueue;
+    osMutexId_t lock_mutex;
+};
+
+void gui_event_input_events_callback(const void* value, size_t size, void* ctx) {
+    assert(ctx);
+    GuiEvent* gui_event = ctx;
+
+    GuiMessage message;
+    message.type = GuiMessageTypeInput;
+    message.input = *(InputEvent*)value;
+
+    osMessageQueuePut(gui_event->mqueue, &message, 0, osWaitForever);
+}
+
+GuiEvent* gui_event_alloc() {
+    GuiEvent* gui_event = furi_alloc(sizeof(GuiEvent));
+    // Allocate message que
+    gui_event->mqueue = osMessageQueueNew(GUI_EVENT_MQUEUE_SIZE, sizeof(GuiMessage), NULL);
+    assert(gui_event->mqueue);
+
+    // Input
+    gui_event->input_event_record = furi_open_deprecated(
+        "input_events", false, false, gui_event_input_events_callback, NULL, gui_event);
+    assert(gui_event->input_event_record != NULL);
+    // Lock mutex
+    gui_event->lock_mutex = osMutexNew(NULL);
+    assert(gui_event->lock_mutex);
+    gui_event_lock(gui_event);
+
+    return gui_event;
+}
+
+void gui_event_free(GuiEvent* gui_event) {
+    assert(gui_event);
+    gui_event_unlock(gui_event);
+    assert(osMessageQueueDelete(gui_event->mqueue) == osOK);
+    free(gui_event);
+}
+
+void gui_event_lock(GuiEvent* gui_event) {
+    assert(gui_event);
+    assert(osMutexAcquire(gui_event->lock_mutex, osWaitForever) == osOK);
+}
+
+void gui_event_unlock(GuiEvent* gui_event) {
+    assert(gui_event);
+    assert(osMutexRelease(gui_event->lock_mutex) == osOK);
+}
+
+void gui_event_messsage_send(GuiEvent* gui_event, GuiMessage* message) {
+    assert(gui_event);
+    assert(message);
+    osMessageQueuePut(gui_event->mqueue, message, 0, 0);
+}
+
+GuiMessage gui_event_message_next(GuiEvent* gui_event) {
+    assert(gui_event);
+    GuiMessage message;
+    gui_event_unlock(gui_event);
+    assert(osMessageQueueGet(gui_event->mqueue, &message, NULL, osWaitForever) == osOK);
+    gui_event_lock(gui_event);
+    return message;
+}

+ 29 - 0
applications/gui/gui_event.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <stdint.h>
+#include <input/input.h>
+
+typedef enum {
+    GuiMessageTypeRedraw = 0x00,
+    GuiMessageTypeInput = 0x01,
+} GuiMessageType;
+
+typedef struct {
+    GuiMessageType type;
+    InputEvent input;
+    void* data;
+} GuiMessage;
+
+typedef struct GuiEvent GuiEvent;
+
+GuiEvent* gui_event_alloc();
+
+void gui_event_free(GuiEvent* gui_event);
+
+void gui_event_lock(GuiEvent* gui_event);
+
+void gui_event_unlock(GuiEvent* gui_event);
+
+void gui_event_messsage_send(GuiEvent* gui_event, GuiMessage* message);
+
+GuiMessage gui_event_message_next(GuiEvent* gui_event);

+ 5 - 0
applications/gui/gui_i.h

@@ -0,0 +1,5 @@
+#pragma once
+
+typedef struct Gui Gui;
+
+void gui_update(Gui* gui);

+ 114 - 0
applications/gui/u8g2_periphery.c

@@ -0,0 +1,114 @@
+#include "u8g2/u8g2.h"
+#include "flipper.h"
+
+extern SPI_HandleTypeDef hspi1;
+
+// TODO: fix log
+#ifdef DEBUG
+#undef DEBUG
+#endif
+
+uint8_t u8g2_gpio_and_delay_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) {
+    switch(msg) {
+    //Initialize SPI peripheral
+    case U8X8_MSG_GPIO_AND_DELAY_INIT:
+        /* HAL initialization contains all what we need so we can skip this part. */
+        break;
+
+    //Function which implements a delay, arg_int contains the amount of ms
+    case U8X8_MSG_DELAY_MILLI:
+        osDelay(arg_int);
+        break;
+
+    //Function which delays 10us
+    case U8X8_MSG_DELAY_10MICRO:
+        delay_us(10);
+        break;
+
+    //Function which delays 100ns
+    case U8X8_MSG_DELAY_100NANO:
+        asm("nop");
+        break;
+
+    // Function to define the logic level of the RESET line
+    case U8X8_MSG_GPIO_RESET:
+#ifdef DEBUG
+        fuprintf(log, "[u8g2] rst %d\n", arg_int);
+#endif
+
+        // TODO change it to FuriRecord pin
+        HAL_GPIO_WritePin(
+            DISPLAY_RST_GPIO_Port, DISPLAY_RST_Pin, arg_int ? GPIO_PIN_SET : GPIO_PIN_RESET);
+        break;
+
+    default:
+#ifdef DEBUG
+        fufuprintf(log, "[u8g2] unknown io %d\n", msg);
+#endif
+
+        return 0; //A message was received which is not implemented, return 0 to indicate an error
+    }
+
+    return 1; // command processed successfully.
+}
+
+uint8_t u8x8_hw_spi_stm32(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ptr) {
+    switch(msg) {
+    case U8X8_MSG_BYTE_SEND:
+#ifdef DEBUG
+        fuprintf(log, "[u8g2] send %d bytes %02X\n", arg_int, ((uint8_t*)arg_ptr)[0]);
+#endif
+
+        // TODO change it to FuriRecord SPI
+        HAL_SPI_Transmit(&hspi1, (uint8_t*)arg_ptr, arg_int, 10000);
+        break;
+
+    case U8X8_MSG_BYTE_SET_DC:
+#ifdef DEBUG
+        fuprintf(log, "[u8g2] dc %d\n", arg_int);
+#endif
+
+        // TODO change it to FuriRecord pin
+        HAL_GPIO_WritePin(
+            DISPLAY_DI_GPIO_Port, DISPLAY_DI_Pin, arg_int ? GPIO_PIN_SET : GPIO_PIN_RESET);
+        break;
+
+    case U8X8_MSG_BYTE_INIT:
+#ifdef DEBUG
+        fuprintf(log, "[u8g2] init\n");
+#endif
+
+        // TODO change it to FuriRecord pin
+        HAL_GPIO_WritePin(DISPLAY_CS_GPIO_Port, DISPLAY_CS_Pin, GPIO_PIN_RESET);
+        break;
+
+    case U8X8_MSG_BYTE_START_TRANSFER:
+#ifdef DEBUG
+        fuprintf(log, "[u8g2] start\n");
+#endif
+
+        // TODO change it to FuriRecord pin
+        HAL_GPIO_WritePin(DISPLAY_CS_GPIO_Port, DISPLAY_CS_Pin, GPIO_PIN_RESET);
+        asm("nop");
+        break;
+
+    case U8X8_MSG_BYTE_END_TRANSFER:
+#ifdef DEBUG
+        fuprintf(log, "[u8g2] end\n");
+#endif
+
+        asm("nop");
+        // TODO change it to FuriRecord pin
+        HAL_GPIO_WritePin(DISPLAY_CS_GPIO_Port, DISPLAY_CS_Pin, GPIO_PIN_SET);
+        break;
+
+    default:
+#ifdef DEBUG
+        fuprintf(log, "[u8g2] unknown xfer %d\n", msg);
+#endif
+
+        return 0;
+    }
+
+    return 1;
+}

+ 83 - 0
applications/gui/widget.c

@@ -0,0 +1,83 @@
+#include "widget.h"
+#include "widget_i.h"
+
+#include <cmsis_os.h>
+#include <flipper.h>
+
+#include "gui.h"
+#include "gui_i.h"
+
+// TODO add mutex to widget ops
+
+struct Widget {
+    Gui* gui;
+    bool is_enabled;
+    WidgetDrawCallback draw_callback;
+    void* draw_callback_context;
+    WidgetInputCallback input_callback;
+    void* input_callback_context;
+};
+
+Widget* widget_alloc(WidgetDrawCallback callback, void* callback_context) {
+    Widget* widget = furi_alloc(sizeof(Widget));
+    widget->is_enabled = true;
+    return widget;
+}
+
+void widget_free(Widget* widget) {
+    assert(widget);
+    assert(widget->gui == NULL);
+    free(widget);
+}
+
+void widget_enabled_set(Widget* widget, bool enabled) {
+    assert(widget);
+    widget->is_enabled = enabled;
+    widget_update(widget);
+}
+
+bool widget_is_enabled(Widget* widget) {
+    assert(widget);
+    return widget->is_enabled;
+}
+
+void widget_draw_callback_set(Widget* widget, WidgetDrawCallback callback, void* context) {
+    assert(widget);
+    widget->draw_callback = callback;
+    widget->draw_callback_context = context;
+}
+
+void widget_input_callback_set(Widget* widget, WidgetInputCallback callback, void* context) {
+    assert(widget);
+    widget->input_callback = callback;
+    widget->input_callback_context = context;
+}
+
+void widget_update(Widget* widget) {
+    assert(widget);
+    if(widget->gui) gui_update(widget->gui);
+}
+
+void widget_gui_set(Widget* widget, Gui* gui) {
+    assert(widget);
+    assert(gui);
+    widget->gui = gui;
+}
+
+void widget_draw(Widget* widget, CanvasApi* canvas_api) {
+    assert(widget);
+    assert(canvas_api);
+    assert(widget->gui);
+
+    if(widget->draw_callback) {
+        widget->draw_callback(canvas_api, widget->draw_callback_context);
+    }
+}
+
+void widget_input(Widget* widget, InputEvent* event) {
+    assert(widget);
+    assert(event);
+    assert(widget->gui);
+
+    if(widget->input_callback) widget->input_callback(event, widget->input_callback_context);
+}

+ 21 - 0
applications/gui/widget.h

@@ -0,0 +1,21 @@
+#pragma once
+
+#include <input/input.h>
+#include "canvas.h"
+
+typedef struct Widget Widget;
+
+typedef void (*WidgetDrawCallback)(CanvasApi* api, void* context);
+typedef void (*WidgetInputCallback)(InputEvent* event, void* context);
+
+Widget* widget_alloc();
+void widget_free(Widget* widget);
+
+void widget_enabled_set(Widget* widget, bool enabled);
+bool widget_is_enabled(Widget* widget);
+
+void widget_draw_callback_set(Widget* widget, WidgetDrawCallback callback, void* context);
+void widget_input_callback_set(Widget* widget, WidgetInputCallback callback, void* context);
+
+// emit update signal
+void widget_update(Widget* widget);

+ 9 - 0
applications/gui/widget_i.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#include "gui_i.h"
+
+void widget_gui_set(Widget* widget, Gui* gui);
+
+void widget_draw(Widget* widget, CanvasApi* canvas_api);
+
+void widget_input(Widget* widget, InputEvent* event);

+ 217 - 0
applications/menu/menu.c

@@ -0,0 +1,217 @@
+#include "menu.h"
+#include <cmsis_os.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <flipper_v2.h>
+#include <gui/gui.h>
+
+#include "menu_event.h"
+#include "menu_item.h"
+
+struct Menu {
+    MenuEvent* event;
+
+    // GUI
+    Widget* widget;
+
+    // State
+    MenuItem* root;
+    MenuItem* settings;
+    MenuItem* current;
+    uint32_t position;
+};
+
+void menu_widget_callback(CanvasApi* canvas, void* context);
+
+ValueMutex* menu_init() {
+    Menu* menu = furi_alloc(sizeof(Menu));
+
+    // Event dispatcher
+    menu->event = menu_event_alloc();
+
+    ValueMutex* menu_mutex = furi_alloc(sizeof(ValueMutex));
+    if(menu_mutex == NULL || !init_mutex(menu_mutex, menu, sizeof(Menu))) {
+        printf("[menu_task] cannot create menu mutex\n");
+        furiac_exit(NULL);
+    }
+
+    // Allocate and configure widget
+    menu->widget = widget_alloc();
+
+    // Open GUI and register fullscreen widget
+    GuiApi* gui = furi_open("gui");
+    assert(gui);
+    gui->add_widget(gui, menu->widget, WidgetLayerFullscreen);
+
+    widget_draw_callback_set(menu->widget, menu_widget_callback, menu_mutex);
+    widget_input_callback_set(menu->widget, menu_event_input_callback, menu->event);
+
+    return menu_mutex;
+}
+
+void menu_build_main(Menu* menu) {
+    assert(menu);
+    // Root point
+    menu->root = menu_item_alloc_menu(NULL, NULL);
+
+    menu->settings = menu_item_alloc_menu("Setting", NULL);
+    menu_item_subitem_add(menu->settings, menu_item_alloc_function("one", NULL, NULL, NULL));
+    menu_item_subitem_add(menu->settings, menu_item_alloc_function("two", NULL, NULL, NULL));
+    menu_item_subitem_add(menu->settings, menu_item_alloc_function("three", NULL, NULL, NULL));
+
+    menu_item_add(menu, menu->settings);
+}
+
+void menu_item_add(Menu* menu, MenuItem* item) {
+    menu_item_subitem_add(menu->root, item);
+}
+
+void menu_settings_item_add(Menu* menu, MenuItem* item) {
+    menu_item_subitem_add(menu->settings, item);
+}
+
+void menu_widget_callback(CanvasApi* canvas, void* context) {
+    assert(canvas);
+    assert(context);
+
+    Menu* menu = acquire_mutex((ValueMutex*)context, 100); // wait 10 ms to get mutex
+    if(menu == NULL) return; // redraw fail
+
+    if(!menu->current) {
+        canvas->clear(canvas);
+        canvas->set_color(canvas, ColorBlack);
+        canvas->set_font(canvas, FontPrimary);
+        canvas->draw_str(canvas, 2, 32, "Idle Screen");
+    } else {
+        MenuItemArray_t* items = menu_item_get_subitems(menu->current);
+        canvas->clear(canvas);
+        canvas->set_color(canvas, ColorBlack);
+        canvas->set_font(canvas, FontSecondary);
+        for(size_t i = 0; i < 5; i++) {
+            size_t shift_position = i + menu->position + MenuItemArray_size(*items) - 2;
+            shift_position = shift_position % (MenuItemArray_size(*items));
+            MenuItem* item = *MenuItemArray_get(*items, shift_position);
+            canvas->draw_str(canvas, 2, 12 * (i + 1), menu_item_get_label(item));
+        }
+    }
+
+    release_mutex((ValueMutex*)context, menu);
+}
+
+void menu_update(Menu* menu) {
+    assert(menu);
+
+    menu_event_activity_notify(menu->event);
+    widget_update(menu->widget);
+}
+
+void menu_up(Menu* menu) {
+    assert(menu);
+
+    MenuItemArray_t* items = menu_item_get_subitems(menu->current);
+    if(menu->position == 0) menu->position = MenuItemArray_size(*items);
+    menu->position--;
+    menu_update(menu);
+}
+
+void menu_down(Menu* menu) {
+    assert(menu);
+
+    MenuItemArray_t* items = menu_item_get_subitems(menu->current);
+    menu->position++;
+    menu->position = menu->position % MenuItemArray_size(*items);
+    menu_update(menu);
+}
+
+void menu_ok(Menu* menu) {
+    assert(menu);
+
+    if(!menu->current) {
+        menu->current = menu->root;
+        menu_update(menu);
+        return;
+    }
+
+    MenuItemArray_t* items = menu_item_get_subitems(menu->current);
+    MenuItem* item = *MenuItemArray_get(*items, menu->position);
+    MenuItemType type = menu_item_get_type(item);
+
+    if(type == MenuItemTypeMenu) {
+        menu->current = item;
+        menu->position = 0;
+        menu_update(menu);
+    } else if(type == MenuItemTypeFunction) {
+        menu_item_function_call(item);
+    }
+}
+
+void menu_back(Menu* menu) {
+    assert(menu);
+    MenuItem* parent = menu_item_get_parent(menu->current);
+    if(parent) {
+        menu->current = parent;
+        menu->position = 0;
+        menu_update(menu);
+    } else {
+        menu_exit(menu);
+    }
+}
+
+void menu_exit(Menu* menu) {
+    assert(menu);
+    menu->position = 0;
+    menu->current = NULL;
+    menu_update(menu);
+}
+
+void menu_task(void* p) {
+    ValueMutex* menu_mutex = menu_init();
+
+    MenuEvent* menu_event = NULL;
+    {
+        Menu* menu = acquire_mutex_block(menu_mutex);
+        assert(menu);
+
+        menu_build_main(menu);
+
+        // immutable thread-safe object
+        menu_event = menu->event;
+
+        release_mutex(menu_mutex, menu);
+    }
+
+    if(!furi_create("menu", menu_mutex)) {
+        printf("[menu_task] cannot create the menu record\n");
+        furiac_exit(NULL);
+    }
+
+    furiac_ready();
+
+    while(1) {
+        MenuMessage m = menu_event_next(menu_event);
+
+        Menu* menu = acquire_mutex_block(menu_mutex);
+
+        if(!menu->current && m.type != MenuMessageTypeOk) {
+        } else if(m.type == MenuMessageTypeUp) {
+            menu_up(menu);
+        } else if(m.type == MenuMessageTypeDown) {
+            menu_down(menu);
+        } else if(m.type == MenuMessageTypeOk) {
+            menu_ok(menu);
+        } else if(m.type == MenuMessageTypeLeft) {
+            menu_back(menu);
+        } else if(m.type == MenuMessageTypeRight) {
+            menu_ok(menu);
+        } else if(m.type == MenuMessageTypeBack) {
+            menu_back(menu);
+        } else if(m.type == MenuMessageTypeIdle) {
+            menu_exit(menu);
+        } else {
+            // TODO: fail somehow?
+        }
+
+        release_mutex(menu_mutex, menu);
+    }
+}

+ 19 - 0
applications/menu/menu.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include "menu/menu_item.h"
+
+typedef struct Menu Menu;
+typedef struct MenuItem MenuItem;
+
+// Add menu item to root menu
+void menu_item_add(Menu* menu, MenuItem* item);
+
+// Add menu item to settings menu
+void menu_settings_item_add(Menu* menu, MenuItem* item);
+
+// Menu controls
+void menu_up(Menu* menu);
+void menu_down(Menu* menu);
+void menu_ok(Menu* menu);
+void menu_back(Menu* menu);
+void menu_exit(Menu* menu);

+ 79 - 0
applications/menu/menu_event.c

@@ -0,0 +1,79 @@
+#include "menu_event.h"
+
+#include <cmsis_os.h>
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <flipper.h>
+
+#define MENU_MESSAGE_MQUEUE_SIZE 8
+
+struct MenuEvent {
+    osMessageQueueId_t mqueue;
+    osTimerId_t timeout_timer;
+};
+
+void MenuEventimeout_callback(void* arg) {
+    MenuEvent* menu_event = arg;
+    MenuMessage message;
+    message.type = MenuMessageTypeIdle;
+    osMessageQueuePut(menu_event->mqueue, &message, 0, osWaitForever);
+}
+
+MenuEvent* menu_event_alloc() {
+    MenuEvent* menu_event = furi_alloc(sizeof(MenuEvent));
+
+    menu_event->mqueue = osMessageQueueNew(MENU_MESSAGE_MQUEUE_SIZE, sizeof(MenuMessage), NULL);
+    assert(menu_event->mqueue);
+
+    menu_event->timeout_timer =
+        osTimerNew(MenuEventimeout_callback, osTimerOnce, menu_event, NULL);
+    assert(menu_event->timeout_timer);
+
+    return menu_event;
+}
+
+void menu_event_free(MenuEvent* menu_event) {
+    assert(menu_event);
+    assert(osMessageQueueDelete(menu_event->mqueue) == osOK);
+    free(menu_event);
+}
+
+void menu_event_activity_notify(MenuEvent* menu_event) {
+    assert(menu_event);
+    osTimerStart(menu_event->timeout_timer, 60000U); // 1m timeout, return to main
+}
+
+MenuMessage menu_event_next(MenuEvent* menu_event) {
+    assert(menu_event);
+    MenuMessage message;
+    while(osMessageQueueGet(menu_event->mqueue, &message, NULL, osWaitForever) != osOK) {
+    };
+    return message;
+}
+
+void menu_event_input_callback(InputEvent* input_event, void* context) {
+    MenuEvent* menu_event = context;
+    MenuMessage message;
+
+    if(!input_event->state) return;
+
+    if(input_event->input == InputUp) {
+        message.type = MenuMessageTypeUp;
+    } else if(input_event->input == InputDown) {
+        message.type = MenuMessageTypeDown;
+    } else if(input_event->input == InputRight) {
+        message.type = MenuMessageTypeRight;
+    } else if(input_event->input == InputLeft) {
+        message.type = MenuMessageTypeLeft;
+    } else if(input_event->input == InputOk) {
+        message.type = MenuMessageTypeOk;
+    } else if(input_event->input == InputBack) {
+        message.type = MenuMessageTypeBack;
+    } else {
+        message.type = MenuMessageTypeUnknown;
+    }
+
+    osMessageQueuePut(menu_event->mqueue, &message, 0, osWaitForever);
+}

+ 32 - 0
applications/menu/menu_event.h

@@ -0,0 +1,32 @@
+#pragma once
+
+#include <stdint.h>
+#include <input/input.h>
+
+typedef enum {
+    MenuMessageTypeUp = 0x00,
+    MenuMessageTypeDown = 0x01,
+    MenuMessageTypeLeft = 0x02,
+    MenuMessageTypeRight = 0x03,
+    MenuMessageTypeOk = 0x04,
+    MenuMessageTypeBack = 0x05,
+    MenuMessageTypeIdle = 0x06,
+    MenuMessageTypeUnknown = 0xFF,
+} MenuMessageType;
+
+typedef struct {
+    MenuMessageType type;
+    void* data;
+} MenuMessage;
+
+typedef struct MenuEvent MenuEvent;
+
+MenuEvent* menu_event_alloc();
+
+void menu_event_free(MenuEvent* menu_event);
+
+void menu_event_activity_notify(MenuEvent* menu_event);
+
+MenuMessage menu_event_next(MenuEvent* menu_event);
+
+void menu_event_input_callback(InputEvent* input_event, void* context);

+ 107 - 0
applications/menu/menu_item.c

@@ -0,0 +1,107 @@
+#include "menu_item.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <flipper.h>
+
+struct MenuItem {
+    MenuItemType type;
+    const char* label;
+    void* icon;
+    MenuItem* parent;
+    void* data;
+    MenuItemCallback callback;
+    void* callback_context;
+};
+
+MenuItem* menu_item_alloc() {
+    MenuItem* menu_item = furi_alloc(sizeof(MenuItem));
+    return menu_item;
+}
+
+MenuItem* menu_item_alloc_menu(const char* label, void* icon) {
+    MenuItem* menu_item = menu_item_alloc();
+
+    menu_item->type = MenuItemTypeMenu;
+    menu_item->label = label;
+    menu_item->icon = icon;
+
+    MenuItemArray_t* items = furi_alloc(sizeof(MenuItemArray_t));
+    MenuItemArray_init(*items);
+    menu_item->data = items;
+
+    return menu_item;
+}
+
+MenuItem*
+menu_item_alloc_function(const char* label, void* icon, MenuItemCallback callback, void* context) {
+    MenuItem* menu_item = menu_item_alloc();
+
+    menu_item->type = MenuItemTypeFunction;
+    menu_item->label = label;
+    menu_item->icon = icon;
+    menu_item->callback = callback;
+    menu_item->callback_context = context;
+
+    return menu_item;
+}
+
+void menu_item_release(MenuItem* menu_item) {
+    assert(menu_item);
+    if(menu_item->type == MenuItemTypeMenu) {
+        //TODO: iterate and release
+        free(menu_item->data);
+    }
+    free(menu_item);
+}
+
+MenuItem* menu_item_get_parent(MenuItem* menu_item) {
+    assert(menu_item);
+    return menu_item->parent;
+}
+
+void menu_item_subitem_add(MenuItem* menu_item, MenuItem* sub_item) {
+    assert(menu_item);
+    assert(menu_item->type == MenuItemTypeMenu);
+    MenuItemArray_t* items = menu_item->data;
+    sub_item->parent = menu_item;
+    MenuItemArray_push_back(*items, sub_item);
+}
+
+uint8_t menu_item_get_type(MenuItem* menu_item) {
+    assert(menu_item);
+    return menu_item->type;
+}
+
+void menu_item_set_label(MenuItem* menu_item, const char* label) {
+    assert(menu_item);
+    menu_item->label = label;
+}
+
+const char* menu_item_get_label(MenuItem* menu_item) {
+    assert(menu_item);
+    return menu_item->label;
+}
+
+void menu_item_set_icon(MenuItem* menu_item, void* icon) {
+    assert(menu_item);
+    menu_item->icon = icon;
+}
+
+void* menu_item_get_icon(MenuItem* menu_item) {
+    assert(menu_item);
+    return menu_item->icon;
+}
+
+MenuItemArray_t* menu_item_get_subitems(MenuItem* menu_item) {
+    assert(menu_item);
+    assert(menu_item->type == MenuItemTypeMenu);
+    return menu_item->data;
+}
+
+void menu_item_function_call(MenuItem* menu_item) {
+    assert(menu_item);
+    assert(menu_item->type == MenuItemTypeFunction);
+
+    if(menu_item->callback) menu_item->callback(menu_item->callback_context);
+}

+ 37 - 0
applications/menu/menu_item.h

@@ -0,0 +1,37 @@
+#pragma once
+
+#include <stdint.h>
+#include <m-array.h>
+
+typedef enum {
+    MenuItemTypeMenu = 0x00,
+    MenuItemTypeFunction = 0x01,
+} MenuItemType;
+
+typedef struct MenuItem MenuItem;
+typedef void (*MenuItemCallback)(void* context);
+
+ARRAY_DEF(MenuItemArray, MenuItem*, M_PTR_OPLIST);
+
+MenuItem* menu_item_alloc_menu(const char* label, void* icon);
+
+MenuItem*
+menu_item_alloc_function(const char* label, void* icon, MenuItemCallback callback, void* context);
+
+void menu_item_release(MenuItem* menu_item);
+
+MenuItem* menu_item_get_parent(MenuItem* menu_item);
+
+void menu_item_subitem_add(MenuItem* menu_item, MenuItem* sub_item);
+
+MenuItemType menu_item_get_type(MenuItem* menu_item);
+
+void menu_item_set_label(MenuItem* menu_item, const char* label);
+const char* menu_item_get_label(MenuItem* menu_item);
+
+void menu_item_set_icon(MenuItem* menu_item, void* icon);
+void* menu_item_get_icon(MenuItem* menu_item);
+
+MenuItemArray_t* menu_item_get_subitems(MenuItem* menu_item);
+
+void menu_item_function_call(MenuItem* menu_item);

+ 13 - 14
applications/startup.h

@@ -2,8 +2,6 @@
 
 #include "flipper.h"
 
-#define FURI_LIB (const char*[])
-
 #ifdef APP_TEST
 void flipper_test_app(void* p);
 #endif
@@ -26,6 +24,9 @@ void cc1101_workaround(void* p);
 
 void u8g2_qrcode(void* p);
 void fatfs_list(void* p);
+void gui_task(void* p);
+void backlight_control(void* p);
+void app_loader(void* p);
 
 const FlipperStartupApp FLIPPER_STARTUP[] = {
 #ifdef APP_DISPLAY
@@ -36,18 +37,20 @@ const FlipperStartupApp FLIPPER_STARTUP[] = {
     {.app = input_task, .name = "input_task", .libs = {0}},
 #endif
 
-// {.app = coreglitch_demo_0, .name = "coreglitch_demo_0", .libs = ""},
-
-#ifdef APP_TEST
-    {.app = flipper_test_app, .name = "test app", .libs = {0}},
+#ifdef APP_GUI
+    {.app = backlight_control, .name = "backlight_control", .libs = {1, FURI_LIB{"input_task"}}},
+    {.app = gui_task, .name = "gui_task", .libs = {0}},
 #endif
 
-#ifdef APP_EXAMPLE_BLINK
-    {.app = application_blink, .name = "blink", .libs = {0}},
+#ifdef APP_MENU
+    {.app = menu_task, .name = "menu_task", .libs = {1, FURI_LIB{"gui_task"}}},
+    {.app = app_loader, .name = "app_loader", .libs = {1, FURI_LIB{"menu_task"}}},
 #endif
 
-#ifdef APP_EXAMPLE_UART_WRITE
-    {.app = application_uart_write, .name = "uart write", .libs = {0}},
+// {.app = coreglitch_demo_0, .name = "coreglitch_demo_0", .libs = ""},
+
+#ifdef APP_TEST
+    {.app = flipper_test_app, .name = "test app", .libs = {0}},
 #endif
 
 #ifdef APP_EXAMPLE_IPC
@@ -55,10 +58,6 @@ const FlipperStartupApp FLIPPER_STARTUP[] = {
     {.app = application_ipc_widget, .name = "ipc widget", .libs = {0}},
 #endif
 
-#ifdef APP_EXAMPLE_INPUT_DUMP
-    {.app = application_input_dump, .name = "input dump", .libs = {1, FURI_LIB{"input_task"}}},
-#endif
-
 #ifdef APP_EXAMPLE_QRCODE
     {.app = u8g2_qrcode, .name = "u8g2_qrcode", .libs = {1, FURI_LIB{"display_u8g2"}}},
 #endif

+ 99 - 0
applications/tests/furi_memmgr_test.c

@@ -0,0 +1,99 @@
+#include "minunit.h"
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+// this test is not accurate, but gives a basic understanding
+// that memory management is working fine
+
+// do not include memmgr.h here
+// we also test that we are linking against stdlib
+extern size_t memmgr_get_free_heap(void);
+extern size_t memmgr_get_minimum_free_heap(void);
+
+// current heap managment realization consume:
+// X bytes after allocate and 0 bytes after allocate and free,
+// where X = sizeof(void*) + sizeof(size_t), look to BlockLink_t
+const size_t heap_overhead_max_size = sizeof(void*) + sizeof(size_t);
+
+bool heap_equal(size_t heap_size, size_t heap_size_old) {
+    // heap borders with overhead
+    const size_t heap_low = heap_size_old - heap_overhead_max_size;
+    const size_t heap_high = heap_size_old + heap_overhead_max_size;
+
+    // not extact, so we must test it against bigger numbers than "overhead size"
+    const bool result = ((heap_size >= heap_low) && (heap_size <= heap_high));
+
+    // debug allocation info
+    if(!result) {
+        printf("\n(hl: %zu) <= (p: %zu) <= (hh: %zu)\n", heap_low, heap_size, heap_high);
+    }
+
+    return result;
+}
+
+void test_furi_memmgr() {
+    size_t heap_size = 0;
+    size_t heap_size_old = 0;
+    const int alloc_size = 128;
+
+    void* ptr = NULL;
+    void* original_ptr = NULL;
+
+    // do not include furi memmgr.h case
+#ifdef FURI_MEMMGR_GUARD
+    mu_fail("do not link against furi memmgr.h");
+#endif
+
+    // allocate memory case
+    heap_size_old = memmgr_get_free_heap();
+    ptr = malloc(alloc_size);
+    heap_size = memmgr_get_free_heap();
+    mu_assert_pointers_not_eq(ptr, NULL);
+    mu_assert(heap_equal(heap_size, heap_size_old - alloc_size), "allocate failed");
+
+    // free memory case
+    heap_size_old = memmgr_get_free_heap();
+    free(ptr);
+    ptr = NULL;
+    heap_size = memmgr_get_free_heap();
+    mu_assert(heap_equal(heap_size, heap_size_old + alloc_size), "free failed");
+
+    // reallocate memory case
+
+    // get filled array with some data
+    original_ptr = malloc(alloc_size);
+    mu_assert_pointers_not_eq(original_ptr, NULL);
+    for(int i = 0; i < alloc_size; i++) {
+        *(unsigned char*)(original_ptr + i) = i;
+    }
+
+    // malloc array and copy data
+    ptr = malloc(alloc_size);
+    mu_assert_pointers_not_eq(ptr, NULL);
+    memcpy(ptr, original_ptr, alloc_size);
+
+    // reallocate array
+    heap_size_old = memmgr_get_free_heap();
+    ptr = realloc(ptr, alloc_size * 2);
+    heap_size = memmgr_get_free_heap();
+    mu_assert(heap_equal(heap_size, heap_size_old - alloc_size), "reallocate failed");
+    mu_assert_int_eq(memcmp(original_ptr, ptr, alloc_size), 0);
+    free(original_ptr);
+    free(ptr);
+
+    // allocate and zero-initialize array (calloc)
+    original_ptr = malloc(alloc_size);
+    mu_assert_pointers_not_eq(original_ptr, NULL);
+
+    for(int i = 0; i < alloc_size; i++) {
+        *(unsigned char*)(original_ptr + i) = 0;
+    }
+    heap_size_old = memmgr_get_free_heap();
+    ptr = calloc(1, alloc_size);
+    heap_size = memmgr_get_free_heap();
+    mu_assert(heap_equal(heap_size, heap_size_old - alloc_size), "callocate failed");
+    mu_assert_int_eq(memcmp(original_ptr, ptr, alloc_size), 0);
+
+    free(original_ptr);
+    free(ptr);
+}

+ 56 - 0
applications/tests/furi_pubsub_test.c

@@ -0,0 +1,56 @@
+#include <stdio.h>
+#include <string.h>
+#include "flipper_v2.h"
+#include "log.h"
+
+#include "minunit.h"
+
+const uint32_t context_value = 0xdeadbeef;
+const uint32_t notify_value_0 = 0x12345678;
+const uint32_t notify_value_1 = 0x11223344;
+
+uint32_t pubsub_value = 0;
+uint32_t pubsub_context_value = 0;
+
+void test_pubsub_handler(void* arg, void* ctx) {
+    pubsub_value = *(uint32_t*)arg;
+    pubsub_context_value = *(uint32_t*)ctx;
+}
+
+void test_furi_pubsub() {
+    bool result;
+    PubSub test_pubsub;
+    PubSubItem* test_pubsub_item;
+
+    // init pubsub case
+    result = init_pubsub(&test_pubsub);
+    mu_assert(result, "init pubsub failed");
+
+    // subscribe pubsub case
+    test_pubsub_item = subscribe_pubsub(&test_pubsub, test_pubsub_handler, (void*)&context_value);
+    mu_assert_pointers_not_eq(test_pubsub_item, NULL);
+
+    /// notify pubsub case
+    result = notify_pubsub(&test_pubsub, (void*)&notify_value_0);
+    mu_assert(result, "notify pubsub failed");
+    mu_assert_int_eq(pubsub_value, notify_value_0);
+    mu_assert_int_eq(pubsub_context_value, context_value);
+
+    // unsubscribe pubsub case
+    result = unsubscribe_pubsub(test_pubsub_item);
+    mu_assert(result, "unsubscribe pubsub failed");
+
+    result = unsubscribe_pubsub(test_pubsub_item);
+    mu_assert(!result, "unsubscribe pubsub not failed");
+
+    /// notify unsubscribed pubsub case
+    result = notify_pubsub(&test_pubsub, (void*)&notify_value_1);
+    mu_assert(result, "notify pubsub failed");
+    mu_assert_int_not_eq(pubsub_value, notify_value_1);
+
+    // delete pubsub case
+    result = delete_pubsub(&test_pubsub);
+    mu_assert(result, "unsubscribe pubsub failed");
+
+    // TODO test case that the pubsub_delete will remove pubsub from heap
+}

+ 0 - 194
applications/tests/furi_record_test.c

@@ -14,197 +14,3 @@ void test_furi_create_open() {
     void* record = furi_open("test/holding");
     mu_assert_pointers_eq(record, &test_data);
 }
-
-/*
-TEST: non-existent data
-1. Try to open non-existent record
-2. Check for NULL handler
-3. Try to write/read, get error
-
-TODO: implement this test
-*/
-bool test_furi_nonexistent_data() {
-    return true;
-}
-
-/*
-TEST: mute algorithm
-1. Create "parent" application:
-    1. Create pipe record
-    2. Open watch handler: no_mute=false, solo=false, subscribe to data.
-
-2. Open handler A: no_mute=false, solo=false, NULL subscriber. Subscribe to state.
-Try to write data to A and check subscriber.
-
-3. Open handler B: no_mute=true, solo=true, NULL subscriber.
-Check A state cb get FlipperRecordStateMute.
-Try to write data to A and check that subscriber get no data. (muted)
-Try to write data to B and check that subscriber get data.
-
-TODO: test 3 not pass beacuse state callback not implemented
-
-4. Open hadler C: no_mute=false, solo=true, NULL subscriber.
-Try to write data to A and check that subscriber get no data. (muted)
-Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
-Try to write data to C and check that subscriber get data.
-
-5. Open handler D: no_mute=false, solo=false, NULL subscriber.
-Try to write data to A and check that subscriber get no data. (muted)
-Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
-Try to write data to C and check that subscriber get data. (not muted because D open without solo)
-Try to write data to D and check that subscriber get data.
-
-6. Close C, close B.
-Check A state cb get FlipperRecordStateUnmute
-Try to write data to A and check that subscriber get data. (unmuted)
-Try to write data to D and check that subscriber get data.
-
-TODO: test 6 not pass beacuse cleanup is not implemented
-TODO: test 6 not pass because mute algorithm is unfinished.
-
-7. Exit "parent application"
-Check A state cb get FlipperRecordStateDeleted
-
-TODO: test 7 not pass beacuse cleanup is not implemented
-*/
-
-static uint8_t mute_last_value = 0;
-static FlipperRecordState mute_last_state = 255;
-
-void mute_record_cb(const void* value, size_t size, void* ctx) {
-    // hold value to static var
-    mute_last_value = *((uint8_t*)value);
-}
-
-void mute_record_state_cb(FlipperRecordState state, void* ctx) {
-    mute_last_state = state;
-}
-
-void furi_mute_parent_app(void* p) {
-    // 1. Create pipe record
-    if(!furi_create_deprecated("test/mute", NULL, 0)) {
-        printf("cannot create record\n");
-        furiac_exit(NULL);
-    }
-
-    // 2. Open watch handler: solo=false, no_mute=false, subscribe to data
-    FuriRecordSubscriber* watch_handler =
-        furi_open_deprecated("test/mute", false, false, mute_record_cb, NULL, NULL);
-    if(watch_handler == NULL) {
-        printf("cannot open watch handler\n");
-        furiac_exit(NULL);
-    }
-
-    while(1) {
-        // TODO we don't have thread sleep
-        delay(100000);
-    }
-}
-
-bool test_furi_mute_algorithm() {
-    // 1. Create "parent" application:
-    FuriApp* parent_app = furiac_start(furi_mute_parent_app, "parent app", NULL);
-
-    delay(2); // wait creating record
-
-    // 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state.
-    FuriRecordSubscriber* handler_a =
-        furi_open_deprecated("test/mute", false, false, NULL, mute_record_state_cb, NULL);
-    if(handler_a == NULL) {
-        printf("cannot open handler A\n");
-        return false;
-    }
-
-    uint8_t test_counter = 1;
-
-    // Try to write data to A and check subscriber
-    if(!furi_write(handler_a, &test_counter, sizeof(uint8_t))) {
-        printf("write to A failed\n");
-        return false;
-    }
-
-    if(mute_last_value != test_counter) {
-        printf("value A mismatch: %d vs %d\n", mute_last_value, test_counter);
-        return false;
-    }
-
-    // 3. Open handler B: solo=true, no_mute=true, NULL subscriber.
-    FuriRecordSubscriber* handler_b =
-        furi_open_deprecated("test/mute", true, true, NULL, NULL, NULL);
-    if(handler_b == NULL) {
-        printf("cannot open handler B\n");
-        return false;
-    }
-
-    // Check A state cb get FlipperRecordStateMute.
-    if(mute_last_state != FlipperRecordStateMute) {
-        printf("A state is not FlipperRecordStateMute: %d\n", mute_last_state);
-        return false;
-    }
-
-    test_counter = 2;
-
-    // Try to write data to A and check that subscriber get no data. (muted)
-    if(furi_write(handler_a, &test_counter, sizeof(uint8_t))) {
-        printf("A not muted\n");
-        return false;
-    }
-
-    if(mute_last_value == test_counter) {
-        printf("value A must be muted\n");
-        return false;
-    }
-
-    test_counter = 3;
-
-    // Try to write data to B and check that subscriber get data.
-    if(!furi_write(handler_b, &test_counter, sizeof(uint8_t))) {
-        printf("write to B failed\n");
-        return false;
-    }
-
-    if(mute_last_value != test_counter) {
-        printf("value B mismatch: %d vs %d\n", mute_last_value, test_counter);
-        return false;
-    }
-
-    // 4. Open hadler C: solo=true, no_mute=false, NULL subscriber.
-    FuriRecordSubscriber* handler_c =
-        furi_open_deprecated("test/mute", true, false, NULL, NULL, NULL);
-    if(handler_c == NULL) {
-        printf("cannot open handler C\n");
-        return false;
-    }
-
-    // TODO: Try to write data to A and check that subscriber get no data. (muted)
-    // TODO: Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
-    // TODO: Try to write data to C and check that subscriber get data.
-
-    // 5. Open handler D: solo=false, no_mute=false, NULL subscriber.
-    FuriRecordSubscriber* handler_d =
-        furi_open_deprecated("test/mute", false, false, NULL, NULL, NULL);
-    if(handler_d == NULL) {
-        printf("cannot open handler D\n");
-        return false;
-    }
-
-    // TODO: Try to write data to A and check that subscriber get no data. (muted)
-    // TODO: Try to write data to B and check that subscriber get data. (not muted because open with no_mute)
-    // TODO: Try to write data to C and check that subscriber get data. (not muted because D open without solo)
-    // TODO: Try to write data to D and check that subscriber get data.
-
-    // 6. Close C, close B.
-    // TODO: Check A state cb get FlipperRecordStateUnmute
-    // TODO: Try to write data to A and check that subscriber get data. (unmuted)
-    // TODO: Try to write data to D and check that subscriber get data.
-
-    // 7. Exit "parent application"
-    if(!furiac_kill(parent_app)) {
-        printf("kill parent_app fail\n");
-        return false;
-    }
-
-    // TODO: Check A state cb get FlipperRecordStateDeleted
-
-    return true;
-}

+ 16 - 9
applications/tests/minunit_test.c

@@ -7,13 +7,13 @@
 bool test_furi_ac_create_kill();
 bool test_furi_ac_switch_exit();
 
-bool test_furi_nonexistent_data();
-bool test_furi_mute_algorithm();
-
 // v2 tests
 void test_furi_create_open();
 void test_furi_valuemutex();
 void test_furi_concurrent_access();
+void test_furi_pubsub();
+
+void test_furi_memmgr();
 
 static int foo = 0;
 
@@ -37,10 +37,6 @@ MU_TEST(mu_test_furi_ac_switch_exit) {
     mu_assert_int_eq(test_furi_ac_switch_exit(), true);
 }
 
-MU_TEST(mu_test_furi_nonexistent_data) {
-    mu_assert_int_eq(test_furi_nonexistent_data(), true);
-}
-
 // v2 tests
 MU_TEST(mu_test_furi_create_open) {
     test_furi_create_open();
@@ -54,6 +50,16 @@ MU_TEST(mu_test_furi_concurrent_access) {
     test_furi_concurrent_access();
 }
 
+MU_TEST(mu_test_furi_pubsub) {
+    test_furi_pubsub();
+}
+
+MU_TEST(mu_test_furi_memmgr) {
+    // this test is not accurate, but gives a basic understanding
+    // that memory management is working fine
+    test_furi_memmgr();
+}
+
 MU_TEST_SUITE(test_suite) {
     MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
 
@@ -61,12 +67,13 @@ MU_TEST_SUITE(test_suite) {
     MU_RUN_TEST(mu_test_furi_ac_create_kill);
     MU_RUN_TEST(mu_test_furi_ac_switch_exit);
 
-    MU_RUN_TEST(mu_test_furi_nonexistent_data);
-
     // v2 tests
     MU_RUN_TEST(mu_test_furi_create_open);
     MU_RUN_TEST(mu_test_furi_valuemutex);
     MU_RUN_TEST(mu_test_furi_concurrent_access);
+    MU_RUN_TEST(mu_test_furi_pubsub);
+
+    MU_RUN_TEST(mu_test_furi_memmgr);
 }
 
 int run_minunit() {

+ 51 - 0
core/api-basic/memmgr.c

@@ -0,0 +1,51 @@
+#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;
+}
+
+size_t memmgr_get_free_heap(void) {
+    return xPortGetFreeHeapSize();
+}
+
+size_t memmgr_get_minimum_free_heap(void) {
+    return xPortGetMinimumEverFreeHeapSize();
+}

+ 13 - 0
core/api-basic/memmgr.h

@@ -0,0 +1,13 @@
+#pragma once
+#include <stddef.h>
+
+// 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);

+ 90 - 0
core/api-basic/pubsub.c

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

+ 0 - 48
core/api-basic/pubsub.c.unimplemented

@@ -1,48 +0,0 @@
-#include "pubsub.h"
-
-void init_pubsub(PubSub* pubsub) {
-    pubsub->count = 0;
-
-    for(size_t i = 0; i < NUM_OF_CALLBACKS; i++) {
-        pubsub->items[i].
-    }
-}
-
-// TODO add mutex to reconfigurate PubSub
-PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx) {
-    if(pubsub->count >= NUM_OF_CALLBACKS) return NULL;
-
-    pubsub->count++;
-    PubSubItem* current = pubsub->items[pubsub->count];
-    
-    current->cb = cb;
-    currrnt->ctx = ctx;
-
-    pubsub->ids[pubsub->count].self = pubsub;
-    pubsub->ids[pubsub->count].item = current;
-
-    flapp_on_exit(unsubscribe_pubsub, &(pubsub->ids[pubsub->count]));
-    
-    return current;
-}
-
-void unsubscribe_pubsub(PubSubId* pubsub_id) {
-    // TODO: add, and rearrange all items to keep subscribers item continuous
-    // TODO: keep ids link actual
-    // TODO: also add mutex on every pubsub changes
-
-    // trivial implementation for NUM_OF_CALLBACKS = 1
-    if(NUM_OF_CALLBACKS != 1) return;
-
-    if(pubsub_id != NULL || pubsub_id->self != NULL || pubsub_id->item != NULL) return;
-
-    pubsub_id->self->count = 0;
-    pubsub_id->item = NULL;
-}
-
-void notify_pubsub(PubSub* pubsub, void* arg) {
-    // iterate over subscribers
-    for(size_t i = 0; i < pubsub->count; i++) {
-        pubsub->items[i]->cb(arg, pubsub->items[i]->ctx);
-    }
-}

+ 19 - 15
core/api-basic/pubsub.h

@@ -1,6 +1,7 @@
 #pragma once
 
-#include "flipper.h"
+#include "flipper_v2.h"
+#include "m-list.h"
 
 /*
 == PubSub ==
@@ -11,43 +12,46 @@ and also subscriber can set `void*` context pointer that pass into
 callback (you can see callback signature below).
 */
 
-typedef void(PubSubCallback*)(void*, void*);
+typedef void (*PubSubCallback)(void*, void*);
+typedef struct PubSubType PubSub;
 
 typedef struct {
     PubSubCallback cb;
     void* ctx;
+    PubSub* self;
 } PubSubItem;
 
-typedef struct {
-    PubSub* self;
-    PubSubItem* item;
-} PubSubId;
+LIST_DEF(list_pubsub_cb, PubSubItem, M_POD_OPLIST);
 
-typedef struct {
-    PubSubItem items[NUM_OF_CALLBACKS];
-    PubSubId ids[NUM_OF_CALLBACKS]; ///< permanent links to item
-    size_t count; ///< count of callbacks
-} PubSub;
+struct PubSubType {
+    list_pubsub_cb_t items;
+    osMutexId_t mutex;
+};
 
 /*
 To create PubSub you should create PubSub instance and call `init_pubsub`.
 */
-void init_pubsub(PubSub* 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.
 */
-PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx);
+PubSubItem* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx);
 
 /*
 Use `unsubscribe_pubsub` to unregister callback.
 */
-void unsubscribe_pubsub(PubSubId* pubsub_id);
+bool unsubscribe_pubsub(PubSubItem* pubsub_id);
 
 /*
 Use `notify_pubsub` to notify subscribers.
 */
-void notify_pubsub(PubSub* pubsub, void* arg);
+bool notify_pubsub(PubSub* pubsub, void* arg);
 
 /*
 

+ 4 - 0
core/flipper.h

@@ -16,6 +16,8 @@ extern "C" {
 }
 #endif
 
+#include <stdio.h>
+
 // Arduino defines
 
 #define pinMode app_gpio_init
@@ -32,3 +34,5 @@ extern "C" {
 #define HIGH true
 
 void set_exitcode(uint32_t _exitcode);
+
+#define FURI_LIB (const char*[])

+ 5 - 1
core/flipper_v2.h

@@ -1,7 +1,11 @@
 #pragma once
 
+#include "flipper.h"
+
 #include "api-basic/furi.h"
 //#include "api-basic/flapp.h"
 #include "cmsis_os2.h"
 #include "api-basic/valuemutex.h"
-//#include "api-basic/pubsub.h"
+#include "api-basic/pubsub.h"
+
+#include "api-basic/memmgr.h"

+ 11 - 0
core/furi-deprecated.h

@@ -1,15 +1,26 @@
 #pragma once
 
 #include "cmsis_os.h"
+
 #ifdef HAVE_FREERTOS
 #include <semphr.h>
 #endif
+
 #include <stdbool.h>
+#include <stdlib.h>
 #include <stdint.h>
+#include <string.h>
+#include <assert.h>
 
 #define MAX_TASK_RECORDS 8
 #define MAX_RECORD_SUBSCRIBERS 8
 
+inline static void* furi_alloc(size_t size) {
+    void* p = malloc(size);
+    assert(p);
+    return memset(p, 0, size);
+}
+
 /// application is just a function
 typedef void (*FlipperApplication)(void*);
 

+ 6 - 5
docker/Dockerfile

@@ -21,6 +21,7 @@ RUN apt update && \
         libstdc++-arm-none-eabi-newlib \
         libclang-10-dev \
         clang-format-10 \
+        git \
         && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 
 RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --profile=minimal --target thumbv7em-none-eabi thumbv7em-none-eabihf && \
@@ -31,11 +32,11 @@ RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --profile=minimal --target thum
 RUN apt update && \
     apt install -y --no-install-recommends \
     gcc build-essential cmake libusb-1.0 libusb-1.0-0-dev libgtk-3-dev pandoc \
-    && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
-RUN wget https://github.com/stlink-org/stlink/archive/v1.5.1.zip
-RUN unzip v1.5.1.zip
-RUN cd stlink-1.5.1 && make clean && make release
-RUN cd stlink-1.5.1/build/Release && make install && ldconfig
+    && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
+    wget https://github.com/stlink-org/stlink/archive/v1.5.1.zip && \
+    unzip v1.5.1.zip && \
+    cd stlink-1.5.1 && make clean && make release && \
+    cd build/Release && make install && ldconfig
 
 COPY entrypoint.sh syntax_check.sh /
 

+ 2 - 2
docker/syntax_check.sh

@@ -26,7 +26,7 @@ rust_syntax_rc=$?
 
 if [[ $rust_syntax_rc -eq 0 ]] && [[ $c_syntax_rc -eq 0 ]]; then
     echo "Code looks fine for me!"
-    exit 1
+    exit 0
 fi
 
 read -p "Do you want fix syntax? (y/n): " confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1
@@ -38,4 +38,4 @@ cd $PROJECT_DIR
 # We use root in container and clang-format rewriting files. We'll need change owner to original
 local_user=$(stat -c '%u' .clang-format)
 $CLANG_FORMAT_BIN -style=file -i $C_FILES
-chown $local_user $C_FILES
+chown $local_user $C_FILES

+ 14 - 1
firmware/targets/f2/Inc/FreeRTOSConfig.h

@@ -51,6 +51,10 @@
 #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
   #include <stdint.h>
   extern uint32_t SystemCoreClock;
+/* USER CODE BEGIN 0 */
+  extern void configureTimerForRunTimeStats(void);
+  extern unsigned long getRunTimeCounterValue(void);
+/* USER CODE END 0 */
 #endif
 #define configENABLE_FPU                         0
 #define configENABLE_MPU                         0
@@ -64,15 +68,18 @@
 #define configTICK_RATE_HZ                       ((TickType_t)1000)
 #define configMAX_PRIORITIES                     ( 56 )
 #define configMINIMAL_STACK_SIZE                 ((uint16_t)128)
-#define configTOTAL_HEAP_SIZE                    ((size_t)8192)
+#define configTOTAL_HEAP_SIZE                    ((size_t)40960)
 #define configMAX_TASK_NAME_LEN                  ( 16 )
+#define configGENERATE_RUN_TIME_STATS            1
 #define configUSE_TRACE_FACILITY                 1
 #define configUSE_16_BIT_TICKS                   0
 #define configUSE_MUTEXES                        1
 #define configQUEUE_REGISTRY_SIZE                8
+#define configCHECK_FOR_STACK_OVERFLOW           1
 #define configUSE_RECURSIVE_MUTEXES              1
 #define configUSE_COUNTING_SEMAPHORES            1
 #define configUSE_PORT_OPTIMISED_TASK_SELECTION  0
+#define configRECORD_STACK_HIGH_ADDRESS          1
 /* USER CODE BEGIN MESSAGE_BUFFER_LENGTH_TYPE */
 /* Defaults to size_t for backward compatibility, but can be changed
    if lengths will always be less than the number of bytes in a size_t. */
@@ -152,6 +159,12 @@ standard names. */
 
 #define xPortSysTickHandler SysTick_Handler
 
+/* USER CODE BEGIN 2 */
+/* Definitions needed when configGENERATE_RUN_TIME_STATS is on */
+#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS configureTimerForRunTimeStats
+#define portGET_RUN_TIME_COUNTER_VALUE getRunTimeCounterValue
+/* USER CODE END 2 */
+
 /* USER CODE BEGIN Defines */
 /* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */
 /* USER CODE END Defines */

+ 2 - 2
firmware/targets/f2/Inc/usbd_conf.h

@@ -92,10 +92,10 @@
 /* Memory management macros */
 
 /** Alias for memory allocation. */
-#define USBD_malloc         malloc
+#define USBD_malloc         USBD_static_malloc
 
 /** Alias for memory release. */
-#define USBD_free          free
+#define USBD_free          USBD_static_free
 
 /** Alias for memory set. */
 #define USBD_memset         memset

+ 204 - 0
firmware/targets/f2/STM32L476RGTx_FLASH_NO_BOOT.ld

@@ -0,0 +1,204 @@
+/*
+******************************************************************************
+**
+
+**  File        : LinkerScript.ld
+**
+**  Author		: Auto-generated by System Workbench for STM32
+**
+**  Abstract    : Linker script for STM32L476RGTx series
+**                1024Kbytes FLASH and 128Kbytes RAM
+**
+**                Set heap size, stack size and stack location according
+**                to application requirements.
+**
+**                Set memory bank area and size if external memory is used.
+**
+**  Target      : STMicroelectronics STM32
+**
+**  Distribution: The file is distributed “as is,” without any warranty
+**                of any kind.
+**
+*****************************************************************************
+** @attention
+**
+** <h2><center>&copy; COPYRIGHT(c) 2019 STMicroelectronics</center></h2>
+**
+** Redistribution and use in source and binary forms, with or without modification,
+** are permitted provided that the following conditions are met:
+**   1. Redistributions of source code must retain the above copyright notice,
+**      this list of conditions and the following disclaimer.
+**   2. Redistributions in binary form must reproduce the above copyright notice,
+**      this list of conditions and the following disclaimer in the documentation
+**      and/or other materials provided with the distribution.
+**   3. Neither the name of STMicroelectronics nor the names of its contributors
+**      may be used to endorse or promote products derived from this software
+**      without specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+**
+*****************************************************************************
+*/
+
+/* Entry Point */
+ENTRY(Reset_Handler)
+
+/* Highest address of the user mode stack */
+_estack = 0x20018000;    /* end of RAM */
+/* Generate a link error if heap and stack don't fit into RAM */
+_Min_Heap_Size = 0x200;      /* required amount of heap  */
+_Min_Stack_Size = 0x400; /* required amount of stack */
+
+/* Specify the memory areas */
+MEMORY
+{
+RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 96K
+RAM2 (xrw)      : ORIGIN = 0x10000000, LENGTH = 32K
+FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 1024K
+}
+
+/* Define output sections */
+SECTIONS
+{
+  /* The startup code goes first into FLASH */
+  .isr_vector :
+  {
+    . = ALIGN(8);
+    KEEP(*(.isr_vector)) /* Startup code */
+    . = ALIGN(8);
+  } >FLASH
+
+  /* The program code and other data goes into FLASH */
+  .text :
+  {
+    . = ALIGN(8);
+    *(.text)           /* .text sections (code) */
+    *(.text*)          /* .text* sections (code) */
+    *(.glue_7)         /* glue arm to thumb code */
+    *(.glue_7t)        /* glue thumb to arm code */
+    *(.eh_frame)
+
+    KEEP (*(.init))
+    KEEP (*(.fini))
+
+    . = ALIGN(8);
+    _etext = .;        /* define a global symbols at end of code */
+  } >FLASH
+
+  /* Constant data goes into FLASH */
+  .rodata :
+  {
+    . = ALIGN(8);
+    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
+    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
+    . = ALIGN(8);
+  } >FLASH
+
+  .ARM.extab   : 
+  { 
+  . = ALIGN(8);
+  *(.ARM.extab* .gnu.linkonce.armextab.*)
+  . = ALIGN(8);
+  } >FLASH
+  .ARM : {
+	. = ALIGN(8);
+    __exidx_start = .;
+    *(.ARM.exidx*)
+    __exidx_end = .;
+	. = ALIGN(8);
+  } >FLASH
+
+  .preinit_array     :
+  {
+	. = ALIGN(8);
+    PROVIDE_HIDDEN (__preinit_array_start = .);
+    KEEP (*(.preinit_array*))
+    PROVIDE_HIDDEN (__preinit_array_end = .);
+	. = ALIGN(8);
+  } >FLASH
+  
+  .init_array :
+  {
+	. = ALIGN(8);
+    PROVIDE_HIDDEN (__init_array_start = .);
+    KEEP (*(SORT(.init_array.*)))
+    KEEP (*(.init_array*))
+    PROVIDE_HIDDEN (__init_array_end = .);
+	. = ALIGN(8);
+  } >FLASH
+  .fini_array :
+  {
+	. = ALIGN(8);
+    PROVIDE_HIDDEN (__fini_array_start = .);
+    KEEP (*(SORT(.fini_array.*)))
+    KEEP (*(.fini_array*))
+    PROVIDE_HIDDEN (__fini_array_end = .);
+	. = ALIGN(8);
+  } >FLASH
+
+  /* used by the startup to initialize data */
+  _sidata = LOADADDR(.data);
+
+  /* Initialized data sections goes into RAM, load LMA copy after code */
+  .data : 
+  {
+    . = ALIGN(8);
+    _sdata = .;        /* create a global symbol at data start */
+    *(.data)           /* .data sections */
+    *(.data*)          /* .data* sections */
+
+    . = ALIGN(8);
+    _edata = .;        /* define a global symbol at data end */
+  } >RAM AT> FLASH
+
+  
+  /* Uninitialized data section */
+  . = ALIGN(4);
+  .bss :
+  {
+    /* This is used by the startup in order to initialize the .bss secion */
+    _sbss = .;         /* define a global symbol at bss start */
+    __bss_start__ = _sbss;
+    *(.bss)
+    *(.bss*)
+    *(COMMON)
+
+    . = ALIGN(4);
+    _ebss = .;         /* define a global symbol at bss end */
+    __bss_end__ = _ebss;
+  } >RAM
+
+  /* User_heap_stack section, used to check that there is enough RAM left */
+  ._user_heap_stack :
+  {
+    . = ALIGN(8);
+    PROVIDE ( end = . );
+    PROVIDE ( _end = . );
+    . = . + _Min_Heap_Size;
+    . = . + _Min_Stack_Size;
+    . = ALIGN(8);
+  } >RAM
+
+  
+
+  /* Remove information from the standard libraries */
+  /DISCARD/ :
+  {
+    libc.a ( * )
+    libm.a ( * )
+    libgcc.a ( * )
+  }
+
+  .ARM.attributes 0 : { *(.ARM.attributes) }
+}
+
+

+ 27 - 1
firmware/targets/f2/Src/freertos.c

@@ -26,7 +26,7 @@
 
 /* Private includes ----------------------------------------------------------*/
 /* USER CODE BEGIN Includes */
-
+#include <stdlib.h>
 /* USER CODE END Includes */
 
 /* Private typedef -----------------------------------------------------------*/
@@ -75,7 +75,23 @@ extern void MX_USB_DEVICE_Init(void);
 void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
 
 /* Hook prototypes */
+void configureTimerForRunTimeStats(void);
+unsigned long getRunTimeCounterValue(void);
 void vApplicationIdleHook(void);
+void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName);
+
+/* USER CODE BEGIN 1 */
+/* Functions needed when configGENERATE_RUN_TIME_STATS is on */
+__weak void configureTimerForRunTimeStats(void)
+{
+
+}
+
+__weak unsigned long getRunTimeCounterValue(void)
+{
+return 0;
+}
+/* USER CODE END 1 */
 
 /* USER CODE BEGIN 2 */
 __weak void vApplicationIdleHook( void )
@@ -92,6 +108,16 @@ __weak void vApplicationIdleHook( void )
 }
 /* USER CODE END 2 */
 
+/* USER CODE BEGIN 4 */
+__weak void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName)
+{
+   /* Run time stack overflow checking is performed if
+   configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook function is
+   called if a stack overflow is detected. */
+    exit(255);
+}
+/* USER CODE END 4 */
+
 /**
   * @brief  FreeRTOS initialization
   * @param  None

+ 3 - 1
firmware/targets/f2/Src/stm32l4xx_it.c

@@ -86,7 +86,9 @@ void NMI_Handler(void)
 void HardFault_Handler(void)
 {
   /* USER CODE BEGIN HardFault_IRQn 0 */
-
+  if ((*(volatile uint32_t *)CoreDebug_BASE) & (1 << 0)) {
+    __asm("bkpt 1");
+  }
   /* USER CODE END HardFault_IRQn 0 */
   while (1)
   {

+ 14 - 7
firmware/targets/f2/Src/system_stm32l4xx.c

@@ -123,25 +123,32 @@
 /*!< Uncomment the following line if you need to relocate your vector Table in
      Internal SRAM. */
 /* #define VECT_TAB_SRAM */
+
+#ifdef NO_BOOTLOADER
+#define VECT_TAB_OFFSET  0x0000 /*!< Vector Table base offset field.
+                                   This value must be a multiple of 0x200. */
+#else
 #define VECT_TAB_OFFSET  0x8000 /*!< Vector Table base offset field.
                                    This value must be a multiple of 0x200. */
-/******************************************************************************/
-/**
+#endif
+
+    /******************************************************************************/
+    /**
   * @}
   */
 
-/** @addtogroup STM32L4xx_System_Private_Macros
+    /** @addtogroup STM32L4xx_System_Private_Macros
   * @{
   */
 
-/**
+    /**
   * @}
   */
 
-/** @addtogroup STM32L4xx_System_Private_Variables
+    /** @addtogroup STM32L4xx_System_Private_Variables
   * @{
   */
-  /* The SystemCoreClock variable is updated in three ways:
+    /* The SystemCoreClock variable is updated in three ways:
       1) by calling CMSIS function SystemCoreClockUpdate()
       2) by calling HAL API function HAL_RCC_GetHCLKFreq()
       3) each time HAL_RCC_ClockConfig() is called to configure the system clock frequency
@@ -149,7 +156,7 @@
                is no need to call the 2 first functions listed above, since SystemCoreClock
                variable is updated automatically.
   */
-  uint32_t SystemCoreClock = 4000000U;
+    uint32_t SystemCoreClock = 4000000U;
 
   const uint8_t  AHBPrescTable[16] = {0U, 0U, 0U, 0U, 0U, 0U, 0U, 0U, 1U, 2U, 3U, 4U, 6U, 7U, 8U, 9U};
   const uint8_t  APBPrescTable[8] =  {0U, 0U, 0U, 0U, 1U, 2U, 3U, 4U};

+ 6 - 3
firmware/targets/f2/cube.ioc

@@ -97,6 +97,7 @@ PB14.GPIOParameters=GPIO_Speed,PinState,GPIO_Label,GPIO_ModeDefaultOutputPP
 NVIC.EXTI2_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true
 RCC.PLLPoutputFreq_Value=18285714.285714287
 RCC.APB1TimFreq_Value=64000000
+FREERTOS.configGENERATE_RUN_TIME_STATS=1
 NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
 RCC.LPUART1Freq_Value=64000000
 USB_OTG_FS.IPParameters=VirtualMode
@@ -123,6 +124,7 @@ PC13.Locked=true
 ADC1.OffsetNumber-0\#ChannelRegularConversion=ADC_OFFSET_NONE
 PC13.Signal=GPXTI13
 RCC.SWPMI1Freq_Value=64000000
+FREERTOS.configCHECK_FOR_STACK_OVERFLOW=1
 PB8.GPIO_PuPd=GPIO_PULLDOWN
 PC6.Signal=GPIO_Output
 PC2.Signal=GPXTI2
@@ -182,8 +184,8 @@ SPI1.Mode=SPI_MODE_MASTER
 Mcu.Pin39=PA15 (JTDI)
 PB3\ (JTDO-TRACESWO).Mode=TX_Only_Simplex_Unidirect_Master
 RCC.RNGFreq_Value=48000000
-VP_ADC1_TempSens_Input.Signal=ADC1_TempSens_Input
 PC2.GPIOParameters=GPIO_PuPd,GPIO_Label,GPIO_ModeDefaultEXTI
+VP_ADC1_TempSens_Input.Signal=ADC1_TempSens_Input
 Mcu.Pin30=PC8
 PA1.GPIO_Label=BUTTON_DOWN
 Mcu.Pin33=PA9
@@ -310,7 +312,7 @@ PB9.GPIO_ModeDefaultEXTI=GPIO_MODE_IT_RISING_FALLING
 Mcu.Pin7=PC2
 Mcu.Pin8=PC3
 Mcu.Pin9=PA0
-FREERTOS.IPParameters=Tasks01,configTOTAL_HEAP_SIZE,HEAP_NUMBER,configUSE_TIMERS,configUSE_IDLE_HOOK,FootprintOK
+FREERTOS.IPParameters=Tasks01,configTOTAL_HEAP_SIZE,HEAP_NUMBER,configUSE_TIMERS,configUSE_IDLE_HOOK,FootprintOK,configCHECK_FOR_STACK_OVERFLOW,configRECORD_STACK_HIGH_ADDRESS,configGENERATE_RUN_TIME_STATS
 RCC.AHBFreq_Value=64000000
 Mcu.Pin0=PC13
 SPI3.DataSize=SPI_DATASIZE_8BIT
@@ -342,7 +344,7 @@ PB7.Signal=GPIO_Input
 PB8.Locked=true
 PB6.GPIOParameters=GPIO_Speed,GPIO_Label
 PB0.Locked=true
-FREERTOS.configTOTAL_HEAP_SIZE=8192
+FREERTOS.configTOTAL_HEAP_SIZE=40960
 VP_COMP1_VS_VREFINT12.Mode=VREFINT_12
 ProjectManager.ProjectName=cube
 PB1.PinState=GPIO_PIN_SET
@@ -405,6 +407,7 @@ PA9.Mode=Asynchronous
 PB4\ (NJTRST).GPIOParameters=GPIO_PuPd,GPIO_Label,GPIO_ModeDefaultEXTI
 NVIC.TIM8_CC_IRQn=true\:5\:0\:false\:false\:true\:true\:true\:true
 PB14.PinState=GPIO_PIN_SET
+FREERTOS.configRECORD_STACK_HIGH_ADDRESS=1
 ProjectManager.TargetToolchain=Makefile
 PB10.GPIO_Label=DISPLAY_RST
 PB7.GPIOParameters=GPIO_Label

+ 15 - 1
firmware/targets/f2/target.mk

@@ -5,6 +5,15 @@ FW_ADDRESS		= 0x08008000
 OS_OFFSET		= 0x00008000
 FLASH_ADDRESS	= 0x08008000
 
+NO_BOOTLOADER ?= 0
+ifeq ($(NO_BOOTLOADER), 1)
+BOOT_ADDRESS	= 0x08000000
+FW_ADDRESS		= 0x08000000
+OS_OFFSET		= 0x00000000
+FLASH_ADDRESS	= 0x08000000
+CFLAGS			+= -DNO_BOOTLOADER
+endif
+
 BOOT_CFLAGS		= -DBOOT_ADDRESS=$(BOOT_ADDRESS) -DFW_ADDRESS=$(FW_ADDRESS) -DOS_OFFSET=$(OS_OFFSET)
 MCU_FLAGS		= -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard
 
@@ -48,7 +57,7 @@ C_SOURCES		+= \
 	$(CUBE_DIR)/Middlewares/Third_Party/FreeRTOS/Source/tasks.c \
 	$(CUBE_DIR)/Middlewares/Third_Party/FreeRTOS/Source/timers.c \
 	$(CUBE_DIR)/Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os2.c \
-	$(CUBE_DIR)/Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_1.c \
+	$(CUBE_DIR)/Middlewares/Third_Party/FreeRTOS/Source/portable/MemMang/heap_4.c \
 	$(CUBE_DIR)/Middlewares/Third_Party/FreeRTOS/Source/portable/GCC/ARM_CM4F/port.c \
 	$(CUBE_DIR)/Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_core.c \
 	$(CUBE_DIR)/Middlewares/ST/STM32_USB_Device_Library/Core/Src/usbd_ctlreq.c \
@@ -65,7 +74,12 @@ CFLAGS			+= \
 	-DHAVE_FREERTOS \
 	-DBUTON_INVERT=false \
 	-DDEBUG_UART=huart1
+
+ifeq ($(NO_BOOTLOADER), 1)
+LDFLAGS			+= -T$(TARGET_DIR)/STM32L476RGTx_FLASH_NO_BOOT.ld
+else
 LDFLAGS			+= -T$(TARGET_DIR)/STM32L476RGTx_FLASH.ld
+endif
 
 CFLAGS += \
 	-I$(TARGET_DIR)/Inc \

+ 1 - 0
firmware/targets/local/Inc/cmsis_os.h

@@ -94,5 +94,6 @@ typedef enum {
 
 osStatus_t osMutexAcquire (osMutexId_t mutex_id, uint32_t timeout);
 osStatus_t osMutexRelease (osMutexId_t mutex_id);
+osStatus_t osMutexDelete (osMutexId_t mutex_id);
 
 #define osWaitForever portMAX_DELAY

+ 389 - 0
firmware/targets/local/Src/heap_4.c

@@ -0,0 +1,389 @@
+/*
+ * 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 "heap.h"
+
+osMutexId_t heap_managment_mutex = NULL;
+
+/* 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)
+
+/* Allocate the memory for the heap. */
+#if(configAPPLICATION_ALLOCATED_HEAP == 1)
+/* The application writer has already defined the array used for the RTOS
+	heap - probably so it can be placed in a special segment or address. */
+extern uint8_t ucHeap[configTOTAL_HEAP_SIZE];
+#else
+static uint8_t ucHeap[configTOTAL_HEAP_SIZE];
+#endif /* configAPPLICATION_ALLOCATED_HEAP */
+
+/* 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);
+
+// this function is not thread-safe, so it must be called in single thread context
+bool 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;
+
+/*-----------------------------------------------------------*/
+
+void* pvPortMalloc(size_t xWantedSize) {
+    BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
+    void* pvReturn = NULL;
+
+    acquire_memalloc_mutex();
+    {
+        /* If this is the first call to malloc then the heap will require
+		initialisation to setup the list of free blocks. */
+        if(pxEnd == NULL) {
+            prvHeapInit();
+        } else {
+            mtCOVERAGE_TEST_MARKER();
+        }
+
+        /* 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;
+                } else {
+                    mtCOVERAGE_TEST_MARKER();
+                }
+            } else {
+                mtCOVERAGE_TEST_MARKER();
+            }
+        } else {
+            mtCOVERAGE_TEST_MARKER();
+        }
+
+        traceMALLOC(pvReturn, xWantedSize);
+    }
+    release_memalloc_mutex();
+
+#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);
+    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;
+
+                acquire_memalloc_mutex();
+                {
+                    /* Add this block to the list of free blocks. */
+                    xFreeBytesRemaining += pxLink->xBlockSize;
+                    traceFREE(pv, pxLink->xBlockSize);
+                    prvInsertBlockIntoFreeList(((BlockLink_t*)pxLink));
+                }
+                release_memalloc_mutex();
+            } else {
+                mtCOVERAGE_TEST_MARKER();
+            }
+        } else {
+            mtCOVERAGE_TEST_MARKER();
+        }
+    }
+}
+/*-----------------------------------------------------------*/
+
+size_t xPortGetFreeHeapSize(void) {
+    return xFreeBytesRemaining;
+}
+/*-----------------------------------------------------------*/
+
+size_t xPortGetMinimumEverFreeHeapSize(void) {
+    return xMinimumEverFreeBytesRemaining;
+}
+/*-----------------------------------------------------------*/
+
+void vPortInitialiseBlocks(void) {
+    /* This just exists to keep the linker quiet. */
+}
+/*-----------------------------------------------------------*/
+
+bool prvHeapInit(void) {
+    BlockLink_t* pxFirstFreeBlock;
+    uint8_t* pucAlignedHeap;
+    size_t uxAddress;
+    size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
+
+    /* 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);
+
+    // now we can use malloc, so we init heap managment mutex
+    const osMutexAttr_t heap_managment_mutext_attr = {
+        .name = NULL, .attr_bits = 0, .cb_mem = NULL, .cb_size = 0U};
+
+    heap_managment_mutex = osMutexNew(&heap_managment_mutext_attr);
+
+    return heap_managment_mutex != NULL;
+}
+/*-----------------------------------------------------------*/
+
+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();
+    }
+}
+
+/* 
+at first run (heap init) it not work properly and prvHeapInit
+is not thread-safe. But then we init mutex or die
+*/
+void acquire_memalloc_mutex() {
+    if(heap_managment_mutex != NULL) {
+        osMutexAcquire(heap_managment_mutex, osWaitForever);
+    }
+}
+
+void release_memalloc_mutex() {
+    if(heap_managment_mutex != NULL) {
+        osMutexRelease(heap_managment_mutex);
+    }
+}

+ 12 - 0
firmware/targets/local/Src/lo_os.c

@@ -253,3 +253,15 @@ osStatus_t osMutexRelease (osMutexId_t mutex_id) {
         return osError;
     }
 }
+
+osStatus_t osMutexDelete (osMutexId_t mutex_id) {
+    osMutexRelease(mutex_id);
+    
+    int res = 0;
+    if((res = pthread_mutex_destroy(&mutex_id->mutex)) == 0) {
+        return osOK;
+    } else {
+        printf("res = %d\n", res);
+        return osError;
+    }
+}

+ 8 - 0
firmware/targets/local/Src/main.c

@@ -1,3 +1,6 @@
+#include "heap.h"
+#include "errno.h"
+
 /*
 Flipper devices inc.
 
@@ -7,5 +10,10 @@ Local fw build entry point.
 int app();
 
 int main() {
+    // this function is not thread-safe, so it must be called in single thread context
+    if(!prvHeapInit()){
+        return ENOMEM;
+    }
+
     return app();
 }

+ 37 - 0
firmware/targets/local/fatfs/heap.h

@@ -0,0 +1,37 @@
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <cmsis_os.h>
+
+#define configTOTAL_HEAP_SIZE ((size_t)(8192 * 16))
+#define configAPPLICATION_ALLOCATED_HEAP 0
+#define portBYTE_ALIGNMENT 8
+
+#if portBYTE_ALIGNMENT == 8
+#define portBYTE_ALIGNMENT_MASK (0x0007)
+#endif
+
+/* No test marker by default. */
+#ifndef mtCOVERAGE_TEST_MARKER
+#define mtCOVERAGE_TEST_MARKER()
+#endif
+
+/* No tracing by default. */
+#ifndef traceMALLOC
+#define traceMALLOC(pvReturn, xWantedSize)
+#endif
+
+/* No tracing by default. */
+#ifndef traceFREE
+#define traceFREE(pvReturn, xBlockSize)
+#endif
+
+/* No assert by default. */
+#ifndef configASSERT
+#define configASSERT(var)
+#endif
+
+bool prvHeapInit(void);
+
+void acquire_memalloc_mutex();
+void release_memalloc_mutex();

+ 3 - 0
firmware/targets/local/target.mk

@@ -15,5 +15,8 @@ LDFLAGS += -pthread
 CFLAGS += -I$(TARGET_DIR)/fatfs
 C_SOURCES += $(TARGET_DIR)/fatfs/syscall.c
 
+# memory manager
+C_SOURCES += $(TARGET_DIR)/Src/heap_4.c
+
 run: all
 	$(OBJ_DIR)/$(PROJECT).elf

+ 1 - 0
lib/lib.mk

@@ -1,5 +1,6 @@
 LIB_DIR			= $(PROJECT_ROOT)/lib
 
+# TODO: some places use lib/header.h includes, is it ok?
 CFLAGS			+= -I$(LIB_DIR)
 
 # Mlib containers

+ 20 - 13
make/rules.mk

@@ -14,12 +14,19 @@ DEPS = $(OBJECTS:.o=.d)
 $(shell mkdir -p $(OBJ_DIR))
 
 BUILD_FLAGS_SHELL=\
-	echo -n "$(CFLAGS)" > $(OBJ_DIR)/BUILD_FLAGS.tmp; \
-	diff $(OBJ_DIR)/BUILD_FLAGS $(OBJ_DIR)/BUILD_FLAGS.tmp > /dev/null \
+	echo "$(CFLAGS)" > $(OBJ_DIR)/BUILD_FLAGS.tmp; \
+	diff $(OBJ_DIR)/BUILD_FLAGS $(OBJ_DIR)/BUILD_FLAGS.tmp 2>/dev/null \
 		&& ( echo "CFLAGS ok"; rm $(OBJ_DIR)/BUILD_FLAGS.tmp) \
 		|| ( echo "CFLAGS has been changed"; mv $(OBJ_DIR)/BUILD_FLAGS.tmp $(OBJ_DIR)/BUILD_FLAGS )
 $(info $(shell $(BUILD_FLAGS_SHELL)))
 
+CHECK_AND_REINIT_SUBMODULES_SHELL=\
+	if git submodule status | egrep -q '^[-]|^[+]' ; then \
+		echo "INFO: Need to reinitialize git submodules"; \
+		git submodule update --init; \
+	fi
+$(info $(shell $(CHECK_AND_REINIT_SUBMODULES_SHELL)))
+
 all: $(OBJ_DIR)/$(PROJECT).elf $(OBJ_DIR)/$(PROJECT).hex $(OBJ_DIR)/$(PROJECT).bin
 
 $(OBJ_DIR)/$(PROJECT).elf: $(OBJECTS)
@@ -60,25 +67,18 @@ flash: $(OBJ_DIR)/flash
 upload: $(OBJ_DIR)/upload
 
 debug: flash
-	set -m; st-util -n --semihosting & echo $$! > st-util.PID
-	arm-none-eabi-gdb \
+	set -m; st-util -n --semihosting & echo $$! > $(OBJ_DIR)/st-util.PID
+	arm-none-eabi-gdb-py \
 		-ex "target extended-remote 127.0.0.1:4242" \
 		-ex "set confirm off" \
 		$(OBJ_DIR)/$(PROJECT).elf; \
-	kill `cat st-util.PID`; \
-	rm st-util.PID
+	kill `cat $(OBJ_DIR)/st-util.PID`; \
+	rm $(OBJ_DIR)/st-util.PID
 
 clean:
 	@echo "\tCLEAN\t"
 	@$(RM) $(OBJ_DIR)/*
 
-.PHONY: check-and-reinit-submodules
-check-and-reinit-submodules:
-	@if git submodule status | egrep -q '^[-]|^[+]' ; then \
-		echo "INFO: Need to reinitialize git submodules"; \
-		git submodule update --init; \
-	fi
-
 z: clean
 	$(MAKE) all
 
@@ -88,4 +88,11 @@ zz: clean
 zzz: clean
 	$(MAKE) debug
 
+FORMAT_SOURCES := $(shell find ../applications -iname "*.h" -o -iname "*.c" -o -iname "*.cpp")
+FORMAT_SOURCES += $(shell find ../core -iname "*.h" -o -iname "*.c" -o -iname "*.cpp")
+
+format:
+	@echo "Formatting sources with clang-format"
+	@clang-format -style=file -i $(FORMAT_SOURCES)
+
 -include $(DEPS)