/* * 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 */