adjtime: Rewrite implementation to work for RTC and tickless kernel
Previous adjtime() implementation was limited to adjusting system timer tick period. This commit reimplements the internals to use a kernel watchdog timer. Platform-independent part of the code now works also for adjusting hires RTC and tickless timer rate. User code facing API is unchanged. Architecture code API has changed: up_adj_timer_period() is replaced by up_adjtime(). Other improvements: - Support query of remaining adjustment by passing NULL to first argument of adjtime(). This matches Linux behavior. - Improve resolution available for architecture driver, previously limited to 1 microsecond per tick. Now 1 nanosecond per second.
This commit is contained in:
parent
4033018a72
commit
1825f9534c
@ -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
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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
|
||||
|
@ -31,6 +31,7 @@
|
||||
|
||||
#include <nuttx/arch.h>
|
||||
#include <nuttx/irq.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#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);
|
||||
|
Loading…
Reference in New Issue
Block a user