mirror of
https://gitlab.com/chrony/chrony.git
synced 2025-12-03 16:45:07 -05:00
By default, the clock precision is set to the minimum measured time needed to read the clock. This value is typically larger than the actual resolution, which causes the NTP server to add more noise to NTP timestamps than necessary. With HW timestamping and PTP corrections enabled by the NTP-over-PTP transport that can be the limiting factor in the stability of NTP measurements. Try to determine the actual resolution of the clock. On non-Linux systems use the clock_getres() function. On FreeBSD and NetBSD it seems to provide expected values. On illumos it returns a large value (kernel tick length?). On Linux it seems to be the internal timer resolution, which is 1 ns with hrtimers, even when using a lower-resolution clocksource like hpet or acpi_pm. On Linux, try to measure the resolution as the minimum observed change in differences between consecutive readings of the CLOCK_MONOTONIC_RAW clock with a varying amount of busy work. Ignore 1ns changes due to the kernel converting readings to timespec. This seems to work reliably. In a test with the acpi_pm clocksource, differences of 3073, 3352, and 3631 ns were measured, which gives a resolution of 279 ns, matching the clocksource frequency of ~3.58 MHz. With a tsc clocksource it gives the minimum accepted resolution of 2 ns and with kvm-clock 10 ns. As the final value of the precision, use the minimum value from the measured or clock_getres() resolution and the original minimum time needed to read the clock.
932 lines
24 KiB
C
932 lines
24 KiB
C
/*
|
|
chronyd/chronyc - Programs for keeping computer clocks accurate.
|
|
|
|
**********************************************************************
|
|
* Copyright (C) Richard P. Curnow 1997-2003
|
|
* Copyright (C) Miroslav Lichvar 2011, 2014-2015
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of version 2 of the GNU General Public License as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
**********************************************************************
|
|
|
|
=======================================================================
|
|
|
|
The routines in this file present a common local (system) clock
|
|
interface to the rest of the software.
|
|
|
|
They interface with the system specific driver files in sys_*.c
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "sysincl.h"
|
|
|
|
#include "conf.h"
|
|
#include "local.h"
|
|
#include "localp.h"
|
|
#include "memory.h"
|
|
#include "smooth.h"
|
|
#include "util.h"
|
|
#include "logging.h"
|
|
|
|
/* ================================================== */
|
|
|
|
/* Variable to store the current frequency, in ppm */
|
|
static double current_freq_ppm;
|
|
|
|
/* Maximum allowed frequency, in ppm */
|
|
static double max_freq_ppm;
|
|
|
|
/* Temperature compensation, in ppm */
|
|
static double temp_comp_ppm;
|
|
|
|
/* ================================================== */
|
|
/* Store the system dependent drivers */
|
|
|
|
static lcl_ReadFrequencyDriver drv_read_freq;
|
|
static lcl_SetFrequencyDriver drv_set_freq;
|
|
static lcl_AccrueOffsetDriver drv_accrue_offset;
|
|
static lcl_ApplyStepOffsetDriver drv_apply_step_offset;
|
|
static lcl_OffsetCorrectionDriver drv_offset_convert;
|
|
static lcl_SetLeapDriver drv_set_leap;
|
|
static lcl_SetSyncStatusDriver drv_set_sync_status;
|
|
|
|
/* ================================================== */
|
|
|
|
/* Types and variables associated with handling the parameter change
|
|
list */
|
|
|
|
typedef struct _ChangeListEntry {
|
|
struct _ChangeListEntry *next;
|
|
struct _ChangeListEntry *prev;
|
|
LCL_ParameterChangeHandler handler;
|
|
void *anything;
|
|
} ChangeListEntry;
|
|
|
|
static ChangeListEntry change_list;
|
|
|
|
/* ================================================== */
|
|
|
|
/* Types and variables associated with handling the parameter change
|
|
list */
|
|
|
|
typedef struct _DispersionNotifyListEntry {
|
|
struct _DispersionNotifyListEntry *next;
|
|
struct _DispersionNotifyListEntry *prev;
|
|
LCL_DispersionNotifyHandler handler;
|
|
void *anything;
|
|
} DispersionNotifyListEntry;
|
|
|
|
static DispersionNotifyListEntry dispersion_notify_list;
|
|
|
|
/* ================================================== */
|
|
|
|
static int precision_log;
|
|
static double precision_quantum;
|
|
|
|
static double max_clock_error;
|
|
|
|
#define NSEC_PER_SEC 1000000000
|
|
|
|
/* ================================================== */
|
|
|
|
/* Ask the system for the resolution of the system clock. The Linux
|
|
clock_getres() is not usable, because it reports the internal timer
|
|
resolution, which is 1 ns when high-resolution timers are enabled,
|
|
even when using a lower-resolution clocksource. */
|
|
|
|
static int
|
|
get_clock_resolution(void)
|
|
{
|
|
#if defined(HAVE_CLOCK_GETTIME) && !defined(LINUX)
|
|
struct timespec res;
|
|
|
|
if (clock_getres(CLOCK_REALTIME, &res) < 0)
|
|
return 0;
|
|
|
|
return NSEC_PER_SEC * res.tv_sec + res.tv_nsec;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
#if defined(LINUX) && defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC_RAW)
|
|
|
|
static int
|
|
compare_ints(const void *a, const void *b)
|
|
{
|
|
return *(const int *)a - *(const int *)b;
|
|
}
|
|
|
|
#define READINGS 64
|
|
|
|
/* On Linux, try to measure the actual resolution of the system
|
|
clock by performing a varying amount of busy work between clock
|
|
readings and finding the minimum change in the measured interval.
|
|
Require a change of at least two nanoseconds to ignore errors
|
|
caused by conversion to timespec. Use the raw monotonic clock
|
|
to avoid the impact of potential frequency changes due to NTP
|
|
adjustments made by other processes, and the kernel dithering of
|
|
the 32-bit multiplier. */
|
|
|
|
static int
|
|
measure_clock_resolution(void)
|
|
{
|
|
int i, j, b, busy, diffs[READINGS - 1], diff2, min;
|
|
struct timespec start_ts, ts[READINGS];
|
|
uint32_t acc;
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC_RAW, &start_ts) < 0)
|
|
return 0;
|
|
|
|
for (acc = 0, busy = 1; busy < 100000; busy = busy * 3 / 2 + 1) {
|
|
for (i = 0, b = busy * READINGS; i < READINGS; i++, b -= busy) {
|
|
if (clock_gettime(CLOCK_MONOTONIC_RAW, &ts[i]) < 0)
|
|
return 0;
|
|
|
|
for (j = b; j > 0; j--)
|
|
acc += (acc & 1) + (uint32_t)ts[i].tv_nsec;
|
|
}
|
|
|
|
/* Give up after 0.1 seconds */
|
|
if (UTI_DiffTimespecsToDouble(&ts[READINGS - 1], &start_ts) > 0.1) {
|
|
DEBUG_LOG("Measurement too slow");
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < READINGS - 1; i++) {
|
|
diffs[i] = NSEC_PER_SEC * (ts[i + 1].tv_sec - ts[i].tv_sec) +
|
|
(ts[i + 1].tv_nsec - ts[i].tv_nsec);
|
|
|
|
/* Make sure the differences are sane. A resolution larger than the
|
|
reading time will be measured in measure_clock_read_delay(). */
|
|
if (diffs[i] <= 0 || diffs[i] > NSEC_PER_SEC)
|
|
return 0;
|
|
}
|
|
|
|
/* Sort the differences and keep values unique within 1 ns from the
|
|
first half of the array, which are less likely to be impacted by CPU
|
|
interruptions */
|
|
qsort(diffs, READINGS - 1, sizeof (diffs[0]), compare_ints);
|
|
for (i = 1, j = 0; i < READINGS / 2; i++) {
|
|
if (diffs[j] + 1 < diffs[i])
|
|
diffs[++j] = diffs[i];
|
|
}
|
|
j++;
|
|
|
|
#if 0
|
|
for (i = 0; i < j; i++)
|
|
DEBUG_LOG("busy %d diff %d %d", busy, i, diffs[i]);
|
|
#endif
|
|
|
|
/* Require at least three unique differences to be more confident
|
|
with the result */
|
|
if (j < 3)
|
|
continue;
|
|
|
|
/* Find the smallest difference between the unique differences */
|
|
for (i = 1, min = 0; i < j; i++) {
|
|
diff2 = diffs[i] - diffs[i - 1];
|
|
if (min == 0 || min > diff2)
|
|
min = diff2;
|
|
}
|
|
|
|
if (min == 0)
|
|
continue;
|
|
|
|
/* Prevent the compiler from optimising the busy work out */
|
|
if (acc == 0)
|
|
min += 1;
|
|
|
|
return min;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
static int
|
|
measure_clock_resolution(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* ================================================== */
|
|
|
|
/* As a fallback, measure how long it takes to read the clock. It
|
|
typically takes longer than the resolution of the clock (and it
|
|
depends on the CPU speed), i.e. every reading gives a different
|
|
value, but handle also low-resolution clocks that might give
|
|
the same reading multiple times. */
|
|
|
|
/* Define the number of increments of the system clock that we want
|
|
to see to be fairly sure that we've got something approaching
|
|
the minimum increment. Even on a crummy implementation that can't
|
|
interpolate between 10ms ticks, we should get this done in
|
|
under 1s of busy waiting. */
|
|
#define NITERS 100
|
|
|
|
static int
|
|
measure_clock_read_delay(void)
|
|
{
|
|
struct timespec ts, old_ts;
|
|
int iters, diff, best;
|
|
|
|
LCL_ReadRawTime(&old_ts);
|
|
|
|
/* Assume we must be better than a second */
|
|
best = NSEC_PER_SEC;
|
|
iters = 0;
|
|
|
|
do {
|
|
LCL_ReadRawTime(&ts);
|
|
|
|
diff = NSEC_PER_SEC * (ts.tv_sec - old_ts.tv_sec) + (ts.tv_nsec - old_ts.tv_nsec);
|
|
|
|
old_ts = ts;
|
|
if (diff > 0) {
|
|
if (diff < best)
|
|
best = diff;
|
|
iters++;
|
|
}
|
|
} while (iters < NITERS);
|
|
|
|
assert(best > 0);
|
|
|
|
return best;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static double
|
|
measure_clock_precision(void)
|
|
{
|
|
int res, delay, prec;
|
|
|
|
res = get_clock_resolution();
|
|
if (res <= 0)
|
|
res = measure_clock_resolution();
|
|
|
|
delay = measure_clock_read_delay();
|
|
|
|
if (res > 0)
|
|
prec = MIN(res, delay);
|
|
else
|
|
prec = delay;
|
|
|
|
return prec / 1.0e9;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_Initialise(void)
|
|
{
|
|
change_list.next = change_list.prev = &change_list;
|
|
|
|
dispersion_notify_list.next = dispersion_notify_list.prev = &dispersion_notify_list;
|
|
|
|
/* Null out the system drivers, so that we die
|
|
if they never get defined before use */
|
|
|
|
drv_read_freq = NULL;
|
|
drv_set_freq = NULL;
|
|
drv_accrue_offset = NULL;
|
|
drv_offset_convert = NULL;
|
|
|
|
/* This ought to be set from the system driver layer */
|
|
current_freq_ppm = 0.0;
|
|
temp_comp_ppm = 0.0;
|
|
|
|
precision_quantum = CNF_GetClockPrecision();
|
|
if (precision_quantum <= 0.0)
|
|
precision_quantum = measure_clock_precision();
|
|
|
|
precision_quantum = CLAMP(1.0e-9, precision_quantum, 1.0);
|
|
precision_log = round(log(precision_quantum) / log(2.0));
|
|
/* NTP code doesn't support smaller log than -30 */
|
|
assert(precision_log >= -30);
|
|
|
|
DEBUG_LOG("Clock precision %.9f (%d)", precision_quantum, precision_log);
|
|
|
|
/* This is the maximum allowed frequency offset in ppm, the time must
|
|
never stop or run backwards */
|
|
max_freq_ppm = CNF_GetMaxDrift();
|
|
max_freq_ppm = CLAMP(0.0, max_freq_ppm, 500000.0);
|
|
|
|
max_clock_error = CNF_GetMaxClockError() * 1e-6;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_Finalise(void)
|
|
{
|
|
/* Make sure all handlers have been removed */
|
|
BRIEF_ASSERT(change_list.next == &change_list);
|
|
BRIEF_ASSERT(dispersion_notify_list.next == &dispersion_notify_list);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
/* Routine to read the system precision as a log to base 2 value. */
|
|
int
|
|
LCL_GetSysPrecisionAsLog(void)
|
|
{
|
|
return precision_log;
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* Routine to read the system precision in terms of the actual time step */
|
|
|
|
double
|
|
LCL_GetSysPrecisionAsQuantum(void)
|
|
{
|
|
return precision_quantum;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
double
|
|
LCL_GetMaxClockError(void)
|
|
{
|
|
return max_clock_error;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_AddParameterChangeHandler(LCL_ParameterChangeHandler handler, void *anything)
|
|
{
|
|
ChangeListEntry *ptr, *new_entry;
|
|
|
|
/* Check that the handler is not already registered */
|
|
for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
|
|
BRIEF_ASSERT(ptr->handler != handler || ptr->anything != anything);
|
|
}
|
|
|
|
new_entry = MallocNew(ChangeListEntry);
|
|
|
|
new_entry->handler = handler;
|
|
new_entry->anything = anything;
|
|
|
|
/* Chain it into the list */
|
|
new_entry->next = &change_list;
|
|
new_entry->prev = change_list.prev;
|
|
change_list.prev->next = new_entry;
|
|
change_list.prev = new_entry;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
/* Remove a handler */
|
|
void LCL_RemoveParameterChangeHandler(LCL_ParameterChangeHandler handler, void *anything)
|
|
{
|
|
|
|
ChangeListEntry *ptr;
|
|
int ok;
|
|
|
|
ptr = NULL;
|
|
ok = 0;
|
|
|
|
for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
|
|
if (ptr->handler == handler && ptr->anything == anything) {
|
|
ok = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(ok);
|
|
|
|
/* Unlink entry from the list */
|
|
ptr->next->prev = ptr->prev;
|
|
ptr->prev->next = ptr->next;
|
|
|
|
Free(ptr);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
LCL_IsFirstParameterChangeHandler(LCL_ParameterChangeHandler handler)
|
|
{
|
|
return change_list.next->handler == handler;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
invoke_parameter_change_handlers(struct timespec *raw, struct timespec *cooked,
|
|
double dfreq, double doffset,
|
|
LCL_ChangeType change_type)
|
|
{
|
|
ChangeListEntry *ptr;
|
|
|
|
for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
|
|
(ptr->handler)(raw, cooked, dfreq, doffset, change_type, ptr->anything);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_AddDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything)
|
|
{
|
|
DispersionNotifyListEntry *ptr, *new_entry;
|
|
|
|
/* Check that the handler is not already registered */
|
|
for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
|
|
BRIEF_ASSERT(ptr->handler != handler || ptr->anything != anything);
|
|
}
|
|
|
|
new_entry = MallocNew(DispersionNotifyListEntry);
|
|
|
|
new_entry->handler = handler;
|
|
new_entry->anything = anything;
|
|
|
|
/* Chain it into the list */
|
|
new_entry->next = &dispersion_notify_list;
|
|
new_entry->prev = dispersion_notify_list.prev;
|
|
dispersion_notify_list.prev->next = new_entry;
|
|
dispersion_notify_list.prev = new_entry;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
/* Remove a handler */
|
|
extern
|
|
void LCL_RemoveDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything)
|
|
{
|
|
|
|
DispersionNotifyListEntry *ptr;
|
|
int ok;
|
|
|
|
ptr = NULL;
|
|
ok = 0;
|
|
|
|
for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
|
|
if (ptr->handler == handler && ptr->anything == anything) {
|
|
ok = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(ok);
|
|
|
|
/* Unlink entry from the list */
|
|
ptr->next->prev = ptr->prev;
|
|
ptr->prev->next = ptr->next;
|
|
|
|
Free(ptr);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_ReadRawTime(struct timespec *ts)
|
|
{
|
|
#if HAVE_CLOCK_GETTIME
|
|
if (clock_gettime(CLOCK_REALTIME, ts) < 0)
|
|
LOG_FATAL("clock_gettime() failed : %s", strerror(errno));
|
|
#else
|
|
struct timeval tv;
|
|
|
|
if (gettimeofday(&tv, NULL) < 0)
|
|
LOG_FATAL("gettimeofday() failed : %s", strerror(errno));
|
|
|
|
UTI_TimevalToTimespec(&tv, ts);
|
|
#endif
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_ReadCookedTime(struct timespec *result, double *err)
|
|
{
|
|
struct timespec raw;
|
|
|
|
LCL_ReadRawTime(&raw);
|
|
LCL_CookTime(&raw, result, err);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_CookTime(struct timespec *raw, struct timespec *cooked, double *err)
|
|
{
|
|
double correction;
|
|
|
|
LCL_GetOffsetCorrection(raw, &correction, err);
|
|
UTI_AddDoubleToTimespec(raw, correction, cooked);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_GetOffsetCorrection(struct timespec *raw, double *correction, double *err)
|
|
{
|
|
/* Call system specific driver to get correction */
|
|
(*drv_offset_convert)(raw, correction, err);
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* Return current frequency */
|
|
|
|
double
|
|
LCL_ReadAbsoluteFrequency(void)
|
|
{
|
|
double freq;
|
|
|
|
freq = current_freq_ppm;
|
|
|
|
/* Undo temperature compensation */
|
|
if (temp_comp_ppm != 0.0) {
|
|
freq = (freq + temp_comp_ppm) / (1.0 - 1.0e-6 * temp_comp_ppm);
|
|
}
|
|
|
|
return freq;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static double
|
|
clamp_freq(double freq)
|
|
{
|
|
if (freq <= max_freq_ppm && freq >= -max_freq_ppm)
|
|
return freq;
|
|
|
|
LOG(LOGS_WARN, "Frequency %.1f ppm exceeds allowed maximum", freq);
|
|
|
|
return CLAMP(-max_freq_ppm, freq, max_freq_ppm);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static int
|
|
check_offset(struct timespec *now, double offset)
|
|
{
|
|
/* Check if the time will be still sane with accumulated offset */
|
|
if (UTI_IsTimeOffsetSane(now, -offset))
|
|
return 1;
|
|
|
|
LOG(LOGS_WARN, "Adjustment of %.1f seconds is invalid", -offset);
|
|
return 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
/* This involves both setting the absolute frequency with the
|
|
system-specific driver, as well as calling all notify handlers */
|
|
|
|
void
|
|
LCL_SetAbsoluteFrequency(double afreq_ppm)
|
|
{
|
|
struct timespec raw, cooked;
|
|
double dfreq;
|
|
|
|
afreq_ppm = clamp_freq(afreq_ppm);
|
|
|
|
/* Apply temperature compensation */
|
|
if (temp_comp_ppm != 0.0) {
|
|
afreq_ppm = afreq_ppm * (1.0 - 1.0e-6 * temp_comp_ppm) - temp_comp_ppm;
|
|
}
|
|
|
|
/* Call the system-specific driver for setting the frequency */
|
|
|
|
afreq_ppm = (*drv_set_freq)(afreq_ppm);
|
|
|
|
dfreq = (afreq_ppm - current_freq_ppm) / (1.0e6 - current_freq_ppm);
|
|
|
|
LCL_ReadRawTime(&raw);
|
|
LCL_CookTime(&raw, &cooked, NULL);
|
|
|
|
/* Dispatch to all handlers */
|
|
invoke_parameter_change_handlers(&raw, &cooked, dfreq, 0.0, LCL_ChangeAdjust);
|
|
|
|
current_freq_ppm = afreq_ppm;
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_AccumulateDeltaFrequency(double dfreq)
|
|
{
|
|
struct timespec raw, cooked;
|
|
double old_freq_ppm;
|
|
|
|
old_freq_ppm = current_freq_ppm;
|
|
|
|
/* Work out new absolute frequency. Note that absolute frequencies
|
|
are handled in units of ppm, whereas the 'dfreq' argument is in
|
|
terms of the gradient of the (offset) v (local time) function. */
|
|
|
|
current_freq_ppm += dfreq * (1.0e6 - current_freq_ppm);
|
|
|
|
current_freq_ppm = clamp_freq(current_freq_ppm);
|
|
|
|
/* Call the system-specific driver for setting the frequency */
|
|
current_freq_ppm = (*drv_set_freq)(current_freq_ppm);
|
|
dfreq = (current_freq_ppm - old_freq_ppm) / (1.0e6 - old_freq_ppm);
|
|
|
|
LCL_ReadRawTime(&raw);
|
|
LCL_CookTime(&raw, &cooked, NULL);
|
|
|
|
/* Dispatch to all handlers */
|
|
invoke_parameter_change_handlers(&raw, &cooked, dfreq, 0.0, LCL_ChangeAdjust);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
LCL_AccumulateOffset(double offset, double corr_rate)
|
|
{
|
|
struct timespec raw, cooked;
|
|
|
|
/* In this case, the cooked time to be passed to the notify clients
|
|
has to be the cooked time BEFORE the change was made */
|
|
|
|
LCL_ReadRawTime(&raw);
|
|
LCL_CookTime(&raw, &cooked, NULL);
|
|
|
|
if (!check_offset(&cooked, offset))
|
|
return 0;
|
|
|
|
(*drv_accrue_offset)(offset, corr_rate);
|
|
|
|
/* Dispatch to all handlers */
|
|
invoke_parameter_change_handlers(&raw, &cooked, 0.0, offset, LCL_ChangeAdjust);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
LCL_ApplyStepOffset(double offset)
|
|
{
|
|
struct timespec raw, cooked;
|
|
|
|
/* In this case, the cooked time to be passed to the notify clients
|
|
has to be the cooked time BEFORE the change was made */
|
|
|
|
LCL_ReadRawTime(&raw);
|
|
LCL_CookTime(&raw, &cooked, NULL);
|
|
|
|
if (!check_offset(&raw, offset))
|
|
return 0;
|
|
|
|
if (!(*drv_apply_step_offset)(offset)) {
|
|
LOG(LOGS_ERR, "Could not step system clock");
|
|
return 0;
|
|
}
|
|
|
|
/* Reset smoothing on all clock steps */
|
|
SMT_Reset(&cooked);
|
|
|
|
/* Dispatch to all handlers */
|
|
invoke_parameter_change_handlers(&raw, &cooked, 0.0, offset, LCL_ChangeStep);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_NotifyExternalTimeStep(struct timespec *raw, struct timespec *cooked,
|
|
double offset, double dispersion)
|
|
{
|
|
LCL_CancelOffsetCorrection();
|
|
|
|
/* Dispatch to all handlers */
|
|
invoke_parameter_change_handlers(raw, cooked, 0.0, offset, LCL_ChangeUnknownStep);
|
|
|
|
lcl_InvokeDispersionNotifyHandlers(dispersion);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_NotifyLeap(int leap)
|
|
{
|
|
struct timespec raw, cooked;
|
|
|
|
LCL_ReadRawTime(&raw);
|
|
LCL_CookTime(&raw, &cooked, NULL);
|
|
|
|
/* Smooth the leap second out */
|
|
SMT_Leap(&cooked, leap);
|
|
|
|
/* Dispatch to all handlers as if the clock was stepped */
|
|
invoke_parameter_change_handlers(&raw, &cooked, 0.0, -leap, LCL_ChangeStep);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
LCL_AccumulateFrequencyAndOffset(double dfreq, double doffset, double corr_rate)
|
|
{
|
|
struct timespec raw, cooked;
|
|
double old_freq_ppm;
|
|
|
|
LCL_ReadRawTime(&raw);
|
|
/* Due to modifying the offset, this has to be the cooked time prior
|
|
to the change we are about to make */
|
|
LCL_CookTime(&raw, &cooked, NULL);
|
|
|
|
if (!check_offset(&cooked, doffset))
|
|
return 0;
|
|
|
|
old_freq_ppm = current_freq_ppm;
|
|
|
|
/* Work out new absolute frequency. Note that absolute frequencies
|
|
are handled in units of ppm, whereas the 'dfreq' argument is in
|
|
terms of the gradient of the (offset) v (local time) function. */
|
|
current_freq_ppm += dfreq * (1.0e6 - current_freq_ppm);
|
|
|
|
current_freq_ppm = clamp_freq(current_freq_ppm);
|
|
|
|
DEBUG_LOG("old_freq=%.3fppm new_freq=%.3fppm offset=%.6fsec",
|
|
old_freq_ppm, current_freq_ppm, doffset);
|
|
|
|
/* Call the system-specific driver for setting the frequency */
|
|
current_freq_ppm = (*drv_set_freq)(current_freq_ppm);
|
|
dfreq = (current_freq_ppm - old_freq_ppm) / (1.0e6 - old_freq_ppm);
|
|
|
|
(*drv_accrue_offset)(doffset, corr_rate);
|
|
|
|
/* Dispatch to all handlers */
|
|
invoke_parameter_change_handlers(&raw, &cooked, dfreq, doffset, LCL_ChangeAdjust);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
LCL_AccumulateFrequencyAndOffsetNoHandlers(double dfreq, double doffset, double corr_rate)
|
|
{
|
|
ChangeListEntry *first_handler;
|
|
int r;
|
|
|
|
first_handler = change_list.next;
|
|
change_list.next = &change_list;
|
|
|
|
r = LCL_AccumulateFrequencyAndOffset(dfreq, doffset, corr_rate);
|
|
|
|
change_list.next = first_handler;
|
|
|
|
return r;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
lcl_InvokeDispersionNotifyHandlers(double dispersion)
|
|
{
|
|
DispersionNotifyListEntry *ptr;
|
|
|
|
for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
|
|
(ptr->handler)(dispersion, ptr->anything);
|
|
}
|
|
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
lcl_RegisterSystemDrivers(lcl_ReadFrequencyDriver read_freq,
|
|
lcl_SetFrequencyDriver set_freq,
|
|
lcl_AccrueOffsetDriver accrue_offset,
|
|
lcl_ApplyStepOffsetDriver apply_step_offset,
|
|
lcl_OffsetCorrectionDriver offset_convert,
|
|
lcl_SetLeapDriver set_leap,
|
|
lcl_SetSyncStatusDriver set_sync_status)
|
|
{
|
|
drv_read_freq = read_freq;
|
|
drv_set_freq = set_freq;
|
|
drv_accrue_offset = accrue_offset;
|
|
drv_apply_step_offset = apply_step_offset;
|
|
drv_offset_convert = offset_convert;
|
|
drv_set_leap = set_leap;
|
|
drv_set_sync_status = set_sync_status;
|
|
|
|
current_freq_ppm = (*drv_read_freq)();
|
|
|
|
DEBUG_LOG("Local freq=%.3fppm", current_freq_ppm);
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* Look at the current difference between the system time and the NTP
|
|
time, and make a step to cancel it. */
|
|
|
|
int
|
|
LCL_MakeStep(void)
|
|
{
|
|
struct timespec raw;
|
|
double correction;
|
|
|
|
LCL_ReadRawTime(&raw);
|
|
LCL_GetOffsetCorrection(&raw, &correction, NULL);
|
|
|
|
if (!check_offset(&raw, -correction))
|
|
return 0;
|
|
|
|
/* Cancel remaining slew and make the step */
|
|
LCL_AccumulateOffset(correction, 0.0);
|
|
if (!LCL_ApplyStepOffset(-correction)) {
|
|
/* Revert the correction */
|
|
LCL_AccumulateOffset(-correction, 0.0);
|
|
return 0;
|
|
}
|
|
|
|
LOG(LOGS_WARN, "System clock was stepped by %.6f seconds", correction);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_CancelOffsetCorrection(void)
|
|
{
|
|
struct timespec raw;
|
|
double correction;
|
|
|
|
LCL_ReadRawTime(&raw);
|
|
LCL_GetOffsetCorrection(&raw, &correction, NULL);
|
|
LCL_AccumulateOffset(correction, 0.0);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
int
|
|
LCL_CanSystemLeap(void)
|
|
{
|
|
return drv_set_leap ? 1 : 0;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_SetSystemLeap(int leap, int tai_offset)
|
|
{
|
|
if (drv_set_leap) {
|
|
(drv_set_leap)(leap, tai_offset);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
double
|
|
LCL_SetTempComp(double comp)
|
|
{
|
|
double uncomp_freq_ppm;
|
|
|
|
if (temp_comp_ppm == comp)
|
|
return comp;
|
|
|
|
/* Undo previous compensation */
|
|
current_freq_ppm = (current_freq_ppm + temp_comp_ppm) /
|
|
(1.0 - 1.0e-6 * temp_comp_ppm);
|
|
|
|
uncomp_freq_ppm = current_freq_ppm;
|
|
|
|
/* Apply new compensation */
|
|
current_freq_ppm = current_freq_ppm * (1.0 - 1.0e-6 * comp) - comp;
|
|
|
|
/* Call the system-specific driver for setting the frequency */
|
|
current_freq_ppm = (*drv_set_freq)(current_freq_ppm);
|
|
|
|
temp_comp_ppm = (uncomp_freq_ppm - current_freq_ppm) /
|
|
(1.0e-6 * uncomp_freq_ppm + 1.0);
|
|
|
|
return temp_comp_ppm;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
LCL_SetSyncStatus(int synchronised, double est_error, double max_error)
|
|
{
|
|
if (drv_set_sync_status) {
|
|
(drv_set_sync_status)(synchronised, est_error, max_error);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|