1 /*
2  * Copyright (c) 2017 Oticon A/S
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /**
8  * This provides a model of:
9  *  - A system tick
10  *  - A real time clock
11  *  - A one shot HW timer which can be used to awake the CPU at a given time
12  *  - The clock source for all of this, and therefore for native_posix
13  *
14  * Please see doc/board.rst for more information, specially sections:
15  *  About time in native_posix
16  *  Peripherals:
17  *	Clock source, system tick and timer
18  *	Real time clock
19  */
20 
21 #include <stdint.h>
22 #include <time.h>
23 #include <stdbool.h>
24 #include <math.h>
25 #include "hw_models_top.h"
26 #include "irq_ctrl.h"
27 #include "board_soc.h"
28 #include <zephyr/types.h>
29 #include <zephyr/arch/posix/posix_trace.h>
30 #include <zephyr/sys/util.h>
31 #include "cmdline.h"
32 #include "posix_native_task.h"
33 
34 #define DEBUG_NP_TIMER 0
35 
36 #if DEBUG_NP_TIMER
37 
38 /**
39  * Helper function to convert a 64 bit time in microseconds into a string.
40  * The format will always be: hh:mm:ss.ssssss\0
41  *
42  * Note: the caller has to allocate the destination buffer (at least 17 chars)
43  */
44 #include <stdio.h>
us_time_to_str(char * dest,uint64_t time)45 static char *us_time_to_str(char *dest, uint64_t time)
46 {
47 	if (time != NEVER) {
48 		unsigned int hour;
49 		unsigned int minute;
50 		unsigned int second;
51 		unsigned int us;
52 
53 		hour   = (time / 3600U / 1000000U) % 24;
54 		minute = (time / 60U / 1000000U) % 60;
55 		second = (time / 1000000U) % 60;
56 		us     = time % 1000000;
57 
58 		sprintf(dest, "%02u:%02u:%02u.%06u", hour, minute, second, us);
59 	} else {
60 		sprintf(dest, " NEVER/UNKNOWN ");
61 
62 	}
63 	return dest;
64 }
65 #endif
66 
67 uint64_t hw_timer_timer;
68 
69 uint64_t hw_timer_tick_timer;
70 uint64_t hw_timer_awake_timer;
71 
72 static uint64_t tick_p; /* Period of the ticker */
73 static int64_t silent_ticks;
74 
75 static bool real_time_mode =
76 #if defined(CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME)
77 	true;
78 #else
79 	false;
80 #endif
81 
82 static bool reset_rtc; /*"Reset" the RTC on boot*/
83 
84 /*
85  * When this executable started running, this value shall not be changed after
86  * boot
87  */
88 static uint64_t boot_time;
89 
90 /*
91  * Ratio of the simulated clock to the real host time
92  * For ex. a clock_ratio = 1+100e-6 means the simulated time is 100ppm faster
93  * than real time
94  */
95 static double clock_ratio = 1.0;
96 
97 #if DEBUG_NP_TIMER
98 /*
99  * Offset of the simulated time vs the real host time due to drift/clock ratio
100  * until "last_radj_*time"
101  *
102  * A positive value means simulated time is ahead of the host time
103  *
104  * This variable is only kept for debugging purposes
105  */
106 static int64_t last_drift_offset;
107 #endif
108 
109 /*
110  * Offsets of the RTC relative to the hardware models simu_time
111  * "simu_time" == simulated time which starts at 0 on boot
112  */
113 static int64_t rtc_offset;
114 
115 /* Last host/real time when the ratio was adjusted */
116 static uint64_t last_radj_rtime;
117 /* Last simulated time when the ratio was adjusted */
118 static uint64_t last_radj_stime;
119 
120 extern uint64_t posix_get_hw_cycle(void);
121 
hwtimer_set_real_time_mode(bool new_rt)122 void hwtimer_set_real_time_mode(bool new_rt)
123 {
124 	real_time_mode = new_rt;
125 }
126 
hwtimer_update_timer(void)127 static void hwtimer_update_timer(void)
128 {
129 	hw_timer_timer = MIN(hw_timer_tick_timer, hw_timer_awake_timer);
130 }
131 
host_clock_gettime(struct timespec * tv)132 static inline void host_clock_gettime(struct timespec *tv)
133 {
134 #if defined(CLOCK_MONOTONIC_RAW)
135 	clock_gettime(CLOCK_MONOTONIC_RAW, tv);
136 #else
137 	clock_gettime(CLOCK_MONOTONIC, tv);
138 #endif
139 }
140 
get_host_us_time(void)141 uint64_t get_host_us_time(void)
142 {
143 	struct timespec tv;
144 
145 	host_clock_gettime(&tv);
146 	return (uint64_t)tv.tv_sec * 1e6 + tv.tv_nsec / 1000;
147 }
148 
hwtimer_init(void)149 void hwtimer_init(void)
150 {
151 	silent_ticks = 0;
152 	hw_timer_tick_timer = NEVER;
153 	hw_timer_awake_timer = NEVER;
154 	hwtimer_update_timer();
155 	if (real_time_mode) {
156 		boot_time = get_host_us_time();
157 		last_radj_rtime = boot_time;
158 		last_radj_stime = 0U;
159 	}
160 	if (!reset_rtc) {
161 		struct timespec tv;
162 		uint64_t realhosttime;
163 
164 		clock_gettime(CLOCK_REALTIME, &tv);
165 		realhosttime = (uint64_t)tv.tv_sec * 1e6 + tv.tv_nsec / 1000;
166 
167 		rtc_offset += realhosttime;
168 	}
169 }
170 
hwtimer_cleanup(void)171 void hwtimer_cleanup(void)
172 {
173 
174 }
175 
176 /**
177  * Enable the HW timer tick interrupts with a period <period> in microseconds
178  */
hwtimer_enable(uint64_t period)179 void hwtimer_enable(uint64_t period)
180 {
181 	tick_p = period;
182 	hw_timer_tick_timer = hwm_get_time() + tick_p;
183 	hwtimer_update_timer();
184 	hwm_find_next_timer();
185 }
186 
hwtimer_tick_timer_reached(void)187 static void hwtimer_tick_timer_reached(void)
188 {
189 	if (real_time_mode) {
190 		uint64_t expected_rt = (hw_timer_tick_timer - last_radj_stime)
191 				    / clock_ratio
192 				    + last_radj_rtime;
193 		uint64_t real_time = get_host_us_time();
194 
195 		int64_t diff = expected_rt - real_time;
196 
197 #if DEBUG_NP_TIMER
198 		char es[30];
199 		char rs[30];
200 
201 		us_time_to_str(es, expected_rt - boot_time);
202 		us_time_to_str(rs, real_time - boot_time);
203 		printf("tick @%5llims: diff = expected_rt - real_time = "
204 			"%5lli = %s - %s\n",
205 			hw_timer_tick_timer/1000U, diff, es, rs);
206 #endif
207 
208 		if (diff > 0) { /* we need to slow down */
209 			struct timespec requested_time;
210 			struct timespec remaining;
211 
212 			requested_time.tv_sec  = diff / 1e6;
213 			requested_time.tv_nsec = (diff -
214 						 requested_time.tv_sec*1e6)*1e3;
215 
216 			(void) nanosleep(&requested_time, &remaining);
217 		}
218 	}
219 
220 	hw_timer_tick_timer += tick_p;
221 	hwtimer_update_timer();
222 
223 	if (silent_ticks > 0) {
224 		silent_ticks -= 1;
225 	} else {
226 		hw_irq_ctrl_set_irq(TIMER_TICK_IRQ);
227 	}
228 }
229 
hwtimer_awake_timer_reached(void)230 static void hwtimer_awake_timer_reached(void)
231 {
232 	hw_timer_awake_timer = NEVER;
233 	hwtimer_update_timer();
234 	hw_irq_ctrl_set_irq(PHONY_HARD_IRQ);
235 }
236 
hwtimer_timer_reached(void)237 void hwtimer_timer_reached(void)
238 {
239 	uint64_t Now = hw_timer_timer;
240 
241 	if (hw_timer_awake_timer == Now) {
242 		hwtimer_awake_timer_reached();
243 	}
244 
245 	if (hw_timer_tick_timer == Now) {
246 		hwtimer_tick_timer_reached();
247 	}
248 }
249 
250 /**
251  * The timer HW will awake the CPU (without an interrupt) at least when <time>
252  * comes (it may awake it earlier)
253  *
254  * If there was a previous request for an earlier time, the old one will prevail
255  *
256  * This is meant for k_busy_wait() like functionality
257  */
hwtimer_wake_in_time(uint64_t time)258 void hwtimer_wake_in_time(uint64_t time)
259 {
260 	if (hw_timer_awake_timer > time) {
261 		hw_timer_awake_timer = time;
262 		hwtimer_update_timer();
263 		hwm_find_next_timer();
264 	}
265 }
266 
267 /**
268  * The kernel wants to skip the next sys_ticks tick interrupts
269  * If sys_ticks == 0, the next interrupt will be raised.
270  */
hwtimer_set_silent_ticks(int64_t sys_ticks)271 void hwtimer_set_silent_ticks(int64_t sys_ticks)
272 {
273 	silent_ticks = sys_ticks;
274 }
275 
hwtimer_get_pending_silent_ticks(void)276 int64_t hwtimer_get_pending_silent_ticks(void)
277 {
278 	return silent_ticks;
279 }
280 
281 
282 /**
283  * During boot set the real time clock simulated time not start
284  * from the real host time
285  */
hwtimer_reset_rtc(void)286 void hwtimer_reset_rtc(void)
287 {
288 	reset_rtc = true;
289 }
290 
291 /**
292  * Set a time offset (microseconds) of the RTC simulated time
293  * Note: This should not be used after starting
294  */
hwtimer_set_rtc_offset(int64_t offset)295 void hwtimer_set_rtc_offset(int64_t offset)
296 {
297 	rtc_offset = offset;
298 }
299 
300 /**
301  * Set the ratio of the simulated time to host (real) time.
302  * Note: This should not be used after starting
303  */
hwtimer_set_rt_ratio(double ratio)304 void hwtimer_set_rt_ratio(double ratio)
305 {
306 	clock_ratio = ratio;
307 }
308 
309 /**
310  * Increase or decrease the RTC simulated time by offset_delta
311  */
hwtimer_adjust_rtc_offset(int64_t offset_delta)312 void hwtimer_adjust_rtc_offset(int64_t offset_delta)
313 {
314 	rtc_offset += offset_delta;
315 }
316 
317 /**
318  * Adjust the ratio of the simulated time by a factor
319  */
hwtimer_adjust_rt_ratio(double ratio_correction)320 void hwtimer_adjust_rt_ratio(double ratio_correction)
321 {
322 	uint64_t current_stime = hwm_get_time();
323 	int64_t s_diff = current_stime - last_radj_stime;
324 	/* Accumulated real time drift time since last adjustment: */
325 
326 	last_radj_rtime += s_diff / clock_ratio;
327 	last_radj_stime = current_stime;
328 
329 #if DEBUG_NP_TIMER
330 	char ct[30];
331 	int64_t r_drift = (long double)(clock_ratio-1.0)/(clock_ratio)*s_diff;
332 
333 	last_drift_offset += r_drift;
334 	us_time_to_str(ct, current_stime);
335 
336 	printf("%s(): @%s, s_diff= %llius after last adjust\n"
337 		" during which we drifted %.3fms\n"
338 		" total acc drift (last_drift_offset) = %.3fms\n"
339 		" last_radj_rtime = %.3fms (+%.3fms )\n"
340 		" Ratio adjusted to %f\n",
341 		__func__, ct, s_diff,
342 		r_drift/1000.0,
343 		last_drift_offset/1000.0,
344 		last_radj_rtime/1000.0,
345 		s_diff/clock_ratio/1000.0,
346 		clock_ratio*ratio_correction);
347 #endif
348 
349 	clock_ratio *= ratio_correction;
350 }
351 
352 /**
353  * Return the current simulated RTC time in microseconds
354  */
hwtimer_get_simu_rtc_time(void)355 int64_t hwtimer_get_simu_rtc_time(void)
356 {
357 	return hwm_get_time() + rtc_offset;
358 }
359 
360 
361 /**
362  * Return a version of the host time which would have drifted as if the host
363  * real time clock had been running from the native_posix clock, and adjusted
364  * both in rate and in offsets as the native_posix has been.
365  *
366  * Note that this time may be significantly ahead of the simulated time
367  * (the time the Zephyr kernel thinks it is).
368  * This will be the case in general if native_posix is not able to run at or
369  * faster than real time.
370  */
hwtimer_get_pseudohost_rtc_time(uint32_t * nsec,uint64_t * sec)371 void hwtimer_get_pseudohost_rtc_time(uint32_t *nsec, uint64_t *sec)
372 {
373 	/*
374 	 * Note: long double has a 64bits mantissa in x86.
375 	 * Therefore to avoid loss of precision after 500 odd years into
376 	 * the epoch, we first calculate the offset from the last adjustment
377 	 * time split in us and ns. So we keep the full precision for 500 odd
378 	 * years after the last clock ratio adjustment (or native_posix boot,
379 	 * whichever is latest).
380 	 * Meaning, we will still start to loose precision after 500 off
381 	 * years of runtime without a clock ratio adjustment, but that really
382 	 * should not be much of a problem, given that the ns lower digits are
383 	 * pretty much noise anyhow.
384 	 * (So, all this is a huge overkill)
385 	 *
386 	 * The operation below in plain is just:
387 	 *   st = (rt - last_rt_adj_time)*ratio + last_dt_adj_time
388 	 * where st = simulated time
389 	 *       rt = real time
390 	 *       last_rt_adj_time = time (real) when the last ratio
391 	 *			    adjustment took place
392 	 *       last_st_adj_time = time (simulated) when the last ratio
393 	 *			    adjustment took place
394 	 *       ratio = ratio between simulated time and real time
395 	 */
396 	struct timespec tv;
397 
398 	host_clock_gettime(&tv);
399 
400 	uint64_t rt_us = (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_nsec / 1000;
401 	uint32_t rt_ns = tv.tv_nsec % 1000;
402 
403 	long double drt_us = (long double)rt_us - last_radj_rtime;
404 	long double drt_ns = drt_us * 1000.0L + (long double)rt_ns;
405 	long double st = drt_ns * (long double)clock_ratio +
406 			 (long double)(last_radj_stime + rtc_offset) * 1000.0L;
407 
408 	*nsec = fmodl(st, 1e9L);
409 	*sec = st / 1e9L;
410 }
411 
412 static struct {
413 	double stop_at;
414 	double rtc_offset;
415 	double rt_drift;
416 	double rt_ratio;
417 } args;
418 
cmd_stop_at_found(char * argv,int offset)419 static void cmd_stop_at_found(char *argv, int offset)
420 {
421 	ARG_UNUSED(offset);
422 	if (args.stop_at < 0) {
423 		posix_print_error_and_exit("Error: stop-at must be positive "
424 					   "(%s)\n", argv);
425 	}
426 	hwm_set_end_of_time(args.stop_at*1e6);
427 }
428 
cmd_realtime_found(char * argv,int offset)429 static void cmd_realtime_found(char *argv, int offset)
430 {
431 	ARG_UNUSED(argv);
432 	ARG_UNUSED(offset);
433 	hwtimer_set_real_time_mode(true);
434 }
435 
cmd_no_realtime_found(char * argv,int offset)436 static void cmd_no_realtime_found(char *argv, int offset)
437 {
438 	ARG_UNUSED(argv);
439 	ARG_UNUSED(offset);
440 	hwtimer_set_real_time_mode(false);
441 }
442 
cmd_rtcoffset_found(char * argv,int offset)443 static void cmd_rtcoffset_found(char *argv, int offset)
444 {
445 	ARG_UNUSED(argv);
446 	ARG_UNUSED(offset);
447 	hwtimer_set_rtc_offset(args.rtc_offset*1e6);
448 }
449 
cmd_rt_drift_found(char * argv,int offset)450 static void cmd_rt_drift_found(char *argv, int offset)
451 {
452 	ARG_UNUSED(argv);
453 	ARG_UNUSED(offset);
454 	if (!(args.rt_drift > -1)) {
455 		posix_print_error_and_exit("The drift needs to be > -1. "
456 					  "Please use --help for more info\n");
457 	}
458 	args.rt_ratio = args.rt_drift + 1;
459 	hwtimer_set_rt_ratio(args.rt_ratio);
460 }
461 
cmd_rt_ratio_found(char * argv,int offset)462 static void cmd_rt_ratio_found(char *argv, int offset)
463 {
464 	ARG_UNUSED(argv);
465 	ARG_UNUSED(offset);
466 	if ((args.rt_ratio <= 0)) {
467 		posix_print_error_and_exit("The ratio needs to be > 0. "
468 					  "Please use --help for more info\n");
469 	}
470 	hwtimer_set_rt_ratio(args.rt_ratio);
471 }
472 
cmd_rtcreset_found(char * argv,int offset)473 static void cmd_rtcreset_found(char *argv, int offset)
474 {
475 	ARG_UNUSED(argv);
476 	ARG_UNUSED(offset);
477 	hwtimer_reset_rtc();
478 }
479 
native_add_time_options(void)480 static void native_add_time_options(void)
481 {
482 	static struct args_struct_t timer_options[] = {
483 		/*
484 		 * Fields:
485 		 * manual, mandatory, switch,
486 		 * option_name, var_name ,type,
487 		 * destination, callback,
488 		 * description
489 		 */
490 		{false, false, true,
491 		"rt", "", 'b',
492 		NULL, cmd_realtime_found,
493 		"Slow down the execution to the host real time, "
494 		"or a ratio of it (see --rt-ratio below)"},
495 
496 		{false, false, true,
497 		"no-rt", "", 'b',
498 		NULL, cmd_no_realtime_found,
499 		"Do NOT slow down the execution to real time, but advance "
500 		"Zephyr's time as fast as possible and decoupled from the host "
501 		"time"},
502 
503 		{false, false, false,
504 		"rt-drift", "dratio", 'd',
505 		(void *)&args.rt_drift, cmd_rt_drift_found,
506 		"Drift of the simulated clock relative to the host real time. "
507 		"Normally this would be set to a value of a few ppm (e.g. 50e-6"
508 		") "
509 		"This option has no effect in non real time mode"
510 		},
511 
512 		{false, false, false,
513 		"rt-ratio", "ratio", 'd',
514 		(void *)&args.rt_ratio, cmd_rt_ratio_found,
515 		"Relative speed of the simulated time vs real time. "
516 		"For ex. set to 2 to have simulated time pass at double the "
517 		"speed of real time. "
518 		"Note that both rt-drift & rt-ratio adjust the same clock "
519 		"speed, and therefore it does not make sense to use them "
520 		"simultaneously. "
521 		"This option has no effect in non real time mode"
522 		},
523 
524 		{false, false, false,
525 		"rtc-offset", "time_offset", 'd',
526 		(void *)&args.rtc_offset, cmd_rtcoffset_found,
527 		"At boot offset the RTC clock by this amount of seconds"
528 		},
529 
530 		{false, false, true,
531 		"rtc-reset", "", 'b',
532 		NULL, cmd_rtcreset_found,
533 		"Start the simulated real time clock at 0. Otherwise it starts "
534 		"matching the value provided by the host real time clock"},
535 
536 		{false, false, false,
537 		 "stop_at", "time", 'd',
538 		(void *)&args.stop_at, cmd_stop_at_found,
539 		"In simulated seconds, when to stop automatically"},
540 
541 		ARG_TABLE_ENDMARKER};
542 
543 	native_add_command_line_opts(timer_options);
544 }
545 
546 NATIVE_TASK(native_add_time_options, PRE_BOOT_1, 1);
547