Skip to content

Commit a974b23

Browse files
author
Joakim Nohlgård
committed
tests/periph_timer_timeout0: Regression test for timer callbacks setting 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.
1 parent 606a294 commit a974b23

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
include ../Makefile.tests_common
2+
3+
FEATURES_REQUIRED = periph_timer
4+
5+
TEST_ON_CI_WHITELIST += all
6+
7+
include $(RIOTBASE)/Makefile.include
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Timer callback stack overflow regression test
2+
3+
This test is designed to catch an implementation bug where a timer callback is
4+
called directly from inside timer_set if the given timeout=0, leading to a
5+
stack overflow if timer_set is called from within the callback of the same
6+
timer.
7+
8+
## Test algorithm
9+
10+
The test will attempt to initialize each timer in the system and set a non-zero
11+
timeout at first. The callback function provided will then attempt to set a new
12+
timeout=0 until we have called the callback TEST_ITERATIONS times (default 10000).
13+
The expected behavior is that the timer will trigger again as soon as the timer
14+
callback function returns. If the timer driver implementation is broken, then
15+
the callback will be called again by timer_set, causing a stack overflow after a
16+
number of iterations.
17+
18+
## Consequences of a failed test
19+
20+
If this test is failing (or crashing), it means that there may be a risk for
21+
stack overflows if using the high level timer subsystems (xtimer, ztimer) in
22+
certain scenarios.

tests/periph_timer_timeout0/main.c

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright (C) 2018 Eistec AB
3+
*
4+
* This file is subject to the terms and conditions of the GNU Lesser
5+
* General Public License v2.1. See the file LICENSE in the top level
6+
* directory for more details.
7+
*/
8+
9+
/**
10+
* @ingroup tests
11+
* @{
12+
*
13+
* @file
14+
* @brief Peripheral timer regression test application
15+
*
16+
* @author Joakim Nohlgård <[email protected]>
17+
*
18+
* @}
19+
*/
20+
21+
#include <stdio.h>
22+
#include <stdint.h>
23+
#include <stdlib.h>
24+
25+
#include "mutex.h"
26+
#include "periph/timer.h"
27+
28+
#ifndef TIMER_NUMOF
29+
#error "TIMER_NUMOF not defined!"
30+
#endif
31+
32+
#ifndef TIM_TEST_FREQ
33+
#define TIM_TEST_FREQ (1000000ul)
34+
#endif
35+
36+
#ifndef TEST_ITERATIONS
37+
#define TEST_ITERATIONS (10000ul)
38+
#endif
39+
40+
typedef struct {
41+
unsigned long counter;
42+
tim_t dev;
43+
mutex_t mtx;
44+
} test_ctx_t;
45+
46+
static void cb_incr(void *arg, int chan)
47+
{
48+
(void)chan;
49+
test_ctx_t *ctx = arg;
50+
ctx->counter++;
51+
if (ctx->counter < TEST_ITERATIONS) {
52+
/* Rescheduling the timer like this will trigger a bug in the lptmr
53+
* implementation in Kinetis */
54+
timer_set(ctx->dev, chan, 100000ul);
55+
timer_set(ctx->dev, chan, 0);
56+
}
57+
mutex_unlock(&ctx->mtx);
58+
}
59+
60+
/* List of frequencies to try for timer_init */
61+
static const unsigned long timer_freqs[] = {
62+
TIM_TEST_FREQ,
63+
1000000ul,
64+
250000ul,
65+
32768ul,
66+
1000ul,
67+
};
68+
69+
static int test_timer(unsigned num)
70+
{
71+
/* initialize and halt timer */
72+
unsigned long switches = 0;
73+
test_ctx_t ctx = {
74+
.counter = 0,
75+
.dev = TIMER_DEV(num),
76+
.mtx = MUTEX_INIT_LOCKED
77+
};
78+
int res;
79+
for (unsigned k = 0; k < sizeof(timer_freqs) / sizeof(timer_freqs[0]); ++k) {
80+
res = timer_init(ctx.dev, timer_freqs[k], cb_incr, &ctx);
81+
if (res >= 0) {
82+
printf("TIMER_DEV(%u) running at %lu Hz\n", num, timer_freqs[k]);
83+
break;
84+
}
85+
}
86+
if (res < 0) {
87+
printf("TIMER_DEV(%u) init failed: %d\n", num, res);
88+
return -1;
89+
}
90+
/* Send the initial trigger for the timer */
91+
timer_set(ctx.dev, 0, 100);
92+
/* Wait until we have executed the zero timeout callback enough times */
93+
while (ctx.counter < TEST_ITERATIONS) {
94+
mutex_lock(&ctx.mtx);
95+
++switches;
96+
}
97+
98+
printf("(debug) TIMER_DEV(%u) switches: %lu\n", num, switches);
99+
/* verify results */
100+
if (ctx.counter != TEST_ITERATIONS) {
101+
printf("TIMER_DEV(%u) counter mismatch, expected: %lu, actual: %lu\n", num, TEST_ITERATIONS, ctx.counter);
102+
return 0;
103+
}
104+
return 1;
105+
}
106+
107+
int main(void)
108+
{
109+
int res = 0;
110+
111+
puts("\nTest for timer_set with timeout=0\n");
112+
113+
printf("Available timers: %i\n", TIMER_NUMOF);
114+
115+
/* test all configured timers */
116+
for (unsigned i = 0; i < TIMER_NUMOF; i++) {
117+
printf("\nTesting TIMER_DEV(%u):\n", i);
118+
res += test_timer(i);
119+
}
120+
/* draw conclusion */
121+
if (res == TIMER_NUMOF) {
122+
puts("\nTEST SUCCEEDED");
123+
}
124+
else {
125+
puts("\nTEST FAILED");
126+
}
127+
128+
return 0;
129+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env python3
2+
3+
# Copyright (C) 2016 Kaspar Schleiser <[email protected]>
4+
#
5+
# This file is subject to the terms and conditions of the GNU Lesser
6+
# General Public License v2.1. See the file LICENSE in the top level
7+
# directory for more details.
8+
9+
import sys
10+
from testrunner import run
11+
12+
13+
def testfunc(child):
14+
child.expect('Available timers: (\d+)')
15+
timers_num = int(child.match.group(1))
16+
for timer in range(timers_num):
17+
child.expect_exact('Testing TIMER_DEV(%u)' % (timer, ))
18+
child.expect('TIMER_DEV\(%u\) running at \d+ Hz' % (timer, ))
19+
child.expect('TEST SUCCEEDED')
20+
21+
22+
if __name__ == "__main__":
23+
sys.exit(run(testfunc))

0 commit comments

Comments
 (0)