546 lines
13 KiB
C
546 lines
13 KiB
C
/*
|
|
* Copyright (c) 2017 Oticon A/S
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* This provides a model of:
|
|
* - A system tick
|
|
* - A real time clock
|
|
* - A one shot HW timer which can be used to awake the CPU at a given time
|
|
* - The clock source for all of this, and therefore for native_posix
|
|
*
|
|
* Please see doc/board.rst for more information, specially sections:
|
|
* About time in native_posix
|
|
* Peripherals:
|
|
* Clock source, system tick and timer
|
|
* Real time clock
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <time.h>
|
|
#include <stdbool.h>
|
|
#include <math.h>
|
|
#include "hw_models_top.h"
|
|
#include "irq_ctrl.h"
|
|
#include "board_soc.h"
|
|
#include "zephyr/types.h"
|
|
#include "posix_trace.h"
|
|
#include <sys/util.h>
|
|
#include "cmdline.h"
|
|
#include "soc.h"
|
|
|
|
#define DEBUG_NP_TIMER 0
|
|
|
|
#if DEBUG_NP_TIMER
|
|
|
|
/**
|
|
* Helper function to convert a 64 bit time in microseconds into a string.
|
|
* The format will always be: hh:mm:ss.ssssss\0
|
|
*
|
|
* Note: the caller has to allocate the destination buffer (at least 17 chars)
|
|
*/
|
|
#include <stdio.h>
|
|
static char *us_time_to_str(char *dest, u64_t time)
|
|
{
|
|
if (time != NEVER) {
|
|
unsigned int hour;
|
|
unsigned int minute;
|
|
unsigned int second;
|
|
unsigned int us;
|
|
|
|
hour = (time / 3600U / 1000000U) % 24;
|
|
minute = (time / 60U / 1000000U) % 60;
|
|
second = (time / 1000000U) % 60;
|
|
us = time % 1000000;
|
|
|
|
sprintf(dest, "%02u:%02u:%02u.%06u", hour, minute, second, us);
|
|
} else {
|
|
sprintf(dest, " NEVER/UNKNOWN ");
|
|
|
|
}
|
|
return dest;
|
|
}
|
|
#endif
|
|
|
|
u64_t hw_timer_timer;
|
|
|
|
u64_t hw_timer_tick_timer;
|
|
u64_t hw_timer_awake_timer;
|
|
|
|
static u64_t tick_p; /* Period of the ticker */
|
|
static s64_t silent_ticks;
|
|
|
|
static bool real_time_mode =
|
|
#if defined(CONFIG_NATIVE_POSIX_SLOWDOWN_TO_REAL_TIME)
|
|
true;
|
|
#else
|
|
false;
|
|
#endif
|
|
|
|
static bool reset_rtc; /*"Reset" the RTC on boot*/
|
|
|
|
/*
|
|
* When this executable started running, this value shall not be changed after
|
|
* boot
|
|
*/
|
|
static u64_t boot_time;
|
|
|
|
/*
|
|
* Ratio of the simulated clock to the real host time
|
|
* For ex. a clock_ratio = 1+100e-6 means the simulated time is 100ppm faster
|
|
* than real time
|
|
*/
|
|
static double clock_ratio = 1.0;
|
|
|
|
#if DEBUG_NP_TIMER
|
|
/*
|
|
* Offset of the simulated time vs the real host time due to drift/clock ratio
|
|
* until "last_radj_*time"
|
|
*
|
|
* A positive value means simulated time is ahead of the host time
|
|
*
|
|
* This variable is only kept for debugging purposes
|
|
*/
|
|
static s64_t last_drift_offset;
|
|
#endif
|
|
|
|
/*
|
|
* Offsets of the RTC relative to the hardware models simu_time
|
|
* "simu_time" == simulated time which starts at 0 on boot
|
|
*/
|
|
static s64_t rtc_offset;
|
|
|
|
/* Last host/real time when the ratio was adjusted */
|
|
static u64_t last_radj_rtime;
|
|
/* Last simulated time when the ratio was adjusted */
|
|
static u64_t last_radj_stime;
|
|
|
|
extern u64_t posix_get_hw_cycle(void);
|
|
|
|
void hwtimer_set_real_time_mode(bool new_rt)
|
|
{
|
|
real_time_mode = new_rt;
|
|
}
|
|
|
|
static void hwtimer_update_timer(void)
|
|
{
|
|
hw_timer_timer = MIN(hw_timer_tick_timer, hw_timer_awake_timer);
|
|
}
|
|
|
|
static inline void host_clock_gettime(struct timespec *tv)
|
|
{
|
|
#if defined(CLOCK_MONOTONIC_RAW)
|
|
clock_gettime(CLOCK_MONOTONIC_RAW, tv);
|
|
#else
|
|
clock_gettime(CLOCK_MONOTONIC, tv);
|
|
#endif
|
|
}
|
|
|
|
u64_t get_host_us_time(void)
|
|
{
|
|
struct timespec tv;
|
|
|
|
host_clock_gettime(&tv);
|
|
return (u64_t)tv.tv_sec * 1e6 + tv.tv_nsec / 1000;
|
|
}
|
|
|
|
void hwtimer_init(void)
|
|
{
|
|
silent_ticks = 0;
|
|
hw_timer_tick_timer = NEVER;
|
|
hw_timer_awake_timer = NEVER;
|
|
hwtimer_update_timer();
|
|
if (real_time_mode) {
|
|
boot_time = get_host_us_time();
|
|
last_radj_rtime = boot_time;
|
|
last_radj_stime = 0U;
|
|
}
|
|
if (!reset_rtc) {
|
|
struct timespec tv;
|
|
u64_t realhosttime;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &tv);
|
|
realhosttime = (u64_t)tv.tv_sec * 1e6 + tv.tv_nsec / 1000;
|
|
|
|
rtc_offset += realhosttime;
|
|
}
|
|
}
|
|
|
|
void hwtimer_cleanup(void)
|
|
{
|
|
|
|
}
|
|
|
|
/**
|
|
* Enable the HW timer tick interrupts with a period <period> in micoseconds
|
|
*/
|
|
void hwtimer_enable(u64_t period)
|
|
{
|
|
tick_p = period;
|
|
hw_timer_tick_timer = hwm_get_time() + tick_p;
|
|
hwtimer_update_timer();
|
|
hwm_find_next_timer();
|
|
}
|
|
|
|
static void hwtimer_tick_timer_reached(void)
|
|
{
|
|
if (real_time_mode) {
|
|
u64_t expected_rt = (hw_timer_tick_timer - last_radj_stime)
|
|
/ clock_ratio
|
|
+ last_radj_rtime;
|
|
u64_t real_time = get_host_us_time();
|
|
|
|
s64_t diff = expected_rt - real_time;
|
|
|
|
#if DEBUG_NP_TIMER
|
|
char es[30];
|
|
char rs[30];
|
|
|
|
us_time_to_str(es, expected_rt - boot_time);
|
|
us_time_to_str(rs, real_time - boot_time);
|
|
printf("tick @%5llims: diff = expected_rt - real_time = "
|
|
"%5lli = %s - %s\n",
|
|
hw_timer_tick_timer/1000U, diff, es, rs);
|
|
#endif
|
|
|
|
if (diff > 0) { /* we need to slow down */
|
|
struct timespec requested_time;
|
|
struct timespec remaining;
|
|
|
|
requested_time.tv_sec = diff / 1e6;
|
|
requested_time.tv_nsec = (diff -
|
|
requested_time.tv_sec*1e6)*1e3;
|
|
|
|
nanosleep(&requested_time, &remaining);
|
|
}
|
|
}
|
|
|
|
hw_timer_tick_timer += tick_p;
|
|
hwtimer_update_timer();
|
|
|
|
if (silent_ticks > 0) {
|
|
silent_ticks -= 1;
|
|
} else {
|
|
hw_irq_ctrl_set_irq(TIMER_TICK_IRQ);
|
|
}
|
|
}
|
|
|
|
static void hwtimer_awake_timer_reached(void)
|
|
{
|
|
hw_timer_awake_timer = NEVER;
|
|
hwtimer_update_timer();
|
|
hw_irq_ctrl_set_irq(PHONY_HARD_IRQ);
|
|
}
|
|
|
|
void hwtimer_timer_reached(void)
|
|
{
|
|
u64_t Now = hw_timer_timer;
|
|
|
|
if (hw_timer_awake_timer == Now) {
|
|
hwtimer_awake_timer_reached();
|
|
}
|
|
|
|
if (hw_timer_tick_timer == Now) {
|
|
hwtimer_tick_timer_reached();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The timer HW will awake the CPU (without an interrupt) at least when <time>
|
|
* comes (it may awake it earlier)
|
|
*
|
|
* If there was a previous request for an earlier time, the old one will prevail
|
|
*
|
|
* This is meant for k_busy_wait() like functionality
|
|
*/
|
|
void hwtimer_wake_in_time(u64_t time)
|
|
{
|
|
if (hw_timer_awake_timer > time) {
|
|
hw_timer_awake_timer = time;
|
|
hwtimer_update_timer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The kernel wants to skip the next sys_ticks tick interrupts
|
|
* If sys_ticks == 0, the next interrupt will be raised.
|
|
*/
|
|
void hwtimer_set_silent_ticks(s64_t sys_ticks)
|
|
{
|
|
silent_ticks = sys_ticks;
|
|
}
|
|
|
|
s64_t hwtimer_get_pending_silent_ticks(void)
|
|
{
|
|
return silent_ticks;
|
|
}
|
|
|
|
|
|
/**
|
|
* During boot set the real time clock simulated time not start
|
|
* from the real host time
|
|
*/
|
|
void hwtimer_reset_rtc(void)
|
|
{
|
|
reset_rtc = true;
|
|
}
|
|
|
|
/**
|
|
* Set a time offset (microseconds) of the RTC simulated time
|
|
* Note: This should not be used after starting
|
|
*/
|
|
void hwtimer_set_rtc_offset(s64_t offset)
|
|
{
|
|
rtc_offset = offset;
|
|
}
|
|
|
|
/**
|
|
* Set the ratio of the simulated time to host (real) time.
|
|
* Note: This should not be used after starting
|
|
*/
|
|
void hwtimer_set_rt_ratio(double ratio)
|
|
{
|
|
clock_ratio = ratio;
|
|
}
|
|
|
|
/**
|
|
* Increase or decrease the RTC simulated time by offset_delta
|
|
*/
|
|
void hwtimer_adjust_rtc_offset(s64_t offset_delta)
|
|
{
|
|
rtc_offset += offset_delta;
|
|
}
|
|
|
|
/**
|
|
* Adjust the ratio of the simulated time by a factor
|
|
*/
|
|
void hwtimer_adjust_rt_ratio(double ratio_correction)
|
|
{
|
|
u64_t current_stime = hwm_get_time();
|
|
s64_t s_diff = current_stime - last_radj_stime;
|
|
/* Accumulated real time drift time since last adjustment: */
|
|
|
|
last_radj_rtime += s_diff / clock_ratio;
|
|
last_radj_stime = current_stime;
|
|
|
|
#if DEBUG_NP_TIMER
|
|
char ct[30];
|
|
s64_t r_drift = (long double)(clock_ratio-1.0)/(clock_ratio)*s_diff;
|
|
|
|
last_drift_offset += r_drift;
|
|
us_time_to_str(ct, current_stime);
|
|
|
|
printf("%s(): @%s, s_diff= %llius after last adjust\n"
|
|
" during which we drifted %.3fms\n"
|
|
" total acc drift (last_drift_offset) = %.3fms\n"
|
|
" last_radj_rtime = %.3fms (+%.3fms )\n"
|
|
" Ratio adjusted to %f\n",
|
|
__func__, ct, s_diff,
|
|
r_drift/1000.0,
|
|
last_drift_offset/1000.0,
|
|
last_radj_rtime/1000.0,
|
|
s_diff/clock_ratio/1000.0,
|
|
clock_ratio*ratio_correction);
|
|
#endif
|
|
|
|
clock_ratio *= ratio_correction;
|
|
}
|
|
|
|
/**
|
|
* Return the current simulated RTC time in microseconds
|
|
*/
|
|
s64_t hwtimer_get_simu_rtc_time(void)
|
|
{
|
|
return hwm_get_time() + rtc_offset;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a version of the host time which would have drifted as if the host
|
|
* real time clock had been running from the native_posix clock, and adjusted
|
|
* both in rate and in offsets as the native_posix has been.
|
|
*
|
|
* Note that this time may be significantly ahead of the simulated time
|
|
* (the time the Zephyr kernel thinks it is).
|
|
* This will be the case in general if native_posix is not able to run at or
|
|
* faster than real time.
|
|
*/
|
|
void hwtimer_get_pseudohost_rtc_time(u32_t *nsec, u64_t *sec)
|
|
{
|
|
/*
|
|
* Note: long double has a 64bits mantissa in x86.
|
|
* Therefore to avoid loss of precision after 500 odd years into
|
|
* the epoc, we first calculate the offset from the last adjustment
|
|
* time split in us and ns. So we keep the full precision for 500 odd
|
|
* years after the last clock ratio adjustment (or native_posix boot,
|
|
* whichever is latest).
|
|
* Meaning, we will still start to loose precision after 500 off
|
|
* years of runtime without a clock ratio adjustment, but that really
|
|
* should not be much of a problem, given that the ns lower digits are
|
|
* pretty much noise anyhow.
|
|
* (So, all this is a huge overkill)
|
|
*
|
|
* The operation below in plain is just:
|
|
* st = (rt - last_rt_adj_time)*ratio + last_dt_adj_time
|
|
* where st = simulated time
|
|
* rt = real time
|
|
* last_rt_adj_time = time (real) when the last ratio
|
|
* adjustment took place
|
|
* last_st_adj_time = time (simulated) when the last ratio
|
|
* adjustment took place
|
|
* ratio = ratio between simulated time and real time
|
|
*/
|
|
struct timespec tv;
|
|
|
|
host_clock_gettime(&tv);
|
|
|
|
u64_t rt_us = (u64_t)tv.tv_sec * 1000000ULL + tv.tv_nsec / 1000;
|
|
u32_t rt_ns = tv.tv_nsec % 1000;
|
|
|
|
long double drt_us = (long double)rt_us - last_radj_rtime;
|
|
long double drt_ns = drt_us * 1000.0 + (long double)rt_ns;
|
|
long double st = drt_ns * clock_ratio +
|
|
(long double)(last_radj_stime + rtc_offset) * 1000.0;
|
|
|
|
*nsec = fmodl(st, 1e9);
|
|
*sec = st / 1e9;
|
|
}
|
|
|
|
static struct {
|
|
double stop_at;
|
|
double rtc_offset;
|
|
double rt_drift;
|
|
double rt_ratio;
|
|
} args;
|
|
|
|
static void cmd_stop_at_found(char *argv, int offset)
|
|
{
|
|
ARG_UNUSED(offset);
|
|
if (args.stop_at < 0) {
|
|
posix_print_error_and_exit("Error: stop-at must be positive "
|
|
"(%s)\n", argv);
|
|
}
|
|
hwm_set_end_of_time(args.stop_at*1e6);
|
|
}
|
|
|
|
static void cmd_realtime_found(char *argv, int offset)
|
|
{
|
|
ARG_UNUSED(argv);
|
|
ARG_UNUSED(offset);
|
|
hwtimer_set_real_time_mode(true);
|
|
}
|
|
|
|
static void cmd_no_realtime_found(char *argv, int offset)
|
|
{
|
|
ARG_UNUSED(argv);
|
|
ARG_UNUSED(offset);
|
|
hwtimer_set_real_time_mode(false);
|
|
}
|
|
|
|
static void cmd_rtcoffset_found(char *argv, int offset)
|
|
{
|
|
ARG_UNUSED(argv);
|
|
ARG_UNUSED(offset);
|
|
hwtimer_set_rtc_offset(args.rtc_offset*1e6);
|
|
}
|
|
|
|
static void cmd_rt_drift_found(char *argv, int offset)
|
|
{
|
|
ARG_UNUSED(argv);
|
|
ARG_UNUSED(offset);
|
|
if (!(args.rt_drift > -1)) {
|
|
posix_print_error_and_exit("The drift needs to be > -1. "
|
|
"Please use --help for more info\n");
|
|
}
|
|
args.rt_ratio = args.rt_drift + 1;
|
|
hwtimer_set_rt_ratio(args.rt_ratio);
|
|
}
|
|
|
|
static void cmd_rt_ratio_found(char *argv, int offset)
|
|
{
|
|
ARG_UNUSED(argv);
|
|
ARG_UNUSED(offset);
|
|
if ((args.rt_ratio <= 0)) {
|
|
posix_print_error_and_exit("The ratio needs to be > 0. "
|
|
"Please use --help for more info\n");
|
|
}
|
|
hwtimer_set_rt_ratio(args.rt_ratio);
|
|
}
|
|
|
|
static void cmd_rtcreset_found(char *argv, int offset)
|
|
{
|
|
ARG_UNUSED(argv);
|
|
ARG_UNUSED(offset);
|
|
hwtimer_reset_rtc();
|
|
}
|
|
|
|
static void native_add_time_options(void)
|
|
{
|
|
static struct args_struct_t timer_options[] = {
|
|
/*
|
|
* Fields:
|
|
* manual, mandatory, switch,
|
|
* option_name, var_name ,type,
|
|
* destination, callback,
|
|
* description
|
|
*/
|
|
{false, false, true,
|
|
"rt", "", 'b',
|
|
NULL, cmd_realtime_found,
|
|
"Slow down the execution to the host real time, "
|
|
"or a ratio of it (see --rt-ratio below)"},
|
|
|
|
{false, false, true,
|
|
"no-rt", "", 'b',
|
|
NULL, cmd_no_realtime_found,
|
|
"Do NOT slow down the execution to real time, but advance "
|
|
"Zephyr's time as fast as possible and decoupled from the host "
|
|
"time"},
|
|
|
|
{false, false, false,
|
|
"rt-drift", "dratio", 'd',
|
|
(void *)&args.rt_drift, cmd_rt_drift_found,
|
|
"Drift of the simulated clock relative to the host real time. "
|
|
"Normally this would be set to a value of a few ppm (e.g. 50e-6"
|
|
") "
|
|
"This option has no effect in non real time mode"
|
|
},
|
|
|
|
{false, false, false,
|
|
"rt-ratio", "ratio", 'd',
|
|
(void *)&args.rt_ratio, cmd_rt_ratio_found,
|
|
"Relative speed of the simulated time vs real time. "
|
|
"For ex. set to 2 to have simulated time pass at double the "
|
|
"speed of real time. "
|
|
"Note that both rt-drift & rt-ratio adjust the same clock "
|
|
"speed, and therefore it does not make sense to use them "
|
|
"simultaneously. "
|
|
"This option has no effect in non real time mode"
|
|
},
|
|
|
|
{false, false, false,
|
|
"rtc-offset", "time_offset", 'd',
|
|
(void *)&args.rtc_offset, cmd_rtcoffset_found,
|
|
"At boot offset the RTC clock by this amount of seconds"
|
|
},
|
|
|
|
{false, false, true,
|
|
"rtc-reset", "", 'b',
|
|
NULL, cmd_rtcreset_found,
|
|
"Start the simulated real time clock at 0. Otherwise it starts "
|
|
"matching the value provided by the host real time clock"},
|
|
|
|
{false, false, false,
|
|
"stop_at", "time", 'd',
|
|
(void *)&args.stop_at, cmd_stop_at_found,
|
|
"In simulated seconds, when to stop automatically"},
|
|
|
|
ARG_TABLE_ENDMARKER};
|
|
|
|
native_add_command_line_opts(timer_options);
|
|
}
|
|
|
|
NATIVE_TASK(native_add_time_options, PRE_BOOT_1, 1);
|