| /* |
| * Copyright (c) 2021 Stephanos Ioannidis <root@stephanos.io> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /* |
| * @file Newlib thread-safety lock test |
| * |
| * This file contains a set of tests to verify that the newlib retargetable |
| * locking interface is functional and the internal newlib locks function as |
| * intended. |
| */ |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/ztest.h> |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <malloc.h> |
| #include <envlock.h> |
| |
| #define STACK_SIZE (512 + CONFIG_TEST_EXTRA_STACK_SIZE) |
| |
| #ifdef CONFIG_USERSPACE |
| #define THREAD_OPT (K_USER | K_INHERIT_PERMS) |
| #else |
| #define THREAD_OPT (0) |
| #endif /* CONFIG_USERSPACE */ |
| |
| static struct k_thread tdata; |
| static K_THREAD_STACK_DEFINE(tstack, STACK_SIZE); |
| |
| /* Newlib internal lock functions */ |
| extern void __sfp_lock_acquire(void); |
| extern void __sfp_lock_release(void); |
| extern void __sinit_lock_acquire(void); |
| extern void __sinit_lock_release(void); |
| extern void __tz_lock(void); |
| extern void __tz_unlock(void); |
| |
| /* Static locks */ |
| extern struct k_mutex __lock___sinit_recursive_mutex; |
| extern struct k_mutex __lock___sfp_recursive_mutex; |
| extern struct k_mutex __lock___atexit_recursive_mutex; |
| extern struct k_mutex __lock___malloc_recursive_mutex; |
| extern struct k_mutex __lock___env_recursive_mutex; |
| extern struct k_sem __lock___at_quick_exit_mutex; |
| extern struct k_sem __lock___tz_mutex; |
| extern struct k_sem __lock___dd_hash_mutex; |
| extern struct k_sem __lock___arc4random_mutex; |
| |
| /** |
| * @brief Test retargetable locking non-recursive (semaphore) interface |
| * |
| * This test verifies that a non-recursive lock (semaphore) can be dynamically |
| * created, acquired, released and closed through the retargetable locking |
| * interface. |
| */ |
| ZTEST(newlib_thread_safety_locks, test_retargetable_lock_sem) |
| { |
| _LOCK_T lock = NULL; |
| |
| /* Dynamically allocate and initialise a new lock */ |
| __retarget_lock_init(&lock); |
| zassert_not_null(lock, "non-recursive lock init failed"); |
| |
| /* Acquire lock and verify acquisition */ |
| __retarget_lock_acquire(lock); |
| zassert_equal(__retarget_lock_try_acquire(lock), 0, |
| "non-recursive lock acquisition failed"); |
| |
| /* Release lock and verify release */ |
| __retarget_lock_release(lock); |
| zassert_not_equal(__retarget_lock_try_acquire(lock), 0, |
| "non-recursive lock release failed"); |
| |
| /* Close and deallocate lock */ |
| __retarget_lock_close(lock); |
| } |
| |
| static void retargetable_lock_mutex_thread_acq(void *p1, void *p2, void *p3) |
| { |
| _LOCK_T lock = p1; |
| int ret; |
| |
| /* |
| * Attempt to lock the recursive lock from child thread and verify |
| * that it fails. |
| */ |
| ret = __retarget_lock_try_acquire_recursive(lock); |
| zassert_equal(ret, 0, "recursive lock acquisition failed"); |
| } |
| |
| static void retargetable_lock_mutex_thread_rel(void *p1, void *p2, void *p3) |
| { |
| _LOCK_T lock = p1; |
| int ret; |
| |
| /* |
| * Attempt to lock the recursive lock from child thread and verify |
| * that it fails. |
| */ |
| ret = __retarget_lock_try_acquire_recursive(lock); |
| zassert_not_equal(ret, 0, "recursive lock release failed"); |
| } |
| |
| /** |
| * @brief Test retargetable locking recursive (mutex) interface |
| * |
| * This test verifies that a recursive lock (mutex) can be dynamically created, |
| * acquired, released, and closed through the retargetable locking interface. |
| */ |
| ZTEST(newlib_thread_safety_locks, test_retargetable_lock_mutex) |
| { |
| _LOCK_T lock = NULL; |
| k_tid_t tid; |
| |
| /* Dynamically allocate and initialise a new lock */ |
| __retarget_lock_init_recursive(&lock); |
| zassert_not_null(lock, "recursive lock init failed"); |
| |
| /* Acquire lock from parent thread */ |
| __retarget_lock_acquire_recursive(lock); |
| |
| /* Spawn a lock acquisition check thread and wait for exit */ |
| tid = k_thread_create(&tdata, tstack, STACK_SIZE, |
| retargetable_lock_mutex_thread_acq, lock, |
| NULL, NULL, K_PRIO_PREEMPT(0), THREAD_OPT, |
| K_NO_WAIT); |
| |
| k_thread_join(tid, K_FOREVER); |
| |
| /* Release lock from parent thread */ |
| __retarget_lock_release_recursive(lock); |
| |
| /* Spawn a lock release check thread and wait for exit */ |
| tid = k_thread_create(&tdata, tstack, STACK_SIZE, |
| retargetable_lock_mutex_thread_rel, lock, |
| NULL, NULL, K_PRIO_PREEMPT(0), THREAD_OPT, |
| K_NO_WAIT); |
| |
| k_thread_join(tid, K_FOREVER); |
| |
| /* Close and deallocate lock */ |
| __retarget_lock_close_recursive(lock); |
| } |
| |
| static void sinit_lock_thread_acq(void *p1, void *p2, void *p3) |
| { |
| int ret; |
| |
| /* |
| * Attempt to lock the sinit mutex from child thread using |
| * retargetable locking interface. This operation should fail if the |
| * __sinit_lock_acquire() implementation internally uses the |
| * retargetable locking interface. |
| */ |
| ret = __retarget_lock_try_acquire_recursive( |
| (_LOCK_T)&__lock___sinit_recursive_mutex); |
| |
| zassert_equal(ret, 0, "__sinit_lock_acquire() is not using " |
| "retargetable locking interface"); |
| } |
| |
| static void sinit_lock_thread_rel(void *p1, void *p2, void *p3) |
| { |
| int ret; |
| |
| /* |
| * Attempt to lock the sinit mutex from child thread using |
| * retargetable locking interface. This operation should succeed if the |
| * __sinit_lock_release() implementation internally uses the |
| * retargetable locking interface. |
| */ |
| ret = __retarget_lock_try_acquire_recursive( |
| (_LOCK_T)&__lock___sinit_recursive_mutex); |
| |
| zassert_not_equal(ret, 0, "__sinit_lock_release() is not using " |
| "retargetable locking interface"); |
| |
| /* Release sinit lock */ |
| __retarget_lock_release_recursive( |
| (_LOCK_T)&__lock___sinit_recursive_mutex); |
| } |
| |
| /** |
| * @brief Test sinit lock functions |
| * |
| * This test calls the __sinit_lock_acquire() and __sinit_lock_release() |
| * functions to verify that sinit lock is functional and its implementation |
| * is provided by the retargetable locking interface. |
| */ |
| ZTEST(newlib_thread_safety_locks, test_sinit_lock) |
| { |
| k_tid_t tid; |
| |
| /* Lock the sinit mutex from parent thread */ |
| __sinit_lock_acquire(); |
| |
| /* Spawn a lock check thread and wait for exit */ |
| tid = k_thread_create(&tdata, tstack, STACK_SIZE, |
| sinit_lock_thread_acq, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT); |
| |
| k_thread_join(tid, K_FOREVER); |
| |
| /* Unlock the sinit mutex from parent thread */ |
| __sinit_lock_release(); |
| |
| /* Spawn an unlock check thread and wait for exit */ |
| tid = k_thread_create(&tdata, tstack, STACK_SIZE, |
| sinit_lock_thread_rel, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT); |
| |
| k_thread_join(tid, K_FOREVER); |
| } |
| |
| static void sfp_lock_thread_acq(void *p1, void *p2, void *p3) |
| { |
| int ret; |
| |
| /* |
| * Attempt to lock the sfp mutex from child thread using retargetable |
| * locking interface. This operation should fail if the |
| * __sfp_lock_acquire() implementation internally uses the retargetable |
| * locking interface. |
| */ |
| ret = __retarget_lock_try_acquire_recursive( |
| (_LOCK_T)&__lock___sfp_recursive_mutex); |
| |
| zassert_equal(ret, 0, "__sfp_lock_acquire() is not using " |
| "retargetable locking interface"); |
| } |
| |
| static void sfp_lock_thread_rel(void *p1, void *p2, void *p3) |
| { |
| int ret; |
| |
| /* |
| * Attempt to lock the sfp mutex from child thread using retargetable |
| * locking interface. This operation should succeed if the |
| * __sfp_lock_release() implementation internally uses the retargetable |
| * locking interface. |
| */ |
| ret = __retarget_lock_try_acquire_recursive( |
| (_LOCK_T)&__lock___sfp_recursive_mutex); |
| |
| zassert_not_equal(ret, 0, "__sfp_lock_release() is not using " |
| "retargetable locking interface"); |
| |
| /* Release sfp lock */ |
| __retarget_lock_release_recursive( |
| (_LOCK_T)&__lock___sfp_recursive_mutex); |
| } |
| |
| /** |
| * @brief Test sfp lock functions |
| * |
| * This test calls the __sfp_lock_acquire() and __sfp_lock_release() functions |
| * to verify that sfp lock is functional and its implementation is provided by |
| * the retargetable locking interface. |
| */ |
| ZTEST(newlib_thread_safety_locks, test_sfp_lock) |
| { |
| k_tid_t tid; |
| |
| /* Lock the sfp mutex from parent thread */ |
| __sfp_lock_acquire(); |
| |
| /* Spawn a lock check thread and wait for exit */ |
| tid = k_thread_create(&tdata, tstack, STACK_SIZE, |
| sfp_lock_thread_acq, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT); |
| |
| k_thread_join(tid, K_FOREVER); |
| |
| /* Unlock the sfp mutex from parent thread */ |
| __sfp_lock_release(); |
| |
| /* Spawn an unlock check thread and wait for exit */ |
| tid = k_thread_create(&tdata, tstack, STACK_SIZE, |
| sfp_lock_thread_rel, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT); |
| |
| k_thread_join(tid, K_FOREVER); |
| } |
| |
| static void malloc_lock_thread_lock(void *p1, void *p2, void *p3) |
| { |
| int ret; |
| |
| /* |
| * Attempt to lock the malloc mutex from child thread using |
| * retargetable locking interface. This operation should fail if the |
| * __malloc_lock() implementation internally uses the retargetable |
| * locking interface. |
| */ |
| ret = __retarget_lock_try_acquire_recursive( |
| (_LOCK_T)&__lock___malloc_recursive_mutex); |
| |
| zassert_equal(ret, 0, "__malloc_lock() is not using retargetable " |
| "locking interface"); |
| } |
| |
| static void malloc_lock_thread_unlock(void *p1, void *p2, void *p3) |
| { |
| int ret; |
| |
| /* |
| * Attempt to lock the malloc mutex from child thread using |
| * retargetable locking interface. This operation should succeed if the |
| * __malloc_unlock() implementation internally uses the retargetable |
| * locking interface. |
| */ |
| ret = __retarget_lock_try_acquire_recursive( |
| (_LOCK_T)&__lock___malloc_recursive_mutex); |
| |
| zassert_not_equal(ret, 0, "__malloc_unlock() is not using " |
| "retargetable locking interface"); |
| |
| /* Release malloc lock */ |
| __retarget_lock_release_recursive( |
| (_LOCK_T)&__lock___malloc_recursive_mutex); |
| } |
| |
| /** |
| * @brief Test malloc lock functions |
| * |
| * This test calls the __malloc_lock() and __malloc_unlock() functions to |
| * verify that malloc lock is functional and its implementation is provided by |
| * the retargetable locking interface. |
| */ |
| ZTEST(newlib_thread_safety_locks, test_malloc_lock) |
| { |
| k_tid_t tid; |
| |
| /* Lock the malloc mutex from parent thread */ |
| __malloc_lock(_REENT); |
| |
| /* Spawn a lock check thread and wait for exit */ |
| tid = k_thread_create(&tdata, tstack, STACK_SIZE, |
| malloc_lock_thread_lock, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT); |
| |
| k_thread_join(tid, K_FOREVER); |
| |
| /* Unlock the malloc mutex from parent thread */ |
| __malloc_unlock(_REENT); |
| |
| /* Spawn an unlock check thread and wait for exit */ |
| tid = k_thread_create(&tdata, tstack, STACK_SIZE, |
| malloc_lock_thread_unlock, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT); |
| |
| k_thread_join(tid, K_FOREVER); |
| } |
| |
| static void env_lock_thread_lock(void *p1, void *p2, void *p3) |
| { |
| int ret; |
| |
| /* |
| * Attempt to lock the env mutex from child thread using |
| * retargetable locking interface. This operation should fail if the |
| * __env_lock() implementation internally uses the retargetable |
| * locking interface. |
| */ |
| ret = __retarget_lock_try_acquire_recursive( |
| (_LOCK_T)&__lock___env_recursive_mutex); |
| |
| zassert_equal(ret, 0, "__env_lock() is not using retargetable " |
| "locking interface"); |
| } |
| |
| static void env_lock_thread_unlock(void *p1, void *p2, void *p3) |
| { |
| int ret; |
| |
| /* |
| * Attempt to lock the env mutex from child thread using |
| * retargetable locking interface. This operation should succeed if the |
| * __env_unlock() implementation internally uses the retargetable |
| * locking interface. |
| */ |
| ret = __retarget_lock_try_acquire_recursive( |
| (_LOCK_T)&__lock___env_recursive_mutex); |
| |
| zassert_not_equal(ret, 0, "__env_unlock() is not using " |
| "retargetable locking interface"); |
| |
| /* Release env lock */ |
| __retarget_lock_release_recursive( |
| (_LOCK_T)&__lock___env_recursive_mutex); |
| } |
| |
| /** |
| * @brief Test env lock functions |
| * |
| * This test calls the __env_lock() and __env_unlock() functions to verify |
| * that env lock is functional and its implementation is provided by the |
| * retargetable locking interface. |
| */ |
| ZTEST(newlib_thread_safety_locks, test_env_lock) |
| { |
| k_tid_t tid; |
| |
| /* Lock the env mutex from parent thread */ |
| __env_lock(_REENT); |
| |
| /* Spawn a lock check thread and wait for exit */ |
| tid = k_thread_create(&tdata, tstack, STACK_SIZE, |
| env_lock_thread_lock, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT); |
| |
| k_thread_join(tid, K_FOREVER); |
| |
| /* Unlock the env mutex from parent thread */ |
| __env_unlock(_REENT); |
| |
| /* Spawn an unlock check thread and wait for exit */ |
| tid = k_thread_create(&tdata, tstack, STACK_SIZE, |
| env_lock_thread_unlock, NULL, NULL, NULL, |
| K_PRIO_PREEMPT(0), THREAD_OPT, K_NO_WAIT); |
| |
| k_thread_join(tid, K_FOREVER); |
| } |
| |
| /** |
| * @brief Test tz lock functions |
| * |
| * This test calls the __tz_lock() and __tz_unlock() functions to verify that |
| * tz lock is functional and its implementation is provided by the retargetable |
| * locking interface. |
| */ |
| ZTEST(newlib_thread_safety_locks, test_tz_lock) |
| { |
| /* Lock the tz semaphore */ |
| __tz_lock(); |
| |
| /* Attempt to acquire lock and verify failure */ |
| zassert_equal( |
| __retarget_lock_try_acquire((_LOCK_T)&__lock___tz_mutex), 0, |
| "__tz_lock() is not using retargetable locking interface"); |
| |
| /* Unlock the tz semaphore */ |
| __tz_unlock(); |
| |
| /* Attempt to acquire lock and verify success */ |
| zassert_not_equal( |
| __retarget_lock_try_acquire((_LOCK_T)&__lock___tz_mutex), 0, |
| "__tz_unlock() is not using retargetable locking interface"); |
| |
| /* Clean up */ |
| __retarget_lock_release((_LOCK_T)&__lock___tz_mutex); |
| } |
| |
| void *newlib_thread_safety_locks_setup(void) |
| { |
| #ifdef CONFIG_USERSPACE |
| k_thread_access_grant(k_current_get(), &tdata, &tstack); |
| #endif /* CONFIG_USERSPACE */ |
| return NULL; |
| } |
| ZTEST_SUITE(newlib_thread_safety_locks, NULL, newlib_thread_safety_locks_setup, NULL, NULL, NULL); |