1 /*
2  * Copyright (c) 2021 Stephanos Ioannidis <root@stephanos.io>
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /*
8  * @file Newlib thread-safety lock test
9  *
10  * This file contains a set of tests to verify that the newlib retargetable
11  * locking interface is functional and the internal newlib locks function as
12  * intended.
13  */
14 
15 #include <zephyr.h>
16 #include <ztest.h>
17 
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <malloc.h>
21 #include <envlock.h>
22 
23 #define STACK_SIZE	(512 + CONFIG_TEST_EXTRA_STACKSIZE)
24 
25 #ifdef CONFIG_USERSPACE
26 #define THREAD_OPT	(K_USER | K_INHERIT_PERMS)
27 #else
28 #define THREAD_OPT	(0)
29 #endif /* CONFIG_USERSPACE */
30 
31 static struct k_thread tdata;
32 static K_THREAD_STACK_DEFINE(tstack, STACK_SIZE);
33 
34 /* Newlib internal lock functions */
35 extern void __sfp_lock_acquire(void);
36 extern void __sfp_lock_release(void);
37 extern void __sinit_lock_acquire(void);
38 extern void __sinit_lock_release(void);
39 extern void __tz_lock(void);
40 extern void __tz_unlock(void);
41 
42 /* Static locks */
43 extern struct k_mutex __lock___sinit_recursive_mutex;
44 extern struct k_mutex __lock___sfp_recursive_mutex;
45 extern struct k_mutex __lock___atexit_recursive_mutex;
46 extern struct k_mutex __lock___malloc_recursive_mutex;
47 extern struct k_mutex __lock___env_recursive_mutex;
48 extern struct k_sem __lock___at_quick_exit_mutex;
49 extern struct k_sem __lock___tz_mutex;
50 extern struct k_sem __lock___dd_hash_mutex;
51 extern struct k_sem __lock___arc4random_mutex;
52 
53 /**
54  * @brief Test retargetable locking non-recursive (semaphore) interface
55  *
56  * This test verifies that a non-recursive lock (semaphore) can be dynamically
57  * created, acquired, released and closed through the retargetable locking
58  * interface.
59  */
test_retargetable_lock_sem(void)60 static void test_retargetable_lock_sem(void)
61 {
62 	_LOCK_T lock = NULL;
63 
64 	/* Dynamically allocate and initialise a new lock */
65 	__retarget_lock_init(&lock);
66 	zassert_not_null(lock, "non-recursive lock init failed");
67 
68 	/* Acquire lock and verify acquisition */
69 	__retarget_lock_acquire(lock);
70 	zassert_equal(__retarget_lock_try_acquire(lock), 0,
71 		      "non-recursive lock acquisition failed");
72 
73 	/* Release lock and verify release */
74 	__retarget_lock_release(lock);
75 	zassert_not_equal(__retarget_lock_try_acquire(lock), 0,
76 			  "non-recursive lock release failed");
77 
78 	/* Close and deallocate lock */
79 	__retarget_lock_close(lock);
80 }
81 
retargetable_lock_mutex_thread_acq(void * p1,void * p2,void * p3)82 static void retargetable_lock_mutex_thread_acq(void *p1, void *p2, void *p3)
83 {
84 	_LOCK_T lock = p1;
85 	int ret;
86 
87 	/*
88 	 * Attempt to lock the recursive lock from child thread and verify
89 	 * that it fails.
90 	 */
91 	ret = __retarget_lock_try_acquire_recursive(lock);
92 	zassert_equal(ret, 0, "recursive lock acquisition failed");
93 }
94 
retargetable_lock_mutex_thread_rel(void * p1,void * p2,void * p3)95 static void retargetable_lock_mutex_thread_rel(void *p1, void *p2, void *p3)
96 {
97 	_LOCK_T lock = p1;
98 	int ret;
99 
100 	/*
101 	 * Attempt to lock the recursive lock from child thread and verify
102 	 * that it fails.
103 	 */
104 	ret = __retarget_lock_try_acquire_recursive(lock);
105 	zassert_not_equal(ret, 0, "recursive lock release failed");
106 }
107 
108 /**
109  * @brief Test retargetable locking recursive (mutex) interface
110  *
111  * This test verifies that a recursive lock (mutex) can be dynamically created,
112  * acquired, released, and closed through the retargetable locking interface.
113  */
test_retargetable_lock_mutex(void)114 static void test_retargetable_lock_mutex(void)
115 {
116 	_LOCK_T lock = NULL;
117 	k_tid_t tid;
118 
119 	/* Dynamically allocate and initialise a new lock */
120 	__retarget_lock_init_recursive(&lock);
121 	zassert_not_null(lock, "recursive lock init failed");
122 
123 	/* Acquire lock from parent thread */
124 	__retarget_lock_acquire_recursive(lock);
125 
126 	/* Spawn a lock acquisition check thread and wait for exit */
127 	tid = k_thread_create(&tdata, tstack, STACK_SIZE,
128 			      retargetable_lock_mutex_thread_acq, lock,
129 			      NULL, NULL, K_PRIO_PREEMPT(0), THREAD_OPT,
130 			      K_NO_WAIT);
131 
132 	k_thread_join(tid, K_FOREVER);
133 
134 	/* Release lock from parent thread */
135 	__retarget_lock_release_recursive(lock);
136 
137 	/* Spawn a lock release check thread and wait for exit */
138 	tid = k_thread_create(&tdata, tstack, STACK_SIZE,
139 			      retargetable_lock_mutex_thread_rel, lock,
140 			      NULL, NULL, K_PRIO_PREEMPT(0), THREAD_OPT,
141 			      K_NO_WAIT);
142 
143 	k_thread_join(tid, K_FOREVER);
144 
145 	/* Close and deallocate lock */
146 	__retarget_lock_close_recursive(lock);
147 }
148 
sinit_lock_thread_acq(void * p1,void * p2,void * p3)149 static void sinit_lock_thread_acq(void *p1, void *p2, void *p3)
150 {
151 	int ret;
152 
153 	/*
154 	 * Attempt to lock the sinit mutex from child thread using
155 	 * retargetable locking interface. This operation should fail if the
156 	 * __sinit_lock_acquire() implementation internally uses the
157 	 * retargetable locking interface.
158 	 */
159 	ret = __retarget_lock_try_acquire_recursive(
160 			(_LOCK_T)&__lock___sinit_recursive_mutex);
161 
162 	zassert_equal(ret, 0, "__sinit_lock_acquire() is not using "
163 			      "retargetable locking interface");
164 }
165 
sinit_lock_thread_rel(void * p1,void * p2,void * p3)166 static void sinit_lock_thread_rel(void *p1, void *p2, void *p3)
167 {
168 	int ret;
169 
170 	/*
171 	 * Attempt to lock the sinit mutex from child thread using
172 	 * retargetable locking interface. This operation should succeed if the
173 	 * __sinit_lock_release() implementation internally uses the
174 	 * retargetable locking interface.
175 	 */
176 	ret = __retarget_lock_try_acquire_recursive(
177 			(_LOCK_T)&__lock___sinit_recursive_mutex);
178 
179 	zassert_not_equal(ret, 0, "__sinit_lock_release() is not using "
180 				  "retargetable locking interface");
181 
182 	/* Release sinit lock */
183 	__retarget_lock_release_recursive(
184 		(_LOCK_T)&__lock___sinit_recursive_mutex);
185 }
186 
187 /**
188  * @brief Test sinit lock functions
189  *
190  * This test calls the __sinit_lock_acquire() and __sinit_lock_release()
191  * functions to verify that sinit lock is functional and its implementation
192  * is provided by the retargetable locking interface.
193  */
test_sinit_lock(void)194 static void test_sinit_lock(void)
195 {
196 	k_tid_t tid;
197 
198 	/* Lock the sinit mutex from parent thread */
199 	__sinit_lock_acquire();
200 
201 	/* Spawn a lock check thread and wait for exit */
202 	tid = k_thread_create(&tdata, tstack, STACK_SIZE,
203 			      sinit_lock_thread_acq, NULL, NULL, NULL,
204 			      K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT);
205 
206 	k_thread_join(tid, K_FOREVER);
207 
208 	/* Unlock the sinit mutex from parent thread */
209 	__sinit_lock_release();
210 
211 	/* Spawn an unlock check thread and wait for exit */
212 	tid = k_thread_create(&tdata, tstack, STACK_SIZE,
213 			      sinit_lock_thread_rel, NULL, NULL, NULL,
214 			      K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT);
215 
216 	k_thread_join(tid, K_FOREVER);
217 }
218 
sfp_lock_thread_acq(void * p1,void * p2,void * p3)219 static void sfp_lock_thread_acq(void *p1, void *p2, void *p3)
220 {
221 	int ret;
222 
223 	/*
224 	 * Attempt to lock the sfp mutex from child thread using retargetable
225 	 * locking interface. This operation should fail if the
226 	 * __sfp_lock_acquire() implementation internally uses the retargetable
227 	 * locking interface.
228 	 */
229 	ret = __retarget_lock_try_acquire_recursive(
230 			(_LOCK_T)&__lock___sfp_recursive_mutex);
231 
232 	zassert_equal(ret, 0, "__sfp_lock_acquire() is not using "
233 			      "retargetable locking interface");
234 }
235 
sfp_lock_thread_rel(void * p1,void * p2,void * p3)236 static void sfp_lock_thread_rel(void *p1, void *p2, void *p3)
237 {
238 	int ret;
239 
240 	/*
241 	 * Attempt to lock the sfp mutex from child thread using retargetable
242 	 * locking interface. This operation should succeed if the
243 	 * __sfp_lock_release() implementation internally uses the retargetable
244 	 * locking interface.
245 	 */
246 	ret = __retarget_lock_try_acquire_recursive(
247 			(_LOCK_T)&__lock___sfp_recursive_mutex);
248 
249 	zassert_not_equal(ret, 0, "__sfp_lock_release() is not using "
250 				  "retargetable locking interface");
251 
252 	/* Release sfp lock */
253 	__retarget_lock_release_recursive(
254 		(_LOCK_T)&__lock___sfp_recursive_mutex);
255 }
256 
257 /**
258  * @brief Test sfp lock functions
259  *
260  * This test calls the __sfp_lock_acquire() and __sfp_lock_release() functions
261  * to verify that sfp lock is functional and its implementation is provided by
262  * the retargetable locking interface.
263  */
test_sfp_lock(void)264 static void test_sfp_lock(void)
265 {
266 	k_tid_t tid;
267 
268 	/* Lock the sfp mutex from parent thread */
269 	__sfp_lock_acquire();
270 
271 	/* Spawn a lock check thread and wait for exit */
272 	tid = k_thread_create(&tdata, tstack, STACK_SIZE,
273 			      sfp_lock_thread_acq, NULL, NULL, NULL,
274 			      K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT);
275 
276 	k_thread_join(tid, K_FOREVER);
277 
278 	/* Unlock the sfp mutex from parent thread */
279 	__sfp_lock_release();
280 
281 	/* Spawn an unlock check thread and wait for exit */
282 	tid = k_thread_create(&tdata, tstack, STACK_SIZE,
283 			      sfp_lock_thread_rel, NULL, NULL, NULL,
284 			      K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT);
285 
286 	k_thread_join(tid, K_FOREVER);
287 }
288 
malloc_lock_thread_lock(void * p1,void * p2,void * p3)289 static void malloc_lock_thread_lock(void *p1, void *p2, void *p3)
290 {
291 	int ret;
292 
293 	/*
294 	 * Attempt to lock the malloc mutex from child thread using
295 	 * retargetable locking interface. This operation should fail if the
296 	 * __malloc_lock() implementation internally uses the retargetable
297 	 * locking interface.
298 	 */
299 	ret = __retarget_lock_try_acquire_recursive(
300 			(_LOCK_T)&__lock___malloc_recursive_mutex);
301 
302 	zassert_equal(ret, 0, "__malloc_lock() is not using retargetable "
303 			      "locking interface");
304 }
305 
malloc_lock_thread_unlock(void * p1,void * p2,void * p3)306 static void malloc_lock_thread_unlock(void *p1, void *p2, void *p3)
307 {
308 	int ret;
309 
310 	/*
311 	 * Attempt to lock the malloc mutex from child thread using
312 	 * retargetable locking interface. This operation should succeed if the
313 	 * __malloc_unlock() implementation internally uses the retargetable
314 	 * locking interface.
315 	 */
316 	ret = __retarget_lock_try_acquire_recursive(
317 			(_LOCK_T)&__lock___malloc_recursive_mutex);
318 
319 	zassert_not_equal(ret, 0, "__malloc_unlock() is not using "
320 				  "retargetable locking interface");
321 
322 	/* Release malloc lock */
323 	__retarget_lock_release_recursive(
324 		(_LOCK_T)&__lock___malloc_recursive_mutex);
325 }
326 
327 /**
328  * @brief Test malloc lock functions
329  *
330  * This test calls the __malloc_lock() and __malloc_unlock() functions to
331  * verify that malloc lock is functional and its implementation is provided by
332  * the retargetable locking interface.
333  */
test_malloc_lock(void)334 static void test_malloc_lock(void)
335 {
336 	k_tid_t tid;
337 
338 	/* Lock the malloc mutex from parent thread */
339 	__malloc_lock(_REENT);
340 
341 	/* Spawn a lock check thread and wait for exit */
342 	tid = k_thread_create(&tdata, tstack, STACK_SIZE,
343 			      malloc_lock_thread_lock, NULL, NULL, NULL,
344 			      K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT);
345 
346 	k_thread_join(tid, K_FOREVER);
347 
348 	/* Unlock the malloc mutex from parent thread */
349 	__malloc_unlock(_REENT);
350 
351 	/* Spawn an unlock check thread and wait for exit */
352 	tid = k_thread_create(&tdata, tstack, STACK_SIZE,
353 			      malloc_lock_thread_unlock, NULL, NULL, NULL,
354 			      K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT);
355 
356 	k_thread_join(tid, K_FOREVER);
357 }
358 
env_lock_thread_lock(void * p1,void * p2,void * p3)359 static void env_lock_thread_lock(void *p1, void *p2, void *p3)
360 {
361 	int ret;
362 
363 	/*
364 	 * Attempt to lock the env mutex from child thread using
365 	 * retargetable locking interface. This operation should fail if the
366 	 * __env_lock() implementation internally uses the retargetable
367 	 * locking interface.
368 	 */
369 	ret = __retarget_lock_try_acquire_recursive(
370 			(_LOCK_T)&__lock___env_recursive_mutex);
371 
372 	zassert_equal(ret, 0, "__env_lock() is not using retargetable "
373 			      "locking interface");
374 }
375 
env_lock_thread_unlock(void * p1,void * p2,void * p3)376 static void env_lock_thread_unlock(void *p1, void *p2, void *p3)
377 {
378 	int ret;
379 
380 	/*
381 	 * Attempt to lock the env mutex from child thread using
382 	 * retargetable locking interface. This operation should succeed if the
383 	 * __env_unlock() implementation internally uses the retargetable
384 	 * locking interface.
385 	 */
386 	ret = __retarget_lock_try_acquire_recursive(
387 			(_LOCK_T)&__lock___env_recursive_mutex);
388 
389 	zassert_not_equal(ret, 0, "__env_unlock() is not using "
390 				  "retargetable locking interface");
391 
392 	/* Release env lock */
393 	__retarget_lock_release_recursive(
394 		(_LOCK_T)&__lock___env_recursive_mutex);
395 }
396 
397 /**
398  * @brief Test env lock functions
399  *
400  * This test calls the __env_lock() and __env_unlock() functions to verify
401  * that env lock is functional and its implementation is provided by the
402  * retargetable locking interface.
403  */
test_env_lock(void)404 static void test_env_lock(void)
405 {
406 	k_tid_t tid;
407 
408 	/* Lock the env mutex from parent thread */
409 	__env_lock(_REENT);
410 
411 	/* Spawn a lock check thread and wait for exit */
412 	tid = k_thread_create(&tdata, tstack, STACK_SIZE,
413 			      env_lock_thread_lock, NULL, NULL, NULL,
414 			      K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT);
415 
416 	k_thread_join(tid, K_FOREVER);
417 
418 	/* Unlock the env mutex from parent thread */
419 	__env_unlock(_REENT);
420 
421 	/* Spawn an unlock check thread and wait for exit */
422 	tid = k_thread_create(&tdata, tstack, STACK_SIZE,
423 			      env_lock_thread_unlock, NULL, NULL, NULL,
424 			      K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT);
425 
426 	k_thread_join(tid, K_FOREVER);
427 }
428 
429 /**
430  * @brief Test tz lock functions
431  *
432  * This test calls the __tz_lock() and __tz_unlock() functions to verify that
433  * tz lock is functional and its implementation is provided by the retargetable
434  * locking interface.
435  */
test_tz_lock(void)436 static void test_tz_lock(void)
437 {
438 	/* Lock the tz semaphore */
439 	__tz_lock();
440 
441 	/* Attempt to acquire lock and verify failure */
442 	zassert_equal(
443 		__retarget_lock_try_acquire((_LOCK_T)&__lock___tz_mutex), 0,
444 		"__tz_lock() is not using retargetable locking interface");
445 
446 	/* Unlock the tz semaphore */
447 	__tz_unlock();
448 
449 	/* Attempt to acquire lock and verify success */
450 	zassert_not_equal(
451 		__retarget_lock_try_acquire((_LOCK_T)&__lock___tz_mutex), 0,
452 		"__tz_unlock() is not using retargetable locking interface");
453 
454 	/* Clean up */
455 	__retarget_lock_release((_LOCK_T)&__lock___tz_mutex);
456 }
457 
test_newlib_thread_safety_locks(void)458 void test_newlib_thread_safety_locks(void)
459 {
460 #ifdef CONFIG_USERSPACE
461 	k_thread_access_grant(k_current_get(), &tdata, &tstack);
462 #endif /* CONFIG_USERSPACE */
463 
464 	ztest_test_suite(newlib_thread_safety_locks,
465 			 ztest_user_unit_test(test_retargetable_lock_sem),
466 			 ztest_user_unit_test(test_retargetable_lock_mutex),
467 			 ztest_user_unit_test(test_sinit_lock),
468 			 ztest_user_unit_test(test_sfp_lock),
469 			 ztest_user_unit_test(test_malloc_lock),
470 			 ztest_user_unit_test(test_env_lock),
471 			 ztest_user_unit_test(test_tz_lock));
472 
473 	ztest_run_test_suite(newlib_thread_safety_locks);
474 }
475