1 /*
2  * Copyright (c) 2023 Husqvarna AB
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include "test_fat.h"
8 
9 #ifdef CONFIG_FS_FATFS_REENTRANT
10 
11 #define REENTRANT_TEST_STACK_SIZE 500
12 #define SEMAPHORE_OP_SUCCESS 0
13 #define TEST_FILE2 FATFS_MNTP"/tfile2.txt"
14 
15 void tlock_mutex(void *p1, void *p2, void *p3);
16 void tfile2_access(void *p1, void *p2, void *p3);
17 
18 K_THREAD_STACK_DEFINE(tlock_mutex_stack_area, REENTRANT_TEST_STACK_SIZE);
19 K_THREAD_STACK_DEFINE(tfile2_access_stack_area, REENTRANT_TEST_STACK_SIZE);
20 struct k_thread tlock_mutex_data;
21 struct k_thread tfile2_access_data;
22 struct k_sem mutex_unlocked_sem;
23 struct k_sem run_non_thread_sem;
24 
test_reentrant_access(void)25 static int test_reentrant_access(void)
26 {
27 	int res;
28 
29 	TC_PRINT("\nReentrant tests:\n");
30 	zassert_ok(k_sem_init(&mutex_unlocked_sem, 0, 1), NULL);
31 	zassert_ok(k_sem_init(&run_non_thread_sem, 0, 1), NULL);
32 
33 	/* Start mutex locking thread */
34 	k_tid_t tid = k_thread_create(&tlock_mutex_data, tlock_mutex_stack_area,
35 			K_THREAD_STACK_SIZEOF(tlock_mutex_stack_area),
36 			tlock_mutex,
37 			NULL, NULL, NULL,
38 			K_PRIO_PREEMPT(0), 0, K_NO_WAIT);
39 
40 	/* Make sure thread was able to lock mutex */
41 	k_sem_take(&run_non_thread_sem, K_FOREVER);
42 
43 	/* File open should wait here, as the fs is locked. Therefore, automatic switch back to
44 	 * thread
45 	 */
46 	TC_PRINT("Open file\n");
47 	res = fs_open(&filep, TEST_FILE, FS_O_CREATE | FS_O_RDWR);
48 	zassert_ok(res, "Err: File could not be opened [%d]\n", res);
49 	TC_PRINT("File opened\n");
50 
51 	/* Check if mutex thread really unlocked the mutexes */
52 	zassert_equal(SEMAPHORE_OP_SUCCESS, k_sem_take(&mutex_unlocked_sem, K_NO_WAIT),
53 		      "File open with locked mutex");
54 
55 	/* Cleanup */
56 	res = fs_close(&filep);
57 	zassert_ok(res, "Error closing file [%d]\n", res);
58 	res = fs_unlink(TEST_FILE);
59 	zassert_ok(res, "Error deleting file [%d]\n", res);
60 
61 	k_thread_join(tid, K_FOREVER);
62 
63 	return res;
64 }
65 
test_reentrant_parallel_file_access(void)66 static int test_reentrant_parallel_file_access(void)
67 {
68 	int res;
69 
70 	TC_PRINT("\nParallel reentrant-safe file access test:\n");
71 
72 	TC_PRINT("Open file 1\n");
73 	res = fs_open(&filep, TEST_FILE, FS_O_CREATE | FS_O_RDWR);
74 	zassert_ok(res, "Err: File 1 could not be opened [%d]\n", res);
75 	TC_PRINT("File 1 opened 1\n");
76 
77 	/* Start 2nd file acces thread */
78 	k_tid_t tid = k_thread_create(&tfile2_access_data, tfile2_access_stack_area,
79 				      K_THREAD_STACK_SIZEOF(tfile2_access_stack_area),
80 				      tfile2_access,
81 				      NULL, NULL, NULL,
82 				      K_PRIO_PREEMPT(0), 0, K_NO_WAIT);
83 
84 	/* Wait for thread to finish accessing file 2 */
85 	k_thread_join(tid, K_FOREVER);
86 
87 	/* Check existence of file 2 */
88 	struct fs_file_t filep2;
89 
90 	fs_file_t_init(&filep2);
91 
92 	TC_PRINT("Check file 2 existence\n");
93 	res = fs_open(&filep2, TEST_FILE2, FA_OPEN_EXISTING | FA_READ);
94 	zassert_ok(res, "Err: File 2 does not exist [%d]\n", res);
95 
96 	/* Cleanup */
97 	res = fs_close(&filep2);
98 	zassert_ok(res, "Error closing file 2 [%d]\n", res);
99 	res = fs_unlink(TEST_FILE2);
100 	zassert_ok(res, "Error deleting file 2 [%d]\n", res);
101 	res = fs_close(&filep);
102 	zassert_ok(res, "Error closing file 1 [%d]\n", res);
103 	res = fs_unlink(TEST_FILE);
104 	zassert_ok(res, "Error deleting file 1 [%d]\n", res);
105 
106 	return res;
107 }
108 
release_dirty_mutex(void)109 void release_dirty_mutex(void)
110 {
111 	ff_mutex_give(fat_fs.ldrv);
112 }
113 
request_dirty_mutex(void)114 int request_dirty_mutex(void)
115 {
116 	return ff_mutex_take(fat_fs.ldrv);
117 }
118 
tlock_mutex(void * p1,void * p2,void * p3)119 void tlock_mutex(void *p1, void *p2, void *p3)
120 {
121 	TC_PRINT("Mutex thread: Started, locking fs\n");
122 	request_dirty_mutex();
123 	TC_PRINT("Mutex thread: Lock acquired, yield to switch back to try to open file\n");
124 	k_sem_give(&run_non_thread_sem);
125 	k_yield();
126 
127 	TC_PRINT("Mutex thread: Got back to thread, release mutex now and give semaphore to check "
128 		 "if file opened\n");
129 	k_sem_give(&mutex_unlocked_sem);
130 	release_dirty_mutex();
131 
132 	TC_PRINT("Mutex thread: Lock released, thread terminating\n");
133 }
134 
tfile2_access(void * p1,void * p2,void * p3)135 void tfile2_access(void *p1, void *p2, void *p3)
136 {
137 	int res;
138 	ssize_t brw;
139 	struct fs_file_t filep2;
140 
141 	TC_PRINT("File 2 access thread started\n");
142 
143 	/* Init fp for 2nd File for parallel access test */
144 	fs_file_t_init(&filep2);
145 
146 	/* open 2nd file */
147 	TC_PRINT("Open file 2\n");
148 	res = fs_open(&filep2, TEST_FILE2, FS_O_CREATE | FS_O_RDWR);
149 	zassert_ok(res, "Err: File 2 could not be opened [%d]\n", res);
150 	TC_PRINT("File 2 opened 2\n");
151 
152 	/* Verify fs_write() not locked */
153 	brw = fs_write(&filep2, (char *)test_str, strlen(test_str));
154 	if (brw < 0) {
155 		TC_PRINT("Failed writing to file [%zd]\n", brw);
156 		fs_close(&filep2);
157 		return;
158 	}
159 
160 	if (brw < strlen(test_str)) {
161 		TC_PRINT("Unable to complete write. Volume full.\n");
162 		TC_PRINT("Number of bytes written: [%zd]\n", brw);
163 		fs_close(&filep2);
164 		return;
165 	}
166 
167 	/* Close file and switch back to test context*/
168 	res = fs_close(&filep2);
169 	zassert_ok(res, "Error closing file [%d]\n", res);
170 
171 	TC_PRINT("File 2 access thread successfully wrote to file 2\n");
172 }
173 
test_fat_file_reentrant(void)174 void test_fat_file_reentrant(void)
175 {
176 	zassert_true(test_reentrant_access() == TC_PASS, NULL);
177 	zassert_true(test_reentrant_parallel_file_access() == TC_PASS, NULL);
178 }
179 #endif  /* CONFIG_FS_FATFS_REENTRANT */
180