source: rtems/cpukit/libtest/t-test-interrupt.c @ cc3fd8fc

Last change on this file since cc3fd8fc was cc3fd8fc, checked in by Sebastian Huber <sebastian.huber@…>, on 07/17/20 at 17:42:32

libtest: Add T_interrupt_test()

Update #3199.

  • Property mode set to 100644
File size: 10.2 KB
Line 
1/* SPDX-License-Identifier: BSD-2-Clause */
2
3/**
4 * @file
5 *
6 * @ingroup RTEMSTestFrameworkImpl
7 *
8 * @brief Implementation of T_interrupt_test().
9 */
10
11/*
12 * Copyright (C) 2020 embedded brains GmbH (http://www.embedded-brains.de)
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
17 * 1. Redistributions of source code must retain the above copyright
18 *    notice, this list of conditions and the following disclaimer.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 *    notice, this list of conditions and the following disclaimer in the
21 *    documentation and/or other materials provided with the distribution.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 * POSSIBILITY OF SUCH DAMAGE.
34 */
35
36#ifdef HAVE_CONFIG_H
37#include "config.h"
38#endif
39
40#include <rtems/test.h>
41
42#include <rtems/score/atomic.h>
43#include <rtems/score/percpu.h>
44#include <rtems/score/thread.h>
45#include <rtems/score/timecounter.h>
46#include <rtems/score/timestampimpl.h>
47#include <rtems/score/userextimpl.h>
48#include <rtems/score/watchdogimpl.h>
49
50typedef T_interrupt_test_state (*T_interrupt_test_handler)(void *);
51
52#define T_INTERRUPT_SAMPLE_COUNT 8
53
54typedef struct {
55        uint_fast32_t one_tick_busy;
56        int64_t t0;
57        Thread_Control *self;
58        Atomic_Uint state;
59        void (*prepare)(void *);
60        void (*action)(void *);
61        T_interrupt_test_state (*interrupt)(void *);
62        void (*blocked)(void *);
63        void *arg;
64        Watchdog_Control wdg;
65        User_extensions_Control ext;
66        T_fixture_node node;
67} T_interrupt_context;
68
69typedef struct {
70        int64_t t;
71        int64_t d;
72} T_interrupt_clock_time;
73
74static void
75T_interrupt_sort(T_interrupt_clock_time *ct, size_t n)
76{
77        size_t i;
78
79        /* Bubble sort */
80        for (i = 1; i < n ; ++i) {
81                size_t j;
82
83                for (j = 0; j < n - i; ++j) {
84                         if (ct[j].d > ct[j + 1].d) {
85                                T_interrupt_clock_time tmp;
86
87                                tmp = ct[j];
88                                ct[j] = ct[j + 1];
89                                ct[j + 1] = tmp;
90                         }
91                }
92        }
93}
94
95static int64_t
96T_interrupt_time_close_to_tick(void)
97{
98        Watchdog_Interval c0;
99        Watchdog_Interval c1;
100        T_interrupt_clock_time ct[12];
101        Timestamp_Control t;
102        int32_t ns_per_tick;
103        size_t i;
104        size_t n;
105
106        ns_per_tick = (int32_t)_Watchdog_Nanoseconds_per_tick;
107        n = RTEMS_ARRAY_SIZE(ct);
108        c0 = _Watchdog_Ticks_since_boot;
109
110        for (i = 0; i < n; ++i) {
111                do {
112                        c1 = _Watchdog_Ticks_since_boot;
113                        t = _Timecounter_Sbinuptime();
114                } while (c0 == c1);
115
116                c0 = c1;
117                ct[i].t = sbttons(t);
118        }
119
120        for (i = 1; i < n; ++i) {
121                int64_t d;
122
123                d = (ct[i].t - ct[1].t) % ns_per_tick;
124
125                if (d > ns_per_tick / 2) {
126                        d -= ns_per_tick;
127                }
128
129                ct[i].d = d;
130        }
131
132        /*
133         * Use the median and not the arithmetic mean since on simulator
134         * platforms there may be outliers.
135         */
136        T_interrupt_sort(&ct[1], n - 1);
137        return ct[1 + (n - 1) / 2].t;
138}
139
140static void
141T_interrupt_watchdog(Watchdog_Control *wdg)
142{
143        T_interrupt_context *ctx;
144        ISR_Level level;
145        T_interrupt_test_state state;
146        unsigned int expected;
147
148        ctx = RTEMS_CONTAINER_OF(wdg, T_interrupt_context, wdg);
149
150        _ISR_Local_disable(level);
151        _Watchdog_Per_CPU_insert_ticks(&ctx->wdg,
152            _Watchdog_Get_CPU(&ctx->wdg), 1);
153        _ISR_Local_enable(level);
154
155        state = (*ctx->interrupt)(ctx->arg);
156
157        expected = T_INTERRUPT_TEST_ACTION;
158        _Atomic_Compare_exchange_uint(&ctx->state, &expected,
159            state, ATOMIC_ORDER_RELAXED, ATOMIC_ORDER_RELAXED);
160}
161
162static void
163T_interrupt_watchdog_insert(T_interrupt_context *ctx)
164{
165        ISR_Level level;
166
167        _ISR_Local_disable(level);
168        _Watchdog_Per_CPU_insert_ticks(&ctx->wdg, _Per_CPU_Get(), 1);
169        _ISR_Local_enable(level);
170}
171
172static void
173T_interrupt_watchdog_remove(T_interrupt_context *ctx)
174{
175        ISR_Level level;
176
177        _ISR_Local_disable(level);
178        _Watchdog_Per_CPU_remove_ticks(&ctx->wdg);
179        _ISR_Local_enable(level);
180}
181
182static void
183T_interrupt_init_once(T_interrupt_context *ctx)
184{
185        ctx->t0 = T_interrupt_time_close_to_tick();
186        ctx->one_tick_busy = T_get_one_clock_tick_busy();
187}
188
189static T_interrupt_test_state
190T_interrupt_continue(void *arg)
191{
192        (void)arg;
193        return T_INTERRUPT_TEST_CONTINUE;
194}
195
196static void
197T_interrupt_do_nothing(void *arg)
198{
199        (void)arg;
200}
201
202static void T_interrupt_thread_switch(Thread_Control *, Thread_Control *);
203
204static T_interrupt_context T_interrupt_instance = {
205        .interrupt = T_interrupt_continue,
206        .blocked = T_interrupt_do_nothing,
207        .wdg = WATCHDOG_INITIALIZER(T_interrupt_watchdog),
208        .ext = {
209                .Callouts = {
210                        .thread_switch = T_interrupt_thread_switch
211                }
212        }
213};
214
215T_interrupt_test_state
216T_interrupt_test_change_state(T_interrupt_test_state expected_state,
217    T_interrupt_test_state desired_state)
218{
219        T_interrupt_context *ctx;
220        unsigned int expected;
221
222        ctx = &T_interrupt_instance;
223        expected = expected_state;
224        _Atomic_Compare_exchange_uint(&ctx->state, &expected,
225            desired_state, ATOMIC_ORDER_RELAXED, ATOMIC_ORDER_RELAXED);
226
227        return expected;
228}
229
230T_interrupt_test_state
231T_interrupt_test_get_state(void)
232{
233        T_interrupt_context *ctx;
234
235        ctx = &T_interrupt_instance;
236        return _Atomic_Load_uint(&ctx->state, ATOMIC_ORDER_RELAXED);
237}
238
239void
240T_interrupt_test_busy_wait_for_interrupt(void)
241{
242        T_interrupt_context *ctx;
243        unsigned int state;
244
245        ctx = &T_interrupt_instance;
246
247        do {
248                state = _Atomic_Load_uint(&ctx->state, ATOMIC_ORDER_RELAXED);
249        } while (state == T_INTERRUPT_TEST_ACTION);
250}
251
252static void
253T_interrupt_thread_switch(Thread_Control *executing, Thread_Control *heir)
254{
255        T_interrupt_context *ctx;
256
257        (void)heir;
258        ctx = &T_interrupt_instance;
259
260        if (ctx->self == executing) {
261                T_interrupt_test_state state;
262
263                state = _Atomic_Load_uint(&ctx->state, ATOMIC_ORDER_RELAXED);
264
265                if (state != T_INTERRUPT_TEST_INITIAL) {
266                        (*ctx->blocked)(ctx->arg);
267                }
268        }
269}
270
271static T_interrupt_context *
272T_interrupt_setup(const T_interrupt_test_config *config, void *arg)
273{
274        T_interrupt_context *ctx;
275
276        T_quiet_assert_not_null(config->action);
277        T_quiet_assert_not_null(config->interrupt);
278        ctx = &T_interrupt_instance;
279        ctx->self = _Thread_Get_executing();
280        ctx->arg = arg;
281        ctx->interrupt = config->interrupt;
282
283        if (config->blocked != NULL) {
284                ctx->blocked = config->blocked;
285        }
286
287        if (ctx->t0 == 0) {
288                T_interrupt_init_once(ctx);
289        }
290
291        _User_extensions_Add_set(&ctx->ext);
292        T_interrupt_watchdog_insert(ctx);
293        return ctx;
294}
295
296static void
297T_interrupt_teardown(void *arg)
298{
299        T_interrupt_context *ctx;
300
301        ctx = arg;
302        ctx->interrupt = T_interrupt_continue;
303        ctx->blocked = T_interrupt_do_nothing;
304        T_interrupt_watchdog_remove(ctx);
305        _User_extensions_Remove_set(&ctx->ext);
306        ctx->self = NULL;
307        ctx->arg = NULL;
308}
309
310static T_fixture T_interrupt_fixture = {
311        .teardown = T_interrupt_teardown,
312        .initial_context = &T_interrupt_instance
313};
314
315T_interrupt_test_state
316T_interrupt_test(const T_interrupt_test_config *config, void *arg)
317{
318        T_interrupt_context *ctx;
319        uint_fast32_t lower_bound[T_INTERRUPT_SAMPLE_COUNT];
320        uint_fast32_t upper_bound[T_INTERRUPT_SAMPLE_COUNT];
321        uint_fast32_t lower_sum;
322        uint_fast32_t upper_sum;
323        int32_t ns_per_tick;
324        size_t sample;
325        uint32_t iter;
326
327        ctx = T_interrupt_setup(config, arg);
328        T_push_fixture(&ctx->node, &T_interrupt_fixture);
329        ns_per_tick = (int32_t)_Watchdog_Nanoseconds_per_tick;
330        lower_sum = 0;
331        upper_sum = T_INTERRUPT_SAMPLE_COUNT * ctx->one_tick_busy;
332
333        for (sample = 0; sample < T_INTERRUPT_SAMPLE_COUNT; ++sample) {
334                lower_bound[sample] = 0;
335                upper_bound[sample] = ctx->one_tick_busy;
336        }
337
338        sample = 0;
339
340        for (iter = 0; iter < config->max_iteration_count; ++iter) {
341                T_interrupt_test_state state;
342                int64_t t;
343                int64_t d;
344                Timestamp_Control s1;
345                Timestamp_Control s0;
346                uint_fast32_t busy;
347                uint_fast32_t delta;
348
349                if (config->prepare != NULL) {
350                        (*config->prepare)(arg);
351                }
352
353                /*
354                 * We use some sort of a damped bisection to find the right
355                 * interrupt time point.
356                 */
357                busy = (lower_sum + upper_sum) /
358                    (2 * T_INTERRUPT_SAMPLE_COUNT);
359
360                t = sbttons(_Timecounter_Sbinuptime());
361                d = (t - ctx->t0) % ns_per_tick;
362                t += ns_per_tick / 4 - d;
363
364                if (d > ns_per_tick / 8) {
365                        t += ns_per_tick;
366                }
367
368                /*
369                 * The s1 value is a future time point close to 25% of a clock
370                 * tick interval.
371                 */
372                s1 = nstosbt(t);
373
374                /*
375                 * The path from here to the action call must avoid anything
376                 * which can cause jitters.  We wait until 25% of the clock
377                 * tick interval are elapsed using the timecounter.  Then we do
378                 * a busy wait and call the action.  The interrupt time point
379                 * is controlled by the busy count.
380                 */
381
382                do {
383                        s0 = _Timecounter_Sbinuptime();
384                } while (s0 < s1);
385
386                _Atomic_Store_uint(&ctx->state, T_INTERRUPT_TEST_ACTION,
387                    ATOMIC_ORDER_RELAXED);
388                T_busy(busy);
389                (*config->action)(arg);
390
391                state = _Atomic_Exchange_uint(&ctx->state,
392                    T_INTERRUPT_TEST_INITIAL, ATOMIC_ORDER_RELAXED);
393
394                if (state == T_INTERRUPT_TEST_DONE) {
395                        break;
396                }
397
398                /* Adjust the lower/upper bound of the bisection interval */
399                if (state == T_INTERRUPT_TEST_EARLY) {
400                        uint_fast32_t lower;
401
402                        upper_sum -= upper_bound[sample];
403                        upper_sum += busy;
404                        upper_bound[sample] = busy;
405
406                        /* Round down to make sure no underflow happens */
407                        lower = lower_bound[sample];
408                        delta = lower / 32;
409                        lower_sum -= delta;
410                        lower_bound[sample] = lower - delta;
411
412                        sample = (sample + 1) % T_INTERRUPT_SAMPLE_COUNT;
413                } else if (state == T_INTERRUPT_TEST_LATE) {
414                        uint_fast32_t upper;
415
416                        lower_sum -= lower_bound[sample];
417                        lower_sum += busy;
418                        lower_bound[sample] = busy;
419
420                        /*
421                         * The one tick busy count value is not really
422                         * trustable on some platforms.  Allow the upper bound
423                         * to grow over this value in time.
424                         */
425                        upper = upper_bound[sample];
426                        delta = (upper + 31) / 32;
427                        upper_sum += delta;
428                        upper_bound[sample] = upper + delta;
429
430                        sample = (sample + 1) % T_INTERRUPT_SAMPLE_COUNT;
431                }
432        }
433
434        T_pop_fixture();
435
436        if (iter == config->max_iteration_count) {
437                return T_INTERRUPT_TEST_TIMEOUT;
438        }
439
440        return T_INTERRUPT_TEST_DONE;
441}
Note: See TracBrowser for help on using the repository browser.