Linux Audio

Check our new training course

Loading...
/*
 * Copyright (c) 2016 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include <zephyr/ztest.h>

#define TIMEOUT 500
#define STACK_SIZE (512 + CONFIG_TEST_EXTRA_STACK_SIZE)
#define THREAD_HIGH_PRIORITY 1
#define THREAD_MID_PRIORITY 3
#define THREAD_LOW_PRIORITY 5

/* use to pass case type to threads */
static ZTEST_DMEM int case_type;
static ZTEST_DMEM int thread_ret = TC_FAIL;

/**TESTPOINT: init via K_MUTEX_DEFINE*/
K_MUTEX_DEFINE(kmutex);
static struct k_mutex mutex;

static K_THREAD_STACK_DEFINE(tstack, STACK_SIZE);
static K_THREAD_STACK_DEFINE(tstack2, STACK_SIZE);
static K_THREAD_STACK_DEFINE(tstack3, STACK_SIZE);
static struct k_thread tdata;
static struct k_thread tdata2;
static struct k_thread tdata3;

static void tThread_entry_lock_forever(void *p1, void *p2, void *p3)
{
	zassert_false(k_mutex_lock((struct k_mutex *)p1, K_FOREVER) == 0,
		      "access locked resource from spawn thread");
	/* should not hit here */
}

static void tThread_entry_lock_no_wait(void *p1, void *p2, void *p3)
{
	zassert_true(k_mutex_lock((struct k_mutex *)p1, K_NO_WAIT) != 0);
	TC_PRINT("bypass locked resource from spawn thread\n");
}

static void tThread_entry_lock_timeout_fail(void *p1, void *p2, void *p3)
{
	zassert_true(k_mutex_lock((struct k_mutex *)p1,
				  K_MSEC(TIMEOUT - 100)) != 0, NULL);
	TC_PRINT("bypass locked resource from spawn thread\n");
}

static void tThread_entry_lock_timeout_pass(void *p1, void *p2, void *p3)
{
	zassert_true(k_mutex_lock((struct k_mutex *)p1,
				  K_MSEC(TIMEOUT + 100)) == 0, NULL);
	TC_PRINT("access resource from spawn thread\n");
	k_mutex_unlock((struct k_mutex *)p1);
}

static void tmutex_test_lock(struct k_mutex *pmutex,
			     void (*entry_fn)(void *, void *, void *))
{
	k_mutex_init(pmutex);
	k_thread_create(&tdata, tstack, STACK_SIZE,
			entry_fn, pmutex, NULL, NULL,
			K_PRIO_PREEMPT(0),
			K_USER | K_INHERIT_PERMS, K_NO_WAIT);
	zassert_true(k_mutex_lock(pmutex, K_FOREVER) == 0);
	TC_PRINT("access resource from main thread\n");

	/* wait for spawn thread to take action */
	k_msleep(TIMEOUT);
}

static void tmutex_test_lock_timeout(struct k_mutex *pmutex,
				     void (*entry_fn)(void *, void *, void *))
{
	/**TESTPOINT: test k_mutex_init mutex*/
	k_mutex_init(pmutex);
	k_thread_create(&tdata, tstack, STACK_SIZE,
			entry_fn, pmutex, NULL, NULL,
			K_PRIO_PREEMPT(0),
			K_USER | K_INHERIT_PERMS, K_NO_WAIT);
	zassert_true(k_mutex_lock(pmutex, K_FOREVER) == 0);
	TC_PRINT("access resource from main thread\n");

	/* wait for spawn thread to take action */
	k_msleep(TIMEOUT);
	k_mutex_unlock(pmutex);
	k_msleep(TIMEOUT);

}

static void tmutex_test_lock_unlock(struct k_mutex *pmutex)
{
	k_mutex_init(pmutex);
	zassert_true(k_mutex_lock(pmutex, K_FOREVER) == 0,
		     "fail to lock K_FOREVER");
	k_mutex_unlock(pmutex);
	zassert_true(k_mutex_lock(pmutex, K_NO_WAIT) == 0,
		     "fail to lock K_NO_WAIT");
	k_mutex_unlock(pmutex);
	zassert_true(k_mutex_lock(pmutex, K_MSEC(TIMEOUT)) == 0,
		     "fail to lock TIMEOUT");
	k_mutex_unlock(pmutex);
}

