| /* |
| * 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); |
| |
| /** |
| * @} |
| */ |