tests: kernel: test_context: Porting legacy tests to unified kernel
This is the port of the legacy/kernel/test_context test case to the unified kernel Change-Id: I344ac240eb3b48042477f8875115fdc3fca1154a Signed-off-by: Sergio Rodriguez <sergio.sf.rodriguez@intel.com> Signed-off-by: Anas Nashif <anas.nashif@intel.com>
This commit is contained in:
parent
b10ab24c02
commit
a6a1360b24
6 changed files with 1063 additions and 0 deletions
4
tests/kernel/context/Makefile
Normal file
4
tests/kernel/context/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
CONF_FILE ?= prj.conf
|
||||
BOARD ?= qemu_x86
|
||||
|
||||
include $(ZEPHYR_BASE)/Makefile.inc
|
125
tests/kernel/context/README.txt
Normal file
125
tests/kernel/context/README.txt
Normal file
|
@ -0,0 +1,125 @@
|
|||
Title: Context and IRQ APIs
|
||||
|
||||
Description:
|
||||
|
||||
This test verifies that the kernel CPU and context APIs operate as expected.
|
||||
|
||||
|
||||
APIs tested in this test set
|
||||
============================
|
||||
|
||||
k_thread_spawn
|
||||
- start a helper fiber to help with k_yield() tests
|
||||
- start a fiber to test fiber related functionality
|
||||
|
||||
k_yield
|
||||
- Called by a higher priority fiber when there is another fiber
|
||||
- Called by an equal priority fiber when there is another fiber
|
||||
- Called by a lower priority fiber when there is another fiber
|
||||
|
||||
k_current_get
|
||||
- Called from an ISR (interrupted a task)
|
||||
- Called from an ISR (interrupted a fiber)
|
||||
- Called from a task
|
||||
- Called from a fiber
|
||||
|
||||
k_is_in_isr
|
||||
- Called from an ISR that interrupted a task
|
||||
- Called from an ISR that interrupted a fiber
|
||||
- Called from a task
|
||||
- Called from a fiber
|
||||
|
||||
k_cpu_idle
|
||||
- CPU to be woken up by tick timer. Thus, after each call, the tick count
|
||||
should have advanced by one tick.
|
||||
|
||||
irq_lock
|
||||
- 1. Count the number of calls to _tick_get_32() before a tick expires.
|
||||
- 2. Once determined, call _tick_get_32() many more times than that
|
||||
with interrupts locked. Check that the tick count remains unchanged.
|
||||
|
||||
irq_unlock
|
||||
- Continuation irq_lock: unlock interrupts, loop and verify the tick
|
||||
count changes.
|
||||
|
||||
irq_offload
|
||||
- Used when triggering an ISR to perform ISR context work.
|
||||
|
||||
irq_enable
|
||||
irq_disable
|
||||
- Use these routines to disable and enable timer interrupts so that they can
|
||||
be tested in the same way as irq_lock() and irq_unlock().
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
Building and Running Project:
|
||||
|
||||
This project outputs to the console. It can be built and executed
|
||||
on QEMU as follows:
|
||||
|
||||
make qemu
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
Troubleshooting:
|
||||
|
||||
Problems caused by out-dated project information can be addressed by
|
||||
issuing one of the following commands then rebuilding the project:
|
||||
|
||||
make clean # discard results of previous builds
|
||||
# but keep existing configuration info
|
||||
or
|
||||
make pristine # discard results of previous builds
|
||||
# and restore pre-defined configuration info
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
Sample Output:
|
||||
|
||||
tc_start() - Test kernel CPU and thread routines
|
||||
Initializing kernel objects
|
||||
Testing k_cpu_idle()
|
||||
Testing interrupt locking and unlocking
|
||||
Testing irq_disable() and irq_enable()
|
||||
Testing some kernel context routines
|
||||
Testing k_current_get() from an ISR and task
|
||||
Testing k_is_in_isr() from an ISR
|
||||
Testing k_is_in_isr() from a preemtible thread
|
||||
Spawning a thread from a task
|
||||
Thread to test k_current_get() and k_is_in_isr()
|
||||
Thread to test k_yield()
|
||||
Testing k_busy_wait()
|
||||
Thread busy waiting for 20000 usecs
|
||||
Thread busy waiting completed
|
||||
Testing k_sleep()
|
||||
thread sleeping for 50 milliseconds
|
||||
thread back from sleep
|
||||
Testing k_thread_spawn() without cancellation
|
||||
thread (q order: 2, t/o: 500) is running
|
||||
got thread (q order: 2, t/o: 500) as expected
|
||||
thread (q order: 3, t/o: 750) is running
|
||||
got thread (q order: 3, t/o: 750) as expected
|
||||
thread (q order: 0, t/o: 1000) is running
|
||||
got thread (q order: 0, t/o: 1000) as expected
|
||||
thread (q order: 6, t/o: 1250) is running
|
||||
got thread (q order: 6, t/o: 1250) as expected
|
||||
thread (q order: 1, t/o: 1500) is running
|
||||
got thread (q order: 1, t/o: 1500) as expected
|
||||
thread (q order: 4, t/o: 1750) is running
|
||||
got thread (q order: 4, t/o: 1750) as expected
|
||||
thread (q order: 5, t/o: 2000) is running
|
||||
got thread (q order: 5, t/o: 2000) as expected
|
||||
Testing k_thread_spawn() with cancellations
|
||||
cancelling [q order: 0, t/o: 1000, t/o order: 0]
|
||||
thread (q order: 3, t/o: 750) is running
|
||||
got (q order: 3, t/o: 750, t/o order 1) as expected
|
||||
thread (q order: 0, t/o: 1000) is running
|
||||
got (q order: 0, t/o: 1000, t/o order 2) as expected
|
||||
cancelling [q order: 3, t/o: 750, t/o order: 3]
|
||||
cancelling [q order: 4, t/o: 1750, t/o order: 4]
|
||||
thread (q order: 4, t/o: 1750) is running
|
||||
got (q order: 4, t/o: 1750, t/o order 5) as expected
|
||||
cancelling [q order: 6, t/o: 1250, t/o order: 6]
|
||||
PASS - main.
|
||||
===================================================================
|
||||
PROJECT EXECUTION SUCCESSFUL
|
1
tests/kernel/context/prj.conf
Normal file
1
tests/kernel/context/prj.conf
Normal file
|
@ -0,0 +1 @@
|
|||
CONFIG_IRQ_OFFLOAD=y
|
3
tests/kernel/context/src/Makefile
Normal file
3
tests/kernel/context/src/Makefile
Normal file
|
@ -0,0 +1,3 @@
|
|||
ccflags-y += -I${ZEPHYR_BASE}/tests/include
|
||||
|
||||
obj-y = context.o
|
928
tests/kernel/context/src/context.c
Normal file
928
tests/kernel/context/src/context.c
Normal file
|
@ -0,0 +1,928 @@
|
|||
/* context.c - test context and thread APIs */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2012-2015 Wind River Systems, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* DESCRIPTION
|
||||
* This module tests the following CPU and thread related routines:
|
||||
* k_thread_spawn, k_yield(), k_is_in_isr(),
|
||||
* k_current_get(), k_cpu_idle(),
|
||||
* irq_lock(), irq_unlock(),
|
||||
* irq_offload(), irq_enable(), irq_disable(),
|
||||
*/
|
||||
|
||||
#include <tc_util.h>
|
||||
#include <kernel_structs.h>
|
||||
#include <arch/cpu.h>
|
||||
#include <irq_offload.h>
|
||||
|
||||
#include <util_test_common.h>
|
||||
|
||||
/*
|
||||
* Include board.h from platform to get IRQ number.
|
||||
* NOTE: Cortex-M does not need IRQ numbers
|
||||
*/
|
||||
#if !defined(CONFIG_CPU_CORTEX_M)
|
||||
#include <board.h>
|
||||
#endif
|
||||
|
||||
#define THREAD_STACKSIZE 384
|
||||
#define THREAD_PRIORITY 4
|
||||
|
||||
#define THREAD_SELF_CMD 0
|
||||
#define EXEC_CTX_TYPE_CMD 1
|
||||
|
||||
#define UNKNOWN_COMMAND -1
|
||||
|
||||
/*
|
||||
* Get the timer type dependent IRQ number. If timer type
|
||||
* is not defined in platform, generate an error
|
||||
*/
|
||||
#if defined(CONFIG_HPET_TIMER)
|
||||
#define TICK_IRQ CONFIG_HPET_TIMER_IRQ
|
||||
#elif defined(CONFIG_LOAPIC_TIMER)
|
||||
#if defined(CONFIG_LOAPIC)
|
||||
#define TICK_IRQ CONFIG_LOAPIC_TIMER_IRQ
|
||||
#else
|
||||
/* MVIC case */
|
||||
#define TICK_IRQ CONFIG_MVIC_TIMER_IRQ
|
||||
#endif
|
||||
#elif defined(CONFIG_ALTERA_AVALON_TIMER)
|
||||
#define TICK_IRQ TIMER_0_IRQ
|
||||
#elif defined(CONFIG_ARCV2_TIMER)
|
||||
#define TICK_IRQ IRQ_TIMER0
|
||||
#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.
|
||||
*/
|
||||
#else
|
||||
/* generate an error */
|
||||
#error Timer type is not defined for this platform
|
||||
#endif
|
||||
|
||||
/* Nios II doesn't have a power saving instruction, so k_cpu_idle()
|
||||
* returns immediately
|
||||
*/
|
||||
#if !defined(CONFIG_NIOS2)
|
||||
#define HAS_POWERSAVE_INSTRUCTION
|
||||
#endif
|
||||
|
||||
extern uint32_t _tick_get_32(void);
|
||||
extern int64_t _tick_get(void);
|
||||
|
||||
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_detected_error;
|
||||
static int thread_evidence;
|
||||
|
||||
static char __stack thread_stack1[THREAD_STACKSIZE];
|
||||
static char __stack thread_stack2[THREAD_STACKSIZE];
|
||||
|
||||
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>.
|
||||
*
|
||||
* @return N/A
|
||||
*/
|
||||
static void isr_handler(void *data)
|
||||
{
|
||||
ARG_UNUSED(data);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Initialize kernel objects
|
||||
*
|
||||
* This routine initializes the kernel objects used in this module's tests.
|
||||
*
|
||||
* @return TC_PASS
|
||||
*/
|
||||
static int kernel_init_objects(void)
|
||||
{
|
||||
k_sem_init(&sem_thread, 0, UINT_MAX);
|
||||
k_sem_init(&reply_timeout, 0, UINT_MAX);
|
||||
k_timer_init(&timer, NULL, NULL);
|
||||
k_fifo_init(&timeout_order_fifo);
|
||||
|
||||
return TC_PASS;
|
||||
}
|
||||
|
||||
#ifdef HAS_POWERSAVE_INSTRUCTION
|
||||
/**
|
||||
*
|
||||
* @brief Test the k_cpu_idle() routine
|
||||
*
|
||||
* This tests the k_cpu_idle() routine. The first thing it does is align to
|
||||
* a tick boundary. The only source of interrupts while the test is running is
|
||||
* expected to be the tick clock timer which should wake the CPU. Thus after
|
||||
* each call to k_cpu_idle(), the tick count should be one higher.
|
||||
*
|
||||
* @return TC_PASS on success
|
||||
* @return TC_FAIL on failure
|
||||
*/
|
||||
static int test_kernel_cpu_idle(void)
|
||||
{
|
||||
int tick; /* current tick count */
|
||||
int i; /* loop variable */
|
||||
|
||||
/* Align to a "tick boundary". */
|
||||
tick = _tick_get_32();
|
||||
while (tick == _tick_get_32()) {
|
||||
}
|
||||
|
||||
tick = _tick_get_32();
|
||||
for (i = 0; i < 5; i++) { /* Repeat the test five times */
|
||||
k_cpu_idle();
|
||||
tick++;
|
||||
if (_tick_get_32() != tick) {
|
||||
return TC_FAIL;
|
||||
}
|
||||
}
|
||||
return TC_PASS;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
* @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()
|
||||
*
|
||||
* @return N/A
|
||||
*/
|
||||
void irq_unlock_wrapper(int imask)
|
||||
{
|
||||
irq_unlock(imask);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief A wrapper for irq_disable()
|
||||
*
|
||||
* @return <irq>
|
||||
*/
|
||||
int irq_disable_wrapper(int irq)
|
||||
{
|
||||
irq_disable(irq);
|
||||
return irq;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief A wrapper for irq_enable()
|
||||
*
|
||||
* @return N/A
|
||||
*/
|
||||
void irq_enable_wrapper(int irq)
|
||||
{
|
||||
irq_enable(irq);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Test routines for disabling and enabling ints
|
||||
*
|
||||
* This routine tests the routines for disabling and enabling interrupts.
|
||||
* These include irq_lock() and irq_unlock(), irq_disable() and irq_enable().
|
||||
*
|
||||
* @return TC_PASS on success
|
||||
* @return TC_FAIL on failure
|
||||
*/
|
||||
static int test_kernel_interrupts(disable_int_func disable_int,
|
||||
enable_int_func enable_int, int irq)
|
||||
{
|
||||
unsigned long long count = 0;
|
||||
unsigned long long i = 0;
|
||||
int tick;
|
||||
int tick2;
|
||||
int imask;
|
||||
|
||||
/* Align to a "tick boundary" */
|
||||
tick = _tick_get_32();
|
||||
while (_tick_get_32() == tick) {
|
||||
}
|
||||
|
||||
tick++;
|
||||
while (_tick_get_32() == tick) {
|
||||
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 = _tick_get_32();
|
||||
for (i = 0; i < count; i++) {
|
||||
_tick_get_32();
|
||||
}
|
||||
|
||||
tick2 = _tick_get_32();
|
||||
|
||||
/*
|
||||
* Re-enable interrupts before returning (for both success and failure
|
||||
* cases).
|
||||
*/
|
||||
|
||||
enable_int(imask);
|
||||
|
||||
if (tick2 != tick) {
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
/* Now repeat with interrupts unlocked. */
|
||||
for (i = 0; i < count; i++) {
|
||||
_tick_get_32();
|
||||
}
|
||||
|
||||
return (tick == _tick_get_32()) ? TC_FAIL : TC_PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Test some context routines from a preemptible thread
|
||||
*
|
||||
* This routines tests the k_current_get() and
|
||||
* k_is_in_isr() routines from both a preemtible thread and an ISR (that
|
||||
* interrupted a preemtible thread). Checking those routines with cooperative
|
||||
* threads are done elsewhere.
|
||||
*
|
||||
* @return TC_PASS on success
|
||||
* @return TC_FAIL on failure
|
||||
*/
|
||||
static int test_kernel_ctx_task(void)
|
||||
{
|
||||
k_tid_t self_thread_id;
|
||||
|
||||
TC_PRINT("Testing k_current_get() from an ISR and task\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();
|
||||
if (isr_info.error || isr_info.data != (void *)self_thread_id) {
|
||||
/*
|
||||
* Either the ISR detected an error, or the ISR context ID
|
||||
* does not match the interrupted task's thread ID.
|
||||
*/
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
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();
|
||||
if (isr_info.error || isr_info.value != K_ISR) {
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
TC_PRINT("Testing k_is_in_isr() from a preemtible thread\n");
|
||||
if (k_is_in_isr() || _current->base.prio < 0) {
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
return TC_PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* This routine may set <thread_detected_error> to the following values:
|
||||
* 1 - if thread ID matches that of the task
|
||||
* 2 - if thread ID taken during ISR does not match that of the thread
|
||||
* 3 - k_is_in_isr() when called from an ISR is false
|
||||
* 4 - k_is_in_isr() when called from a thread is true
|
||||
* 5 - if thread is not a cooperative thread
|
||||
*
|
||||
* @return TC_PASS on success
|
||||
* @return TC_FAIL on failure
|
||||
*/
|
||||
static int test_kernel_thread(k_tid_t task_thread_id)
|
||||
{
|
||||
k_tid_t self_thread_id;
|
||||
|
||||
self_thread_id = k_current_get();
|
||||
if (self_thread_id == task_thread_id) {
|
||||
thread_detected_error = 1;
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
isr_info.command = THREAD_SELF_CMD;
|
||||
isr_info.error = 0;
|
||||
isr_handler_trigger();
|
||||
if (isr_info.error || isr_info.data != (void *)self_thread_id) {
|
||||
/*
|
||||
* Either the ISR detected an error, or the ISR context ID
|
||||
* does not match the interrupted thread's thread ID.
|
||||
*/
|
||||
thread_detected_error = 2;
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
isr_info.command = EXEC_CTX_TYPE_CMD;
|
||||
isr_info.error = 0;
|
||||
isr_handler_trigger();
|
||||
if (isr_info.error || (isr_info.value != K_ISR)) {
|
||||
thread_detected_error = 3;
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
if (k_is_in_isr()) {
|
||||
thread_detected_error = 4;
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
if (_current->base.prio >= 0) {
|
||||
thread_detected_error = 5;
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
return TC_PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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 behaviour of the k_yield() routine.
|
||||
*
|
||||
* @param arg1 unused
|
||||
* @param arg2 unused
|
||||
*
|
||||
* @return N/A
|
||||
*/
|
||||
|
||||
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 Test the k_yield() routine
|
||||
*
|
||||
* This routine tests the k_yield() routine. It starts another thread
|
||||
* (thus also testing k_thread_spawn() and checks that behaviour of
|
||||
* k_yield() against the cases of there being a higher priority thread,
|
||||
* a lower priority thread, and another thread of equal priority.
|
||||
*
|
||||
* On error, it may set <thread_detected_error> to one of the following values:
|
||||
* 10 - helper thread ran prematurely
|
||||
* 11 - k_yield() did not yield to a higher priority thread
|
||||
* 12 - k_yield() did not yield to an equal prioirty thread
|
||||
* 13 - k_yield() yielded to a lower priority thread
|
||||
*
|
||||
* @return TC_PASS on success
|
||||
* @return TC_FAIL on failure
|
||||
*/
|
||||
static int test_k_yield(void)
|
||||
{
|
||||
k_tid_t self_thread_id;
|
||||
|
||||
/*
|
||||
* 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 a task.
|
||||
*/
|
||||
|
||||
self_thread_id = k_current_get();
|
||||
thread_evidence = 0;
|
||||
|
||||
k_thread_spawn(thread_stack2, THREAD_STACKSIZE, thread_helper,
|
||||
NULL, NULL, NULL,
|
||||
K_PRIO_COOP(THREAD_PRIORITY - 1), 0, 0);
|
||||
|
||||
if (thread_evidence != 0) {
|
||||
/* ERROR! Helper spawned at higher */
|
||||
thread_detected_error = 10; /* priority ran prematurely. */
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that the thread will yield to the higher priority helper.
|
||||
* <thread_evidence> is still 0.
|
||||
*/
|
||||
|
||||
k_yield();
|
||||
|
||||
if (thread_evidence == 0) {
|
||||
/* ERROR! Did not yield to higher */
|
||||
thread_detected_error = 11; /* priority thread. */
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
if (thread_evidence > 1) {
|
||||
/* ERROR! Helper did not yield to */
|
||||
thread_detected_error = 12; /* equal priority thread. */
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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();
|
||||
|
||||
if (thread_evidence != 1) {
|
||||
/* ERROR! Context switched to a lower */
|
||||
thread_detected_error = 13; /* priority thread! */
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Block on <sem_thread>. This will allow the helper thread to
|
||||
* complete. The main task will wake this thread.
|
||||
*/
|
||||
|
||||
k_sem_take(&sem_thread, K_FOREVER);
|
||||
|
||||
return TC_PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Entry point to thread started by the task
|
||||
*
|
||||
* This routine is the entry point to the thread started by the task.
|
||||
*
|
||||
* @param task_thread_id thread ID of the spawning task
|
||||
* @param arg1 unused
|
||||
* @param arg2 unused
|
||||
*
|
||||
* @return N/A
|
||||
*/
|
||||
static void thread_entry(void *task_thread_id, void *arg1, void *arg2)
|
||||
{
|
||||
int rv;
|
||||
|
||||
ARG_UNUSED(arg1);
|
||||
ARG_UNUSED(arg2);
|
||||
|
||||
thread_evidence++; /* Prove to the task that the thread has run */
|
||||
k_sem_take(&sem_thread, K_FOREVER);
|
||||
|
||||
rv = test_kernel_thread((k_tid_t) task_thread_id);
|
||||
if (rv != TC_PASS) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Allow the task to print any messages before the next test runs */
|
||||
k_sem_take(&sem_thread, K_FOREVER);
|
||||
|
||||
rv = test_k_yield();
|
||||
if (rv != TC_PASS) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Timeout tests
|
||||
*
|
||||
* Test the k_sleep() API, as well as the k_thread_spawn() 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 char __stack timeout_stacks[NUM_TIMEOUT_THREADS][THREAD_STACKSIZE];
|
||||
|
||||
/* a thread busy waits, then reports through a fifo */
|
||||
static void test_busy_wait(void *mseconds, void *arg2, void *arg3)
|
||||
{
|
||||
uint32_t usecs;
|
||||
|
||||
ARG_UNUSED(arg2);
|
||||
ARG_UNUSED(arg3);
|
||||
|
||||
usecs = (int)mseconds * 1000;
|
||||
|
||||
TC_PRINT("Thread busy waiting for %d usecs\n", usecs);
|
||||
k_busy_wait(usecs);
|
||||
TC_PRINT("Thread busy waiting completed\n");
|
||||
|
||||
/*
|
||||
* 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 task 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 test_thread_sleep(void *delta, void *arg2, void *arg3)
|
||||
{
|
||||
int64_t timestamp;
|
||||
int timeout = (int)delta;
|
||||
|
||||
ARG_UNUSED(arg2);
|
||||
ARG_UNUSED(arg3);
|
||||
|
||||
TC_PRINT(" thread sleeping for %d milliseconds\n", timeout);
|
||||
timestamp = k_uptime_get();
|
||||
k_sleep(timeout);
|
||||
timestamp = k_uptime_get() - timestamp;
|
||||
TC_PRINT(" thread back from sleep\n");
|
||||
|
||||
if (timestamp < timeout || timestamp > timeout + 10) {
|
||||
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[(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);
|
||||
}
|
||||
|
||||
static int test_timeout(void)
|
||||
{
|
||||
struct timeout_order *data;
|
||||
int32_t timeout;
|
||||
int rv;
|
||||
int i;
|
||||
|
||||
/* test k_busy_wait() */
|
||||
TC_PRINT("Testing k_busy_wait()\n");
|
||||
timeout = 20; /* in ms */
|
||||
|
||||
k_thread_spawn(timeout_stacks[0], THREAD_STACKSIZE, test_busy_wait,
|
||||
(void *)(intptr_t) timeout, NULL,
|
||||
NULL, K_PRIO_COOP(THREAD_PRIORITY), 0, 0);
|
||||
|
||||
rv = k_sem_take(&reply_timeout, timeout * 2);
|
||||
|
||||
if (rv) {
|
||||
TC_ERROR(" *** task timed out waiting for " "k_busy_wait()\n");
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
/* test k_sleep() */
|
||||
|
||||
TC_PRINT("Testing k_sleep()\n");
|
||||
timeout = 50;
|
||||
|
||||
k_thread_spawn(timeout_stacks[0], THREAD_STACKSIZE, test_thread_sleep,
|
||||
(void *)(intptr_t) timeout, NULL,
|
||||
NULL, K_PRIO_COOP(THREAD_PRIORITY), 0, 0);
|
||||
|
||||
rv = k_sem_take(&reply_timeout, timeout * 2);
|
||||
if (rv) {
|
||||
TC_ERROR(" *** task timed out waiting for thread on "
|
||||
"k_sleep().\n");
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
/* test k_thread_spawn() without cancellation */
|
||||
TC_PRINT("Testing k_thread_spawn() without cancellation\n");
|
||||
|
||||
for (i = 0; i < NUM_TIMEOUT_THREADS; i++) {
|
||||
k_thread_spawn(timeout_stacks[i], THREAD_STACKSIZE,
|
||||
delayed_thread,
|
||||
(void *)i,
|
||||
NULL, NULL,
|
||||
K_PRIO_COOP(5), 0, timeouts[i].timeout);
|
||||
}
|
||||
for (i = 0; i < NUM_TIMEOUT_THREADS; i++) {
|
||||
data = k_fifo_get(&timeout_order_fifo, 750);
|
||||
if (!data) {
|
||||
TC_ERROR
|
||||
(" *** timeout while waiting for delayed thread\n");
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
if (data->timeout_order != i) {
|
||||
TC_ERROR(" *** wrong delayed thread ran (got %d, "
|
||||
"expected %d)\n", data->timeout_order, i);
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
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, 750);
|
||||
|
||||
if (data) {
|
||||
TC_ERROR(" *** got something unexpected in the fifo\n");
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
/* test k_thread_spawn() with cancellation */
|
||||
TC_PRINT("Testing k_thread_spawn() 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_spawn(timeout_stacks[i], THREAD_STACKSIZE,
|
||||
delayed_thread,
|
||||
(void *)i, NULL, NULL,
|
||||
K_PRIO_COOP(5), 0, 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;
|
||||
}
|
||||
}
|
||||
k_thread_cancel(delayed_threads[j]);
|
||||
++next_cancellation;
|
||||
continue;
|
||||
}
|
||||
|
||||
data = k_fifo_get(&timeout_order_fifo, 2750);
|
||||
|
||||
if (!data) {
|
||||
TC_ERROR
|
||||
(" *** timeout while waiting for delayed thread\n");
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
if (data->timeout_order != i) {
|
||||
TC_ERROR(" *** wrong delayed thread ran (got %d, "
|
||||
"expected %d)\n", data->timeout_order, i);
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
TC_PRINT(" got (q order: %d, t/o: %d, t/o order %d) "
|
||||
"as expected\n", data->q_order, data->timeout,
|
||||
data->timeout_order);
|
||||
}
|
||||
|
||||
if (num_cancellations != next_cancellation) {
|
||||
TC_ERROR(" *** wrong number of cancellations (expected %d, "
|
||||
"got %d\n", num_cancellations, next_cancellation);
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
/* ensure no more thread fire */
|
||||
data = k_fifo_get(&timeout_order_fifo, 750);
|
||||
if (data) {
|
||||
TC_ERROR(" *** got something unexpected in the fifo\n");
|
||||
return TC_FAIL;
|
||||
}
|
||||
|
||||
return TC_PASS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Entry point to timer tests
|
||||
*
|
||||
* This is the entry point to the CPU and thread tests.
|
||||
*
|
||||
* @return N/A
|
||||
*/
|
||||
void main(void)
|
||||
{
|
||||
int rv; /* return value from tests */
|
||||
|
||||
thread_detected_error = 0;
|
||||
thread_evidence = 0;
|
||||
|
||||
TC_START("Test kernel CPU and thread routines");
|
||||
|
||||
TC_PRINT("Initializing kernel objects\n");
|
||||
rv = kernel_init_objects();
|
||||
if (rv != TC_PASS) {
|
||||
goto tests_done;
|
||||
}
|
||||
#ifdef HAS_POWERSAVE_INSTRUCTION
|
||||
TC_PRINT("Testing k_cpu_idle()\n");
|
||||
rv = test_kernel_cpu_idle();
|
||||
if (rv != TC_PASS) {
|
||||
goto tests_done;
|
||||
}
|
||||
#endif
|
||||
|
||||
TC_PRINT("Testing interrupt locking and unlocking\n");
|
||||
rv = test_kernel_interrupts(irq_lock_wrapper, irq_unlock_wrapper, -1);
|
||||
if (rv != TC_PASS) {
|
||||
goto tests_done;
|
||||
}
|
||||
#ifdef TICK_IRQ
|
||||
/* Disable interrupts coming from the timer. */
|
||||
|
||||
TC_PRINT("Testing irq_disable() and irq_enable()\n");
|
||||
rv = test_kernel_interrupts(irq_disable_wrapper, irq_enable_wrapper,
|
||||
TICK_IRQ);
|
||||
if (rv != TC_PASS) {
|
||||
goto tests_done;
|
||||
}
|
||||
#endif
|
||||
|
||||
TC_PRINT("Testing some kernel context routines\n");
|
||||
rv = test_kernel_ctx_task();
|
||||
if (rv != TC_PASS) {
|
||||
goto tests_done;
|
||||
}
|
||||
|
||||
TC_PRINT("Spawning a thread from a task\n");
|
||||
thread_evidence = 0;
|
||||
|
||||
k_thread_spawn(thread_stack1, THREAD_STACKSIZE, thread_entry,
|
||||
k_current_get(), NULL,
|
||||
NULL, K_PRIO_COOP(THREAD_PRIORITY), 0, 0);
|
||||
|
||||
if (thread_evidence != 1) {
|
||||
rv = TC_FAIL;
|
||||
TC_ERROR(" - thread did not execute as expected!\n");
|
||||
goto tests_done;
|
||||
}
|
||||
|
||||
/*
|
||||
* The thread ran, now wake it so it can test k_current_get and
|
||||
* k_is_in_isr.
|
||||
*/
|
||||
TC_PRINT("Thread to test k_current_get() and " "k_is_in_isr()\n");
|
||||
k_sem_give(&sem_thread);
|
||||
|
||||
if (thread_detected_error != 0) {
|
||||
rv = TC_FAIL;
|
||||
TC_ERROR(" - failure detected in thread; "
|
||||
"thread_detected_error = %d\n", thread_detected_error);
|
||||
goto tests_done;
|
||||
}
|
||||
|
||||
TC_PRINT("Thread to test k_yield()\n");
|
||||
k_sem_give(&sem_thread);
|
||||
|
||||
if (thread_detected_error != 0) {
|
||||
rv = TC_FAIL;
|
||||
TC_ERROR(" - failure detected in thread; "
|
||||
"thread_detected_error = %d\n", thread_detected_error);
|
||||
goto tests_done;
|
||||
}
|
||||
|
||||
k_sem_give(&sem_thread);
|
||||
|
||||
rv = test_timeout();
|
||||
if (rv != TC_PASS) {
|
||||
goto tests_done;
|
||||
}
|
||||
|
||||
tests_done:
|
||||
TC_END_RESULT(rv);
|
||||
TC_END_REPORT(rv);
|
||||
}
|
2
tests/kernel/context/testcase.ini
Normal file
2
tests/kernel/context/testcase.ini
Normal file
|
@ -0,0 +1,2 @@
|
|||
[test]
|
||||
tags = core bat_commit
|
Loading…
Add table
Add a link
Reference in a new issue