Skip to content

Commit 2666702

Browse files
teburdnashif
authored andcommitted
tests: Tick rate testing with timer train
Test timers with a train of one tick timers to test that a configured SYS_CLOCK_TICKS_PER_SEC is sensible. If the TICKS_PER_SEC is too high the timer train will take longer than expected to reach the station. Worse, if the timer driver has too short of a minimum delay for its processing power and the tick rate is too high its possible the device will get caught in an interrupt loop preventing any threads from running while processing timers. This test validates that the tick rate configured is actually able to be processed without delays while also having work done in threads ensuring that no thread scheduling delays occur either from delayed timers or an interrupt loop from preventing threads from running. Signed-off-by: Tom Burdick <[email protected]>
1 parent 00abfa9 commit 2666702

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

tests/kernel/timer/timer_behavior/src/main.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
void test_main(void)
1010
{
1111
ztest_run_test_suite(timer_jitter_drift);
12+
ztest_run_test_suite(timer_tick_train);
1213
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright (c) 2022 Intel Corporation
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/kernel.h>
8+
#include <zephyr/sys/time_units.h>
9+
#include <zephyr/tc_util.h>
10+
#include <zephyr/ztest.h>
11+
12+
#define TIMERS 4
13+
#define TEST_SECONDS 10
14+
#define MAX_CALLBACKS (CONFIG_SYS_CLOCK_TICKS_PER_SEC*TEST_SECONDS)/TIMERS
15+
16+
struct timer_wrapper {
17+
uint32_t callbacks;
18+
struct k_timer tm;
19+
uint32_t last_isr;
20+
int64_t last_scheduled;
21+
uint32_t max_delta;
22+
};
23+
24+
K_SEM_DEFINE(timers_sem, 0, K_SEM_MAX_LIMIT);
25+
26+
struct timer_wrapper timers[TIMERS];
27+
28+
void tm_fn(struct k_timer *tm)
29+
{
30+
struct timer_wrapper *tm_wrap =
31+
CONTAINER_OF(tm, struct timer_wrapper, tm);
32+
uint32_t now = k_cycle_get_32();
33+
34+
if (tm_wrap->last_isr != 0) {
35+
uint32_t delta = now - tm_wrap->last_isr;
36+
37+
tm_wrap->max_delta = delta > tm_wrap->max_delta ? delta : tm_wrap->max_delta;
38+
}
39+
tm_wrap->last_isr = now;
40+
tm_wrap->callbacks++;
41+
if (tm_wrap->callbacks >= MAX_CALLBACKS) {
42+
k_timer_stop(tm);
43+
k_sem_give(&timers_sem);
44+
} else {
45+
int64_t next = tm_wrap->last_scheduled + TIMERS;
46+
47+
tm_wrap->last_scheduled = next;
48+
k_timer_start(tm, K_TIMEOUT_ABS_TICKS(next), K_NO_WAIT);
49+
}
50+
}
51+
52+
53+
/**
54+
* @brief Test timers can be scheduled 1 tick apart without issues
55+
*
56+
* Schedules timers with absolute scheduling with a 1 tick
57+
* period. Measures the total time elapsed and tries to run
58+
* some fake busy work while doing so. If the print outs don't show up or
59+
* the timer train is late to the station, the test fails.
60+
*/
61+
ZTEST(timer_tick_train, test_one_tick_timer_train)
62+
{
63+
const uint32_t max_time = TEST_SECONDS*1000 + 1000;
64+
65+
TC_PRINT("Initializing %u Timers, Tick Rate %uHz, Expecting %u callbacks in %u ms\n",
66+
TIMERS, CONFIG_SYS_CLOCK_TICKS_PER_SEC, MAX_CALLBACKS, max_time);
67+
68+
for (int i = 0; i < TIMERS; i++) {
69+
k_timer_init(&timers[i].tm, tm_fn, NULL);
70+
timers[i].max_delta = 0;
71+
}
72+
73+
TC_PRINT("Starting Timers with Skews\n");
74+
int64_t tick = k_uptime_ticks();
75+
76+
for (int i = 0; i < TIMERS; i++) {
77+
timers[i].last_scheduled = tick + i;
78+
k_timer_start(&timers[i].tm, K_TIMEOUT_ABS_TICKS(timers[i].last_scheduled),
79+
K_NO_WAIT);
80+
}
81+
82+
#ifdef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
83+
uint64_t start_cycle = k_cycle_get_64();
84+
#else
85+
uint32_t start_time_ms = k_uptime_get();
86+
#endif
87+
88+
uint32_t remaining_timers = TIMERS;
89+
90+
/* Do work in the meantime, proving there's enough time to do other things */
91+
uint32_t busy_loops = 0;
92+
93+
while (true) {
94+
while (k_sem_take(&timers_sem, K_NO_WAIT) == 0) {
95+
remaining_timers--;
96+
97+
}
98+
if (remaining_timers == 0) {
99+
break;
100+
}
101+
TC_PRINT("Faking busy work, remaining timers is %u, timer callbacks %u\n",
102+
remaining_timers, timers[0].callbacks);
103+
busy_loops++;
104+
k_busy_wait(250000);
105+
}
106+
107+
#ifdef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
108+
uint64_t end_cycle = k_cycle_get_64();
109+
#else
110+
uint64_t end_time_ms = k_uptime_get();
111+
#endif
112+
113+
#ifdef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
114+
uint64_t delta_cycles = end_cycle - start_cycle;
115+
uint32_t delta_time = k_cyc_to_ms_floor32(delta_cycles);
116+
#else
117+
uint32_t delta_time = end_time_ms - start_time_ms;
118+
#endif
119+
120+
TC_PRINT("One Tick Timer Train Done, took %u ms, busy loop ran %u times\n",
121+
delta_time, busy_loops);
122+
123+
uint32_t max_delta = 0;
124+
125+
for (int i = 0; i < TIMERS; i++) {
126+
TC_PRINT("Timer %d max delta %u cycles or %u us\n", i, timers[i].max_delta,
127+
k_cyc_to_us_near32(timers[i].max_delta));
128+
max_delta = timers[i].max_delta > max_delta ? timers[i].max_delta : max_delta;
129+
}
130+
131+
for (int i = 0; i < TIMERS; i++) {
132+
k_timer_stop(&timers[i].tm);
133+
}
134+
135+
const uint32_t expected_busy_loops = TEST_SECONDS*4;
136+
const uint32_t acceptable_busy_loops = expected_busy_loops - expected_busy_loops/10;
137+
138+
zassert_true(busy_loops > acceptable_busy_loops,
139+
"Expected thread to run while 1 tick timers are firing");
140+
141+
zassert_true(delta_time < max_time,
142+
"Expected timer train to finish in under %u milliseconds, took %u", max_time,
143+
delta_time);
144+
}
145+
146+
ZTEST_SUITE(timer_tick_train, NULL, NULL, NULL, NULL, NULL);

0 commit comments

Comments
 (0)