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

Make printf great again (#1438)

* Printf lib: wrap *printf* functions
* Printf lib, FW: drop sprintf. Dolphin: dump timestamp as is, wo asctime.
* FW: remove sniprintf, wrap assert functions
* Printf lib: wrap putc, puts, putchar
* Printf: a working but not thread-safe concept.
* Poorly wrap fflush
* stdglue: buffers
* Core: thread local buffers
* Core: move stdglue to thread api, add ability to get FuriThread instance of current thread.
* RPC tests: replace sprintf with snprintf
* Applications: use new stdout api
* Printf lib: wrap more printf-like and stdout functions
* Documentation
* Apps: snprintf size fixes

Co-authored-by: あく <alleteam@gmail.com>
SG 3 лет назад
Родитель
Сommit
bc34689ed6

+ 5 - 5
applications/cli/cli.c

@@ -439,9 +439,9 @@ void cli_session_open(Cli* cli, void* session) {
     cli->session = session;
     if(cli->session != NULL) {
         cli->session->init();
-        furi_stdglue_set_thread_stdout_callback(cli->session->tx_stdout);
+        furi_thread_set_stdout_callback(cli->session->tx_stdout);
     } else {
-        furi_stdglue_set_thread_stdout_callback(NULL);
+        furi_thread_set_stdout_callback(NULL);
     }
     furi_semaphore_release(cli->idle_sem);
     furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
@@ -455,7 +455,7 @@ void cli_session_close(Cli* cli) {
         cli->session->deinit();
     }
     cli->session = NULL;
-    furi_stdglue_set_thread_stdout_callback(NULL);
+    furi_thread_set_stdout_callback(NULL);
     furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
 }
 
@@ -469,9 +469,9 @@ int32_t cli_srv(void* p) {
     furi_record_create(RECORD_CLI, cli);
 
     if(cli->session != NULL) {
-        furi_stdglue_set_thread_stdout_callback(cli->session->tx_stdout);
+        furi_thread_set_stdout_callback(cli->session->tx_stdout);
     } else {
-        furi_stdglue_set_thread_stdout_callback(NULL);
+        furi_thread_set_stdout_callback(NULL);
     }
 
     if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) {

+ 1 - 1
applications/cli/cli_i.h

@@ -25,7 +25,7 @@ struct CliSession {
     void (*deinit)(void);
     size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout);
     void (*tx)(const uint8_t* buffer, size_t size);
-    void (*tx_stdout)(void* _cookie, const char* data, size_t size);
+    void (*tx_stdout)(const char* data, size_t size);
     bool (*is_connected)(void);
 };
 

+ 1 - 2
applications/cli/cli_vcp.c

@@ -277,8 +277,7 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) {
 #endif
 }
 
-static void cli_vcp_tx_stdout(void* _cookie, const char* data, size_t size) {
-    UNUSED(_cookie);
+static void cli_vcp_tx_stdout(const char* data, size_t size) {
     cli_vcp_tx((const uint8_t*)data, size);
 }
 

+ 5 - 5
applications/debug_tools/keypad_test.c

@@ -26,11 +26,11 @@ static void keypad_test_render_callback(Canvas* canvas, void* ctx) {
     canvas_clear(canvas);
     char strings[5][20];
 
-    sprintf(strings[0], "Ok: %d", state->ok);
-    sprintf(strings[1], "L: %d", state->left);
-    sprintf(strings[2], "R: %d", state->right);
-    sprintf(strings[3], "U: %d", state->up);
-    sprintf(strings[4], "D: %d", state->down);
+    snprintf(strings[0], 20, "Ok: %d", state->ok);
+    snprintf(strings[1], 20, "L: %d", state->left);
+    snprintf(strings[2], 20, "R: %d", state->right);
+    snprintf(strings[3], 20, "U: %d", state->up);
+    snprintf(strings[4], 20, "D: %d", state->down);
 
     canvas_set_font(canvas, FontPrimary);
     canvas_draw_str(canvas, 0, 10, "Keypad test");

+ 5 - 4
applications/desktop/views/desktop_view_debug.c

@@ -78,7 +78,6 @@ void desktop_debug_render(Canvas* canvas, void* model) {
         canvas_draw_str(canvas, 5, 50 + STATUS_BAR_Y_SHIFT, buffer);
 
     } else {
-        char buffer[64];
         Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
         DolphinStats stats = dolphin_stats(dolphin);
         furi_record_close(RECORD_DOLPHIN);
@@ -87,18 +86,20 @@ void desktop_debug_render(Canvas* canvas, void* model) {
         uint32_t remaining = dolphin_state_xp_to_levelup(m->icounter);
 
         canvas_set_font(canvas, FontSecondary);
-        snprintf(buffer, 64, "Icounter: %ld  Butthurt %ld", m->icounter, m->butthurt);
+        snprintf(buffer, sizeof(buffer), "Icounter: %ld  Butthurt %ld", m->icounter, m->butthurt);
         canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer);
 
         snprintf(
             buffer,
-            64,
+            sizeof(buffer),
             "Level: %ld  To level up: %ld",
             current_lvl,
             (remaining == (uint32_t)(-1) ? remaining : 0));
         canvas_draw_str(canvas, 5, 29 + STATUS_BAR_Y_SHIFT, buffer);
 
-        snprintf(buffer, 64, "%s", asctime(localtime((const time_t*)&m->timestamp)));
+        // even if timestamp is uint64_t, it's safe to cast it to uint32_t, because furi_hal_rtc_datetime_to_timestamp only returns uint32_t
+        snprintf(buffer, sizeof(buffer), "%ld", (uint32_t)m->timestamp);
+
         canvas_draw_str(canvas, 5, 39 + STATUS_BAR_Y_SHIFT, buffer);
         canvas_draw_str(canvas, 0, 49 + STATUS_BAR_Y_SHIFT, "[< >] icounter value   [ok] save");
     }

+ 4 - 4
applications/infrared/infrared_cli.c

@@ -27,7 +27,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
 
     if(infrared_worker_signal_is_decoded(received_signal)) {
         const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal);
-        buf_cnt = sniprintf(
+        buf_cnt = snprintf(
             buf,
             sizeof(buf),
             "%s, A:0x%0*lX, C:0x%0*lX%s\r\n",
@@ -43,13 +43,13 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv
         size_t timings_cnt;
         infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt);
 
-        buf_cnt = sniprintf(buf, sizeof(buf), "RAW, %d samples:\r\n", timings_cnt);
+        buf_cnt = snprintf(buf, sizeof(buf), "RAW, %d samples:\r\n", timings_cnt);
         cli_write(cli, (uint8_t*)buf, buf_cnt);
         for(size_t i = 0; i < timings_cnt; ++i) {
-            buf_cnt = sniprintf(buf, sizeof(buf), "%lu ", timings[i]);
+            buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[i]);
             cli_write(cli, (uint8_t*)buf, buf_cnt);
         }
-        buf_cnt = sniprintf(buf, sizeof(buf), "\r\n");
+        buf_cnt = snprintf(buf, sizeof(buf), "\r\n");
         cli_write(cli, (uint8_t*)buf, buf_cnt);
     }
 }

+ 1 - 1
applications/rpc/rpc_storage.c

@@ -541,7 +541,7 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont
         (void)md5sum_size;
         furi_assert(hash_size <= ((md5sum_size - 1) / 2));
         for(uint8_t i = 0; i < hash_size; i++) {
-            md5sum += sprintf(md5sum, "%02x", hash[i]);
+            md5sum += snprintf(md5sum, md5sum_size, "%02x", hash[i]);
         }
 
         free(hash);

