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