blob: 7a10a53f9480daf42ee379ca303c0ec6fc32af51 [file]
/*
* Copyright (c) 2016 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/tc_util.h>
#include <zephyr/ztest.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/sys/util.h>
#include <zephyr/irq_offload.h>
#include <stdbool.h>
#if defined(CONFIG_ASSERT) && defined(CONFIG_DEBUG)
#define THREAD_STACK (512 + CONFIG_TEST_EXTRA_STACK_SIZE)
#else
#define THREAD_STACK (384 + CONFIG_TEST_EXTRA_STACK_SIZE)
#endif
#define TEST_THREAD_PRIORITY -4
#define HELPER_THREAD_PRIORITY -10
#define ONE_SECOND (MSEC_PER_SEC)
#define ONE_SECOND_ALIGNED \
(uint32_t)(k_ticks_to_ms_floor64(k_ms_to_ticks_ceil32(ONE_SECOND) + _TICK_ALIGN))
#if defined(CONFIG_SOC_XILINX_ZYNQMP) || defined(CONFIG_SOC_VERSAL_RPU)
/*
* The Xilinx QEMU, used to emulate the Xilinx ZynqMP and Versal platforms,
* is particularly unstable in terms of timing. The tick margin of at least 5
* is necessary to allow this test to pass with a reasonable repeatability.
*/
#define TICK_MARGIN 5
#else
#define TICK_MARGIN 1
#endif
static struct k_sem test_thread_sem;
static struct k_sem helper_thread_sem;
static struct k_sem task_sem;
static K_THREAD_STACK_DEFINE(test_thread_stack, THREAD_STACK);
static K_THREAD_STACK_DEFINE(helper_thread_stack, THREAD_STACK);
static k_tid_t test_thread_id;
static k_tid_t helper_thread_id;
static struct k_thread test_thread_data;
static struct k_thread helper_thread_data;
static bool test_failure = true; /* Assume the test will fail */
/**
* @brief Kernel thread sleep and wakeup tests
* @defgroup tests_kernel_sleep Sleep Tests
* @ingroup all_tests
* @{
*/
static void test_objects_init(void)
{
k_sem_init(&test_thread_sem, 0, UINT_MAX);
k_sem_init(&helper_thread_sem, 0, UINT_MAX);
k_sem_init(&task_sem, 0, UINT_MAX);
}
static void align_to_tick_boundary(void)
{
uint32_t tick;
tick = k_uptime_get_32();
while (k_uptime_get_32() == tick) {
/* Busy wait to align to tick boundary */
Z_SPIN_DELAY(50);
}
}
/* Shouldn't ever sleep for less than requested time, but allow for 1
* tick of "too long" slop for aliasing between wakeup and
* measurement. Qemu at least will leak the external world's clock
* rate into the simulator when the host is under load.
*/
static int sleep_time_valid(uint32_t start, uint32_t end, uint32_t dur)
{
uint32_t dt = end - start;
return dt >= dur && dt <= (dur + TICK_MARGIN);
}
static void test_thread(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
uint32_t start_tick;
uint32_t end_tick;
k_sem_take(&test_thread_sem, K_FOREVER);
align_to_tick_boundary();
start_tick = k_uptime_get_32();
k_sleep(K_SECONDS(1));
end_tick = k_uptime_get_32();
if (!sleep_time_valid(start_tick, end_tick, ONE_SECOND_ALIGNED)) {
TC_ERROR(" *** k_sleep() slept for %d ticks not %d.",
end_tick - start_tick, ONE_SECOND_ALIGNED);
return;
}
k_sem_give(&helper_thread_sem); /* Activate helper thread */
align_to_tick_boundary();
start_tick = k_uptime_get_32();
k_sleep(K_SECONDS(1));
end_tick = k_uptime_get_32();
if (end_tick - start_tick > TICK_MARGIN) {
TC_ERROR(" *** k_wakeup() took too long (%d ticks)\n",
end_tick - start_tick);
return;
}
k_sem_give(&helper_thread_sem); /* Activate helper thread */
align_to_tick_boundary();
start_tick = k_uptime_get_32();
k_sleep(K_SECONDS(1));
end_tick = k_uptime_get_32();
if (end_tick - start_tick > TICK_MARGIN) {
TC_ERROR(" *** k_wakeup() took too long (%d ticks)\n",
end_tick - start_tick);
return;
}
k_sem_give(&task_sem); /* Activate task */
align_to_tick_boundary();
start_tick = k_uptime_get_32();
k_sleep(K_SECONDS(1)); /* Task will execute */
end_tick = k_uptime_get_32();
if (end_tick - start_tick > TICK_MARGIN) {
TC_ERROR(" *** k_wakeup() took too long (%d ticks) at LAST\n",
end_tick - start_tick);
return;
}
test_failure = false;
}
static void irq_offload_isr(const void *arg)
{
k_wakeup((k_tid_t) arg);
}
static void helper_thread(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
k_sem_take(&helper_thread_sem, K_FOREVER);
/* Wake the test thread */
k_wakeup(test_thread_id);
k_sem_take(&helper_thread_sem, K_FOREVER);
/* Wake the test thread from an ISR */
irq_offload(irq_offload_isr, (const void *)test_thread_id);
}
/**
* @brief Verify that k_sleep() sleeps for the requested time and that
* k_wakeup() cancels a pending sleep immediately.
*
* @details
* A cooperative test thread performs a sequence of one-second sleeps while a
* helper thread, an ISR (via irq_offload()) and the main thread each wake it
* early. The test validates both the timekeeping and the cancellation paths of
* the sleep primitives:
* - An uncancelled k_sleep() must block for at least the requested duration and
* no more than one tick of slop.
* - A k_wakeup() targeting a sleeping thread must return it to the ready queue
* without waiting for the timeout to expire, regardless of whether the wakeup
* originates from a thread, an ISR, or the main thread.
*
* Test steps:
* - Create a cooperative test thread and a higher-priority helper thread.
* - Let the test thread run one uncancelled k_sleep(K_SECONDS(1)) and confirm
* the elapsed time matches the request within TICK_MARGIN.
* - Have the helper thread, an irq_offload() ISR, and the main thread each
* call k_wakeup() on the test thread mid-sleep and confirm each sleep returns
* within TICK_MARGIN.
* - Run one final uncancelled k_sleep() on the main thread and validate its
* duration.
*
* Expected result:
* - Uncancelled sleeps elapse for the requested duration (within slop).
* - Every k_wakeup() cancels the sleep promptly and test_failure stays false.
*
* @see k_sleep()
* @see k_wakeup()
* @see k_uptime_get_32()
*/
ZTEST(sleep, test_sleep)
{
int status = TC_FAIL;
uint32_t start_tick;
uint32_t end_tick;
/*
* Main thread(test_main) priority is 0 but ztest thread runs at
* priority -1. To run the test smoothly make both main and ztest
* threads run at same priority level.
*/
k_thread_priority_set(k_current_get(), 0);
test_objects_init();
test_thread_id = k_thread_create(&test_thread_data, test_thread_stack,
THREAD_STACK,
test_thread,
0, 0, NULL, TEST_THREAD_PRIORITY,
0, K_NO_WAIT);
helper_thread_id = k_thread_create(&helper_thread_data,
helper_thread_stack, THREAD_STACK,
helper_thread,
0, 0, NULL, HELPER_THREAD_PRIORITY,
0, K_NO_WAIT);
/* Activate test_thread */
k_sem_give(&test_thread_sem);
/* Wait for test_thread to activate us */
k_sem_take(&task_sem, K_FOREVER);
/* Wake the test thread */
k_wakeup(test_thread_id);
zassert_false(test_failure, "test failure");
align_to_tick_boundary();
start_tick = k_uptime_get_32();
k_sleep(K_SECONDS(1));
end_tick = k_uptime_get_32();
zassert_true(sleep_time_valid(start_tick, end_tick, ONE_SECOND_ALIGNED),
"k_sleep() slept for %d ticks, not %d\n",
end_tick - start_tick, ONE_SECOND_ALIGNED);
status = TC_PASS;
}
static void forever_thread_entry(void *p1, void *p2, void *p3)
{
int32_t ret;
ret = k_sleep(K_FOREVER);
zassert_equal(ret, K_TICKS_FOREVER, "unexpected return value");
k_sem_give(&test_thread_sem);
}
/**
* @brief Verify that k_sleep(K_FOREVER) blocks until an explicit wakeup and
* returns K_TICKS_FOREVER.
*
* @details
* A user-mode thread calls k_sleep(K_FOREVER), which must never expire on its
* own. The thread only becomes runnable again after the main thread issues a
* k_wakeup(). On return, k_sleep() must report K_TICKS_FOREVER to indicate the
* sleep was cancelled rather than timed out.
*
* Test steps:
* - Create a user-mode thread that calls k_sleep(K_FOREVER).
* - Yield so the thread reaches the sleeping state.
* - Call k_wakeup() on the thread from the main thread.
* - In the woken thread, assert the return value equals K_TICKS_FOREVER.
*
* Expected result:
* - The thread stays asleep until woken and k_sleep() returns K_TICKS_FOREVER.
*
* @see k_sleep()
* @see k_wakeup()
*/
ZTEST(sleep, test_sleep_forever)
{
test_objects_init();
test_thread_id = k_thread_create(&test_thread_data,
test_thread_stack,
THREAD_STACK,
forever_thread_entry,
0, 0, NULL, TEST_THREAD_PRIORITY,
K_USER | K_INHERIT_PERMS, K_NO_WAIT);
/* Allow forever thread to run */
k_yield();
k_wakeup(test_thread_id);
k_sem_take(&test_thread_sem, K_FOREVER);
}
static volatile int32_t wakeup_remaining;
static void early_wake_thread_entry(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
/* Sleep for a long duration; the main thread wakes us well before it
* elapses. Capture the remaining time reported by k_sleep().
*/
wakeup_remaining = k_sleep(K_SECONDS(10));
k_sem_give(&test_thread_sem);
}
/**
* @brief Verify k_sleep() reports the remaining time when woken early
*
* @details Put a thread to sleep for a long, finite duration and wake it with
* k_wakeup() before that duration elapses. k_sleep() must return the amount of
* time that was still remaining, which is non-zero and does not exceed the
* originally requested duration.
*
* Test steps:
* - Start a thread that sleeps for 10 seconds and stores k_sleep()'s return.
* - Yield so the thread begins sleeping, let ~100 ms pass, then wake it with
* k_wakeup().
* - Inspect the stored return value.
*
* Expected result:
* - k_sleep() returns a value greater than zero and less than the requested
* 10000 ms, i.e. it reports the time that was still remaining.
*
* @see k_sleep()
* @see k_wakeup()
*/
ZTEST(sleep, test_sleep_wakeup_returns_remaining)
{
test_objects_init();
wakeup_remaining = 0;
test_thread_id = k_thread_create(&test_thread_data,
test_thread_stack,
THREAD_STACK,
early_wake_thread_entry,
0, 0, NULL, TEST_THREAD_PRIORITY,
0, K_NO_WAIT);
/* Allow early_wake_thread_entry to start. */
k_yield();
/* Let part of the sleep elapse, then wake it well before 10 seconds. */
k_msleep(100);
k_wakeup(test_thread_id);
k_sem_take(&test_thread_sem, K_FOREVER);
zassert_true(wakeup_remaining > 0,
"k_sleep() reported %d ms remaining; expected > 0",
wakeup_remaining);
zassert_true(wakeup_remaining < 10000,
"k_sleep() reported %d ms remaining; expected less than the "
"requested 10000", wakeup_remaining);
}
/*test case main entry*/
static void *sleep_setup(void)
{
k_thread_access_grant(k_current_get(), &test_thread_sem);
return NULL;
}
ZTEST_SUITE(sleep, NULL, sleep_setup,
ztest_simple_1cpu_before, ztest_simple_1cpu_after, NULL);
/**
* @}
*/