blob: ca2bdafc6d264df93f3d6bdf8df79eb6c7be7fd2 [file] [log] [blame]
/*
* 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/zephyr.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);