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