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:
Petteri Aimonen 2023-11-14 13:52:53 +02:00 committed by Xiang Xiao
parent 4033018a72
commit 1825f9534c
7 changed files with 164 additions and 212 deletions

View File

@ -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

View File

@ -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
*

View File

@ -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

View File

@ -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);

View File

@ -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 */

View File

@ -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

View File

@ -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);