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>
This commit is contained in:
		
							parent
							
								
									eed4296890
								
							
						
					
					
						commit
						bc34689ed6
					
				| @ -439,9 +439,9 @@ void cli_session_open(Cli* cli, void* session) { | |||||||
|     cli->session = session; |     cli->session = session; | ||||||
|     if(cli->session != NULL) { |     if(cli->session != NULL) { | ||||||
|         cli->session->init(); |         cli->session->init(); | ||||||
|         furi_stdglue_set_thread_stdout_callback(cli->session->tx_stdout); |         furi_thread_set_stdout_callback(cli->session->tx_stdout); | ||||||
|     } else { |     } else { | ||||||
|         furi_stdglue_set_thread_stdout_callback(NULL); |         furi_thread_set_stdout_callback(NULL); | ||||||
|     } |     } | ||||||
|     furi_semaphore_release(cli->idle_sem); |     furi_semaphore_release(cli->idle_sem); | ||||||
|     furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); |     furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); | ||||||
| @ -455,7 +455,7 @@ void cli_session_close(Cli* cli) { | |||||||
|         cli->session->deinit(); |         cli->session->deinit(); | ||||||
|     } |     } | ||||||
|     cli->session = NULL; |     cli->session = NULL; | ||||||
|     furi_stdglue_set_thread_stdout_callback(NULL); |     furi_thread_set_stdout_callback(NULL); | ||||||
|     furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); |     furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -469,9 +469,9 @@ int32_t cli_srv(void* p) { | |||||||
|     furi_record_create(RECORD_CLI, cli); |     furi_record_create(RECORD_CLI, cli); | ||||||
| 
 | 
 | ||||||
|     if(cli->session != NULL) { |     if(cli->session != NULL) { | ||||||
|         furi_stdglue_set_thread_stdout_callback(cli->session->tx_stdout); |         furi_thread_set_stdout_callback(cli->session->tx_stdout); | ||||||
|     } else { |     } else { | ||||||
|         furi_stdglue_set_thread_stdout_callback(NULL); |         furi_thread_set_stdout_callback(NULL); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { |     if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ struct CliSession { | |||||||
|     void (*deinit)(void); |     void (*deinit)(void); | ||||||
|     size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout); |     size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout); | ||||||
|     void (*tx)(const uint8_t* buffer, size_t size); |     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); |     bool (*is_connected)(void); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -277,8 +277,7 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) { | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static void cli_vcp_tx_stdout(void* _cookie, const char* data, size_t size) { | static void cli_vcp_tx_stdout(const char* data, size_t size) { | ||||||
|     UNUSED(_cookie); |  | ||||||
|     cli_vcp_tx((const uint8_t*)data, size); |     cli_vcp_tx((const uint8_t*)data, size); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -26,11 +26,11 @@ static void keypad_test_render_callback(Canvas* canvas, void* ctx) { | |||||||
|     canvas_clear(canvas); |     canvas_clear(canvas); | ||||||
|     char strings[5][20]; |     char strings[5][20]; | ||||||
| 
 | 
 | ||||||
|     sprintf(strings[0], "Ok: %d", state->ok); |     snprintf(strings[0], 20, "Ok: %d", state->ok); | ||||||
|     sprintf(strings[1], "L: %d", state->left); |     snprintf(strings[1], 20, "L: %d", state->left); | ||||||
|     sprintf(strings[2], "R: %d", state->right); |     snprintf(strings[2], 20, "R: %d", state->right); | ||||||
|     sprintf(strings[3], "U: %d", state->up); |     snprintf(strings[3], 20, "U: %d", state->up); | ||||||
|     sprintf(strings[4], "D: %d", state->down); |     snprintf(strings[4], 20, "D: %d", state->down); | ||||||
| 
 | 
 | ||||||
|     canvas_set_font(canvas, FontPrimary); |     canvas_set_font(canvas, FontPrimary); | ||||||
|     canvas_draw_str(canvas, 0, 10, "Keypad test"); |     canvas_draw_str(canvas, 0, 10, "Keypad test"); | ||||||
|  | |||||||
| @ -78,7 +78,6 @@ void desktop_debug_render(Canvas* canvas, void* model) { | |||||||
|         canvas_draw_str(canvas, 5, 50 + STATUS_BAR_Y_SHIFT, buffer); |         canvas_draw_str(canvas, 5, 50 + STATUS_BAR_Y_SHIFT, buffer); | ||||||
| 
 | 
 | ||||||
|     } else { |     } else { | ||||||
|         char buffer[64]; |  | ||||||
|         Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); |         Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); | ||||||
|         DolphinStats stats = dolphin_stats(dolphin); |         DolphinStats stats = dolphin_stats(dolphin); | ||||||
|         furi_record_close(RECORD_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); |         uint32_t remaining = dolphin_state_xp_to_levelup(m->icounter); | ||||||
| 
 | 
 | ||||||
|         canvas_set_font(canvas, FontSecondary); |         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); |         canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer); | ||||||
| 
 | 
 | ||||||
|         snprintf( |         snprintf( | ||||||
|             buffer, |             buffer, | ||||||
|             64, |             sizeof(buffer), | ||||||
|             "Level: %ld  To level up: %ld", |             "Level: %ld  To level up: %ld", | ||||||
|             current_lvl, |             current_lvl, | ||||||
|             (remaining == (uint32_t)(-1) ? remaining : 0)); |             (remaining == (uint32_t)(-1) ? remaining : 0)); | ||||||
|         canvas_draw_str(canvas, 5, 29 + STATUS_BAR_Y_SHIFT, buffer); |         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, 5, 39 + STATUS_BAR_Y_SHIFT, buffer); | ||||||
|         canvas_draw_str(canvas, 0, 49 + STATUS_BAR_Y_SHIFT, "[< >] icounter value   [ok] save"); |         canvas_draw_str(canvas, 0, 49 + STATUS_BAR_Y_SHIFT, "[< >] icounter value   [ok] save"); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv | |||||||
| 
 | 
 | ||||||
|     if(infrared_worker_signal_is_decoded(received_signal)) { |     if(infrared_worker_signal_is_decoded(received_signal)) { | ||||||
|         const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal); |         const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal); | ||||||
|         buf_cnt = sniprintf( |         buf_cnt = snprintf( | ||||||
|             buf, |             buf, | ||||||
|             sizeof(buf), |             sizeof(buf), | ||||||
|             "%s, A:0x%0*lX, C:0x%0*lX%s\r\n", |             "%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; |         size_t timings_cnt; | ||||||
|         infrared_worker_get_raw_signal(received_signal, &timings, &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); |         cli_write(cli, (uint8_t*)buf, buf_cnt); | ||||||
|         for(size_t i = 0; i < timings_cnt; ++i) { |         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); |             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); |         cli_write(cli, (uint8_t*)buf, buf_cnt); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -541,7 +541,7 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont | |||||||
|         (void)md5sum_size; |         (void)md5sum_size; | ||||||
|         furi_assert(hash_size <= ((md5sum_size - 1) / 2)); |         furi_assert(hash_size <= ((md5sum_size - 1) / 2)); | ||||||
|         for(uint8_t i = 0; i < hash_size; i++) { |         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); |         free(hash); | ||||||
|  | |||||||
| @ -73,8 +73,9 @@ static void subghz_scene_receiver_config_set_frequency(VariableItem* item) { | |||||||
| 
 | 
 | ||||||
|     if(subghz->txrx->hopper_state == SubGhzHopperStateOFF) { |     if(subghz->txrx->hopper_state == SubGhzHopperStateOFF) { | ||||||
|         char text_buf[10] = {0}; |         char text_buf[10] = {0}; | ||||||
|         sprintf( |         snprintf( | ||||||
|             text_buf, |             text_buf, | ||||||
|  |             sizeof(text_buf), | ||||||
|             "%lu.%02lu", |             "%lu.%02lu", | ||||||
|             subghz_setting_get_frequency(subghz->setting, index) / 1000000, |             subghz_setting_get_frequency(subghz->setting, index) / 1000000, | ||||||
|             (subghz_setting_get_frequency(subghz->setting, index) % 1000000) / 10000); |             (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]); |     variable_item_set_current_value_text(item, hopping_text[index]); | ||||||
|     if(hopping_value[index] == SubGhzHopperStateOFF) { |     if(hopping_value[index] == SubGhzHopperStateOFF) { | ||||||
|         char text_buf[10] = {0}; |         char text_buf[10] = {0}; | ||||||
|         sprintf( |         snprintf( | ||||||
|             text_buf, |             text_buf, | ||||||
|  |             sizeof(text_buf), | ||||||
|             "%lu.%02lu", |             "%lu.%02lu", | ||||||
|             subghz_setting_get_default_frequency(subghz->setting) / 1000000, |             subghz_setting_get_default_frequency(subghz->setting) / 1000000, | ||||||
|             (subghz_setting_get_default_frequency(subghz->setting) % 1000000) / 10000); |             (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); |         subghz->scene_manager, SubGhzSceneReceiverConfig, (uint32_t)item); | ||||||
|     variable_item_set_current_value_index(item, value_index); |     variable_item_set_current_value_index(item, value_index); | ||||||
|     char text_buf[10] = {0}; |     char text_buf[10] = {0}; | ||||||
|     sprintf( |     snprintf( | ||||||
|         text_buf, |         text_buf, | ||||||
|  |         sizeof(text_buf), | ||||||
|         "%lu.%02lu", |         "%lu.%02lu", | ||||||
|         subghz_setting_get_frequency(subghz->setting, value_index) / 1000000, |         subghz_setting_get_frequency(subghz->setting, value_index) / 1000000, | ||||||
|         (subghz_setting_get_frequency(subghz->setting, value_index) % 1000000) / 10000); |         (subghz_setting_get_frequency(subghz->setting, value_index) % 1000000) / 10000); | ||||||
|  | |||||||
| @ -189,8 +189,9 @@ static void clean_directory(Storage* fs_api, const char* clean_dir) { | |||||||
|         FileInfo fileinfo; |         FileInfo fileinfo; | ||||||
|         char* name = malloc(MAX_NAME_LENGTH + 1); |         char* name = malloc(MAX_NAME_LENGTH + 1); | ||||||
|         while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { |         while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { | ||||||
|             char* fullname = malloc(strlen(clean_dir) + strlen(name) + 1 + 1); |             size_t size = strlen(clean_dir) + strlen(name) + 1 + 1; | ||||||
|             sprintf(fullname, "%s/%s", clean_dir, name); |             char* fullname = malloc(size); | ||||||
|  |             snprintf(fullname, size, "%s/%s", clean_dir, name); | ||||||
|             if(fileinfo.flags & FSF_DIRECTORY) { |             if(fileinfo.flags & FSF_DIRECTORY) { | ||||||
|                 clean_directory(fs_api, fullname); |                 clean_directory(fs_api, fullname); | ||||||
|             } |             } | ||||||
| @ -1226,7 +1227,7 @@ MU_TEST(test_storage_mkdir) { | |||||||
|     mu_check(test_is_exists(TEST_DIR "dir2")); |     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); |     Storage* api = furi_record_open(RECORD_STORAGE); | ||||||
|     File* file = storage_file_alloc(api); |     File* file = storage_file_alloc(api); | ||||||
| 
 | 
 | ||||||
| @ -1247,7 +1248,7 @@ static void test_storage_calculate_md5sum(const char* path, char* md5sum) { | |||||||
|         free(md5_ctx); |         free(md5_ctx); | ||||||
| 
 | 
 | ||||||
|         for(uint8_t i = 0; i < hash_size; i++) { |         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); |         free(hash); | ||||||
| @ -1299,9 +1300,9 @@ MU_TEST(test_storage_md5sum) { | |||||||
|     test_create_file(TEST_DIR "file1.txt", 0); |     test_create_file(TEST_DIR "file1.txt", 0); | ||||||
|     test_create_file(TEST_DIR "file2.txt", 1); |     test_create_file(TEST_DIR "file2.txt", 1); | ||||||
|     test_create_file(TEST_DIR "file3.txt", 512); |     test_create_file(TEST_DIR "file3.txt", 512); | ||||||
|     test_storage_calculate_md5sum(TEST_DIR "file1.txt", md5sum1); |     test_storage_calculate_md5sum(TEST_DIR "file1.txt", md5sum1, MD5SUM_SIZE * 2 + 1); | ||||||
|     test_storage_calculate_md5sum(TEST_DIR "file2.txt", md5sum2); |     test_storage_calculate_md5sum(TEST_DIR "file2.txt", md5sum2, MD5SUM_SIZE * 2 + 1); | ||||||
|     test_storage_calculate_md5sum(TEST_DIR "file3.txt", md5sum3); |     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); | ||||||
|     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); | ||||||
|  | |||||||
| @ -164,8 +164,6 @@ fwenv.AppendUnique( | |||||||
|         "-Wl,--wrap,_free_r", |         "-Wl,--wrap,_free_r", | ||||||
|         "-Wl,--wrap,_calloc_r", |         "-Wl,--wrap,_calloc_r", | ||||||
|         "-Wl,--wrap,_realloc_r", |         "-Wl,--wrap,_realloc_r", | ||||||
|         "-u", |  | ||||||
|         "_printf_float", |  | ||||||
|         "-n", |         "-n", | ||||||
|         "-Xlinker", |         "-Xlinker", | ||||||
|         "-Map=${TARGET}.map", |         "-Map=${TARGET}.map", | ||||||
| @ -181,6 +179,7 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( | |||||||
|     "${FIRMWARE_BUILD_CFG}", |     "${FIRMWARE_BUILD_CFG}", | ||||||
|     sources, |     sources, | ||||||
|     LIBS=[ |     LIBS=[ | ||||||
|  |         "print", | ||||||
|         "flipper${TARGET_HW}", |         "flipper${TARGET_HW}", | ||||||
|         "furi", |         "furi", | ||||||
|         "freertos", |         "freertos", | ||||||
|  | |||||||
| @ -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(); |  | ||||||
| } |  | ||||||
| @ -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 |  | ||||||
| @ -4,12 +4,21 @@ | |||||||
| #include "memmgr_heap.h" | #include "memmgr_heap.h" | ||||||
| #include "check.h" | #include "check.h" | ||||||
| #include "common_defines.h" | #include "common_defines.h" | ||||||
|  | #include "mutex.h" | ||||||
| 
 | 
 | ||||||
| #include <task.h> | #include <task.h> | ||||||
| #include <m-string.h> | #include <m-string.h> | ||||||
|  | #include <furi_hal_console.h> | ||||||
| 
 | 
 | ||||||
| #define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers
 | #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 { | struct FuriThread { | ||||||
|     FuriThreadState state; |     FuriThreadState state; | ||||||
|     int32_t ret; |     int32_t ret; | ||||||
| @ -27,8 +36,13 @@ struct FuriThread { | |||||||
|     TaskHandle_t task_handle; |     TaskHandle_t task_handle; | ||||||
|     bool heap_trace_enabled; |     bool heap_trace_enabled; | ||||||
|     size_t heap_size; |     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 */ | /** Catch threads that are trying to exit wrong way */ | ||||||
| __attribute__((__noreturn__)) void furi_thread_catch() { | __attribute__((__noreturn__)) void furi_thread_catch() { | ||||||
|     asm volatile("nop"); // extra magic
 |     asm volatile("nop"); // extra magic
 | ||||||
| @ -47,6 +61,10 @@ static void furi_thread_body(void* context) { | |||||||
|     furi_assert(context); |     furi_assert(context); | ||||||
|     FuriThread* thread = 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_assert(thread->state == FuriThreadStateStarting); | ||||||
|     furi_thread_set_state(thread, FuriThreadStateRunning); |     furi_thread_set_state(thread, FuriThreadStateRunning); | ||||||
| 
 | 
 | ||||||
| @ -66,12 +84,18 @@ static void furi_thread_body(void* context) { | |||||||
|     furi_assert(thread->state == FuriThreadStateRunning); |     furi_assert(thread->state == FuriThreadStateRunning); | ||||||
|     furi_thread_set_state(thread, FuriThreadStateStopped); |     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); |     vTaskDelete(thread->task_handle); | ||||||
|     furi_thread_catch(); |     furi_thread_catch(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| FuriThread* furi_thread_alloc() { | FuriThread* furi_thread_alloc() { | ||||||
|     FuriThread* thread = malloc(sizeof(FuriThread)); |     FuriThread* thread = malloc(sizeof(FuriThread)); | ||||||
|  |     string_init(thread->output.buffer); | ||||||
| 
 | 
 | ||||||
|     return thread; |     return thread; | ||||||
| } | } | ||||||
| @ -81,6 +105,8 @@ void furi_thread_free(FuriThread* thread) { | |||||||
|     furi_assert(thread->state == FuriThreadStateStopped); |     furi_assert(thread->state == FuriThreadStateStopped); | ||||||
| 
 | 
 | ||||||
|     if(thread->name) free((void*)thread->name); |     if(thread->name) free((void*)thread->name); | ||||||
|  |     string_clear(thread->output.buffer); | ||||||
|  | 
 | ||||||
|     free(thread); |     free(thread); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -199,6 +225,12 @@ FuriThreadId furi_thread_get_current_id() { | |||||||
|     return xTaskGetCurrentTaskHandle(); |     return xTaskGetCurrentTaskHandle(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | FuriThread* furi_thread_get_current() { | ||||||
|  |     FuriThread* thread = pvTaskGetThreadLocalStoragePointer(NULL, 0); | ||||||
|  |     furi_assert(thread != NULL); | ||||||
|  |     return thread; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void furi_thread_yield() { | void furi_thread_yield() { | ||||||
|     furi_assert(!FURI_IS_IRQ_MODE()); |     furi_assert(!FURI_IS_IRQ_MODE()); | ||||||
|     taskYIELD(); |     taskYIELD(); | ||||||
| @ -408,3 +440,59 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) { | |||||||
| 
 | 
 | ||||||
|     return (sz); |     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()); | ||||||
|  | } | ||||||
| @ -42,6 +42,12 @@ typedef void* FuriThreadId; | |||||||
|  */ |  */ | ||||||
| typedef int32_t (*FuriThreadCallback)(void* context); | 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
 | /** FuriThread state change calback called upon thread state change
 | ||||||
|  * @param      state    new thread state |  * @param      state    new thread state | ||||||
|  * @param      context  callback context |  * @param      context  callback context | ||||||
| @ -177,6 +183,12 @@ int32_t furi_thread_get_return_code(FuriThread* thread); | |||||||
|  */ |  */ | ||||||
| FuriThreadId furi_thread_get_current_id(); | FuriThreadId furi_thread_get_current_id(); | ||||||
| 
 | 
 | ||||||
|  | /** Get FuriThread instance for current thread
 | ||||||
|  |  *  | ||||||
|  |  * @return FuriThread*  | ||||||
|  |  */ | ||||||
|  | FuriThread* furi_thread_get_current(); | ||||||
|  | 
 | ||||||
| /** Return control to scheduler */ | /** Return control to scheduler */ | ||||||
| void furi_thread_yield(); | 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); | 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 | #ifdef __cplusplus | ||||||
| } | } | ||||||
| #endif | #endif | ||||||
|  | |||||||
| @ -8,7 +8,6 @@ void furi_init() { | |||||||
| 
 | 
 | ||||||
|     furi_log_init(); |     furi_log_init(); | ||||||
|     furi_record_init(); |     furi_record_init(); | ||||||
|     furi_stdglue_init(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void furi_run() { | void furi_run() { | ||||||
|  | |||||||
| @ -14,7 +14,6 @@ | |||||||
| #include <core/pubsub.h> | #include <core/pubsub.h> | ||||||
| #include <core/record.h> | #include <core/record.h> | ||||||
| #include <core/semaphore.h> | #include <core/semaphore.h> | ||||||
| #include <core/stdglue.h> |  | ||||||
| #include <core/thread.h> | #include <core/thread.h> | ||||||
| #include <core/timer.h> | #include <core/timer.h> | ||||||
| #include <core/valuemutex.h> | #include <core/valuemutex.h> | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ env.Append( | |||||||
|         "lib/toolbox", |         "lib/toolbox", | ||||||
|         "lib/u8g2", |         "lib/u8g2", | ||||||
|         "lib/update_util", |         "lib/update_util", | ||||||
|  |         "lib/print", | ||||||
|     ] |     ] | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @ -60,6 +61,7 @@ libs = env.BuildModules( | |||||||
|     [ |     [ | ||||||
|         "STM32CubeWB", |         "STM32CubeWB", | ||||||
|         "freertos", |         "freertos", | ||||||
|  |         "print", | ||||||
|         "microtar", |         "microtar", | ||||||
|         "toolbox", |         "toolbox", | ||||||
|         "ST25RFAL002", |         "ST25RFAL002", | ||||||
|  | |||||||
							
								
								
									
										107
									
								
								lib/print/SConscript
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								lib/print/SConscript
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								lib/print/printf_tiny.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1037
									
								
								lib/print/printf_tiny.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										103
									
								
								lib/print/printf_tiny.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								lib/print/printf_tiny.h
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								lib/print/wrappers.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								lib/print/wrappers.c
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||||
|  | } | ||||||
| @ -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 prefix_i = rand() % COUNT_OF(prefix); | ||||||
|     uint8_t suffix_i = rand() % COUNT_OF(suffix); |     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
 |     // Set first symbol to upper case
 | ||||||
|     name[0] = name[0] - 0x20; |     name[0] = name[0] - 0x20; | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 SG
						SG