diff options
Diffstat (limited to 'local.c')
-rw-r--r-- | local.c | 781 |
1 files changed, 781 insertions, 0 deletions
@@ -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); + } +} + +/* ================================================== */ |