Branch data Line data Source code
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>
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 : :
122 : 0 : void hwtimer_set_real_time_mode(bool new_rt)
123 : : {
124 : 0 : real_time_mode = new_rt;
125 : 0 : }
126 : :
127 : 2 : static void hwtimer_update_timer(void)
128 : : {
129 : 2 : hw_timer_timer = MIN(hw_timer_tick_timer, hw_timer_awake_timer);
130 : 2 : }
131 : :
132 : 0 : static inline void host_clock_gettime(struct timespec *tv)
133 : : {
134 : : #if defined(CLOCK_MONOTONIC_RAW)
135 : 0 : clock_gettime(CLOCK_MONOTONIC_RAW, tv);
136 : : #else
137 : : clock_gettime(CLOCK_MONOTONIC, tv);
138 : : #endif
139 : 0 : }
140 : :
141 : 0 : uint64_t get_host_us_time(void)
142 : : {
143 : 0 : struct timespec tv;
144 : :
145 : 0 : host_clock_gettime(&tv);
146 : 0 : return (uint64_t)tv.tv_sec * 1e6 + tv.tv_nsec / 1000;
147 : : }
148 : :
149 : 1 : void hwtimer_init(void)
150 : : {
151 : 1 : silent_ticks = 0;
152 : 1 : hw_timer_tick_timer = NEVER;
153 : 1 : hw_timer_awake_timer = NEVER;
154 : 1 : hwtimer_update_timer();
155 [ - + ]: 1 : if (real_time_mode) {
156 : 0 : boot_time = get_host_us_time();
157 : 0 : last_radj_rtime = boot_time;
158 : 0 : last_radj_stime = 0U;
159 : : }
160 [ + - ]: 1 : if (!reset_rtc) {
161 : 1 : struct timespec tv;
162 : 1 : uint64_t realhosttime;
163 : :
164 : 1 : clock_gettime(CLOCK_REALTIME, &tv);
165 : 1 : realhosttime = (uint64_t)tv.tv_sec * 1e6 + tv.tv_nsec / 1000;
166 : :
167 : 1 : rtc_offset += realhosttime;
168 : : }
169 : 1 : }
170 : :
171 : 1 : void hwtimer_cleanup(void)
172 : : {
173 : :
174 : 1 : }
175 : :
176 : : /**
177 : : * Enable the HW timer tick interrupts with a period <period> in microseconds
178 : : */
179 : 1 : void hwtimer_enable(uint64_t period)
180 : : {
181 : 1 : tick_p = period;
182 : 1 : hw_timer_tick_timer = hwm_get_time() + tick_p;
183 : 1 : hwtimer_update_timer();
184 : 1 : hwm_find_next_timer();
185 : 1 : }
186 : :
187 : 0 : static void hwtimer_tick_timer_reached(void)
188 : : {
189 [ # # ]: 0 : if (real_time_mode) {
190 : 0 : uint64_t expected_rt = (hw_timer_tick_timer - last_radj_stime)
191 : 0 : / clock_ratio
192 : 0 : + last_radj_rtime;
193 : 0 : uint64_t real_time = get_host_us_time();
194 : :
195 : 0 : 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 [ # # ]: 0 : if (diff > 0) { /* we need to slow down */
209 : 0 : struct timespec requested_time;
210 : 0 : struct timespec remaining;
211 : :
212 : 0 : requested_time.tv_sec = diff / 1e6;
213 : 0 : requested_time.tv_nsec = (diff -
214 : 0 : requested_time.tv_sec*1e6)*1e3;
215 : :
216 : 0 : (void) nanosleep(&requested_time, &remaining);
217 : : }
218 : : }
219 : :
220 : 0 : hw_timer_tick_timer += tick_p;
221 : 0 : hwtimer_update_timer();
222 : :
223 [ # # ]: 0 : if (silent_ticks > 0) {
224 : 0 : silent_ticks -= 1;
225 : : } else {
226 : 0 : hw_irq_ctrl_set_irq(TIMER_TICK_IRQ);
227 : : }
228 : 0 : }
229 : :
230 : 0 : static void hwtimer_awake_timer_reached(void)
231 : : {
232 : 0 : hw_timer_awake_timer = NEVER;
233 : 0 : hwtimer_update_timer();
234 : 0 : hw_irq_ctrl_set_irq(PHONY_HARD_IRQ);
235 : 0 : }
236 : :
237 : 0 : void hwtimer_timer_reached(void)
238 : : {
239 : 0 : uint64_t Now = hw_timer_timer;
240 : :
241 [ # # ]: 0 : if (hw_timer_awake_timer == Now) {
242 : 0 : hwtimer_awake_timer_reached();
243 : : }
244 : :
245 [ # # ]: 0 : if (hw_timer_tick_timer == Now) {
246 : 0 : hwtimer_tick_timer_reached();
247 : : }
248 : 0 : }
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 : : */
258 : 0 : void hwtimer_wake_in_time(uint64_t time)
259 : : {
260 [ # # ]: 0 : if (hw_timer_awake_timer > time) {
261 : 0 : hw_timer_awake_timer = time;
262 : 0 : hwtimer_update_timer();
263 : 0 : hwm_find_next_timer();
264 : : }
265 : 0 : }
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 : : */
271 : 0 : void hwtimer_set_silent_ticks(int64_t sys_ticks)
272 : : {
273 : 0 : silent_ticks = sys_ticks;
274 : 0 : }
275 : :
276 : 0 : int64_t hwtimer_get_pending_silent_ticks(void)
277 : : {
278 : 0 : 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 : : */
286 : 0 : void hwtimer_reset_rtc(void)
287 : : {
288 : 0 : reset_rtc = true;
289 : 0 : }
290 : :
291 : : /**
292 : : * Set a time offset (microseconds) of the RTC simulated time
293 : : * Note: This should not be used after starting
294 : : */
295 : 0 : void hwtimer_set_rtc_offset(int64_t offset)
296 : : {
297 : 0 : rtc_offset = offset;
298 : 0 : }
299 : :
300 : : /**
301 : : * Set the ratio of the simulated time to host (real) time.
302 : : * Note: This should not be used after starting
303 : : */
304 : 0 : void hwtimer_set_rt_ratio(double ratio)
305 : : {
306 : 0 : clock_ratio = ratio;
307 : 0 : }
308 : :
309 : : /**
310 : : * Increase or decrease the RTC simulated time by offset_delta
311 : : */
312 : 0 : void hwtimer_adjust_rtc_offset(int64_t offset_delta)
313 : : {
314 : 0 : rtc_offset += offset_delta;
315 : 0 : }
316 : :
317 : : /**
318 : : * Adjust the ratio of the simulated time by a factor
319 : : */
320 : 0 : void hwtimer_adjust_rt_ratio(double ratio_correction)
321 : : {
322 : 0 : uint64_t current_stime = hwm_get_time();
323 : 0 : int64_t s_diff = current_stime - last_radj_stime;
324 : : /* Accumulated real time drift time since last adjustment: */
325 : :
326 : 0 : last_radj_rtime += s_diff / clock_ratio;
327 : 0 : 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 : 0 : clock_ratio *= ratio_correction;
350 : 0 : }
351 : :
352 : : /**
353 : : * Return the current simulated RTC time in microseconds
354 : : */
355 : 0 : int64_t hwtimer_get_simu_rtc_time(void)
356 : : {
357 : 0 : 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 : : */
371 : 0 : 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 : 0 : struct timespec tv;
397 : :
398 : 0 : host_clock_gettime(&tv);
399 : :
400 : 0 : uint64_t rt_us = (uint64_t)tv.tv_sec * 1000000ULL + tv.tv_nsec / 1000;
401 : 0 : uint32_t rt_ns = tv.tv_nsec % 1000;
402 : :
403 : 0 : long double drt_us = (long double)rt_us - last_radj_rtime;
404 : 0 : long double drt_ns = drt_us * 1000.0L + (long double)rt_ns;
405 : 0 : long double st = drt_ns * (long double)clock_ratio +
406 : 0 : (long double)(last_radj_stime + rtc_offset) * 1000.0L;
407 : :
408 : 0 : *nsec = fmodl(st, 1e9L);
409 : 0 : *sec = st / 1e9L;
410 : 0 : }
411 : :
412 : : static struct {
413 : : double stop_at;
414 : : double rtc_offset;
415 : : double rt_drift;
416 : : double rt_ratio;
417 : : } args;
418 : :
419 : 0 : static void cmd_stop_at_found(char *argv, int offset)
420 : : {
421 : 0 : ARG_UNUSED(offset);
422 [ # # ]: 0 : if (args.stop_at < 0) {
423 : 0 : posix_print_error_and_exit("Error: stop-at must be positive "
424 : : "(%s)\n", argv);
425 : : }
426 : 0 : hwm_set_end_of_time(args.stop_at*1e6);
427 : 0 : }
428 : :
429 : 0 : static void cmd_realtime_found(char *argv, int offset)
430 : : {
431 : 0 : ARG_UNUSED(argv);
432 : 0 : ARG_UNUSED(offset);
433 : 0 : hwtimer_set_real_time_mode(true);
434 : 0 : }
435 : :
436 : 0 : static void cmd_no_realtime_found(char *argv, int offset)
437 : : {
438 : 0 : ARG_UNUSED(argv);
439 : 0 : ARG_UNUSED(offset);
440 : 0 : hwtimer_set_real_time_mode(false);
441 : 0 : }
442 : :
443 : 0 : static void cmd_rtcoffset_found(char *argv, int offset)
444 : : {
445 : 0 : ARG_UNUSED(argv);
446 : 0 : ARG_UNUSED(offset);
447 : 0 : hwtimer_set_rtc_offset(args.rtc_offset*1e6);
448 : 0 : }
449 : :
450 : 0 : static void cmd_rt_drift_found(char *argv, int offset)
451 : : {
452 : 0 : ARG_UNUSED(argv);
453 : 0 : ARG_UNUSED(offset);
454 [ # # ]: 0 : if (!(args.rt_drift > -1)) {
455 : 0 : posix_print_error_and_exit("The drift needs to be > -1. "
456 : : "Please use --help for more info\n");
457 : : }
458 : 0 : args.rt_ratio = args.rt_drift + 1;
459 : 0 : hwtimer_set_rt_ratio(args.rt_ratio);
460 : 0 : }
461 : :
462 : 0 : static void cmd_rt_ratio_found(char *argv, int offset)
463 : : {
464 : 0 : ARG_UNUSED(argv);
465 : 0 : ARG_UNUSED(offset);
466 [ # # ]: 0 : if ((args.rt_ratio <= 0)) {
467 : 0 : posix_print_error_and_exit("The ratio needs to be > 0. "
468 : : "Please use --help for more info\n");
469 : : }
470 : 0 : hwtimer_set_rt_ratio(args.rt_ratio);
471 : 0 : }
472 : :
473 : 0 : static void cmd_rtcreset_found(char *argv, int offset)
474 : : {
475 : 0 : ARG_UNUSED(argv);
476 : 0 : ARG_UNUSED(offset);
477 : 0 : hwtimer_reset_rtc();
478 : 0 : }
479 : :
480 : 1 : static void native_add_time_options(void)
481 : : {
482 : 1 : 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 : 1 : native_add_command_line_opts(timer_options);
544 : 1 : }
545 : :
546 : : NATIVE_TASK(native_add_time_options, PRE_BOOT_1, 1);
|