diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index 192dc2d817e27..775633860541a 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -1371,6 +1371,16 @@ Filesystems: labels: - "area: File System" +"Filesystems: FatFs reentrant support": + status: maintained + maintainers: + - ox11 + files: + - modules/fatfs/zfs_ffsystem.c + - tests/subsys/fs/fat_fs_api/src/test_fat_file_reentrant.c + labels: + - "area: File System" + Formatted Output: status: maintained maintainers: diff --git a/doc/releases/release-notes-3.4.rst b/doc/releases/release-notes-3.4.rst index 332d3e78d6304..484af9199dd96 100644 --- a/doc/releases/release-notes-3.4.rst +++ b/doc/releases/release-notes-3.4.rst @@ -228,6 +228,10 @@ Devicetree Libraries / Subsystems ********************** +* File systems + + * Added :kconfig:option:`CONFIG_FS_FATFS_REENTRANT` to enable the FAT FS reentrant option. + HALs **** diff --git a/modules/fatfs/CMakeLists.txt b/modules/fatfs/CMakeLists.txt index 97371b1fb44f7..63c4701bb987d 100644 --- a/modules/fatfs/CMakeLists.txt +++ b/modules/fatfs/CMakeLists.txt @@ -18,9 +18,12 @@ if(CONFIG_FAT_FILESYSTEM_ELM) zephyr_library_sources_ifdef(CONFIG_FS_FATFS_LFN ${ZEPHYR_FATFS_MODULE_DIR}/option/ffunicode.c - ${ZEPHYR_FATFS_MODULE_DIR}/option/ffsystem.c ) + if(DEFINED CONFIG_FS_FATFS_LFN OR DEFINED CONFIG_FS_FATFS_REENTRANT) + zephyr_library_sources(zfs_ffsystem.c) + endif() + zephyr_library_link_libraries(ELMFAT) target_link_libraries(ELMFAT INTERFACE zephyr_interface) endif() diff --git a/modules/fatfs/zephyr_fatfs_config.h b/modules/fatfs/zephyr_fatfs_config.h index be6dd04a1b4c4..7ced4f74cdf4a 100644 --- a/modules/fatfs/zephyr_fatfs_config.h +++ b/modules/fatfs/zephyr_fatfs_config.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2022 Nordic Semiconductor ASA + * Copyright (c) 2023 Husqvarna AB * * SPDX-License-Identifier: Apache-2.0 */ @@ -62,6 +63,14 @@ #define FF_FS_EXFAT CONFIG_FS_FATFS_EXFAT #endif /* defined(CONFIG_FS_FATFS_EXFAT) */ +#if defined(CONFIG_FS_FATFS_REENTRANT) +#undef FF_FS_REENTRANT +#undef FF_FS_TIMEOUT +#include +#define FF_FS_REENTRANT CONFIG_FS_FATFS_REENTRANT +#define FF_FS_TIMEOUT K_FOREVER +#endif /* defined(CONFIG_FS_FATFS_REENTRANT) */ + /* * These options are override from default values, but have no Kconfig * options. diff --git a/modules/fatfs/zfs_ffsystem.c b/modules/fatfs/zfs_ffsystem.c new file mode 100644 index 0000000000000..6f4d134f798b3 --- /dev/null +++ b/modules/fatfs/zfs_ffsystem.c @@ -0,0 +1,69 @@ +/* + * OS Dependent Functions for FatFs + * + * Copyright (c) 2023 Husqvarna AB + * + * SPDX-License-Identifier: Apache-2.0 + */ +/* The file is based on template file by (C)ChaN, 2022, as + * available from FAT FS module source: + * https://github.com/zephyrproject-rtos/fatfs/blob/master/option/ffsystem.c + */ + +#include + +#if FF_USE_LFN == 3 /* Dynamic memory allocation */ +/* Allocate a memory block */ +void *ff_memalloc(UINT msize) +{ + return k_malloc(msize); +} + +/* Free a memory block */ +void ff_memfree(void *mblock) +{ + k_free(mblock); +} +#endif /* FF_USE_LFN == 3 */ + +#if FF_FS_REENTRANT /* Mutual exclusion */ +/* Table of Zephyr mutex. One for each volume and an extra one for the ff system. + * See also the template file used as reference. Link is available in the header of this file. + */ +static struct k_mutex fs_reentrant_mutex[FF_VOLUMES + 1]; + +/* Create a Mutex + * Mutex ID vol: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) + * Returns 1: Succeeded or 0: Could not create the mutex + */ +int ff_mutex_create(int vol) +{ + return (int)(k_mutex_init(&fs_reentrant_mutex[vol]) == 0); +} + +/* Delete a Mutex + * Mutex ID vol: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) + */ +void ff_mutex_delete(int vol) +{ + /* (nothing to do) */ + (void)vol; +} + +/* Request Grant to Access the Volume + * Mutex ID vol: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) + * Returns 1: Succeeded or 0: Timeout + */ +int ff_mutex_take(int vol) +{ + return (int)(k_mutex_lock(&fs_reentrant_mutex[vol], FF_FS_TIMEOUT) == 0); +} + +/* Release Grant to Access the Volume + * Mutex ID vol: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) + */ +void ff_mutex_give(int vol) +{ + k_mutex_unlock(&fs_reentrant_mutex[vol]); +} +#endif /* FF_FS_REENTRANT */ diff --git a/subsys/fs/Kconfig.fatfs b/subsys/fs/Kconfig.fatfs index 2a138dbb9567c..e44ca1c781d91 100644 --- a/subsys/fs/Kconfig.fatfs +++ b/subsys/fs/Kconfig.fatfs @@ -1,5 +1,6 @@ # Copyright (c) 2016 Intel Corporation # Copyright (c) 2020 Nordic Semiconductor ASA +# Copyright (c) 2023 Husqvarna AB # SPDX-License-Identifier: Apache-2.0 config FAT_FILESYSTEM_ELM @@ -220,6 +221,14 @@ config FS_FATFS_WINDOW_ALIGNMENT that, in worst scenario, value provided here may cause FATFS structure to have size of twice the value. +config FS_FATFS_REENTRANT + bool "FatFs reentrant" + depends on !FS_FATFS_LFN_MODE_BSS + help + Enable the FatFs re-entrancy (thread safe) option for file/directory + access for each volume. Will create a zephyr mutex object for each + FatFs volume and a FatFs system mutex. + endmenu endif # FAT_FILESYSTEM_ELM diff --git a/tests/subsys/fs/fat_fs_api/CMakeLists.txt b/tests/subsys/fs/fat_fs_api/CMakeLists.txt index 54fa261231314..96a02b2be72ef 100644 --- a/tests/subsys/fs/fat_fs_api/CMakeLists.txt +++ b/tests/subsys/fs/fat_fs_api/CMakeLists.txt @@ -18,3 +18,5 @@ target_sources(app PRIVATE target_sources_ifdef(CONFIG_FLASH app PRIVATE ../common/test_fs_mkfs.c src/test_fat_mkfs.c) +target_sources_ifdef(CONFIG_FS_FATFS_REENTRANT app PRIVATE + src/test_fat_file_reentrant.c) diff --git a/tests/subsys/fs/fat_fs_api/src/common.c b/tests/subsys/fs/fat_fs_api/src/common.c index 4e250fcb4a9e2..fd4aa46b30828 100644 --- a/tests/subsys/fs/fat_fs_api/src/common.c +++ b/tests/subsys/fs/fat_fs_api/src/common.c @@ -1,11 +1,14 @@ /* * Copyright (c) 2016 Intel Corporation. + * Copyright (c) 2023 Husqvarna AB * * SPDX-License-Identifier: Apache-2.0 */ #include "test_fat.h" +/* FatFs work area */ +FATFS fat_fs; struct fs_file_t filep; const char test_str[] = "hello world!"; diff --git a/tests/subsys/fs/fat_fs_api/src/main.c b/tests/subsys/fs/fat_fs_api/src/main.c index 870cda4def207..30f73aa0d8a02 100644 --- a/tests/subsys/fs/fat_fs_api/src/main.c +++ b/tests/subsys/fs/fat_fs_api/src/main.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2016 Intel Corporation. + * Copyright (c) 2023 Husqvarna AB * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,6 +19,9 @@ static void *fat_fs_basic_setup(void) test_fat_fs(); test_fat_rename(); test_fs_open_flags(); +#ifdef CONFIG_FS_FATFS_REENTRANT + test_fat_file_reentrant(); +#endif /* CONFIG_FS_FATFS_REENTRANT */ test_fat_unmount(); return NULL; diff --git a/tests/subsys/fs/fat_fs_api/src/test_fat.h b/tests/subsys/fs/fat_fs_api/src/test_fat.h index 607f3eb0bc41d..9c168b4bccf80 100644 --- a/tests/subsys/fs/fat_fs_api/src/test_fat.h +++ b/tests/subsys/fs/fat_fs_api/src/test_fat.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2016 Intel Corporation. * Copyright (c) 2020 Nordic Semiconductor ASA + * Copyright (c) 2023 Husqvarna AB * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +9,7 @@ #include #include #include +#include #ifdef CONFIG_DISK_DRIVER_RAM #define DISK_NAME CONFIG_DISK_RAM_VOLUME_NAME @@ -27,12 +29,14 @@ "/testlongfilenamethatsmuchlongerthan8.3chars.text" #else #define TEST_FILE FATFS_MNTP"/testfile.txt" -#endif /* IS_ENABLED(CONFIG_FS_FATFS_LFN) */ +#endif /* CONFIG_FS_FATFS_LFN */ #define TEST_DIR FATFS_MNTP"/testdir" #define TEST_DIR_FILE FATFS_MNTP"/testdir/testfile.txt" extern struct fs_file_t filep; extern const char test_str[]; +/* FatFs work area */ +extern FATFS fat_fs; int check_file_dir_exists(const char *path); @@ -42,3 +46,6 @@ void test_fat_file(void); void test_fat_dir(void); void test_fat_fs(void); void test_fat_rename(void); +#ifdef CONFIG_FS_FATFS_REENTRANT +void test_fat_file_reentrant(void); +#endif /* CONFIG_FS_FATFS_REENTRANT */ diff --git a/tests/subsys/fs/fat_fs_api/src/test_fat_file_reentrant.c b/tests/subsys/fs/fat_fs_api/src/test_fat_file_reentrant.c new file mode 100644 index 0000000000000..2ef24aaea02d3 --- /dev/null +++ b/tests/subsys/fs/fat_fs_api/src/test_fat_file_reentrant.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2023 Husqvarna AB + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_fat.h" + +#ifdef CONFIG_FS_FATFS_REENTRANT + +#define REENTRANT_TEST_STACK_SIZE 500 +#define SEMAPHORE_OP_SUCCESS 0 +#define TEST_FILE2 FATFS_MNTP"/tfile2.txt" + +void tlock_mutex(void *p1, void *p2, void *p3); +void tfile2_access(void *p1, void *p2, void *p3); + +K_THREAD_STACK_DEFINE(tlock_mutex_stack_area, REENTRANT_TEST_STACK_SIZE); +K_THREAD_STACK_DEFINE(tfile2_access_stack_area, REENTRANT_TEST_STACK_SIZE); +struct k_thread tlock_mutex_data; +struct k_thread tfile2_access_data; +struct k_sem mutex_unlocked_sem; +struct k_sem run_non_thread_sem; + +static int test_reentrant_access(void) +{ + int res; + + TC_PRINT("\nReentrant tests:\n"); + zassert_ok(k_sem_init(&mutex_unlocked_sem, 0, 1), NULL); + zassert_ok(k_sem_init(&run_non_thread_sem, 0, 1), NULL); + + /* Start mutex locking thread */ + k_tid_t tid = k_thread_create(&tlock_mutex_data, tlock_mutex_stack_area, + K_THREAD_STACK_SIZEOF(tlock_mutex_stack_area), + tlock_mutex, + NULL, NULL, NULL, + K_PRIO_PREEMPT(0), 0, K_NO_WAIT); + + /* Make sure thread was able to lock mutex */ + k_sem_take(&run_non_thread_sem, K_FOREVER); + + /* File open should wait here, as the fs is locked. Therefore, automatic switch back to + * thread + */ + TC_PRINT("Open file\n"); + res = fs_open(&filep, TEST_FILE, FS_O_CREATE | FS_O_RDWR); + zassert_ok(res, "Err: File could not be opened [%d]\n", res); + TC_PRINT("File opened\n"); + + /* Check if mutex thread really unlocked the mutexes */ + zassert_equal(SEMAPHORE_OP_SUCCESS, k_sem_take(&mutex_unlocked_sem, K_NO_WAIT), + "File open with locked mutex"); + + /* Cleanup */ + res = fs_close(&filep); + zassert_ok(res, "Error closing file [%d]\n", res); + res = fs_unlink(TEST_FILE); + zassert_ok(res, "Error deleting file [%d]\n", res); + + k_thread_join(tid, K_FOREVER); + + return res; +} + +static int test_reentrant_parallel_file_access(void) +{ + int res; + + TC_PRINT("\nParallel reentrant-safe file access test:\n"); + + TC_PRINT("Open file 1\n"); + res = fs_open(&filep, TEST_FILE, FS_O_CREATE | FS_O_RDWR); + zassert_ok(res, "Err: File 1 could not be opened [%d]\n", res); + TC_PRINT("File 1 opened 1\n"); + + /* Start 2nd file acces thread */ + k_tid_t tid = k_thread_create(&tfile2_access_data, tfile2_access_stack_area, + K_THREAD_STACK_SIZEOF(tfile2_access_stack_area), + tfile2_access, + NULL, NULL, NULL, + K_PRIO_PREEMPT(0), 0, K_NO_WAIT); + + /* Wait for thread to finish accessing file 2 */ + k_thread_join(tid, K_FOREVER); + + /* Check existence of file 2 */ + struct fs_file_t filep2; + + fs_file_t_init(&filep2); + + TC_PRINT("Check file 2 existence\n"); + res = fs_open(&filep2, TEST_FILE2, FA_OPEN_EXISTING | FA_READ); + zassert_ok(res, "Err: File 2 does not exist [%d]\n", res); + + /* Cleanup */ + res = fs_close(&filep2); + zassert_ok(res, "Error closing file 2 [%d]\n", res); + res = fs_unlink(TEST_FILE2); + zassert_ok(res, "Error deleting file 2 [%d]\n", res); + res = fs_close(&filep); + zassert_ok(res, "Error closing file 1 [%d]\n", res); + res = fs_unlink(TEST_FILE); + zassert_ok(res, "Error deleting file 1 [%d]\n", res); + + return res; +} + +void release_dirty_mutex(void) +{ + ff_mutex_give(fat_fs.ldrv); +} + +int request_dirty_mutex(void) +{ + return ff_mutex_take(fat_fs.ldrv); +} + +void tlock_mutex(void *p1, void *p2, void *p3) +{ + TC_PRINT("Mutex thread: Started, locking fs\n"); + request_dirty_mutex(); + TC_PRINT("Mutex thread: Lock acquired, yield to switch back to try to open file\n"); + k_sem_give(&run_non_thread_sem); + k_yield(); + + TC_PRINT("Mutex thread: Got back to thread, release mutex now and give semaphore to check " + "if file opened\n"); + k_sem_give(&mutex_unlocked_sem); + release_dirty_mutex(); + + TC_PRINT("Mutex thread: Lock released, thread terminating\n"); +} + +void tfile2_access(void *p1, void *p2, void *p3) +{ + int res; + ssize_t brw; + struct fs_file_t filep2; + + TC_PRINT("File 2 access thread started\n"); + + /* Init fp for 2nd File for parallel access test */ + fs_file_t_init(&filep2); + + /* open 2nd file */ + TC_PRINT("Open file 2\n"); + res = fs_open(&filep2, TEST_FILE2, FS_O_CREATE | FS_O_RDWR); + zassert_ok(res, "Err: File 2 could not be opened [%d]\n", res); + TC_PRINT("File 2 opened 2\n"); + + /* Verify fs_write() not locked */ + brw = fs_write(&filep2, (char *)test_str, strlen(test_str)); + if (brw < 0) { + TC_PRINT("Failed writing to file [%zd]\n", brw); + fs_close(&filep2); + return; + } + + if (brw < strlen(test_str)) { + TC_PRINT("Unable to complete write. Volume full.\n"); + TC_PRINT("Number of bytes written: [%zd]\n", brw); + fs_close(&filep2); + return; + } + + /* Close file and switch back to test context*/ + res = fs_close(&filep2); + zassert_ok(res, "Error closing file [%d]\n", res); + + TC_PRINT("File 2 access thread successfully wrote to file 2\n"); +} + +void test_fat_file_reentrant(void) +{ + zassert_true(test_reentrant_access() == TC_PASS, NULL); + zassert_true(test_reentrant_parallel_file_access() == TC_PASS, NULL); +} +#endif /* CONFIG_FS_FATFS_REENTRANT */ diff --git a/tests/subsys/fs/fat_fs_api/src/test_fat_mkfs.c b/tests/subsys/fs/fat_fs_api/src/test_fat_mkfs.c index 2c567cc2d829f..a377fd1a5234a 100644 --- a/tests/subsys/fs/fat_fs_api/src/test_fat_mkfs.c +++ b/tests/subsys/fs/fat_fs_api/src/test_fat_mkfs.c @@ -13,9 +13,6 @@ #include #endif -/* FatFs work area */ -static FATFS fat_fs; - /* mounting info */ static struct fs_mount_t fatfs_mnt = { .type = FS_FATFS, diff --git a/tests/subsys/fs/fat_fs_api/src/test_fat_mount.c b/tests/subsys/fs/fat_fs_api/src/test_fat_mount.c index 020104b01ac50..d0c7de813c3b2 100644 --- a/tests/subsys/fs/fat_fs_api/src/test_fat_mount.c +++ b/tests/subsys/fs/fat_fs_api/src/test_fat_mount.c @@ -7,10 +7,6 @@ #include "test_fat.h" -#include - -/* FatFs work area */ -static FATFS fat_fs; /* mounting info */ static struct fs_mount_t fatfs_mnt = { diff --git a/tests/subsys/fs/fat_fs_api/src/test_fat_rd_only_mount.c b/tests/subsys/fs/fat_fs_api/src/test_fat_rd_only_mount.c index 7331cc9442f2e..0a49b6430f995 100644 --- a/tests/subsys/fs/fat_fs_api/src/test_fat_rd_only_mount.c +++ b/tests/subsys/fs/fat_fs_api/src/test_fat_rd_only_mount.c @@ -6,10 +6,6 @@ #include "test_fat.h" -#include - -/* FatFs work area */ -static FATFS fat_fs; /* mounting info */ static struct fs_mount_t fatfs_mnt = { diff --git a/tests/subsys/fs/fat_fs_api/testcase.yaml b/tests/subsys/fs/fat_fs_api/testcase.yaml index 522b51ceed5ab..ac546a4ed75ef 100644 --- a/tests/subsys/fs/fat_fs_api/testcase.yaml +++ b/tests/subsys/fs/fat_fs_api/testcase.yaml @@ -14,3 +14,8 @@ tests: filesystem.fat.ram.api: platform_allow: native_posix extra_args: CONF_FILE="prj_native_posix_ram.conf" + filesystem.fat.api.reentrant: + platform_allow: native_posix + extra_configs: + - CONFIG_FS_FATFS_REENTRANT=y + - CONFIG_MULTITHREADING=y