libtest: Add T_interrupt_test()

Update #3199.
This commit is contained in:
Sebastian Huber
2020-07-17 19:42:32 +02:00
parent c9d2405848
commit cc3fd8fcf1
8 changed files with 704 additions and 0 deletions

View File

@@ -1864,6 +1864,7 @@ librtemstest_a_SOURCES += libtest/t-test-checks.c
librtemstest_a_SOURCES += libtest/t-test-checks-eno.c
librtemstest_a_SOURCES += libtest/t-test-checks-psx.c
librtemstest_a_SOURCES += libtest/t-test-hash-sha256.c
librtemstest_a_SOURCES += libtest/t-test-interrupt.c
librtemstest_a_SOURCES += libtest/t-test-malloc.c
librtemstest_a_SOURCES += libtest/t-test-rtems.c
librtemstest_a_SOURCES += libtest/t-test-rtems-context.c

View File

@@ -2360,6 +2360,36 @@ void T_busy(uint_fast32_t);
uint_fast32_t T_get_one_clock_tick_busy(void);
typedef enum {
T_INTERRUPT_TEST_INITIAL,
T_INTERRUPT_TEST_ACTION,
T_INTERRUPT_TEST_BLOCKED,
T_INTERRUPT_TEST_CONTINUE,
T_INTERRUPT_TEST_DONE,
T_INTERRUPT_TEST_EARLY,
T_INTERRUPT_TEST_INTERRUPT,
T_INTERRUPT_TEST_LATE,
T_INTERRUPT_TEST_TIMEOUT
} T_interrupt_test_state;
typedef struct {
void (*prepare)(void *);
void (*action)(void *);
T_interrupt_test_state (*interrupt)(void *);
void (*blocked)(void *);
uint32_t max_iteration_count;
} T_interrupt_test_config;
T_interrupt_test_state T_interrupt_test_change_state(T_interrupt_test_state,
T_interrupt_test_state);
T_interrupt_test_state T_interrupt_test_get_state(void);
void T_interrupt_test_busy_wait_for_interrupt(void);
T_interrupt_test_state T_interrupt_test(const T_interrupt_test_config *config,
void *arg);
void T_report_hash_sha256(T_event, const char *);
void T_check_heap(T_event, const char *);

View File

