Skip to content

Commit

Permalink
tests/periph_timer_timeout0: Regression test for timer callbacks sett…
Browse files Browse the repository at this point in the history
…ing new timers

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 bug is present on the current revision of the Kinetis LPTMR periph
driver. A bug fix is provided in a separate commit.
  • Loading branch information
Joakim Nohlgård committed Sep 24, 2018
1 parent 606a294 commit a974b23
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 0 deletions.
7 changes: 7 additions & 0 deletions tests/periph_timer_timeout0/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
include ../Makefile.tests_common

FEATURES_REQUIRED = periph_timer

TEST_ON_CI_WHITELIST += all

include $(RIOTBASE)/Makefile.include
22 changes: 22 additions & 0 deletions tests/periph_timer_timeout0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Timer callback stack overflow regression test

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.

## Test algorithm

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.

## Consequences of a failed test

If this test is failing (or crashing), it means that there may be a risk for
stack overflows if using the high level timer subsystems (xtimer, ztimer) in
certain scenarios.
129 changes: 129 additions & 0 deletions tests/periph_timer_timeout0/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright (C) 2018 Eistec AB
*
* 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
* directory for more details.
*/

/**
* @ingroup tests
* @{
*
* @file
* @brief Peripheral timer regression test application
*
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
*
* @}
*/

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#include "mutex.h"
#include "periph/timer.h"

#ifndef TIMER_NUMOF
#error "TIMER_NUMOF not defined!"
#endif

#ifndef TIM_TEST_FREQ
#define TIM_TEST_FREQ (1000000ul)
#endif

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

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, 100000ul);
timer_set(ctx->dev, chan, 0);
}
mutex_unlock(&ctx->mtx);
}

/* List of frequencies to try for timer_init */
static const unsigned long timer_freqs[] = {
TIM_TEST_FREQ,
1000000ul,
250000ul,
32768ul,
1000ul,
};

static int test_timer(unsigned num)
{
/* initialize and halt timer */
unsigned long switches = 0;
test_ctx_t ctx = {
.counter = 0,
.dev = TIMER_DEV(num),
.mtx = MUTEX_INIT_LOCKED
};
int res;
for (unsigned k = 0; k < sizeof(timer_freqs) / sizeof(timer_freqs[0]); ++k) {
res = timer_init(ctx.dev, timer_freqs[k], cb_incr, &ctx);
if (res >= 0) {
printf("TIMER_DEV(%u) running at %lu Hz\n", num, timer_freqs[k]);
break;
}
}
if (res < 0) {
printf("TIMER_DEV(%u) init failed: %d\n", num, res);
return -1;
}
/* 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;
}

printf("(debug) TIMER_DEV(%u) switches: %lu\n", num, 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;
}
return 1;
}

int main(void)
{
int res = 0;

puts("\nTest for timer_set with timeout=0\n");

printf("Available timers: %i\n", TIMER_NUMOF);

/* test all configured timers */
for (unsigned i = 0; i < TIMER_NUMOF; i++) {
printf("\nTesting TIMER_DEV(%u):\n", i);
res += test_timer(i);
}
/* draw conclusion */
if (res == TIMER_NUMOF) {
puts("\nTEST SUCCEEDED");
}
else {
puts("\nTEST FAILED");
}

return 0;
}
23 changes: 23 additions & 0 deletions tests/periph_timer_timeout0/tests/01-run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env python3

# Copyright (C) 2016 Kaspar Schleiser <kaspar@schleiser.de>
#
# 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
# directory for more details.

import sys
from testrunner import run


def testfunc(child):
child.expect('Available timers: (\d+)')
timers_num = int(child.match.group(1))
for timer in range(timers_num):
child.expect_exact('Testing TIMER_DEV(%u)' % (timer, ))
child.expect('TIMER_DEV\(%u\) running at \d+ Hz' % (timer, ))
child.expect('TEST SUCCEEDED')


if __name__ == "__main__":
sys.exit(run(testfunc))

0 comments on commit a974b23

Please sign in to comment.