/* 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 #include #include #include #include #include #include #include #ifdef RTEMS_SMP #include #endif 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; #ifdef RTEMS_SMP Per_CPU_Job job; Per_CPU_Job_context job_context; #endif 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; } #ifdef RTEMS_SMP static void T_interrupt_blocked(void *arg) { T_interrupt_context *ctx; ctx = arg; (*ctx->blocked)(ctx->arg); } #endif 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, #ifdef RTEMS_SMP .job = { .context = &T_interrupt_instance.job_context }, .job_context = { .handler = T_interrupt_blocked, .arg = &T_interrupt_instance }, #endif .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) { #ifdef RTEMS_SMP Per_CPU_Control *cpu_self; /* * In SMP configurations, the thread switch extension * runs in a very restricted environment. Interrupts * are disabled and the caller owns the per-CPU lock. * In order to avoid deadlocks at SMP lock level, we * have to use an SMP job which runs later in the * context of the inter-processor interrupt. */ cpu_self = _Per_CPU_Get(); _Per_CPU_Add_job(cpu_self, &ctx->job); _SMP_Send_message(_Per_CPU_Get_index(cpu_self), SMP_MESSAGE_PERFORM_JOBS); #else (*ctx->blocked)(ctx->arg); #endif } } } 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 const 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; }