@@ -0,0 +1,441 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/**
* @file
*
* @ingroup RTEMSTestFrameworkImpl
*
* @brief Implementation of T_interrupt_test().
*/
/*
* Copyright (C) 2020 embedded brains GmbH (http://www.embedded-brains.de)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <rtems/test.h>
#include <rtems/score/atomic.h>
#include <rtems/score/percpu.h>
#include <rtems/score/thread.h>
#include <rtems/score/timecounter.h>
#include <rtems/score/timestampimpl.h>
#include <rtems/score/userextimpl.h>
#include <rtems/score/watchdogimpl.h>
typedef T_interrupt_test_state (*T_interrupt_test_handler)(void *);
#define T_INTERRUPT_SAMPLE_COUNT 8
typedef struct {
uint_fast32_t one_tick_busy;
int64_t t0;
Thread_Control *self;
Atomic_Uint state;
void (*prepare)(void *);
void (*action)(void *);
T_interrupt_test_state (*interrupt)(void *);
void (*blocked)(void *);
void *arg;
Watchdog_Control wdg;
User_extensions_Control ext;
T_fixture_node node;
} T_interrupt_context;
typedef struct {
int64_t t;
int64_t d;
} T_interrupt_clock_time;
static void
T_interrupt_sort(T_interrupt_clock_time *ct, size_t n)
{
size_t i;
/* Bubble sort */
for (i = 1; i < n ; ++i) {
size_t j;
for (j = 0; j < n - i; ++j) {
if (ct[j].d > ct[j + 1].d) {
T_interrupt_clock_time tmp;
tmp = ct[j];
ct[j] = ct[j + 1];
ct[j + 1] = tmp;
}
}
}
}
static int64_t
T_interrupt_time_close_to_tick(void)
{
Watchdog_Interval c0;
Watchdog_Interval c1;
T_interrupt_clock_time ct[12];
Timestamp_Control t;
int32_t ns_per_tick;
size_t i;
size_t n;
ns_per_tick = (int32_t)_Watchdog_Nanoseconds_per_tick;
n = RTEMS_ARRAY_SIZE(ct);
c0 = _Watchdog_Ticks_since_boot;
for (i = 0; i < n; ++i) {
do {
c1 = _Watchdog_Ticks_since_boot;
t = _Timecounter_Sbinuptime();
} while (c0 == c1);
c0 = c1;
ct[i].t = sbttons(t);
}
for (i = 1; i < n; ++i) {
int64_t d;
d = (ct[i].t - ct[1].t) % ns_per_tick;
if (d > ns_per_tick / 2) {
d -= ns_per_tick;
}
ct[i].d = d;
}
/*
* Use the median and not the arithmetic mean since on simulator
* platforms there may be outliers.
*/
T_interrupt_sort(&ct[1], n - 1);
return ct[1 + (n - 1) / 2].t;
}
static void
T_interrupt_watchdog(Watchdog_Control *wdg)
{
T_interrupt_context *ctx;
ISR_Level level;
T_interrupt_test_state state;
unsigned int expected;
ctx = RTEMS_CONTAINER_OF(wdg, T_interrupt_context, wdg);
_ISR_Local_disable(level);
_Watchdog_Per_CPU_insert_ticks(&ctx->wdg,
_Watchdog_Get_CPU(&ctx->wdg), 1);
_ISR_Local_enable(level);
state = (*ctx->interrupt)(ctx->arg);
expected = T_INTERRUPT_TEST_ACTION;
_Atomic_Compare_exchange_uint(&ctx->state, &expected,
state, ATOMIC_ORDER_RELAXED, ATOMIC_ORDER_RELAXED);
}
static void
T_interrupt_watchdog_insert(T_interrupt_context *ctx)
{
ISR_Level level;
_ISR_Local_disable(level);
_Watchdog_Per_CPU_insert_ticks(&ctx->wdg, _Per_CPU_Get(), 1);
_ISR_Local_enable(level);
}
static void
T_interrupt_watchdog_remove(T_interrupt_context *ctx)
{
ISR_Level level;
_ISR_Local_disable(level);
_Watchdog_Per_CPU_remove_ticks(&ctx->wdg);
_ISR_Local_enable(level);
}
static void
T_interrupt_init_once(T_interrupt_context *ctx)
{
ctx->t0 = T_interrupt_time_close_to_tick();
ctx->one_tick_busy = T_get_one_clock_tick_busy();
}
static T_interrupt_test_state
T_interrupt_continue(void *arg)
{
(void)arg;
return T_INTERRUPT_TEST_CONTINUE;
}
static void
T_interrupt_do_nothing(void *arg)
{
(void)arg;
}
static void T_interrupt_thread_switch(Thread_Control *, Thread_Control *);
static T_interrupt_context T_interrupt_instance = {
.interrupt = T_interrupt_continue,
.blocked = T_interrupt_do_nothing,
.wdg = WATCHDOG_INITIALIZER(T_interrupt_watchdog),
.ext = {
.Callouts = {
.thread_switch = T_interrupt_thread_switch
}
}
};
T_interrupt_test_state
T_interrupt_test_change_state(T_interrupt_test_state expected_state,
T_interrupt_test_state desired_state)
{
T_interrupt_context *ctx;
unsigned int expected;
ctx = &T_interrupt_instance;
expected = expected_state;
_Atomic_Compare_exchange_uint(&ctx->state, &expected,
desired_state, ATOMIC_ORDER_RELAXED, ATOMIC_ORDER_RELAXED);
return expected;
}
T_interrupt_test_state
T_interrupt_test_get_state(void)
{
T_interrupt_context *ctx;
ctx = &T_interrupt_instance;
return _Atomic_Load_uint(&ctx->state, ATOMIC_ORDER_RELAXED);
}
void
T_interrupt_test_busy_wait_for_interrupt(void)
{
T_interrupt_context *ctx;
unsigned int state;
ctx = &T_interrupt_instance;
do {
state = _Atomic_Load_uint(&ctx->state, ATOMIC_ORDER_RELAXED);
} while (state == T_INTERRUPT_TEST_ACTION);
}
static void
T_interrupt_thread_switch(Thread_Control *executing, Thread_Control *heir)
{
T_interrupt_context *ctx;
(void)heir;
ctx = &T_interrupt_instance;
if (ctx->self == executing) {
T_interrupt_test_state state;
state = _Atomic_Load_uint(&ctx->state, ATOMIC_ORDER_RELAXED);
if (state != T_INTERRUPT_TEST_INITIAL) {
(*ctx->blocked)(ctx->arg);
}
}
}
static T_interrupt_context *
T_interrupt_setup(const T_interrupt_test_config *config, void *arg)
{
T_interrupt_context *ctx;
T_quiet_assert_not_null(config->action);
T_quiet_assert_not_null(config->interrupt);
ctx = &T_interrupt_instance;
ctx->self = _Thread_Get_executing();
ctx->arg = arg;
ctx->interrupt = config->interrupt;
if (config->blocked != NULL) {
ctx->blocked = config->blocked;
}
if (ctx->t0 == 0) {
T_interrupt_init_once(ctx);
}
_User_extensions_Add_set(&ctx->ext);
T_interrupt_watchdog_insert(ctx);
return ctx;
}
static void
T_interrupt_teardown(void *arg)
{
T_interrupt_context *ctx;
ctx = arg;
ctx->interrupt = T_interrupt_continue;
ctx->blocked = T_interrupt_do_nothing;
T_interrupt_watchdog_remove(ctx);
_User_extensions_Remove_set(&ctx->ext);
ctx->self = NULL;
ctx->arg = NULL;
}
static T_fixture T_interrupt_fixture = {
.teardown = T_interrupt_teardown,
.initial_context = &T_interrupt_instance
};
T_interrupt_test_state
T_interrupt_test(const T_interrupt_test_config *config, void *arg)
{
T_interrupt_context *ctx;
uint_fast32_t lower_bound[T_INTERRUPT_SAMPLE_COUNT];
uint_fast32_t upper_bound[T_INTERRUPT_SAMPLE_COUNT];
uint_fast32_t lower_sum;
uint_fast32_t upper_sum;
int32_t ns_per_tick;
size_t sample;
uint32_t iter;
ctx = T_interrupt_setup(config, arg);
T_push_fixture(&ctx->node, &T_interrupt_fixture);
ns_per_tick = (int32_t)_Watchdog_Nanoseconds_per_tick;
lower_sum = 0;
upper_sum = T_INTERRUPT_SAMPLE_COUNT * ctx->one_tick_busy;
for (sample = 0; sample < T_INTERRUPT_SAMPLE_COUNT; ++sample) {
lower_bound[sample] = 0;
upper_bound[sample] = ctx->one_tick_busy;
}
sample = 0;
for (iter = 0; iter < config->max_iteration_count; ++iter) {
T_interrupt_test_state state;
int64_t t;
int64_t d;
Timestamp_Control s1;
Timestamp_Control s0;
uint_fast32_t busy;
uint_fast32_t delta;
if (config->prepare != NULL) {
(*config->prepare)(arg);
}
/*
* We use some sort of a damped bisection to find the right
* interrupt time point.
*/
busy = (lower_sum + upper_sum) /
(2 * T_INTERRUPT_SAMPLE_COUNT);
t = sbttons(_Timecounter_Sbinuptime());
d = (t - ctx->t0) % ns_per_tick;
t += ns_per_tick / 4 - d;
if (d > ns_per_tick / 8) {
t += ns_per_tick;
}
/*
* The s1 value is a future time point close to 25% of a clock
* tick interval.
*/
s1 = nstosbt(t);
/*
* The path from here to the action call must avoid anything
* which can cause jitters. We wait until 25% of the clock
* tick interval are elapsed using the timecounter. Then we do
* a busy wait and call the action. The interrupt time point
* is controlled by the busy count.
*/
do {
s0 = _Timecounter_Sbinuptime();
} while (s0 < s1);
_Atomic_Store_uint(&ctx->state, T_INTERRUPT_TEST_ACTION,
ATOMIC_ORDER_RELAXED);
T_busy(busy);
(*config->action)(arg);
state = _Atomic_Exchange_uint(&ctx->state,
T_INTERRUPT_TEST_INITIAL, ATOMIC_ORDER_RELAXED);
if (state == T_INTERRUPT_TEST_DONE) {
break;
}
/* Adjust the lower/upper bound of the bisection interval */
if (state == T_INTERRUPT_TEST_EARLY) {
uint_fast32_t lower;
upper_sum -= upper_bound[sample];
upper_sum += busy;
upper_bound[sample] = busy;
/* Round down to make sure no underflow happens */
lower = lower_bound[sample];
delta = lower / 32;
lower_sum -= delta;
lower_bound[sample] = lower - delta;
sample = (sample + 1) % T_INTERRUPT_SAMPLE_COUNT;
} else if (state == T_INTERRUPT_TEST_LATE) {
uint_fast32_t upper;
lower_sum -= lower_bound[sample];
lower_sum += busy;
lower_bound[sample] = busy;
/*
* The one tick busy count value is not really
* trustable on some platforms. Allow the upper bound
* to grow over this value in time.
*/
upper = upper_bound[sample];
delta = (upper + 31) / 32;
upper_sum += delta;
upper_bound[sample] = upper + delta;
sample = (sample + 1) % T_INTERRUPT_SAMPLE_COUNT;
}
}
T_pop_fixture();
if (iter == config->max_iteration_count) {
return T_INTERRUPT_TEST_TIMEOUT;
}
return T_INTERRUPT_TEST_DONE;
}

