forked from Imagelibrary/rtems
441
cpukit/libtest/t-test-interrupt.c
Normal file
441
cpukit/libtest/t-test-interrupt.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user