static void tThread_T1_priority_inheritance(void *p1, void *p2, void *p3)
{
	/* t1 will get mutex first */
	zassert_true(k_mutex_lock((struct k_mutex *)p1, K_FOREVER) == 0,
		      "access locked resource from spawn thread T1");

	/* record its original priority */
	int priority_origin = k_thread_priority_get((k_tid_t)p2);

	/* wait for a time period to see if priority inheritance happened */
	k_sleep(K_MSEC(500));

	int priority = k_thread_priority_get((k_tid_t)p2);

	if (case_type == 1) {
		zassert_equal(priority, THREAD_HIGH_PRIORITY,
			"priority inheritance not happened!");

		k_mutex_unlock((struct k_mutex *)p1);

		/* check if priority set back to original one */
		priority = k_thread_priority_get((k_tid_t)p2);

		zassert_equal(priority, priority_origin,
			"priority inheritance adjust back not happened!");
	} else if (case_type == 2) {
		zassert_equal(priority, priority_origin,
			"priority inheritance should not be happened!");

		/* wait for t2 timeout to get mutex*/
		k_sleep(K_MSEC(TIMEOUT));

		k_mutex_unlock((struct k_mutex *)p1);
	} else if (case_type == 3) {
		zassert_equal(priority, THREAD_HIGH_PRIORITY,
			"priority inheritance not happened!");

		/* wait for t2 timeout to get mutex*/
		k_sleep(K_MSEC(TIMEOUT));

		k_mutex_unlock((struct k_mutex *)p1);
	} else {
		zassert_true(0, "should not be here!");
	}
}

static void tThread_T2_priority_inheritance(void *p1, void *p2, void *p3)
{
	if (case_type == 1) {
		zassert_true(k_mutex_lock((struct k_mutex *)p1, K_FOREVER) == 0,
		      "access locked resource from spawn thread T2");

		k_mutex_unlock((struct k_mutex *)p1);
	} else if (case_type == 2 || case_type == 3) {
		zassert_false(k_mutex_lock((struct k_mutex *)p1,
				K_MSEC(100)) == 0,
				"T2 should not get the resource");
	} else {
		zassert_true(0, "should not be here!");
	}
}

static void tThread_lock_with_time_period(void *p1, void *p2, void *p3)
{
	zassert_true(k_mutex_lock((struct k_mutex *)p1, K_FOREVER) == 0,
		      "access locked resource from spawn thread");

	/* This thread will hold mutex for 600 ms, then release it */
	k_sleep(K_MSEC(TIMEOUT + 100));

	k_mutex_unlock((struct k_mutex *)p1);
}

static void tThread_waiter(void *p1, void *p2, void *p3)
{
	/* This thread participates in recursive locking tests */
	/* Wait for mutex to be released */
	zassert_true(k_mutex_lock((struct k_mutex *)p1, K_FOREVER) == 0,
			"Failed to get the test_mutex");

	/* keep the next waiter waiting for a while */
	thread_ret = TC_PASS;
	k_mutex_unlock((struct k_mutex *)p1);
}

/*test cases*/
ZTEST_USER(mutex_api_1cpu, test_mutex_reent_lock_forever)
{
	/**TESTPOINT: test k_mutex_init mutex*/
	k_mutex_init(&mutex);
	tmutex_test_lock(&mutex, tThread_entry_lock_forever);
	k_thread_abort(&tdata);

	/**TESTPOINT: test K_MUTEX_DEFINE mutex*/
	tmutex_test_lock(&kmutex, tThread_entry_lock_forever);
	k_thread_abort(&tdata);
}

ZTEST_USER(mutex_api, test_mutex_reent_lock_no_wait)
{
	/**TESTPOINT: test k_mutex_init mutex*/
	tmutex_test_lock(&mutex, tThread_entry_lock_no_wait);

	/**TESTPOINT: test K_MUTEX_DEFINE mutex*/
	tmutex_test_lock(&kmutex, tThread_entry_lock_no_wait);
}

