1 /*
2 * Copyright (c) 2022 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <limits.h>
8 #include <math.h>
9 #include <stdlib.h>
10
11 #include <zephyr/kernel.h>
12 #include <zephyr/drivers/gpio.h>
13
14 #include <zephyr/tc_util.h>
15 #include <zephyr/ztest.h>
16
17 #ifdef CONFIG_TIMER_EXTERNAL_TEST
18 #define TIMER_OUT_NODE DT_INST(0, test_kernel_timer_behavior_external)
19 static const struct gpio_dt_spec timer_out = GPIO_DT_SPEC_GET(TIMER_OUT_NODE,
20 timerout_gpios);
21 #endif
22
23 static uint32_t periodic_idx;
24 static uint64_t periodic_data[CONFIG_TIMER_TEST_SAMPLES + 1];
25 static uint64_t periodic_start, periodic_end;
26 static struct k_timer periodic_timer;
27 static struct k_sem periodic_sem;
28
29 /*
30 * The following code collects periodic time samples using the timer's
31 * auto-restart feature based on its period argument.
32 */
33
timer_period_fn(struct k_timer * t)34 static void timer_period_fn(struct k_timer *t)
35 {
36 uint64_t curr_cycle;
37
38 #ifdef CONFIG_TIMER_EXTERNAL_TEST
39 gpio_pin_toggle_dt(&timer_out);
40 #endif
41
42 #ifdef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
43 curr_cycle = k_cycle_get_64();
44 #else
45 curr_cycle = k_cycle_get_32();
46 #endif
47 periodic_data[periodic_idx] = curr_cycle;
48
49 if (periodic_idx == 0) {
50 periodic_start = curr_cycle;
51 }
52 ++periodic_idx;
53 if (periodic_idx >= ARRAY_SIZE(periodic_data)) {
54 periodic_end = curr_cycle;
55 k_timer_stop(t);
56 k_sem_give(&periodic_sem);
57 }
58 }
59
collect_timer_period_time_samples(void)60 static void collect_timer_period_time_samples(void)
61 {
62 k_timer_init(&periodic_timer, timer_period_fn, NULL);
63 k_timer_start(&periodic_timer, K_NO_WAIT, K_USEC(CONFIG_TIMER_TEST_PERIOD));
64 }
65
66 /*
67 * The following code collects periodic time samples by explicitly restarting
68 * the timer and relying solely on the timer's start delay argument to
69 * create periodicity.
70 */
71
timer_startdelay_fn(struct k_timer * t)72 static void timer_startdelay_fn(struct k_timer *t)
73 {
74 uint64_t curr_cycle;
75
76 #ifdef CONFIG_TIMER_EXTERNAL_TEST
77 gpio_pin_toggle_dt(&timer_out);
78 #endif
79
80 #ifdef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
81 curr_cycle = k_cycle_get_64();
82 #else
83 curr_cycle = k_cycle_get_32();
84 #endif
85 periodic_data[periodic_idx] = curr_cycle;
86
87 if (periodic_idx == 0) {
88 periodic_start = curr_cycle;
89 }
90 ++periodic_idx;
91 if (periodic_idx < ARRAY_SIZE(periodic_data)) {
92 k_timer_start(t, K_USEC(CONFIG_TIMER_TEST_PERIOD), K_FOREVER);
93 } else {
94 periodic_end = curr_cycle;
95 k_sem_give(&periodic_sem);
96 }
97 }
98
collect_timer_startdelay_time_samples(void)99 static void collect_timer_startdelay_time_samples(void)
100 {
101 k_timer_init(&periodic_timer, timer_startdelay_fn, NULL);
102 k_timer_start(&periodic_timer, K_NO_WAIT, K_FOREVER);
103 }
104
105 /* Get a difference in cycles between one timer count and an earlier one
106 * accounting for potentially wrapped values.
107 *
108 * @retval 0 an unhandled wrap of the timer occurred and the value should be ignored
109 */
periodic_diff(uint64_t later,uint64_t earlier)110 static uint64_t periodic_diff(uint64_t later, uint64_t earlier)
111 {
112 /* Timer wrap around, will be ignored in statistics */
113 if (later < earlier) {
114 TC_PRINT("WARNING: Caught a timer wrap-around !\n");
115 return 0;
116 }
117
118 return later - earlier;
119 }
120
cycles_to_us(uint64_t cycles)121 static double cycles_to_us(uint64_t cycles)
122 {
123 return 1000000.0 * cycles / sys_clock_hw_cycles_per_sec();
124 }
125
126 /**
127 * @brief Test a timers jitter and drift over time
128 */
do_test_using(void (* sample_collection_fn)(void),const char * mechanism)129 static void do_test_using(void (*sample_collection_fn)(void), const char *mechanism)
130 {
131 k_timeout_t actual_timeout = K_USEC(CONFIG_TIMER_TEST_PERIOD);
132 uint64_t expected_duration = (uint64_t)actual_timeout.ticks * CONFIG_TIMER_TEST_SAMPLES;
133
134 TC_PRINT("collecting time samples for approx %llu seconds\n",
135 k_ticks_to_ms_ceil64(expected_duration) / MSEC_PER_SEC);
136
137 periodic_idx = 0;
138 k_sem_init(&periodic_sem, 0, 1);
139
140 /* Align to tick boundary. Otherwise the first handler execution
141 * might turn out to be significantly late and cause the test to
142 * fail. This can happen if k_timer_start() is called right before
143 * the upcoming tick boundary and in consequence the tick passes
144 * between the moment when the kernel decides what tick to use for
145 * the next timeout and the moment when the system timer actually
146 * sets up that timeout.
147 */
148 k_sleep(K_TICKS(1));
149
150 sample_collection_fn();
151 k_sem_take(&periodic_sem, K_FOREVER);
152
153 TC_PRINT("periodic timer samples gathered, calculating statistics\n");
154
155 /* calculate variance, and precision */
156 uint64_t total_cycles = 0;
157 uint32_t periodic_rollovers = 0;
158
159 uint64_t max_cyc = 0;
160 uint64_t min_cyc = UINT64_MAX;
161
162 for (int i = 0; i < CONFIG_TIMER_TEST_SAMPLES; i++) {
163 uint64_t diff = periodic_diff(periodic_data[i + 1], periodic_data[i]);
164
165 if (diff == 0) {
166 periodic_rollovers++;
167 } else {
168 total_cycles += diff;
169 min_cyc = MIN(diff, min_cyc);
170 max_cyc = MAX(diff, max_cyc);
171 }
172 }
173
174 #ifndef CONFIG_TIMER_HAS_64BIT_CYCLE_COUNTER
175 /*
176 * Account for rollovers if any, and only when k_cycle_get_32()
177 * is used. This should not happen with k_cycle_get_64() and will
178 * be trapped later otherwise.
179 */
180 periodic_end += (1ULL << 32) * periodic_rollovers;
181 #endif
182
183 double min_us = cycles_to_us(min_cyc);
184 double max_us = cycles_to_us(max_cyc);
185
186 double mean_cyc =
187 (double)total_cycles / (double)(CONFIG_TIMER_TEST_SAMPLES - periodic_rollovers);
188 double mean_us = cycles_to_us(total_cycles) /
189 (double)(CONFIG_TIMER_TEST_SAMPLES - periodic_rollovers);
190 double variance_us = 0;
191 double variance_cyc = 0;
192
193 for (int i = 0; i < CONFIG_TIMER_TEST_SAMPLES; i++) {
194 uint64_t diff = periodic_diff(periodic_data[i + 1], periodic_data[i]);
195
196 if (diff != 0) {
197 double mean_cyc_diff = (double)diff - mean_cyc;
198 double mean_us_diff = cycles_to_us(diff) - mean_us;
199 double mean_cyc_diff_sq = mean_cyc_diff * mean_cyc_diff;
200 double mean_us_diff_sq = mean_us_diff * mean_us_diff;
201
202 variance_us += mean_us_diff_sq;
203 variance_cyc += mean_cyc_diff_sq;
204 }
205 }
206
207 /* A measure of how wide the distribution is, ideal is 0 */
208 variance_us = variance_us / (double)(CONFIG_TIMER_TEST_SAMPLES - periodic_rollovers);
209 variance_cyc = variance_cyc / (double)(CONFIG_TIMER_TEST_SAMPLES - periodic_rollovers);
210
211 /* A measure of timer precision, ideal is 0 */
212 double stddev_us = sqrt(variance_us);
213 double stddev_cyc = sqrt(variance_cyc);
214
215 /* Use double precision math here as integer overflows are possible in doing all the
216 * conversions otherwise
217 */
218 double expected_time_us =
219 (double)CONFIG_TIMER_TEST_PERIOD * (double)CONFIG_TIMER_TEST_SAMPLES;
220 double actual_time_us = cycles_to_us(periodic_end - periodic_start);
221
222 /* While this could be non-integer, the mean should be very close to it over time */
223 double expected_period =
224 (double)CONFIG_TIMER_TEST_PERIOD * (double)sys_clock_hw_cycles_per_sec()
225 / 1000000.0;
226 /*
227 * Expected period drift(us) due to round up/down errors during the
228 * conversion between ticks, cycles and delay.
229 */
230 uint32_t cyc_per_tick = sys_clock_hw_cycles_per_sec()
231 / CONFIG_SYS_CLOCK_TICKS_PER_SEC;
232 double expected_period_drift = ((double)actual_timeout.ticks * cyc_per_tick
233 - expected_period) / sys_clock_hw_cycles_per_sec() * 1000000;
234 double expected_time_drift_us = expected_period_drift
235 * CONFIG_TIMER_TEST_SAMPLES;
236 double time_diff_us = actual_time_us - expected_time_us
237 - expected_time_drift_us;
238 /* If max stddev is lower than a single clock cycle then round it up. */
239 uint32_t max_stddev = MAX(k_cyc_to_us_ceil32(1), CONFIG_TIMER_TEST_MAX_STDDEV);
240
241 TC_PRINT("timer clock rate %d, kernel tick rate %d\n",
242 sys_clock_hw_cycles_per_sec(), CONFIG_SYS_CLOCK_TICKS_PER_SEC);
243 if ((USEC_PER_SEC / CONFIG_TIMER_TEST_PERIOD) > CONFIG_SYS_CLOCK_TICKS_PER_SEC) {
244 TC_PRINT("test timer period (%u us) is smaller than "
245 "system tick period (%u us)\n",
246 CONFIG_TIMER_TEST_PERIOD, k_ticks_to_us_near32(1));
247 zassert_true(expected_period_drift != 0.0);
248 }
249 if (expected_period_drift != 0.0) {
250 TC_PRINT("expected period drift: %.8g us\n", expected_period_drift);
251 }
252 TC_PRINT("period duration statistics for %d samples (%u rollovers):\n",
253 CONFIG_TIMER_TEST_SAMPLES - periodic_rollovers, periodic_rollovers);
254 TC_PRINT(" expected: %d us, \t%f cycles\n",
255 CONFIG_TIMER_TEST_PERIOD, expected_period);
256 TC_PRINT(" min: %f us, \t%llu cycles\n", min_us, min_cyc);
257 TC_PRINT(" max: %f us, \t%llu cycles\n", max_us, max_cyc);
258 TC_PRINT(" mean: %f us, \t%f cycles\n", mean_us, mean_cyc);
259 TC_PRINT(" variance: %f us, \t%f cycles\n", variance_us, variance_cyc);
260 TC_PRINT(" stddev: %f us, \t%f cycles\n", stddev_us, stddev_cyc);
261 TC_PRINT("timer start cycle %llu, end cycle %llu,\n"
262 "total time %f us, expected time %f us,\n"
263 "expected time drift %f us, difference %f us\n",
264 periodic_start, periodic_end, actual_time_us, expected_time_us,
265 expected_time_drift_us, time_diff_us);
266
267 /* Record the stats gathered as a JSON object including related CONFIG_* params. */
268 TC_PRINT("RECORD: {"
269 "\"testcase\":\"jitter_drift_timer\", \"mechanism\":\"%s\""
270 ", \"stats_count\":%d, \"rollovers\":%d"
271 ", \"mean_us\":%.6f, \"mean_cycles\":%.0f"
272 ", \"stddev_us\":%.6f, \"stddev_cycles\":%.0f"
273 ", \"var_us\":%.6f, \"var_cycles\":%.0f"
274 ", \"min_us\":%.6f, \"min_cycles\":%llu"
275 ", \"max_us\":%.6f, \"max_cycles\":%llu"
276 ", \"timer_start_cycle\": %llu, \"timer_end_cycle\": %llu"
277 ", \"total_time_us\":%.6f"
278 ", \"expected_total_time_us\":%.6f"
279 ", \"expected_total_drift_us\":%.6f"
280 ", \"total_drift_us\":%.6f"
281 ", \"expected_period_cycles\":%.0f"
282 ", \"expected_period_drift_us\":%.6f"
283 ", \"sys_clock_hw_cycles_per_sec\":%d"
284 ", \"CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC\":%d"
285 ", \"CONFIG_SYS_CLOCK_TICKS_PER_SEC\":%d"
286 ", \"CONFIG_TIMER_TEST_PERIOD\":%d"
287 ", \"CONFIG_TIMER_TEST_SAMPLES\":%d"
288 ", \"MAX_STD_DEV\":%d"
289 "}\n",
290 mechanism,
291 CONFIG_TIMER_TEST_SAMPLES - periodic_rollovers, periodic_rollovers,
292 mean_us, mean_cyc,
293 stddev_us, stddev_cyc,
294 variance_us, variance_cyc,
295 min_us, min_cyc,
296 max_us, max_cyc,
297 periodic_start, periodic_end,
298 actual_time_us,
299 expected_time_us,
300 expected_time_drift_us,
301 time_diff_us,
302 expected_period,
303 expected_period_drift,
304 sys_clock_hw_cycles_per_sec(),
305 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC,
306 CONFIG_SYS_CLOCK_TICKS_PER_SEC,
307 CONFIG_TIMER_TEST_PERIOD,
308 CONFIG_TIMER_TEST_SAMPLES,
309 max_stddev
310 );
311
312 /* Validate the maximum/minimum timer period is off by no more than 10% */
313 double test_period = (double)CONFIG_TIMER_TEST_PERIOD;
314 double period_max_drift_percentage =
315 (double)CONFIG_TIMER_TEST_PERIOD_MAX_DRIFT_PERCENT / 100;
316 double min_us_bound = test_period - period_max_drift_percentage * test_period
317 + expected_period_drift;
318 double max_us_bound = test_period + period_max_drift_percentage * test_period
319 + expected_period_drift;
320
321 zassert_true(min_us >= min_us_bound,
322 "Shortest timer period too short (off by more than expected %d%)",
323 CONFIG_TIMER_TEST_PERIOD_MAX_DRIFT_PERCENT);
324 zassert_true(max_us <= max_us_bound,
325 "Longest timer period too long (off by more than expected %d%)",
326 CONFIG_TIMER_TEST_PERIOD_MAX_DRIFT_PERCENT);
327
328
329 /* Validate the timer deviation (precision/jitter of the timer) is within a configurable
330 * bound
331 */
332 zassert_true(stddev_us < (double)max_stddev,
333 "Standard deviation (in microseconds) outside expected bound");
334
335 /* Validate the timer drift (accuracy over time) is within a configurable bound */
336 zassert_true(fabs(time_diff_us) < CONFIG_TIMER_TEST_MAX_DRIFT,
337 "Drift (in microseconds) outside expected bound");
338 }
339
ZTEST(timer_jitter_drift,test_jitter_drift_timer_period)340 ZTEST(timer_jitter_drift, test_jitter_drift_timer_period)
341 {
342 TC_PRINT("periodic timer behavior test using built-in restart mechanism\n");
343 #ifdef CONFIG_TIMER_EXTERNAL_TEST
344 TC_PRINT("===== External Tool Sync Point =====\n");
345 TC_PRINT("===== builtin =====\n");
346 TC_PRINT("===== Waiting %d seconds =====\n",
347 CONFIG_TIMER_EXTERNAL_TEST_SYNC_DELAY);
348 k_sleep(K_SECONDS(CONFIG_TIMER_EXTERNAL_TEST_SYNC_DELAY));
349 gpio_pin_configure_dt(&timer_out, GPIO_OUTPUT_LOW);
350 #endif
351 do_test_using(collect_timer_period_time_samples, "builtin");
352 }
353
ZTEST(timer_jitter_drift,test_jitter_drift_timer_startdelay)354 ZTEST(timer_jitter_drift, test_jitter_drift_timer_startdelay)
355 {
356 TC_PRINT("periodic timer behavior test using explicit start with delay\n");
357 #ifdef CONFIG_TIMER_EXTERNAL_TEST
358 TC_PRINT("===== External Tool Sync Point =====\n");
359 TC_PRINT("===== startdelay =====\n");
360 TC_PRINT("===== Waiting %d seconds =====\n",
361 CONFIG_TIMER_EXTERNAL_TEST_SYNC_DELAY);
362 k_sleep(K_SECONDS(CONFIG_TIMER_EXTERNAL_TEST_SYNC_DELAY));
363 gpio_pin_configure_dt(&timer_out, GPIO_OUTPUT_LOW);
364 #endif
365 do_test_using(collect_timer_startdelay_time_samples, "startdelay");
366 }
367
368 ZTEST_SUITE(timer_jitter_drift, NULL, NULL, NULL, NULL, NULL);
369