/* 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); } } /* ================================================== */