blob: 7057d5ebc42cf5c77663f236f294e5f38e4933de [file]
/*
* Copyright (c) 2012-2015 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief Context and thread API tests
*
* @defgroup tests_kernel_context Kernel context and thread tests
*
* @ingroup all_tests
*
* This module tests the following CPU and thread related routines:
* k_thread_create(), k_yield(), k_is_in_isr(),
* k_current_get(), k_cpu_idle(), k_cpu_atomic_idle(),
* irq_lock(), irq_unlock(),
* irq_offload(), irq_enable(), irq_disable().
* @{
* @}
*/
#include <stdlib.h>
#include <zephyr/ztest.h>
#include <zephyr/kernel_structs.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/irq_offload.h>
#include <zephyr/sys/clock.h>
#if defined(CONFIG_SOC_POSIX)
/* TIMER_TICK_IRQ <soc.h> header for certain platforms */
#include <soc.h>
#endif
#define THREAD_STACKSIZE (1024 + CONFIG_TEST_EXTRA_STACK_SIZE)
#define THREAD_STACKSIZE2 (1024 + CONFIG_TEST_EXTRA_STACK_SIZE)
#define THREAD_PRIORITY 4
#define THREAD_SELF_CMD 0
#define EXEC_CTX_TYPE_CMD 1
#define UNKNOWN_COMMAND -1
#define INVALID_BEHAVIOUR -2
/*
* Get the timer type dependent IRQ number. If timer type
* is not defined in platform, generate an error
*/
#if defined(CONFIG_APIC_TSC_DEADLINE_TIMER) || defined(CONFIG_APIC_TIMER_TSC)
#define TICK_IRQ z_loapic_irq_base() /* first LVT interrupt */
#elif defined(CONFIG_CPU_CORTEX_M)
/*
* The Cortex-M use the SYSTICK exception for the system timer, which is
* not considered an IRQ by the irq_enable/Disable APIs.
*/
#elif defined(CONFIG_SPARC)
#elif defined(CONFIG_MIPS)
#elif defined(CONFIG_OPENRISC)
#elif defined(CONFIG_ARCH_POSIX)
#if defined(CONFIG_BOARD_NATIVE_SIM)
#define TICK_IRQ TIMER_TICK_IRQ
#else
/*
* Other POSIX arch boards will skip the irq_disable() and irq_enable() test
* unless TICK_IRQ is defined here for them
*/
#endif /* defined(CONFIG_ARCH_POSIX) */
#else
extern const int32_t z_sys_timer_irq_for_test;
#define TICK_IRQ (z_sys_timer_irq_for_test)
#endif
/* Cortex-M1 does have a power saving instruction, so k_cpu_idle()
* returns immediately
*/
#if !defined(CONFIG_CPU_CORTEX_M1)
#define HAS_POWERSAVE_INSTRUCTION
#endif
/* MAX32 CPU idle hook prevents WFI, so no powersave instruction if that
* config is set.
*/
#if defined(CONFIG_MAX32_ON_ENTER_CPU_IDLE_HOOK)
#undef HAS_POWERSAVE_INSTRUCTION
#endif
/* whisper simulator does not currently have working implementation for
* wfi instruction. It simply treats wfi as no-op such that k_cpu_idle()
* returns immediately and will fail idle tests.
*/
#if defined(CONFIG_WHISPER_TARGET)
#undef HAS_POWERSAVE_INSTRUCTION
#endif
typedef struct {
int command; /* command to process */
int error; /* error value (if any) */
union {
void *data; /* pointer to data to use or return */
int value; /* value to be passed or returned */
};
} ISR_INFO;
typedef int (*disable_int_func) (int);
typedef void (*enable_int_func) (int);
static struct k_sem sem_thread;
static struct k_timer timer;
static struct k_sem reply_timeout;
struct k_fifo timeout_order_fifo;
static int thread_evidence;
static K_THREAD_STACK_DEFINE(thread_stack1, THREAD_STACKSIZE);
static K_THREAD_STACK_DEFINE(thread_stack2, THREAD_STACKSIZE);
static K_THREAD_STACK_DEFINE(thread_stack3, THREAD_STACKSIZE);
static struct k_thread thread_data1;
static struct k_thread thread_data2;
static struct k_thread thread_data3;
static ISR_INFO isr_info;
/**
* @brief Handler to perform various actions from within an ISR context
*
* This routine is the ISR handler for isr_handler_trigger(). It performs
* the command requested in <isr_info.command>.
*/
static void isr_handler(const void *data)
{
ARG_UNUSED(data);
if (k_can_yield()) {
isr_info.error = INVALID_BEHAVIOUR;
}
switch (isr_info.command) {
case THREAD_SELF_CMD:
isr_info.data = (void *)k_current_get();
break;
case EXEC_CTX_TYPE_CMD:
if (k_is_in_isr()) {
isr_info.value = K_ISR;
break;
}
if (_current->base.prio < 0) {
isr_info.value = K_COOP_THREAD;
break;
}
isr_info.value = K_PREEMPT_THREAD;
break;
default:
isr_info.error = UNKNOWN_COMMAND;
break;
}
}
static void isr_handler_trigger(void)
{
irq_offload(isr_handler, NULL);
}
/* Records k_can_yield() as observed from within an irq_offload() handler. */
static volatile bool offload_can_yield;
static void can_yield_probe(const void *arg)
{
ARG_UNUSED(arg);
offload_can_yield = k_can_yield();
}
/**
*
* @brief Initialize kernel objects
*
* This routine initializes the kernel objects used in this module's tests.
*
*/
static void kernel_init_objects(void)
{
k_sem_init(&reply_timeout, 0, UINT_MAX);
k_timer_init(&timer, NULL, NULL);
k_fifo_init(&timeout_order_fifo);
}
/**
* @brief A wrapper for irq_lock()
*
* @return irq_lock() return value
*/
int irq_lock_wrapper(int unused)
{
ARG_UNUSED(unused);
return irq_lock();
}
/**
* @brief A wrapper for irq_unlock()
*/
void irq_unlock_wrapper(int imask)
{
irq_unlock(imask);
}
/**
* @brief A wrapper for irq_disable()
*
* @return @a irq
*/
int irq_disable_wrapper(int irq)
{
irq_disable(irq);
return irq;
}
/**
* @brief A wrapper for irq_enable()
*/
void irq_enable_wrapper(int irq)
{
irq_enable(irq);
}
#if defined(HAS_POWERSAVE_INSTRUCTION)
#if defined(CONFIG_TICKLESS_KERNEL)
static struct k_timer idle_timer;
static volatile bool idle_timer_done;
static void idle_timer_expiry_function(struct k_timer *timer_id)
{
k_timer_stop(&idle_timer);
idle_timer_done = true;
}
static void _test_kernel_cpu_idle(int atomic)
{
uint64_t t0, dt;
unsigned int i, key;
uint32_t dur = k_ms_to_ticks_ceil32(10);
/* 1 tick for z_add_timeout()'s "at least N" round-up, plus
* 1 ms measurement slop.
*/
uint32_t slop = 2 + k_ms_to_ticks_ceil32(1);
int idle_loops;
/* Set up a time to trigger events to exit idle mode */
k_timer_init(&idle_timer, idle_timer_expiry_function, NULL);
for (i = 0; i < 5; i++) {
k_usleep(1);
t0 = k_uptime_ticks();
idle_loops = 0;
idle_timer_done = false;
k_timer_start(&idle_timer, K_TICKS(dur), K_NO_WAIT);
key = irq_lock();
do {
if (atomic) {
k_cpu_atomic_idle(key);
} else {
k_cpu_idle();
}
} while ((idle_loops++ < CONFIG_MAX_IDLE_WAKES) && (idle_timer_done == false));
zassert_true(idle_timer_done,
"The CPU was waken spuriously too many times (%d > %d)",
idle_loops, CONFIG_MAX_IDLE_WAKES);
dt = k_uptime_ticks() - t0;
zassert_true(abs((int32_t) (dt - dur)) <= slop,
"Inaccurate wakeup, idled for %d ticks, expected %d",
(int)dt, dur);
}
}
#else /* CONFIG_TICKLESS_KERNEL */
static void _test_kernel_cpu_idle(int atomic)
{
int tms, tms2;
int i;
/* Align to a "ms boundary". */
tms = k_uptime_get_32();
while (tms == k_uptime_get_32()) {
Z_SPIN_DELAY(50);
}
tms = k_uptime_get_32();
for (i = 0; i < 5; i++) { /* Repeat the test five times */
if (atomic) {
unsigned int key = irq_lock();
k_cpu_atomic_idle(key);
} else {
k_cpu_idle();
}
/* calculating milliseconds per tick*/
tms += k_ticks_to_ms_floor64(1);
tms2 = k_uptime_get_32();
zassert_false(tms2 < tms, "Bad ms per tick value computed,"
"got %d which is less than %d\n",
tms2, tms);
}
}
#endif /* CONFIG_TICKLESS_KERNEL */
/**
* @brief Verify k_cpu_atomic_idle() idles the CPU until an interrupt wakes it.
*
* @ingroup tests_kernel_context
*
* @details
* The architecture provides an atomic idle primitive that suspends the CPU
* with interrupts locked and resumes when an interrupt arrives, without losing
* a wake-up that races the idle. The test arms a timer, enters
* k_cpu_atomic_idle(), and confirms the CPU stayed idle for the expected
* duration and was woken by the timer.
*
* Test steps:
* - Record the system time before idling.
* - Lock interrupts, arm a timer, and enter k_cpu_atomic_idle().
* - On wake-up, record the system time again and compare against the timer.
*
* Expected result:
* - The CPU idles until the timer fires and the elapsed time matches the timer
* duration within tolerance.
*
* @see k_cpu_atomic_idle()
*/
ZTEST(context_cpu_idle, test_cpu_idle_atomic)
{
#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
/* On ARM k_cpu_atomic_idle() returns immediately, so the idle-duration
* check below does not apply.
*/
TC_PRINT("Skipped: k_cpu_atomic_idle() is a no-op on ARM/ARM64\n");
ztest_test_skip();
#else
_test_kernel_cpu_idle(1);
#endif
}
/**
* @brief Verify k_cpu_idle() idles the CPU until an interrupt wakes it.
*
* @ingroup tests_kernel_context
*
* @details
* The architecture provides an idle primitive that suspends the CPU when there
* is no work to do and resumes on the next interrupt. The test enters
* k_cpu_idle() and confirms the CPU stayed idle for the expected duration
* before an interrupt woke it.
*
* Test steps:
* - Record the system time before idling.
* - Enter k_cpu_idle() and wait for an interrupt to resume execution.
* - On wake-up, record the system time again and compare the elapsed time.
*
* Expected result:
* - The CPU idles and resumes after the expected time has elapsed.
*
* @see k_cpu_idle()
*/
ZTEST(context_cpu_idle, test_cpu_idle)
{
_test_kernel_cpu_idle(0);
}
#else /* HAS_POWERSAVE_INSTRUCTION */
ZTEST(context_cpu_idle, test_cpu_idle)
{
TC_PRINT("Skipped: target has no power-save (idle) instruction\n");
ztest_test_skip();
}
ZTEST(context_cpu_idle, test_cpu_idle_atomic)
{
TC_PRINT("Skipped: target has no power-save (idle) instruction\n");
ztest_test_skip();
}
#endif
static void _test_kernel_interrupts(disable_int_func disable_int,
enable_int_func enable_int, int irq)
{
unsigned long long count = 1ull;
unsigned long long i = 0;
int tick;
int tick2;
int imask;
/* Align to a "tick boundary" */
tick = sys_clock_tick_get_32();
while (sys_clock_tick_get_32() == tick) {
Z_SPIN_DELAY(1000);
}
tick++;
while (sys_clock_tick_get_32() == tick) {
Z_SPIN_DELAY(1000);
count++;
}
/*
* Inflate <count> so that when we loop later, many ticks should have
* elapsed during the loop. This later loop will not exactly match the
* previous loop, but it should be close enough in structure that when
* combined with the inflated count, many ticks will have passed.
*/
count <<= 4;
imask = disable_int(irq);
tick = sys_clock_tick_get_32();
for (i = 0; i < count; i++) {
sys_clock_tick_get_32();
Z_SPIN_DELAY(1000);
}
tick2 = sys_clock_tick_get_32();
/*
* Re-enable interrupts before returning (for both success and failure
* cases).
*/
enable_int(imask);
/* In TICKLESS, current time is retrieved from a hardware
* counter and ticks DO advance with interrupts locked!
*/
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
zassert_equal(tick2, tick,
"tick advanced with interrupts locked");
}
/* Now repeat with interrupts unlocked. */
for (i = 0; i < count; i++) {
sys_clock_tick_get_32();
Z_SPIN_DELAY(1000);
}
tick2 = sys_clock_tick_get_32();
zassert_not_equal(tick, tick2,
"tick didn't advance as expected");
}
/**
* @brief Verify irq_lock()/irq_unlock() mask interrupts and stop the tick.
*
* @ingroup tests_kernel_context
*
* @details
* irq_lock() must mask maskable interrupts so the system tick cannot advance,
* and irq_unlock() must restore delivery so ticks resume. With a non-tickless
* timer the tick count is the observable proxy for interrupt delivery.
*
* Test steps:
* - Align to a tick boundary and read the tick count.
* - Call irq_lock(), busy-loop across what would be several ticks, and confirm
* the tick count did not advance.
* - Call irq_unlock(), busy-loop again, and confirm the tick count advanced.
*
* Expected result:
* - Ticks do not advance while interrupts are locked and resume after unlock.
*
* @see irq_lock()
* @see irq_unlock()
*/
ZTEST(context, test_interrupts)
{
/* IRQ locks don't prevent ticks from advancing in tickless mode */
if (IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
TC_PRINT("Skipped: tick advances under irq_lock() in tickless mode\n");
ztest_test_skip();
}
_test_kernel_interrupts(irq_lock_wrapper, irq_unlock_wrapper, -1);
}
/**
* @brief Verify arch_cpu_irqs_are_enabled() reports IRQ state without altering it.
*
* @ingroup tests_kernel_context
*
* @details
* The probe must report the current CPU's interrupt-enable state and must be
* a pure read — calling it must not change the state it observes.
*
* Test steps:
* - In thread context (IRQs enabled), call the probe twice.
* - Lock IRQs with arch_irq_lock() and call the probe twice.
* - Restore IRQs with arch_irq_unlock() and call the probe again.
*
* Expected result:
* - Reports enabled in thread context and after unlock; disabled while locked.
* - Repeated calls return the same value (the probe does not flip the state).
*
* @see arch_cpu_irqs_are_enabled()
*/
ZTEST(context, test_arch_cpu_irqs_are_enabled)
{
unsigned int key;
/* In thread context IRQs are enabled. Call twice to confirm the
* probe does not flip the state it observes.
*/
zassert_true(arch_cpu_irqs_are_enabled(),
"IRQs reported disabled in thread context");
zassert_true(arch_cpu_irqs_are_enabled(),
"probe altered the IRQ state (enabled -> disabled)");
key = arch_irq_lock();
zassert_false(arch_cpu_irqs_are_enabled(),
"IRQs reported enabled after arch_irq_lock()");
zassert_false(arch_cpu_irqs_are_enabled(),
"probe altered the IRQ state (disabled -> enabled)");
arch_irq_unlock(key);
zassert_true(arch_cpu_irqs_are_enabled(),
"IRQs reported disabled after arch_irq_unlock()");
}
/**
* @brief Verify irq_lock()/irq_unlock() nest and only the outer unlock restores
* interrupts.
*
* @ingroup tests_kernel_context
*
* @details
* irq_lock() is reentrant: it returns a key encoding the interrupt state at the
* time of the call, and irq_unlock() restores exactly that state. Nesting two
* locks must therefore keep interrupts masked until the call balancing the
* outermost lock runs - unlocking the inner key must NOT prematurely re-enable
* interrupts. arch_cpu_irqs_are_enabled() is used to observe the state at each
* step.
*
* Test steps:
* - In thread context (IRQs enabled), take an outer irq_lock(); confirm masked.
* - Take a nested inner irq_lock(); confirm still masked.
* - irq_unlock() the inner key; confirm interrupts remain masked.
* - irq_unlock() the outer key; confirm interrupts are enabled again.
*
* Expected result:
* - Interrupts stay masked across the inner unlock and are only restored by the
* unlock balancing the outermost lock.
*
* @see irq_lock()
* @see irq_unlock()
* @see arch_cpu_irqs_are_enabled()
*/
ZTEST(context, test_irq_lock_nested)
{
unsigned int key_outer;
unsigned int key_inner;
/* Sanity: start from thread context with interrupts enabled. */
zassert_true(arch_cpu_irqs_are_enabled(),
"IRQs not enabled at test entry");
key_outer = irq_lock();
zassert_false(arch_cpu_irqs_are_enabled(),
"IRQs not masked after outer irq_lock()");
key_inner = irq_lock();
zassert_false(arch_cpu_irqs_are_enabled(),
"IRQs not masked after nested irq_lock()");
/* Balancing the inner lock must leave interrupts masked, because the
* outer lock is still held.
*/
irq_unlock(key_inner);
zassert_false(arch_cpu_irqs_are_enabled(),
"inner irq_unlock() re-enabled IRQs while outer lock held");
/* Balancing the outermost lock restores the original (enabled) state. */
irq_unlock(key_outer);
zassert_true(arch_cpu_irqs_are_enabled(),
"outer irq_unlock() did not restore IRQs");
}
/**
* @brief Verify k_can_yield() is true in a thread and false in an ISR.
*
* @ingroup tests_kernel_context
*
* @details
* k_can_yield() reports whether the current context is allowed to yield the CPU.
* A normal thread may yield, but an ISR must not. The ISR case is observed via
* an irq_offload() handler that records k_can_yield().
*
* Test steps:
* - In thread context, confirm k_can_yield() returns true.
* - Trigger an irq_offload() handler that records k_can_yield() and confirm it
* observed false in ISR context.
*
* Expected result:
* - k_can_yield() is true in thread context and false in ISR context.
*
* @see k_can_yield()
*/
ZTEST(context, test_k_can_yield)
{
zassert_true(k_can_yield(),
"k_can_yield() reported false in thread context");
/* Seed with the opposite value so the assertion below proves the
* handler actually ran and wrote the ISR-context result.
*/
offload_can_yield = true;
irq_offload(can_yield_probe, NULL);
zassert_false(offload_can_yield,
"k_can_yield() reported true in ISR context");
}
/**
* @brief Verify irq_disable()/irq_enable() mask a specific numeric IRQ.
*
* @ingroup tests_kernel_context
*
* @details
* irq_disable() must mask a single numeric interrupt line so its handler cannot
* run, and irq_enable() must re-enable delivery. The test disables the timer
* IRQ directly and uses the tick count as the observable proxy: ticks must
* stall while the timer IRQ is disabled and resume once it is re-enabled.
*
* Test steps:
* - Align to a tick boundary and read the tick count.
* - Call irq_disable(TICK_IRQ), busy-loop across several ticks, and confirm the
* tick count did not advance.
* - Call irq_enable(TICK_IRQ), busy-loop again, and confirm ticks advanced.
*
* Expected result:
* - Ticks stall while the timer IRQ is disabled and resume after enable.
*
* @note This test disables the timer interrupt directly, bypassing the timer
* driver and timeout subsystem. Not all architectures latch a timer interrupt
* that arrives while disabled, so the timeout list may be left corrupted with
* already-expired entries. Kernel timeouts must not be used after this test —
* RUN THIS TEST LAST IN THE SUITE.
*
* @see irq_disable()
* @see irq_enable()
*/
ZTEST(context_one_cpu, test_timer_interrupts)
{
#if (defined(TICK_IRQ) && defined(CONFIG_TICKLESS_KERNEL))
/* Disable interrupts coming from the timer. */
_test_kernel_interrupts(irq_disable_wrapper, irq_enable_wrapper, TICK_IRQ);
#else
TC_PRINT("Skipped: requires a known timer IRQ (TICK_IRQ) and "
"CONFIG_TICKLESS_KERNEL\n");
ztest_test_skip();
#endif
}
/**
* @brief Verify context identity is reported correctly across an ISR from a
* preemptible thread.
*
* @ingroup tests_kernel_context
*
* @details
* From a preemptible thread the kernel must report the running thread's identity
* and execution context consistently, and the thread context must be restored on
* interrupt exit. An ISR triggered from the thread reports back the interrupted
* thread's id and that it is executing in ISR context.
*
* Test steps:
* - Set the current thread to preemptible priority and read its id.
* - Trigger an ISR that returns the interrupted thread's id; compare with the
* caller's id.
* - Trigger an ISR that reports its execution context; confirm it is K_ISR.
* - Back in the thread, confirm k_is_in_isr() is false and the priority is
* preemptible.
*
* Expected result:
* - The ISR observes the calling thread's id and K_ISR context, and the thread
* context is intact after interrupt exit.
*
* @see k_current_get()
* @see k_is_in_isr()
*/
ZTEST(context, test_ctx_thread)
{
k_tid_t self_thread_id;
k_thread_priority_set(k_current_get(), 0);
TC_PRINT("Testing k_current_get() from an ISR and thread\n");
self_thread_id = k_current_get();
isr_info.command = THREAD_SELF_CMD;
isr_info.error = 0;
/* isr_info is modified by the isr_handler routine */
isr_handler_trigger();
zassert_false(isr_info.error, "ISR detected an error");
zassert_equal(isr_info.data, (void *)self_thread_id,
"ISR context ID mismatch");
TC_PRINT("Testing k_is_in_isr() from an ISR\n");
isr_info.command = EXEC_CTX_TYPE_CMD;
isr_info.error = 0;
isr_handler_trigger();
zassert_false(isr_info.error, "ISR detected an error");
zassert_equal(isr_info.value, K_ISR,
"isr_info.value was not K_ISR");
TC_PRINT("Testing k_is_in_isr() from a preemptible thread\n");
zassert_false(k_is_in_isr(), "Should not be in ISR context");
zassert_false(_current->base.prio < 0,
"Current thread should have preemptible priority: %d",
_current->base.prio);
}
/**
* @brief Test the various context/thread routines from a cooperative thread
*
* This routines tests the k_current_get() and k_is_in_isr() routines from both
* a thread and an ISR (that interrupted a cooperative thread). Checking those
* routines with preemptible threads are done elsewhere.
*
* @see k_current_get(), k_is_in_isr()
*/
static void _test_kernel_thread(k_tid_t _thread_id)
{
k_tid_t self_thread_id;
self_thread_id = k_current_get();
zassert_true((self_thread_id != _thread_id), "thread id matches parent thread");
isr_info.command = THREAD_SELF_CMD;
isr_info.error = 0;
isr_handler_trigger();
/*
* Either the ISR detected an error, or the ISR context ID
* does not match the interrupted thread's ID.
*/
zassert_false((isr_info.error || (isr_info.data != (void *)self_thread_id)),
"Thread ID taken during ISR != calling thread");
isr_info.command = EXEC_CTX_TYPE_CMD;
isr_info.error = 0;
isr_handler_trigger();
zassert_false((isr_info.error || (isr_info.value != K_ISR)),
"k_is_in_isr() when called from an ISR is false");
zassert_false(k_is_in_isr(), "k_is_in_isr() when called from a thread is true");
zassert_false((_current->base.prio >= 0),
"thread is not a cooperative thread");
}
/**
*
* @brief Entry point to the thread's helper
*
* This routine is the entry point to the thread's helper thread. It is used to
* help test the behavior of the k_yield() routine.
*
* @param arg1 unused
* @param arg2 unused
* @param arg3 unused
*
*/
static void thread_helper(void *arg1, void *arg2, void *arg3)
{
k_tid_t self_thread_id;
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
/*
* This thread starts off at a higher priority than thread_entry().
* Thus, it should execute immediately.
*/
thread_evidence++;
/* Test that helper will yield to a thread of equal priority */
self_thread_id = k_current_get();
/* Lower priority to that of thread_entry() */
k_thread_priority_set(self_thread_id, self_thread_id->base.prio + 1);
k_yield(); /* Yield to thread of equal priority */
thread_evidence++;
/* thread_evidence should now be 2 */
}
/**
* @brief Entry point to thread started by another thread
*
* This routine is the entry point to the thread started by the thread.
*/
static void k_yield_entry(void *arg0, void *arg1, void *arg2)
{
k_tid_t self_thread_id;
ARG_UNUSED(arg0);
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
thread_evidence++; /* Prove that the thread has run */
k_sem_take(&sem_thread, K_FOREVER);
/*
* Start a thread of higher priority. Note that since the new thread is
* being started from a thread, it will not automatically switch to the
* thread as it would if done from another thread.
*/
self_thread_id = k_current_get();
thread_evidence = 0;
k_thread_create(&thread_data2, thread_stack2, THREAD_STACKSIZE,
thread_helper, NULL, NULL, NULL,
K_PRIO_COOP(THREAD_PRIORITY - 1), 0, K_NO_WAIT);
zassert_equal(thread_evidence, 0,
"Helper created at higher priority ran prematurely.");
/*
* Validate the thread is allowed to yield
*/
zassert_true(k_can_yield(), "Thread incorrectly detected it could not yield");
/*
* Test that the thread will yield to the higher priority helper.
* thread_evidence is still 0.
*/
k_yield();
zassert_not_equal(thread_evidence, 0,
"k_yield() did not yield to a higher priority thread: %d",
thread_evidence);
zassert_false((thread_evidence > 1),
"k_yield() did not yield to an equal priority thread: %d",
thread_evidence);
/*
* Raise the priority of thread_entry(). Calling k_yield() should
* not result in switching to the helper.
*/
k_thread_priority_set(self_thread_id, self_thread_id->base.prio - 1);
k_yield();
zassert_equal(thread_evidence, 1,
"k_yield() yielded to a lower priority thread");
/*
* Block on sem_thread. This will allow the helper thread to
* complete. The main thread will wake this thread.
*/
k_sem_take(&sem_thread, K_FOREVER);
}
static void kernel_thread_entry(void *_thread_id, void *arg1, void *arg2)
{
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
thread_evidence++; /* Prove that the thread has run */
_test_kernel_thread((k_tid_t) _thread_id);
}
/*
* @brief Timeout tests
*
* Test the k_sleep() API, as well as the k_thread_create() ones.
*/
struct timeout_order {
void *link_in_fifo;
int32_t timeout;
int timeout_order;
int q_order;
};
struct timeout_order timeouts[] = {
{ 0, 1000, 2, 0 },
{ 0, 1500, 4, 1 },
{ 0, 500, 0, 2 },
{ 0, 750, 1, 3 },
{ 0, 1750, 5, 4 },
{ 0, 2000, 6, 5 },
{ 0, 1250, 3, 6 },
};
#define NUM_TIMEOUT_THREADS ARRAY_SIZE(timeouts)
static K_THREAD_STACK_ARRAY_DEFINE(timeout_stacks, NUM_TIMEOUT_THREADS,
THREAD_STACKSIZE2);
static struct k_thread timeout_threads[NUM_TIMEOUT_THREADS];
/* a thread busy waits */
static void busy_wait_thread(void *mseconds, void *arg2, void *arg3)
{
uint32_t usecs;
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
usecs = POINTER_TO_INT(mseconds) * 1000;
k_busy_wait(usecs);
int key = arch_irq_lock();
k_busy_wait(usecs);
arch_irq_unlock(key);
/*
* Ideally the test should verify that the correct number of ticks
* have elapsed. However, when running under QEMU, the tick interrupt
* may be processed on a very irregular basis, meaning that far
* fewer than the expected number of ticks may occur for a given
* number of clock cycles vs. what would ordinarily be expected.
*
* Consequently, the best we can do for now to test busy waiting is
* to invoke the API and verify that it returns. (If it takes way
* too long, or never returns, the main test thread may be able to
* time out and report an error.)
*/
k_sem_give(&reply_timeout);
}
/* a thread sleeps and times out, then reports through a fifo */
static void thread_sleep(void *delta, void *arg2, void *arg3)
{
int64_t timestamp;
int timeout = POINTER_TO_INT(delta);
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
timestamp = k_uptime_get();
k_msleep(timeout);
timestamp = k_uptime_get() - timestamp;
int slop = MAX(k_ticks_to_ms_floor64(2), 1);
if (timestamp < timeout || timestamp > timeout + slop) {
TC_ERROR("timestamp out of range, got %d\n", (int)timestamp);
return;
}
k_sem_give(&reply_timeout);
}
/* a thread is started with a delay, then it reports that it ran via a fifo */
static void delayed_thread(void *num, void *arg2, void *arg3)
{
struct timeout_order *timeout = &timeouts[POINTER_TO_INT(num)];
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
TC_PRINT(" thread (q order: %d, t/o: %d) is running\n",
timeout->q_order, timeout->timeout);
k_fifo_put(&timeout_order_fifo, timeout);
}
/**
* @brief Verify that k_busy_wait() blocks and returns.
*
* @ingroup tests_kernel_context
*
* @details
* k_busy_wait() must spin for the requested duration and return, both with
* interrupts enabled and with them locked. Tick accounting under emulation is
* too irregular to assert an exact elapsed time, so the test confirms the call
* completes and the worker reports back within a generous timeout.
*
* Test steps:
* - Start a cooperative thread that calls k_busy_wait() (once normally, once
* with IRQs locked) and then gives a semaphore.
* - Wait on that semaphore with a timeout of several busy-wait durations.
*
* Expected result:
* - The semaphore take succeeds (the busy-wait thread ran to completion).
*
* @see k_busy_wait()
*/
ZTEST(context_one_cpu, test_busy_wait)
{
int32_t timeout;
int rv;
timeout = 20; /* in ms */
k_thread_create(&timeout_threads[0], timeout_stacks[0],
THREAD_STACKSIZE2, busy_wait_thread,
INT_TO_POINTER(timeout), NULL,
NULL, K_PRIO_COOP(THREAD_PRIORITY), 0, K_NO_WAIT);
rv = k_sem_take(&reply_timeout, K_MSEC(timeout * 2 * 2));
zassert_false(rv, " *** thread timed out waiting for " "k_busy_wait()");
}
/**
* @brief Verify k_sleep() duration and delayed-start thread ordering.
*
* @ingroup tests_kernel_context
*
* @details
* k_sleep() must block the caller for at least the requested time, and threads
* created with a start delay must become ready in delay order. Aborting a
* delayed thread before it starts must remove it from the timeout queue.
*
* Test steps:
* - Start a thread that sleeps a fixed time and reports back; confirm it wakes.
* - Create several threads with different start delays; confirm they report in
* ascending-delay order and that no extra thread fires.
* - Repeat with a subset of the delayed threads aborted before they start;
* confirm only the non-cancelled threads run, in order.
*
* Expected result:
* - The sleeper wakes within the expected window.
* - Delayed threads run strictly in delay order; cancelled ones never run.
*
* @see k_sleep()
* @see k_thread_create()
*/
ZTEST(context_one_cpu, test_k_sleep)
{
struct timeout_order *data;
int32_t timeout;
int rv;
int i;
timeout = 50;
k_thread_create(&timeout_threads[0], timeout_stacks[0],
THREAD_STACKSIZE2, thread_sleep,
INT_TO_POINTER(timeout), NULL,
NULL, K_PRIO_COOP(THREAD_PRIORITY), 0, K_NO_WAIT);
rv = k_sem_take(&reply_timeout, K_MSEC(timeout * 2));
zassert_equal(rv, 0, " *** thread timed out waiting for thread on "
"k_sleep().");
/* test k_thread_create() without cancellation */
TC_PRINT("Testing k_thread_create() without cancellation\n");
for (i = 0; i < NUM_TIMEOUT_THREADS; i++) {
k_thread_create(&timeout_threads[i], timeout_stacks[i],
THREAD_STACKSIZE2,
delayed_thread,
INT_TO_POINTER(i), NULL, NULL,
K_PRIO_COOP(5), 0,
K_MSEC(timeouts[i].timeout));
}
for (i = 0; i < NUM_TIMEOUT_THREADS; i++) {
data = k_fifo_get(&timeout_order_fifo, K_MSEC(750));
zassert_not_null(data, " *** timeout while waiting for"
" delayed thread");
zassert_equal(data->timeout_order, i,
" *** wrong delayed thread ran (got %d, "
"expected %d)\n", data->timeout_order, i);
TC_PRINT(" got thread (q order: %d, t/o: %d) as expected\n",
data->q_order, data->timeout);
}
/* ensure no more thread fire */
data = k_fifo_get(&timeout_order_fifo, K_MSEC(750));
zassert_false(data, " *** got something unexpected in the fifo");
/* test k_thread_create() with cancellation */
TC_PRINT("Testing k_thread_create() with cancellations\n");
int cancellations[] = { 0, 3, 4, 6 };
int num_cancellations = ARRAY_SIZE(cancellations);
int next_cancellation = 0;
k_tid_t delayed_threads[NUM_TIMEOUT_THREADS];
for (i = 0; i < NUM_TIMEOUT_THREADS; i++) {
k_tid_t id;
id = k_thread_create(&timeout_threads[i], timeout_stacks[i],
THREAD_STACKSIZE2, delayed_thread,
INT_TO_POINTER(i), NULL, NULL,
K_PRIO_COOP(5), 0,
K_MSEC(timeouts[i].timeout));
delayed_threads[i] = id;
}
for (i = 0; i < NUM_TIMEOUT_THREADS; i++) {
int j;
if (i == cancellations[next_cancellation]) {
TC_PRINT(" cancelling "
"[q order: %d, t/o: %d, t/o order: %d]\n",
timeouts[i].q_order, timeouts[i].timeout, i);
for (j = 0; j < NUM_TIMEOUT_THREADS; j++) {
if (timeouts[j].timeout_order == i) {
break;
}
}
if (j < NUM_TIMEOUT_THREADS) {
k_thread_abort(delayed_threads[j]);
++next_cancellation;
continue;
}
}
data = k_fifo_get(&timeout_order_fifo, K_MSEC(2750));
zassert_not_null(data, " *** timeout while waiting for"
" delayed thread");
zassert_equal(data->timeout_order, i,
" *** wrong delayed thread ran (got %d, "
"expected %d)\n", data->timeout_order, i);
TC_PRINT(" got (q order: %d, t/o: %d, t/o order %d) "
"as expected\n", data->q_order, data->timeout,
data->timeout_order);
}
zassert_equal(num_cancellations, next_cancellation,
" *** wrong number of cancellations (expected %d, "
"got %d\n", num_cancellations, next_cancellation);
/* ensure no more thread fire */
data = k_fifo_get(&timeout_order_fifo, K_MSEC(750));
zassert_false(data, " *** got something unexpected in the fifo");
}
/**
* @brief Verify k_yield() only switches to threads of equal or higher priority.
*
* @ingroup tests_kernel_context
*
* @details
* k_yield() relinquishes the CPU to any ready thread of equal or higher
* priority but must not switch to a lower-priority thread. A cooperative helper
* thread is used to exercise k_yield() against higher-, equal-, and
* lower-priority peers, with a shared counter recording when each thread runs.
*
* Test steps:
* - Create a cooperative worker thread (also exercising k_thread_create()).
* - From the worker, yield to a higher-priority and an equal-priority helper
* and confirm each runs.
* - Raise the worker's priority and yield again; confirm the lower-priority
* helper does not run.
*
* Expected result:
* - k_yield() switches to equal/higher-priority threads and never to a
* lower-priority thread.
*
* @see k_yield()
* @see k_thread_create()
*/
ZTEST(context_one_cpu, test_k_yield)
{
thread_evidence = 0;
k_thread_priority_set(k_current_get(), 0);
k_sem_init(&sem_thread, 0, UINT_MAX);
k_thread_create(&thread_data1, thread_stack1, THREAD_STACKSIZE,
k_yield_entry, NULL, NULL,
NULL, K_PRIO_COOP(THREAD_PRIORITY), 0, K_NO_WAIT);
zassert_equal(thread_evidence, 1,
"Thread did not execute as expected!: %d", thread_evidence);
k_sem_give(&sem_thread);
k_sem_give(&sem_thread);
k_sem_give(&sem_thread);
}
/**
* @brief Verify a cooperative thread observes correct identity and ISR context.
*
* @ingroup tests_kernel_context
*
* @details
* A thread started with k_thread_create() must actually run, and from both
* thread and ISR context the kernel must report the correct identity and
* execution context (k_current_get(), k_is_in_isr()) for a cooperative thread.
* This complements test_ctx_thread(), which covers the preemptible case.
*
* Test steps:
* - Create a cooperative thread, passing the spawning thread's id as its arg.
* - In the worker: confirm its own id differs from the spawner, trigger an ISR
* that reports back the interrupted thread's id and K_ISR context, and confirm
* k_is_in_isr() is false and the priority is cooperative in thread context.
* - Join the worker and confirm it ran exactly once.
*
* Expected result:
* - The worker runs to completion, its identity differs from the spawner, and
* all context/identity checks pass.
*
* @see k_thread_create()
* @see k_current_get()
* @see k_is_in_isr()
*/
ZTEST(context_one_cpu, test_ctx_coop_thread)
{
k_tid_t tid;
thread_evidence = 0;
tid = k_thread_create(&thread_data3, thread_stack3, THREAD_STACKSIZE,
kernel_thread_entry, k_current_get(), NULL,
NULL, K_PRIO_COOP(THREAD_PRIORITY), 0, K_NO_WAIT);
/* Wait for the cooperative worker to finish its context checks so any
* failed assertion it raises is observed before the test returns.
*/
zassert_equal(k_thread_join(tid, K_FOREVER), 0,
"join with cooperative worker failed");
zassert_equal(thread_evidence, 1,
"cooperative thread did not run exactly once: %d",
thread_evidence);
}
static void *context_setup(void)
{
kernel_init_objects();
return NULL;
}
ZTEST_SUITE(context_cpu_idle, NULL, context_setup, NULL, NULL, NULL);
ZTEST_SUITE(context, NULL, context_setup, NULL, NULL, NULL);
ZTEST_SUITE(context_one_cpu, NULL, context_setup,
ztest_simple_1cpu_before, ztest_simple_1cpu_after, NULL);