[FL-520] Filesystem Api and App (#280)
* update fatfs integer types * fix sector size to 512 * fix sector size calculation * common fs api * fs api realization (sd card + fat fs) * better sector size definition * more api realization fns * add error description api, add common api * fix api flag naming, run app * add fs_info call * disable fatfs strfuncs, enable fatfs chmod * rework filesystem app * sd detect cycle, sd menu, sd eject feature * fix sd detect cycle * sd card format routine * ui improvements, sd info routine * properly unmount card * separate mode flags * add api folder, move app, rename app * fix api naming * update st-card-test to use api * update path to app * fixed potential problem of using sizeof union * updated api documentation, new time/date fns * update codeowners * changed app requirements * changed app order * sd insert/remove log
This commit is contained in:
		
							
								
								
									
										11
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							| @@ -43,6 +43,8 @@ applications/gui/** @skotopes | |||||||
| # iButton | # iButton | ||||||
|  |  | ||||||
| applications/ibutton/** @DrZlo13 | applications/ibutton/** @DrZlo13 | ||||||
|  | lib/cyfral/** @DrZlo13 | ||||||
|  | lib/onewire/** @DrZlo13 | ||||||
|  |  | ||||||
| # IR | # IR | ||||||
|  |  | ||||||
| @@ -61,9 +63,12 @@ applications/menu/** @skotopes | |||||||
| applications/nfc/** @skotopes | applications/nfc/** @skotopes | ||||||
| lib/ST25RFAL002/** @skotopes | lib/ST25RFAL002/** @skotopes | ||||||
|  |  | ||||||
| # SD Card | # SD Card and filesystem | ||||||
|  |  | ||||||
| applications/sd-card-test/** @DrZlo13 | applications/sd-card-test/** @DrZlo13 | ||||||
|  | applications/sd-filesystem/** @DrZlo13 | ||||||
|  | lib/common-api/filesystem-api.h @DrZlo13 | ||||||
|  | lib/fatfs/** @DrZlo13 | ||||||
|  |  | ||||||
| # Power control app | # Power control app | ||||||
|  |  | ||||||
| @@ -74,3 +79,7 @@ applications/power/** @skotopes | |||||||
| applications/music-player/** @DrZlo13 | applications/music-player/** @DrZlo13 | ||||||
| applications/floopper-bloopper/** @glitchcore | applications/floopper-bloopper/** @glitchcore | ||||||
| applications/gpio-tester/** @glitchcore | applications/gpio-tester/** @glitchcore | ||||||
|  |  | ||||||
|  | lib/app-template/** @DrZlo13 | ||||||
|  | lib/qrcode/** @DrZlo13 | ||||||
|  | lib/callback-connector/** @DrZlo13 | ||||||
| @@ -41,6 +41,7 @@ void cli_task(void* p); | |||||||
| void music_player(void* p); | void music_player(void* p); | ||||||
| void sdnfc(void* p); | void sdnfc(void* p); | ||||||
| void floopper_bloopper(void* p); | void floopper_bloopper(void* p); | ||||||
|  | void sd_filesystem(void* p); | ||||||
|  |  | ||||||
| const FlipperStartupApp FLIPPER_STARTUP[] = { | const FlipperStartupApp FLIPPER_STARTUP[] = { | ||||||
| #ifdef APP_DISPLAY | #ifdef APP_DISPLAY | ||||||
| @@ -88,6 +89,13 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { | |||||||
|      .icon = A_Plugins_14}, |      .icon = A_Plugins_14}, | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #ifdef APP_SD_FILESYSTEM | ||||||
|  |     {.app = sd_filesystem, | ||||||
|  |      .name = "sd_filesystem", | ||||||
|  |      .libs = {1, FURI_LIB{"menu_task"}}, | ||||||
|  |      .icon = A_Plugins_14}, | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #ifdef APP_DOLPHIN | #ifdef APP_DOLPHIN | ||||||
|     {.app = dolphin_task, |     {.app = dolphin_task, | ||||||
|      .name = "dolphin_task", |      .name = "dolphin_task", | ||||||
| @@ -165,7 +173,7 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { | |||||||
| #ifdef APP_SD_TEST | #ifdef APP_SD_TEST | ||||||
|     {.app = sd_card_test, |     {.app = sd_card_test, | ||||||
|      .name = "sd_card_test", |      .name = "sd_card_test", | ||||||
|      .libs = {1, FURI_LIB{"gui_task"}}, |      .libs = {2, FURI_LIB{"gui_task", "sd_filesystem"}}, | ||||||
|      .icon = A_Plugins_14}, |      .icon = A_Plugins_14}, | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| @@ -257,7 +265,7 @@ const FlipperStartupApp FLIPPER_PLUGINS[] = { | |||||||
| #ifdef BUILD_SD_TEST | #ifdef BUILD_SD_TEST | ||||||
|     {.app = sd_card_test, |     {.app = sd_card_test, | ||||||
|      .name = "sd_card_test", |      .name = "sd_card_test", | ||||||
|      .libs = {1, FURI_LIB{"gui_task"}}, |      .libs = {2, FURI_LIB{"gui_task", "sd_filesystem"}}, | ||||||
|      .icon = A_Plugins_14}, |      .icon = A_Plugins_14}, | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ APP_NFC  = 1 | |||||||
| APP_POWER = 1 | APP_POWER = 1 | ||||||
| APP_BT = 1 | APP_BT = 1 | ||||||
| APP_CLI = 1 | APP_CLI = 1 | ||||||
|  | APP_SD_FILESYSTEM = 1 | ||||||
| BUILD_IRDA  = 1 | BUILD_IRDA  = 1 | ||||||
| APP_DOLPHIN = 1 | APP_DOLPHIN = 1 | ||||||
| BUILD_EXAMPLE_BLINK = 1 | BUILD_EXAMPLE_BLINK = 1 | ||||||
| @@ -220,6 +221,7 @@ CFLAGS		+= -DBUILD_SD_TEST | |||||||
| CPP_SOURCES	+= $(wildcard $(APP_DIR)/sd-card-test/*.cpp) | CPP_SOURCES	+= $(wildcard $(APP_DIR)/sd-card-test/*.cpp) | ||||||
| APP_INPUT = 1 | APP_INPUT = 1 | ||||||
| APP_GUI = 1 | APP_GUI = 1 | ||||||
|  | APP_SD_FILESYSTEM = 1 | ||||||
| endif | endif | ||||||
|  |  | ||||||
| APP_SPEAKER_DEMO ?= 0 | APP_SPEAKER_DEMO ?= 0 | ||||||
| @@ -305,6 +307,12 @@ C_SOURCES	+= $(wildcard $(APP_DIR)/gui/*.c) | |||||||
| C_SOURCES	+= $(wildcard $(APP_DIR)/backlight-control/*.c) | C_SOURCES	+= $(wildcard $(APP_DIR)/backlight-control/*.c) | ||||||
| endif | endif | ||||||
|  |  | ||||||
|  | APP_SD_FILESYSTEM	?= 0 | ||||||
|  | ifeq ($(APP_SD_FILESYSTEM), 1) | ||||||
|  | CFLAGS		+= -DAPP_SD_FILESYSTEM | ||||||
|  | C_SOURCES	+= $(wildcard $(APP_DIR)/sd-filesystem/*.c) | ||||||
|  | endif | ||||||
|  |  | ||||||
| # deprecated | # deprecated | ||||||
| ifeq ($(APP_DISPLAY), 1) | ifeq ($(APP_DISPLAY), 1) | ||||||
| CFLAGS		+= -DAPP_DISPLAY | CFLAGS		+= -DAPP_DISPLAY | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| #include "app-template.h" | #include "app-template.h" | ||||||
| #include "fatfs/ff.h" |  | ||||||
| #include "stm32_adafruit_sd.h" | #include "stm32_adafruit_sd.h" | ||||||
| #include "fnv1a-hash.h" | #include "fnv1a-hash.h" | ||||||
|  | #include "filesystem-api.h" | ||||||
|  |  | ||||||
| // event enumeration type | // event enumeration type | ||||||
| typedef uint8_t event_t; | typedef uint8_t event_t; | ||||||
| @@ -43,17 +43,15 @@ public: | |||||||
|     // vars |     // vars | ||||||
|     GpioPin* red_led_record; |     GpioPin* red_led_record; | ||||||
|     GpioPin* green_led_record; |     GpioPin* green_led_record; | ||||||
|     FATFS sd_fat_fs; |  | ||||||
|     char sd_path[6]; |  | ||||||
|     const uint32_t benchmark_data_size = 4096; |     const uint32_t benchmark_data_size = 4096; | ||||||
|     uint8_t* benchmark_data; |     uint8_t* benchmark_data; | ||||||
|  |     FS_Api* fs_api; | ||||||
|  |  | ||||||
|     // funcs |     // funcs | ||||||
|     void run(); |     void run(); | ||||||
|     void render(Canvas* canvas); |     void render(Canvas* canvas); | ||||||
|     template <class T> void set_text(std::initializer_list<T> list); |     template <class T> void set_text(std::initializer_list<T> list); | ||||||
|     template <class T> void set_error(std::initializer_list<T> list); |     template <class T> void set_error(std::initializer_list<T> list); | ||||||
|     const char* fatfs_error_desc(FRESULT res); |  | ||||||
|     void wait_for_button(Input input_button); |     void wait_for_button(Input input_button); | ||||||
|     bool ask(Input input_button_cancel, Input input_button_ok); |     bool ask(Input input_button_cancel, Input input_button_ok); | ||||||
|     void blink_red(); |     void blink_red(); | ||||||
| @@ -63,11 +61,6 @@ public: | |||||||
|     // "tests" |     // "tests" | ||||||
|     void detect_sd_card(); |     void detect_sd_card(); | ||||||
|     void show_warning(); |     void show_warning(); | ||||||
|     void init_sd_card(); |  | ||||||
|     bool is_sd_card_formatted(); |  | ||||||
|     void ask_and_format_sd_card(); |  | ||||||
|     void mount_sd_card(); |  | ||||||
|     void format_sd_card(); |  | ||||||
|     void get_sd_card_info(); |     void get_sd_card_info(); | ||||||
|  |  | ||||||
|     void prepare_benchmark_data(); |     void prepare_benchmark_data(); | ||||||
| @@ -76,7 +69,7 @@ public: | |||||||
|     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); | ||||||
|  |  | ||||||
|     void read_benchmark(); |     void read_benchmark(); | ||||||
|     uint32_t read_benchmark_internal(const uint32_t size, const uint32_t count, FIL* file); |     uint32_t read_benchmark_internal(const uint32_t size, const uint32_t count, File* file); | ||||||
|  |  | ||||||
|     void hash_benchmark(); |     void hash_benchmark(); | ||||||
| }; | }; | ||||||
| @@ -97,16 +90,17 @@ void SdTest::run() { | |||||||
|  |  | ||||||
|     app_ready(); |     app_ready(); | ||||||
|  |  | ||||||
|     detect_sd_card(); |     fs_api = static_cast<FS_Api*>(furi_open("sdcard")); | ||||||
|     show_warning(); |  | ||||||
|     init_sd_card(); |     if(fs_api == NULL) { | ||||||
|     if(!is_sd_card_formatted()) { |         set_error({"cannot get sdcard api"}); | ||||||
|         format_sd_card(); |         exit(); | ||||||
|     } else { |  | ||||||
|         ask_and_format_sd_card(); |  | ||||||
|     } |     } | ||||||
|     mount_sd_card(); |  | ||||||
|  |     detect_sd_card(); | ||||||
|     get_sd_card_info(); |     get_sd_card_info(); | ||||||
|  |     show_warning(); | ||||||
|  |  | ||||||
|     prepare_benchmark_data(); |     prepare_benchmark_data(); | ||||||
|     write_benchmark(); |     write_benchmark(); | ||||||
|     read_benchmark(); |     read_benchmark(); | ||||||
| @@ -134,7 +128,7 @@ void SdTest::detect_sd_card() { | |||||||
|     uint8_t i = 0; |     uint8_t i = 0; | ||||||
|  |  | ||||||
|     // detect sd card pin |     // detect sd card pin | ||||||
|     while(!hal_gpio_read_sd_detect()) { |     while(fs_api->common.get_fs_info(NULL, NULL) == FSE_NOT_READY) { | ||||||
|         delay(100); |         delay(100); | ||||||
|  |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "Waiting%s", dots[i]); |         snprintf(str_buffer, str_buffer_size, "Waiting%s", dots[i]); | ||||||
| @@ -155,7 +149,7 @@ void SdTest::show_warning() { | |||||||
|     set_text( |     set_text( | ||||||
|         {"!!Warning!!", |         {"!!Warning!!", | ||||||
|          "during the tests", |          "during the tests", | ||||||
|          "card may be formatted", |          "files can be overwritten", | ||||||
|          "or data on card may be lost", |          "or data on card may be lost", | ||||||
|          "", |          "", | ||||||
|          "press UP DOWN OK to continue"}); |          "press UP DOWN OK to continue"}); | ||||||
| @@ -165,96 +159,22 @@ void SdTest::show_warning() { | |||||||
|     wait_for_button(InputOk); |     wait_for_button(InputOk); | ||||||
| } | } | ||||||
|  |  | ||||||
| // init low level driver |  | ||||||
| void SdTest::init_sd_card() { |  | ||||||
|     uint8_t bsp_result = BSP_SD_Init(); |  | ||||||
|  |  | ||||||
|     // BSP_SD_OK = 0 |  | ||||||
|     if(bsp_result) { |  | ||||||
|         set_error({"SD card init error", "BSP error"}); |  | ||||||
|     } |  | ||||||
|     blink_green(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // test, if sd card need to be formatted |  | ||||||
| bool SdTest::is_sd_card_formatted() { |  | ||||||
|     FRESULT result; |  | ||||||
|     set_text({"checking if card needs to be formatted"}); |  | ||||||
|  |  | ||||||
|     result = f_mount(&sd_fat_fs, sd_path, 1); |  | ||||||
|     if(result == FR_NO_FILESYSTEM) { |  | ||||||
|         return false; |  | ||||||
|     } else { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void SdTest::ask_and_format_sd_card() { |  | ||||||
|     set_text({"Want to format sd card?", "", "", "", "", "LEFT to CANCEL | RIGHT to OK"}); |  | ||||||
|     if(ask(InputLeft, InputRight)) { |  | ||||||
|         format_sd_card(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // mount sd card |  | ||||||
| void SdTest::mount_sd_card() { |  | ||||||
|     FRESULT result; |  | ||||||
|     set_text({"mounting sdcard"}); |  | ||||||
|  |  | ||||||
|     result = f_mount(&sd_fat_fs, sd_path, 1); |  | ||||||
|     if(result) { |  | ||||||
|         set_error({"SD card mount error", fatfs_error_desc(result)}); |  | ||||||
|     } |  | ||||||
|     blink_green(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // format sd card |  | ||||||
| void SdTest::format_sd_card() { |  | ||||||
|     FRESULT result; |  | ||||||
|     BYTE* work_area; |  | ||||||
|  |  | ||||||
|     set_text({"formatting sdcard", "procedure can be lengthy", "please wait"}); |  | ||||||
|     delay(100); |  | ||||||
|  |  | ||||||
|     work_area = static_cast<BYTE*>(malloc(_MAX_SS)); |  | ||||||
|     if(work_area == NULL) { |  | ||||||
|         set_error({"SD card format error", "cannot allocate memory"}); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     result = f_mkfs(sd_path, (FM_FAT | FM_FAT32 | FM_EXFAT), 0, work_area, _MAX_SS); |  | ||||||
|     free(work_area); |  | ||||||
|  |  | ||||||
|     if(result) { |  | ||||||
|         set_error({"SD card format error", fatfs_error_desc(result)}); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     result = f_setlabel("Flipper SD"); |  | ||||||
|     if(result) { |  | ||||||
|         set_error({"SD card set label error", fatfs_error_desc(result)}); |  | ||||||
|     } |  | ||||||
|     blink_green(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // get info about sd card, label, sn | // get info about sd card, label, sn | ||||||
| // sector, cluster, total and free size | // sector, cluster, total and free size | ||||||
| void SdTest::get_sd_card_info() { | void SdTest::get_sd_card_info() { | ||||||
|     const uint8_t str_buffer_size = 26; |     const uint8_t str_buffer_size = 26; | ||||||
|     char str_buffer[4][str_buffer_size]; |     char str_buffer[2][str_buffer_size]; | ||||||
|     char volume_label[128]; |     FS_Error result; | ||||||
|     DWORD serial_num; |     uint64_t bytes_total, bytes_free; | ||||||
|     FRESULT result; |  | ||||||
|     FATFS* fs; |  | ||||||
|     DWORD free_clusters, free_sectors, total_sectors; |  | ||||||
|  |  | ||||||
|     // suppress "'%s' directive output may be truncated" warning about snprintf |  | ||||||
|     int __attribute__((unused)) snprintf_count = 0; |     int __attribute__((unused)) snprintf_count = 0; | ||||||
|  |  | ||||||
|     // get label and s/n |     result = fs_api->common.get_fs_info(&bytes_total, &bytes_free); | ||||||
|     result = f_getlabel(sd_path, volume_label, &serial_num); |     if(result != FSE_OK) set_error({"get_fs_info error", fs_api->error.get_desc(result)}); | ||||||
|     if(result) set_error({"f_getlabel error", fatfs_error_desc(result)}); |  | ||||||
|  |  | ||||||
|     snprintf_count = snprintf(str_buffer[0], str_buffer_size, "Label: %s", volume_label); |     snprintf( | ||||||
|     snprintf(str_buffer[1], str_buffer_size, "S/N: %lu", serial_num); |         str_buffer[0], str_buffer_size, "%lu KB total", static_cast<uint32_t>(bytes_total / 1024)); | ||||||
|  |     snprintf( | ||||||
|  |         str_buffer[1], str_buffer_size, "%lu KB free", static_cast<uint32_t>(bytes_free / 1024)); | ||||||
|  |  | ||||||
|     set_text( |     set_text( | ||||||
|         {static_cast<const char*>(str_buffer[0]), |         {static_cast<const char*>(str_buffer[0]), | ||||||
| @@ -267,30 +187,6 @@ void SdTest::get_sd_card_info() { | |||||||
|     blink_green(); |     blink_green(); | ||||||
|  |  | ||||||
|     wait_for_button(InputOk); |     wait_for_button(InputOk); | ||||||
|  |  | ||||||
|     // get total and free space |  | ||||||
|     result = f_getfree(sd_path, &free_clusters, &fs); |  | ||||||
|     if(result) set_error({"f_getfree error", fatfs_error_desc(result)}); |  | ||||||
|  |  | ||||||
|     total_sectors = (fs->n_fatent - 2) * fs->csize; |  | ||||||
|     free_sectors = free_clusters * fs->csize; |  | ||||||
|  |  | ||||||
|     snprintf(str_buffer[0], str_buffer_size, "Cluster: %d sectors", fs->csize); |  | ||||||
|     snprintf(str_buffer[1], str_buffer_size, "Sector: %d bytes", fs->ssize); |  | ||||||
|     snprintf(str_buffer[2], str_buffer_size, "%lu KB total", total_sectors / 1024 * fs->ssize); |  | ||||||
|     snprintf(str_buffer[3], str_buffer_size, "%lu KB free", free_sectors / 1024 * fs->ssize); |  | ||||||
|  |  | ||||||
|     set_text( |  | ||||||
|         {static_cast<const char*>(str_buffer[0]), |  | ||||||
|          static_cast<const char*>(str_buffer[1]), |  | ||||||
|          static_cast<const char*>(str_buffer[2]), |  | ||||||
|          static_cast<const char*>(str_buffer[3]), |  | ||||||
|          "", |  | ||||||
|          "press OK to continue"}); |  | ||||||
|  |  | ||||||
|     blink_green(); |  | ||||||
|  |  | ||||||
|     wait_for_button(InputOk); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // prepare benchmark data (allocate data in ram) | // prepare benchmark data (allocate data in ram) | ||||||
| @@ -375,30 +271,27 @@ void SdTest::write_benchmark() { | |||||||
|  |  | ||||||
| uint32_t SdTest::write_benchmark_internal(const uint32_t size, const uint32_t count) { | 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 start_tick, stop_tick, benchmark_bps, benchmark_time, bytes_written; | ||||||
|     FRESULT result; |     File file; | ||||||
|     FIL file; |  | ||||||
|  |  | ||||||
|     const uint8_t str_buffer_size = 32; |     const uint8_t str_buffer_size = 32; | ||||||
|     char str_buffer[str_buffer_size]; |     char str_buffer[str_buffer_size]; | ||||||
|  |  | ||||||
|     result = f_open(&file, "write.test", FA_WRITE | FA_OPEN_ALWAYS); |     if(!fs_api->file.open(&file, "write.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { | ||||||
|     if(result) { |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); |         snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); | ||||||
|         set_error({"cannot open file ", static_cast<const char*>(str_buffer)}); |         set_error({"cannot open file ", static_cast<const char*>(str_buffer)}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     start_tick = osKernelGetTickCount(); |     start_tick = osKernelGetTickCount(); | ||||||
|     for(size_t i = 0; i < count; i++) { |     for(size_t i = 0; i < count; i++) { | ||||||
|         result = f_write(&file, benchmark_data, size, reinterpret_cast<UINT*>(&bytes_written)); |         bytes_written = fs_api->file.write(&file, benchmark_data, size); | ||||||
|         if(bytes_written != size || result) { |         if(bytes_written != size || file.error_id != FSE_OK) { | ||||||
|             snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); |             snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); | ||||||
|             set_error({"cannot write to file ", static_cast<const char*>(str_buffer)}); |             set_error({"cannot write to file ", static_cast<const char*>(str_buffer)}); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     stop_tick = osKernelGetTickCount(); |     stop_tick = osKernelGetTickCount(); | ||||||
|  |  | ||||||
|     result = f_close(&file); |     if(!fs_api->file.close(&file)) { | ||||||
|     if(result) { |  | ||||||
|         snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); |         snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); | ||||||
|         set_error({"cannot close file ", static_cast<const char*>(str_buffer)}); |         set_error({"cannot close file ", static_cast<const char*>(str_buffer)}); | ||||||
|     } |     } | ||||||
| @@ -425,8 +318,7 @@ void SdTest::read_benchmark() { | |||||||
|         static_cast<const char*>(str_buffer[4]), |         static_cast<const char*>(str_buffer[4]), | ||||||
|         static_cast<const char*>(str_buffer[5])}; |         static_cast<const char*>(str_buffer[5])}; | ||||||
|  |  | ||||||
|     FRESULT result; |     File file; | ||||||
|     FIL file; |  | ||||||
|  |  | ||||||
|     const uint32_t b1_size = 1; |     const uint32_t b1_size = 1; | ||||||
|     const uint32_t b8_size = 8; |     const uint32_t b8_size = 8; | ||||||
| @@ -438,21 +330,18 @@ void SdTest::read_benchmark() { | |||||||
|     set_text({"prepare data", "for read speed test", "procedure can be lengthy", "please wait"}); |     set_text({"prepare data", "for read speed test", "procedure can be lengthy", "please wait"}); | ||||||
|     delay(100); |     delay(100); | ||||||
|  |  | ||||||
|     result = f_open(&file, "read.test", FA_WRITE | FA_OPEN_ALWAYS); |     if(!fs_api->file.open(&file, "read.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { | ||||||
|     if(result) { |  | ||||||
|         set_error({"cannot open file ", "in prepare read"}); |         set_error({"cannot open file ", "in prepare read"}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     for(size_t i = 0; i < benchmark_data_size / b4096_size; i++) { |     for(size_t i = 0; i < benchmark_data_size / b4096_size; i++) { | ||||||
|         result = |         bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size); | ||||||
|             f_write(&file, benchmark_data, b4096_size, reinterpret_cast<UINT*>(&bytes_written)); |         if(bytes_written != b4096_size || file.error_id != FSE_OK) { | ||||||
|         if(bytes_written != b4096_size || result) { |  | ||||||
|             set_error({"cannot write to file ", "in prepare read"}); |             set_error({"cannot write to file ", "in prepare read"}); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     result = f_close(&file); |     if(!fs_api->file.close(&file)) { | ||||||
|     if(result) { |  | ||||||
|         set_error({"cannot close file ", "in prepare read"}); |         set_error({"cannot close file ", "in prepare read"}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -461,8 +350,7 @@ void SdTest::read_benchmark() { | |||||||
|     delay(100); |     delay(100); | ||||||
|  |  | ||||||
|     // open file |     // open file | ||||||
|     result = f_open(&file, "read.test", FA_READ | FA_OPEN_EXISTING); |     if(!fs_api->file.open(&file, "read.test", FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||||
|     if(result) { |  | ||||||
|         set_error({"cannot open file ", "in read benchmark"}); |         set_error({"cannot open file ", "in read benchmark"}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -497,8 +385,7 @@ void SdTest::read_benchmark() { | |||||||
|     set_text(string_list); |     set_text(string_list); | ||||||
|  |  | ||||||
|     // close file |     // close file | ||||||
|     result = f_close(&file); |     if(!fs_api->file.close(&file)) { | ||||||
|     if(result) { |  | ||||||
|         set_error({"cannot close file ", "in read test"}); |         set_error({"cannot close file ", "in read test"}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -507,9 +394,9 @@ void SdTest::read_benchmark() { | |||||||
|     wait_for_button(InputOk); |     wait_for_button(InputOk); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint32_t SdTest::read_benchmark_internal(const uint32_t size, const uint32_t count, FIL* file) { | 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; |     uint32_t start_tick, stop_tick, benchmark_bps, benchmark_time, bytes_readed; | ||||||
|     FRESULT result; |     //FRESULT result; | ||||||
|  |  | ||||||
|     const uint8_t str_buffer_size = 32; |     const uint8_t str_buffer_size = 32; | ||||||
|     char str_buffer[str_buffer_size]; |     char str_buffer[str_buffer_size]; | ||||||
| @@ -522,12 +409,12 @@ uint32_t SdTest::read_benchmark_internal(const uint32_t size, const uint32_t cou | |||||||
|         set_error({"cannot allocate memory", static_cast<const char*>(str_buffer)}); |         set_error({"cannot allocate memory", static_cast<const char*>(str_buffer)}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     f_rewind(file); |     fs_api->file.seek(file, 0, true); | ||||||
|  |  | ||||||
|     start_tick = osKernelGetTickCount(); |     start_tick = osKernelGetTickCount(); | ||||||
|     for(size_t i = 0; i < count; i++) { |     for(size_t i = 0; i < count; i++) { | ||||||
|         result = f_read(file, read_buffer, size, reinterpret_cast<UINT*>(&bytes_readed)); |         bytes_readed = fs_api->file.read(file, read_buffer, size); | ||||||
|         if(bytes_readed != size || result) { |         if(bytes_readed != size || file->error_id != FSE_OK) { | ||||||
|             snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size); |             snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size); | ||||||
|             set_error({"cannot read from file ", static_cast<const char*>(str_buffer)}); |             set_error({"cannot read from file ", static_cast<const char*>(str_buffer)}); | ||||||
|         } |         } | ||||||
| @@ -555,8 +442,7 @@ void SdTest::hash_benchmark() { | |||||||
|     const uint8_t str_buffer_size = 32; |     const uint8_t str_buffer_size = 32; | ||||||
|     char str_buffer[3][str_buffer_size] = {"", "", ""}; |     char str_buffer[3][str_buffer_size] = {"", "", ""}; | ||||||
|  |  | ||||||
|     FRESULT result; |     File file; | ||||||
|     FIL file; |  | ||||||
|  |  | ||||||
|     const uint32_t b4096_size = 4096; |     const uint32_t b4096_size = 4096; | ||||||
|     const uint32_t benchmark_count = 20; |     const uint32_t benchmark_count = 20; | ||||||
| @@ -566,17 +452,15 @@ void SdTest::hash_benchmark() { | |||||||
|     delay(100); |     delay(100); | ||||||
|  |  | ||||||
|     // write data to test file and calculate hash |     // write data to test file and calculate hash | ||||||
|     result = f_open(&file, "hash.test", FA_WRITE | FA_OPEN_ALWAYS); |     if(!fs_api->file.open(&file, "hash.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { | ||||||
|     if(result) { |  | ||||||
|         set_error({"cannot open file ", "in prepare hash"}); |         set_error({"cannot open file ", "in prepare hash"}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     for(uint32_t i = 0; i < benchmark_count; i++) { |     for(uint32_t i = 0; i < benchmark_count; i++) { | ||||||
|         mcu_data_hash = fnv1a_buffer_hash(benchmark_data, b4096_size, mcu_data_hash); |         mcu_data_hash = fnv1a_buffer_hash(benchmark_data, b4096_size, mcu_data_hash); | ||||||
|         result = |         bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size); | ||||||
|             f_write(&file, benchmark_data, b4096_size, reinterpret_cast<UINT*>(&bytes_written)); |  | ||||||
|  |  | ||||||
|         if(bytes_written != b4096_size || result) { |         if(bytes_written != b4096_size || file.error_id != FSE_OK) { | ||||||
|             set_error({"cannot write to file ", "in prepare hash"}); |             set_error({"cannot write to file ", "in prepare hash"}); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -585,8 +469,7 @@ void SdTest::hash_benchmark() { | |||||||
|         delay(100); |         delay(100); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     result = f_close(&file); |     if(!fs_api->file.close(&file)) { | ||||||
|     if(result) { |  | ||||||
|         set_error({"cannot close file ", "in prepare hash"}); |         set_error({"cannot close file ", "in prepare hash"}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -602,16 +485,15 @@ void SdTest::hash_benchmark() { | |||||||
|         set_error({"cannot allocate memory", "in hash test"}); |         set_error({"cannot allocate memory", "in hash test"}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     result = f_open(&file, "hash.test", FA_READ | FA_OPEN_EXISTING); |     if(!fs_api->file.open(&file, "hash.test", FSAM_READ, FSOM_OPEN_EXISTING)) { | ||||||
|     if(result) { |  | ||||||
|         set_error({"cannot open file ", "in hash test"}); |         set_error({"cannot open file ", "in hash test"}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     for(uint32_t i = 0; i < benchmark_count; i++) { |     for(uint32_t i = 0; i < benchmark_count; i++) { | ||||||
|         result = f_read(&file, read_buffer, b4096_size, reinterpret_cast<UINT*>(&bytes_readed)); |         bytes_readed = fs_api->file.read(&file, read_buffer, b4096_size); | ||||||
|         sdcard_data_hash = fnv1a_buffer_hash(read_buffer, b4096_size, sdcard_data_hash); |         sdcard_data_hash = fnv1a_buffer_hash(read_buffer, b4096_size, sdcard_data_hash); | ||||||
|  |  | ||||||
|         if(bytes_readed != b4096_size || result) { |         if(bytes_readed != b4096_size || file.error_id != FSE_OK) { | ||||||
|             set_error({"cannot read from file ", "in hash test"}); |             set_error({"cannot read from file ", "in hash test"}); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -620,9 +502,7 @@ void SdTest::hash_benchmark() { | |||||||
|         delay(100); |         delay(100); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     result = f_close(&file); |     if(!fs_api->file.close(&file)) { | ||||||
|  |  | ||||||
|     if(result) { |  | ||||||
|         set_error({"cannot close file ", "in hash test"}); |         set_error({"cannot close file ", "in hash test"}); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -730,76 +610,6 @@ void SdTest::blink_green() { | |||||||
|     gpio_write(green_led_record, 1); |     gpio_write(green_led_record, 1); | ||||||
| } | } | ||||||
|  |  | ||||||
| // FatFs errors descriptions |  | ||||||
| const char* SdTest::fatfs_error_desc(FRESULT res) { |  | ||||||
|     switch(res) { |  | ||||||
|     case FR_OK: |  | ||||||
|         return "ok"; |  | ||||||
|         break; |  | ||||||
|     case FR_DISK_ERR: |  | ||||||
|         return "low level error"; |  | ||||||
|         break; |  | ||||||
|     case FR_INT_ERR: |  | ||||||
|         return "internal error"; |  | ||||||
|         break; |  | ||||||
|     case FR_NOT_READY: |  | ||||||
|         return "not ready"; |  | ||||||
|         break; |  | ||||||
|     case FR_NO_FILE: |  | ||||||
|         return "no file"; |  | ||||||
|         break; |  | ||||||
|     case FR_NO_PATH: |  | ||||||
|         return "no path"; |  | ||||||
|         break; |  | ||||||
|     case FR_INVALID_NAME: |  | ||||||
|         return "invalid name"; |  | ||||||
|         break; |  | ||||||
|     case FR_DENIED: |  | ||||||
|         return "denied"; |  | ||||||
|         break; |  | ||||||
|     case FR_EXIST: |  | ||||||
|         return "already exist"; |  | ||||||
|         break; |  | ||||||
|     case FR_INVALID_OBJECT: |  | ||||||
|         return "invalid file/dir obj"; |  | ||||||
|         break; |  | ||||||
|     case FR_WRITE_PROTECTED: |  | ||||||
|         return "write protected"; |  | ||||||
|         break; |  | ||||||
|     case FR_INVALID_DRIVE: |  | ||||||
|         return "invalid drive"; |  | ||||||
|         break; |  | ||||||
|     case FR_NOT_ENABLED: |  | ||||||
|         return "no work area in volume"; |  | ||||||
|         break; |  | ||||||
|     case FR_NO_FILESYSTEM: |  | ||||||
|         return "no valid FS volume"; |  | ||||||
|         break; |  | ||||||
|     case FR_MKFS_ABORTED: |  | ||||||
|         return "aborted, any problem"; |  | ||||||
|         break; |  | ||||||
|     case FR_TIMEOUT: |  | ||||||
|         return "timeout"; |  | ||||||
|         break; |  | ||||||
|     case FR_LOCKED: |  | ||||||
|         return "file locked"; |  | ||||||
|         break; |  | ||||||
|     case FR_NOT_ENOUGH_CORE: |  | ||||||
|         return "not enough core memory"; |  | ||||||
|         break; |  | ||||||
|     case FR_TOO_MANY_OPEN_FILES: |  | ||||||
|         return "too many open files"; |  | ||||||
|         break; |  | ||||||
|     case FR_INVALID_PARAMETER: |  | ||||||
|         return "invalid parameter"; |  | ||||||
|         break; |  | ||||||
|  |  | ||||||
|     default: |  | ||||||
|         return "unknown error"; |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // set text, but with infinite loop | // set text, but with infinite loop | ||||||
| template <class T> void SdTest::set_error(std::initializer_list<T> list) { | template <class T> void SdTest::set_error(std::initializer_list<T> list) { | ||||||
|     set_text(list); |     set_text(list); | ||||||
|   | |||||||
							
								
								
									
										738
									
								
								applications/sd-filesystem/sd-filesystem-api.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										738
									
								
								applications/sd-filesystem/sd-filesystem-api.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,738 @@ | |||||||
|  | #include "fatfs.h" | ||||||
|  | #include "filesystem-api.h" | ||||||
|  | #include "sd-filesystem.h" | ||||||
|  |  | ||||||
|  | /******************* Global vars for api *******************/ | ||||||
|  |  | ||||||
|  | static SdFsInfo* fs_info; | ||||||
|  |  | ||||||
|  | /******************* Core Functions *******************/ | ||||||
|  |  | ||||||
|  | bool _fs_init(SdFsInfo* _fs_info) { | ||||||
|  |     bool result = true; | ||||||
|  |     _fs_info->mutex = osMutexNew(NULL); | ||||||
|  |     if(_fs_info->mutex == NULL) result = false; | ||||||
|  |  | ||||||
|  |     for(uint8_t i = 0; i < SD_FS_MAX_FILES; i++) { | ||||||
|  |         _fs_info->files[i].thread_id = NULL; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     _fs_info->path = "0:/"; | ||||||
|  |     _fs_info->status = SD_NO_CARD; | ||||||
|  |  | ||||||
|  |     // store pointer for api fns | ||||||
|  |     fs_info = _fs_info; | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool _fs_lock(SdFsInfo* fs_info) { | ||||||
|  |     return (osMutexAcquire(fs_info->mutex, osWaitForever) == osOK); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool _fs_unlock(SdFsInfo* fs_info) { | ||||||
|  |     return (osMutexRelease(fs_info->mutex) == osOK); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SDError _get_filedata(SdFsInfo* fs_info, File* file, FileData** filedata, FiledataFilter filter) { | ||||||
|  |     SDError error = SD_OK; | ||||||
|  |     _fs_lock(fs_info); | ||||||
|  |  | ||||||
|  |     if(fs_info->status == SD_OK) { | ||||||
|  |         if(file != NULL && file->file_id < SD_FS_MAX_FILES) { | ||||||
|  |             if(fs_info->files[file->file_id].thread_id == osThreadGetId()) { | ||||||
|  |                 if(filter == FDF_ANY) { | ||||||
|  |                     // any type | ||||||
|  |                     *filedata = &fs_info->files[file->file_id]; | ||||||
|  |                 } else if(filter == FDF_FILE) { | ||||||
|  |                     // file type | ||||||
|  |                     if(!fs_info->files[file->file_id].is_dir) { | ||||||
|  |                         *filedata = &fs_info->files[file->file_id]; | ||||||
|  |                     } else { | ||||||
|  |                         error = SD_NOT_A_FILE; | ||||||
|  |                     } | ||||||
|  |                 } else if(filter == FDF_DIR) { | ||||||
|  |                     // dir type | ||||||
|  |                     if(fs_info->files[file->file_id].is_dir) { | ||||||
|  |                         *filedata = &fs_info->files[file->file_id]; | ||||||
|  |                     } else { | ||||||
|  |                         error = SD_NOT_A_DIR; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 error = SD_OTHER_APP; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             error = SD_INVALID_PARAMETER; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         error = SD_NO_CARD; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     _fs_unlock(fs_info); | ||||||
|  |     return error; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SDError _get_file(SdFsInfo* fs_info, File* file, FileData** filedata) { | ||||||
|  |     return _get_filedata(fs_info, file, filedata, FDF_FILE); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SDError _get_dir(SdFsInfo* fs_info, File* file, FileData** filedata) { | ||||||
|  |     return _get_filedata(fs_info, file, filedata, FDF_DIR); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SDError _get_any(SdFsInfo* fs_info, File* file, FileData** filedata) { | ||||||
|  |     return _get_filedata(fs_info, file, filedata, FDF_ANY); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SDError _fs_status(SdFsInfo* fs_info) { | ||||||
|  |     SDError result; | ||||||
|  |  | ||||||
|  |     _fs_lock(fs_info); | ||||||
|  |     result = fs_info->status; | ||||||
|  |     _fs_unlock(fs_info); | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void _fs_on_client_app_exit(SdFsInfo* fs_info) { | ||||||
|  |     _fs_lock(fs_info); | ||||||
|  |     for(uint8_t i = 0; i < SD_FS_MAX_FILES; i++) { | ||||||
|  |         if(fs_info->files[i].thread_id == osThreadGetId()) { | ||||||
|  |             if(fs_info->files[i].is_dir) { | ||||||
|  |                 // TODO close dir | ||||||
|  |             } else { | ||||||
|  |                 // TODO close file | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     _fs_unlock(fs_info); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | FS_Error _fs_parse_error(SDError error) { | ||||||
|  |     FS_Error result; | ||||||
|  |     switch(error) { | ||||||
|  |     case SD_OK: | ||||||
|  |         result = FSE_OK; | ||||||
|  |         break; | ||||||
|  |     case SD_INT_ERR: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     case SD_NO_FILE: | ||||||
|  |         result = FSE_NOT_EXIST; | ||||||
|  |         break; | ||||||
|  |     case SD_NO_PATH: | ||||||
|  |         result = FSE_NOT_EXIST; | ||||||
|  |         break; | ||||||
|  |     case SD_INVALID_NAME: | ||||||
|  |         result = FSE_INVALID_NAME; | ||||||
|  |         break; | ||||||
|  |     case SD_DENIED: | ||||||
|  |         result = FSE_DENIED; | ||||||
|  |         break; | ||||||
|  |     case SD_EXIST: | ||||||
|  |         result = FSE_EXIST; | ||||||
|  |         break; | ||||||
|  |     case SD_INVALID_OBJECT: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     case SD_WRITE_PROTECTED: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     case SD_INVALID_DRIVE: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     case SD_NOT_ENABLED: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     case SD_NO_FILESYSTEM: | ||||||
|  |         result = FSE_NOT_READY; | ||||||
|  |         break; | ||||||
|  |     case SD_MKFS_ABORTED: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     case SD_TIMEOUT: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     case SD_LOCKED: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     case SD_NOT_ENOUGH_CORE: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     case SD_TOO_MANY_OPEN_FILES: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     case SD_INVALID_PARAMETER: | ||||||
|  |         result = FSE_INVALID_PARAMETER; | ||||||
|  |         break; | ||||||
|  |     case SD_NO_CARD: | ||||||
|  |         result = FSE_NOT_READY; | ||||||
|  |         break; | ||||||
|  |     case SD_NOT_A_FILE: | ||||||
|  |         result = FSE_INVALID_PARAMETER; | ||||||
|  |         break; | ||||||
|  |     case SD_NOT_A_DIR: | ||||||
|  |         result = FSE_INVALID_PARAMETER; | ||||||
|  |         break; | ||||||
|  |     case SD_OTHER_APP: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |  | ||||||
|  |     default: | ||||||
|  |         result = FSE_INTERNAL; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /******************* File Functions *******************/ | ||||||
|  |  | ||||||
|  | // Open/Create a file | ||||||
|  | bool fs_file_open(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode) { | ||||||
|  |     SDFile* sd_file = NULL; | ||||||
|  |  | ||||||
|  |     _fs_lock(fs_info); | ||||||
|  |     for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) { | ||||||
|  |         FileData* filedata = &fs_info->files[index]; | ||||||
|  |  | ||||||
|  |         if(filedata->thread_id == NULL) { | ||||||
|  |             file->file_id = index; | ||||||
|  |  | ||||||
|  |             memset(&(filedata->data), 0, sizeof(SDFileDirStorage)); | ||||||
|  |             filedata->thread_id = osThreadGetId(); | ||||||
|  |             filedata->is_dir = false; | ||||||
|  |             sd_file = &(filedata->data.file); | ||||||
|  |  | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     _fs_unlock(fs_info); | ||||||
|  |  | ||||||
|  |     if(sd_file == NULL) { | ||||||
|  |         file->internal_error_id = SD_TOO_MANY_OPEN_FILES; | ||||||
|  |     } else { | ||||||
|  |         uint8_t _mode = 0; | ||||||
|  |  | ||||||
|  |         if(access_mode & FSAM_READ) _mode |= FA_READ; | ||||||
|  |         if(access_mode & FSAM_WRITE) _mode |= FA_WRITE; | ||||||
|  |         if(open_mode & FSOM_OPEN_EXISTING) _mode |= FA_OPEN_EXISTING; | ||||||
|  |         if(open_mode & FSOM_OPEN_ALWAYS) _mode |= FA_OPEN_ALWAYS; | ||||||
|  |         if(open_mode & FSOM_OPEN_APPEND) _mode |= FA_OPEN_APPEND; | ||||||
|  |         if(open_mode & FSOM_CREATE_NEW) _mode |= FA_CREATE_NEW; | ||||||
|  |         if(open_mode & FSOM_CREATE_ALWAYS) _mode |= FA_CREATE_ALWAYS; | ||||||
|  |  | ||||||
|  |         file->internal_error_id = f_open(sd_file, path, _mode); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // TODO on exit | ||||||
|  |     //furiac_onexit(_fs_on_client_app_exit, fs_info); | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return (file->internal_error_id == SD_OK); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close an opened file | ||||||
|  | bool fs_file_close(File* file) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         file->internal_error_id = f_close(&filedata->data.file); | ||||||
|  |  | ||||||
|  |         _fs_lock(fs_info); | ||||||
|  |         filedata->thread_id = NULL; | ||||||
|  |         _fs_unlock(fs_info); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return (file->internal_error_id == SD_OK); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Read data from the file | ||||||
|  | uint16_t fs_file_read(File* file, void* buff, uint16_t const bytes_to_read) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |     uint16_t bytes_readed = 0; | ||||||
|  |  | ||||||
|  |     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         file->internal_error_id = f_read(&filedata->data.file, buff, bytes_to_read, &bytes_readed); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return bytes_readed; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Write data to the file | ||||||
|  | uint16_t fs_file_write(File* file, void* buff, uint16_t const bytes_to_write) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |     uint16_t bytes_written = 0; | ||||||
|  |  | ||||||
|  |     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         file->internal_error_id = | ||||||
|  |             f_write(&filedata->data.file, buff, bytes_to_write, &bytes_written); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return bytes_written; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Move read/write pointer, expand size | ||||||
|  | bool fs_file_seek(File* file, const uint32_t offset, const bool from_start) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |  | ||||||
|  |     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         if(from_start) { | ||||||
|  |             file->internal_error_id = f_lseek(&filedata->data.file, offset); | ||||||
|  |         } else { | ||||||
|  |             uint64_t position = f_tell(&filedata->data.file); | ||||||
|  |             position += offset; | ||||||
|  |             file->internal_error_id = f_lseek(&filedata->data.file, position); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return (file->internal_error_id == SD_OK); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Tell pointer position | ||||||
|  | uint64_t fs_file_tell(File* file) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |     uint64_t position = 0; | ||||||
|  |     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         position = f_tell(&filedata->data.file); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return position; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Truncate file size to current pointer value | ||||||
|  | bool fs_file_truncate(File* file) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |     uint64_t position = 0; | ||||||
|  |  | ||||||
|  |     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         file->internal_error_id = f_truncate(&filedata->data.file); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return (file->internal_error_id == SD_OK); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Flush cached data | ||||||
|  | bool fs_file_sync(File* file) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |  | ||||||
|  |     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         file->internal_error_id = f_sync(&filedata->data.file); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return (file->internal_error_id == SD_OK); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get size | ||||||
|  | uint64_t fs_file_size(File* file) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |     uint64_t size = 0; | ||||||
|  |     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         size = f_size(&filedata->data.file); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return size; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Test EOF | ||||||
|  | bool fs_file_eof(File* file) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |     bool eof = true; | ||||||
|  |     file->internal_error_id = _get_file(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         eof = f_eof(&filedata->data.file); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return eof; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /******************* Dir Functions *******************/ | ||||||
|  |  | ||||||
|  | // Open directory | ||||||
|  | bool fs_dir_open(File* file, const char* path) { | ||||||
|  |     SDDir* sd_dir = NULL; | ||||||
|  |  | ||||||
|  |     _fs_lock(fs_info); | ||||||
|  |     for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) { | ||||||
|  |         FileData* filedata = &fs_info->files[index]; | ||||||
|  |  | ||||||
|  |         if(filedata->thread_id == NULL) { | ||||||
|  |             file->file_id = index; | ||||||
|  |  | ||||||
|  |             memset(&(filedata->data), 0, sizeof(SDFileDirStorage)); | ||||||
|  |             filedata->thread_id = osThreadGetId(); | ||||||
|  |             filedata->is_dir = true; | ||||||
|  |             sd_dir = &(filedata->data.dir); | ||||||
|  |  | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     _fs_unlock(fs_info); | ||||||
|  |  | ||||||
|  |     if(sd_dir == NULL) { | ||||||
|  |         file->internal_error_id = SD_TOO_MANY_OPEN_FILES; | ||||||
|  |     } else { | ||||||
|  |         if(file->internal_error_id == SD_OK) file->internal_error_id = f_opendir(sd_dir, path); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // TODO on exit | ||||||
|  |     //furiac_onexit(_fs_on_client_app_exit, fs_info); | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return (file->internal_error_id == SD_OK); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close directory | ||||||
|  | bool fs_dir_close(File* file) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |     file->internal_error_id = _get_dir(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         file->internal_error_id = f_closedir(&filedata->data.dir); | ||||||
|  |  | ||||||
|  |         _fs_lock(fs_info); | ||||||
|  |         filedata->thread_id = NULL; | ||||||
|  |         _fs_unlock(fs_info); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return (file->internal_error_id == SD_OK); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Read next file info and name from directory | ||||||
|  | bool fs_dir_read(File* file, FileInfo* fileinfo, char* name, const uint16_t name_length) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |     file->internal_error_id = _get_dir(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         SDFileInfo _fileinfo; | ||||||
|  |         file->internal_error_id = f_readdir(&filedata->data.dir, &_fileinfo); | ||||||
|  |  | ||||||
|  |         if(fileinfo != NULL) { | ||||||
|  |             fileinfo->date.value = _fileinfo.fdate; | ||||||
|  |             fileinfo->time.value = _fileinfo.ftime; | ||||||
|  |             fileinfo->size = _fileinfo.fsize; | ||||||
|  |             fileinfo->flags = 0; | ||||||
|  |  | ||||||
|  |             if(_fileinfo.fattrib & AM_RDO) fileinfo->flags |= FSF_READ_ONLY; | ||||||
|  |             if(_fileinfo.fattrib & AM_HID) fileinfo->flags |= FSF_HIDDEN; | ||||||
|  |             if(_fileinfo.fattrib & AM_SYS) fileinfo->flags |= FSF_SYSTEM; | ||||||
|  |             if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||||
|  |             if(_fileinfo.fattrib & AM_ARC) fileinfo->flags |= FSF_ARCHIVE; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(name != NULL && name_length > 0) { | ||||||
|  |             strncpy(name, _fileinfo.fname, name_length); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return (file->internal_error_id == SD_OK); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool fs_dir_rewind(File* file) { | ||||||
|  |     FileData* filedata = NULL; | ||||||
|  |     file->internal_error_id = _get_dir(fs_info, file, &filedata); | ||||||
|  |  | ||||||
|  |     if(file->internal_error_id == SD_OK) { | ||||||
|  |         file->internal_error_id = f_readdir(&filedata->data.dir, NULL); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     file->error_id = _fs_parse_error(file->internal_error_id); | ||||||
|  |     return (file->internal_error_id == SD_OK); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /******************* Common FS Functions *******************/ | ||||||
|  |  | ||||||
|  | // Get info about file/dir | ||||||
|  | FS_Error | ||||||
|  | fs_common_info(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length) { | ||||||
|  |     SDFileInfo _fileinfo; | ||||||
|  |     SDError fresult = _fs_status(fs_info); | ||||||
|  |  | ||||||
|  |     if(fresult == SD_OK) { | ||||||
|  |         fresult = f_stat(path, &_fileinfo); | ||||||
|  |         if(fresult == FR_OK) { | ||||||
|  |             if(fileinfo != NULL) { | ||||||
|  |                 fileinfo->date.value = _fileinfo.fdate; | ||||||
|  |                 fileinfo->time.value = _fileinfo.ftime; | ||||||
|  |                 fileinfo->size = _fileinfo.fsize; | ||||||
|  |                 fileinfo->flags = 0; | ||||||
|  |  | ||||||
|  |                 if(_fileinfo.fattrib & AM_RDO) fileinfo->flags |= FSF_READ_ONLY; | ||||||
|  |                 if(_fileinfo.fattrib & AM_HID) fileinfo->flags |= FSF_HIDDEN; | ||||||
|  |                 if(_fileinfo.fattrib & AM_SYS) fileinfo->flags |= FSF_SYSTEM; | ||||||
|  |                 if(_fileinfo.fattrib & AM_DIR) fileinfo->flags |= FSF_DIRECTORY; | ||||||
|  |                 if(_fileinfo.fattrib & AM_ARC) fileinfo->flags |= FSF_ARCHIVE; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if(name != NULL && name_length > 0) { | ||||||
|  |                 strncpy(name, _fileinfo.fname, name_length); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return _fs_parse_error(fresult); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Delete file/dir | ||||||
|  | // File/dir must not have read-only attribute. | ||||||
|  | // File/dir must be empty. | ||||||
|  | // File/dir must not be opened, or the FAT volume can be collapsed. FF_FS_LOCK fix that. | ||||||
|  | FS_Error fs_common_remove(const char* path) { | ||||||
|  |     SDError fresult = _fs_status(fs_info); | ||||||
|  |  | ||||||
|  |     if(fresult == SD_OK) { | ||||||
|  |         fresult = f_unlink(path); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return _fs_parse_error(fresult); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Rename file/dir | ||||||
|  | // File/dir must not be opened, or the FAT volume can be collapsed. FF_FS_LOCK fix that. | ||||||
|  | FS_Error fs_common_rename(const char* old_path, const char* new_path) { | ||||||
|  |     SDError fresult = _fs_status(fs_info); | ||||||
|  |  | ||||||
|  |     if(fresult == SD_OK) { | ||||||
|  |         fresult = f_rename(old_path, new_path); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return _fs_parse_error(fresult); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set attributes of file/dir | ||||||
|  | // For example: | ||||||
|  | // set "read only" flag and remove "hidden" flag | ||||||
|  | // fs_common_set_attr("file.txt", FSF_READ_ONLY, FSF_READ_ONLY | FSF_HIDDEN); | ||||||
|  | FS_Error fs_common_set_attr(const char* path, uint8_t attr, uint8_t mask) { | ||||||
|  |     SDError fresult = _fs_status(fs_info); | ||||||
|  |  | ||||||
|  |     if(fresult == SD_OK) { | ||||||
|  |         uint8_t _mask = 0; | ||||||
|  |         uint8_t _attr = 0; | ||||||
|  |  | ||||||
|  |         if(mask & FSF_READ_ONLY) _mask |= AM_RDO; | ||||||
|  |         if(mask & FSF_HIDDEN) _mask |= AM_HID; | ||||||
|  |         if(mask & FSF_SYSTEM) _mask |= AM_SYS; | ||||||
|  |         if(mask & FSF_DIRECTORY) _mask |= AM_DIR; | ||||||
|  |         if(mask & FSF_ARCHIVE) _mask |= AM_ARC; | ||||||
|  |  | ||||||
|  |         if(attr & FSF_READ_ONLY) _attr |= AM_RDO; | ||||||
|  |         if(attr & FSF_HIDDEN) _attr |= AM_HID; | ||||||
|  |         if(attr & FSF_SYSTEM) _attr |= AM_SYS; | ||||||
|  |         if(attr & FSF_DIRECTORY) _attr |= AM_DIR; | ||||||
|  |         if(attr & FSF_ARCHIVE) _attr |= AM_ARC; | ||||||
|  |  | ||||||
|  |         fresult = f_chmod(path, attr, mask); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return _fs_parse_error(fresult); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Set time of file/dir | ||||||
|  | FS_Error fs_common_set_time(const char* path, FileDateUnion date, FileTimeUnion time) { | ||||||
|  |     SDError fresult = _fs_status(fs_info); | ||||||
|  |  | ||||||
|  |     if(fresult == SD_OK) { | ||||||
|  |         SDFileInfo _fileinfo; | ||||||
|  |  | ||||||
|  |         _fileinfo.fdate = date.value; | ||||||
|  |         _fileinfo.ftime = time.value; | ||||||
|  |  | ||||||
|  |         fresult = f_utime(path, &_fileinfo); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return _fs_parse_error(fresult); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Create new directory | ||||||
|  | FS_Error fs_common_mkdir(const char* path) { | ||||||
|  |     SDError fresult = _fs_status(fs_info); | ||||||
|  |  | ||||||
|  |     if(fresult == SD_OK) { | ||||||
|  |         fresult = f_mkdir(path); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return _fs_parse_error(fresult); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get common info about FS | ||||||
|  | FS_Error fs_get_fs_info(uint64_t* total_space, uint64_t* free_space) { | ||||||
|  |     SDError fresult = _fs_status(fs_info); | ||||||
|  |  | ||||||
|  |     if(fresult == SD_OK) { | ||||||
|  |         DWORD free_clusters; | ||||||
|  |         FATFS* fs; | ||||||
|  |  | ||||||
|  |         fresult = f_getfree("0:/", &free_clusters, &fs); | ||||||
|  |         if(fresult == FR_OK) { | ||||||
|  |             uint32_t total_sectors = (fs->n_fatent - 2) * fs->csize; | ||||||
|  |             uint32_t free_sectors = free_clusters * fs->csize; | ||||||
|  |  | ||||||
|  |             uint16_t sector_size = _MAX_SS; | ||||||
|  | #if _MAX_SS != _MIN_SS | ||||||
|  |             sector_size = fs->ssize; | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |             if(total_space != NULL) { | ||||||
|  |                 *total_space = (uint64_t)total_sectors * (uint64_t)sector_size; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if(free_space != NULL) { | ||||||
|  |                 *free_space = (uint64_t)free_sectors * (uint64_t)sector_size; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return _fs_parse_error(fresult); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /******************* Error Reporting Functions *******************/ | ||||||
|  |  | ||||||
|  | // Get common error description | ||||||
|  | const char* fs_error_get_desc(FS_Error error_id) { | ||||||
|  |     const char* result; | ||||||
|  |     switch(error_id) { | ||||||
|  |     case(FSE_OK): | ||||||
|  |         result = "OK"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_NOT_READY): | ||||||
|  |         result = "filesystem not ready"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_EXIST): | ||||||
|  |         result = "file/dir already exist"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_NOT_EXIST): | ||||||
|  |         result = "file/dir not exist"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_INVALID_PARAMETER): | ||||||
|  |         result = "invalid parameter"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_DENIED): | ||||||
|  |         result = "access denied"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_INVALID_NAME): | ||||||
|  |         result = "invalid name/path"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_INTERNAL): | ||||||
|  |         result = "internal error"; | ||||||
|  |         break; | ||||||
|  |     case(FSE_NOT_IMPLEMENTED): | ||||||
|  |         result = "function not implemented"; | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         result = "unknown error"; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Get internal error description | ||||||
|  | const char* fs_error_get_internal_desc(uint32_t internal_error_id) { | ||||||
|  |     const char* result; | ||||||
|  |     switch(internal_error_id) { | ||||||
|  |     case(SD_OK): | ||||||
|  |         result = "OK"; | ||||||
|  |         break; | ||||||
|  |     case(SD_DISK_ERR): | ||||||
|  |         result = "disk error"; | ||||||
|  |         break; | ||||||
|  |     case(SD_INT_ERR): | ||||||
|  |         result = "internal error"; | ||||||
|  |         break; | ||||||
|  |     case(SD_NO_FILE): | ||||||
|  |         result = "no file"; | ||||||
|  |         break; | ||||||
|  |     case(SD_NO_PATH): | ||||||
|  |         result = "no path"; | ||||||
|  |         break; | ||||||
|  |     case(SD_INVALID_NAME): | ||||||
|  |         result = "invalid name"; | ||||||
|  |         break; | ||||||
|  |     case(SD_DENIED): | ||||||
|  |         result = "access denied"; | ||||||
|  |         break; | ||||||
|  |     case(SD_EXIST): | ||||||
|  |         result = "file/dir exist"; | ||||||
|  |         break; | ||||||
|  |     case(SD_INVALID_OBJECT): | ||||||
|  |         result = "invalid object"; | ||||||
|  |         break; | ||||||
|  |     case(SD_WRITE_PROTECTED): | ||||||
|  |         result = "write protected"; | ||||||
|  |         break; | ||||||
|  |     case(SD_INVALID_DRIVE): | ||||||
|  |         result = "invalid drive"; | ||||||
|  |         break; | ||||||
|  |     case(SD_NOT_ENABLED): | ||||||
|  |         result = "not enabled"; | ||||||
|  |         break; | ||||||
|  |     case(SD_NO_FILESYSTEM): | ||||||
|  |         result = "no filesystem"; | ||||||
|  |         break; | ||||||
|  |     case(SD_MKFS_ABORTED): | ||||||
|  |         result = "aborted"; | ||||||
|  |         break; | ||||||
|  |     case(SD_TIMEOUT): | ||||||
|  |         result = "timeout"; | ||||||
|  |         break; | ||||||
|  |     case(SD_LOCKED): | ||||||
|  |         result = "file locked"; | ||||||
|  |         break; | ||||||
|  |     case(SD_NOT_ENOUGH_CORE): | ||||||
|  |         result = "not enough memory"; | ||||||
|  |         break; | ||||||
|  |     case(SD_TOO_MANY_OPEN_FILES): | ||||||
|  |         result = "too many open files"; | ||||||
|  |         break; | ||||||
|  |     case(SD_INVALID_PARAMETER): | ||||||
|  |         result = "invalid parameter"; | ||||||
|  |         break; | ||||||
|  |     case(SD_NO_CARD): | ||||||
|  |         result = "no SD Card"; | ||||||
|  |         break; | ||||||
|  |     case(SD_NOT_A_FILE): | ||||||
|  |         result = "not a file"; | ||||||
|  |         break; | ||||||
|  |     case(SD_NOT_A_DIR): | ||||||
|  |         result = "not a directory"; | ||||||
|  |         break; | ||||||
|  |     case(SD_OTHER_APP): | ||||||
|  |         result = "opened by other app"; | ||||||
|  |         break; | ||||||
|  |     case(SD_LOW_LEVEL_ERR): | ||||||
|  |         result = "low level error"; | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         result = "unknown error"; | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     return result; | ||||||
|  | } | ||||||
							
								
								
									
										433
									
								
								applications/sd-filesystem/sd-filesystem.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										433
									
								
								applications/sd-filesystem/sd-filesystem.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,433 @@ | |||||||
|  | #include "fatfs.h" | ||||||
|  | #include "filesystem-api.h" | ||||||
|  | #include "sd-filesystem.h" | ||||||
|  | #include "menu/menu.h" | ||||||
|  | #include "menu/menu_item.h" | ||||||
|  |  | ||||||
|  | FS_Api* fs_api_alloc() { | ||||||
|  |     FS_Api* fs_api = furi_alloc(sizeof(FS_Api)); | ||||||
|  |  | ||||||
|  |     // fill file api | ||||||
|  |     fs_api->file.open = fs_file_open; | ||||||
|  |     fs_api->file.close = fs_file_close; | ||||||
|  |     fs_api->file.read = fs_file_read; | ||||||
|  |     fs_api->file.write = fs_file_write; | ||||||
|  |     fs_api->file.seek = fs_file_seek; | ||||||
|  |     fs_api->file.tell = fs_file_tell; | ||||||
|  |     fs_api->file.truncate = fs_file_truncate; | ||||||
|  |     fs_api->file.size = fs_file_size; | ||||||
|  |     fs_api->file.sync = fs_file_sync; | ||||||
|  |     fs_api->file.eof = fs_file_eof; | ||||||
|  |  | ||||||
|  |     // fill dir api | ||||||
|  |     fs_api->dir.open = fs_dir_open; | ||||||
|  |     fs_api->dir.close = fs_dir_close; | ||||||
|  |     fs_api->dir.read = fs_dir_read; | ||||||
|  |     fs_api->dir.rewind = fs_dir_rewind; | ||||||
|  |  | ||||||
|  |     // fill common api | ||||||
|  |     fs_api->common.info = fs_common_info; | ||||||
|  |     fs_api->common.remove = fs_common_remove; | ||||||
|  |     fs_api->common.rename = fs_common_rename; | ||||||
|  |     fs_api->common.set_attr = fs_common_set_attr; | ||||||
|  |     fs_api->common.mkdir = fs_common_mkdir; | ||||||
|  |     fs_api->common.set_time = fs_common_set_time; | ||||||
|  |     fs_api->common.get_fs_info = fs_get_fs_info; | ||||||
|  |  | ||||||
|  |     // fill errors api | ||||||
|  |     fs_api->error.get_desc = fs_error_get_desc; | ||||||
|  |     fs_api->error.get_internal_desc = fs_error_get_internal_desc; | ||||||
|  |  | ||||||
|  |     return fs_api; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void sd_set_lines(SdApp* sd_app, uint8_t count, ...) { | ||||||
|  |     va_list argptr; | ||||||
|  |     count = min(count, SD_STATE_LINES_COUNT); | ||||||
|  |  | ||||||
|  |     for(uint8_t i = 0; i < SD_STATE_LINES_COUNT; i++) { | ||||||
|  |         sd_app->line[i] = ""; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     va_start(argptr, count); | ||||||
|  |  | ||||||
|  |     for(uint8_t i = 0; i < count; i++) { | ||||||
|  |         sd_app->line[i] = va_arg(argptr, char*); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     va_end(argptr); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void sd_icon_draw_callback(Canvas* canvas, void* context) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  |     furi_assert(context); | ||||||
|  |     SdApp* sd_app = context; | ||||||
|  |  | ||||||
|  |     switch(sd_app->info.status) { | ||||||
|  |     case SD_NO_CARD: | ||||||
|  |         break; | ||||||
|  |     case SD_OK: | ||||||
|  |         canvas_draw_icon(canvas, 0, 0, sd_app->icon.mounted); | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         canvas_draw_icon(canvas, 0, 0, sd_app->icon.fail); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void sd_app_draw_callback(Canvas* canvas, void* context) { | ||||||
|  |     furi_assert(canvas); | ||||||
|  |     furi_assert(context); | ||||||
|  |     SdApp* sd_app = context; | ||||||
|  |  | ||||||
|  |     canvas_clear(canvas); | ||||||
|  |     canvas_set_color(canvas, ColorBlack); | ||||||
|  |     canvas_set_font(canvas, FontPrimary); | ||||||
|  |  | ||||||
|  |     for(uint8_t i = 0; i < SD_STATE_LINES_COUNT; i++) { | ||||||
|  |         canvas_draw_str(canvas, 0, (i + 1) * 10, sd_app->line[i]); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void sd_app_input_callback(InputEvent* event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     SdApp* sd_app = context; | ||||||
|  |  | ||||||
|  |     osMessageQueuePut(sd_app->event_queue, event, 0, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SdApp* sd_app_alloc() { | ||||||
|  |     SdApp* sd_app = furi_alloc(sizeof(SdApp)); | ||||||
|  |  | ||||||
|  |     // init inner fs data | ||||||
|  |     if(!_fs_init(&sd_app->info)) { | ||||||
|  |         furiac_exit(NULL); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     sd_app->event_queue = osMessageQueueNew(1, sizeof(InputEvent), NULL); | ||||||
|  |  | ||||||
|  |     // init widget | ||||||
|  |     sd_app->widget = widget_alloc(); | ||||||
|  |     widget_draw_callback_set(sd_app->widget, sd_app_draw_callback, sd_app); | ||||||
|  |     widget_input_callback_set(sd_app->widget, sd_app_input_callback, sd_app); | ||||||
|  |     widget_enabled_set(sd_app->widget, false); | ||||||
|  |  | ||||||
|  |     // init lines | ||||||
|  |     sd_set_lines(sd_app, 0); | ||||||
|  |  | ||||||
|  |     // init icon widget | ||||||
|  |     sd_app->icon.widget = widget_alloc(); | ||||||
|  |     sd_app->icon.mounted = assets_icons_get(I_SDcardMounted_11x8); | ||||||
|  |     sd_app->icon.fail = assets_icons_get(I_SDcardFail_11x8); | ||||||
|  |     widget_set_width(sd_app->icon.widget, icon_get_width(sd_app->icon.mounted)); | ||||||
|  |     widget_draw_callback_set(sd_app->icon.widget, sd_icon_draw_callback, sd_app); | ||||||
|  |     widget_enabled_set(sd_app->icon.widget, false); | ||||||
|  |  | ||||||
|  |     return sd_app; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool app_sd_ask(SdApp* sd_app, Input input_true, Input input_false) { | ||||||
|  |     bool result; | ||||||
|  |  | ||||||
|  |     InputEvent event; | ||||||
|  |     while(1) { | ||||||
|  |         osStatus_t event_status = | ||||||
|  |             osMessageQueueGet(sd_app->event_queue, &event, NULL, osWaitForever); | ||||||
|  |  | ||||||
|  |         if(event_status == osOK) { | ||||||
|  |             if(event.state && event.input == input_true) { | ||||||
|  |                 result = true; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             if(event.state && event.input == InputBack) { | ||||||
|  |                 result = false; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void app_sd_info_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     SdApp* sd_app = context; | ||||||
|  |     widget_enabled_set(sd_app->widget, true); | ||||||
|  |  | ||||||
|  |     // dynamic strings | ||||||
|  |     const uint8_t str_buffer_size = 26; | ||||||
|  |     const uint8_t str_count = 6; | ||||||
|  |     char* str_buffer[str_count]; | ||||||
|  |     bool memory_error = false; | ||||||
|  |  | ||||||
|  |     // 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]; | ||||||
|  |  | ||||||
|  |     // init strings | ||||||
|  |     for(uint8_t i = 0; i < str_count; i++) { | ||||||
|  |         str_buffer[i] = malloc(str_buffer_size + 1); | ||||||
|  |         if(str_buffer[i] == NULL) { | ||||||
|  |             memory_error = true; | ||||||
|  |         } else { | ||||||
|  |             snprintf(str_buffer[i], str_buffer_size, ""); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if(memory_error) { | ||||||
|  |         sd_set_lines(sd_app, 1, "not enough memory"); | ||||||
|  |     } else { | ||||||
|  |         // 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) { | ||||||
|  |             snprintf(str_buffer[0], str_buffer_size, "%s", volume_label); | ||||||
|  |  | ||||||
|  |             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[1], str_buffer_size, "%s, S/N: %lu", fs_type, serial_num); | ||||||
|  |  | ||||||
|  |             snprintf(str_buffer[2], str_buffer_size, "Cluster: %d sectors", fs->csize); | ||||||
|  |             snprintf(str_buffer[3], str_buffer_size, "Sector: %d bytes", sector_size); | ||||||
|  |             snprintf( | ||||||
|  |                 str_buffer[4], str_buffer_size, "%lu KB total", total_sectors / 1024 * sector_size); | ||||||
|  |             snprintf( | ||||||
|  |                 str_buffer[5], str_buffer_size, "%lu KB free", free_sectors / 1024 * sector_size); | ||||||
|  |         } else { | ||||||
|  |             snprintf(str_buffer[0], str_buffer_size, "SD status error:"); | ||||||
|  |             snprintf( | ||||||
|  |                 str_buffer[1], | ||||||
|  |                 str_buffer_size, | ||||||
|  |                 "%s", | ||||||
|  |                 fs_error_get_internal_desc(_fs_status(&sd_app->info))); | ||||||
|  |             snprintf(str_buffer[2], str_buffer_size, "Label error:"); | ||||||
|  |             snprintf( | ||||||
|  |                 str_buffer[3], str_buffer_size, "%s", fs_error_get_internal_desc(get_label_result)); | ||||||
|  |             snprintf(str_buffer[4], str_buffer_size, "Get free error:"); | ||||||
|  |             snprintf( | ||||||
|  |                 str_buffer[5], str_buffer_size, "%s", fs_error_get_internal_desc(get_free_result)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // dynamic strings to screen | ||||||
|  |         sd_set_lines( | ||||||
|  |             sd_app, | ||||||
|  |             6, | ||||||
|  |             str_buffer[0], | ||||||
|  |             str_buffer[1], | ||||||
|  |             str_buffer[2], | ||||||
|  |             str_buffer[3], | ||||||
|  |             str_buffer[4], | ||||||
|  |             str_buffer[5]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     app_sd_ask(sd_app, InputBack, InputBack); | ||||||
|  |  | ||||||
|  |     sd_set_lines(sd_app, 0); | ||||||
|  |     widget_enabled_set(sd_app->widget, false); | ||||||
|  |  | ||||||
|  |     for(uint8_t i = 0; i < str_count; i++) { | ||||||
|  |         free(str_buffer[i]); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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"); | ||||||
|  |     widget_enabled_set(sd_app->widget, true); | ||||||
|  |  | ||||||
|  |     // wait for input | ||||||
|  |     if(!app_sd_ask(sd_app, InputUp, InputBack)) { | ||||||
|  |         widget_enabled_set(sd_app->widget, false); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // show warning | ||||||
|  |     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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if(sd_app->info.status != SD_OK) { | ||||||
|  |         sd_set_lines( | ||||||
|  |             sd_app, 2, "SD card format error", fs_error_get_internal_desc(sd_app->info.status)); | ||||||
|  |     } else { | ||||||
|  |         sd_set_lines(sd_app, 1, "SD card formatted"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     _fs_unlock(&sd_app->info); | ||||||
|  |  | ||||||
|  |     // wait for BACK | ||||||
|  |     app_sd_ask(sd_app, InputBack, InputBack); | ||||||
|  |  | ||||||
|  |     widget_enabled_set(sd_app->widget, false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void app_sd_unmount_card(SdApp* sd_app) { | ||||||
|  |     _fs_lock(&sd_app->info); | ||||||
|  |  | ||||||
|  |     // set status | ||||||
|  |     sd_app->info.status = SD_NO_CARD; | ||||||
|  |     widget_enabled_set(sd_app->icon.widget, false); | ||||||
|  |  | ||||||
|  |     // close files | ||||||
|  |     for(uint8_t index = 0; index < SD_FS_MAX_FILES; index++) { | ||||||
|  |         FileData* filedata = &sd_app->info.files[index]; | ||||||
|  |  | ||||||
|  |         if(filedata->thread_id != NULL) { | ||||||
|  |             if(filedata->is_dir) { | ||||||
|  |                 f_closedir(&filedata->data.dir); | ||||||
|  |             } else { | ||||||
|  |                 f_close(&filedata->data.file); | ||||||
|  |             } | ||||||
|  |             filedata->thread_id = NULL; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // unmount volume | ||||||
|  |     f_mount(0, sd_app->info.path, 0); | ||||||
|  |  | ||||||
|  |     _fs_unlock(&sd_app->info); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void app_sd_eject_callback(void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  |     SdApp* sd_app = context; | ||||||
|  |  | ||||||
|  |     sd_set_lines(sd_app, 1, "ejecting SD card"); | ||||||
|  |     widget_enabled_set(sd_app->widget, true); | ||||||
|  |  | ||||||
|  |     app_sd_unmount_card(sd_app); | ||||||
|  |  | ||||||
|  |     sd_set_lines(sd_app, 1, "SD card can be pulled out"); | ||||||
|  |  | ||||||
|  |     // wait for BACK | ||||||
|  |     app_sd_ask(sd_app, InputBack, InputBack); | ||||||
|  |  | ||||||
|  |     widget_enabled_set(sd_app->widget, false); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void sd_filesystem(void* p) { | ||||||
|  |     SdApp* sd_app = sd_app_alloc(); | ||||||
|  |     FS_Api* fs_api = fs_api_alloc(); | ||||||
|  |  | ||||||
|  |     Gui* gui = furi_open("gui"); | ||||||
|  |     gui_add_widget(gui, sd_app->widget, GuiLayerFullscreen); | ||||||
|  |     gui_add_widget(gui, sd_app->icon.widget, GuiLayerStatusBarLeft); | ||||||
|  |  | ||||||
|  |     // add api record | ||||||
|  |     if(!furi_create("sdcard", fs_api)) { | ||||||
|  |         furiac_exit(NULL); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // init menu | ||||||
|  |     // TODO menu icon | ||||||
|  |     MenuItem* menu_item; | ||||||
|  |     menu_item = menu_item_alloc_menu("SD Card", assets_icons_get(I_SDcardMounted_11x8)); | ||||||
|  |  | ||||||
|  |     menu_item_subitem_add( | ||||||
|  |         menu_item, menu_item_alloc_function("Info", NULL, app_sd_info_callback, sd_app)); | ||||||
|  |     menu_item_subitem_add( | ||||||
|  |         menu_item, menu_item_alloc_function("Format", NULL, app_sd_format_callback, sd_app)); | ||||||
|  |     menu_item_subitem_add( | ||||||
|  |         menu_item, menu_item_alloc_function("Eject", NULL, app_sd_eject_callback, sd_app)); | ||||||
|  |  | ||||||
|  |     // add item to menu | ||||||
|  |     ValueMutex* menu_vm = furi_open("menu"); | ||||||
|  |     furi_check(menu_vm); | ||||||
|  |     with_value_mutex( | ||||||
|  |         menu_vm, (Menu * menu) { menu_item_add(menu, menu_item); }); | ||||||
|  |  | ||||||
|  |     furiac_ready(); | ||||||
|  |  | ||||||
|  |     printf("[sd_filesystem] start\n"); | ||||||
|  |  | ||||||
|  |     // sd card cycle | ||||||
|  |     bool sd_was_present = true; | ||||||
|  |  | ||||||
|  |     while(true) { | ||||||
|  |         if(sd_was_present) { | ||||||
|  |             if(hal_gpio_read_sd_detect()) { | ||||||
|  |                 printf("[sd_filesystem] card detected\n"); | ||||||
|  |  | ||||||
|  |                 uint8_t bsp_result = BSP_SD_Init(); | ||||||
|  |  | ||||||
|  |                 if(bsp_result) { | ||||||
|  |                     sd_app->info.status = SD_LOW_LEVEL_ERR; | ||||||
|  |                     printf("[sd_filesystem] bsp error: %x\n", bsp_result); | ||||||
|  |                 } else { | ||||||
|  |                     printf("[sd_filesystem] bsp ok\n"); | ||||||
|  |                     sd_app->info.status = f_mount(&sd_app->info.fat_fs, sd_app->info.path, 1); | ||||||
|  |  | ||||||
|  |                     if(sd_app->info.status != SD_OK) { | ||||||
|  |                         printf("[sd_filesystem] mount error: %d\n", sd_app->info.status); | ||||||
|  |                     } else { | ||||||
|  |                         printf("[sd_filesystem] mount ok\n"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 widget_enabled_set(sd_app->icon.widget, true); | ||||||
|  |                 sd_was_present = false; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             if(!hal_gpio_read_sd_detect()) { | ||||||
|  |                 printf("[sd_filesystem] card removed\n"); | ||||||
|  |  | ||||||
|  |                 widget_enabled_set(sd_app->icon.widget, false); | ||||||
|  |                 app_sd_unmount_card(sd_app); | ||||||
|  |                 sd_was_present = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         delay(1000); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										122
									
								
								applications/sd-filesystem/sd-filesystem.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								applications/sd-filesystem/sd-filesystem.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "flipper.h" | ||||||
|  | #include "flipper_v2.h" | ||||||
|  |  | ||||||
|  | #define SD_FS_MAX_FILES _FS_LOCK | ||||||
|  | #define SD_STATE_LINES_COUNT 6 | ||||||
|  |  | ||||||
|  | #ifndef min | ||||||
|  | #define min(a, b) (((a) < (b)) ? (a) : (b)) | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | /* api data */ | ||||||
|  | typedef FIL SDFile; | ||||||
|  | typedef DIR SDDir; | ||||||
|  | typedef FILINFO SDFileInfo; | ||||||
|  |  | ||||||
|  | /* storage for file/directory objects*/ | ||||||
|  | typedef union { | ||||||
|  |     SDFile file; | ||||||
|  |     SDDir dir; | ||||||
|  | } SDFileDirStorage; | ||||||
|  |  | ||||||
|  | typedef enum { | ||||||
|  |     SD_OK = FR_OK, | ||||||
|  |     SD_DISK_ERR = FR_DISK_ERR, | ||||||
|  |     SD_INT_ERR = FR_INT_ERR, | ||||||
|  |     SD_NO_FILE = FR_NO_FILE, | ||||||
|  |     SD_NO_PATH = FR_NO_PATH, | ||||||
|  |     SD_INVALID_NAME = FR_INVALID_NAME, | ||||||
|  |     SD_DENIED = FR_DENIED, | ||||||
|  |     SD_EXIST = FR_EXIST, | ||||||
|  |     SD_INVALID_OBJECT = FR_INVALID_OBJECT, | ||||||
|  |     SD_WRITE_PROTECTED = FR_WRITE_PROTECTED, | ||||||
|  |     SD_INVALID_DRIVE = FR_INVALID_DRIVE, | ||||||
|  |     SD_NOT_ENABLED = FR_NOT_ENABLED, | ||||||
|  |     SD_NO_FILESYSTEM = FR_NO_FILESYSTEM, | ||||||
|  |     SD_MKFS_ABORTED = FR_MKFS_ABORTED, | ||||||
|  |     SD_TIMEOUT = FR_TIMEOUT, | ||||||
|  |     SD_LOCKED = FR_LOCKED, | ||||||
|  |     SD_NOT_ENOUGH_CORE = FR_NOT_ENOUGH_CORE, | ||||||
|  |     SD_TOO_MANY_OPEN_FILES = FR_TOO_MANY_OPEN_FILES, | ||||||
|  |     SD_INVALID_PARAMETER = FR_INVALID_PARAMETER, | ||||||
|  |     SD_NO_CARD, | ||||||
|  |     SD_NOT_A_FILE, | ||||||
|  |     SD_NOT_A_DIR, | ||||||
|  |     SD_OTHER_APP, | ||||||
|  |     SD_LOW_LEVEL_ERR, | ||||||
|  | } SDError; | ||||||
|  |  | ||||||
|  | typedef enum { | ||||||
|  |     FDF_DIR, | ||||||
|  |     FDF_FILE, | ||||||
|  |     FDF_ANY, | ||||||
|  | } FiledataFilter; | ||||||
|  |  | ||||||
|  | typedef struct { | ||||||
|  |     osThreadId_t thread_id; | ||||||
|  |     bool is_dir; | ||||||
|  |     SDFileDirStorage data; | ||||||
|  | } FileData; | ||||||
|  |  | ||||||
|  | /* application data */ | ||||||
|  | typedef struct { | ||||||
|  |     Widget* widget; | ||||||
|  |     Icon* mounted; | ||||||
|  |     Icon* fail; | ||||||
|  | } SdFsIcon; | ||||||
|  |  | ||||||
|  | typedef struct { | ||||||
|  |     osMutexId_t mutex; | ||||||
|  |     FileData files[SD_FS_MAX_FILES]; | ||||||
|  |     SDError status; | ||||||
|  |     char* path; | ||||||
|  |     FATFS fat_fs; | ||||||
|  | } SdFsInfo; | ||||||
|  |  | ||||||
|  | typedef struct { | ||||||
|  |     SdFsInfo info; | ||||||
|  |     SdFsIcon icon; | ||||||
|  |  | ||||||
|  |     Widget* widget; | ||||||
|  |     const char* line[SD_STATE_LINES_COUNT]; | ||||||
|  |     osMessageQueueId_t event_queue; | ||||||
|  | } SdApp; | ||||||
|  |  | ||||||
|  | /* core api fns */ | ||||||
|  | bool _fs_init(SdFsInfo* _fs_info); | ||||||
|  | bool _fs_lock(SdFsInfo* fs_info); | ||||||
|  | bool _fs_unlock(SdFsInfo* fs_info); | ||||||
|  | SDError _fs_status(SdFsInfo* fs_info); | ||||||
|  |  | ||||||
|  | /* sd api fns */ | ||||||
|  | bool fs_file_open(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode); | ||||||
|  | bool fs_file_close(File* file); | ||||||
|  | uint16_t fs_file_read(File* file, void* buff, uint16_t bytes_to_read); | ||||||
|  | uint16_t fs_file_write(File* file, void* buff, uint16_t bytes_to_write); | ||||||
|  | bool fs_file_seek(File* file, uint32_t offset, bool from_start); | ||||||
|  | uint64_t fs_file_tell(File* file); | ||||||
|  | bool fs_file_truncate(File* file); | ||||||
|  | uint64_t fs_file_size(File* file); | ||||||
|  | bool fs_file_sync(File* file); | ||||||
|  | bool fs_file_eof(File* file); | ||||||
|  |  | ||||||
|  | /* dir api */ | ||||||
|  | bool fs_dir_open(File* file, const char* path); | ||||||
|  | bool fs_dir_close(File* file); | ||||||
|  | bool fs_dir_read(File* file, FileInfo* fileinfo, char* name, uint16_t name_length); | ||||||
|  | bool fs_dir_rewind(File* file); | ||||||
|  |  | ||||||
|  | /* common api */ | ||||||
|  | FS_Error | ||||||
|  | fs_common_info(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length); | ||||||
|  | FS_Error fs_common_remove(const char* path); | ||||||
|  | FS_Error fs_common_rename(const char* old_path, const char* new_path); | ||||||
|  | FS_Error fs_common_set_attr(const char* path, uint8_t attr, uint8_t mask); | ||||||
|  | FS_Error fs_common_mkdir(const char* path); | ||||||
|  | FS_Error fs_common_set_time(const char* path, FileDateUnion date, FileTimeUnion time); | ||||||
|  | FS_Error fs_get_fs_info(uint64_t* total_space, uint64_t* free_space); | ||||||
|  |  | ||||||
|  | /* errors api */ | ||||||
|  | const char* fs_error_get_desc(FS_Error error_id); | ||||||
|  | const char* fs_error_get_internal_desc(uint32_t internal_error_id); | ||||||
| @@ -47,7 +47,7 @@ | |||||||
| /   2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1. | /   2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1. | ||||||
| /   3: f_lseek() function is removed in addition to 2. */ | /   3: f_lseek() function is removed in addition to 2. */ | ||||||
|  |  | ||||||
| #define _USE_STRFUNC         2      /* 0:Disable or 1-2:Enable */ | #define _USE_STRFUNC         0      /* 0:Disable or 1-2:Enable */ | ||||||
| /* This option switches string functions, f_gets(), f_putc(), f_puts() and | /* This option switches string functions, f_gets(), f_putc(), f_puts() and | ||||||
| /  f_printf(). | /  f_printf(). | ||||||
| / | / | ||||||
| @@ -68,7 +68,7 @@ | |||||||
| #define	_USE_EXPAND		0 | #define	_USE_EXPAND		0 | ||||||
| /* This option switches f_expand function. (0:Disable or 1:Enable) */ | /* This option switches f_expand function. (0:Disable or 1:Enable) */ | ||||||
|  |  | ||||||
| #define _USE_CHMOD		0 | #define _USE_CHMOD		1 | ||||||
| /* This option switches attribute manipulation functions, f_chmod() and f_utime(). | /* This option switches attribute manipulation functions, f_chmod() and f_utime(). | ||||||
| /  (0:Disable or 1:Enable) Also _FS_READONLY needs to be 0 to enable this option. */ | /  (0:Disable or 1:Enable) Also _FS_READONLY needs to be 0 to enable this option. */ | ||||||
|  |  | ||||||
| @@ -177,7 +177,7 @@ | |||||||
| /  arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk() | /  arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk() | ||||||
| /  funciton will be available. */ | /  funciton will be available. */ | ||||||
| #define _MIN_SS    512  /* 512, 1024, 2048 or 4096 */ | #define _MIN_SS    512  /* 512, 1024, 2048 or 4096 */ | ||||||
| #define _MAX_SS    4096  /* 512, 1024, 2048 or 4096 */ | #define _MAX_SS    512  /* 512, 1024, 2048 or 4096 */ | ||||||
| /* These options configure the range of sector size to be supported. (512, 1024, | /* These options configure the range of sector size to be supported. (512, 1024, | ||||||
| /  2048 or 4096) Always set both 512 for most systems, all type of memory cards and | /  2048 or 4096) Always set both 512 for most systems, all type of memory cards and | ||||||
| /  harddisk. But a larger value may be required for on-board flash memory and some | /  harddisk. But a larger value may be required for on-board flash memory and some | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| #include "api-hal-gpio.h" | #include "api-hal-gpio.h" | ||||||
| #include "api-hal-resources.h" | #include "api-hal-resources.h" | ||||||
|  | #include "spi.h" | ||||||
|  |  | ||||||
| // init GPIO | // init GPIO | ||||||
| void hal_gpio_init( | void hal_gpio_init( | ||||||
| @@ -24,16 +25,23 @@ bool hal_gpio_read_sd_detect(void) { | |||||||
|     // TODO open record |     // TODO open record | ||||||
|     const GpioPin* sd_cs_record = &sd_cs_gpio; |     const GpioPin* sd_cs_record = &sd_cs_gpio; | ||||||
|  |  | ||||||
|  |     // TODO: SPI manager | ||||||
|  |     api_hal_spi_lock(&SPI_SD_HANDLE); | ||||||
|  |  | ||||||
|     // configure pin as input |     // configure pin as input | ||||||
|     gpio_init_ex(sd_cs_record, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh); |     gpio_init_ex(sd_cs_record, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh); | ||||||
|     delay(50); |     delay(1); | ||||||
|  |  | ||||||
|     // if gpio_read == 0 return true else return false |     // if gpio_read == 0 return true else return false | ||||||
|     result = !gpio_read(sd_cs_record); |     result = !gpio_read(sd_cs_record); | ||||||
|  |  | ||||||
|     // configure pin back |     // configure pin back | ||||||
|     gpio_init_ex(sd_cs_record, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); |     gpio_init_ex(sd_cs_record, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); | ||||||
|     delay(50); |     gpio_write(sd_cs_record, 1); | ||||||
|  |     delay(1); | ||||||
|  |  | ||||||
|  |     // TODO: SPI manager | ||||||
|  |     api_hal_spi_unlock(&SPI_SD_HANDLE); | ||||||
|  |  | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										330
									
								
								lib/common-api/filesystem-api.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										330
									
								
								lib/common-api/filesystem-api.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,330 @@ | |||||||
|  | #pragma once | ||||||
|  | #include "flipper.h" | ||||||
|  | #include "flipper_v2.h" | ||||||
|  |  | ||||||
|  | #ifdef __cplusplus | ||||||
|  | extern "C" { | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  @brief Access mode flags | ||||||
|  |  */ | ||||||
|  | typedef enum { | ||||||
|  |     FSAM_READ = (1 << 0), /**< Read access */ | ||||||
|  |     FSAM_WRITE = (1 << 1), /**< Write access */ | ||||||
|  | } FS_AccessMode; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  @brief Open mode flags | ||||||
|  |  */ | ||||||
|  | typedef enum { | ||||||
|  |     FSOM_OPEN_EXISTING = 1, /**< Open file, fail if file doesn't exist */ | ||||||
|  |     FSOM_OPEN_ALWAYS = 2, /**< Open file. Create new file if not exist */ | ||||||
|  |     FSOM_OPEN_APPEND = 4, /**< Open file. Create new file if not exist. Set R/W pointer to EOF */ | ||||||
|  |     FSOM_CREATE_NEW = 8, /**< Creates a new file. Fails if the file is exist */ | ||||||
|  |     FSOM_CREATE_ALWAYS = 16, /**< Creates a new file. If file exist, truncate to zero size */ | ||||||
|  | } FS_OpenMode; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  @brief API errors enumeration | ||||||
|  |  */ | ||||||
|  | typedef enum { | ||||||
|  |     FSE_OK, /**< No error */ | ||||||
|  |     FSE_NOT_READY, /**< FS not ready */ | ||||||
|  |     FSE_EXIST, /**< File/Dir alrady exist */ | ||||||
|  |     FSE_NOT_EXIST, /**< File/Dir does not exist */ | ||||||
|  |     FSE_INVALID_PARAMETER, /**< Invalid API parameter */ | ||||||
|  |     FSE_DENIED, /**< Access denied */ | ||||||
|  |     FSE_INVALID_NAME, /**< Invalid name/path */ | ||||||
|  |     FSE_INTERNAL, /**< Internal error */ | ||||||
|  |     FSE_NOT_IMPLEMENTED, /**< Functon not implemented */ | ||||||
|  | } FS_Error; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  *  @brief FileInfo flags | ||||||
|  |  */ | ||||||
|  | typedef enum { | ||||||
|  |     FSF_READ_ONLY = (1 << 0), /**< Readonly */ | ||||||
|  |     FSF_HIDDEN = (1 << 1), /**< Hidden */ | ||||||
|  |     FSF_SYSTEM = (1 << 2), /**< System */ | ||||||
|  |     FSF_DIRECTORY = (1 << 3), /**< Directory */ | ||||||
|  |     FSF_ARCHIVE = (1 << 4), /**< Archive */ | ||||||
|  | } FS_Flags; | ||||||
|  |  | ||||||
|  | /**  | ||||||
|  |  *  @brief Structure that hold file index and returned api errors  | ||||||
|  |  */ | ||||||
|  | typedef struct { | ||||||
|  |     uint32_t file_id; /**< File ID for internal references */ | ||||||
|  |     FS_Error error_id; /**< Standart API error from FS_Error enum */ | ||||||
|  |     uint32_t internal_error_id; /**< Internal API error value */ | ||||||
|  | } File; | ||||||
|  |  | ||||||
|  | // TODO: solve year 2107 problem | ||||||
|  | /**  | ||||||
|  |  *  @brief Structure that hold packed date values  | ||||||
|  |  */ | ||||||
|  | typedef struct __attribute__((packed)) { | ||||||
|  |     uint16_t month_day : 5; /**< month day */ | ||||||
|  |     uint16_t month : 4; /**< month index */ | ||||||
|  |     uint16_t year : 7; /**< year, year + 1980 to get actual value */ | ||||||
|  | } FileDate; | ||||||
|  |  | ||||||
|  | /**  | ||||||
|  |  *  @brief Structure that hold packed time values  | ||||||
|  |  */ | ||||||
|  | typedef struct __attribute__((packed)) { | ||||||
|  |     uint16_t second : 5; /**< second, second * 2 to get actual value  */ | ||||||
|  |     uint16_t minute : 6; /**< minute */ | ||||||
|  |     uint16_t hour : 5; /**< hour */ | ||||||
|  | } FileTime; | ||||||
|  |  | ||||||
|  | /**  | ||||||
|  |  *  @brief Union of simple date and real value  | ||||||
|  |  */ | ||||||
|  | typedef union { | ||||||
|  |     FileDate simple; /**< simple access to date */ | ||||||
|  |     uint16_t value; /**< real date value */ | ||||||
|  | } FileDateUnion; | ||||||
|  |  | ||||||
|  | /**  | ||||||
|  |  *  @brief Union of simple time and real value  | ||||||
|  |  */ | ||||||
|  | typedef union { | ||||||
|  |     FileTime simple; /**< simple access to time */ | ||||||
|  |     uint16_t value; /**< real time value */ | ||||||
|  | } FileTimeUnion; | ||||||
|  |  | ||||||
|  | /**  | ||||||
|  |  *  @brief Structure that hold file info | ||||||
|  |  */ | ||||||
|  | typedef struct { | ||||||
|  |     uint8_t flags; /**< flags from FS_Flags enum */ | ||||||
|  |     uint64_t size; /**< file size */ | ||||||
|  |     FileDateUnion date; /**< file date */ | ||||||
|  |     FileTimeUnion time; /**< file time */ | ||||||
|  | } FileInfo; | ||||||
|  |  | ||||||
|  | /** @struct FS_File_Api | ||||||
|  |  *  @brief File api structure | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::open | ||||||
|  |  *      @brief Open file | ||||||
|  |  *      @param file pointer to file object, filled by api | ||||||
|  |  *      @param path path to file  | ||||||
|  |  *      @param access_mode access mode from FS_AccessMode  | ||||||
|  |  *      @param open_mode open mode from FS_OpenMode  | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::close  | ||||||
|  |  *      @brief Close file | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::read | ||||||
|  |  *      @brief Read bytes from file to buffer | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @param buff pointer to buffer for reading | ||||||
|  |  *      @param bytes_to_read how many bytes to read, must be smaller or equal to buffer size  | ||||||
|  |  *      @return how many bytes actually has been readed | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::write | ||||||
|  |  *      @brief Write bytes from buffer to file | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @param buff pointer to buffer for writing | ||||||
|  |  *      @param bytes_to_read how many bytes to write, must be smaller or equal to buffer size  | ||||||
|  |  *      @return how many bytes actually has been writed | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::seek | ||||||
|  |  *      @brief Move r/w pointer  | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @param offset offset to move r/w pointer | ||||||
|  |  *      @param from_start set offset from start, or from current position | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::tell | ||||||
|  |  *      @brief Get r/w pointer position | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return current r/w pointer position | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::truncate | ||||||
|  |  *      @brief Truncate file size to current r/w pointer position | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::size | ||||||
|  |  *      @brief Fet file size | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return file size | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::sync | ||||||
|  |  *      @brief Write file cache to storage | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_File_Api::eof | ||||||
|  |  *      @brief Checks that the r/w pointer is at the end of the file | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return end of file flag | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief File api structure | ||||||
|  |  */ | ||||||
|  | typedef struct { | ||||||
|  |     bool (*open)(File* file, const char* path, FS_AccessMode access_mode, FS_OpenMode open_mode); | ||||||
|  |     bool (*close)(File* file); | ||||||
|  |     uint16_t (*read)(File* file, void* buff, uint16_t bytes_to_read); | ||||||
|  |     uint16_t (*write)(File* file, void* buff, uint16_t bytes_to_write); | ||||||
|  |     bool (*seek)(File* file, uint32_t offset, bool from_start); | ||||||
|  |     uint64_t (*tell)(File* file); | ||||||
|  |     bool (*truncate)(File* file); | ||||||
|  |     uint64_t (*size)(File* file); | ||||||
|  |     bool (*sync)(File* file); | ||||||
|  |     bool (*eof)(File* file); | ||||||
|  | } FS_File_Api; | ||||||
|  |  | ||||||
|  | /** @struct FS_Dir_Api | ||||||
|  |  *  @brief Dir api structure | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Dir_Api::open | ||||||
|  |  *      @brief Open directory to get objects from | ||||||
|  |  *      @param file pointer to file object, filled by api | ||||||
|  |  *      @param path path to directory  | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Dir_Api::close  | ||||||
|  |  *      @brief Close directory | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return success flag | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Dir_Api::read | ||||||
|  |  *      @brief Read next object info in directory | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @param fileinfo pointer to readed FileInfo, can be NULL | ||||||
|  |  *      @param name pointer to name buffer, can be NULL | ||||||
|  |  *      @param name_length name buffer length | ||||||
|  |  *      @return success flag (if next object not exist also returns false and set error_id to FSE_NOT_EXIST) | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Dir_Api::rewind | ||||||
|  |  *      @brief Rewind to first object info in directory | ||||||
|  |  *      @param file pointer to file object | ||||||
|  |  *      @return success flag | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Dir api structure | ||||||
|  |  */ | ||||||
|  | typedef struct { | ||||||
|  |     bool (*open)(File* file, const char* path); | ||||||
|  |     bool (*close)(File* file); | ||||||
|  |     bool (*read)(File* file, FileInfo* fileinfo, char* name, uint16_t name_length); | ||||||
|  |     bool (*rewind)(File* file); | ||||||
|  | } FS_Dir_Api; | ||||||
|  |  | ||||||
|  | /** @struct FS_Common_Api | ||||||
|  |  *  @brief Common api structure | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Common_Api::info | ||||||
|  |  *      @brief Open directory to get objects from | ||||||
|  |  *      @param path path to file/directory | ||||||
|  |  *      @param fileinfo pointer to readed FileInfo, can be NULL | ||||||
|  |  *      @param name pointer to name buffer, can be NULL | ||||||
|  |  *      @param name_length name buffer length | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Common_Api::remove | ||||||
|  |  *      @brief Remove file/directory from storage,  | ||||||
|  |  *          directory must be empty, | ||||||
|  |  *          file/directory must not be opened, | ||||||
|  |  *          file/directory must not have FSF_READ_ONLY flag | ||||||
|  |  *      @param path path to file/directory | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Common_Api::rename | ||||||
|  |  *      @brief Rename file/directory, | ||||||
|  |  *          file/directory must not be opened | ||||||
|  |  *      @param path path to file/directory | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Common_Api::set_attr | ||||||
|  |  *      @brief Set attributes of file/directory,  | ||||||
|  |  *          for example: | ||||||
|  |  *          @code | ||||||
|  |  *          set "read only" flag and remove "hidden" flag | ||||||
|  |  *          set_attr("file.txt", FSF_READ_ONLY, FSF_READ_ONLY | FSF_HIDDEN); | ||||||
|  |  *          @endcode | ||||||
|  |  *      @param path path to file/directory | ||||||
|  |  *      @param attr attribute values consist of FS_Flags | ||||||
|  |  *      @param mask attribute mask consist of FS_Flags | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Common_Api::mkdir | ||||||
|  |  *      @brief Create new directory | ||||||
|  |  *      @param path path to new directory | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Common_Api::set_time | ||||||
|  |  *      @brief Set file/directory modification time | ||||||
|  |  *      @param path path to file/directory | ||||||
|  |  *      @param date modification date  | ||||||
|  |  *      @param time modification time | ||||||
|  |  *      @see FileDateUnion | ||||||
|  |  *      @see FileTimeUnion | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Common_Api::get_fs_info | ||||||
|  |  *      @brief Get total and free space storage values | ||||||
|  |  *      @param total_space pointer to total space value | ||||||
|  |  *      @param free_space pointer to free space value | ||||||
|  |  *      @return FS_Error error info | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Common api structure | ||||||
|  |  */ | ||||||
|  | typedef struct { | ||||||
|  |     FS_Error (*info)(const char* path, FileInfo* fileinfo, char* name, const uint16_t name_length); | ||||||
|  |     FS_Error (*remove)(const char* path); | ||||||
|  |     FS_Error (*rename)(const char* old_path, const char* new_path); | ||||||
|  |     FS_Error (*set_attr)(const char* path, uint8_t attr, uint8_t mask); | ||||||
|  |     FS_Error (*mkdir)(const char* path); | ||||||
|  |     FS_Error (*set_time)(const char* path, FileDateUnion date, FileTimeUnion time); | ||||||
|  |     FS_Error (*get_fs_info)(uint64_t* total_space, uint64_t* free_space); | ||||||
|  | } FS_Common_Api; | ||||||
|  |  | ||||||
|  | /** @struct FS_Error_Api | ||||||
|  |  *  @brief Errors api structure | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Error_Api::get_desc | ||||||
|  |  *      @brief Get error description text | ||||||
|  |  *      @param error_id FS_Error error id (for fire/dir functions result can be obtained from File.error_id) | ||||||
|  |  *      @return pointer to description text | ||||||
|  |  *  | ||||||
|  |  *  @var FS_Error_Api::get_internal_desc | ||||||
|  |  *      @brief Get internal error description text | ||||||
|  |  *      @param internal_error_id error id (for fire/dir functions result can be obtained from File.internal_error_id) | ||||||
|  |  *      @return pointer to description text | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Errors api structure | ||||||
|  |  */ | ||||||
|  | typedef struct { | ||||||
|  |     const char* (*get_desc)(FS_Error error_id); | ||||||
|  |     const char* (*get_internal_desc)(uint32_t internal_error_id); | ||||||
|  | } FS_Error_Api; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Full filesystem api structure | ||||||
|  |  */ | ||||||
|  | typedef struct { | ||||||
|  |     FS_File_Api file; | ||||||
|  |     FS_Dir_Api dir; | ||||||
|  |     FS_Common_Api common; | ||||||
|  |     FS_Error_Api error; | ||||||
|  | } FS_Api; | ||||||
|  |  | ||||||
|  | #ifdef __cplusplus | ||||||
|  | } | ||||||
|  | #endif | ||||||
| @@ -11,27 +11,27 @@ | |||||||
| #include <tchar.h> | #include <tchar.h> | ||||||
| typedef unsigned __int64 QWORD; | typedef unsigned __int64 QWORD; | ||||||
|  |  | ||||||
|  |  | ||||||
| #else /* Embedded platform */ | #else /* Embedded platform */ | ||||||
|  | #include <stdint.h> | ||||||
|  |  | ||||||
| /* These types MUST be 16-bit or 32-bit */ | /* These types MUST be 16-bit or 32-bit */ | ||||||
| typedef int				INT; | typedef int16_t INT; | ||||||
| typedef unsigned int	UINT; | typedef uint16_t UINT; | ||||||
|  |  | ||||||
| /* This type MUST be 8-bit */ | /* This type MUST be 8-bit */ | ||||||
| typedef unsigned char	BYTE; | typedef uint8_t BYTE; | ||||||
|  |  | ||||||
| /* These types MUST be 16-bit */ | /* These types MUST be 16-bit */ | ||||||
| typedef short			SHORT; | typedef int16_t SHORT; | ||||||
| typedef unsigned short	WORD; | typedef uint16_t WORD; | ||||||
| typedef unsigned short	WCHAR; | typedef uint16_t WCHAR; | ||||||
|  |  | ||||||
| /* These types MUST be 32-bit */ | /* These types MUST be 32-bit */ | ||||||
| typedef long			LONG; | typedef int32_t LONG; | ||||||
| typedef unsigned long	DWORD; | typedef uint32_t DWORD; | ||||||
|  |  | ||||||
| /* This type MUST be 64-bit (Remove this for ANSI C (C89) compatibility) */ | /* This type MUST be 64-bit (Remove this for ANSI C (C89) compatibility) */ | ||||||
| typedef unsigned long long QWORD; | typedef uint64_t QWORD; | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   | |||||||
| @@ -70,6 +70,9 @@ CFLAGS			+= -I$(CYFRAL_DIR) | |||||||
| CPP_SOURCES		+= $(wildcard $(CYFRAL_DIR)/*.cpp) | CPP_SOURCES		+= $(wildcard $(CYFRAL_DIR)/*.cpp) | ||||||
| endif | endif | ||||||
|  |  | ||||||
|  | # common apps api | ||||||
|  | CFLAGS			+= -I$(LIB_DIR)/common-api | ||||||
|  |  | ||||||
| # drivers | # drivers | ||||||
| ifneq ($(TARGET), local) | ifneq ($(TARGET), local) | ||||||
| ifneq ($(TARGET), f2) | ifneq ($(TARGET), f2) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user