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