ZTEST_USER(mutex_api, test_mutex_reent_lock_timeout_fail)
{
	/**TESTPOINT: test k_mutex_init mutex*/
	tmutex_test_lock_timeout(&mutex, tThread_entry_lock_timeout_fail);

	/**TESTPOINT: test K_MUTEX_DEFINE mutex*/
	tmutex_test_lock_timeout(&kmutex, tThread_entry_lock_no_wait);
}

ZTEST_USER(mutex_api_1cpu, test_mutex_reent_lock_timeout_pass)
{
	/**TESTPOINT: test k_mutex_init mutex*/
	tmutex_test_lock_timeout(&mutex, tThread_entry_lock_timeout_pass);

	/**TESTPOINT: test K_MUTEX_DEFINE mutex*/
	tmutex_test_lock_timeout(&kmutex, tThread_entry_lock_no_wait);
}

ZTEST_USER(mutex_api_1cpu, test_mutex_lock_unlock)
{
	/**TESTPOINT: test k_mutex_init mutex*/
	tmutex_test_lock_unlock(&mutex);

	/**TESTPOINT: test K_MUTEX_DEFINE mutex*/
	tmutex_test_lock_unlock(&kmutex);
}

/**
 * @brief Test recursive mutex
 * @details To verify that getting a lock of a mutex already locked will
 * succeed and waiters will be unblocked only when the number of locks
 * reaches zero.
 * @ingroup kernel_mutex_tests
 */
ZTEST_USER(mutex_api, test_mutex_recursive)
{
	k_mutex_init(&mutex);

	/**TESTPOINT: when mutex has no owner, we cannot unlock it */
	zassert_true(k_mutex_unlock(&mutex) == -EINVAL,
			"fail: mutex has no owner");

	zassert_true(k_mutex_lock(&mutex, K_NO_WAIT) == 0,
			"Failed to lock mutex");

	/**TESTPOINT: lock the mutex recursively */
	zassert_true(k_mutex_lock(&mutex, K_NO_WAIT) == 0,
		"Failed to recursively lock mutex");

	thread_ret = TC_FAIL;
	/* Spawn a waiter thread */
	k_thread_create(&tdata3, tstack3, STACK_SIZE,
			(k_thread_entry_t)tThread_waiter, &mutex, NULL, NULL,
			K_PRIO_PREEMPT(12),
			K_USER | K_INHERIT_PERMS, K_NO_WAIT);

	zassert_true(thread_ret == TC_FAIL,
		"waiter thread should block on the recursively locked mutex");

	zassert_true(k_mutex_unlock(&mutex) == 0, "fail to unlock");

	/**TESTPOINT: unlock the mutex recursively */
	zassert_true(thread_ret == TC_FAIL,
		"waiter thread should still block on the locked mutex");

	zassert_true(k_mutex_unlock(&mutex) == 0, "fail to unlock");

	/* Give thread_waiter a chance to get the mutex */
	k_sleep(K_MSEC(1));

	/**TESTPOINT: waiter thread got the mutex */
	zassert_true(thread_ret == TC_PASS,
			"waiter thread can't take the mutex");
}

/**
 * @brief Test mutex's priority inheritance mechanism
 * @details To verify mutex provide priority inheritance to prevent priority
 * inversion, and there are 3 cases need to run.
 * The thread T1 hold the mutex first and cases list as below:
 * - case 1. When priority T2 > T1, priority inheritance happened.
 * - case 2. When priority T1 > T2, priority inheritance won't happened.
 * - case 3. When priority T2 > T3 > T1, priority inheritance happened but T2
 *   wait for timeout and T3 got the mutex.
 * @ingroup kernel_mutex_tests
 */
