-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
tests/periph_timer_timeout0: Regression test for timer callbacks sett…
…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
Showing
4 changed files
with
181 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |