summaryrefslogtreecommitdiffstats
path: root/local.c
diff options
context:
space:
mode:
Diffstat (limited to 'local.c')
-rw-r--r--local.c781
1 files changed, 781 insertions, 0 deletions
diff --git a/local.c b/local.c
new file mode 100644
index 0000000..b32d0c6
--- /dev/null
+++ b/local.c
@@ -0,0 +1,781 @@
+/*
+ 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 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
+
+#define NSEC_PER_SEC 1000000000
+
+static double
+measure_clock_precision(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 1.0e-9 * best;
+}
+
+/* ================================================== */
+
+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 */
+ if (change_list.next != &change_list)
+ assert(0);
+ if (dispersion_notify_list.next != &dispersion_notify_list)
+ assert(0);
+}
+
+/* ================================================== */
+
+/* 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) {
+ if (!(ptr->handler != handler || ptr->anything != anything)) {
+ assert(0);
+ }
+ }
+
+ 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) {
+ if (!(ptr->handler != handler || ptr->anything != anything)) {
+ assert(0);
+ }
+ }
+
+ 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))
+ 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);
+ }
+}
+
+/* ================================================== */