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