/* * 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 #include #include #include #include "hw_models_top.h" #include "irq_ctrl.h" #include "board_soc.h" #include "zephyr/types.h" #include #include #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 static char *us_time_to_str(char *dest, uint64_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 uint64_t hw_timer_timer; uint64_t hw_timer_tick_timer; uint64_t hw_timer_awake_timer; static uint64_t tick_p; /* Period of the ticker */ static int64_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 uint64_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 int64_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 int64_t rtc_offset; /* Last host/real time when the ratio was adjusted */ static uint64_t last_radj_rtime; /* Last simulated time when the ratio was adjusted */ static uint64_t last_radj_stime; extern uint64_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 } uint64_t get_host_us_time(void) { struct timespec tv; host_clock_gettime(&tv); return (uint64_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; uint64_t realhosttime; clock_gettime(CLOCK_REALTIME, &tv); realhosttime = (uint64_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 in micoseconds */ void hwtimer_enable(uint64_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) { uint64_t expected_rt = (hw_timer_tick_timer - last_radj_stime) / clock_ratio + last_radj_rtime; uint64_t real_time = get_host_us_time(); int64_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; (void) 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) { uint64_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