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 int64_t last_scheduled;
18 struct k_timer tm;
19 uint32_t callbacks;
20 uint32_t late_callbacks;
21 uint32_t last_isr;
22 uint32_t max_delta;
23 };
24
25 K_SEM_DEFINE(timers_sem, 0, K_SEM_MAX_LIMIT);
26
27 struct timer_wrapper timers[TIMERS];
28
tm_fn(struct k_timer * tm)29 void tm_fn(struct k_timer *tm)
30 {
31 struct timer_wrapper *tm_wrap =
32 CONTAINER_OF(tm, struct timer_wrapper, tm);
33 uint32_t now = k_cycle_get_32();
34
35 if (tm_wrap->last_isr != 0) {
36 uint32_t delta = now - tm_wrap->last_isr;
37
38 tm_wrap->max_delta = delta > tm_wrap->max_delta ? delta : tm_wrap->max_delta;
39 if (delta >= k_ticks_to_cyc_floor32(TIMERS + 1)) {
40 tm_wrap->late_callbacks++;
41 }
42 }
43 tm_wrap->last_isr = now;
44 tm_wrap->callbacks++;
45 if (tm_wrap->callbacks >= MAX_CALLBACKS) {
46 k_timer_stop(tm);
47 k_sem_give(&timers_sem);
48 } else {
49 int64_t next = tm_wrap->last_scheduled + TIMERS;
50
51 tm_wrap->last_scheduled = next;
52 k_timer_start(tm, K_TIMEOUT_ABS_TICKS(next), K_NO_WAIT);
53 }
54 }
55
56
57 /**
58 * @brief Test timers can be scheduled 1 tick apart without issues
59 *
60 * Schedules timers with absolute scheduling with a 1 tick
61 * period. Measures the total time elapsed and tries to run
62 * some fake busy work while doing so. If the print outs don't show up or
63 * the timer train is late to the station, the test fails.
64 */
ZTEST(timer_tick_train,test_one_tick_timer_train)65 ZTEST(timer_tick_train, test_one_tick_timer_train)
66 {
67 const uint32_t max_time = TEST_SECONDS*1000 + 1000;
68
69 TC_PRINT("Initializing %u Timers, Tick Rate %uHz, Expecting %u callbacks in %u ms\n",
70 TIMERS, CONFIG_SYS_CLOCK_TICKS_PER_SEC, MAX_CALLBACKS, max_time);
71
72 for (int i = 0; i < TIMERS; i++) {
73 k_timer_init(&timers[i].tm, tm_fn, NULL);
74 timers[i].max_delta = 0;
75 }
76
77 TC_PRINT("Starting Timers with Skews\n");
78 int64_t tick = k_uptime_ticks();
79
80 for (int i = 0; i < TIMERS; i++) {
81 timers[i].last_scheduled = tick + i;
82 k_timer_start(&timers[i].tm, K_TIMEOUT_ABS_TICKS(timers[i].last_scheduled),
83 K_NO_WAIT);
84 }
85
86 #ifdef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
87 uint64_t start_cycle = k_cycle_get_64();
88 #else
89 uint32_t start_time_ms = k_uptime_get();
90 #endif
91
92 uint32_t remaining_timers = TIMERS;
93
94 /* Do work in the meantime, proving there's enough time to do other things */
95 uint32_t busy_loops = 0;
96
97 while (true) {
98 while (k_sem_take(&timers_sem, K_NO_WAIT) == 0) {
99 remaining_timers--;
100
101 }
102 if (remaining_timers == 0) {
103 break;
104 }
105 TC_PRINT("Faking busy work, remaining timers is %u, timer callbacks %u\n",
106 remaining_timers, timers[0].callbacks);
107 busy_loops++;
108 k_busy_wait(250000);
109 }
110
111 #ifdef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
112 uint64_t end_cycle = k_cycle_get_64();
113 #else
114 uint64_t end_time_ms = k_uptime_get();
115 #endif
116
117 #ifdef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
118 uint64_t delta_cycles = end_cycle - start_cycle;
119 uint32_t delta_time = k_cyc_to_ms_floor32(delta_cycles);
120 #else
121 uint32_t delta_time = end_time_ms - start_time_ms;
122 #endif
123
124 TC_PRINT("One Tick Timer Train Done, took %u ms, busy loop ran %u times\n",
125 delta_time, busy_loops);
126
127 uint32_t max_delta = 0;
128
129 TC_PRINT(" Perfect delta %u cycles or %u us\n",
130 k_ticks_to_cyc_floor32(TIMERS), k_ticks_to_us_near32(TIMERS));
131 for (int i = 0; i < TIMERS; i++) {
132 TC_PRINT("Timer %d max delta %u cycles or %u us, "
133 "%u late callbacks (%u.%u%%)\n",
134 i, timers[i].max_delta,
135 k_cyc_to_us_near32(timers[i].max_delta),
136 timers[i].late_callbacks,
137 (1000 * timers[i].late_callbacks + MAX_CALLBACKS/2) / MAX_CALLBACKS / 10,
138 (1000 * timers[i].late_callbacks + MAX_CALLBACKS/2) / MAX_CALLBACKS % 10);
139 /* Record the stats gathered as a JSON object including related CONFIG_* params. */
140 TC_PRINT("RECORD: {"
141 "\"testcase\":\"one_tick_timer_train\""
142 ", \"timer\":%d, \"max_delta_cycles\":%u, \"max_delta_us\":%u"
143 ", \"late_callbacks\":%u"
144 ", \"perfect_delta_cycles\":%u, \"perfect_delta_us\":%u"
145 ", \"train_time_ms\":%u, \"busy_loops\":%u"
146 ", \"timers\":%u, \"expected_callbacks\":%u, \"expected_time_ms\":%u"
147 ", \"CONFIG_SYS_CLOCK_TICKS_PER_SEC\":%u"
148 "}\n",
149 i, timers[i].max_delta, k_cyc_to_us_near32(timers[i].max_delta),
150 timers[i].late_callbacks,
151 k_ticks_to_cyc_floor32(TIMERS), k_ticks_to_us_near32(TIMERS),
152 delta_time, busy_loops,
153 TIMERS, MAX_CALLBACKS, max_time,
154 CONFIG_SYS_CLOCK_TICKS_PER_SEC
155 );
156 max_delta = timers[i].max_delta > max_delta ? timers[i].max_delta : max_delta;
157 k_timer_stop(&timers[i].tm);
158 }
159
160 if (max_delta >= k_ticks_to_cyc_floor32(TIMERS + 1)) {
161 TC_PRINT("!! Some ticks were missed.\n");
162 TC_PRINT("!! Consider making CONFIG_SYS_CLOCK_TICKS_PER_SEC smaller.\n");
163 /* should this fail the test? */
164 }
165
166 const uint32_t maximum_busy_loops = TEST_SECONDS * 4;
167
168 if (busy_loops < (maximum_busy_loops - maximum_busy_loops/10)) {
169 TC_PRINT("!! The busy loop didn't run as much as expected.\n");
170 TC_PRINT("!! Consider making CONFIG_SYS_CLOCK_TICKS_PER_SEC smaller.\n");
171 }
172
173 /* On some platforms, where the tick period is short, like on nRF
174 * platforms where it is ~30 us, execution of the timer handlers
175 * can take significant part of the CPU time, so accept if at least
176 * one-third of possible busy loop iterations is actually performed.
177 */
178 const uint32_t acceptable_busy_loops = maximum_busy_loops / 3;
179
180 zassert_true(busy_loops > acceptable_busy_loops,
181 "Expected thread to run while 1 tick timers are firing");
182
183 zassert_true(delta_time < max_time,
184 "Expected timer train to finish in under %u milliseconds, took %u", max_time,
185 delta_time);
186 }
187
188 ZTEST_SUITE(timer_tick_train, NULL, NULL, NULL, NULL, NULL);
189