+ 6 - 3
applications/subghz/scenes/subghz_scene_receiver_config.c

@@ -73,8 +73,9 @@ static void subghz_scene_receiver_config_set_frequency(VariableItem* item) {
 
     if(subghz->txrx->hopper_state == SubGhzHopperStateOFF) {
         char text_buf[10] = {0};
-        sprintf(
+        snprintf(
             text_buf,
+            sizeof(text_buf),
             "%lu.%02lu",
             subghz_setting_get_frequency(subghz->setting, index) / 1000000,
             (subghz_setting_get_frequency(subghz->setting, index) % 1000000) / 10000);
@@ -106,8 +107,9 @@ static void subghz_scene_receiver_config_set_hopping_runing(VariableItem* item)
     variable_item_set_current_value_text(item, hopping_text[index]);
     if(hopping_value[index] == SubGhzHopperStateOFF) {
         char text_buf[10] = {0};
-        sprintf(
+        snprintf(
             text_buf,
+            sizeof(text_buf),
             "%lu.%02lu",
             subghz_setting_get_default_frequency(subghz->setting) / 1000000,
             (subghz_setting_get_default_frequency(subghz->setting) % 1000000) / 10000);
@@ -160,8 +162,9 @@ void subghz_scene_receiver_config_on_enter(void* context) {
         subghz->scene_manager, SubGhzSceneReceiverConfig, (uint32_t)item);
     variable_item_set_current_value_index(item, value_index);
     char text_buf[10] = {0};
-    sprintf(
+    snprintf(
         text_buf,
+        sizeof(text_buf),
         "%lu.%02lu",
         subghz_setting_get_frequency(subghz->setting, value_index) / 1000000,
         (subghz_setting_get_frequency(subghz->setting, value_index) % 1000000) / 10000);

+ 8 - 7
applications/unit_tests/rpc/rpc_test.c

@@ -189,8 +189,9 @@ static void clean_directory(Storage* fs_api, const char* clean_dir) {
         FileInfo fileinfo;
         char* name = malloc(MAX_NAME_LENGTH + 1);
         while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) {
-            char* fullname = malloc(strlen(clean_dir) + strlen(name) + 1 + 1);
-            sprintf(fullname, "%s/%s", clean_dir, name);
+            size_t size = strlen(clean_dir) + strlen(name) + 1 + 1;
+            char* fullname = malloc(size);
+            snprintf(fullname, size, "%s/%s", clean_dir, name);
             if(fileinfo.flags & FSF_DIRECTORY) {
                 clean_directory(fs_api, fullname);
             }
@@ -1226,7 +1227,7 @@ MU_TEST(test_storage_mkdir) {
     mu_check(test_is_exists(TEST_DIR "dir2"));
 }
 
-static void test_storage_calculate_md5sum(const char* path, char* md5sum) {
+static void test_storage_calculate_md5sum(const char* path, char* md5sum, size_t md5sum_size) {
     Storage* api = furi_record_open(RECORD_STORAGE);
     File* file = storage_file_alloc(api);
 
@@ -1247,7 +1248,7 @@ static void test_storage_calculate_md5sum(const char* path, char* md5sum) {
         free(md5_ctx);
 
         for(uint8_t i = 0; i < hash_size; i++) {
-            md5sum += sprintf(md5sum, "%02x", hash[i]);
+            md5sum += snprintf(md5sum, md5sum_size, "%02x", hash[i]);
         }
 
         free(hash);
@@ -1299,9 +1300,9 @@ MU_TEST(test_storage_md5sum) {
     test_create_file(TEST_DIR "file1.txt", 0);
     test_create_file(TEST_DIR "file2.txt", 1);
     test_create_file(TEST_DIR "file3.txt", 512);
-    test_storage_calculate_md5sum(TEST_DIR "file1.txt", md5sum1);
-    test_storage_calculate_md5sum(TEST_DIR "file2.txt", md5sum2);
-    test_storage_calculate_md5sum(TEST_DIR "file3.txt", md5sum3);
+    test_storage_calculate_md5sum(TEST_DIR "file1.txt", md5sum1, MD5SUM_SIZE * 2 + 1);
+    test_storage_calculate_md5sum(TEST_DIR "file2.txt", md5sum2, MD5SUM_SIZE * 2 + 1);
+    test_storage_calculate_md5sum(TEST_DIR "file3.txt", md5sum3, MD5SUM_SIZE * 2 + 1);
 
     test_storage_md5sum_run(TEST_DIR "file1.txt", ++command_id, md5sum1, PB_CommandStatus_OK);
     test_storage_md5sum_run(TEST_DIR "file1.txt", ++command_id, md5sum1, PB_CommandStatus_OK);

+ 1 - 2
firmware.scons

@@ -164,8 +164,6 @@ fwenv.AppendUnique(
         "-Wl,--wrap,_free_r",
         "-Wl,--wrap,_calloc_r",
         "-Wl,--wrap,_realloc_r",
-        "-u",
-        "_printf_float",
         "-n",
         "-Xlinker",
         "-Map=${TARGET}.map",
@@ -181,6 +179,7 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program(
     "${FIRMWARE_BUILD_CFG}",
     sources,
     LIBS=[
+        "print",
         "flipper${TARGET_HW}",
         "furi",
         "freertos",

+ 0 - 102
furi/core/stdglue.c

@@ -1,102 +0,0 @@
-#include "stdglue.h"
-#include "check.h"
-#include "memmgr.h"
-
-#include <FreeRTOS.h>
-#include <task.h>
-
-#include <furi_hal.h>
-#include <m-dict.h>
-
-DICT_DEF2(
-    FuriStdglueCallbackDict,
-    uint32_t,
-    M_DEFAULT_OPLIST,
-    FuriStdglueWriteCallback,
-    M_PTR_OPLIST)
-
-typedef struct {
-    FuriMutex* mutex;
-    FuriStdglueCallbackDict_t thread_outputs;
-} FuriStdglue;
-
-static FuriStdglue* furi_stdglue = NULL;
-
-static ssize_t stdout_write(void* _cookie, const char* data, size_t size) {
-    furi_assert(furi_stdglue);
-    bool consumed = false;
-    FuriThreadId task_id = furi_thread_get_current_id();
-    if(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING && task_id &&
-       furi_mutex_acquire(furi_stdglue->mutex, FuriWaitForever) == FuriStatusOk) {
-        // We are in the thread context
-        // Handle thread callbacks
-        FuriStdglueWriteCallback* callback_ptr =
-            FuriStdglueCallbackDict_get(furi_stdglue->thread_outputs, (uint32_t)task_id);
-        if(callback_ptr) {
-            (*callback_ptr)(_cookie, data, size);
-            consumed = true;
-        }
-        furi_check(furi_mutex_release(furi_stdglue->mutex) == FuriStatusOk);
-    }
-    // Flush
-    if(data == 0) {
-        /*
-         * This means that we should flush internal buffers.  Since we
-         * don't we just return.  (Remember, "handle" == -1 means that all
-         * handles should be flushed.)
-         */
-        return 0;
-    }
-    // Debug uart
-    if(!consumed) furi_hal_console_tx((const uint8_t*)data, size);
-    // All data consumed
-    return size;
-}
-
-void furi_stdglue_init() {
-    furi_stdglue = malloc(sizeof(FuriStdglue));
-    // Init outputs structures
-    furi_stdglue->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
-    furi_check(furi_stdglue->mutex);
-    FuriStdglueCallbackDict_init(furi_stdglue->thread_outputs);
-    // Prepare and set stdout descriptor
-    FILE* fp = fopencookie(
-        NULL,
-        "w",
-        (cookie_io_functions_t){
-            .read = NULL,
-            .write = stdout_write,
-            .seek = NULL,
-            .close = NULL,
-        });
-    setvbuf(fp, NULL, _IOLBF, 0);
-    stdout = fp;
-}
-
-bool furi_stdglue_set_thread_stdout_callback(FuriStdglueWriteCallback callback) {
-    furi_assert(furi_stdglue);
-    FuriThreadId task_id = furi_thread_get_current_id();
-    if(task_id) {
-        furi_check(furi_mutex_acquire(furi_stdglue->mutex, FuriWaitForever) == FuriStatusOk);
-        if(callback) {
-            FuriStdglueCallbackDict_set_at(
-                furi_stdglue->thread_outputs, (uint32_t)task_id, callback);
-        } else {
-            FuriStdglueCallbackDict_erase(furi_stdglue->thread_outputs, (uint32_t)task_id);
-        }
-        furi_check(furi_mutex_release(furi_stdglue->mutex) == FuriStatusOk);
-        return true;
-    } else {
-        return false;
-    }
-}
-
-void __malloc_lock(struct _reent* REENT) {
-    UNUSED(REENT);
-    vTaskSuspendAll();
-}
-
-void __malloc_unlock(struct _reent* REENT) {
-    UNUSED(REENT);
-    xTaskResumeAll();
-}

+ 0 - 36
furi/core/stdglue.h

@@ -1,36 +0,0 @@
-/**
- * @file stdglue.h
- * Furi: stdlibc glue
- */
-
-#pragma once
-
-#include <stdbool.h>
-#include <stdlib.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/** Write callback
- * @param      _cookie  pointer to cookie (see stdio gnu extension)
- * @param      data     pointer to data
- * @param      size     data size @warnign your handler must consume everything
- */
-typedef void (*FuriStdglueWriteCallback)(void* _cookie, const char* data, size_t size);
-
-/** Initialized std library glue code */
-void furi_stdglue_init();
-
-/** Set STDOUT callback for your thread
- *
- * @param      callback  callback or NULL to clear
- *
- * @return     true on success, otherwise fail
- * @warning    function is thread aware, use this API from the same thread
- */
-bool furi_stdglue_set_thread_stdout_callback(FuriStdglueWriteCallback callback);
-
-#ifdef __cplusplus
-}
-#endif

+ 88 - 0
furi/core/thread.c

@@ -4,12 +4,21 @@
 #include "memmgr_heap.h"
 #include "check.h"
 #include "common_defines.h"
+#include "mutex.h"
 
 #include <task.h>
 #include <m-string.h>
+#include <furi_hal_console.h>
 
 #define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers
 
+typedef struct FuriThreadStdout FuriThreadStdout;
+
+struct FuriThreadStdout {
+    FuriThreadStdoutWriteCallback write_callback;
+    string_t buffer;
+};
+
 struct FuriThread {
     FuriThreadState state;
     int32_t ret;
@@ -27,8 +36,13 @@ struct FuriThread {
     TaskHandle_t task_handle;
     bool heap_trace_enabled;
     size_t heap_size;
+
+    FuriThreadStdout output;
 };
 
+static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size);
+static int32_t __furi_thread_stdout_flush(FuriThread* thread);
+
 /** Catch threads that are trying to exit wrong way */
 __attribute__((__noreturn__)) void furi_thread_catch() {
     asm volatile("nop"); // extra magic
@@ -47,6 +61,10 @@ static void furi_thread_body(void* context) {
     furi_assert(context);
     FuriThread* thread = context;
 
+    // store thread instance to thread local storage
+    furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) == NULL);
+    vTaskSetThreadLocalStoragePointer(NULL, 0, thread);
+
     furi_assert(thread->state == FuriThreadStateStarting);
     furi_thread_set_state(thread, FuriThreadStateRunning);
 
@@ -66,12 +84,18 @@ static void furi_thread_body(void* context) {
     furi_assert(thread->state == FuriThreadStateRunning);
     furi_thread_set_state(thread, FuriThreadStateStopped);
 
+    // clear thread local storage
+    __furi_thread_stdout_flush(thread);
+    furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL);
+    vTaskSetThreadLocalStoragePointer(NULL, 0, NULL);
+
     vTaskDelete(thread->task_handle);
     furi_thread_catch();
 }
 
 FuriThread* furi_thread_alloc() {
     FuriThread* thread = malloc(sizeof(FuriThread));
+    string_init(thread->output.buffer);
 
     return thread;
 }
@@ -81,6 +105,8 @@ void furi_thread_free(FuriThread* thread) {
     furi_assert(thread->state == FuriThreadStateStopped);
 
     if(thread->name) free((void*)thread->name);
+    string_clear(thread->output.buffer);
+
     free(thread);
 }
 
@@ -199,6 +225,12 @@ FuriThreadId furi_thread_get_current_id() {
     return xTaskGetCurrentTaskHandle();
 }
 
+FuriThread* furi_thread_get_current() {
+    FuriThread* thread = pvTaskGetThreadLocalStoragePointer(NULL, 0);
+    furi_assert(thread != NULL);
+    return thread;
+}
+
 void furi_thread_yield() {
     furi_assert(!FURI_IS_IRQ_MODE());
     taskYIELD();
@@ -408,3 +440,59 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) {
 
     return (sz);
 }
+
+static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size) {
+    if(thread->output.write_callback != NULL) {
+        thread->output.write_callback(data, size);
+    } else {
+        furi_hal_console_tx((const uint8_t*)data, size);
+    }
+    return size;
+}
+
+static int32_t __furi_thread_stdout_flush(FuriThread* thread) {
+    string_ptr buffer = thread->output.buffer;
+    size_t size = string_size(buffer);
+    if(size > 0) {
+        __furi_thread_stdout_write(thread, string_get_cstr(buffer), size);
+        string_reset(buffer);
+    }
+    return 0;
+}
+
+bool furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) {
+    FuriThread* thread = furi_thread_get_current();
+
+    __furi_thread_stdout_flush(thread);
+    thread->output.write_callback = callback;
+
+    return true;
+}
+
+size_t furi_thread_stdout_write(const char* data, size_t size) {
+    FuriThread* thread = furi_thread_get_current();
+
+    if(size == 0 || data == NULL) {
+        return __furi_thread_stdout_flush(thread);
+    } else {
+        if(data[size - 1] == '\n') {
+            // if the last character is a newline, we can flush buffer and write data as is, wo buffers
+            __furi_thread_stdout_flush(thread);
+            __furi_thread_stdout_write(thread, data, size);
+        } else {
+            // string_cat doesn't work here because we need to write the exact size data
+            for(size_t i = 0; i < size; i++) {
+                string_push_back(thread->output.buffer, data[i]);
+                if(data[i] == '\n') {
+                    __furi_thread_stdout_flush(thread);
+                }
+            }
+        }
+    }
+
+    return size;
+}
+
+int32_t furi_thread_stdout_flush() {
+    return __furi_thread_stdout_flush(furi_thread_get_current());
+}

+ 35 - 0
furi/core/thread.h

@@ -42,6 +42,12 @@ typedef void* FuriThreadId;
  */
 typedef int32_t (*FuriThreadCallback)(void* context);
 
+/** Write to stdout callback
+ * @param      data     pointer to data
+ * @param      size     data size @warning your handler must consume everything
+ */
+typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size);
+
 /** FuriThread state change calback called upon thread state change
  * @param      state    new thread state
  * @param      context  callback context
@@ -177,6 +183,12 @@ int32_t furi_thread_get_return_code(FuriThread* thread);
  */
 FuriThreadId furi_thread_get_current_id();
 
+/** Get FuriThread instance for current thread
+ * 
+ * @return FuriThread* 
+ */
+FuriThread* furi_thread_get_current();
+
 /** Return control to scheduler */
 void furi_thread_yield();
 
@@ -194,6 +206,29 @@ const char* furi_thread_get_name(FuriThreadId thread_id);
 
 uint32_t furi_thread_get_stack_space(FuriThreadId thread_id);
 
+/** Set STDOUT callback for thread
+ * 
+ * @param      callback  callback or NULL to clear
+ * 
+ * @return     true on success, otherwise fail
+ */
+bool furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback);
+
+/** Write data to buffered STDOUT
+ * 
+ * @param data input data
+ * @param size input data size
+ * 
+ * @return size_t written data size
+ */
+size_t furi_thread_stdout_write(const char* data, size_t size);
+
+/** Flush data to STDOUT
+ * 
+ * @return int32_t error code
+ */
+int32_t furi_thread_stdout_flush();
+
 #ifdef __cplusplus
 }
 #endif

+ 0 - 1
furi/furi.c

@@ -8,7 +8,6 @@ void furi_init() {
 
     furi_log_init();
     furi_record_init();
-    furi_stdglue_init();
 }
 
 void furi_run() {

+ 0 - 1
furi/furi.h

@@ -14,7 +14,6 @@
 #include <core/pubsub.h>
 #include <core/record.h>
 #include <core/semaphore.h>
-#include <core/stdglue.h>
 #include <core/thread.h>
 #include <core/timer.h>
 #include <core/valuemutex.h>

+ 2 - 0
lib/SConscript

@@ -14,6 +14,7 @@ env.Append(
         "lib/toolbox",
         "lib/u8g2",
         "lib/update_util",
+        "lib/print",
     ]
 )
 
@@ -60,6 +61,7 @@ libs = env.BuildModules(
     [
         "STM32CubeWB",
         "freertos",
+        "print",
         "microtar",
         "toolbox",
         "ST25RFAL002",

+ 107 - 0
lib/print/SConscript

@@ -0,0 +1,107 @@
+Import("env")
+
+wrapped_fn_list = [
+    #
+    # used by our firmware, so we provide their realizations
+    #
+    "fflush",
+    "printf",
+    "putc",  # fallback from printf, thanks gcc
+    "putchar",  # storage cli
+    "puts",  # fallback from printf, thanks gcc
+    "snprintf",
+    "vsnprintf",  # m-string
+    "__assert",  # ???
+    "__assert_func",  # ???
+    #
+    # wrap other functions to make sure they are not called
+    # realization is not provided
+    #
+    "setbuf",
+    "setvbuf",
+    "fprintf",
+    "vfprintf",
+    "vprintf",
+    "fputc",
+    "fputs",
+    "sprintf",  # specially, because this function is dangerous
+    "asprintf",
+    "vasprintf",
+    "asiprintf",
+    "asniprintf",
+    "asnprintf",
+    "diprintf",
+    "fiprintf",
+    "iprintf",
+    "siprintf",
+    "sniprintf",
+    "vasiprintf",
+    "vasniprintf",
+    "vasnprintf",
+    "vdiprintf",
+    "vfiprintf",
+    "viprintf",
+    "vsiprintf",
+    "vsniprintf",
+    #
+    # Scanf is not implemented 4 now
+    #
+    # "fscanf",
+    # "scanf",
+    # "sscanf",
+    # "vsprintf",
+    # "fgetc",
+    # "fgets",
+    # "getc",
+    # "getchar",
+    # "gets",
+    # "ungetc",
+    # "vfscanf",
+    # "vscanf",
+    # "vsscanf",
+    # "fiscanf",
+    # "iscanf",
+    # "siscanf",
+    # "vfiscanf",
+    # "viscanf",
+    # "vsiscanf",
+    #
+    # File management
+    #
+    # "fclose",
+    # "freopen",
+    # "fread",
+    # "fwrite",
+    # "fgetpos",
+    # "fseek",
+    # "fsetpos",
+    # "ftell",
+    # "rewind",
+    # "feof",
+    # "ferror",
+    # "fopen",
+    # "remove",
+    # "rename",
+    # "fseeko",
+    # "ftello",
+]
+
+for wrapped_fn in wrapped_fn_list:
+    env.Append(
+        LINKFLAGS=[
+            "-Wl,--wrap," + wrapped_fn,
+            "-Wl,--wrap," + wrapped_fn + "_unlocked",
+            "-Wl,--wrap,_" + wrapped_fn + "_r",
+            "-Wl,--wrap,_" + wrapped_fn + "_unlocked_r",
+        ]
+    )
+
+libenv = env.Clone(FW_LIB_NAME="print")
+libenv.ApplyLibFlags()
+libenv.Append(CCFLAGS=["-Wno-double-promotion"])
+
+sources = libenv.GlobRecursive("*.c*", ".")
+
+lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
+libenv.Install("${LIB_DIST_DIR}", lib)
+Return("lib")

+ 1037 - 0
lib/print/printf_tiny.c

@@ -0,0 +1,1037 @@
+///////////////////////////////////////////////////////////////////////////////
+// \author (c) Marco Paland (info@paland.com)
+//             2014-2019, PALANDesign Hannover, Germany
+//
+// \license The MIT License (MIT)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on
+//        embedded systems with a very limited resources. These routines are thread
+//        safe and reentrant!
+//        Use this instead of the bloated standard/newlib printf cause these use
+//        malloc for printf (and may not be thread safe).
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "printf_tiny.h"
+
+// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
+// printf_config.h header file
+// default: undefined
+#ifdef PRINTF_INCLUDE_CONFIG_H
+#include "printf_config.h"
+#endif
+
+// 'ntoa' conversion buffer size, this must be big enough to hold one converted
+// numeric number including padded zeros (dynamically created on stack)
+// default: 32 byte
+#ifndef PRINTF_NTOA_BUFFER_SIZE
+#define PRINTF_NTOA_BUFFER_SIZE 32U
+#endif
+
+// 'ftoa' conversion buffer size, this must be big enough to hold one converted
+// float number including padded zeros (dynamically created on stack)
+// default: 32 byte
+#ifndef PRINTF_FTOA_BUFFER_SIZE
+#define PRINTF_FTOA_BUFFER_SIZE 32U
+#endif
+
+// support for the floating point type (%f)
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
+#define PRINTF_SUPPORT_FLOAT
+#endif
+
+// support for exponential floating point notation (%e/%g)
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
+#define PRINTF_SUPPORT_EXPONENTIAL
+#endif
+
+// define the default floating point precision
+// default: 6 digits
+#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
+#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
+#endif
+
+// define the largest float suitable to print with %f
+// default: 1e9
+#ifndef PRINTF_MAX_FLOAT
+#define PRINTF_MAX_FLOAT 1e9
+#endif
+
+// support for the long long types (%llu or %p)
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
+#define PRINTF_SUPPORT_LONG_LONG
+#endif
+
+// support for the ptrdiff_t type (%t)
+// ptrdiff_t is normally defined in <stddef.h> as long or long long type
+// default: activated
+#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
+#define PRINTF_SUPPORT_PTRDIFF_T
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+// internal flag definitions
+#define FLAGS_ZEROPAD (1U << 0U)
+#define FLAGS_LEFT (1U << 1U)
+#define FLAGS_PLUS (1U << 2U)
+#define FLAGS_SPACE (1U << 3U)
+#define FLAGS_HASH (1U << 4U)
+#define FLAGS_UPPERCASE (1U << 5U)
+#define FLAGS_CHAR (1U << 6U)
+#define FLAGS_SHORT (1U << 7U)
+#define FLAGS_LONG (1U << 8U)
+#define FLAGS_LONG_LONG (1U << 9U)
+#define FLAGS_PRECISION (1U << 10U)
+#define FLAGS_ADAPT_EXP (1U << 11U)
+
+// import float.h for DBL_MAX
+#if defined(PRINTF_SUPPORT_FLOAT)
+#include <float.h>
+#endif
+
+// output function type
+typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
+
+// wrapper (used as buffer) for output function type
+typedef struct {
+    void (*fct)(char character, void* arg);
+    void* arg;
+} out_fct_wrap_type;
+
+// internal buffer output
+static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) {
+    if(idx < maxlen) {
+        ((char*)buffer)[idx] = character;
+    }
+}
+
+// internal null output
+static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) {
+    (void)character;
+    (void)buffer;
+    (void)idx;
+    (void)maxlen;
+}
+
+// internal _putchar wrapper
+static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen) {
+    (void)buffer;
+    (void)idx;
+    (void)maxlen;
+    if(character) {
+        _putchar(character);
+    }
+}
+
+// internal output function wrapper
+static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) {
+    (void)idx;
+    (void)maxlen;
+    if(character) {
+        // buffer is the output fct pointer
+        ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
+    }
+}
+
+// internal secure strlen
+// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
+static inline unsigned int _strnlen_s(const char* str, size_t maxsize) {
+    const char* s;
+    for(s = str; *s && maxsize--; ++s)
+        ;
+    return (unsigned int)(s - str);
+}
+
+// internal test if char is a digit (0-9)
+// \return true if char is a digit
+static inline bool _is_digit(char ch) {
+    return (ch >= '0') && (ch <= '9');
+}
+
+// internal ASCII string to unsigned int conversion
+static unsigned int _atoi(const char** str) {
+    unsigned int i = 0U;
+    while(_is_digit(**str)) {
+        i = i * 10U + (unsigned int)(*((*str)++) - '0');
+    }
+    return i;
+}
+
+// output the specified string in reverse, taking care of any zero-padding
+static size_t _out_rev(
+    out_fct_type out,
+    char* buffer,
+    size_t idx,
+    size_t maxlen,
+    const char* buf,
+    size_t len,
+    unsigned int width,
+    unsigned int flags) {
+    const size_t start_idx = idx;
+
+    // pad spaces up to given width
+    if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
+        for(size_t i = len; i < width; i++) {
+            out(' ', buffer, idx++, maxlen);
+        }
+    }
+
+    // reverse string
+    while(len) {
+        out(buf[--len], buffer, idx++, maxlen);
+    }
+
+    // append pad spaces up to given width
+    if(flags & FLAGS_LEFT) {
+        while(idx - start_idx < width) {
+            out(' ', buffer, idx++, maxlen);
+        }
+    }
+
+    return idx;
+}
+
+// internal itoa format
+static size_t _ntoa_format(
+    out_fct_type out,
+    char* buffer,
+    size_t idx,
+    size_t maxlen,
+    char* buf,
+    size_t len,
+    bool negative,
+    unsigned int base,
+    unsigned int prec,
+    unsigned int width,
+    unsigned int flags) {
+    // pad leading zeros
+    if(!(flags & FLAGS_LEFT)) {
+        if(width && (flags & FLAGS_ZEROPAD) &&
+           (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
+            width--;
+        }
+        while((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+            buf[len++] = '0';
+        }
+        while((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+            buf[len++] = '0';
+        }
+    }
+
+    // handle hash
+    if(flags & FLAGS_HASH) {
+        if(!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
+            len--;
+            if(len && (base == 16U)) {
+                len--;
+            }
+        }
+        if((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+            buf[len++] = 'x';
+        } else if((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+            buf[len++] = 'X';
+        } else if((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
+            buf[len++] = 'b';
+        }
+        if(len < PRINTF_NTOA_BUFFER_SIZE) {
+            buf[len++] = '0';
+        }
+    }
+
+    if(len < PRINTF_NTOA_BUFFER_SIZE) {
+        if(negative) {
+            buf[len++] = '-';
+        } else if(flags & FLAGS_PLUS) {
+            buf[len++] = '+'; // ignore the space if the '+' exists
+        } else if(flags & FLAGS_SPACE) {
+            buf[len++] = ' ';
+        }
+    }
+
+    return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
+}
+
+// internal itoa for 'long' type
+static size_t _ntoa_long(
+    out_fct_type out,
+    char* buffer,
+    size_t idx,
+    size_t maxlen,
+    unsigned long value,
+    bool negative,
+    unsigned long base,
+    unsigned int prec,
+    unsigned int width,
+    unsigned int flags) {
+    char buf[PRINTF_NTOA_BUFFER_SIZE];
+    size_t len = 0U;
+
+    // no hash for 0 values
+    if(!value) {
+        flags &= ~FLAGS_HASH;
+    }
+
+    // write if precision != 0 and value is != 0
+    if(!(flags & FLAGS_PRECISION) || value) {
+        do {
+            const char digit = (char)(value % base);
+            buf[len++] = digit < 10 ? '0' + digit :
+                                      (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
+            value /= base;
+        } while(value && (len < PRINTF_NTOA_BUFFER_SIZE));
+    }
+
+    return _ntoa_format(
+        out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
+}
+
+// internal itoa for 'long long' type
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+static size_t _ntoa_long_long(
+    out_fct_type out,
+    char* buffer,
+    size_t idx,
+    size_t maxlen,
+    unsigned long long value,
+    bool negative,
+    unsigned long long base,
+    unsigned int prec,
+    unsigned int width,
+    unsigned int flags) {
+    char buf[PRINTF_NTOA_BUFFER_SIZE];
+    size_t len = 0U;
+
+    // no hash for 0 values
+    if(!value) {
+        flags &= ~FLAGS_HASH;
+    }
+
+    // write if precision != 0 and value is != 0
+    if(!(flags & FLAGS_PRECISION) || value) {
+        do {
+            const char digit = (char)(value % base);
+            buf[len++] = digit < 10 ? '0' + digit :
+                                      (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
+            value /= base;
+        } while(value && (len < PRINTF_NTOA_BUFFER_SIZE));
+    }
+
+    return _ntoa_format(
+        out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
+}
+#endif // PRINTF_SUPPORT_LONG_LONG
+
+#if defined(PRINTF_SUPPORT_FLOAT)
+
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
+static size_t _etoa(
+    out_fct_type out,
+    char* buffer,
+    size_t idx,
+    size_t maxlen,
+    double value,
+    unsigned int prec,
+    unsigned int width,
+    unsigned int flags);
+#endif
+
+// internal ftoa for fixed decimal floating point
+static size_t _ftoa(
+    out_fct_type out,
+    char* buffer,
+    size_t idx,
+    size_t maxlen,
+    double value,
+    unsigned int prec,
+    unsigned int width,
+    unsigned int flags) {
+    char buf[PRINTF_FTOA_BUFFER_SIZE];
+    size_t len = 0U;
+    double diff = 0.0;
+
+    // powers of 10
+    static const double pow10[] = {
+        1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};
+
+    // test for special values
+    if(value != value) return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
+    if(value < -DBL_MAX) return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
+    if(value > DBL_MAX)
+        return _out_rev(
+            out,
+            buffer,
+            idx,
+            maxlen,
+            (flags & FLAGS_PLUS) ? "fni+" : "fni",
+            (flags & FLAGS_PLUS) ? 4U : 3U,
+            width,
+            flags);
+
+    // test for very large values
+    // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
+    if((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+        return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
+#else
+        return 0U;
+#endif
+    }
+
+    // test for negative
+    bool negative = false;
+    if(value < 0) {
+        negative = true;
+        value = 0 - value;
+    }
+
+    // set default precision, if not set explicitly
+    if(!(flags & FLAGS_PRECISION)) {
+        prec = PRINTF_DEFAULT_FLOAT_PRECISION;
+    }
+    // limit precision to 9, cause a prec >= 10 can lead to overflow errors
+    while((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
+        buf[len++] = '0';
+        prec--;
+    }
+
+    int whole = (int)value;
+    double tmp = (value - whole) * pow10[prec];
+    unsigned long frac = (unsigned long)tmp;
+    diff = tmp - frac;
+
+    if(diff > 0.5) {
+        ++frac;
+        // handle rollover, e.g. case 0.99 with prec 1 is 1.0
+        if(frac >= pow10[prec]) {
+            frac = 0;
+            ++whole;
+        }
+    } else if(diff < 0.5) {
+    } else if((frac == 0U) || (frac & 1U)) {
+        // if halfway, round up if odd OR if last digit is 0
+        ++frac;
+    }
+
+    if(prec == 0U) {
+        diff = value - (double)whole;
+        if((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
+            // exactly 0.5 and ODD, then round up
+            // 1.5 -> 2, but 2.5 -> 2
+            ++whole;
+        }
+    } else {
+        unsigned int count = prec;
+        // now do fractional part, as an unsigned number
+        while(len < PRINTF_FTOA_BUFFER_SIZE) {
+            --count;
+            buf[len++] = (char)(48U + (frac % 10U));
+            if(!(frac /= 10U)) {
+                break;
+            }
+        }
+        // add extra 0s
+        while((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
+            buf[len++] = '0';
+        }
+        if(len < PRINTF_FTOA_BUFFER_SIZE) {
+            // add decimal
+            buf[len++] = '.';
+        }
+    }
+
+    // do whole part, number is reversed
+    while(len < PRINTF_FTOA_BUFFER_SIZE) {
+        buf[len++] = (char)(48 + (whole % 10));
+        if(!(whole /= 10)) {
+            break;
+        }
+    }
+
+    // pad leading zeros
+    if(!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
+        if(width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
+            width--;
+        }
+        while((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
+            buf[len++] = '0';
+        }
+    }
+
+    if(len < PRINTF_FTOA_BUFFER_SIZE) {
+        if(negative) {
+            buf[len++] = '-';
+        } else if(flags & FLAGS_PLUS) {
+            buf[len++] = '+'; // ignore the space if the '+' exists
+        } else if(flags & FLAGS_SPACE) {
+            buf[len++] = ' ';
+        }
+    }
+
+    return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
+}
+
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse <m.jasperse@gmail.com>
+static size_t _etoa(
+    out_fct_type out,
+    char* buffer,
+    size_t idx,
+    size_t maxlen,
+    double value,
+    unsigned int prec,
+    unsigned int width,
+    unsigned int flags) {
+    // check for NaN and special values
+    if((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
+        return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
+    }
+
+    // determine the sign
+    const bool negative = value < 0;
+    if(negative) {
+        value = -value;
+    }
+
+    // default precision
+    if(!(flags & FLAGS_PRECISION)) {
+        prec = PRINTF_DEFAULT_FLOAT_PRECISION;
+    }
+
+    // determine the decimal exponent
+    // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
+    union {
+        uint64_t U;
+        double F;
+    } conv;
+
+    conv.F = value;
+    int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
+    conv.U = (conv.U & ((1ULL << 52U) - 1U)) |
+             (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
+    // now approximate log10 from the log2 integer part and an expansion of ln around 1.5
+    int expval =
+        (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);
+    // now we want to compute 10^expval but we want to be sure it won't overflow
+    exp2 = (int)(expval * 3.321928094887362 + 0.5);
+    const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
+    const double z2 = z * z;
+    conv.U = (uint64_t)(exp2 + 1023) << 52U;
+    // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
+    conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
+    // correct for rounding errors
+    if(value < conv.F) {
+        expval--;
+        conv.F /= 10;
+    }
+
+    // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
+    unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
+
+    // in "%g" mode, "prec" is the number of *significant figures* not decimals
+    if(flags & FLAGS_ADAPT_EXP) {
+        // do we want to fall-back to "%f" mode?
+        if((value >= 1e-4) && (value < 1e6)) {
+            if((int)prec > expval) {
+                prec = (unsigned)((int)prec - expval - 1);
+            } else {
+                prec = 0;
+            }
+            flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
+            // no characters in exponent
+            minwidth = 0U;
+            expval = 0;
+        } else {
+            // we use one sigfig for the whole part
+            if((prec > 0) && (flags & FLAGS_PRECISION)) {
+                --prec;
+            }
+        }
+    }
+
+    // will everything fit?
+    unsigned int fwidth = width;
+    if(width > minwidth) {
+        // we didn't fall-back so subtract the characters required for the exponent
+        fwidth -= minwidth;
+    } else {
+        // not enough characters, so go back to default sizing
+        fwidth = 0U;
+    }
+    if((flags & FLAGS_LEFT) && minwidth) {
+        // if we're padding on the right, DON'T pad the floating part
+        fwidth = 0U;
+    }
+
+    // rescale the float value
+    if(expval) {
+        value /= conv.F;
+    }
+
+    // output the floating part
+    const size_t start_idx = idx;
+    idx = _ftoa(
+        out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
+
+    // output the exponent part
+    if(minwidth) {
+        // output the exponential symbol
+        out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
+        // output the exponent value
+        idx = _ntoa_long(
+            out,
+            buffer,
+            idx,
+            maxlen,
+            (expval < 0) ? -expval : expval,
+            expval < 0,
+            10,
+            0,
+            minwidth - 1,
+            FLAGS_ZEROPAD | FLAGS_PLUS);
+        // might need to right-pad spaces
+        if(flags & FLAGS_LEFT) {
+            while(idx - start_idx < width) out(' ', buffer, idx++, maxlen);
+        }
+    }
+    return idx;
+}
+#endif // PRINTF_SUPPORT_EXPONENTIAL
+#endif // PRINTF_SUPPORT_FLOAT
+
+// internal vsnprintf
+static int
+    _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) {
+    unsigned int flags, width, precision, n;
+    size_t idx = 0U;
+
+    if(!buffer) {
+        // use null output function
+        out = _out_null;
+    }
+
+    while(*format) {
+        // format specifier?  %[flags][width][.precision][length]
+        if(*format != '%') {
+            // no
+            out(*format, buffer, idx++, maxlen);
+            format++;
+            continue;
+        } else {
+            // yes, evaluate it
+            format++;
+        }
+
+        // evaluate flags
+        flags = 0U;
+        do {
+            switch(*format) {
+            case '0':
+                flags |= FLAGS_ZEROPAD;
+                format++;
+                n = 1U;
+                break;
+            case '-':
+                flags |= FLAGS_LEFT;
+                format++;
+                n = 1U;
+                break;
+            case '+':
+                flags |= FLAGS_PLUS;
+                format++;
+                n = 1U;
+                break;
+            case ' ':
+                flags |= FLAGS_SPACE;
+                format++;
+                n = 1U;
+                break;
+            case '#':
+                flags |= FLAGS_HASH;
+                format++;
+                n = 1U;
+                break;
+            default:
+                n = 0U;
+                break;
+            }
+        } while(n);
+
+        // evaluate width field
+        width = 0U;
+        if(_is_digit(*format)) {
+            width = _atoi(&format);
+        } else if(*format == '*') {
+            const int w = va_arg(va, int);
+            if(w < 0) {
+                flags |= FLAGS_LEFT; // reverse padding
+                width = (unsigned int)-w;
+            } else {
+                width = (unsigned int)w;
+            }
+            format++;
+        }
+
+        // evaluate precision field
+        precision = 0U;
+        if(*format == '.') {
+            flags |= FLAGS_PRECISION;
+            format++;
+            if(_is_digit(*format)) {
+                precision = _atoi(&format);
+            } else if(*format == '*') {
+                const int prec = (int)va_arg(va, int);
+                precision = prec > 0 ? (unsigned int)prec : 0U;
+                format++;
+            }
+        }
+
+        // evaluate length field
+        switch(*format) {
+        case 'l':
+            flags |= FLAGS_LONG;
+            format++;
+            if(*format == 'l') {
+                flags |= FLAGS_LONG_LONG;
+                format++;
+            }
+            break;
+        case 'h':
+            flags |= FLAGS_SHORT;
+            format++;
+            if(*format == 'h') {
+                flags |= FLAGS_CHAR;
+                format++;
+            }
+            break;
+#if defined(PRINTF_SUPPORT_PTRDIFF_T)
+        case 't':
+            flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
+            format++;
+            break;
+#endif
+        case 'j':
+            flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
+            format++;
+            break;
+        case 'z':
+            flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
+            format++;
+            break;
+        default:
+            break;
+        }
+
+        // evaluate specifier
+        switch(*format) {
+        case 'd':
+        case 'i':
+        case 'u':
+        case 'x':
+        case 'X':
+        case 'o':
+        case 'b': {
+            // set the base
+            unsigned int base;
+            if(*format == 'x' || *format == 'X') {
+                base = 16U;
+            } else if(*format == 'o') {
+                base = 8U;
+            } else if(*format == 'b') {
+                base = 2U;
+            } else {
+                base = 10U;
+                flags &= ~FLAGS_HASH; // no hash for dec format
+            }
+            // uppercase
+            if(*format == 'X') {
+                flags |= FLAGS_UPPERCASE;
+            }
+
+            // no plus or space flag for u, x, X, o, b
+            if((*format != 'i') && (*format != 'd')) {
+                flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
+            }
+
+            // ignore '0' flag when precision is given
+            if(flags & FLAGS_PRECISION) {
+                flags &= ~FLAGS_ZEROPAD;
+            }
+
+            // convert the integer
+            if((*format == 'i') || (*format == 'd')) {
+                // signed
+                if(flags & FLAGS_LONG_LONG) {
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+                    const long long value = va_arg(va, long long);
+                    idx = _ntoa_long_long(
+                        out,
+                        buffer,
+                        idx,
+                        maxlen,
+                        (unsigned long long)(value > 0 ? value : 0 - value),
+                        value < 0,
+                        base,
+                        precision,
+                        width,
+                        flags);
+#endif
+                } else if(flags & FLAGS_LONG) {
+                    const long value = va_arg(va, long);
+                    idx = _ntoa_long(
+                        out,
+                        buffer,
+                        idx,
+                        maxlen,
+                        (unsigned long)(value > 0 ? value : 0 - value),
+                        value < 0,
+                        base,
+                        precision,
+                        width,
+                        flags);
+                } else {
+                    const int value = (flags & FLAGS_CHAR)  ? (char)va_arg(va, int) :
+                                      (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) :
+                                                              va_arg(va, int);
+                    idx = _ntoa_long(
+                        out,
+                        buffer,
+                        idx,
+                        maxlen,
+                        (unsigned int)(value > 0 ? value : 0 - value),
+                        value < 0,
+                        base,
+                        precision,
+                        width,
+                        flags);
+                }
+            } else {
+                // unsigned
+                if(flags & FLAGS_LONG_LONG) {
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+                    idx = _ntoa_long_long(
+                        out,
+                        buffer,
+                        idx,
+                        maxlen,
+                        va_arg(va, unsigned long long),
+                        false,
+                        base,
+                        precision,
+                        width,
+                        flags);
+#endif
+                } else if(flags & FLAGS_LONG) {
+                    idx = _ntoa_long(
+                        out,
+                        buffer,
+                        idx,
+                        maxlen,
+                        va_arg(va, unsigned long),
+                        false,
+                        base,
+                        precision,
+                        width,
+                        flags);
+                } else {
+                    const unsigned int value =
+                        (flags & FLAGS_CHAR)  ? (unsigned char)va_arg(va, unsigned int) :
+                        (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) :
+                                                va_arg(va, unsigned int);
+                    idx = _ntoa_long(
+                        out, buffer, idx, maxlen, value, false, base, precision, width, flags);
+                }
+            }
+            format++;
+            break;
+        }
+#if defined(PRINTF_SUPPORT_FLOAT)
+        case 'f':
+        case 'F':
+            if(*format == 'F') flags |= FLAGS_UPPERCASE;
+            idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
+            format++;
+            break;
+#if defined(PRINTF_SUPPORT_EXPONENTIAL)
+        case 'e':
+        case 'E':
+        case 'g':
+        case 'G':
+            if((*format == 'g') || (*format == 'G')) flags |= FLAGS_ADAPT_EXP;
+            if((*format == 'E') || (*format == 'G')) flags |= FLAGS_UPPERCASE;
+            idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
+            format++;
+            break;
+#endif // PRINTF_SUPPORT_EXPONENTIAL
+#endif // PRINTF_SUPPORT_FLOAT
+        case 'c': {
+            unsigned int l = 1U;
+            // pre padding
+            if(!(flags & FLAGS_LEFT)) {
+                while(l++ < width) {
+                    out(' ', buffer, idx++, maxlen);
+                }
+            }
+            // char output
+            out((char)va_arg(va, int), buffer, idx++, maxlen);
+            // post padding
+            if(flags & FLAGS_LEFT) {
+                while(l++ < width) {
+                    out(' ', buffer, idx++, maxlen);
+                }
+            }
+            format++;
+            break;
+        }
+
+        case 's': {
+            const char* p = va_arg(va, char*);
+            unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
+            // pre padding
+            if(flags & FLAGS_PRECISION) {
+                l = (l < precision ? l : precision);
+            }
+            if(!(flags & FLAGS_LEFT)) {
+                while(l++ < width) {
+                    out(' ', buffer, idx++, maxlen);
+                }
+            }
+            // string output
+            while((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
+                out(*(p++), buffer, idx++, maxlen);
+            }
+            // post padding
+            if(flags & FLAGS_LEFT) {
+                while(l++ < width) {
+                    out(' ', buffer, idx++, maxlen);
+                }
+            }
+            format++;
+            break;
+        }
+
+        case 'p': {
+            width = sizeof(void*) * 2U;
+            flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+            const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
+            if(is_ll) {
+                idx = _ntoa_long_long(
+                    out,
+                    buffer,
+                    idx,
+                    maxlen,
+                    (uintptr_t)va_arg(va, void*),
+                    false,
+                    16U,
+                    precision,
+                    width,
+                    flags);
+            } else {
+#endif
+                idx = _ntoa_long(
+                    out,
+                    buffer,
+                    idx,
+                    maxlen,
+                    (unsigned long)((uintptr_t)va_arg(va, void*)),
+                    false,
+                    16U,
+                    precision,
+                    width,
+                    flags);
+#if defined(PRINTF_SUPPORT_LONG_LONG)
+            }
+#endif
+            format++;
+            break;
+        }
+
+        case '%':
+            out('%', buffer, idx++, maxlen);
+            format++;
+            break;
+
+        default:
+            out(*format, buffer, idx++, maxlen);
+            format++;
+            break;
+        }
+    }
+
+    // termination
+    out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
+
+    // return written chars without terminating \0
+    return (int)idx;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int printf_(const char* format, ...) {
+    va_list va;
+    va_start(va, format);
+    char buffer[1];
+    const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
+    va_end(va);
+    return ret;
+}
+
+int sprintf_(char* buffer, const char* format, ...) {
+    va_list va;
+    va_start(va, format);
+    const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
+    va_end(va);
+    return ret;
+}
+
+int snprintf_(char* buffer, size_t count, const char* format, ...) {
+    va_list va;
+    va_start(va, format);
+    const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
+    va_end(va);
+    return ret;
+}
+
+int vprintf_(const char* format, va_list va) {
+    char buffer[1];
+    return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
+}
+
+int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) {
+    return _vsnprintf(_out_buffer, buffer, count, format, va);
+}
+
+int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) {
+    va_list va;
+    va_start(va, format);
+    const out_fct_wrap_type out_fct_wrap = {out, arg};
+    const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
+    va_end(va);
+    return ret;
+}

+ 103 - 0
lib/print/printf_tiny.h

@@ -0,0 +1,103 @@
+///////////////////////////////////////////////////////////////////////////////
+// \author (c) Marco Paland (info@paland.com)
+//             2014-2019, PALANDesign Hannover, Germany
+//
+// \license The MIT License (MIT)
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
+//        embedded systems with a very limited resources.
+//        Use this instead of bloated standard/newlib printf.
+//        These routines are thread safe and reentrant.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _PRINTF_H_
+#define _PRINTF_H_
+
+#include <stdarg.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Output a character to a custom device like UART, used by the printf() function
+ * This function is declared here only. You have to write your custom implementation somewhere
+ * \param character Character to output
+ */
+void _putchar(char character);
+
+/**
+ * Tiny printf implementation
+ * You have to implement _putchar if you use printf()
+ * To avoid conflicts with the regular printf() API it is overridden by macro defines
+ * and internal underscore-appended functions like printf_() are used
+ * \param format A string that specifies the format of the output
+ * \return The number of characters that are written into the array, not counting the terminating null character
+ */
+int printf_(const char* format, ...);
+
+/**
+ * Tiny sprintf implementation
+ * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
+ * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
+ * \param format A string that specifies the format of the output
+ * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
+ */
+int sprintf_(char* buffer, const char* format, ...);
+
+/**
+ * Tiny snprintf/vsnprintf implementation
+ * \param buffer A pointer to the buffer where to store the formatted string
+ * \param count The maximum number of characters to store in the buffer, including a terminating null character
+ * \param format A string that specifies the format of the output
+ * \param va A value identifying a variable arguments list
+ * \return The number of characters that COULD have been written into the buffer, not counting the terminating
+ *         null character. A value equal or larger than count indicates truncation. Only when the returned value
+ *         is non-negative and less than count, the string has been completely written.
+ */
+int snprintf_(char* buffer, size_t count, const char* format, ...);
+int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
+
+/**
+ * Tiny vprintf implementation
+ * \param format A string that specifies the format of the output
+ * \param va A value identifying a variable arguments list
+ * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
+ */
+int vprintf_(const char* format, va_list va);
+
+/**
+ * printf with output function
+ * You may use this as dynamic alternative to printf() with its fixed _putchar() output
+ * \param out An output function which takes one character and an argument pointer
+ * \param arg An argument pointer for user data passed to output function
+ * \param format A string that specifies the format of the output
+ * \return The number of characters that are sent to the output function, not counting the terminating null character
+ */
+int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _PRINTF_H_

+ 74 - 0
lib/print/wrappers.c

@@ -0,0 +1,74 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <furi/core/check.h>
+#include <furi/core/thread.h>
+#include <furi/core/common_defines.h>
+#include <string.h>
+#include "printf_tiny.h"
+
+void _putchar(char character) {
+    furi_thread_stdout_write(&character, 1);
+}
+
+int __wrap_printf(const char* format, ...) {
+    va_list args;
+    va_start(args, format);
+    int ret = vprintf_(format, args);
+    va_end(args);
+
+    return ret;
+}
+
+int __wrap_vsnprintf(char* str, size_t size, const char* format, va_list args) {
+    return vsnprintf_(str, size, format, args);
+}
+
+int __wrap_puts(const char* str) {
+    size_t size = furi_thread_stdout_write(str, strlen(str));
+    size += furi_thread_stdout_write("\n", 1);
+    return size;
+}
+
+int __wrap_putchar(int ch) {
+    size_t size = furi_thread_stdout_write((char*)&ch, 1);
+    return size;
+}
+
+int __wrap_putc(int ch, FILE* stream) {
+    UNUSED(stream);
+    size_t size = furi_thread_stdout_write((char*)&ch, 1);
+    return size;
+}
+
+int __wrap_snprintf(char* str, size_t size, const char* format, ...) {
+    va_list args;
+    va_start(args, format);
+    int ret = __wrap_vsnprintf(str, size, format, args);
+    va_end(args);
+
+    return ret;
+}
+
+int __wrap_fflush(FILE* stream) {
+    UNUSED(stream);
+    furi_thread_stdout_flush();
+    return 0;
+}
+
+__attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e) {
+    UNUSED(file);
+    UNUSED(line);
+    // TODO: message file and line number
+    furi_crash(e);
+}
+
+__attribute__((__noreturn__)) void
+    __wrap___assert_func(const char* file, int line, const char* func, const char* e) {
+    UNUSED(file);
+    UNUSED(line);
+    UNUSED(func);
+    // TODO: message file and line number
+    furi_crash(e);
+}

+ 1 - 1
lib/toolbox/random_name.c

@@ -36,7 +36,7 @@ void set_random_name(char* name, uint8_t max_name_size) {
     uint8_t prefix_i = rand() % COUNT_OF(prefix);
     uint8_t suffix_i = rand() % COUNT_OF(suffix);
 
-    sniprintf(name, max_name_size, "%s_%s", prefix[prefix_i], suffix[suffix_i]);
+    snprintf(name, max_name_size, "%s_%s", prefix[prefix_i], suffix[suffix_i]);
     // Set first symbol to upper case
     name[0] = name[0] - 0x20;
 }