Просмотр исходного кода

[FL-977] Internal Storage (#455)

* Add littlefs submodule
* Furi: add mutex in logging, fix issues with corrupted printf
* ApiHal: disable debug traces in ble glue
* App-loader: more logs
* Passport: fix invalid DolphinState usage
* ApiHal, linker script: flash API is now aware of free space, complete abstraction layer for storage
* Internal Storage: littlefs based storage services with key value API. Migrate dolphin state to new storage API.
あく 4 лет назад
Родитель
Сommit
aa24484b99

+ 1 - 0
.github/CODEOWNERS

@@ -49,6 +49,7 @@ lib/cyfral/** @DrZlo13
 lib/drivers/** @skotopes @gornekich
 lib/fatfs/** @DrZlo13
 lib/fnv1a-hash/** @DrZlo13
+lib/littlefs/** @skotopes
 lib/mlib/** @skotopes
 lib/onewire/** @DrZlo13
 lib/qrcode/** @DrZlo13

+ 3 - 0
.gitmodules

@@ -7,3 +7,6 @@
 [submodule "applications/floopper-bloopper"]
 	path = applications/floopper-bloopper
 	url = https://github.com/Flipper-Zero/floopper-bloopper.git
+[submodule "lib/littlefs"]
+	path = lib/littlefs
+	url = https://github.com/littlefs-project/littlefs.git

+ 5 - 2
applications/app-loader/app-loader.c

@@ -58,7 +58,7 @@ void app_loader_thread_state_callback(FuriThreadState state, void* context) {
 }
 
 int32_t app_loader(void* p) {
-    FURI_LOG_I("APPLOADER", "Started");
+    FURI_LOG_I("app-loader", "Starting");
     state.thread = furi_thread_alloc();
     furi_thread_set_state_context(state.thread, &state);
     furi_thread_set_state_callback(state.thread, app_loader_thread_state_callback);
@@ -67,6 +67,7 @@ int32_t app_loader(void* p) {
     state.cli = furi_record_open("cli");
 
     // Main menu
+    FURI_LOG_I("app-loader", "Building main menu");
     with_value_mutex(
         menu_mutex, (Menu * menu) {
             for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) {
@@ -93,6 +94,7 @@ int32_t app_loader(void* p) {
         });
 
     // Plugins
+    FURI_LOG_I("app-loader", "Building plugins menu");
     with_value_mutex(
         menu_mutex, (Menu * menu) {
             MenuItem* menu_plugins =
@@ -124,6 +126,7 @@ int32_t app_loader(void* p) {
         });
 
     // Debug
+    FURI_LOG_I("app-loader", "Building debug menu");
     with_value_mutex(
         menu_mutex, (Menu * menu) {
             MenuItem* menu_debug =
@@ -159,7 +162,7 @@ int32_t app_loader(void* p) {
         (*FLIPPER_ON_SYSTEM_START[i])();
     }
 
-    FURI_LOG_I("APPLOADER", "OK");
+    FURI_LOG_I("app-loader", "Started");
 
     while(1) {
         osThreadSuspend(osThreadGetId());

+ 8 - 0
applications/applications.c

@@ -34,6 +34,7 @@ int32_t keypad_test(void* p);
 int32_t scene_app(void* p);
 int32_t passport(void* p);
 int32_t app_accessor(void* p);
+int32_t internal_storage_task(void* p);
 
 // On system start hooks declaration
 void nfc_cli_init();
@@ -73,6 +74,13 @@ const FlipperApplication FLIPPER_SERVICES[] = {
     {.app = sd_filesystem, .name = "sd_filesystem", .stack_size = 4096, .icon = A_Plugins_14},
 #endif
 
+#ifdef SRV_INTERNAL_STORAGE
+    {.app = internal_storage_task,
+     .name = "internal_storage",
+     .stack_size = 2048,
+     .icon = A_Plugins_14},
+#endif
+
 #ifdef SRV_DOLPHIN
     {.app = dolphin_task, .name = "dolphin_task", .stack_size = 1024, .icon = A_Plugins_14},
 #endif

+ 7 - 1
applications/applications.mk

@@ -17,6 +17,7 @@ SRV_POWER = 1
 SRV_BT = 1
 SRV_CLI = 1
 SRV_SD_FILESYSTEM = 1
+SRV_INTERNAL_STORAGE = 1
 SRV_DOLPHIN = 1
 
 # Main Apps
@@ -288,11 +289,16 @@ ifeq ($(SRV_GUI), 1)
 CFLAGS		+= -DSRV_GUI
 endif
 
-SRV_SD_FILESYSTEM	?= 0
+SRV_SD_FILESYSTEM ?= 0
 ifeq ($(SRV_SD_FILESYSTEM), 1)
 CFLAGS		+= -DSRV_SD_FILESYSTEM
 endif
 
+SRV_INTERNAL_STORAGE ?= 0
+ifeq ($(SRV_INTERNAL_STORAGE), 1)
+CFLAGS		+= -DSRV_INTERNAL_STORAGE
+endif
+
 SRV_INPUT	?= 0
 ifeq ($(SRV_INPUT), 1)
 CFLAGS		+= -DSRV_INPUT

+ 91 - 39
applications/dolphin/dolphin_state.c

@@ -1,77 +1,129 @@
 #include "dolphin_state.h"
+
+#include <internal-storage/internal-storage.h>
 #include <furi.h>
-#include <api-hal.h>
 #include <math.h>
 
+#define DOLPHIN_STORE_KEY "dolphin_state"
+#define DOLPHIN_STORE_HEADER_MAGIC 0xD0
+#define DOLPHIN_STORE_HEADER_VERSION 0x01
+#define DOLPHIN_LVL_THRESHOLD 20.0f
+
+typedef struct {
+    uint8_t magic;
+    uint8_t version;
+    uint8_t checksum;
+    uint8_t flags;
+    uint32_t timestamp;
+} DolphinStoreHeader;
+
+typedef struct {
+    uint32_t limit_ibutton;
+    uint32_t limit_nfc;
+    uint32_t limit_ir;
+    uint32_t limit_rfid;
+
+    uint32_t flags;
+    uint32_t icounter;
+    uint32_t butthurt;
+} DolphinStoreData;
+
+typedef struct {
+    DolphinStoreHeader header;
+    DolphinStoreData data;
+} DolphinStore;
+
+struct DolphinState {
+    InternalStorage* internal_storage;
+    DolphinStoreData data;
+};
+
 DolphinState* dolphin_state_alloc() {
     DolphinState* dolphin_state = furi_alloc(sizeof(DolphinState));
+    dolphin_state->internal_storage = furi_record_open("internal-storage");
     return dolphin_state;
 }
 
 void dolphin_state_free(DolphinState* dolphin_state) {
+    furi_record_close("internal-storage");
     free(dolphin_state);
 }
 
 bool dolphin_state_save(DolphinState* dolphin_state) {
-    if(!api_hal_flash_erase(DOLPHIN_DATA_PAGE, 1)) {
-        return false;
-    }
-
+    DolphinStore store;
+    FURI_LOG_I("dolphin-state", "Saving state to internal-storage");
+    // Calculate checksum
     uint8_t* source = (uint8_t*)&dolphin_state->data;
     uint8_t checksum = 0;
-    for(size_t i = 0; i < sizeof(DolphinData); i++) {
+    for(size_t i = 0; i < sizeof(DolphinStoreData); i++) {
         checksum += source[i];
     }
-    DolphinDataHeader header;
-    header.magic = DOLPHIN_DATA_HEADER_MAGIC;
-    header.version = DOLPHIN_DATA_HEADER_VERSION;
-    header.checksum = checksum;
-    header.flags = 0;
-    header.timestamp = 0;
-    if(!api_hal_flash_write_dword(DOLPHIN_DATA_HEADER_ADDRESS, *(uint64_t*)&header)) {
+    // Set header
+    store.header.magic = DOLPHIN_STORE_HEADER_MAGIC;
+    store.header.version = DOLPHIN_STORE_HEADER_VERSION;
+    store.header.checksum = checksum;
+    store.header.flags = 0;
+    store.header.timestamp = 0;
+    // Set data
+    store.data = dolphin_state->data;
+    // Store
+    int ret = internal_storage_write_key(
+        dolphin_state->internal_storage, DOLPHIN_STORE_KEY, (uint8_t*)&store, sizeof(DolphinStore));
+    if(ret != sizeof(DolphinStore)) {
+        FURI_LOG_E("dolphin-state", "Save failed. Storage returned: %d", ret);
         return false;
     }
 
-    uint8_t destination[sizeof(uint64_t)];
-    size_t block_count = sizeof(DolphinData) / sizeof(uint64_t) + 1;
-    size_t offset = 0;
-    for(size_t i = 0; i < block_count; i++) {
-        for(size_t n = 0; n < sizeof(uint64_t); n++) {
-            if(offset < sizeof(DolphinData)) {
-                destination[n] = source[offset];
-            } else {
-                destination[n] = 0;
-            }
-            offset++;
-        }
-        if(!api_hal_flash_write_dword(
-               DOLPHIN_DATA_DATA_ADDRESS + i * sizeof(uint64_t), *(uint64_t*)destination)) {
-            return false;
-        }
-    }
+    FURI_LOG_I("dolphin-state", "Saved");
     return true;
 }
 
 bool dolphin_state_load(DolphinState* dolphin_state) {
-    const DolphinDataHeader* header = (const DolphinDataHeader*)DOLPHIN_DATA_HEADER_ADDRESS;
-    if(header->magic == DOLPHIN_DATA_HEADER_MAGIC &&
-       header->version == DOLPHIN_DATA_HEADER_VERSION) {
+    DolphinStore store;
+    // Read Dolphin State Store
+    FURI_LOG_I("dolphin-state", "Loading state from internal-storage");
+    int ret = internal_storage_read_key(
+        dolphin_state->internal_storage, DOLPHIN_STORE_KEY, (uint8_t*)&store, sizeof(DolphinStore));
+    if(ret != sizeof(DolphinStore)) {
+        FURI_LOG_E("dolphin-state", "Load failed. Storage returned: %d", ret);
+        return false;
+    }
+
+    FURI_LOG_I("dolphin-state", "State loaded, verifying header");
+    if(store.header.magic == DOLPHIN_STORE_HEADER_MAGIC &&
+       store.header.version == DOLPHIN_STORE_HEADER_VERSION) {
+        FURI_LOG_I(
+            "dolphin-state",
+            "Magic(%d) and Version(%d) match",
+            store.header.magic,
+            store.header.version);
         uint8_t checksum = 0;
-        const uint8_t* source = (const uint8_t*)DOLPHIN_DATA_DATA_ADDRESS;
-        for(size_t i = 0; i < sizeof(DolphinData); i++) {
+        const uint8_t* source = (const uint8_t*)&store.data;
+        for(size_t i = 0; i < sizeof(DolphinStoreData); i++) {
             checksum += source[i];
         }
-        if(header->checksum == checksum) {
-            memcpy(
-                &dolphin_state->data, (const void*)DOLPHIN_DATA_DATA_ADDRESS, sizeof(DolphinData));
+        if(store.header.checksum == checksum) {
+            FURI_LOG_I("dolphin-state", "Checksum(%d) match", store.header.checksum);
+            dolphin_state->data = store.data;
             return true;
+        } else {
+            FURI_LOG_E(
+                "dolphin-state", "Checksum(%d != %d) mismatch", store.header.checksum, checksum);
         }
+    } else {
+        FURI_LOG_E(
+            "dolphin-state",
+            "Magic(%d != %d) and Version(%d != %d) mismatch",
+            store.header.magic,
+            DOLPHIN_STORE_HEADER_MAGIC,
+            store.header.version,
+            DOLPHIN_STORE_HEADER_VERSION);
     }
     return false;
 }
 
 void dolphin_state_clear(DolphinState* dolphin_state) {
-    memset(&dolphin_state->data, 0, sizeof(DolphinData));
+    memset(&dolphin_state->data, 0, sizeof(DolphinStoreData));
 }
 
 void dolphin_state_on_deed(DolphinState* dolphin_state, DolphinDeed deed) {

+ 0 - 32
applications/dolphin/dolphin_state.h

@@ -4,38 +4,6 @@
 #include <stdbool.h>
 #include <stdint.h>
 
-#define DOLPHIN_DATA_PAGE 0xC0
-#define DOLPHIN_DATA_HEADER_ADDRESS 0x080C0000U
-#define DOLPHIN_DATA_DATA_ADDRESS (DOLPHIN_DATA_HEADER_ADDRESS + sizeof(DolphinDataHeader))
-
-#define DOLPHIN_DATA_HEADER_MAGIC 0xD0
-#define DOLPHIN_DATA_HEADER_VERSION 0x01
-
-#define DOLPHIN_LVL_THRESHOLD 20.0f
-
-typedef struct {
-    uint8_t magic;
-    uint8_t version;
-    uint8_t checksum;
-    uint8_t flags;
-    uint32_t timestamp;
-} DolphinDataHeader;
-
-typedef struct {
-    uint32_t limit_ibutton;
-    uint32_t limit_nfc;
-    uint32_t limit_ir;
-    uint32_t limit_rfid;
-
-    uint32_t flags;
-    uint32_t icounter;
-    uint32_t butthurt;
-} DolphinData;
-
-struct DolphinState {
-    DolphinData data;
-};
-
 typedef struct DolphinState DolphinState;
 
 DolphinState* dolphin_state_alloc();

+ 5 - 3
applications/dolphin/passport/passport.c

@@ -78,14 +78,14 @@ static void render_callback(Canvas* canvas, void* ctx) {
 }
 
 int32_t passport(void* p) {
-    DolphinState _state;
+    DolphinState* dolphin_state = dolphin_state_alloc();
     ValueMutex state_mutex;
-    dolphin_state_load(&_state);
+    dolphin_state_load(dolphin_state);
 
     osMessageQueueId_t event_queue = osMessageQueueNew(2, sizeof(AppEvent), NULL);
     furi_check(event_queue);
 
-    if(!init_mutex(&state_mutex, &_state, sizeof(DolphinState))) {
+    if(!init_mutex(&state_mutex, dolphin_state, sizeof(DolphinState*))) {
         printf("[Passport] cannot create mutex\r\n");
         return 0;
     }
@@ -121,5 +121,7 @@ int32_t passport(void* p) {
 
     osMessageQueueDelete(event_queue);
 
+    dolphin_state_free(dolphin_state);
+
     return 0;
 }

+ 62 - 0
applications/internal-storage/internal-storage-i.h

@@ -0,0 +1,62 @@
+#pragma once
+
+#include "internal-storage.h"
+#include <furi.h>
+#include <api-hal.h>
+#include <lfs.h>
+
+#define INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE (1)
+
+struct InternalStorage {
+    osMessageQueueId_t queue;
+    InternalStorageState state;
+    const size_t start_address;
+    const size_t start_page;
+    struct lfs_config config;
+    lfs_t lfs;
+};
+
+typedef struct {
+    const char* key;
+    uint8_t* buffer;
+    size_t size;
+    int ret;
+} InternalStorageCommandKey;
+
+typedef void (*InternalStorageCommandFunction)(InternalStorage* internal_storage, void* data);
+
+typedef struct {
+    osThreadId thread;
+    InternalStorageCommandFunction function;
+    void* data;
+} InternalStorageCommand;
+
+int internal_storage_device_read(
+    const struct lfs_config* c,
+    lfs_block_t block,
+    lfs_off_t off,
+    void* buffer,
+    lfs_size_t size);
+
+int internal_storage_device_prog(
+    const struct lfs_config* c,
+    lfs_block_t block,
+    lfs_off_t off,
+    const void* buffer,
+    lfs_size_t size);
+
+int internal_storage_device_erase(const struct lfs_config* c, lfs_block_t block);
+
+int internal_storage_device_sync(const struct lfs_config* c);
+
+InternalStorage* internal_storage_alloc();
+
+void internal_storage_free(InternalStorage* internal_storage);
+
+int32_t internal_storage_task(void* p);
+
+void _internal_storage_read_key(InternalStorage* internal_storage, InternalStorageCommandKey* data);
+
+void _internal_storage_write_key(
+    InternalStorage* internal_storage,
+    InternalStorageCommandKey* data);

+ 229 - 0
applications/internal-storage/internal-storage.c

@@ -0,0 +1,229 @@
+#include "internal-storage-i.h"
+
+int internal_storage_device_read(
+    const struct lfs_config* c,
+    lfs_block_t block,
+    lfs_off_t off,
+    void* buffer,
+    lfs_size_t size) {
+    InternalStorage* internal_storage = c->context;
+    size_t address = internal_storage->start_address + block * c->block_size + off;
+
+    FURI_LOG_D(
+        "internal-storage",
+        "Device read: block %d, off %d, buffer: %p, size %d, translated address: %p",
+        block,
+        off,
+        buffer,
+        size,
+        address);
+
+    memcpy(buffer, (void*)address, size);
+
+    return 0;
+}
+
+int internal_storage_device_prog(
+    const struct lfs_config* c,
+    lfs_block_t block,
+    lfs_off_t off,
+    const void* buffer,
+    lfs_size_t size) {
+    InternalStorage* internal_storage = c->context;
+    size_t address = internal_storage->start_address + block * c->block_size + off;
+
+    FURI_LOG_D(
+        "internal-storage",
+        "Device prog: block %d, off %d, buffer: %p, size %d, translated address: %p",
+        block,
+        off,
+        buffer,
+        size,
+        address);
+
+    int ret = 0;
+    while(size > 0) {
+        if(!api_hal_flash_write_dword(address, *(uint64_t*)buffer)) {
+            ret = -1;
+            break;
+        }
+        address += c->prog_size;
+        buffer += c->prog_size;
+        size -= c->prog_size;
+    }
+
+    return ret;
+}
+
+int internal_storage_device_erase(const struct lfs_config* c, lfs_block_t block) {
+    InternalStorage* internal_storage = c->context;
+    size_t page = internal_storage->start_page + block;
+
+    FURI_LOG_D("internal-storage", "Device erase: page %d, translated page: %d", block, page);
+
+    if(api_hal_flash_erase(page, 1)) {
+        return 0;
+    } else {
+        return -1;
+    }
+}
+
+int internal_storage_device_sync(const struct lfs_config* c) {
+    FURI_LOG_D("internal-storage", "Device sync: skipping, cause ");
+    return 0;
+}
+
+InternalStorage* internal_storage_alloc() {
+    InternalStorage* internal_storage = furi_alloc(sizeof(InternalStorage));
+
+    internal_storage->queue = osMessageQueueNew(8, sizeof(InternalStorageCommand), NULL);
+
+    // Internal storage start address
+    internal_storage->state = InternalStorageStateInitializing;
+
+    // Internal storage start address
+    *(size_t*)(&internal_storage->start_address) = api_hal_flash_get_free_page_start_address();
+    *(size_t*)(&internal_storage->start_page) =
+        (internal_storage->start_address - api_hal_flash_get_base()) /
+        api_hal_flash_get_page_size();
+
+    // LFS configuration
+    // Glue and context
+    internal_storage->config.context = internal_storage;
+    internal_storage->config.read = internal_storage_device_read;
+    internal_storage->config.prog = internal_storage_device_prog;
+    internal_storage->config.erase = internal_storage_device_erase;
+    internal_storage->config.sync = internal_storage_device_sync;
+    // Block device description
+    internal_storage->config.read_size = api_hal_flash_get_read_block_size();
+    internal_storage->config.prog_size = api_hal_flash_get_write_block_size();
+    internal_storage->config.block_size = api_hal_flash_get_page_size();
+    internal_storage->config.block_count = api_hal_flash_get_free_page_count();
+    internal_storage->config.block_cycles = api_hal_flash_get_cycles_count();
+    internal_storage->config.cache_size = 16;
+    internal_storage->config.lookahead_size = 16;
+
+    return internal_storage;
+}
+
+void internal_storage_free(InternalStorage* internal_storage) {
+    furi_assert(internal_storage);
+    free(internal_storage);
+}
+
+int32_t internal_storage_task(void* p) {
+    FURI_LOG_I("internal-storage", "Starting");
+    InternalStorage* internal_storage = internal_storage_alloc();
+    FURI_LOG_I(
+        "internal-storage",
+        "Config: start %p, read %d, write %d, page size: %d, page count: %d, cycles: %d",
+        internal_storage->start_address,
+        internal_storage->config.read_size,
+        internal_storage->config.prog_size,
+        internal_storage->config.block_size,
+        internal_storage->config.block_count,
+        internal_storage->config.block_cycles);
+
+    int err = lfs_mount(&internal_storage->lfs, &internal_storage->config);
+    if(err == 0) {
+        FURI_LOG_I("internal-storage", "Mounted");
+        internal_storage->state = InternalStorageStateReady;
+    } else {
+        FURI_LOG_E("internal-storage", "Mount failed, formatting");
+        err = lfs_format(&internal_storage->lfs, &internal_storage->config);
+        if(err == 0) {
+            FURI_LOG_I("internal-storage", "Format successful, trying to mount");
+            err = lfs_mount(&internal_storage->lfs, &internal_storage->config);
+            if(err == 0) {
+                FURI_LOG_I("internal-storage", "Mounted");
+                internal_storage->state = InternalStorageStateReady;
+            } else {
+                FURI_LOG_E("internal-storage", "Mount after format failed");
+                internal_storage->state = InternalStorageStateBroken;
+            }
+        } else {
+            FURI_LOG_E("internal-storage", "Format failed");
+            internal_storage->state = InternalStorageStateBroken;
+        }
+    }
+
+    furi_record_create("internal-storage", internal_storage);
+
+    InternalStorageCommand command;
+    while(1) {
+        furi_check(
+            osMessageQueueGet(internal_storage->queue, &command, NULL, osWaitForever) == osOK);
+        command.function(internal_storage, command.data);
+        osThreadFlagsSet(command.thread, INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE);
+    }
+
+    lfs_unmount(&internal_storage->lfs);
+    internal_storage_free(internal_storage);
+
+    return 0;
+}
+
+void _internal_storage_read_key(InternalStorage* internal_storage, InternalStorageCommandKey* data) {
+    lfs_file_t file;
+    int ret = lfs_file_open(&internal_storage->lfs, &file, data->key, LFS_O_RDONLY);
+    if(ret == 0) {
+        ret = lfs_file_read(&internal_storage->lfs, &file, data->buffer, data->size);
+        lfs_file_close(&internal_storage->lfs, &file);
+    }
+    data->ret = ret;
+}
+
+int internal_storage_read_key(
+    InternalStorage* internal_storage,
+    const char* key,
+    uint8_t* buffer,
+    size_t size) {
+    osThreadId_t caller_thread = osThreadGetId();
+    if(caller_thread == 0) {
+        return -1;
+    }
+
+    InternalStorageCommandKey data = {.key = key, .buffer = buffer, .size = size, .ret = 0};
+    InternalStorageCommand command = {
+        .thread = caller_thread,
+        .function = (InternalStorageCommandFunction)_internal_storage_read_key,
+        .data = &data,
+    };
+    furi_check(osMessageQueuePut(internal_storage->queue, &command, 0, osWaitForever) == osOK);
+    osThreadFlagsWait(INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE, osFlagsWaitAny, osWaitForever);
+    return data.ret;
+}
+
+void _internal_storage_write_key(
+    InternalStorage* internal_storage,
+    InternalStorageCommandKey* data) {
+    lfs_file_t file;
+    int ret = lfs_file_open(
+        &internal_storage->lfs, &file, data->key, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
+    if(ret == 0) {
+        ret = lfs_file_write(&internal_storage->lfs, &file, data->buffer, data->size);
+        lfs_file_close(&internal_storage->lfs, &file);
+    }
+    data->ret = ret;
+}
+
+int internal_storage_write_key(
+    InternalStorage* internal_storage,
+    const char* key,
+    uint8_t* buffer,
+    size_t size) {
+    osThreadId_t caller_thread = osThreadGetId();
+    if(caller_thread == 0) {
+        return -1;
+    }
+
+    InternalStorageCommandKey data = {.key = key, .buffer = buffer, .size = size, .ret = 0};
+    InternalStorageCommand command = {
+        .thread = caller_thread,
+        .function = (InternalStorageCommandFunction)_internal_storage_write_key,
+        .data = &data,
+    };
+    furi_check(osMessageQueuePut(internal_storage->queue, &command, 0, osWaitForever) == osOK);
+    osThreadFlagsWait(INTERNAL_STORAGE_THREAD_FLAG_CALL_COMPLETE, osFlagsWaitAny, osWaitForever);
+    return data.ret;
+}

+ 40 - 0
applications/internal-storage/internal-storage.h

@@ -0,0 +1,40 @@
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+/* Internal storage state */
+typedef enum {
+    InternalStorageStateInitializing,
+    InternalStorageStateReady,
+    InternalStorageStateBroken,
+} InternalStorageState;
+
+typedef struct InternalStorage InternalStorage;
+
+/** Read key, blocking api
+ * @param internal_storage - InternalStorage instance
+ * @param key - file name to read data from
+ * @param buffer - pointer to data buffer
+ * @param size - buffer size
+ * @return negative on error, otherwise data read
+ */
+int internal_storage_read_key(
+    InternalStorage* internal_storage,
+    const char* key,
+    uint8_t* buffer,
+    size_t size);
+
+/** Write key, blocking api
+ * @param internal_storage - InternalStorage instance
+ * @param key - file name to store data to
+ * @param buffer - pointer to data buffer
+ * @param size - buffer size
+ * @return negative on error, otherwise data written
+ */
+int internal_storage_write_key(
+    InternalStorage* internal_storage,
+    const char* key,
+    uint8_t* buffer,
+    size_t size);

+ 5 - 0
core/furi/log.c

@@ -1,12 +1,14 @@
 #include "log.h"
 #include <stm32wbxx_hal.h>
 #include "check.h"
+#include <cmsis_os2.h>
 
 typedef struct {
     FuriLogLevel log_level;
     FuriLogPrint print;
     FuriLogVPrint vprint;
     FuriLogTimestamp timetamp;
+    osMutexId_t mutex;
 } FuriLogParams;
 
 static FuriLogParams furi_log;
@@ -17,14 +19,17 @@ void furi_log_init() {
     furi_log.print = printf;
     furi_log.vprint = vprintf;
     furi_log.timetamp = HAL_GetTick;
+    furi_log.mutex = osMutexNew(NULL);
 }
 
 void furi_log_print(FuriLogLevel level, const char* format, ...) {
     va_list args;
     va_start(args, format);
     if(level <= furi_log.log_level) {
+        osMutexAcquire(furi_log.mutex, osWaitForever);
         furi_log.print("%lu ", furi_log.timetamp());
         furi_log.vprint(format, args);
+        osMutexRelease(furi_log.mutex);
     }
     va_end(args);
 }

+ 54 - 1
firmware/targets/f5/api-hal/api-hal-flash.c

@@ -1,6 +1,59 @@
 #include <api-hal-flash.h>
 #include <api-hal-bt.h>
 #include <stm32wbxx.h>
+#include <furi.h>
+
+/* Free flash space borders, exported by linker */
+extern const void __free_flash_start__;
+extern const void __free_flash_end__;
+
+#define API_HAL_FLASH_READ_BLOCK 8
+#define API_HAL_FLASH_WRITE_BLOCK 8
+#define API_HAL_FLASH_PAGE_SIZE 4096
+#define API_HAL_FLASH_CYCLES_COUNT 10000
+
+size_t api_hal_flash_get_base() {
+    return FLASH_BASE;
+}
+
+size_t api_hal_flash_get_read_block_size() {
+    return API_HAL_FLASH_READ_BLOCK;
+}
+
+size_t api_hal_flash_get_write_block_size() {
+    return API_HAL_FLASH_WRITE_BLOCK;
+}
+
+size_t api_hal_flash_get_page_size() {
+    return API_HAL_FLASH_PAGE_SIZE;
+}
+
+size_t api_hal_flash_get_cycles_count() {
+    return API_HAL_FLASH_CYCLES_COUNT;
+}
+
+const void* api_hal_flash_get_free_start_address() {
+    return &__free_flash_start__;
+}
+
+const void* api_hal_flash_get_free_end_address() {
+    return &__free_flash_end__;
+}
+
+size_t api_hal_flash_get_free_page_start_address() {
+    size_t start = (size_t)api_hal_flash_get_free_start_address();
+    size_t page_start = start - start % API_HAL_FLASH_PAGE_SIZE;
+    if (page_start != start) {
+        page_start += API_HAL_FLASH_PAGE_SIZE;
+    }
+    return page_start;
+}
+
+size_t api_hal_flash_get_free_page_count() {
+    size_t end = (size_t)api_hal_flash_get_free_end_address();
+    size_t page_start = (size_t)api_hal_flash_get_free_page_start_address();
+    return (end-page_start) / API_HAL_FLASH_PAGE_SIZE;
+}
 
 bool api_hal_flash_erase(uint8_t page, uint8_t count) {
     if (!api_hal_bt_lock_flash()) {
@@ -25,7 +78,7 @@ bool api_hal_flash_write_dword(size_t address, uint64_t data) {
     return status == HAL_OK;
 }
 
-bool api_hal_flash_write_row(size_t address, size_t source_address) {
+bool api_hal_flash_write_dword_from(size_t address, size_t source_address) {
     if (!api_hal_bt_lock_flash()) {
         return false;
     }

+ 48 - 3
firmware/targets/f5/api-hal/api-hal-flash.h

@@ -4,6 +4,51 @@
 #include <stdint.h>
 #include <stddef.h>
 
+/** Get flash base address
+ * @return pointer to flash base
+ */
+size_t api_hal_flash_get_base();
+
+/** Get flash read block size
+ * @return size in bytes
+ */
+size_t api_hal_flash_get_read_block_size();
+
+/** Get flash write block size
+ * @return size in bytes
+ */
+size_t api_hal_flash_get_write_block_size();
+
+/** Get flash page size
+ * @return size in bytes
+ */
+size_t api_hal_flash_get_page_size();
+
+/** Get expected flash cycles count
+ * @return count of erase-write operations 
+ */
+size_t api_hal_flash_get_cycles_count();
+
+/** Get free flash start address
+ * @return pointer to free region start
+ */
+const void* api_hal_flash_get_free_start_address();
+
+/** Get free flash end address
+ * @return pointer to free region end
+ */
+const void* api_hal_flash_get_free_end_address();
+
+/** Get first free page start address
+ * @return first free page memory address
+ */
+size_t api_hal_flash_get_free_page_start_address();
+
+/** Get free page count
+ * @return free page count
+ */
+size_t api_hal_flash_get_free_page_count();
+
 /*
  * Erase Flash
  * Locking operation, uses HSEM to manage shared access.
@@ -21,9 +66,9 @@ bool api_hal_flash_erase(uint8_t page, uint8_t count);
 bool api_hal_flash_write_dword(size_t address, uint64_t data);
 
 /*
- * Write page (4096 bytes or 64 rows of double words).
+ * Write double word (64 bits) from address
  * Locking operation, uses HSEM to manage shared access.
- * @param address - destination address, must be page aligned
+ * @param address - destination address, must be block aligned
  * @param source_address - source address
  */
-bool api_hal_flash_write_page(size_t address, size_t source_address);
+bool api_hal_flash_write_dword_from(size_t address, size_t source_address);

+ 1 - 1
firmware/targets/f5/ble-glue/tl_dbg_conf.h

@@ -50,7 +50,7 @@ extern UART_HandleTypeDef DEBUG_UART;
 #define TL_HCI_EVT_DBG_EN       0   /* Reports BLE Asynchronous Events received from CPU2 */
 #define TL_HCI_EVT_DBG_RAW_EN   0   /* Reports raw data BLE Asynchronous Events received from CPU2 */
 
-#define TL_MM_DBG_EN            1   /* Reports the informations of the buffer released to CPU2 */
+#define TL_MM_DBG_EN            0   /* Reports the informations of the buffer released to CPU2 */
 
 /**
  * Macro definition

+ 7 - 1
firmware/targets/f5/stm32wb55xx_flash_cm4_boot.ld

@@ -168,7 +168,13 @@ SECTIONS
     . = ALIGN(8);
   } >RAM1
 
-  
+  /* Free Flash space, that can be used for internal storage */
+  .free_flash(NOLOAD):
+  {
+    __free_flash_start__ = .;
+    . = ORIGIN(FLASH) + LENGTH(FLASH);
+    __free_flash_end__ = .;
+  } >FLASH
 
   /* Remove information from the standard libraries */
   /DISCARD/ :

+ 9 - 3
firmware/targets/f5/stm32wb55xx_flash_cm4_no_boot.ld

@@ -49,8 +49,8 @@ ENTRY(Reset_Handler)
 /* Highest address of the user mode stack */
 _estack = 0x20030000;    /* 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 */
+_Min_Heap_Size = 0x400;      /* required amount of heap  */
+_Min_Stack_Size = 0x1000; /* required amount of stack */
 
 /* Specify the memory areas */
 MEMORY
@@ -168,7 +168,13 @@ SECTIONS
     . = ALIGN(8);
   } >RAM1
 
-  
+  /* Free Flash space, that can be used for internal storage */
+  .free_flash(NOLOAD):
+  {
+    __free_flash_start__ = .;
+    . = ORIGIN(FLASH) + LENGTH(FLASH);
+    __free_flash_end__ = .;
+  } >FLASH
 
   /* Remove information from the standard libraries */
   /DISCARD/ :

+ 7 - 0
lib/lib.mk

@@ -36,6 +36,13 @@ C_SOURCES		+= $(FATFS_DIR)/ff_gen_drv.c
 C_SOURCES		+= $(FATFS_DIR)/diskio.c
 C_SOURCES		+= $(FATFS_DIR)/option/unicode.c
 
+ifeq ($(SRV_INTERNAL_STORAGE), 1)
+LITTLEFS_DIR	= $(LIB_DIR)/littlefs
+CFLAGS			+= -I$(LITTLEFS_DIR)
+C_SOURCES		+= $(LITTLEFS_DIR)/lfs.c
+C_SOURCES		+= $(LITTLEFS_DIR)/lfs_util.c
+endif
+
 ifeq ($(APP_NFC), 1)
 ST25RFAL002_DIR	= $(LIB_DIR)/ST25RFAL002
 CFLAGS			+= -I$(ST25RFAL002_DIR)

+ 1 - 0
lib/littlefs

@@ -0,0 +1 @@
+Subproject commit 1863dc7883d82bd6ca79faa164b65341064d1c16