Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests/periph_timer_timeout0: Regression test for timer callbacks setting new timers #10019

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
93 changes: 89 additions & 4 deletions tests/periph/timer/main.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/*
* Copyright (C) 2015 Freie Universität Berlin
* 2018 Eistec AB
* 2024 HAW Hamburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
Expand All @@ -14,6 +16,8 @@
* @brief Peripheral timer test application
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* Joakim Nohlgård <joakim.nohlgard@eistec.se>
* Bennet Blischke <bennet.blischke@haw-hamburg.de>
*
* @}
*/
Expand All @@ -25,6 +29,7 @@
#include "atomic_utils.h"
#include "architecture.h"
#include "clk.h"
#include "mutex.h"
#include "periph/timer.h"
#include "test_utils/expect.h"
#include "time_units.h"
Expand All @@ -51,11 +56,36 @@
* e.g. when the timer was about to tick anyway */
#define MINIMUM_TICKS 2

#ifndef TEST_ITERATIONS
#define TEST_ITERATIONS (10000ul)
#endif

static uint8_t fired;
static uint32_t sw_count;
static uint32_t timeouts[TIMER_CHANNEL_NUMOF];
static unsigned args[TIMER_CHANNEL_NUMOF];

typedef struct {
unsigned long counter;
tim_t dev;
mutex_t mtx;
} test_ctx_t;

static void cb_incr(void *arg, int chan)
{
(void)chan;
test_ctx_t *ctx = arg;

ctx->counter++;
if (ctx->counter < TEST_ITERATIONS) {
/* Rescheduling the timer like this will trigger a bug in the lptmr
* implementation in Kinetis */
timer_set(ctx->dev, chan, 20000u);
timer_set(ctx->dev, chan, 0);
}
mutex_unlock(&ctx->mtx);
}

static void cb(void *arg, int chan)
{
timeouts[chan] = sw_count;
Expand Down Expand Up @@ -84,6 +114,7 @@
{
/* Use 64 bit arithmetic to avoid overflows for high frequencies. */
unsigned result = ((uint64_t)millisecs * US_PER_MS * timer_freq) / US_PER_SEC;

/* Never return less than MINIMUM_TICKS ticks */
return (result >= MINIMUM_TICKS) ? result : MINIMUM_TICKS;
}
Expand All @@ -102,7 +133,7 @@
}

printf(" - Calling timer_init(%u, %" PRIu32 ")\n ",
num, timer_freq);
num, timer_freq);
/* initialize and halt timer */
if (timer_init(TIMER_DEV(num), timer_freq, cb, (void *)(COOKIE * num)) != 0) {
printf("ERROR: timer_init() failed\n\n");
Expand Down Expand Up @@ -177,6 +208,7 @@

const unsigned duration = milliseconds_to_ticks(timer_freq, MINIMUM_TIMEOUT_MS);
unsigned target = timer_read(TIMER_DEV(num)) + duration;

expect(0 == timer_set_absolute(TIMER_DEV(num), 0, target));
expect(0 == timer_clear(TIMER_DEV(num), 0));
atomic_store_u8(&fired, 0);
Expand Down Expand Up @@ -207,6 +239,53 @@
return 1;
}


Check warning on line 242 in tests/periph/timer/main.c

View workflow job for this annotation

GitHub Actions / static-tests

too many consecutive empty lines
/* This test is designed to catch an implementation bug where a timer callback is
* called directly from inside timer_set if the given timeout=0, leading to a
* stack overflow if timer_set is called from within the callback of the same
* timer.
*
* The test will attempt to initialize each timer in the system and set a non-zero
* timeout at first. The callback function provided will then attempt to set a new
* timeout=0 until we have called the callback TEST_ITERATIONS times (default 10000).
* The expected behavior is that the timer will trigger again as soon as the timer
* callback function returns. If the timer driver implementation is broken, then
* the callback will be called again by timer_set, causing a stack overflow after a
* number of iterations. */
static int test_timer_timeout(unsigned num, uint32_t timer_freq)
{
/* initialize and halt timer */
unsigned long switches = 0;
test_ctx_t ctx = {
.counter = 0,
.dev = TIMER_DEV(num),
.mtx = MUTEX_INIT_LOCKED
};

printf(" - Testing timeout=0 in callback:\n");

if (timer_init(ctx.dev, timer_freq, cb_incr, &ctx) < 0) {
printf(" TIMER_DEV(%u) init failed.\n", num);
return 0;
}
/* Send the initial trigger for the timer */
timer_set(ctx.dev, 0, 100);
/* Wait until we have executed the zero timeout callback enough times */
while (ctx.counter < TEST_ITERATIONS) {
mutex_lock(&ctx.mtx);
++switches;
}

/* verify results */
if (ctx.counter != TEST_ITERATIONS) {
printf(" TIMER_DEV(%u) counter mismatch, expected: %lu, actual: %lu\n",
num, TEST_ITERATIONS, ctx.counter);
return 0;
}
printf(" OK (timer timeout successful)\n");
return 1;
}

static uword_t query_freq_numof(tim_t dev)
{
if (IS_USED(MODULE_PERIPH_TIMER_QUERY_FREQS)) {
Expand Down Expand Up @@ -239,7 +318,8 @@
}

uword_t end = query_freq_numof(dev);
printf(" - supported frequencies:\n");

printf(" - supported frequencies:\n");
for (uword_t i = 0; i < MIN(end, 3); i++) {
printf(" %u: %" PRIu32 "\n", (unsigned)i, timer_query_freqs(dev, i));
}
Expand All @@ -258,10 +338,11 @@
printf("Available timers: %i\n", TIMER_NUMOF);

int failed = 0;

/* test all configured timers */
for (unsigned i = 0; i < TIMER_NUMOF; i++) {
printf("\nTIMER %u\n"
"=======\n\n", i);
"=======\n\n", i);
print_supported_frequencies(TIMER_DEV(i));
uword_t end = query_freq_numof(TIMER_DEV(i));

Expand All @@ -271,7 +352,11 @@
* complete */
end = MIN(end, 3);
for (uword_t j = 0; j < end; j++) {
if (!test_timer(i, query_freq(TIMER_DEV(i), j))) {
uint32_t freq = query_freq(TIMER_DEV(i), j);
if (!test_timer(i, freq)) {
failed = 1;
}
if (!test_timer_timeout(i, freq)) {
failed = 1;
}
}
Expand Down