View File

@@ -1493,6 +1493,15 @@ ttest01_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_FLAGS_ttest01) \
$(support_includes)
endif
if TEST_ttest02
lib_tests += ttest02
lib_screens += ttest02/ttest02.scn
lib_docs += ttest02/ttest02.doc
ttest02_SOURCES = ttest02/init.c
ttest02_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_FLAGS_ttest02) \
$(support_includes)
endif
if TEST_tztest
lib_tests += tztest
lib_screens += tztest/tztest.scn

View File

@@ -225,6 +225,7 @@ RTEMS_TEST_CHECK([termios10])
RTEMS_TEST_CHECK([termios11])
RTEMS_TEST_CHECK([top])
RTEMS_TEST_CHECK([ttest01])
RTEMS_TEST_CHECK([ttest02])
RTEMS_TEST_CHECK([tztest])
RTEMS_TEST_CHECK([uid01])
RTEMS_TEST_CHECK([unlink])

View File

@@ -0,0 +1,174 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Copyright (C) 2020 embedded brains GmbH (http://www.embedded-brains.de)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <rtems/test.h>
#include <rtems/test-info.h>
#include <rtems.h>
static void
prepare(void *arg)
{
Atomic_Uint *state;
state = arg;
_Atomic_Store_uint(state, 0, ATOMIC_ORDER_RELAXED);
}
static void
action(void *arg)
{
Atomic_Uint *state;
unsigned int expected;
bool success_0;
bool success_1;
state = arg;
/*
* This code models a critical section in the operating system. The
* interrupt should happen between the two atomic operations.
*/
expected = 0;
success_0 = _Atomic_Compare_exchange_uint(state, &expected, 1,
ATOMIC_ORDER_RELAXED, ATOMIC_ORDER_RELAXED);
expected = 1;
success_1 = _Atomic_Compare_exchange_uint(state, &expected, 2,
ATOMIC_ORDER_RELAXED, ATOMIC_ORDER_RELAXED);
T_quiet_true(success_0);
T_quiet_true(success_1);
T_interrupt_test_busy_wait_for_interrupt();
}
static T_interrupt_test_state
interrupt(void *arg)
{
Atomic_Uint *state;
unsigned int expected;
if (T_interrupt_test_get_state() != T_INTERRUPT_TEST_ACTION) {
return T_INTERRUPT_TEST_CONTINUE;
}
state = arg;
expected = 1;
if (_Atomic_Compare_exchange_uint(state, &expected, expected,
ATOMIC_ORDER_RELAXED, ATOMIC_ORDER_RELAXED)) {
return T_INTERRUPT_TEST_DONE;
} else if (expected == 0) {
return T_INTERRUPT_TEST_EARLY;
} else {
T_quiet_eq_uint(expected, 2);
return T_INTERRUPT_TEST_LATE;
}
}
static const T_interrupt_test_config done_config = {
.prepare = prepare,
.action = action,
.interrupt = interrupt,
.max_iteration_count = 10000
};
T_TEST_CASE(TestInterruptDone)
{
int i;
for (i = 0; i < 10; ++i) {
Atomic_Uint action_state;
T_interrupt_test_state state;
state = T_interrupt_test(&done_config, &action_state);
T_eq_int(state, T_INTERRUPT_TEST_DONE);
}
}
static const T_interrupt_test_config timeout_config = {
.interrupt = interrupt,
.action = action
};
T_TEST_CASE(TestInterruptTimeout)
{
Atomic_Uint action_state;
T_interrupt_test_state state;
T_plan(1);
state = T_interrupt_test(&timeout_config, &action_state);
T_step_eq_int(0, state, T_INTERRUPT_TEST_TIMEOUT);
}
static void
fatal(void *arg)
{
(void)arg;
T_step(0);
T_stop();
}
static const T_interrupt_test_config fatal_config = {
.prepare = fatal,
.action = action,
.interrupt = interrupt,
.max_iteration_count = 10000
};
T_TEST_CASE(TestInterruptFatal)
{
Atomic_Uint action_state;
T_plan(1);
T_interrupt_test(&fatal_config, &action_state);
T_unreachable();
}
const char rtems_test_name[] = "TTEST 2";
static void
Init(rtems_task_argument argument)
{
rtems_test_run(argument, TEST_STATE);
}
#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER
#define CONFIGURE_APPLICATION_NEEDS_SIMPLE_CONSOLE_DRIVER
#define CONFIGURE_MICROSECONDS_PER_TICK 1000
#define CONFIGURE_MAXIMUM_TASKS 1
#define CONFIGURE_RTEMS_INIT_TASKS_TABLE
#define CONFIGURE_INIT
#include <rtems/confdefs.h>

View File

@@ -0,0 +1,11 @@
This file describes the directives and concepts tested by this test set.
test set name: ttest02
directives:
- T_interrupt_test()
concepts:
- Ensure that the interrupt test works.

View File

@@ -0,0 +1,37 @@
*** BEGIN OF TEST TTEST 2 ***
*** TEST VERSION: 6.0.0.133fcd71c16b87b5c3924c04039a125be9affcfa
*** TEST STATE: EXPECTED_PASS
*** TEST BUILD: RTEMS_SMP
*** TEST TOOLS: 10.0.1 20200406 (RTEMS 6, RSB b69f54d51740810dc54a50662f5da4d4ba0ddd18, Newlib ece49e4)
A:TTEST 2
S:Platform:RTEMS
S:Compiler:10.0.1 20200406 (RTEMS 6, RSB b69f54d51740810dc54a50662f5da4d4ba0ddd18, Newlib ece49e4)
S:Version:6.0.0.133fcd71c16b87b5c3924c04039a125be9affcfa
S:BSP:leon3
S:RTEMS_DEBUG:0
S:RTEMS_MULTIPROCESSING:0
S:RTEMS_POSIX_API:0
S:RTEMS_PROFILING:0
S:RTEMS_SMP:1
B:TestInterruptTimeout
P:0:0:UI1:init.c:130
E:TestInterruptTimeout:N:1:F:0:D:0.042180
B:TestInterruptFatal
P:0:0:UI1:init.c:137
E:TestInterruptFatal:N:1:F:0:D:0.000360
B:TestInterruptDone
P:0:0:UI1:init.c:114
P:1:0:UI1:init.c:114
P:2:0:UI1:init.c:114
P:3:0:UI1:init.c:114
P:4:0:UI1:init.c:114
P:5:0:UI1:init.c:114
P:6:0:UI1:init.c:114
P:7:0:UI1:init.c:114
P:8:0:UI1:init.c:114
P:9:0:UI1:init.c:114
E:TestInterruptDone:N:10:F:0:D:1.233900
Z:TTEST 2:C:3:N:12:F:0:D:1.277400
Y:ReportHash:SHA256:308c997b3220d239738a11dac4133d9f987a34b3f5f9baf1158d1a54f85cc647
*** END OF TEST TTEST 2 ***