From bc34689ed6e6a8c2c757be2da01984814468537a Mon Sep 17 00:00:00 2001 From: SG Date: Thu, 4 Aug 2022 02:00:17 +1000 Subject: [PATCH] Make printf great again (#1438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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: あく --- applications/cli/cli.c | 10 +- applications/cli/cli_i.h | 2 +- applications/cli/cli_vcp.c | 3 +- applications/debug_tools/keypad_test.c | 10 +- .../desktop/views/desktop_view_debug.c | 9 +- applications/infrared/infrared_cli.c | 8 +- applications/rpc/rpc_storage.c | 2 +- .../scenes/subghz_scene_receiver_config.c | 9 +- applications/unit_tests/rpc/rpc_test.c | 15 +- firmware.scons | 3 +- furi/core/stdglue.c | 102 -- furi/core/stdglue.h | 36 - furi/core/thread.c | 88 ++ furi/core/thread.h | 35 + furi/furi.c | 1 - furi/furi.h | 1 - lib/SConscript | 2 + lib/print/SConscript | 107 ++ lib/print/printf_tiny.c | 1037 +++++++++++++++++ lib/print/printf_tiny.h | 103 ++ lib/print/wrappers.c | 74 ++ lib/toolbox/random_name.c | 2 +- 22 files changed, 1484 insertions(+), 175 deletions(-) delete mode 100644 furi/core/stdglue.c delete mode 100644 furi/core/stdglue.h create mode 100644 lib/print/SConscript create mode 100644 lib/print/printf_tiny.c create mode 100644 lib/print/printf_tiny.h create mode 100644 lib/print/wrappers.c diff --git a/applications/cli/cli.c b/applications/cli/cli.c index 4d9b8a5f..e554ac89 100644 --- a/applications/cli/cli.c +++ b/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) { diff --git a/applications/cli/cli_i.h b/applications/cli/cli_i.h index 076dd75e..8f0bd85d 100755 --- a/applications/cli/cli_i.h +++ b/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); }; diff --git a/applications/cli/cli_vcp.c b/applications/cli/cli_vcp.c index 5d66b8f8..5a8b44dc 100644 --- a/applications/cli/cli_vcp.c +++ b/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); } diff --git a/applications/debug_tools/keypad_test.c b/applications/debug_tools/keypad_test.c index 6708c82b..2470baf8 100644 --- a/applications/debug_tools/keypad_test.c +++ b/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"); diff --git a/applications/desktop/views/desktop_view_debug.c b/applications/desktop/views/desktop_view_debug.c index 68c054c2..b69a6a2d 100644 --- a/applications/desktop/views/desktop_view_debug.c +++ b/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"); } diff --git a/applications/infrared/infrared_cli.c b/applications/infrared/infrared_cli.c index c190aad3..aae02e8f 100644 --- a/applications/infrared/infrared_cli.c +++ b/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); } } diff --git a/applications/rpc/rpc_storage.c b/applications/rpc/rpc_storage.c index d2b43ff6..1e2920f5 100644 --- a/applications/rpc/rpc_storage.c +++ b/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); diff --git a/applications/subghz/scenes/subghz_scene_receiver_config.c b/applications/subghz/scenes/subghz_scene_receiver_config.c index 590b51d1..bf2f0cdb 100644 --- a/applications/subghz/scenes/subghz_scene_receiver_config.c +++ b/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); diff --git a/applications/unit_tests/rpc/rpc_test.c b/applications/unit_tests/rpc/rpc_test.c index 69a0c434..d31311af 100644 --- a/applications/unit_tests/rpc/rpc_test.c +++ b/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); diff --git a/firmware.scons b/firmware.scons index 76f0b52d..4eb76f52 100644 --- a/firmware.scons +++ b/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", diff --git a/furi/core/stdglue.c b/furi/core/stdglue.c deleted file mode 100644 index 573277aa..00000000 --- a/furi/core/stdglue.c +++ /dev/null @@ -1,102 +0,0 @@ -#include "stdglue.h" -#include "check.h" -#include "memmgr.h" - -#include -#include - -#include -#include - -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(); -} diff --git a/furi/core/stdglue.h b/furi/core/stdglue.h deleted file mode 100644 index 800fcf92..00000000 --- a/furi/core/stdglue.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @file stdglue.h - * Furi: stdlibc glue - */ - -#pragma once - -#include -#include - -#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 diff --git a/furi/core/thread.c b/furi/core/thread.c index 3b6708f6..044f8371 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -4,12 +4,21 @@ #include "memmgr_heap.h" #include "check.h" #include "common_defines.h" +#include "mutex.h" #include #include +#include #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()); +} \ No newline at end of file diff --git a/furi/core/thread.h b/furi/core/thread.h index 34eb39f0..7f746f03 100644 --- a/furi/core/thread.h +++ b/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 diff --git a/furi/furi.c b/furi/furi.c index e6848624..76aed024 100644 --- a/furi/furi.c +++ b/furi/furi.c @@ -8,7 +8,6 @@ void furi_init() { furi_log_init(); furi_record_init(); - furi_stdglue_init(); } void furi_run() { diff --git a/furi/furi.h b/furi/furi.h index d78129a8..68914b50 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/lib/SConscript b/lib/SConscript index c5bc3947..459a80d6 100644 --- a/lib/SConscript +++ b/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", diff --git a/lib/print/SConscript b/lib/print/SConscript new file mode 100644 index 00000000..412d17a6 --- /dev/null +++ b/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") diff --git a/lib/print/printf_tiny.c b/lib/print/printf_tiny.c new file mode 100644 index 00000000..0db11922 --- /dev/null +++ b/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 +#include + +#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 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 +#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 +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; +} diff --git a/lib/print/printf_tiny.h b/lib/print/printf_tiny.h new file mode 100644 index 00000000..8f292819 --- /dev/null +++ b/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 +#include + +#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_ diff --git a/lib/print/wrappers.c b/lib/print/wrappers.c new file mode 100644 index 00000000..3fe44665 --- /dev/null +++ b/lib/print/wrappers.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#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); +} \ No newline at end of file diff --git a/lib/toolbox/random_name.c b/lib/toolbox/random_name.c index b581bb97..4810a067 100644 --- a/lib/toolbox/random_name.c +++ b/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; }