From f94633863cc7811c5270f7e2fef00331cec5fc26 Mon Sep 17 00:00:00 2001 From: DrZlo13 Date: Tue, 12 Jan 2021 00:52:35 +1000 Subject: [PATCH] [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 --- .github/CODEOWNERS | 11 +- applications/applications.h | 12 +- applications/applications.mk | 8 + applications/sd-card-test/sd-card-test.cpp | 288 ++----- .../sd-filesystem/sd-filesystem-api.c | 738 ++++++++++++++++++ applications/sd-filesystem/sd-filesystem.c | 433 ++++++++++ applications/sd-filesystem/sd-filesystem.h | 122 +++ firmware/targets/f4/Src/fatfs/ffconf.h | 6 +- firmware/targets/f4/api-hal/api-hal-gpio.c | 14 +- lib/common-api/filesystem-api.h | 330 ++++++++ lib/fatfs/integer.h | 24 +- lib/lib.mk | 3 + 12 files changed, 1729 insertions(+), 260 deletions(-) create mode 100644 applications/sd-filesystem/sd-filesystem-api.c create mode 100644 applications/sd-filesystem/sd-filesystem.c create mode 100644 applications/sd-filesystem/sd-filesystem.h create mode 100644 lib/common-api/filesystem-api.h diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 63eb8893..15499106 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -43,6 +43,8 @@ applications/gui/** @skotopes # iButton applications/ibutton/** @DrZlo13 +lib/cyfral/** @DrZlo13 +lib/onewire/** @DrZlo13 # IR @@ -61,9 +63,12 @@ applications/menu/** @skotopes applications/nfc/** @skotopes lib/ST25RFAL002/** @skotopes -# SD Card +# SD Card and filesystem applications/sd-card-test/** @DrZlo13 +applications/sd-filesystem/** @DrZlo13 +lib/common-api/filesystem-api.h @DrZlo13 +lib/fatfs/** @DrZlo13 # Power control app @@ -74,3 +79,7 @@ applications/power/** @skotopes applications/music-player/** @DrZlo13 applications/floopper-bloopper/** @glitchcore applications/gpio-tester/** @glitchcore + +lib/app-template/** @DrZlo13 +lib/qrcode/** @DrZlo13 +lib/callback-connector/** @DrZlo13 \ No newline at end of file diff --git a/applications/applications.h b/applications/applications.h index 8395d52b..1b80891f 100644 --- a/applications/applications.h +++ b/applications/applications.h @@ -41,6 +41,7 @@ void cli_task(void* p); void music_player(void* p); void sdnfc(void* p); void floopper_bloopper(void* p); +void sd_filesystem(void* p); const FlipperStartupApp FLIPPER_STARTUP[] = { #ifdef APP_DISPLAY @@ -88,6 +89,13 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { .icon = A_Plugins_14}, #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 {.app = dolphin_task, .name = "dolphin_task", @@ -165,7 +173,7 @@ const FlipperStartupApp FLIPPER_STARTUP[] = { #ifdef APP_SD_TEST {.app = 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}, #endif @@ -257,7 +265,7 @@ const FlipperStartupApp FLIPPER_PLUGINS[] = { #ifdef BUILD_SD_TEST {.app = 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}, #endif diff --git a/applications/applications.mk b/applications/applications.mk index 470c5da0..e27f6146 100644 --- a/applications/applications.mk +++ b/applications/applications.mk @@ -13,6 +13,7 @@ APP_NFC = 1 APP_POWER = 1 APP_BT = 1 APP_CLI = 1 +APP_SD_FILESYSTEM = 1 BUILD_IRDA = 1 APP_DOLPHIN = 1 BUILD_EXAMPLE_BLINK = 1 @@ -220,6 +221,7 @@ CFLAGS += -DBUILD_SD_TEST CPP_SOURCES += $(wildcard $(APP_DIR)/sd-card-test/*.cpp) APP_INPUT = 1 APP_GUI = 1 +APP_SD_FILESYSTEM = 1 endif APP_SPEAKER_DEMO ?= 0 @@ -305,6 +307,12 @@ C_SOURCES += $(wildcard $(APP_DIR)/gui/*.c) C_SOURCES += $(wildcard $(APP_DIR)/backlight-control/*.c) endif +APP_SD_FILESYSTEM ?= 0 +ifeq ($(APP_SD_FILESYSTEM), 1) +CFLAGS += -DAPP_SD_FILESYSTEM +C_SOURCES += $(wildcard $(APP_DIR)/sd-filesystem/*.c) +endif + # deprecated ifeq ($(APP_DISPLAY), 1) CFLAGS += -DAPP_DISPLAY diff --git a/applications/sd-card-test/sd-card-test.cpp b/applications/sd-card-test/sd-card-test.cpp index c2361511..dfe135f7 100644 --- a/applications/sd-card-test/sd-card-test.cpp +++ b/applications/sd-card-test/sd-card-test.cpp @@ -1,7 +1,7 @@ #include "app-template.h" -#include "fatfs/ff.h" #include "stm32_adafruit_sd.h" #include "fnv1a-hash.h" +#include "filesystem-api.h" // event enumeration type typedef uint8_t event_t; @@ -43,17 +43,15 @@ public: // vars GpioPin* red_led_record; GpioPin* green_led_record; - FATFS sd_fat_fs; - char sd_path[6]; const uint32_t benchmark_data_size = 4096; uint8_t* benchmark_data; + FS_Api* fs_api; // funcs void run(); void render(Canvas* canvas); template void set_text(std::initializer_list list); template void set_error(std::initializer_list list); - const char* fatfs_error_desc(FRESULT res); void wait_for_button(Input input_button); bool ask(Input input_button_cancel, Input input_button_ok); void blink_red(); @@ -63,11 +61,6 @@ public: // "tests" void detect_sd_card(); 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 prepare_benchmark_data(); @@ -76,7 +69,7 @@ public: uint32_t write_benchmark_internal(const uint32_t size, const uint32_t tcount); 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(); }; @@ -97,16 +90,17 @@ void SdTest::run() { app_ready(); - detect_sd_card(); - show_warning(); - init_sd_card(); - if(!is_sd_card_formatted()) { - format_sd_card(); - } else { - ask_and_format_sd_card(); + fs_api = static_cast(furi_open("sdcard")); + + if(fs_api == NULL) { + set_error({"cannot get sdcard api"}); + exit(); } - mount_sd_card(); + + detect_sd_card(); get_sd_card_info(); + show_warning(); + prepare_benchmark_data(); write_benchmark(); read_benchmark(); @@ -134,7 +128,7 @@ void SdTest::detect_sd_card() { uint8_t i = 0; // detect sd card pin - while(!hal_gpio_read_sd_detect()) { + while(fs_api->common.get_fs_info(NULL, NULL) == FSE_NOT_READY) { delay(100); snprintf(str_buffer, str_buffer_size, "Waiting%s", dots[i]); @@ -155,7 +149,7 @@ void SdTest::show_warning() { set_text( {"!!Warning!!", "during the tests", - "card may be formatted", + "files can be overwritten", "or data on card may be lost", "", "press UP DOWN OK to continue"}); @@ -165,96 +159,22 @@ void SdTest::show_warning() { 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(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 // sector, cluster, total and free size void SdTest::get_sd_card_info() { const uint8_t str_buffer_size = 26; - char str_buffer[4][str_buffer_size]; - char volume_label[128]; - DWORD serial_num; - FRESULT result; - FATFS* fs; - DWORD free_clusters, free_sectors, total_sectors; - - // suppress "'%s' directive output may be truncated" warning about snprintf + char str_buffer[2][str_buffer_size]; + FS_Error result; + uint64_t bytes_total, bytes_free; int __attribute__((unused)) snprintf_count = 0; - // get label and s/n - result = f_getlabel(sd_path, volume_label, &serial_num); - if(result) set_error({"f_getlabel error", fatfs_error_desc(result)}); + result = fs_api->common.get_fs_info(&bytes_total, &bytes_free); + if(result != FSE_OK) set_error({"get_fs_info error", fs_api->error.get_desc(result)}); - snprintf_count = snprintf(str_buffer[0], str_buffer_size, "Label: %s", volume_label); - snprintf(str_buffer[1], str_buffer_size, "S/N: %lu", serial_num); + snprintf( + str_buffer[0], str_buffer_size, "%lu KB total", static_cast(bytes_total / 1024)); + snprintf( + str_buffer[1], str_buffer_size, "%lu KB free", static_cast(bytes_free / 1024)); set_text( {static_cast(str_buffer[0]), @@ -267,30 +187,6 @@ void SdTest::get_sd_card_info() { blink_green(); 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(str_buffer[0]), - static_cast(str_buffer[1]), - static_cast(str_buffer[2]), - static_cast(str_buffer[3]), - "", - "press OK to continue"}); - - blink_green(); - - wait_for_button(InputOk); } // 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 start_tick, stop_tick, benchmark_bps, benchmark_time, bytes_written; - FRESULT result; - FIL file; + File file; const uint8_t str_buffer_size = 32; char str_buffer[str_buffer_size]; - result = f_open(&file, "write.test", FA_WRITE | FA_OPEN_ALWAYS); - if(result) { + if(!fs_api->file.open(&file, "write.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); set_error({"cannot open file ", static_cast(str_buffer)}); } start_tick = osKernelGetTickCount(); for(size_t i = 0; i < count; i++) { - result = f_write(&file, benchmark_data, size, reinterpret_cast(&bytes_written)); - if(bytes_written != size || result) { + bytes_written = fs_api->file.write(&file, benchmark_data, size); + if(bytes_written != size || file.error_id != FSE_OK) { snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); set_error({"cannot write to file ", static_cast(str_buffer)}); } } stop_tick = osKernelGetTickCount(); - result = f_close(&file); - if(result) { + if(!fs_api->file.close(&file)) { snprintf(str_buffer, str_buffer_size, "in %lu-byte write test", size); set_error({"cannot close file ", static_cast(str_buffer)}); } @@ -425,8 +318,7 @@ void SdTest::read_benchmark() { static_cast(str_buffer[4]), static_cast(str_buffer[5])}; - FRESULT result; - FIL file; + File file; const uint32_t b1_size = 1; 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"}); delay(100); - result = f_open(&file, "read.test", FA_WRITE | FA_OPEN_ALWAYS); - if(result) { + if(!fs_api->file.open(&file, "read.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { set_error({"cannot open file ", "in prepare read"}); } for(size_t i = 0; i < benchmark_data_size / b4096_size; i++) { - result = - f_write(&file, benchmark_data, b4096_size, reinterpret_cast(&bytes_written)); - if(bytes_written != b4096_size || result) { + bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size); + if(bytes_written != b4096_size || file.error_id != FSE_OK) { set_error({"cannot write to file ", "in prepare read"}); } } - result = f_close(&file); - if(result) { + if(!fs_api->file.close(&file)) { set_error({"cannot close file ", "in prepare read"}); } @@ -461,8 +350,7 @@ void SdTest::read_benchmark() { delay(100); // open file - result = f_open(&file, "read.test", FA_READ | FA_OPEN_EXISTING); - if(result) { + if(!fs_api->file.open(&file, "read.test", FSAM_READ, FSOM_OPEN_EXISTING)) { set_error({"cannot open file ", "in read benchmark"}); } @@ -497,8 +385,7 @@ void SdTest::read_benchmark() { set_text(string_list); // close file - result = f_close(&file); - if(result) { + if(!fs_api->file.close(&file)) { set_error({"cannot close file ", "in read test"}); } @@ -507,9 +394,9 @@ void SdTest::read_benchmark() { 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; - FRESULT result; + //FRESULT result; const uint8_t str_buffer_size = 32; 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(str_buffer)}); } - f_rewind(file); + fs_api->file.seek(file, 0, true); start_tick = osKernelGetTickCount(); for(size_t i = 0; i < count; i++) { - result = f_read(file, read_buffer, size, reinterpret_cast(&bytes_readed)); - if(bytes_readed != size || result) { + bytes_readed = fs_api->file.read(file, read_buffer, size); + if(bytes_readed != size || file->error_id != FSE_OK) { snprintf(str_buffer, str_buffer_size, "in %lu-byte read test", size); set_error({"cannot read from file ", static_cast(str_buffer)}); } @@ -555,8 +442,7 @@ void SdTest::hash_benchmark() { const uint8_t str_buffer_size = 32; char str_buffer[3][str_buffer_size] = {"", "", ""}; - FRESULT result; - FIL file; + File file; const uint32_t b4096_size = 4096; const uint32_t benchmark_count = 20; @@ -566,17 +452,15 @@ void SdTest::hash_benchmark() { delay(100); // write data to test file and calculate hash - result = f_open(&file, "hash.test", FA_WRITE | FA_OPEN_ALWAYS); - if(result) { + if(!fs_api->file.open(&file, "hash.test", FSAM_WRITE, FSOM_OPEN_ALWAYS)) { set_error({"cannot open file ", "in prepare hash"}); } for(uint32_t i = 0; i < benchmark_count; i++) { mcu_data_hash = fnv1a_buffer_hash(benchmark_data, b4096_size, mcu_data_hash); - result = - f_write(&file, benchmark_data, b4096_size, reinterpret_cast(&bytes_written)); + bytes_written = fs_api->file.write(&file, benchmark_data, b4096_size); - 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"}); } @@ -585,8 +469,7 @@ void SdTest::hash_benchmark() { delay(100); } - result = f_close(&file); - if(result) { + if(!fs_api->file.close(&file)) { set_error({"cannot close file ", "in prepare hash"}); } @@ -602,16 +485,15 @@ void SdTest::hash_benchmark() { set_error({"cannot allocate memory", "in hash test"}); } - result = f_open(&file, "hash.test", FA_READ | FA_OPEN_EXISTING); - if(result) { + if(!fs_api->file.open(&file, "hash.test", FSAM_READ, FSOM_OPEN_EXISTING)) { set_error({"cannot open file ", "in hash test"}); } for(uint32_t i = 0; i < benchmark_count; i++) { - result = f_read(&file, read_buffer, b4096_size, reinterpret_cast(&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); - 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"}); } @@ -620,9 +502,7 @@ void SdTest::hash_benchmark() { delay(100); } - result = f_close(&file); - - if(result) { + if(!fs_api->file.close(&file)) { set_error({"cannot close file ", "in hash test"}); } @@ -730,76 +610,6 @@ void SdTest::blink_green() { 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 template void SdTest::set_error(std::initializer_list list) { set_text(list); diff --git a/applications/sd-filesystem/sd-filesystem-api.c b/applications/sd-filesystem/sd-filesystem-api.c new file mode 100644 index 00000000..7301fd5b --- /dev/null +++ b/applications/sd-filesystem/sd-filesystem-api.c @@ -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; +} \ No newline at end of file diff --git a/applications/sd-filesystem/sd-filesystem.c b/applications/sd-filesystem/sd-filesystem.c new file mode 100644 index 00000000..c9640e7b --- /dev/null +++ b/applications/sd-filesystem/sd-filesystem.c @@ -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); + } +} \ No newline at end of file diff --git a/applications/sd-filesystem/sd-filesystem.h b/applications/sd-filesystem/sd-filesystem.h new file mode 100644 index 00000000..171de48c --- /dev/null +++ b/applications/sd-filesystem/sd-filesystem.h @@ -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); \ No newline at end of file diff --git a/firmware/targets/f4/Src/fatfs/ffconf.h b/firmware/targets/f4/Src/fatfs/ffconf.h index 3de4837b..0662e63d 100644 --- a/firmware/targets/f4/Src/fatfs/ffconf.h +++ b/firmware/targets/f4/Src/fatfs/ffconf.h @@ -47,7 +47,7 @@ / 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1. / 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 / f_printf(). / @@ -68,7 +68,7 @@ #define _USE_EXPAND 0 /* 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(). / (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() / funciton will be available. */ #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, / 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 diff --git a/firmware/targets/f4/api-hal/api-hal-gpio.c b/firmware/targets/f4/api-hal/api-hal-gpio.c index 74de4785..1827a88a 100644 --- a/firmware/targets/f4/api-hal/api-hal-gpio.c +++ b/firmware/targets/f4/api-hal/api-hal-gpio.c @@ -1,5 +1,6 @@ #include "api-hal-gpio.h" #include "api-hal-resources.h" +#include "spi.h" // init GPIO void hal_gpio_init( @@ -20,20 +21,27 @@ void hal_gpio_init( bool hal_gpio_read_sd_detect(void) { bool result = false; - + // TODO open record const GpioPin* sd_cs_record = &sd_cs_gpio; + // TODO: SPI manager + api_hal_spi_lock(&SPI_SD_HANDLE); + // configure pin as input gpio_init_ex(sd_cs_record, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh); - delay(50); + delay(1); // if gpio_read == 0 return true else return false result = !gpio_read(sd_cs_record); // configure pin back 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; } diff --git a/lib/common-api/filesystem-api.h b/lib/common-api/filesystem-api.h new file mode 100644 index 00000000..5a710b0e --- /dev/null +++ b/lib/common-api/filesystem-api.h @@ -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 \ No newline at end of file diff --git a/lib/fatfs/integer.h b/lib/fatfs/integer.h index 9ce7865b..efef2984 100644 --- a/lib/fatfs/integer.h +++ b/lib/fatfs/integer.h @@ -5,33 +5,33 @@ #ifndef _FF_INTEGER #define _FF_INTEGER -#ifdef _WIN32 /* FatFs development platform */ +#ifdef _WIN32 /* FatFs development platform */ #include #include typedef unsigned __int64 QWORD; - -#else /* Embedded platform */ +#else /* Embedded platform */ +#include /* These types MUST be 16-bit or 32-bit */ -typedef int INT; -typedef unsigned int UINT; +typedef int16_t INT; +typedef uint16_t UINT; /* This type MUST be 8-bit */ -typedef unsigned char BYTE; +typedef uint8_t BYTE; /* These types MUST be 16-bit */ -typedef short SHORT; -typedef unsigned short WORD; -typedef unsigned short WCHAR; +typedef int16_t SHORT; +typedef uint16_t WORD; +typedef uint16_t WCHAR; /* These types MUST be 32-bit */ -typedef long LONG; -typedef unsigned long DWORD; +typedef int32_t LONG; +typedef uint32_t DWORD; /* This type MUST be 64-bit (Remove this for ANSI C (C89) compatibility) */ -typedef unsigned long long QWORD; +typedef uint64_t QWORD; #endif diff --git a/lib/lib.mk b/lib/lib.mk index 6da48f67..b1813d7c 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -70,6 +70,9 @@ CFLAGS += -I$(CYFRAL_DIR) CPP_SOURCES += $(wildcard $(CYFRAL_DIR)/*.cpp) endif +# common apps api +CFLAGS += -I$(LIB_DIR)/common-api + # drivers ifneq ($(TARGET), local) ifneq ($(TARGET), f2)