summaryrefslogtreecommitdiffstats
path: root/cpukit
diff options
context:
space:
mode:
authorSebastian Huber <sebastian.huber@embedded-brains.de>2020-07-17 19:42:32 +0200
committerSebastian Huber <sebastian.huber@embedded-brains.de>2020-07-23 09:27:47 +0200
commitcc3fd8fcf182cfa35388fc79c14e784a968570ae (patch)
treeb1d2c4cb445837ad51f48a84d893ed05f3d4a4fa /cpukit
parentlibtest: Add rtems_test_run() (diff)
downloadrtems-cc3fd8fcf182cfa35388fc79c14e784a968570ae.tar.bz2
libtest: Add T_interrupt_test()
Update #3199.
Diffstat (limited to 'cpukit')
-rw-r--r--cpukit/Makefile.am1
-rw-r--r--cpukit/include/rtems/test.h30
-rw-r--r--cpukit/libtest/t-test-interrupt.c441
3 files changed, 472 insertions, 0 deletions
diff --git a/cpukit/Makefile.am b/cpukit/Makefile.am
index 89edaefb17..75e119ea3c 100644
--- a/cpukit/Makefile.am
+++ b/cpukit/Makefile.am
@@ -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
diff --git a/cpukit/include/rtems/test.h b/cpukit/include/rtems/test.h
index 5d4c676d16..a7955c735a 100644
--- a/cpukit/include/rtems/test.h
+++ b/cpukit/include/rtems/test.h
@@ -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 *);
diff --git a/cpukit/libtest/t-test-interrupt.c b/cpukit/libtest/t-test-interrupt.c
new file mode 100644
index 0000000000..7012319dd9
--- /dev/null
+++ b/cpukit/libtest/t-test-interrupt.c
@@ -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;
+}