ZTEST_USER(mutex_api_1cpu, test_mutex_priority_inheritance)
{
	/**TESTPOINT: run test case 1, given priority T1 < T2 */
	k_mutex_init(&mutex);

	/* we told thread which case runs now */
	case_type = 1;

	/* spawn a lower priority thread t1 for holding the mutex */
	k_thread_create(&tdata, tstack, STACK_SIZE,
		(k_thread_entry_t)tThread_T1_priority_inheritance,
			&mutex, &tdata, NULL,
			K_PRIO_PREEMPT(THREAD_LOW_PRIORITY),
			K_USER | K_INHERIT_PERMS, K_NO_WAIT);

	/* wait for spawn thread t1 to take action */
	k_msleep(TIMEOUT);

	/**TESTPOINT: The current thread does not own the mutex.*/
	zassert_true(k_mutex_unlock(&mutex) == -EPERM,
			"fail: current thread does not own the mutex");

	/* spawn a higher priority thread t2 for holding the mutex */
	k_thread_create(&tdata2, tstack2, STACK_SIZE,
		(k_thread_entry_t)tThread_T2_priority_inheritance,
			&mutex, &tdata2, NULL,
			K_PRIO_PREEMPT(THREAD_HIGH_PRIORITY),
			K_USER | K_INHERIT_PERMS, K_NO_WAIT);

	/* wait for spawn thread t2 to take action */
	k_msleep(TIMEOUT+1000);

	/**TESTPOINT: run test case 2, given priority T1 > T2, this means
	 * priority inheritance won't happen.
	 */
	k_mutex_init(&mutex);
	case_type = 2;

	/* spawn a lower priority thread t1 for holding the mutex */
	k_thread_create(&tdata, tstack, STACK_SIZE,
		(k_thread_entry_t)tThread_T1_priority_inheritance,
			&mutex, &tdata, NULL,
			K_PRIO_PREEMPT(THREAD_HIGH_PRIORITY),
			K_USER | K_INHERIT_PERMS, K_NO_WAIT);

	/* wait for spawn thread t1 to take action */
	k_msleep(TIMEOUT);

	/* spawn a higher priority thread t2 for holding the mutex */
	k_thread_create(&tdata2, tstack2, STACK_SIZE,
		(k_thread_entry_t)tThread_T2_priority_inheritance,
			&mutex, &tdata2, NULL,
			K_PRIO_PREEMPT(THREAD_LOW_PRIORITY),
			K_USER | K_INHERIT_PERMS, K_NO_WAIT);

	/* wait for spawn thread t2 to take action */
	k_msleep(TIMEOUT+1000);

	/**TESTPOINT: run test case 3, given priority T1 < T3 < T2, but t2 do
	 * not get mutex due to timeout.
	 */
	k_mutex_init(&mutex);
	case_type = 3;

	/* spawn a lower priority thread t1 for holding the mutex */
	k_thread_create(&tdata, tstack, STACK_SIZE,
		(k_thread_entry_t)tThread_T1_priority_inheritance,
			&mutex, &tdata, NULL,
			K_PRIO_PREEMPT(THREAD_LOW_PRIORITY),
			K_USER | K_INHERIT_PERMS, K_NO_WAIT);

	/* wait for spawn thread t1 to take action */
	k_msleep(TIMEOUT);

	/* spawn a higher priority thread t2 for holding the mutex */
	k_thread_create(&tdata2, tstack2, STACK_SIZE,
		(k_thread_entry_t)tThread_T2_priority_inheritance,
			&mutex, &tdata2, NULL,
			K_PRIO_PREEMPT(THREAD_HIGH_PRIORITY),
			K_USER | K_INHERIT_PERMS, K_NO_WAIT);

	/* spawn a higher priority thread t3 for holding the mutex */
	k_thread_create(&tdata3, tstack3, STACK_SIZE,
		(k_thread_entry_t)tThread_lock_with_time_period,
			&mutex, &tdata3, NULL,
			K_PRIO_PREEMPT(THREAD_MID_PRIORITY),
			K_USER | K_INHERIT_PERMS, K_NO_WAIT);

	/* wait for spawn thread t2 and t3 to take action */
	k_msleep(TIMEOUT+1000);
}

static void *mutex_api_tests_setup(void)
{
#ifdef CONFIG_USERSPACE
	k_thread_access_grant(k_current_get(), &tdata, &tstack, &tdata2,
				&tstack2, &tdata3, &tstack3, &kmutex,
				&mutex);
#endif
	return NULL;
}

ZTEST_SUITE(mutex_api, NULL, mutex_api_tests_setup, NULL, NULL, NULL);
ZTEST_SUITE(mutex_api_1cpu, NULL, mutex_api_tests_setup,
			ztest_simple_1cpu_before, ztest_simple_1cpu_after, NULL);