Browse Source

Core api concept (#144)

* add input debounce code from old fw

* exampl of input api

* change input API to get/release

* revert input API to read

* pointer instead of instance

* add input API description

* add display API

* rewrite display names

* migrate to valuemanager

* add LED API

* add closing brakets

* add sound api

* fix led api

* basic api

* rename API pages

* change pubsub implementation

* move FURI AC -> flapp, add valuemutex example, add valuemanager implementation

* pubsub usage example

* user led example

* update example

* simplify input

* add composed display

* add SPI/GPIO and CC1101 bus

* change cc1101 api

* spi api and devices

* spi api and devices

* move SPI to page, add GPIO

* not block pin open

* backlight API and more

* add minunit tests

* fix logging

* ignore unexisting time service on embedded targets

* fix warning, issue with printf

* Deprecate furi_open and furi_close (#167)

Rename existing furi_open and furi_close to deprecated version

* add exitcode

* migrate to printf

* indicate test by leds

* add testing description

* rename furi.h

* wip basic api

* add valuemutex, pubsub, split files

* add value expanders

* value mutex realization and tests

* valuemutex test added to makefile

* do not build unimplemented files

* fix build furmware target f2

* redesigned minunit tests to allow testing in separate files

* test file for valuemutex minunit testing

* minunit partial test valuemutex

* local cmsis_os2 mutex bindings

* implement furi open/create, tests

* migrate concurrent_access to ValueMutex

* add spi header

* Lib: add mlib submodule.

Co-authored-by: rusdacent <rusdacentx0x08@gmail.com>
Co-authored-by: DrZlo13 <who.just.the.doctor@gmail.com>
coreglitch 5 years ago
parent
commit
942bbfaefe
51 changed files with 1872 additions and 690 deletions
  1. 3 0
      .gitmodules
  2. 1 0
      applications/applications.mk
  3. 2 1
      applications/coreglitch_demo_0/coreglitch_demo_0.c
  4. 2 2
      applications/display-u8g2/display-u8g2.c
  5. 2 2
      applications/examples/fatfs_list.c
  6. 2 2
      applications/examples/input_dump.c
  7. 4 3
      applications/examples/ipc.c
  8. 1 1
      applications/examples/u8g2_example.c
  9. 1 1
      applications/examples/u8g2_qrcode.c
  10. 5 5
      applications/input/input.c
  11. 1 1
      applications/startup.h
  12. 17 241
      applications/tests/furi_record_test.c
  13. 123 0
      applications/tests/furi_valuemutex_test.c
  14. 22 21
      applications/tests/minunit_test.c
  15. 47 0
      core/api-basic/flapp.h
  16. 14 0
      core/api-basic/furi.c
  17. 26 0
      core/api-basic/furi.h
  18. 48 0
      core/api-basic/pubsub.c.unimplemented
  19. 83 0
      core/api-basic/pubsub.h
  20. 24 0
      core/api-basic/value-expanders.c.unimplemented
  21. 56 0
      core/api-basic/value-expanders.h
  22. 52 0
      core/api-basic/valuemutex.c
  23. 123 0
      core/api-basic/valuemutex.h
  24. 133 0
      core/api-hal/api-spi.h
  25. 0 1
      core/app.cpp
  26. 1 0
      core/core.mk
  27. 2 1
      core/flipper.h
  28. 7 0
      core/flipper_v2.h
  29. 4 5
      core/furi-deprecated.c
  30. 2 2
      core/furi-deprecated.h
  31. 1 2
      core/furi_ac.c
  32. 2 2
      core/log.c
  33. 1 1
      core/log.h
  34. 4 4
      core/tty_uart.c
  35. 28 0
      firmware/targets/local/Inc/cmsis_os.h
  36. 1 0
      firmware/targets/local/Inc/cmsis_os2.h
  37. 23 0
      firmware/targets/local/Src/lo_os.c
  38. 4 0
      lib/lib.mk
  39. 1 0
      lib/mlib
  40. 27 61
      wiki/fw/Core-API.md
  41. 0 20
      wiki/fw/FURI.md
  42. 0 61
      wiki/fw/api/API-Display.md
  43. 0 124
      wiki/fw/api/API-LED.md
  44. 0 122
      wiki/fw/api/API-Sound.md
  45. 100 0
      wiki/fw/api/Backlight-API.md
  46. 402 0
      wiki/fw/api/Basic-API.md
  47. 68 0
      wiki/fw/api/Display-API.md
  48. 158 0
      wiki/fw/api/HAL-API.md
  49. 4 4
      wiki/fw/api/Input-API.md
  50. 110 0
      wiki/fw/api/LED-API.md
  51. 130 0
      wiki/fw/api/SPI-Devices-API.md

+ 3 - 0
.gitmodules

@@ -1,3 +1,6 @@
 [submodule "lib/STM32CubeL4"]
 	path = lib/STM32CubeL4
 	url = https://github.com/STMicroelectronics/STM32CubeL4.git
+[submodule "lib/mlib"]
+	path = lib/mlib
+	url = https://github.com/P-p-H-d/mlib.git

+ 1 - 0
applications/applications.mk

@@ -16,6 +16,7 @@ C_SOURCES	+= $(APP_DIR)/tests/furiac_test.c
 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
 endif
 
 APP_EXAMPLE_BLINK ?= 0

+ 2 - 1
applications/coreglitch_demo_0/coreglitch_demo_0.c

@@ -9,7 +9,8 @@ void coreglitch_demo_0(void* p) {
     fuprintf(log, "coreglitch demo!\n");
 
     // open record
-    FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL);
+    FuriRecordSubscriber* fb_record =
+        furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL);
 
     if(fb_record == NULL) {
         fuprintf(log, "[widget] cannot create fb record\n");

+ 2 - 2
applications/display-u8g2/display-u8g2.c

@@ -143,7 +143,7 @@ void display_u8g2(void* p) {
         &_u8g2); // send init sequence to the display, display is in sleep mode after this
     u8g2_SetContrast(&_u8g2, 36);
 
-    if(!furi_create("u8g2_fb", (void*)&_u8g2, sizeof(_u8g2))) {
+    if(!furi_create_deprecated("u8g2_fb", (void*)&_u8g2, sizeof(_u8g2))) {
         fuprintf(log, "[display_u8g2] cannot create fb record\n");
         furiac_exit(NULL);
     }
@@ -162,7 +162,7 @@ void display_u8g2(void* p) {
 
     // subscribe to record. ctx will be passed to handle_fb_change
     FuriRecordSubscriber* fb_record =
-        furi_open("u8g2_fb", false, false, handle_fb_change, NULL, &ctx);
+        furi_open_deprecated("u8g2_fb", false, false, handle_fb_change, NULL, &ctx);
 
     if(fb_record == NULL) {
         fuprintf(log, "[display] cannot open fb record\n");

+ 2 - 2
applications/examples/fatfs_list.c

@@ -49,14 +49,14 @@ void fatfs_list(void* p) {
 
     furi_log = get_default_log();
 
-    FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL);
+    FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL);
     if(fb_record == NULL) {
         fuprintf(furi_log, "[widget][fatfs_list] cannot create fb record\n");
         furiac_exit(NULL);
     }
 
     FuriRecordSubscriber* event_record =
-        furi_open("input_events", false, false, event_cb, NULL, event_queue);
+        furi_open_deprecated("input_events", false, false, event_cb, NULL, event_queue);
     if(event_record == NULL) {
         fuprintf(furi_log, "[widget][fatfs_list] cannot register input_events callback\n");
         furiac_exit(NULL);

+ 2 - 2
applications/examples/input_dump.c

@@ -16,9 +16,9 @@ static void event_cb(const void* value, size_t size, void* ctx) {
 void application_input_dump(void* p) {
     // open record
     FuriRecordSubscriber* state_record =
-        furi_open("input_state", false, false, state_cb, NULL, NULL);
+        furi_open_deprecated("input_state", false, false, state_cb, NULL, NULL);
     FuriRecordSubscriber* event_record =
-        furi_open("input_events", false, false, event_cb, NULL, NULL);
+        furi_open_deprecated("input_events", false, false, event_cb, NULL, NULL);
 
     for(;;) {
         delay(100);

+ 4 - 3
applications/examples/ipc.c

@@ -60,7 +60,7 @@ void application_ipc_display(void* p) {
     }
 
     // create record
-    if(!furi_create("test_fb", (void*)_framebuffer, FB_SIZE)) {
+    if(!furi_create_deprecated("test_fb", (void*)_framebuffer, FB_SIZE)) {
         fuprintf(log, "[display] cannot create fb record\n");
         furiac_exit(NULL);
     }
@@ -79,7 +79,7 @@ void application_ipc_display(void* p) {
 
     // subscribe to record. ctx will be passed to handle_fb_change
     FuriRecordSubscriber* fb_record =
-        furi_open("test_fb", false, false, handle_fb_change, NULL, &ctx);
+        furi_open_deprecated("test_fb", false, false, handle_fb_change, NULL, &ctx);
 
     if(fb_record == NULL) {
         fuprintf(log, "[display] cannot open fb record\n");
@@ -124,7 +124,8 @@ void application_ipc_widget(void* p) {
     FuriRecordSubscriber* log = get_default_log();
 
     // open record
-    FuriRecordSubscriber* fb_record = furi_open("test_fb", false, false, NULL, NULL, NULL);
+    FuriRecordSubscriber* fb_record =
+        furi_open_deprecated("test_fb", false, false, NULL, NULL, NULL);
 
     if(fb_record == NULL) {
         fuprintf(log, "[widget] cannot create fb record\n");

+ 1 - 1
applications/examples/u8g2_example.c

@@ -5,7 +5,7 @@ void u8g2_example(void* p) {
     FuriRecordSubscriber* log = get_default_log();
 
     // open record
-    FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL);
+    FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL);
 
     if(fb_record == NULL) {
         fuprintf(log, "[widget] cannot create fb record\n");

+ 1 - 1
applications/examples/u8g2_qrcode.c

@@ -14,7 +14,7 @@ void u8g2_qrcode(void* p) {
     FuriRecordSubscriber* log = get_default_log();
 
     // open record
-    FuriRecordSubscriber* fb_record = furi_open("u8g2_fb", false, false, NULL, NULL, NULL);
+    FuriRecordSubscriber* fb_record = furi_open_deprecated("u8g2_fb", false, false, NULL, NULL, NULL);
 
     // Allocate a chunk of memory to store the QR code
     // https://github.com/ricmoo/QRCode

+ 5 - 5
applications/input/input.c

@@ -1,7 +1,7 @@
 #include <input/input.h>
 #include <input_priv.h>
 #include <stdio.h>
-#include <furi.h>
+#include <flipper.h>
 
 static volatile bool initialized = false;
 static SemaphoreHandle_t event;
@@ -16,25 +16,25 @@ void input_task(void* p) {
 
     event = xSemaphoreCreateCountingStatic(1, 0, &event_semaphore);
 
-    if(!furi_create("input_state", (void*)&input_state, sizeof(input_state))) {
+    if(!furi_create_deprecated("input_state", (void*)&input_state, sizeof(input_state))) {
         printf("[input_task] cannot create the input_state record\n");
         furiac_exit(NULL);
     }
 
     FuriRecordSubscriber* input_state_record =
-        furi_open("input_state", false, false, NULL, NULL, NULL);
+        furi_open_deprecated("input_state", false, false, NULL, NULL, NULL);
     if(input_state_record == NULL) {
         printf("[input_task] cannot open the input_state record\n");
         furiac_exit(NULL);
     }
 
-    if(!furi_create("input_events", NULL, 0)) {
+    if(!furi_create_deprecated("input_events", NULL, 0)) {
         printf("[input_task] cannot create the input_events record\n");
         furiac_exit(NULL);
     }
 
     FuriRecordSubscriber* input_events_record =
-        furi_open("input_events", false, false, NULL, NULL, NULL);
+        furi_open_deprecated("input_events", false, false, NULL, NULL, NULL);
     if(input_events_record == NULL) {
         printf("[input_task] cannot open the input_events record\n");
         furiac_exit(NULL);

+ 1 - 1
applications/startup.h

@@ -1,6 +1,6 @@
 #pragma once
 
-#include "furi.h"
+#include "flipper.h"
 
 #define FURI_LIB (const char*[])
 

+ 17 - 241
applications/tests/furi_record_test.c

@@ -1,245 +1,18 @@
 #include <stdio.h>
 #include <string.h>
 #include "flipper.h"
+#include "flipper_v2.h"
 #include "log.h"
+#include "minunit.h"
 
-/*
-TEST: pipe record
-
-1. create pipe record
-2. Open/subscribe to it 
-3. write data
-4. check that subscriber get data
-5. try to read, get error
-6. close record
-7. try to write, get error
-*/
-
-static uint8_t pipe_record_value = 0;
-
-void pipe_record_cb(const void* value, size_t size, void* ctx) {
-    // hold value to static var
-    pipe_record_value = *((uint8_t*)value);
-}
-
-bool test_furi_pipe_record() {
-    // 1. create pipe record
-    if(!furi_create("test/pipe", NULL, 0)) {
-        printf("cannot create record\n");
-        return false;
-    }
-
-    // 2. Open/subscribe to it
-    FuriRecordSubscriber* pipe_record =
-        furi_open("test/pipe", false, false, pipe_record_cb, NULL, NULL);
-    if(pipe_record == NULL) {
-        printf("cannot open record\n");
-        return false;
-    }
-
-    const uint8_t WRITE_VALUE = 1;
-    // 3. write data
-    if(!furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) {
-        printf("cannot write to record\n");
-        return false;
-    }
-
-    // 4. check that subscriber get data
-    if(pipe_record_value != WRITE_VALUE) {
-        printf("wrong value (get %d, write %d)\n", pipe_record_value, WRITE_VALUE);
-        return false;
-    }
-
-    // 5. try to read, get error
-    uint8_t read_value = 0;
-    if(furi_read(pipe_record, &read_value, sizeof(uint8_t))) {
-        printf("reading from pipe record not allowed\n");
-        return false;
-    }
-
-    // 6. close record
-    furi_close(pipe_record);
-
-    // 7. try to write, get error
-    if(furi_write(pipe_record, &WRITE_VALUE, sizeof(uint8_t))) {
-        printf("writing to closed record not allowed\n");
-        return false;
-    }
-
-    return true;
-}
-
-/*
-TEST: holding data
-
-1. Create holding record
-2. Open/Subscribe on it
-3. Write data
-4. Check that subscriber get data
-5. Read and check data
-6. Try to write/read wrong size of data
-*/
-
-static uint8_t holding_record_value = 0;
-
-void holding_record_cb(const void* value, size_t size, void* ctx) {
-    // hold value to static var
-    holding_record_value = *((uint8_t*)value);
-}
-
-bool test_furi_holding_data() {
-    // 1. Create holding record
-    uint8_t holder = 0;
-    if(!furi_create("test/holding", (void*)&holder, sizeof(holder))) {
-        printf("cannot create record\n");
-        return false;
-    }
-
-    // 2. Open/Subscribe on it
-    FuriRecordSubscriber* holding_record =
-        furi_open("test/holding", false, false, holding_record_cb, NULL, NULL);
-    if(holding_record == NULL) {
-        printf("cannot open record\n");
-        return false;
-    }
-
-    const uint8_t WRITE_VALUE = 1;
-    // 3. write data
-    if(!furi_write(holding_record, &WRITE_VALUE, sizeof(uint8_t))) {
-        printf("cannot write to record\n");
-        return false;
-    }
-
-    // 4. check that subscriber get data
-    if(holding_record_value != WRITE_VALUE) {
-        printf("wrong sub value (get %d, write %d)\n", holding_record_value, WRITE_VALUE);
-        return false;
-    }
-
-    // 5. Read and check data
-    uint8_t read_value = 0;
-    if(!furi_read(holding_record, &read_value, sizeof(uint8_t))) {
-        printf("cannot read from record\n");
-        return false;
-    }
-
-    if(read_value != WRITE_VALUE) {
-        printf("wrong read value (get %d, write %d)\n", read_value, WRITE_VALUE);
-        return false;
-    }
-
-    // 6. Try to write/read wrong size of data
-    if(furi_write(holding_record, &WRITE_VALUE, 100)) {
-        printf("overflowed write not allowed\n");
-        return false;
-    }
-
-    if(furi_read(holding_record, &read_value, 100)) {
-        printf("overflowed read not allowed\n");
-        return false;
-    }
-
-    return true;
-}
-
-/*
-TEST: concurrent access
-
-1. Create holding record
-2. Open it twice
-3. Change value simultaneously in two app and check integrity
-*/
-
-// TODO this test broke because mutex in furi is not implemented
-
-typedef struct {
-    // a and b must be equal
-    uint8_t a;
-    uint8_t b;
-} ConcurrentValue;
-
-void furi_concurent_app(void* p) {
-    FuriRecordSubscriber* holding_record =
-        furi_open("test/concurrent", false, false, NULL, NULL, NULL);
-    if(holding_record == NULL) {
-        printf("cannot open record\n");
-        furiac_exit(NULL);
-    }
-
-    for(size_t i = 0; i < 10; i++) {
-        ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record);
-
-        if(value == NULL) {
-            printf("cannot take record\n");
-            furi_give(holding_record);
-            furiac_exit(NULL);
-        }
-        // emulate read-modify-write broken by context switching
-        uint8_t a = value->a;
-        uint8_t b = value->b;
-        a++;
-        b++;
-        delay(2); // this is only for test, do not add delay between take/give in prod!
-        value->a = a;
-        value->b = b;
-        furi_give(holding_record);
-    }
-
-    furiac_exit(NULL);
-}
-
-bool test_furi_concurrent_access() {
-    // 1. Create holding record
-    ConcurrentValue holder = {.a = 0, .b = 0};
-    if(!furi_create("test/concurrent", (void*)&holder, sizeof(ConcurrentValue))) {
-        printf("cannot create record\n");
-        return false;
-    }
+void test_furi_create_open() {
+    // 1. Create record
+    uint8_t test_data = 0;
+    mu_check(furi_create("test/holding", (void*)&test_data));
 
     // 2. Open it
-    FuriRecordSubscriber* holding_record =
-        furi_open("test/concurrent", false, false, NULL, NULL, NULL);
-    if(holding_record == NULL) {
-        printf("cannot open record\n");
-        return false;
-    }
-
-    // 3. Create second app for interact with it
-    FuriApp* second_app = furiac_start(furi_concurent_app, "furi concurent app", NULL);
-
-    // 4. multiply ConcurrentValue::a
-    for(size_t i = 0; i < 4; i++) {
-        ConcurrentValue* value = (ConcurrentValue*)furi_take(holding_record);
-
-        if(value == NULL) {
-            printf("cannot take record\n");
-            furi_give(holding_record);
-            return false;
-        }
-        // emulate read-modify-write broken by context switching
-        uint8_t a = value->a;
-        uint8_t b = value->b;
-        a++;
-        b++;
-        value->a = a;
-        delay(10); // this is only for test, do not add delay between take/give in prod!
-        value->b = b;
-        furi_give(holding_record);
-    }
-
-    delay(50);
-
-    if(second_app->handler != NULL) {
-        printf("second app still alive\n");
-        return false;
-    }
-
-    if(holder.a != holder.b) {
-        printf("broken integrity: a=%d, b=%d\n", holder.a, holder.b);
-        return false;
-    }
-
-    return true;
+    void* record = furi_open("test/holding");
+    mu_assert_pointers_eq(record, &test_data);
 }
 
 /*
@@ -309,14 +82,14 @@ void mute_record_state_cb(FlipperRecordState state, void* ctx) {
 
 void furi_mute_parent_app(void* p) {
     // 1. Create pipe record
-    if(!furi_create("test/mute", NULL, 0)) {
+    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("test/mute", false, false, mute_record_cb, NULL, NULL);
+        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);
@@ -336,7 +109,7 @@ bool test_furi_mute_algorithm() {
 
     // 2. Open handler A: solo=false, no_mute=false, NULL subscriber. Subscribe to state.
     FuriRecordSubscriber* handler_a =
-        furi_open("test/mute", false, false, NULL, mute_record_state_cb, NULL);
+        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;
@@ -356,7 +129,8 @@ bool test_furi_mute_algorithm() {
     }
 
     // 3. Open handler B: solo=true, no_mute=true, NULL subscriber.
-    FuriRecordSubscriber* handler_b = furi_open("test/mute", true, true, NULL, NULL, NULL);
+    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;
@@ -395,7 +169,8 @@ bool test_furi_mute_algorithm() {
     }
 
     // 4. Open hadler C: solo=true, no_mute=false, NULL subscriber.
-    FuriRecordSubscriber* handler_c = furi_open("test/mute", true, false, NULL, NULL, NULL);
+    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;
@@ -406,7 +181,8 @@ bool test_furi_mute_algorithm() {
     // 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("test/mute", false, false, NULL, NULL, NULL);
+    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;

+ 123 - 0
applications/tests/furi_valuemutex_test.c

@@ -0,0 +1,123 @@
+#include <stdio.h>
+#include <string.h>
+#include "flipper_v2.h"
+#include "log.h"
+
+#include "minunit.h"
+
+void test_furi_valuemutex() {
+    const int init_value = 0xdeadbeef;
+    const int changed_value = 0x12345678;
+
+    int value = init_value;
+    bool result;
+    ValueMutex valuemutex;
+
+    // init mutex case
+    result = init_mutex(&valuemutex, &value, sizeof(value));
+    mu_assert(result, "init mutex failed");
+
+    // acquire mutex case
+    int* value_pointer = acquire_mutex(&valuemutex, 100);
+    mu_assert_pointers_eq(value_pointer, &value);
+
+    // second acquire mutex case
+    int* value_pointer_second = acquire_mutex(&valuemutex, 100);
+    mu_assert_pointers_eq(value_pointer_second, NULL);
+
+    // change value case
+    *value_pointer = changed_value;
+    mu_assert_int_eq(value, changed_value);
+
+    // release mutex case
+    result = release_mutex(&valuemutex, &value);
+    mu_assert(result, "release mutex failed");
+
+    // TODO
+    //acquire mutex blocking case
+    //write mutex blocking case
+    //read mutex blocking case
+}
+
+
+/*
+TEST: concurrent access
+
+1. Create holding record
+2. Open it twice
+3. Change value simultaneously in two app and check integrity
+*/
+
+// TODO this test broke because mutex in furi is not implemented
+
+typedef struct {
+    // a and b must be equal
+    uint8_t a;
+    uint8_t b;
+} ConcurrentValue;
+
+void furi_concurent_app(void* p) {
+    ValueMutex* mutex = (ValueMutex*)p;
+    if(mutex == NULL) {
+        printf("cannot open mutex\n");
+        furiac_exit(NULL);
+    }
+
+    for(size_t i = 0; i < 10; i++) {
+        ConcurrentValue* value = (ConcurrentValue*)acquire_mutex_block(mutex);
+
+        if(value == NULL) {
+            printf("cannot take record\n");
+            release_mutex(mutex, value);
+            furiac_exit(NULL);
+        }
+
+        // emulate read-modify-write broken by context switching
+        uint8_t a = value->a;
+        uint8_t b = value->b;
+        a++;
+        b++;
+        delay(2);
+        value->a = a;
+        value->b = b;
+        release_mutex(mutex, value);
+    }
+
+    furiac_exit(NULL);
+}
+
+void test_furi_concurrent_access() {
+    // 1. Create holding record
+    ConcurrentValue value = {.a = 0, .b = 0};
+    ValueMutex mutex;
+    mu_check(init_mutex(&mutex, &value, sizeof(value)));
+
+    // 3. Create second app for interact with it
+    FuriApp* second_app = furiac_start(furi_concurent_app, "furi concurent app", (void*)&mutex);
+
+    // 4. multiply ConcurrentValue::a
+    for(size_t i = 0; i < 4; i++) {
+        ConcurrentValue* value = (ConcurrentValue*)acquire_mutex_block(&mutex);
+
+        if(value == NULL) {
+            release_mutex(&mutex, value);
+            mu_fail("cannot take record\n");
+        }
+
+        // emulate read-modify-write broken by context switching
+        uint8_t a = value->a;
+        uint8_t b = value->b;
+        a++;
+        b++;
+        value->a = a;
+        delay(10); // this is only for test, do not add delay between take/give in prod!
+        value->b = b;
+        release_mutex(&mutex, value);
+    }
+
+    delay(50);
+
+    mu_assert_pointers_eq(second_app->handler, NULL);
+
+    mu_assert_int_eq(value.a, value.b);
+}

+ 22 - 21
applications/tests/minunit_test.c

@@ -6,12 +6,15 @@
 
 bool test_furi_ac_create_kill();
 bool test_furi_ac_switch_exit();
-bool test_furi_pipe_record();
-bool test_furi_holding_data();
-bool test_furi_concurrent_access();
+
 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();
+
 static int foo = 0;
 
 void test_setup(void) {
@@ -34,27 +37,22 @@ MU_TEST(mu_test_furi_ac_switch_exit) {
     mu_assert_int_eq(test_furi_ac_switch_exit(), true);
 }
 
-MU_TEST(mu_test_furi_pipe_record) {
-    mu_assert_int_eq(test_furi_pipe_record(), true);
+MU_TEST(mu_test_furi_nonexistent_data) {
+    mu_assert_int_eq(test_furi_nonexistent_data(), true);
 }
 
-MU_TEST(mu_test_furi_holding_data) {
-    mu_assert_int_eq(test_furi_holding_data(), true);
+// v2 tests
+MU_TEST(mu_test_furi_create_open) {
+    test_furi_create_open();
 }
 
-MU_TEST(mu_test_furi_concurrent_access) {
-    mu_assert_int_eq(test_furi_concurrent_access(), true);
+MU_TEST(mu_test_furi_valuemutex) {
+    test_furi_valuemutex();
 }
 
-MU_TEST(mu_test_furi_nonexistent_data) {
-    mu_assert_int_eq(test_furi_nonexistent_data(), true);
-}
-
-/*
-MU_TEST(mu_test_furi_mute_algorithm) {
-    mu_assert_int_eq(test_furi_mute_algorithm(test_log), true);
+MU_TEST(mu_test_furi_concurrent_access) {
+    test_furi_concurrent_access();
 }
-*/
 
 MU_TEST_SUITE(test_suite) {
     MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
@@ -62,11 +60,14 @@ MU_TEST_SUITE(test_suite) {
     MU_RUN_TEST(test_check);
     MU_RUN_TEST(mu_test_furi_ac_create_kill);
     MU_RUN_TEST(mu_test_furi_ac_switch_exit);
-    MU_RUN_TEST(mu_test_furi_pipe_record);
-    MU_RUN_TEST(mu_test_furi_holding_data);
-    MU_RUN_TEST(mu_test_furi_concurrent_access);
+
     MU_RUN_TEST(mu_test_furi_nonexistent_data);
-    // MU_RUN_TEST(mu_test_furi_mute_algorithm);
+
+    // 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);
+
 }
 
 int run_minunit() {

+ 47 - 0
core/api-basic/flapp.h

@@ -0,0 +1,47 @@
+#pragma once
+
+#include "flipper.h"
+
+// == Flipper Application control (flapp) ==
+
+typedef FlappHandler uint32_t; // TODO
+
+/*
+simply starts application. It call `app` entrypoint with `param` passed as argument
+Useful for daemon applications and pop-up.
+*/
+FlappHandler* flapp_start(void(app*)(void*), char* name, void* param);
+
+/*
+swtich to other application.
+System **stop current app**, call `app` entrypoint with `param` passed
+as argument and save current application entrypoint to `prev` field in
+current application registry. Useful for UI or "active" application.
+*/
+FlappHandler* flapp_switch(void(app*)(void*), char* name, void* param);
+
+/*
+Exit application
+stop current application (stop thread and clear application's stack),
+start application from `prev` entry in current application registry,
+cleanup current application registry.
+*/
+void flapp_exit(void* param);
+
+/*
+stop specified `app` without returning to `prev` application.
+*/
+bool flapp_kill(FlappHandler* app);
+
+/*
+If case one app depend on other, notify that app is ready.
+*/
+void flapp_ready();
+
+/*
+Register on-exit callback.
+It called before app will be killed.
+Not recommended to use in user scenario, only for system purpose
+(unregister callbacks, release mutexes, etc.)
+*/
+bool flapp_on_exit(void(cb*)(void*), void* ctx);

+ 14 - 0
core/api-basic/furi.c

@@ -0,0 +1,14 @@
+#include "furi.h"
+#include "furi-deprecated.h"
+
+bool furi_create(const char* name, void* ptr) {
+    return furi_create_deprecated(name, ptr, sizeof(size_t));
+}
+
+void* furi_open(const char* name) {
+    FuriRecordSubscriber* record = furi_open_deprecated(name, false, false, NULL, NULL, NULL);
+    void* res = furi_take(record);
+    furi_give(record);
+
+    return res;
+}

+ 26 - 0
core/api-basic/furi.h

@@ -0,0 +1,26 @@
+#pragma once
+
+#include "flipper.h"
+
+/*
+== Flipper universal registry implementation (FURI) ==
+
+## Requirements
+
+* start daemon app
+* kill app
+* start child thread (kill when parent app was killed)
+* switch between UI apps
+*/
+
+/*
+Create record.
+creates new record in registry and store pointer into it
+*/
+bool furi_create(const char* name, void* ptr);
+
+/*
+Open record.
+get stored pointer by its name
+*/
+void* furi_open(const char* name);

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

@@ -0,0 +1,48 @@
+#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);
+    }
+}

+ 83 - 0
core/api-basic/pubsub.h

@@ -0,0 +1,83 @@
+#pragma once
+
+#include "flipper.h"
+
+/*
+== PubSub ==
+
+PubSub allows users to subscribe on notifies and notify subscribers.
+Notifier side can pass `void*` arg to subscriber callback,
+and also subscriber can set `void*` context pointer that pass into
+callback (you can see callback signature below).
+*/
+
+typedef void(PubSubCallback*)(void*, void*);
+
+typedef struct {
+    PubSubCallback cb;
+    void* ctx;
+} PubSubItem;
+
+typedef struct {
+    PubSub* self;
+    PubSubItem* item;
+} PubSubId;
+
+typedef struct {
+    PubSubItem items[NUM_OF_CALLBACKS];
+    PubSubId ids[NUM_OF_CALLBACKS]; ///< permanent links to item
+    size_t count; ///< count of callbacks
+} PubSub;
+
+/*
+To create PubSub you should create PubSub instance and call `init_pubsub`.
+*/
+void init_pubsub(PubSub* pubsub);
+
+/*
+Use `subscribe_pubsub` to register your callback.
+*/
+PubSubId* subscribe_pubsub(PubSub* pubsub, PubSubCallback cb, void* ctx);
+
+/*
+Use `unsubscribe_pubsub` to unregister callback.
+*/
+void unsubscribe_pubsub(PubSubId* pubsub_id);
+
+/*
+Use `notify_pubsub` to notify subscribers.
+*/
+void notify_pubsub(PubSub* pubsub, void* arg);
+
+/*
+
+```C
+// MANIFEST
+// name="test"
+// stack=128
+
+void example_pubsub_handler(void* arg, void* ctx) {
+    printf("get %d from %s\n", *(uint32_t*)arg, (const char*)ctx);
+}
+
+void pubsub_test() {
+    const char* app_name = "test app";
+
+    PubSub example_pubsub;
+    init_pubsub(&example_pubsub);
+
+    if(!subscribe_pubsub(&example_pubsub, example_pubsub_handler, (void*)app_name)) {
+        printf("critical error\n");
+        flapp_exit(NULL);
+    }
+
+    uint32_t counter = 0;
+    while(1) {
+        notify_pubsub(&example_pubsub, (void*)&counter);
+        counter++;
+
+        osDelay(100);
+    }
+}
+```
+*/

+ 24 - 0
core/api-basic/value-expanders.c.unimplemented

@@ -0,0 +1,24 @@
+#include "value-expanders.h"
+
+bool commit_managed(ValueManager* managed, void* value) {
+    if(value != managed->mutex->value) return false;
+
+    notify_pubsub(&managed->pubsub, value);
+    
+    if(!osMutexGive(managed->mutex)) return false;
+    
+    return true;
+}
+
+bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout) {
+    void* value = acquire_mutex(managed->mutex, timeout);
+    if(value == NULL) return false;
+
+    memcpy(value, data, len):
+
+    notify_pubsub(&managed->pubsub, value);
+
+    if(!release_mutex(managed->mutex, value)) return false;
+    
+    return true;
+}

+ 56 - 0
core/api-basic/value-expanders.h

@@ -0,0 +1,56 @@
+#pragma once
+
+#include "flipper.h"
+
+/*
+== Value composer ==
+*/
+
+typedef void(ValueComposerCallback)(void* ctx, void* state);
+
+void COPY_COMPOSE(void* ctx, void* state) {
+    read_mutex((ValueMutex*)ctx, state, 0);
+}
+
+typedef enum {
+    UiLayerBelowNotify
+    UiLayerNotify,
+    UiLayerAboveNotify
+} UiLayer;
+
+ValueComposerHandle* add_compose_layer(
+    ValueComposer* composer, ValueComposerCallback cb, void* ctx, uint32_t layer
+);
+
+bool remove_compose_layer(ValueComposerHandle* handle);
+
+void request_compose(ValueComposerHandle* handle);
+
+// See [LED](LED-API) or [Display](Display-API) API for examples.
+
+/*
+== ValueManager ==
+
+More complicated concept is ValueManager.
+It is like ValueMutex, but user can subscribe to value updates.
+
+First of all you can use value and pubsub part as showing above:
+aquire/release mutex, read value, subscribe/unsubscribe pubsub.
+There are two specific methods for ValueManager: write_managed, commit_managed
+*/
+
+typedef struct {
+    ValueMutex value;
+    PubSub pubsub;
+} ValueManager;
+
+
+/*
+acquire value, changes it and send notify with current value.
+*/
+bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout);
+
+/*
+commit_managed works as `release_mutex` but send notify with current value.
+*/
+bool commit_managed(ValueManager* managed, void* value);

+ 52 - 0
core/api-basic/valuemutex.c

@@ -0,0 +1,52 @@
+#include "valuemutex.h"
+#include <string.h>
+
+bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) {
+    // mutex without name,
+    // no attributes (unfortunatly robust mutex is not supported by FreeRTOS),
+    // with dynamic memory allocation
+    const osMutexAttr_t value_mutext_attr = {
+        .name = NULL, .attr_bits = 0, .cb_mem = NULL, .cb_size = 0U};
+
+    valuemutex->mutex = osMutexNew(&value_mutext_attr);
+    if(valuemutex->mutex == NULL) return false;
+
+    valuemutex->value = value;
+    valuemutex->size = size;
+
+    return true;
+}
+
+void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) {
+    if(osMutexAcquire(valuemutex->mutex, timeout) == osOK) {
+        return valuemutex->value;
+    } else {
+        return NULL;
+    }
+}
+
+bool release_mutex(ValueMutex* valuemutex, void* value) {
+    if(value != valuemutex->value) return false;
+
+    if(osMutexRelease(valuemutex->mutex) != osOK) return false;
+
+    return true;
+}
+
+bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) {
+    void* value = acquire_mutex(valuemutex, timeout);
+    if(value == NULL || len > valuemutex->size) return false;
+    memcpy(data, value, len > 0 ? len : valuemutex->size);
+    if(!release_mutex(valuemutex, value)) return false;
+
+    return true;
+}
+
+bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) {
+    void* value = acquire_mutex(valuemutex, timeout);
+    if(value == NULL || len > valuemutex->size) return false;
+    memcpy(value, data, len > 0 ? len : valuemutex->size);
+    if(!release_mutex(valuemutex, value)) return false;
+
+    return true;
+}

+ 123 - 0
core/api-basic/valuemutex.h

@@ -0,0 +1,123 @@
+#pragma once
+
+#include "flipper.h"
+
+/*
+== ValueMutex ==
+
+The most simple concept is ValueMutex.
+It is wrapper around mutex and value pointer.
+You can take and give mutex to work with value and read and write value.
+*/
+
+typedef struct {
+    void* value;
+    size_t size;
+    osMutexId_t mutex;
+} ValueMutex;
+
+/*
+Creates ValueMutex.
+*/
+bool init_mutex(ValueMutex* valuemutex, void* value, size_t size);
+
+/*
+Call for work with data stored in mutex.
+Returns pointer to data if success, NULL otherwise.
+*/
+void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout);
+
+/*
+Helper: infinitly wait for mutex
+*/
+static inline void* acquire_mutex_block(ValueMutex* valuemutex) {
+    return acquire_mutex(valuemutex, osWaitForever);
+}
+
+/*
+Release mutex after end of work with data.
+Call `release_mutex` and pass ValueData instance and pointer to data.
+*/
+bool release_mutex(ValueMutex* valuemutex, void* value);
+
+/*
+Instead of take-access-give sequence you can use `read_mutex` and `write_mutex` functions.
+Both functions return true in case of success, false otherwise.
+*/
+bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout);
+
+bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout);
+
+inline static bool write_mutex_block(ValueMutex* valuemutex, void* data, size_t len) {
+    return write_mutex(valuemutex, data, len, osWaitForever);
+}
+
+inline static bool read_mutex_block(ValueMutex* valuemutex, void* data, size_t len) {
+    return read_mutex(valuemutex, data, len, osWaitForever);
+}
+
+/*
+
+Usage example
+
+```C
+// MANIFEST
+// name="example-provider-app"
+// stack=128
+
+void provider_app(void* _p) {
+    // create record with mutex
+    uint32_t example_value = 0;
+    ValueMutex example_mutex;
+    // call `init_mutex`.
+    if(!init_mutex(&example_mutex, (void*)&example_value, sizeof(uint32_t))) {
+        printf("critical error\n");
+        flapp_exit(NULL);
+    }
+
+    if(furi_create("provider/example", (void*)&example_mutex)) {
+        printf("critical error\n");
+        flapp_exit(NULL);
+    }
+
+    // we are ready to provide record to other apps
+    flapp_ready();
+
+    // get value and increment it
+    while(1) {
+        uint32_t* value = acquire_mutex(&example_mutex, OsWaitForever);
+        if(value != NULL) {
+            value++;
+        }
+        release_mutex(&example_mutex, value);
+
+        osDelay(100);
+    }
+}
+
+// MANIFEST
+// name="example-consumer-app"
+// stack=128
+// require="example-provider-app"
+void consumer_app(void* _p) {
+    // this app run after flapp_ready call in all requirements app
+
+    // open mutex value
+    ValueMutex* counter_mutex = furi_open("provider/example");
+    if(counter_mutex == NULL) {
+        printf("critical error\n");
+        flapp_exit(NULL);
+    }
+
+    // continously read value every 1s
+    uint32_t counter;
+    while(1) {
+        if(read_mutex(counter_mutex, &counter, sizeof(counter), OsWaitForever)) {
+            printf("counter value: %d\n", counter);
+        }
+
+        osDelay(1000);
+    }
+}
+```
+*/

+ 133 - 0
core/api-hal/api-spi.h

@@ -0,0 +1,133 @@
+#include "flipper_v2.h"
+
+/*
+struct used for handling SPI info.
+*/
+typedef struct {
+    SPI_HandleTypeDef* spi;
+    PubSubCallback cb;
+    void* ctx;
+} SpiHandle;
+
+/*
+For transmit/receive data use `spi_xfer` function.
+
+* `tx_data` and `rx_data` size must be equal (and equal `len`)
+* `cb` called after spi operation is completed, `(NULL, ctx)` passed to callback.
+*/
+bool spi_xfer(
+    SPI_HandleTypeDef* spi,
+    uint8_t* tx_data, uint8_t* rx_data, size_t len,
+    PubSubCallback cb, void* ctx);
+
+/*
+Blocking verison:
+*/
+static inline bool spi_xfer_block(SPI_HandleTypeDef* spi, uint8_t* tx_data, uint8_t* rx_data, size_t len) {
+    semaphoreInfo s;
+    osSemaphore block = createSemaphoreStatic(s);
+    if(!spi_xfer(spi, tx_data, rx_data, len, RELEASE_SEMAPHORE, (void*)block)) {
+        osReleaseSemaphore(block);
+        return false;
+    }
+    osWaitSemaphore(block);
+    return false;
+}
+
+/*
+Common implementation of SPI bus: serial interface + CS pin
+*/
+typedef struct {
+    GpioPin* cs; ///< CS pin
+    ValueMutex* spi; ///< <SpiHandle*>
+} SpiBus;
+
+/*
+For dedicated work with one device there is `SpiDevice` entity.
+It contains ValueMutex around SpiBus: after you acquire device
+you can acquire spi to work with it (don't forget SPI bus is shared
+around many device, release it after every transaction as quick as possible).
+*/
+typedef struct {
+    ValueMutex* bus; ///< <SpiBus*>
+} SpiDevice;
+
+## SPI IRQ device
+
+/*
+Many devices (like CC1101 and NFC) present as SPI bus and IRQ line.
+For work with it there is special entity `SpiIrqDevice`.
+Use `subscribe_pubsub` for subscribinq to irq events.
+*/
+
+typedef struct {
+    ValueMutex* bus; ///< <SpiBus*>
+    PubSub* irq;
+} SpiIrqDevice;
+
+/*
+Special implementation of SPI bus: serial interface + CS, Res, D/I lines.
+*/
+typedef struct {
+    GpioPin* cs; ///< CS pin
+    GpioPin* res; ///< reset pin
+    GpioPin* di; ///< D/I pin
+    ValueMutex* spi; ///< <SPI_HandleTypeDef*>
+} DisplayBus;
+
+
+typedef struct {
+    ValueMutex* bus; ///< <DisplayBus*>
+} DisplayDevice;
+
+/*
+# SPI devices (F2)
+
+* `/dev/sdcard` - SD card SPI, `SpiDevice`
+* `/dev/cc1101_bus` - Sub-GHz radio (CC1101), `SpiIrqDevice`
+* `/dev/nfc` - NFC (ST25R3916), `SpiIrqDevice`
+* `/dev/display` - `DisplayDevice`
+* `/dev/spiext` - External SPI (warning! Lock PA4, PA5, PA6, PA7)
+
+### Application example
+
+```C
+// Be careful, this function called from IRQ context
+void handle_irq(void* _arg, void* _ctx) {
+}
+
+void cc1101_example() {
+    SpiIrqDevice* cc1101_device = open_input("/dev/cc1101_bus");
+    if(cc1101_device == NULL) return; // bus not available, critical error
+
+    subscribe_pubsub(cc1101_device->irq, handle_irq, NULL);
+
+    {
+        // acquire device as device bus
+        SpiBus* spi_bus = acquire_mutex(cc1101_device->bus, 0);
+        if(spi_bus == NULL) {
+            printf("Device busy\n");
+            // wait for device
+            spi_bus = acquire_mutex_block(cc1101_device->bus);
+        }
+        
+        // make transaction
+        uint8_t request[4] = {0xDE, 0xAD, 0xBE, 0xEF};
+        uint8_t response[4];
+
+        {
+            SPI_HandleTypeDef* spi = acquire_mutex_block(spi_bus->spi);
+
+            gpio_write(spi_bus->cs, false);
+            spi_xfer_block(spi, request, response, 4);
+            gpio_write(spi_bus->cs, true);
+
+            release_mutex(cc1101_device->spi, spi);
+        }
+
+        // release device (device bus)
+        release_mutex(cc1101_device->bus, spi_bus);
+    }
+}
+```
+*/

+ 0 - 1
core/app.cpp

@@ -2,7 +2,6 @@
 
 extern "C" {
 #include "flipper.h"
-#include "furi.h"
 #include "log.h"
 #include "startup.h"
 #include "tty_uart.h"

+ 1 - 0
core/core.mk

@@ -3,4 +3,5 @@ CORE_DIR		= $(PROJECT_ROOT)/core
 CFLAGS			+= -I$(CORE_DIR)
 ASM_SOURCES		+= $(wildcard $(CORE_DIR)/*.s)
 C_SOURCES		+= $(wildcard $(CORE_DIR)/*.c)
+C_SOURCES		+= $(wildcard $(CORE_DIR)/api-basic/*.c)
 CPP_SOURCES		+= $(wildcard $(CORE_DIR)/*.cpp)

+ 2 - 1
core/flipper.h

@@ -7,7 +7,8 @@ extern "C" {
 #include "main.h"
 #include "flipper_hal.h"
 #include "cmsis_os.h"
-#include "furi.h"
+#include "furi-deprecated.h"
+
 #include "log.h"
 #include "input/input.h"
 

+ 7 - 0
core/flipper_v2.h

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

+ 4 - 5
core/furi.c → core/furi-deprecated.c

@@ -1,5 +1,4 @@
-#include "furi.h"
-#include "cmsis_os.h"
+#include "furi-deprecated.h"
 #include <string.h>
 
 // TODO: this file contains printf, that not implemented on uC target
@@ -28,7 +27,7 @@ static FuriRecord* find_record(const char* name) {
 }
 
 // TODO: change open-create to only open
-bool furi_create(const char* name, void* value, size_t size) {
+bool furi_create_deprecated(const char* name, void* value, size_t size) {
 #ifdef FURI_DEBUG
     printf("[FURI] creating %s record\n", name);
 #endif
@@ -73,7 +72,7 @@ bool furi_create(const char* name, void* value, size_t size) {
     return true;
 }
 
-FuriRecordSubscriber* furi_open(
+FuriRecordSubscriber* furi_open_deprecated(
     const char* name,
     bool solo,
     bool no_mute,
@@ -94,7 +93,7 @@ FuriRecordSubscriber* furi_open(
 #endif
 
         // create record if not exist
-        if(!furi_create(name, NULL, 0)) {
+        if(!furi_create_deprecated(name, NULL, 0)) {
             return NULL;
         }
 

+ 2 - 2
core/furi.h → core/furi-deprecated.h

@@ -127,7 +127,7 @@ If NULL, create FURI Pipe (only callbacks management, no data/mutex)
 
 Returns false if registry have not enough memory for creating.
 */
-bool furi_create(const char* name, void* value, size_t size);
+bool furi_create_deprecated(const char* name, void* value, size_t size);
 
 /*!
 Opens existing FURI record by name.
@@ -137,7 +137,7 @@ When appication has exited or record has closed, all handlers is unmuted.
 It may be useful for concurrently acces to resources like framebuffer or beeper.
 \param[in] no_mute if true, another applications cannot mute this handler.
 */
-FuriRecordSubscriber* furi_open(
+FuriRecordSubscriber* furi_open_deprecated(
     const char* name,
     bool solo,
     bool no_mute,

+ 1 - 2
core/furi_ac.c

@@ -1,5 +1,4 @@
-#include "furi.h"
-#include "cmsis_os.h"
+#include "flipper.h"
 
 // TODO: this file contains printf, that not implemented on uC target
 

+ 2 - 2
core/log.c

@@ -5,7 +5,7 @@
 #include <string.h>
 
 #include "log.h"
-#include "furi.h"
+#include "flipper.h"
 
 #define PRINT_STR_SIZE 64
 
@@ -21,5 +21,5 @@ void fuprintf(FuriRecordSubscriber* f, const char* format, ...) {
 }
 
 FuriRecordSubscriber* get_default_log() {
-    return furi_open("tty", false, false, NULL, NULL, NULL);
+    return furi_open_deprecated("tty", false, false, NULL, NULL, NULL);
 }

+ 1 - 1
core/log.h

@@ -1,6 +1,6 @@
 #pragma once
 
-#include "furi.h"
+#include "flipper.h"
 
 FuriRecordSubscriber* get_default_log();
 void fuprintf(FuriRecordSubscriber* f, const char* format, ...);

+ 4 - 4
core/tty_uart.c

@@ -1,6 +1,6 @@
 #define _GNU_SOURCE
 #include <stdio.h>
-#include "furi.h"
+#include "flipper.h"
 #include "main.h"
 
 extern UART_HandleTypeDef DEBUG_UART;
@@ -12,7 +12,7 @@ void handle_uart_write(const void* data, size_t size, void* ctx) {
 static ssize_t stdout_write(void* _cookie, const char* buf, size_t n) {
     FuriRecordSubscriber* log = pvTaskGetThreadLocalStoragePointer(NULL, 0);
     if(log == NULL) {
-        log = furi_open("tty", false, false, NULL, NULL, NULL);
+        log = furi_open_deprecated("tty", false, false, NULL, NULL, NULL);
         if(log == NULL) {
             return -1;
         }
@@ -33,11 +33,11 @@ static ssize_t stdout_write(void* _cookie, const char* buf, size_t n) {
 }
 
 bool register_tty_uart() {
-    if(!furi_create("tty", NULL, 0)) {
+    if(!furi_create_deprecated("tty", NULL, 0)) {
         return false;
     }
 
-    if(furi_open("tty", false, false, handle_uart_write, NULL, NULL) == NULL) {
+    if(furi_open_deprecated("tty", false, false, handle_uart_write, NULL, NULL) == NULL) {
         return false;
     }
 

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

@@ -68,3 +68,31 @@ void* pvTaskGetThreadLocalStoragePointer(TaskHandle_t xTaskToQuery, BaseType_t x
 void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet, BaseType_t xIndex, void *pvValue);
 
 QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
+
+typedef struct {
+  const char                   *name;   ///< name of the mutex
+  uint32_t                 attr_bits;   ///< attribute bits
+  void                      *cb_mem;    ///< memory for control block
+  uint32_t                   cb_size;   ///< size of provided memory for control block
+} osMutexAttr_t;
+
+typedef SemaphoreHandle_t osMutexId_t;
+
+osMutexId_t osMutexNew(const osMutexAttr_t *attr);
+
+/// Status code values returned by CMSIS-RTOS functions.
+typedef enum {
+  osOK                      =  0,         ///< Operation completed successfully.
+  osError                   = -1,         ///< Unspecified RTOS error: run-time error but no other error message fits.
+  osErrorTimeout            = -2,         ///< Operation not completed within the timeout period.
+  osErrorResource           = -3,         ///< Resource not available.
+  osErrorParameter          = -4,         ///< Parameter error.
+  osErrorNoMemory           = -5,         ///< System is out of memory: it was impossible to allocate or reserve memory for the operation.
+  osErrorISR                = -6,         ///< Not allowed in ISR context: the function cannot be called from interrupt service routines.
+  osStatusReserved          = 0x7FFFFFFF  ///< Prevents enum down-size compiler optimization.
+} osStatus_t;
+
+osStatus_t osMutexAcquire (osMutexId_t mutex_id, uint32_t timeout);
+osStatus_t osMutexRelease (osMutexId_t mutex_id);
+
+#define osWaitForever portMAX_DELAY

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

@@ -0,0 +1 @@
+#include "cmsis_os.h"

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

@@ -230,3 +230,26 @@ void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet, BaseType_t xInde
     pthread_setspecific(tls_keys[xIndex], pvValue);
 }
 
+
+osMutexId_t osMutexNew(const osMutexAttr_t *attr) {
+    StaticSemaphore_t* pxMutexBuffer = malloc(sizeof(StaticSemaphore_t));
+    xSemaphoreCreateMutexStatic(pxMutexBuffer);
+
+    return (osMutexId_t)pxMutexBuffer;
+}
+
+osStatus_t osMutexAcquire(osMutexId_t mutex_id, uint32_t timeout) {
+    if(xSemaphoreTake((SemaphoreHandle_t)mutex_id, (TickType_t)timeout) == pdTRUE) {
+        return osOK;
+    } else {
+        return osErrorTimeout;
+    }
+}
+
+osStatus_t osMutexRelease (osMutexId_t mutex_id) {
+    if(xSemaphoreGive((SemaphoreHandle_t)mutex_id) == pdTRUE) {
+        return osOK;
+    } else {
+        return osError;
+    }
+}

+ 4 - 0
lib/lib.mk

@@ -2,6 +2,10 @@ LIB_DIR			= $(PROJECT_ROOT)/lib
 
 CFLAGS			+= -I$(LIB_DIR)
 
+# Mlib containers
+CFLAGS			+= -I$(LIB_DIR)/mlib
+
+# U8G2 display library
 U8G2_DIR		= $(LIB_DIR)/u8g2
 CFLAGS			+= -I$(U8G2_DIR)
 C_SOURCES		+= $(U8G2_DIR)/u8x8_d_st7565.c

+ 1 - 0
lib/mlib

@@ -0,0 +1 @@
+Subproject commit eb7556f88faf0bbfd6a4ae99a3d53dcbe2064b88

+ 27 - 61
wiki/fw/Core-API.md

@@ -1,82 +1,48 @@
-# Basic concepts:
+# [Basic concepts](Basic-API)
 
 * ValueMutex
-* PubSub, Publisher, Subscriber
+* PubSub
 * ValueManager
-* LayeredReducer
+* ValueComposer
 
-# HAL
+# [HAL and devices](HAL-API)
 
-We use [Zephyr HAL](https://docs.zephyrproject.org/latest/reference/peripherals/index.html).
+* GPIO
+* PWM
+* ADC
+* I2C
 
-# OS
-
-We use [CMSIS OS v2](https://www.keil.com/pack/doc/CMSIS_Dev/RTOS2/html/group__CMSIS__RTOS.html) for thread management and IPC.
-
-# UI
-
-
-* **[Input](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Input)**
-
-* **[Display](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Display)**
-
-* **[LED](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:LED)**
-
-* **vibro**
-
-* **[Sound](https://github.com/Flipper-Zero/flipperzero-firmware-community/wiki/API:Sound)**
-
-* **backlight**
-
-# System
-
-## batt voltage
+* IR RX (unimplemented)
+* Comparator RX (touch key and RFID 125 kHz RX) (unimplemented)
 
-## batt charge
+# [SPI Devices](SPI-Devices-API.md)
 
-# CC1101
+* Sub-GHz chip
+* NFC
+* SD card
+* display
+* external SPI
 
-## SPI
-
-## IRQ
-
-# SD Card
-
-## SPI
-
-# NFC
-
-## SPI
-
-## IRQ
-
-# IR
-
-## TX LED
-
-## RX ADC
-
-# RFID 125 kHz
-
-## Carrier
+# OS
 
-## Pull
+We use [CMSIS OS v2](https://www.keil.com/pack/doc/CMSIS_Dev/RTOS2/html/group__CMSIS__RTOS.html) for thread management and IPC.
 
-## Comparator RX (shared with touch key)
+# UI
 
-# Touch key
+* **[Input](Input-API)**
 
-## Pull
+* **[Display](Display-API)**
 
-## Comparator RX (shared with RFID 125 kHz)
+* **[LED](LED-API)**
 
-# External GPIO
+* **[Backlight](Backlight-API)** (unimplemented)
 
-# External SPI
+# [Power](Power-API)
 
-# External I2C
+* batt voltage
+* batt charge
 
-# UART
+# [UART](Serial-API)
 
 # USB
 

+ 0 - 20
wiki/fw/FURI.md

@@ -6,27 +6,7 @@ Flipper Universal Registry Implementation or FURI is important part of Flipper f
 
 # Application registry and control (FURIAC)
 
-### Start and change application wrokflow
 
-**`FuriApp* furiac_start(void(app*)(void*), char* name, void* param)`**
-
-simply starts application. It call `app` entrypoint with `param` passed as argument. Useful for daemon applications and pop-up.
-
-
-**`FuriApp furiac_switch(void(app*)(void*), char* name, void* param)`**
-
-swtich to other application. FURI **stop current app**, call `app` entrypoint with `param` passed as argument and save current application entrypoint to `prev` field in current application registry. Useful for UI or "active" application.
-
-### Exit application
-
-**`void furiac_exit(void* param)`**
-
-stop current application (stop thread and clear application's stack), start application from `prev` entry in current application registry, cleanup current application registry.
-
-
-**`bool furiac_kill(FuriApp app)`**
-
-stop specified `app` without returning to `prev` application.
 
 # Data exchange
 

+ 0 - 61
wiki/fw/api/API-Display.md

@@ -1,61 +0,0 @@
-All display operations based on [u8g2](https://github.com/olikraus/u8g2) library.
-
-API available as struct, contains u8g2 functions, instance and fonts:
-
-```C
-typedef struct {
-    ValueManager* display; /// ValueManager<u8g2_t*>
-    void (*u8g2_SetFont)(u8g2_t *u8g2, const uint8_t  *font);
-    void (*u8g2_SetDrawColor)(u8g2_t *u8g2, uint8_t color);
-    void (*u8g2_SetFontMode)(u8g2_t *u8g2, uint8_t is_transparent);
-    u8g2_uint_t (*u8g2_DrawStr)(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str);
-
-    Fonts fonts;
-} Display;
-
-typedef struct {
-    const uint8_t* u8g2_font_6x10_mf;
-} Fonts;
-```
-
-First of all you can open display API instance by calling `open_display`
-
-```C
-/// Get display instance and API
-inline Display* open_display(const char* name) {
-    return (Display*)furi_open(name);
-}
-```
-
-Default display name is `/dev/display`.
-
-For draw something to display you can get display instance pointer by calling `take_display`, do something and commit your changes by calling `commit_display`:
-
-```C
-/// return pointer in case off success, NULL otherwise
-inline u8g2_t* take_display(Display* api, uint32_t timeout) {
-    return (u8g2_t*)take_mutex(api->display->value, timeout);
-}
-
-inline void commit_display(Display* api, u8g2_t* display) {
-    commit_valuemanager(api->display, display);
-}
-```
-
-## Usage example
-
-```C
-void u8g2_example(void* p) {
-    Display* display_api = open_display("/dev/display");
-    if(display_api == NULL) return; // display not available, critical error
-
-    u8g2_t* display = take_display(display_api);
-    if(display != NULL) {
-        display_api->u8g2_SetFont(display, display_api->fonts.u8g2_font_6x10_mf);
-        display_api->u8g2_SetDrawColor(display, 1);
-        display_api->u8g2_SetFontMode(display, 1);
-        display_api->u8g2_DrawStr(display, 2, 12, "hello world!");
-    }
-    commit_display(display_api, display);
-}
-```

+ 0 - 124
wiki/fw/api/API-LED.md

@@ -1,124 +0,0 @@
-LED state describes by struct:
-
-```C
-typedef struct {
-    uint8_t red;
-    uint8_t green;
-    uint8_t blue; 
-} Rgb;
-```
-
-LED API provided by struct:
-
-```C
-typedef struct {
-    LayeredReducer* source; /// every app add its layer to set value, LayeredReducer<Rgb*>
-    Subscriber* updates; /// LED value changes Supscriber<Rgb*>
-    ValueMutex* state; /// LED state, ValueMutex<Rgb*>
-} LedApi;
-```
-
-You can get API instance by calling `open_led`:
-
-```C
-/// Add new layer to LED:
-inline LedApi* open_led(const char* name) {
-    return (LedApi*)furi_open(name);
-}
-```
-
-Default system led is `/dev/led`.
-
-Then add new layer to control LED by calling `add_led_layer`:
-
-```C
-inline ValueManager* add_led_layer(Rgb* layer, uint8_t priority) {
-    ValueManager* manager = register_valuemanager((void*)layer);
-    if(manager == NULL) return NULL;
-
-    if(!add_layered_reducer(manager, priority, layer_compose_default)) {
-        unregister_valuemanager(manager);
-        return NULL;
-    }
-
-    return manager;
-}
-```
-
-For change led you can get display instance pointer by calling `take_led`, do something and commit your changes by calling `commit_led`. Or you can call `write_led`:
-
-```C
-/// return pointer in case off success, NULL otherwise
-inline Rgb* take_led(ValueManager* led, uint32_t timeout) {
-    return (Rgb*)take_mutex(led->value, timeout);
-}
-
-inline void commit_led(ValueManager* led, Rgb* value) {
-    commit_valuemanager(led, value);
-}
-
-/// return true if success, false otherwise
-inline bool write_led(ValueManager* led, Rgb* value, uint32_t timeout) {
-    return write_valuemanager(state, (void*)value, sizeof(Rgb), timeout);
-}
-```
-
-To read current led state you should use `read_led` function:
-
-```C
-/// return true if success, false otherwise
-inline bool read_led(ValueManager* led, Rgb* value, uint32_t timeout) {
-    return read_mutex(led->value, (void*)value, sizeof(Rgb), timeout);
-}
-```
-
-Also you can subscribe to led state changes:
-
-Use `subscribe_led_changes` to register your callback:
-
-```C
-/// return true if success, false otherwise
-inline bool subscribe_led_changes(Subscriber* updates, void(*cb)(Rgb*, void*), void* ctx) {
-    return subscribe_pubsub(events, void(*)(void*, void*)(cb), ctx);
-}
-```
-
-## Usage example
-
-```C
-
-void handle_led_state(Rgb* rgb, void* _ctx) {
-    printf("led: #%02X%02X%02X\n", rgb->red, rgb->green, rgb->blue);
-}
-
-void led_example(void* p) {
-    LedApi* led_api = open_display("/dev/led");
-    if(led_api == NULL) return; // led not available, critical error
-
-    // subscribe to led state updates
-    subscribe_led_changes(led_api->updates, handle_led_state, NULL);
-
-    Rgb current_state;
-    if(read_led(led_api->state, &current_state, OsWaitForever)) {
-        printf(
-            "initial led: #%02X%02X%02X\n",
-            current_state->red,
-            current_state->green,
-            current_state->blue
-        );
-    }
-
-    // add layer to control led
-    ValueManager* led_manager = add_led_layer(&current_state, UI_LAYER_APP);
-
-    // write only blue by getting pointer
-    Rgb* rgb = take_led(led_manager, OsWaitForever);
-    if(rgb != NULL) {
-        rgb->blue = 0;
-    }
-    commit_led(led_manager, rgb);
-
-    // write RGB value
-    write_led(led_manager, &(Rgb{.red = 0xFA, green = 0xCE, .blue = 0x8D}), OsWaitForever);
-}
-```

+ 0 - 122
wiki/fw/api/API-Sound.md

@@ -1,122 +0,0 @@
-sound state describes by struct:
-
-```C
-typedef struct {
-    float freq; /// frequency in Hz
-    float width; /// pulse witdh 0...1
-} Tone;
-```
-
-sound API provided by struct:
-
-```C
-typedef struct {
-    LayeredReducer* source; /// every app add its layer to set value, LayeredReducer<Tone*>
-    Subscriber* updates; /// sound value changes Supscriber<Tone*>
-    ValueMutex* state; /// sound state, ValueMutex<Tone*>
-} SoundApi;
-```
-
-You can get API instance by calling `open_sound`:
-
-```C
-/// Add new layer to sound:
-inline SoundApi* open_sound(const char* name) {
-    return (SoundApi*)furi_open(name);
-}
-```
-
-Default system sound is `/dev/sound`.
-
-Then add new layer to control sound by calling `add_sound_layer`:
-
-```C
-inline ValueManager* add_sound_layer(Tone* layer, uint8_t priority) {
-    ValueManager* manager = register_valuemanager((void*)layer);
-    if(manager == NULL) return NULL;
-
-    if(!add_layered_reducer(manager, priority, layer_compose_default)) {
-        unregister_valuemanager(manager);
-        return NULL;
-    }
-
-    return manager;
-}
-```
-
-For change sound you can get display instance pointer by calling `take_sound`, do something and commit your changes by calling `commit_sound`. Or you can call `write_sound`:
-
-```C
-/// return pointer in case off success, NULL otherwise
-inline Tone* take_sound(ValueManager* sound, uint32_t timeout) {
-    return (Tone*)take_mutex(sound->value, timeout);
-}
-
-inline void commit_sound(ValueManager* sound, Tone* value) {
-    commit_valuemanager(sound, value);
-}
-
-/// return true if success, false otherwise
-inline bool write_sound(ValueManager* sound, Tone* value, uint32_t timeout) {
-    return write_valuemanager(state, (void*)value, sizeof(Tone), timeout);
-}
-```
-
-To read current sound state you should use `read_sound` function:
-
-```C
-/// return true if success, false otherwise
-inline bool read_sound(ValueManager* sound, Tone* value, uint32_t timeout) {
-    return read_mutex(sound->value, (void*)value, sizeof(Tone), timeout);
-}
-```
-
-Also you can subscribe to sound state changes:
-
-Use `subscribe_sound_changes` to register your callback:
-
-```C
-/// return true if success, false otherwise
-inline bool subscribe_sound_changes(Subscriber* updates, void(*cb)(Tone*, void*), void* ctx) {
-    return subscribe_pubsub(events, void(*)(void*, void*)(cb), ctx);
-}
-```
-
-## Usage example
-
-```C
-
-void handle_sound_state(Tone* tone, void* _ctx) {
-    printf("sound: %d Hz, %d %%\n", (uint16_t)tone->freq, (uint8_t)(tone->witdh * 100));
-}
-
-void sound_example(void* p) {
-    soundApi* sound_api = open_display("/dev/sound");
-    if(sound_api == NULL) return; // sound not available, critical error
-
-    // subscribe to sound state updates
-    subscribe_sound_changes(sound_api->updates, handle_sound_state, NULL);
-
-    Tone current_state;
-    if(read_sound(sound_api->state, &current_state, OsWaitForever)) {
-        printf(
-            "sound: %d Hz, %d %%\n",
-            (uint16_t)current_state->freq,
-            (uint8_t)(current_state->witdh * 100)
-        );
-    }
-
-    // add layer to control sound
-    ValueManager* sound_manager = add_sound_layer(&current_state, UI_LAYER_APP);
-
-    // write only freq by getting pointer
-    Tone* tone = take_sound(sound_manager, OsWaitForever);
-    if(tone != NULL) {
-        tone->freq = 440;
-    }
-    commit_sound(sound_manager, tone);
-
-    // write tone value
-    write_sound(sound_manager, &(Tone{.freq = 110., witdh = 0.5}), OsWaitForever);
-}
-```

+ 100 - 0
wiki/fw/api/Backlight-API.md

@@ -0,0 +1,100 @@
+Backlight state describes by `uint8_t level;` brightness level.
+
+LED API provided by struct:
+
+```C
+typedef struct {
+    ValueComposer* composer; /// every app add its value to compose, <uint8_t*>
+    ValueManager* state; /// value state and changes <uint8_t*>
+} BacklightApi;
+```
+
+You can get API instance by calling `open_backlight`:
+
+```C
+/// Add new layer to LED:
+inline BacklightApi* open_backlight(const char* name) {
+    return (BacklightApi*)furi_open(name);
+}
+```
+
+Default system led is `/dev/backlight`.
+
+To read current backlight state you should use `read_backlight` function:
+
+```C
+/// return true if success, false otherwise
+inline bool read_backlight(BacklightApi* api, uint8_t* value, uint32_t timeout) {
+    return read_mutex(api->state->value, (void*)value, sizeof(uint8_t), timeout);
+}
+```
+
+Also you can subscribe to backlight state changes:
+
+Use `subscribe_backlight_changes` to register your callback:
+
+```C
+/// return true if success, false otherwise
+inline bool subscribe_backlight_changes(LedApi* led, void(*cb)(uint8_t*, void*), void* ctx) {
+    return subscribe_pubsub(led->state->pubsub, void(*)(void*, void*)(cb), ctx);
+}
+```
+
+Userspace helpers
+
+```C
+typedef struct {
+    uint8_t value;
+    ValueMutex value_mutex;
+    ValueComposerHandle* composer_handle;
+} Backlight;
+
+inline bool init_backlight_composer(Backlight* backlight, BacklightApi* api, uint32_t layer) {
+    if(!init_mutex(&backlight->value_mutex, (void*)&backlight->value, sizeof(uint8_t))) {
+        return false;
+    }
+    backlight->composer_handle = add_compose_layer(
+        api->composer, COPY_COMPOSE, &backlight->value_mutex, layer
+    ); // just copy backlight state on update
+
+    return backlight->composer_handle != NULL;
+}
+
+inline void write_backlight(Backlight* backlight, uint8_t value) {
+    write_mutex(&backlight->value_mutex, (void*)&value, sizeof(uint8_t), OsWaitForever);
+    request_compose(backlight->composer_handle);
+}
+```
+
+
+## Usage example
+
+```C
+
+void handle_backlight_state(uint8_t* value, void* _ctx) {
+    printf("backlight: %d %%\n", (*value * 100) / 256);
+}
+
+void backlight_example(void* p) {
+    BacklightApi* backlight_api = open_backlight("/dev/backlight");
+    if(backlight_api == NULL) return; // backlight not available, critical error
+
+    // subscribe to led state updates
+    subscribe_backlight_changes(backlight_api, handle_backlight_state, NULL);
+    // get current backlight value
+    uint8_t backlight_value;
+    if(read_backlight(backlight_api, &backlight_value, OsWaitForever)) {
+        printf(
+            "initial backlight: %d %%\n",
+            backlight_value * 100 / 256
+        );
+    }
+
+    // create compose to control led
+    Backlight backlight;
+    if(!init_led_composer(&backlight, backlight_api, UiLayerBelowNotify)) return;
+
+    // write RGB value
+    write_backlight(&backlight, 127);
+}
+```

+ 402 - 0
wiki/fw/api/Basic-API.md

@@ -0,0 +1,402 @@
+# Flipper universal registry implementation (FURI)
+
+Create record.
+
+```C
+// creates new record in registry and store pointer into it
+bool furi_create(const char* name, void* ptr);
+```
+
+Open record.
+
+```C
+// get stored pointer by its name
+void* furi_open(const char* name);
+```
+
+# Flipper Application control (flapp)
+
+## (in progress. Old verison)
+
+**`FlappHandler* flapp_start(void(app*)(void*), char* name, void* param)`**
+
+simply starts application. It call `app` entrypoint with `param` passed as argument. Useful for daemon applications and pop-up.
+
+
+**`FlappHandler* flapp_switch(void(app*)(void*), char* name, void* param)`**
+
+swtich to other application. System **stop current app**, call `app` entrypoint with `param` passed as argument and save current application entrypoint to `prev` field in current application registry. Useful for UI or "active" application.
+
+### Exit application
+
+**`void flapp_exit(void* param)`**
+
+stop current application (stop thread and clear application's stack), start application from `prev` entry in current application registry, cleanup current application registry.
+
+
+**`bool flapp_kill(FlappHandler* app)`**
+
+stop specified `app` without returning to `prev` application.
+
+**`void flapp_ready()`**
+
+If case one app depend on other, notify that app is ready.
+
+## Requirements
+
+* start daemon app
+* kill app
+* start child thread (kill when parent app was killed)
+* switch between UI apps
+
+**`bool flapp_on_exit(void(cb*)(void*), void* ctx);`**
+
+Register on-exit callback. It called before app will be killed. Not recommended to use in user scenario, only for system purpose (unregister callbacks, release mutexes, etc.)
+
+# ValueMutex
+
+The most simple concept is ValueMutex. It is wrapper around mutex and value pointer. You can take and give mutex to work with value and read and write value.
+
+```C
+typedef struct {
+    void* value;
+    size_t size;
+    osMutex mutex;
+    
+    osMutexDescriptor __static // some internals;
+} ValueMutex;
+```
+
+Create ValueMutex. Create instance of ValueMutex and call `init_mutex`.
+
+```C
+bool init_mutex(ValueMutex* valuemutex, void* value, size_t size) {
+    valuemutex->mutex = osMutexCreateStatic(valuemutex->__static);
+    if(valuemutex->mutex == NULL) return false;
+    
+    valuemutex->value = value;
+    valuemutex->size = size;
+    
+    return true;
+}
+```
+
+For work with data stored in mutex you should call `acquire_mutex`. It return pointer to data if success, NULL otherwise.
+
+You must release mutex after end of work with data. Call `release_mutex` and pass ValueData instance and pointer to data.
+
+```C
+void* acquire_mutex(ValueMutex* valuemutex, uint32_t timeout) {
+    if(osMutexTake(valuemutex->mutex, timeout) == osOk) {
+        return valuemutex->value;
+    } else {
+        return NULL;
+    }
+}
+
+// infinitly wait for mutex
+inline static void* acquire_mutex_block(ValueMutex* valuemutex) {
+    return acquire_mutex(valuemutex, OsWaitForever);
+}
+
+bool release_mutex(ValueMutex* valuemutex, void* value) {
+    if(value != valuemutex->value) return false;
+    
+    if(!osMutexGive(valuemutex->mutex)) return false;
+    
+    return true;
+}
+```
+Instead of take-access-give sequence you can use `read_mutex` and `write_mutex` functions. Both functions return true in case of success, false otherwise.
+
+```C
+bool read_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) {
+    void* value = acquire_mutex(valuemutex, timeout);
+    if(value == NULL || len > valuemutex->size) return false;
+    memcpy(data, value, len > 0 ? len : valuemutex->size):
+    if(!release_mutex(valuemutex, value)) return false;
+    
+    return true;
+}
+
+inline static bool read_mutex_block(ValueMutex* valuemutex, void* data, size_t len) {
+    return read_mutex(valuemutex, data, len, OsWaitForever);
+}
+
+bool write_mutex(ValueMutex* valuemutex, void* data, size_t len, uint32_t timeout) {
+    void* value = acquire_mutex(valuemutex, timeout);
+    if(value == NULL || len > valuemutex->size) return false;
+    memcpy(value, data, len > 0 ? len : valuemutex->size):
+    if(!release_mutex(valuemutex, value)) return false;
+    
+    return true;
+}
+
+inline static bool write_mutex_block(ValueMutex* valuemutex, void* data, size_t len) {
+    return write_mutex(valuemutex, data, len, OsWaitForever);
+}
+```
+
+## Usage example
+
+```C
+/*
+MANIFEST
+name="example-provider-app"
+stack=128
+*/
+void provider_app(void* _p) {
+    // create record with mutex
+    uint32_t example_value = 0;
+    ValueMutex example_mutex;
+    if(!init_mutex(&example_mutex, (void*)&example_value, sizeof(uint32_t))) {
+        printf("critical error\n");
+        flapp_exit(NULL);
+    }
+
+    if(furi_create("provider/example", (void*)&example_mutex)) {
+        printf("critical error\n");
+        flapp_exit(NULL);
+    }
+
+    // we are ready to provide record to other apps
+    flapp_ready();
+
+    // get value and increment it
+    while(1) {
+        uint32_t* value = acquire_mutex(&example_mutex, OsWaitForever);
+        if(value != NULL) {
+            value++;
+        }
+        release_mutex(&example_mutex, value);
+
+        osDelay(100);
+    }
+}
+
+/*
+MANIFEST
+name="example-consumer-app"
+stack=128
+require="example-provider-app"
+*/
+void consumer_app(void* _p) {
+    // this app run after flapp_ready call in all requirements app
+
+    // open mutex value
+    ValueMutex* counter_mutex = furi_open("provider/example");
+    if(counter_mutex == NULL) {
+        printf("critical error\n");
+        flapp_exit(NULL);
+    }
+
+    // continously read value every 1s
+    uint32_t counter;
+    while(1) {
+        if(read_mutex(counter_mutex, &counter, sizeof(counter), OsWaitForever)) {
+            printf("counter value: %d\n", counter);
+        }
+
+        osDelay(1000);
+    }
+}
+```
+
+
+# PubSub
+
+PubSub allows users to subscribe on notifies and notify subscribers. Notifier side can pass `void*` arg to subscriber callback, and also subscriber can set `void*` context pointer that pass into callback (you can see callback signature below).
+
+```C
+typedef void(PubSubCallback*)(void*, void*);
+
+typedef struct {
+    PubSubCallback cb;
+    void* ctx;
+} PubSubItem;
+
+typedef struct {
+    PubSub* self;
+    PubSubItem* item;
+} PubSubId;
+
+typedef struct {
+    PubSubItem items[NUM_OF_CALLBACKS];
+    PubSubId ids[NUM_OF_CALLBACKS]; ///< permanent links to item
+    size_t count; ///< count of callbacks
+} PubSub;
+```
+
+To create PubSub you should create PubSub instance and call `init_pubsub`.
+
+```C
+void init_pubsub(PubSub* pubsub) {
+    pubsub->count = 0;
+
+    for(size_t i = 0; i < NUM_OF_CALLBACKS; i++) {
+        pubsub->items[i].
+    }
+}
+```
+
+Use `subscribe_pubsub` to register your callback.
+
+```C
+// 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;
+}
+```
+
+Use `unsubscribe_pubsub` to unregister callback.
+
+```C
+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;
+}
+
+```
+
+Use `notify_pubsub` to notify subscribers.
+
+```C
+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);
+    }
+}
+```
+
+## Usage example
+
+```C
+/*
+MANIFEST
+name="test"
+stack=128
+*/
+
+void example_pubsub_handler(void* arg, void* ctx) {
+    printf("get %d from %s\n", *(uint32_t*)arg, (const char*)ctx);
+}
+
+void pubsub_test() {
+    const char* app_name = "test app";
+
+    PubSub example_pubsub;
+    init_pubsub(&example_pubsub);
+
+    if(!subscribe_pubsub(&example_pubsub, example_pubsub_handler, (void*)app_name)) {
+        printf("critical error\n");
+        flapp_exit(NULL);
+    }
+
+    uint32_t counter = 0;
+    while(1) {
+        notify_pubsub(&example_pubsub, (void*)&counter);
+        counter++;
+
+        osDelay(100);
+    }
+}
+```
+
+# ValueComposer
+
+```C
+typedef void(ValueComposerCallback)(void* ctx, void* state);
+
+void COPY_COMPOSE(void* ctx, void* state) {
+    read_mutex((ValueMutex*)ctx, state, 0);
+}
+
+typedef enum {
+    UiLayerBelowNotify
+    UiLayerNotify,
+    UiLayerAboveNotify
+} UiLayer;
+```
+
+```C
+ValueComposerHandle* add_compose_layer(
+    ValueComposer* composer, ValueComposerCallback cb, void* ctx, uint32_t layer
+);
+```
+
+```C
+bool remove_compose_layer(ValueComposerHandle* handle);
+```
+
+```C
+void request_compose(ValueComposerHandle* handle);
+```
+
+See [LED](LED-API) or [Display](Display-API) API for examples.
+
+# ValueManager
+
+More complicated concept is ValueManager. It is like ValueMutex, but user can subscribe to value updates.
+
+```C
+typedef struct {
+    ValueMutex value;
+    PubSub pubsub;
+} ValueManager;
+```
+
+First of all you can use value and pubsub part as showing above: aquire/release mutex, read value, subscribe/unsubscribe pubsub. There are two specific methods for ValueManager:
+
+`write_managed` acquire value, changes it and send notify with current value.
+
+```C
+bool write_managed(ValueManager* managed, void* data, size_t len, uint32_t timeout) {
+    void* value = acquire_mutex(managed->mutex, timeout);
+    if(value == NULL) return false;
+
+    memcpy(value, data, len):
+
+    notify_pubsub(&managed->pubsub, value);
+
+    if(!release_mutex(managed->mutex, value)) return false;
+    
+    return true;
+}
+```
+
+`commit_managed` works as `release_mutex` but send notify with current value.
+
+```C
+bool commit_managed(ValueManager* managed, void* value) {
+    if(value != managed->mutex->value) return false;
+
+    notify_pubsub(&managed->pubsub, value);
+    
+    if(!osMutexGive(managed->mutex)) return false;
+    
+    return true;
+}
+```

+ 68 - 0
wiki/fw/api/Display-API.md

@@ -0,0 +1,68 @@
+All display operations based on [u8g2](https://github.com/olikraus/u8g2) library.
+
+API available as `ValueComposer`.
+
+Driver call render callback and pass API contains u8g2 functions, instance and fonts:
+
+```C
+typedef struct {
+    u8g2_t* display;
+
+    void (*u8g2_SetFont)(u8g2_t *u8g2, const uint8_t  *font);
+    void (*u8g2_SetDrawColor)(u8g2_t *u8g2, uint8_t color);
+    void (*u8g2_SetFontMode)(u8g2_t *u8g2, uint8_t is_transparent);
+    u8g2_uint_t (*u8g2_DrawStr)(u8g2_t *u8g2, u8g2_uint_t x, u8g2_uint_t y, const char *str);
+
+    Fonts fonts;
+} DisplayApi;
+
+typedef struct {
+    const uint8_t* u8g2_font_6x10_mf;
+} Fonts;
+```
+
+First of all you can open display API instance by calling `open_display`
+
+```C
+/// Get display instance and API
+inline Display* open_display(const char* name) {
+    return (Display*)furi_open(name);
+}
+```
+
+Default display name is `/dev/display`.
+
+For draw something to display you need to register new layer in display composer:
+
+```C
+typedef void (RenderCallback*)(void* ctx, DisplayApi* api);
+
+inline ValueComposerHandle* init_display_composer(
+    Display* api, RenderCallback render, void* ctx, uint32_t layer) {
+    return add_compose_layer(api->composer, (ValueComposerCallback)render, ctx, layer);
+}
+```
+
+And then call `request_compose` every time you need to redraw your image.
+
+## Usage example
+
+```C
+
+void example_render(void* ctx, DisplayApi* api) {
+    api->u8g2_SetFont(api->display, display_api->fonts.u8g2_font_6x10_mf);
+    api->u8g2_SetDrawColor(api->display, 1);
+    api->u8g2_SetFontMode(api->display, 1);
+    api->u8g2_DrawStr(api->display, 2, 12, (char*)ctx); // ctx contains some static text
+}
+
+void u8g2_example(void* p) {
+    Display* display_api = open_display("/dev/display");
+    if(display_api == NULL) return; // display not available, critical error
+
+    ValueComposerHandle display_handler = init_display_composer(
+        display_api, example_render, (void*)"Hello world", UiLayerBelowNotify);
+
+    request_compose(display_handler);
+}
+```

+ 158 - 0
wiki/fw/api/HAL-API.md

@@ -0,0 +1,158 @@
+# GPIO
+
+GPIO defined as struct `GpioPin`.
+
+GPIO functions:
+
+```C
+// Init GPIO
+void gpio_init(GpioPin* gpio, GpioMode mode);
+
+typedef enum { GpioModeInput, GpioModeOutput, GpioModeOpenDrain } GpioMode;
+
+// write value to GPIO
+void gpio_write(GpioPin* gpio, bool state);
+
+// read value from GPIO, f = LOW, t = HIGH
+bool gpio_read(GpioPin* gpio);
+```
+
+When application is exited, system place pin to Z-state by calling `gpio_disable`.
+
+```C
+// put GPIO to Z-state (used for restore pin state on app exit)
+void gpio_disable(ValueMutex* gpio_mutex) {
+    GpioPin* gpio = acquire_mutex(gpio_mutex, 0);
+    gpio_init(gpio, GpioModeInput);
+    release_mutex(gpio_mutex, gpio);
+}
+```
+
+Available GPIO stored in FURI as `ValueMutex<GpioPin*>`.
+
+```C
+inline static ValueMutex* open_gpio_mutex(const char* name) {
+    ValueMutex* gpio_mutex = (ValueMutex*)furi_open(name);
+    if(gpio_mutex != NULL) flapp_on_exit(gpio_disable, gpio_mutex);
+
+    return gpio_mutex;
+}
+
+// helper
+inline static GpioPin* open_gpio(const char* name) {
+    ValueMutex* gpio_mutex = open_gpio(name);
+    return (GpioPin*)acquire_mutex(gpio_mutex, 0);
+}
+```
+
+## Available GPIO (target F2)
+
+* PA4
+* PA5
+* PA6
+* PA7
+* PB2
+* PC3
+* PC0
+* PC1
+* PB6
+* PB7
+* PA13
+* PA14
+* RFID_PULL
+* IR_TX
+* IBUTTON
+* VIBRO
+
+## Usage example
+
+```C
+void gpio_example() {
+    GpioPin* pin = open_gpio("PB6");
+
+    if(pin == NULL) {
+        printf("pin not available\n");
+        return;
+    }
+
+    gpio_init(pin, GpioModeOutput);
+
+    while(1) {
+        gpio_write(pin, true);
+        delay(100);
+        gpio_write(pin, false);
+        delay(100);
+    }
+}
+```
+
+# PWM
+
+PWM defined as `PwmPin`. To set PWM channel:
+
+```C
+void pwm_set(PwmPin* pwm, float value, float freq);
+```
+
+When application is exited, system disable pwm by calling `pwm_disable`.
+
+```C
+// put GPIO to Z-state (used for restore pin state on app exit)
+void pwm_disable(ValueMutex* pwm_mutex) {
+    PwmPin* pwm = acquire_mutex(pwm_mutex, 0);
+    pwm_set(pwm, 0., 0.);
+    release_mutex(pwm_mutex, pwm);
+}
+```
+
+Available PWM stored in FURI as `ValueMutex<PwmPin*>`.
+
+```C
+inline static ValueMutex* open_pwm_mutex(const char* name) {
+    ValueMutex* pwm_mutex = (ValueMutex*)furi_open(name);
+    if(pwm_mutex != NULL) flapp_on_exit(pwm_disable, pwm_mutex);
+
+    return pwm_mutex;
+}
+
+// helper
+inline static PwmPin* open_pwm(const char* name) {
+    ValueMutex* pwm_mutex = open_gpio(name);
+    return (PwmPin*)acquire_mutex(pwm_mutex, 0);
+}
+```
+
+## Available PWM (target F2)
+
+* SPEAKER
+* RFID_OUT
+
+## Usage example
+
+```C
+void sound_example() {
+    PwmPin* speaker = open_pwm("SPEAKER");
+
+    if(speaker == NULL) {
+        printf("speaker not available\n");
+        return;
+    }
+
+    while(1) {
+        pwm_set(speaker, 1000., 0.1);
+        delay(2);
+        pwm_set(speaker, 110., 0.5);
+        delay(198);
+        pwm_set(speaker, 330., 0.5);
+        delay(200);
+    }
+}
+```
+
+# ADC
+
+Coming soon...
+
+# I2C
+
+Coming soon...

+ 4 - 4
wiki/fw/api/API-Input.md → wiki/fw/api/Input-API.md

@@ -13,7 +13,7 @@ You can get API instance by calling `open_input`:
 ```C
 /// Get input struct
 inline Input* open_input(const char* name) {
-    return furi_open(name);
+    return (Input*)furi_open(name);
 }
 ```
 
@@ -37,8 +37,8 @@ To read buttons state you should use `read_state` function:
 
 ```C
 /// read current state of all buttons. Return true if success, false otherwise
-inline bool read_state(ValueMutex* state, InputState* value, uint32_t timeout) {
-    return read_mutex(state, (void*)value, sizeof(InputState), timeout);
+inline bool read_state(Input* api, InputState* value, uint32_t timeout) {
+    return read_mutex(api->state, (void*)value, sizeof(InputState), timeout);
 }
 ```
 
@@ -94,7 +94,7 @@ void input_example(void* p) {
     // blocking way
     InputState state;
     while(1) {
-        if(read_state(input->state, &state, OsWaitForever)) {
+        if(read_state(input, &state, OsWaitForever)) {
             if(state.up) {
                 printf("up is pressed");
                 delay(1000);

+ 110 - 0
wiki/fw/api/LED-API.md

@@ -0,0 +1,110 @@
+LED state describes by struct:
+
+```C
+typedef struct {
+    uint8_t red;
+    uint8_t green;
+    uint8_t blue; 
+} Rgb;
+```
+
+LED API provided by struct:
+
+```C
+typedef struct {
+    ValueComposer* composer; /// every app add its value to compose, <Rgb*>
+    ValueManager* state; /// LED value state and changes <Rgb*>
+} LedApi;
+```
+
+You can get API instance by calling `open_led`:
+
+```C
+/// Add new layer to LED:
+inline LedApi* open_led(const char* name) {
+    return (LedApi*)furi_open(name);
+}
+```
+
+Default system led is `/dev/led`.
+
+To read current led state you should use `read_led` function:
+
+```C
+/// return true if success, false otherwise
+inline bool read_led(LedApi* led, Rgb* value, uint32_t timeout) {
+    return read_mutex(led->state->value, (void*)value, sizeof(Rgb), timeout);
+}
+```
+
+Also you can subscribe to led state changes:
+
+Use `subscribe_led_changes` to register your callback:
+
+```C
+/// return true if success, false otherwise
+inline bool subscribe_led_changes(LedApi* led, void(*cb)(Rgb*, void*), void* ctx) {
+    return subscribe_pubsub(led->state->pubsub, void(*)(void*, void*)(cb), ctx);
+}
+```
+
+Userspace helpers
+
+```C
+typedef struct {
+    Rgb value;
+    ValueMutex value_mutex;
+    ValueComposerHandle* composer_handle;
+} SystemLed;
+
+inline bool init_led_composer(SystemLed* led, LedApi* api, uint32_t layer) {
+    if(!init_mutex(&led->value_mutex, (void*)&led->value, sizeof(Rgb))) {
+        return false;
+    }
+    led->composer_handle = add_compose_layer(
+        api->composer, COPY_COMPOSE, &led->value_mutex, layer
+    ); // just copy led state on update
+
+    return led->composer_handle != NULL;
+}
+
+inline void write_led(SystemLed* led, Rgb* value) {
+    write_mutex(&led->value_mutex, (void*)value, sizeof(Rgb), OsWaitForever);
+    request_compose(led->composer_handle);
+}
+```
+
+
+## Usage example
+
+```C
+
+void handle_led_state(Rgb* rgb, void* _ctx) {
+    printf("led: #%02X%02X%02X\n", rgb->red, rgb->green, rgb->blue);
+}
+
+void led_example(void* p) {
+    LedApi* led_api = open_led("/dev/led");
+    if(led_api == NULL) return; // led not available, critical error
+
+    // subscribe to led state updates
+    subscribe_led_changes(led_api, handle_led_state, NULL);
+    // get current led value
+    Rgb led_value;
+    if(read_led(led_api, &led_value, OsWaitForever)) {
+        printf(
+            "initial led: #%02X%02X%02X\n",
+            led_value->red,
+            led_value->green,
+            led_value->blue
+        );
+    }
+
+    // create compose to control led
+    SystemLed system_led;
+    if(!init_led_composer(&system_led, led_api, UiLayerBelowNotify)) return;
+
+    // write RGB value
+    write_led(&system_led, &(Rgb{.red = 0xFA, green = 0xCE, .blue = 0x8D}));
+}
+```

+ 130 - 0
wiki/fw/api/SPI-Devices-API.md

@@ -0,0 +1,130 @@
+# SPI
+
+HAL struct `SPI_HandleTypeDef*` used for handling SPI info.
+
+For transmit/receive data use `spi_xfer` function:
+
+```C
+bool spi_xfer(
+    SPI_HandleTypeDef* spi,
+    uint8_t* tx_data, uint8_t* rx_data, size_t len,
+    PubSubCallback cb, void* ctx);
+```
+
+* `tx_data` and `rx_data` size must be equal (and equal `len`)
+* `cb` called after spi operation is completed, `(NULL, ctx)` passed to callback.
+
+Blocking verison:
+
+```C
+inline static bool spi_xfer_block(SPI_HandleTypeDef* spi, uint8_t* tx_data, uint8_t* rx_data, size_t len) {
+    semaphoreInfo s;
+    osSemaphore block = createSemaphoreStatic(s);
+    if(!spi_xfer(spi, tx_data, rx_data, len, RELEASE_SEMAPHORE, (void*)block)) {
+        osReleaseSemaphore(block);
+        return false;
+    }
+    osWaitSemaphore(block);
+    return false;
+}
+```
+
+## SPI Bus
+
+Common implementation of SPI bus: serial interface + CS pin
+
+```C
+typedef struct {
+    GpioPin* cs; ///< CS pin
+    ValueMutex* spi; ///< <SPI_HandleTypeDef*>
+} SpiBus;
+```
+
+## SPI device
+
+For dedicated work with one device there is `SpiDevice` entity. It contains ValueMutex around SpiBus: after you acquire device you can acquire spi to work with it (don't forget SPI bus is shared around many device, release it after every transaction as quick as possible).
+
+```C
+typedef struct {
+    ValueMutex* bus; ///< <SpiBus*>
+} SpiDevice;
+```
+
+## SPI IRQ device
+
+Many devices (like CC1101 and NFC) present as SPI bus and IRQ line. For work with it there is special entity `SpiIrqDevice`. Use `subscribe_pubsub` for subscribinq to irq events.
+
+```C
+typedef struct {
+    ValueMutex* bus; ///< <SpiBus*>
+    PubSub* irq;
+} SpiIrqDevice;
+```
+
+## Display device
+
+Special implementation of SPI bus: serial interface + CS, Res, D/I lines.
+
+```C
+typedef struct {
+    GpioPin* cs; ///< CS pin
+    GpioPin* res; ///< reset pin
+    GpioPin* di; ///< D/I pin
+    ValueMutex* spi; ///< <SPI_HandleTypeDef*>
+} DisplayBus;
+
+```C
+typedef struct {
+    ValueMutex* bus; ///< <DisplayBus*>
+} DisplayDevice;
+```
+
+# SPI devices (F2)
+
+* `/dev/sdcard` - SD card SPI, `SpiDevice`
+* `/dev/cc1101_bus` - Sub-GHz radio (CC1101), `SpiIrqDevice`
+* `/dev/nfc` - NFC (ST25R3916), `SpiIrqDevice`
+* `/dev/display` - `DisplayDevice`
+* `/dev/spiext` - External SPI (warning! Lock PA4, PA5, PA6, PA7)
+
+### Application example
+
+```C
+// Be careful, this function called from IRQ context
+void handle_irq(void* _arg, void* _ctx) {
+}
+
+void cc1101_example() {
+    SpiIrqDevice* cc1101_device = open_input("/dev/cc1101_bus");
+    if(cc1101_device == NULL) return; // bus not available, critical error
+
+    subscribe_pubsub(cc1101_device->irq, handle_irq, NULL);
+
+    {
+        // acquire device as device bus
+        SpiBus* spi_bus = acquire_mutex(cc1101_device->bus, 0);
+        if(spi_bus == NULL) {
+            printf("Device busy\n");
+            // wait for device
+            spi_bus = acquire_mutex_block(cc1101_device->bus);
+        }
+        
+        // make transaction
+        uint8_t request[4] = {0xDE, 0xAD, 0xBE, 0xEF};
+        uint8_t response[4];
+
+        {
+            SPI_HandleTypeDef* spi = acquire_mutex_block(spi_bus->spi);
+
+            gpio_write(spi_bus->cs, false);
+            spi_xfer_block(spi, request, response, 4);
+            gpio_write(spi_bus->cs, true);
+
+            release_mutex(cc1101_device->spi, spi);
+        }
+
+        // release device (device bus)
+        release_mutex(cc1101_device->bus, spi_bus);
+    }
+}
+```