diff --git a/applications/cli/cli.h b/applications/cli/cli.h index a7aa742a54b..60f1589f022 100644 --- a/applications/cli/cli.h +++ b/applications/cli/cli.h @@ -1,5 +1,9 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + #include /* Cli type @@ -40,3 +44,7 @@ void cli_print(const char* buffer); * Send new ine sequence */ void cli_nl(); + +#ifdef __cplusplus +} +#endif diff --git a/applications/sd-card-test/sd-card-test.cpp b/applications/sd-card-test/sd-card-test.cpp index dfe135f7f58..5431a392eed 100644 --- a/applications/sd-card-test/sd-card-test.cpp +++ b/applications/sd-card-test/sd-card-test.cpp @@ -2,6 +2,8 @@ #include "stm32_adafruit_sd.h" #include "fnv1a-hash.h" #include "filesystem-api.h" +#include "cli/cli.h" +#include "callback-connector.h" // event enumeration type typedef uint8_t event_t; @@ -47,6 +49,9 @@ class SdTest : public AppTemplate { uint8_t* benchmark_data; FS_Api* fs_api; + // consts + static const uint32_t BENCHMARK_ERROR = UINT_MAX; + // funcs void run(); void render(Canvas* canvas); @@ -63,15 +68,24 @@ class SdTest : public AppTemplate { void show_warning(); void get_sd_card_info(); - void prepare_benchmark_data(); + bool prepare_benchmark_data(); void free_benchmark_data(); void write_benchmark(); - uint32_t write_benchmark_internal(const uint32_t size, const uint32_t tcount); + uint32_t + write_benchmark_internal(const uint32_t size, const uint32_t tcount, bool silent = false); void read_benchmark(); - uint32_t read_benchmark_internal(const uint32_t size, const uint32_t count, File* file); + uint32_t read_benchmark_internal( + const uint32_t size, + const uint32_t count, + File* file, + bool silent = false); void hash_benchmark(); + + // cli tests + void cli_read_benchmark(string_t args, void* _ctx); + void cli_write_benchmark(string_t args, void* _ctx); }; // start app @@ -97,11 +111,29 @@ void SdTest::run() { exit(); } + Cli* cli = static_cast(furi_open("cli")); + + if(cli != NULL) { + // read_benchmark and write_benchmark signatures are same. so we must use tags + auto cli_read_cb = cbc::obtain_connector<0>(this, &SdTest::cli_read_benchmark); + cli_add_command(cli, "sd_read_test", cli_read_cb, this); + + auto cli_write_cb = cbc::obtain_connector<1>(this, &SdTest::cli_write_benchmark); + cli_add_command(cli, "sd_write_test", cli_write_cb, this); + } + detect_sd_card(); get_sd_card_info(); show_warning(); - prepare_benchmark_data(); + set_text({"preparing benchmark data"}); + bool data_prepared = prepare_benchmark_data(); + if(data_prepared) { + set_text({"benchmark data prepared"}); + } else { + set_error({"cannot allocate buffer", "for benchmark data"}); + } + write_benchmark(); read_benchmark(); hash_benchmark(); @@ -190,19 +222,19 @@ void SdTest::get_sd_card_info() { } // prepare benchmark data (allocate data in ram) -void SdTest::prepare_benchmark_data() { - set_text({"preparing benchmark data"}); +bool SdTest::prepare_benchmark_data() { + bool result = true; benchmark_data = static_cast(malloc(benchmark_data_size)); if(benchmark_data == NULL) { - set_error({"cannot allocate buffer", "for benchmark data"}); + result = false; } for(size_t i = 0; i < benchmark_data_size; i++) { benchmark_data[i] = static_cast(i); } - set_text({"benchmark data prepared"}); + return result; } void SdTest::free_benchmark_data() { @@ -269,35 +301,50 @@ void SdTest::write_benchmark() { wait_for_button(InputOk); } -uint32_t SdTest::write_benchmark_internal(const uint32_t size, const uint32_t count) { - uint32_t start_tick, stop_tick, benchmark_bps, benchmark_time, bytes_written; +uint32_t SdTest::write_benchmark_internal(const uint32_t size, const uint32_t count, bool silent) { + uint32_t start_tick, stop_tick, benchmark_bps = 0, benchmark_time, bytes_written; File file; const uint8_t str_buffer_size = 32; char str_buffer[str_buffer_size]; if(!fs_api->file.open(&file, "write.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { - snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); - set_error({"cannot open file ", static_cast(str_buffer)}); + if(!silent) { + snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); + set_error({"cannot open file ", static_cast(str_buffer)}); + } else { + benchmark_bps = BENCHMARK_ERROR; + } } start_tick = osKernelGetTickCount(); for(size_t i = 0; i < count; i++) { bytes_written = fs_api->file.write(&file, benchmark_data, size); if(bytes_written != size || file.error_id != FSE_OK) { - snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); - set_error({"cannot write to file ", static_cast(str_buffer)}); + if(!silent) { + snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); + set_error({"cannot write to file ", static_cast(str_buffer)}); + } else { + benchmark_bps = BENCHMARK_ERROR; + break; + } } } stop_tick = osKernelGetTickCount(); if(!fs_api->file.close(&file)) { - snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); - set_error({"cannot close file ", static_cast(str_buffer)}); + if(!silent) { + snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); + set_error({"cannot close file ", static_cast(str_buffer)}); + } else { + benchmark_bps = BENCHMARK_ERROR; + } } - benchmark_time = stop_tick - start_tick; - benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time; + if(benchmark_bps != BENCHMARK_ERROR) { + benchmark_time = stop_tick - start_tick; + benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time; + } return benchmark_bps; } @@ -394,9 +441,12 @@ void SdTest::read_benchmark() { wait_for_button(InputOk); } -uint32_t SdTest::read_benchmark_internal(const uint32_t size, const uint32_t count, File* file) { - uint32_t start_tick, stop_tick, benchmark_bps, benchmark_time, bytes_readed; - //FRESULT result; +uint32_t SdTest::read_benchmark_internal( + const uint32_t size, + const uint32_t count, + File* file, + bool silent) { + uint32_t start_tick, stop_tick, benchmark_bps = 0, benchmark_time, bytes_readed; const uint8_t str_buffer_size = 32; char str_buffer[str_buffer_size]; @@ -405,8 +455,12 @@ uint32_t SdTest::read_benchmark_internal(const uint32_t size, const uint32_t cou read_buffer = static_cast(malloc(size)); if(read_buffer == NULL) { - snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size); - set_error({"cannot allocate memory", static_cast(str_buffer)}); + if(!silent) { + snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size); + set_error({"cannot allocate memory", static_cast(str_buffer)}); + } else { + benchmark_bps = BENCHMARK_ERROR; + } } fs_api->file.seek(file, 0, true); @@ -415,16 +469,23 @@ uint32_t SdTest::read_benchmark_internal(const uint32_t size, const uint32_t cou for(size_t i = 0; i < count; i++) { bytes_readed = fs_api->file.read(file, read_buffer, size); if(bytes_readed != size || file->error_id != FSE_OK) { - snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size); - set_error({"cannot read from file ", static_cast(str_buffer)}); + if(!silent) { + snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size); + set_error({"cannot read from file ", static_cast(str_buffer)}); + } else { + benchmark_bps = BENCHMARK_ERROR; + break; + } } } stop_tick = osKernelGetTickCount(); free(read_buffer); - benchmark_time = stop_tick - start_tick; - benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time; + if(benchmark_bps != BENCHMARK_ERROR) { + benchmark_time = stop_tick - start_tick; + benchmark_bps = (count * size) * osKernelGetTickFreq() / benchmark_time; + } return benchmark_bps; } @@ -534,6 +595,198 @@ void SdTest::hash_benchmark() { wait_for_button(InputOk); } +void SdTest::cli_read_benchmark(string_t args, void* _ctx) { + SdTest* _this = static_cast(_ctx); + + const uint32_t benchmark_data_size = 16384 * 8; + uint32_t bytes_written; + uint32_t benchmark_bps = 0; + File file; + + const uint32_t b1_size = 1; + const uint32_t b8_size = 8; + const uint32_t b32_size = 32; + const uint32_t b256_size = 256; + const uint32_t b4096_size = 4096; + + const uint8_t str_buffer_size = 64; + char str_buffer[str_buffer_size]; + + cli_print("preparing benchmark data\r\n"); + bool data_prepared = _this->prepare_benchmark_data(); + if(data_prepared) { + cli_print("benchmark data prepared\r\n"); + } else { + cli_print("error: cannot allocate buffer for benchmark data\r\n"); + } + + // prepare data for read test + cli_print("prepare data for read speed test, procedure can be lengthy, please wait\r\n"); + + if(!_this->fs_api->file.open(&file, "read.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { + cli_print("error: cannot open file in prepare read\r\n"); + } + + for(size_t i = 0; i < benchmark_data_size / b4096_size; i++) { + bytes_written = _this->fs_api->file.write(&file, benchmark_data, b4096_size); + if(bytes_written != b4096_size || file.error_id != FSE_OK) { + cli_print("error: cannot write to file in prepare read\r\n"); + } + } + + if(!_this->fs_api->file.close(&file)) { + cli_print("error: cannot close file in prepare read\r\n"); + } + + // test start + cli_print("read speed test, procedure can be lengthy, please wait\r\n"); + + // open file + if(!_this->fs_api->file.open(&file, "read.test", FSAM_READ, FSOM_OPEN_EXISTING)) { + cli_print("error: cannot open file in read benchmark\r\n"); + } + + // 1b test + benchmark_bps = + _this->read_benchmark_internal(b1_size, benchmark_data_size / b1_size, &file, true); + if(benchmark_bps == BENCHMARK_ERROR) { + cli_print("error: in 1-byte read test\r\n"); + } else { + snprintf(str_buffer, str_buffer_size, "1-byte: %lu bytes per second\r\n", benchmark_bps); + cli_print(str_buffer); + } + + // 8b test + benchmark_bps = + _this->read_benchmark_internal(b8_size, benchmark_data_size / b8_size, &file, true); + if(benchmark_bps == BENCHMARK_ERROR) { + cli_print("error: in 8-byte read test\r\n"); + } else { + snprintf(str_buffer, str_buffer_size, "8-byte: %lu bytes per second\r\n", benchmark_bps); + cli_print(str_buffer); + } + + // 32b test + benchmark_bps = + _this->read_benchmark_internal(b32_size, benchmark_data_size / b32_size, &file, true); + if(benchmark_bps == BENCHMARK_ERROR) { + cli_print("error: in 32-byte read test\r\n"); + } else { + snprintf(str_buffer, str_buffer_size, "32-byte: %lu bytes per second\r\n", benchmark_bps); + cli_print(str_buffer); + } + + // 256b test + benchmark_bps = + _this->read_benchmark_internal(b256_size, benchmark_data_size / b256_size, &file, true); + if(benchmark_bps == BENCHMARK_ERROR) { + cli_print("error: in 256-byte read test\r\n"); + } else { + snprintf(str_buffer, str_buffer_size, "256-byte: %lu bytes per second\r\n", benchmark_bps); + cli_print(str_buffer); + } + + // 4096b test + benchmark_bps = + _this->read_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, &file, true); + if(benchmark_bps == BENCHMARK_ERROR) { + cli_print("error: in 4096-byte read test\r\n"); + } else { + snprintf( + str_buffer, str_buffer_size, "4096-byte: %lu bytes per second\r\n", benchmark_bps); + cli_print(str_buffer); + } + + // close file + if(!_this->fs_api->file.close(&file)) { + cli_print("error: cannot close file\r\n"); + } + + _this->free_benchmark_data(); + + cli_print("test completed\r\n"); +} + +void SdTest::cli_write_benchmark(string_t args, void* _ctx) { + SdTest* _this = static_cast(_ctx); + + const uint32_t b1_size = 1; + const uint32_t b8_size = 8; + const uint32_t b32_size = 32; + const uint32_t b256_size = 256; + const uint32_t b4096_size = 4096; + + const uint32_t benchmark_data_size = 16384 * 4; + + uint32_t benchmark_bps = 0; + + const uint8_t str_buffer_size = 64; + char str_buffer[str_buffer_size]; + + cli_print("preparing benchmark data\r\n"); + bool data_prepared = _this->prepare_benchmark_data(); + if(data_prepared) { + cli_print("benchmark data prepared\r\n"); + } else { + cli_print("error: cannot allocate buffer for benchmark data\r\n"); + } + + cli_print("write speed test, procedure can be lengthy, please wait\r\n"); + + // 1b test + benchmark_bps = _this->write_benchmark_internal(b1_size, benchmark_data_size / b1_size, true); + if(benchmark_bps == BENCHMARK_ERROR) { + cli_print("error: in 1-byte write test\r\n"); + } else { + snprintf(str_buffer, str_buffer_size, "1-byte: %lu bytes per second\r\n", benchmark_bps); + cli_print(str_buffer); + } + + // 8b test + benchmark_bps = _this->write_benchmark_internal(b8_size, benchmark_data_size / b8_size, true); + if(benchmark_bps == BENCHMARK_ERROR) { + cli_print("error: in 8-byte write test\r\n"); + } else { + snprintf(str_buffer, str_buffer_size, "8-byte: %lu bytes per second\r\n", benchmark_bps); + cli_print(str_buffer); + } + + // 32b test + benchmark_bps = + _this->write_benchmark_internal(b32_size, benchmark_data_size / b32_size, true); + if(benchmark_bps == BENCHMARK_ERROR) { + cli_print("error: in 32-byte write test\r\n"); + } else { + snprintf(str_buffer, str_buffer_size, "32-byte: %lu bytes per second\r\n", benchmark_bps); + cli_print(str_buffer); + } + + // 256b test + benchmark_bps = + _this->write_benchmark_internal(b256_size, benchmark_data_size / b256_size, true); + if(benchmark_bps == BENCHMARK_ERROR) { + cli_print("error: in 256-byte write test\r\n"); + } else { + snprintf(str_buffer, str_buffer_size, "256-byte: %lu bytes per second\r\n", benchmark_bps); + cli_print(str_buffer); + } + + // 4096b test + benchmark_bps = + _this->write_benchmark_internal(b4096_size, benchmark_data_size / b4096_size, true); + if(benchmark_bps == BENCHMARK_ERROR) { + cli_print("error: in 4096-byte write test\r\n"); + } else { + snprintf( + str_buffer, str_buffer_size, "4096-byte: %lu bytes per second\r\n", benchmark_bps); + cli_print(str_buffer); + } + + _this->free_benchmark_data(); + + cli_print("test completed\r\n"); +} + // wait for button press void SdTest::wait_for_button(Input input_button) { SdTestEvent event; diff --git a/applications/sd-filesystem/sd-filesystem.c b/applications/sd-filesystem/sd-filesystem.c index c9640e7b1f1..cd48ce93dc6 100644 --- a/applications/sd-filesystem/sd-filesystem.c +++ b/applications/sd-filesystem/sd-filesystem.c @@ -3,6 +3,7 @@ #include "sd-filesystem.h" #include "menu/menu.h" #include "menu/menu_item.h" +#include "cli/cli.h" FS_Api* fs_api_alloc() { FS_Api* fs_api = furi_alloc(sizeof(FS_Api)); @@ -263,10 +264,30 @@ void app_sd_info_callback(void* context) { } } +void app_sd_format_internal(SdApp* sd_app) { + uint8_t* work_area; + + _fs_lock(&sd_app->info); + work_area = malloc(_MAX_SS); + if(work_area == NULL) { + sd_app->info.status = SD_NOT_ENOUGH_CORE; + } else { + sd_app->info.status = f_mkfs(sd_app->info.path, FM_ANY, 0, work_area, _MAX_SS); + free(work_area); + + if(sd_app->info.status == SD_OK) { + // set label and mount card + f_setlabel("Flipper SD"); + sd_app->info.status = f_mount(&sd_app->info.fat_fs, sd_app->info.path, 1); + } + } + + _fs_unlock(&sd_app->info); +} + void app_sd_format_callback(void* context) { furi_assert(context); SdApp* sd_app = context; - uint8_t* work_area; // ask to really format sd_set_lines(sd_app, 2, "Press UP to format", "or BACK to exit"); @@ -282,20 +303,7 @@ void app_sd_format_callback(void* context) { sd_set_lines(sd_app, 3, "formatting SD card", "procedure can be lengthy", "please wait"); // format card - _fs_lock(&sd_app->info); - work_area = malloc(_MAX_SS); - if(work_area == NULL) { - sd_app->info.status = SD_NOT_ENOUGH_CORE; - } else { - sd_app->info.status = f_mkfs(sd_app->info.path, FM_ANY, 0, work_area, _MAX_SS); - free(work_area); - - if(sd_app->info.status == SD_OK) { - // set label and mount card - f_setlabel("Flipper SD"); - sd_app->info.status = f_mount(&sd_app->info.fat_fs, sd_app->info.path, 1); - } - } + app_sd_format_internal(sd_app); if(sd_app->info.status != SD_OK) { sd_set_lines( @@ -304,8 +312,6 @@ void app_sd_format_callback(void* context) { sd_set_lines(sd_app, 1, "SD card formatted"); } - _fs_unlock(&sd_app->info); - // wait for BACK app_sd_ask(sd_app, InputBack, InputBack); @@ -356,6 +362,120 @@ void app_sd_eject_callback(void* context) { widget_enabled_set(sd_app->widget, false); } +static void cli_sd_status(string_t args, void* _ctx) { + SdApp* sd_app = (SdApp*)_ctx; + + cli_print("SD status: "); + cli_print(fs_error_get_internal_desc(sd_app->info.status)); + cli_print("\r\n"); +} + +static void cli_sd_format(string_t args, void* _ctx) { + SdApp* sd_app = (SdApp*)_ctx; + + cli_print("formatting SD card, please wait\r\n"); + + // format card + app_sd_format_internal(sd_app); + + if(sd_app->info.status != SD_OK) { + cli_print("SD card format error: "); + cli_print(fs_error_get_internal_desc(sd_app->info.status)); + cli_print("\r\n"); + } else { + cli_print("SD card formatted\r\n"); + } +} + +static void cli_sd_info(string_t args, void* _ctx) { + SdApp* sd_app = (SdApp*)_ctx; + + const uint8_t str_buffer_size = 64; + char str_buffer[str_buffer_size]; + + // info vars + uint32_t serial_num; + SDError get_label_result, get_free_result; + FATFS* fs; + uint32_t free_clusters, free_sectors, total_sectors; + char volume_label[34]; + + // get fs info + _fs_lock(&sd_app->info); + get_label_result = f_getlabel(sd_app->info.path, volume_label, &serial_num); + get_free_result = f_getfree(sd_app->info.path, &free_clusters, &fs); + _fs_unlock(&sd_app->info); + + // calculate size + total_sectors = (fs->n_fatent - 2) * fs->csize; + free_sectors = free_clusters * fs->csize; + uint16_t sector_size = _MAX_SS; +#if _MAX_SS != _MIN_SS + sector_size = fs->ssize; +#endif + + // output info to dynamic strings + if(get_label_result == SD_OK && get_free_result == SD_OK) { + const char* fs_type = ""; + + switch(fs->fs_type) { + case(FS_FAT12): + fs_type = "FAT12"; + break; + case(FS_FAT16): + fs_type = "FAT16"; + break; + case(FS_FAT32): + fs_type = "FAT32"; + break; + case(FS_EXFAT): + fs_type = "EXFAT"; + break; + default: + fs_type = "UNKNOWN"; + break; + } + + snprintf(str_buffer, str_buffer_size, "Label: %s\r\n", volume_label); + cli_print(str_buffer); + + snprintf(str_buffer, str_buffer_size, "%s, S/N: %lu\r\n", fs_type, serial_num); + cli_print(str_buffer); + + snprintf(str_buffer, str_buffer_size, "Cluster: %d sectors\r\n", fs->csize); + cli_print(str_buffer); + + snprintf(str_buffer, str_buffer_size, "Sector: %d bytes\r\n", sector_size); + cli_print(str_buffer); + + snprintf( + str_buffer, str_buffer_size, "%lu KB total\r\n", total_sectors / 1024 * sector_size); + cli_print(str_buffer); + + snprintf( + str_buffer, str_buffer_size, "%lu KB free\r\n", free_sectors / 1024 * sector_size); + cli_print(str_buffer); + } else { + cli_print("SD status error: "); + snprintf( + str_buffer, + str_buffer_size, + "%s\r\n", + fs_error_get_internal_desc(_fs_status(&sd_app->info))); + cli_print(str_buffer); + + cli_print("Label error: "); + snprintf( + str_buffer, str_buffer_size, "%s\r\n", fs_error_get_internal_desc(get_label_result)); + cli_print(str_buffer); + + cli_print("Get free error: "); + snprintf( + str_buffer, str_buffer_size, "%s\r\n", fs_error_get_internal_desc(get_free_result)); + cli_print(str_buffer); + } +} + void sd_filesystem(void* p) { SdApp* sd_app = sd_app_alloc(); FS_Api* fs_api = fs_api_alloc(); @@ -364,6 +484,14 @@ void sd_filesystem(void* p) { gui_add_widget(gui, sd_app->widget, GuiLayerFullscreen); gui_add_widget(gui, sd_app->icon.widget, GuiLayerStatusBarLeft); + Cli* cli = furi_open("cli"); + + if(cli != NULL) { + cli_add_command(cli, "sd_status", cli_sd_status, sd_app); + cli_add_command(cli, "sd_format", cli_sd_format, sd_app); + cli_add_command(cli, "sd_info", cli_sd_info, sd_app); + } + // add api record if(!furi_create("sdcard", fs_api)) { furiac_exit(NULL);