diff --git a/drivers/timers/Kconfig b/drivers/timers/Kconfig index fffbe51fa4..cda936a23e 100644 --- a/drivers/timers/Kconfig +++ b/drivers/timers/Kconfig @@ -183,6 +183,16 @@ config RTC_FREQUENCY must be provided. If RTC_HIRES is not defined, RTC_FREQUENCY is assumed to be one Hz. +config RTC_ADJTIME + bool "RTC Adjustment Support" + default N + depends on RTC_HIRES + ---help--- + Enable support for adjtime() to adjust RTC run rate. The following + interface must be provided by RTC driver: + + int up_rtc_adjtime(long ppb); + endif # !RTC_DATETIME config RTC_ALARM diff --git a/include/nuttx/arch.h b/include/nuttx/arch.h index 227ef8f720..008a56353d 100644 --- a/include/nuttx/arch.h +++ b/include/nuttx/arch.h @@ -1644,33 +1644,23 @@ void up_secure_irq_all(bool secure); #endif /**************************************************************************** - * Function: up_adj_timer_period + * Function: up_adjtime * * Description: * Adjusts timer period. This call is used when adjusting timer period as * defined in adjtime() function. * * Input Parameters: - * period_inc_usec - period adjustment in usec (reset to default value - * if 0) + * ppb - Adjustment in parts per billion (nanoseconds per second). + * Zero is default rate, positive value makes clock run faster + * and negative value slower. * + * Assumptions: + * Called from within critical section or interrupt context. ****************************************************************************/ -#ifdef CONFIG_CLOCK_ADJTIME -void up_adj_timer_period(long long period_inc_usec); - -/**************************************************************************** - * Function: up_get_timer_period - * - * Description: - * This function returns the timer period in usec. - * - * Input Parameters: - * period_usec - returned timer period in usec - * - ****************************************************************************/ - -void up_get_timer_period(long long *period_usec); +#ifdef CONFIG_ARCH_HAVE_ADJTIME +void up_adjtime(long ppb); #endif /**************************************************************************** @@ -2606,6 +2596,29 @@ int up_rtc_getdatetime_with_subseconds(FAR struct tm *tp, FAR long *nsec); int up_rtc_settime(FAR const struct timespec *tp); #endif +/**************************************************************************** + * Name: up_rtc_adjtime + * + * Description: + * Adjust RTC frequency (running rate). Used by adjtime() when RTC is used + * as system time source. + * + * Input Parameters: + * ppb - Adjustment in parts per billion (nanoseconds per second). + * Zero is default rate, positive value makes clock run faster + * and negative value slower. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + * Assumptions: + * Called from within a critical section. + ****************************************************************************/ + +#if defined(CONFIG_RTC_HIRES) && defined(CONFIG_RTC_ADJTIME) +int up_rtc_adjtime(long ppb); +#endif + /**************************************************************************** * Name: arch_phy_irq * diff --git a/sched/Kconfig b/sched/Kconfig index afba5ee82f..1746649a41 100644 --- a/sched/Kconfig +++ b/sched/Kconfig @@ -198,23 +198,17 @@ config ARCH_HAVE_ADJTIME config CLOCK_ADJTIME bool "Support adjtime function" default n - depends on ARCH_HAVE_ADJTIME - depends on !SCHED_TICKLESS - depends on !CLOCK_TIMEKEEPING + depends on ARCH_HAVE_ADJTIME || RTC_ADJTIME ---help--- Enables usage of adjtime() interface used to correct the system time - clock. This requires specific architecture support capable of adjusting - system timer period. + clock. This requires specific architecture support. - Interfaces up_get_timer_period() and up_adj_timer_period() has to be - defined by architecture level support. + Adjustment can affect system timer period and/or high-resolution RTC. + These are implemented by interfaces up_adjtime() and up_rtc_adjtime(). This is not a POSIX interface but derives from 4.3BSD, System V. It is also supported for Linux compatibility. - Currently it is not possible to use adjtime() if SCHED_TICKLESS or - CLOCK_TIMEKEEPING is defined. - if CLOCK_ADJTIME config CLOCK_ADJTIME_SLEWLIMIT_PPM diff --git a/sched/clock/clock.h b/sched/clock/clock.h index 018bc4fc87..c42147807d 100644 --- a/sched/clock/clock.h +++ b/sched/clock/clock.h @@ -66,11 +66,6 @@ extern volatile clock_t g_system_ticks; extern struct timespec g_basetime; #endif -#ifdef CONFIG_CLOCK_ADJTIME -extern long long g_clk_adj_usec; -extern long long g_clk_adj_count; -#endif - /**************************************************************************** * Public Function Prototypes ****************************************************************************/ @@ -84,12 +79,6 @@ void clock_timer(void); # define clock_timer() #endif -#ifdef CONFIG_CLOCK_ADJTIME -void clock_set_adjust(long long adj_usec, long long adj_count, - FAR long long *adj_usec_old, - FAR long long *adj_count_old); -#endif - int clock_abstime2ticks(clockid_t clockid, FAR const struct timespec *abstime, FAR sclock_t *ticks); diff --git a/sched/clock/clock_adjtime.c b/sched/clock/clock_adjtime.c index db538ef0f8..ea02194918 100644 --- a/sched/clock/clock_adjtime.c +++ b/sched/clock/clock_adjtime.c @@ -41,24 +41,102 @@ * Pre-processor Definitions ****************************************************************************/ -/* Current adjtime implementation uses periodic clock tick to adjust clock - * period. Therefore this implementation will not work when tickless mode - * is enabled by CONFIG_SCHED_TICKLESS=y - */ - -#ifdef CONFIG_SCHED_TICKLESS -# error CONFIG_CLOCK_ADJTIME is not supported when CONFIG_SCHED_TICKLESS \ - is enabled! -#endif - /**************************************************************************** * Private Data ****************************************************************************/ +static struct wdog_s g_adjtime_wdog; +static long g_adjtime_ppb; + /**************************************************************************** * Private Functions ****************************************************************************/ +/* Restore default rate after adjustment period expires */ + +static void adjtime_wdog_callback(wdparm_t arg) +{ + UNUSED(arg); + +#ifdef CONFIG_ARCH_HAVE_ADJTIME + up_adjtime(0); +#endif + +#ifdef CONFIG_RTC_ADJTIME + up_rtc_adjtime(0); +#endif + + g_adjtime_ppb = 0; +} + +/* Query remaining adjustment in microseconds */ + +static long long adjtime_remaining_usec(void) +{ + return (long long)g_adjtime_ppb + * TICK2MSEC(wd_gettime(&g_adjtime_wdog)) + / (MSEC_PER_SEC * NSEC_PER_USEC); +} + +/* Start new adjustment period */ + +static int adjtime_start(long long adjust_usec) +{ + long long ppb; + long long ppb_limit; + irqstate_t flags; + int ret = OK; + + /* Calculate rate adjustmend to get adjust_usec change over the + * CONFIG_CLOCK_ADJTIME_PERIOD_MS. + */ + + ppb = adjust_usec * NSEC_PER_USEC; + ppb = ppb * MSEC_PER_SEC / CONFIG_CLOCK_ADJTIME_PERIOD_MS; + + /* Limit to maximum rate adjustment */ + + ppb_limit = CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM * 1000; + if (ppb > ppb_limit) + { + ppb = ppb_limit; + } + else if (ppb < -ppb_limit) + { + ppb = -ppb_limit; + } + + flags = enter_critical_section(); + + /* Set new adjustment */ + + g_adjtime_ppb = ppb; + +#ifdef CONFIG_ARCH_HAVE_ADJTIME + up_adjtime(g_adjtime_ppb); +#endif + +#ifdef CONFIG_RTC_ADJTIME + ret = up_rtc_adjtime(g_adjtime_ppb); +#endif + + /* Queue cancellation of adjustment after configured period */ + + if (g_adjtime_ppb != 0) + { + wd_start(&g_adjtime_wdog, MSEC2TICK(CONFIG_CLOCK_ADJTIME_PERIOD_MS), + adjtime_wdog_callback, 0); + } + else + { + wd_cancel(&g_adjtime_wdog); + } + + leave_critical_section(flags); + + return ret; +} + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -100,109 +178,32 @@ int adjtime(FAR const struct timeval *delta, FAR struct timeval *olddelta) { - irqstate_t flags; - long long adjust_usec; - long long period_usec; - long long adjust_usec_old; - long long count; /* Number of cycles over which - * we adjust the period - */ - long long incr; /* Period increment applied on - * every cycle. - */ - long long count_old; /* Previous number of cycles over which - * we adjust the period - */ - long long incr_old; /* Previous period increment applied on - * every cycle. - */ - long long incr_limit; - int is_negative; + long long adjust_usec = 0; + long long adjust_usec_old = 0; + int ret = OK; - if (!delta) - { - set_errno(EINVAL); - return -1; - } - - flags = enter_critical_section(); - - adjust_usec = (long long)delta->tv_sec * USEC_PER_SEC + delta->tv_usec; - - if (adjust_usec < 0) - { - adjust_usec = -adjust_usec; - is_negative = 1; - } - else - { - is_negative = 0; - } - - /* Get period in usec. Target hardware has to provide support for - * this function call. - */ - - up_get_timer_period(&period_usec); - - /* Determine how much we want to adjust timer period and the number - * of cycles over which we want to do the adjustment. - */ - - count = (USEC_PER_MSEC * CONFIG_CLOCK_ADJTIME_PERIOD_MS) / period_usec; - incr = adjust_usec / count; - - /* Compute maximum possible period increase and check - * whether previously computed increase exceeds the maximum - * one. - */ - - incr_limit = CONFIG_CLOCK_ADJTIME_SLEWLIMIT_PPM - / (USEC_PER_SEC / period_usec); - if (incr > incr_limit) - { - /* It does... limit computed increase and increment count. */ - - incr = incr_limit; - count = adjust_usec / incr; - } - - /* If requested adjustment is smaller than 1 microsecond per tick, - * adjust the count instead. - */ - - if (adjust_usec == 0) - { - incr = 0; - count = 0; - } - else if (incr == 0) - { - incr = 1; - count = adjust_usec / incr; - } - - if (is_negative == 1) - { - /* Positive or negative? */ - - incr = -incr; - } - - leave_critical_section(flags); - - /* Initialize clock adjustment and get old adjust values. */ - - clock_set_adjust(incr, count, &incr_old, &count_old); - - adjust_usec_old = count_old * incr_old; if (olddelta) { + adjust_usec_old = adjtime_remaining_usec(); olddelta->tv_sec = adjust_usec_old / USEC_PER_SEC; olddelta->tv_usec = adjust_usec_old; } - return OK; + if (delta) + { + adjust_usec = (long long)delta->tv_sec * USEC_PER_SEC + delta->tv_usec; + ret = adjtime_start(adjust_usec); + } + + if (ret < 0) + { + set_errno(-ret); + return -1; + } + else + { + return OK; + } } #endif /* CONFIG_CLOCK_ADJTIME */ diff --git a/sched/clock/clock_initialize.c b/sched/clock/clock_initialize.c index 7e73106add..c6933d987c 100644 --- a/sched/clock/clock_initialize.c +++ b/sched/clock/clock_initialize.c @@ -59,11 +59,6 @@ volatile uint32_t g_system_ticks = INITIAL_SYSTEM_TIMER_TICKS; struct timespec g_basetime; #endif -#ifdef CONFIG_CLOCK_ADJTIME -long long g_clk_adj_usec; -long long g_clk_adj_count; -#endif - /**************************************************************************** * Private Functions ****************************************************************************/ @@ -230,11 +225,6 @@ void clock_initialize(void) clock_inittime(NULL); #endif -#if defined(CONFIG_CLOCK_ADJTIME) - g_clk_adj_count = 0; - g_clk_adj_usec = 0; -#endif - #endif perf_init(); @@ -420,41 +410,6 @@ skip: } #endif -/**************************************************************************** - * Name: clock_set_adjust - * - * Description: - * This function is called from adjtime() defined in clock_adjtime.c if - * CONFIG_CLOCK_ADJTIME is enabled. It sets global variables g_clk_adj_usec - * and g_clk_adj_count which are used for clock adjustment. - * - * Input Parameters: - * adj_usec - period adjustment in usec - * adj_count - number of clock ticks over which we apply adj_usec - * - ****************************************************************************/ - -#ifdef CONFIG_CLOCK_ADJTIME -void clock_set_adjust(long long adj_usec, long long adj_count, - FAR long long *adj_usec_old, - FAR long long *adj_count_old) -{ - /* Get old adjust values. */ - - *adj_usec_old = g_clk_adj_usec; - *adj_count_old = g_clk_adj_count; - - /* Set new adjust values. */ - - g_clk_adj_usec = adj_usec; - g_clk_adj_count = adj_count; - - /* And change timer period. */ - - up_adj_timer_period(g_clk_adj_usec); -} -#endif - /**************************************************************************** * Name: clock_timer * @@ -471,25 +426,5 @@ void clock_timer(void) /* Increment the per-tick system counter */ g_system_ticks++; - -#ifdef CONFIG_CLOCK_ADJTIME - /* Do we apply timer adjustment? */ - - if (g_clk_adj_count > 0) - { - /* Yes, decrement the count each tick. */ - - g_clk_adj_count--; - - /* Check if clock adjusment is finished. */ - - if (g_clk_adj_count == 0) - { - /* Yes... reset timer period. */ - - up_adj_timer_period(0); - } - } -#endif } #endif diff --git a/sched/clock/clock_settime.c b/sched/clock/clock_settime.c index 8fe1fa8026..e8614fb2fb 100644 --- a/sched/clock/clock_settime.c +++ b/sched/clock/clock_settime.c @@ -31,6 +31,7 @@ #include #include +#include #include "clock/clock.h" #ifdef CONFIG_CLOCK_TIMEKEEPING @@ -55,6 +56,14 @@ int clock_settime(clockid_t clock_id, FAR const struct timespec *tp) struct timespec bias; irqstate_t flags; #endif + +#ifdef CONFIG_CLOCK_ADJTIME + const struct timeval zerodelta = { + 0, 0 + }; + +#endif + int ret = OK; sinfo("clock_id=%d\n", clock_id); @@ -112,8 +121,9 @@ int clock_settime(clockid_t clock_id, FAR const struct timespec *tp) #endif #ifdef CONFIG_CLOCK_ADJTIME - g_clk_adj_count = 0; - g_clk_adj_usec = 0; + /* Cancel any ongoing adjustment */ + + adjtime(&zerodelta, NULL); #endif leave_critical_section(flags);