Instead of the generic clock driver silently zeroing the remaining offset after detecting an external step, cancel it properly with the slew handlers in order to correct timestamps that are not reset in handling of the unknown step (e.g. the NTP local TX).
419 lines
12 KiB
C
419 lines
12 KiB
C
/*
|
|
chronyd/chronyc - Programs for keeping computer clocks accurate.
|
|
|
|
**********************************************************************
|
|
* Copyright (C) Miroslav Lichvar 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.
|
|
*
|
|
**********************************************************************
|
|
|
|
=======================================================================
|
|
|
|
Generic driver functions to complete system-specific drivers
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "sysincl.h"
|
|
|
|
#include "sys_generic.h"
|
|
|
|
#include "conf.h"
|
|
#include "local.h"
|
|
#include "localp.h"
|
|
#include "logging.h"
|
|
#include "privops.h"
|
|
#include "sched.h"
|
|
#include "util.h"
|
|
|
|
/* ================================================== */
|
|
|
|
/* System clock drivers */
|
|
static lcl_ReadFrequencyDriver drv_read_freq;
|
|
static lcl_SetFrequencyDriver drv_set_freq;
|
|
static lcl_SetSyncStatusDriver drv_set_sync_status;
|
|
static lcl_AccrueOffsetDriver drv_accrue_offset;
|
|
static lcl_OffsetCorrectionDriver drv_get_offset_correction;
|
|
|
|
/* Current frequency as requested by the local module (in ppm) */
|
|
static double base_freq;
|
|
|
|
/* Maximum frequency that can be set by drv_set_freq (in ppm) */
|
|
static double max_freq;
|
|
|
|
/* Maximum expected delay in the actual frequency change (e.g. kernel ticks)
|
|
in local time */
|
|
static double max_freq_change_delay;
|
|
|
|
/* Maximum allowed frequency offset relative to the base frequency */
|
|
static double max_corr_freq;
|
|
|
|
/* Amount of outstanding offset to process */
|
|
static double offset_register;
|
|
|
|
/* Minimum offset to correct */
|
|
#define MIN_OFFSET_CORRECTION 1.0e-9
|
|
|
|
/* Current frequency offset between base_freq and the real clock frequency
|
|
as set by drv_set_freq (not in ppm) */
|
|
static double slew_freq;
|
|
|
|
/* Time (raw) of last update of slewing frequency and offset */
|
|
static struct timespec slew_start;
|
|
|
|
/* Limits for the slew timeout */
|
|
#define MIN_SLEW_TIMEOUT 1.0
|
|
#define MAX_SLEW_TIMEOUT 1.0e4
|
|
|
|
/* Scheduler timeout ID for ending of the currently running slew */
|
|
static SCH_TimeoutID slew_timeout_id;
|
|
|
|
/* Suggested offset correction rate (correction time * offset) */
|
|
static double correction_rate;
|
|
|
|
/* Maximum expected offset correction error caused by delayed change in the
|
|
real frequency of the clock */
|
|
static double slew_error;
|
|
|
|
/* Minimum offset that the system driver can slew faster than the maximum
|
|
frequency offset that it allows to be set directly */
|
|
static double fastslew_min_offset;
|
|
|
|
/* Maximum slew rate of the system driver */
|
|
static double fastslew_max_rate;
|
|
|
|
/* Flag indicating that the system driver is currently slewing */
|
|
static int fastslew_active;
|
|
|
|
/* ================================================== */
|
|
|
|
static void handle_end_of_slew(void *anything);
|
|
static void update_slew(void);
|
|
|
|
/* ================================================== */
|
|
/* Adjust slew_start on clock step */
|
|
|
|
static void
|
|
handle_step(struct timespec *raw, struct timespec *cooked, double dfreq,
|
|
double doffset, LCL_ChangeType change_type, void *anything)
|
|
{
|
|
if (change_type == LCL_ChangeStep) {
|
|
UTI_AddDoubleToTimespec(&slew_start, -doffset, &slew_start);
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
start_fastslew(void)
|
|
{
|
|
if (!drv_accrue_offset)
|
|
return;
|
|
|
|
drv_accrue_offset(offset_register, 0.0);
|
|
|
|
DEBUG_LOG("fastslew offset=%e", offset_register);
|
|
|
|
offset_register = 0.0;
|
|
fastslew_active = 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
stop_fastslew(struct timespec *now)
|
|
{
|
|
double corr;
|
|
|
|
if (!drv_get_offset_correction || !fastslew_active)
|
|
return;
|
|
|
|
/* Cancel the remaining offset */
|
|
drv_get_offset_correction(now, &corr, NULL);
|
|
drv_accrue_offset(corr, 0.0);
|
|
offset_register -= corr;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static double
|
|
clamp_freq(double freq)
|
|
{
|
|
if (freq > max_freq)
|
|
return max_freq;
|
|
if (freq < -max_freq)
|
|
return -max_freq;
|
|
return freq;
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* End currently running slew and start a new one */
|
|
|
|
static void
|
|
update_slew(void)
|
|
{
|
|
struct timespec now, end_of_slew;
|
|
double old_slew_freq, total_freq, corr_freq, duration;
|
|
|
|
/* Remove currently running timeout */
|
|
SCH_RemoveTimeout(slew_timeout_id);
|
|
|
|
LCL_ReadRawTime(&now);
|
|
|
|
/* Adjust the offset register by achieved slew */
|
|
duration = UTI_DiffTimespecsToDouble(&now, &slew_start);
|
|
offset_register -= slew_freq * duration;
|
|
|
|
stop_fastslew(&now);
|
|
|
|
/* Estimate how long should the next slew take */
|
|
if (fabs(offset_register) < MIN_OFFSET_CORRECTION) {
|
|
duration = MAX_SLEW_TIMEOUT;
|
|
} else {
|
|
duration = correction_rate / fabs(offset_register);
|
|
if (duration < MIN_SLEW_TIMEOUT)
|
|
duration = MIN_SLEW_TIMEOUT;
|
|
}
|
|
|
|
/* Get frequency offset needed to slew the offset in the duration
|
|
and clamp it to the allowed maximum */
|
|
corr_freq = offset_register / duration;
|
|
if (corr_freq < -max_corr_freq)
|
|
corr_freq = -max_corr_freq;
|
|
else if (corr_freq > max_corr_freq)
|
|
corr_freq = max_corr_freq;
|
|
|
|
/* Let the system driver perform the slew if the requested frequency
|
|
offset is too large for the frequency driver */
|
|
if (drv_accrue_offset && fabs(corr_freq) >= fastslew_max_rate &&
|
|
fabs(offset_register) > fastslew_min_offset) {
|
|
start_fastslew();
|
|
corr_freq = 0.0;
|
|
}
|
|
|
|
/* Get the new real frequency and clamp it */
|
|
total_freq = clamp_freq(base_freq + corr_freq * (1.0e6 - base_freq));
|
|
|
|
/* Set the new frequency (the actual frequency returned by the call may be
|
|
slightly different from the requested frequency due to rounding) */
|
|
total_freq = (*drv_set_freq)(total_freq);
|
|
|
|
/* Compute the new slewing frequency, it's relative to the real frequency to
|
|
make the calculation in offset_convert() cheaper */
|
|
old_slew_freq = slew_freq;
|
|
slew_freq = (total_freq - base_freq) / (1.0e6 - total_freq);
|
|
|
|
/* Compute the dispersion introduced by changing frequency and add it
|
|
to all statistics held at higher levels in the system */
|
|
slew_error = fabs((old_slew_freq - slew_freq) * max_freq_change_delay);
|
|
if (slew_error >= MIN_OFFSET_CORRECTION)
|
|
lcl_InvokeDispersionNotifyHandlers(slew_error);
|
|
|
|
/* Compute the duration of the slew and clamp it. If the slewing frequency
|
|
is zero or has wrong sign (e.g. due to rounding in the frequency driver or
|
|
when base_freq is larger than max_freq, or fast slew is active), use the
|
|
maximum timeout and try again on the next update. */
|
|
if (fabs(offset_register) < MIN_OFFSET_CORRECTION ||
|
|
offset_register * slew_freq <= 0.0) {
|
|
duration = MAX_SLEW_TIMEOUT;
|
|
} else {
|
|
duration = offset_register / slew_freq;
|
|
if (duration < MIN_SLEW_TIMEOUT)
|
|
duration = MIN_SLEW_TIMEOUT;
|
|
else if (duration > MAX_SLEW_TIMEOUT)
|
|
duration = MAX_SLEW_TIMEOUT;
|
|
}
|
|
|
|
/* Restart timer for the next update */
|
|
UTI_AddDoubleToTimespec(&now, duration, &end_of_slew);
|
|
slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL);
|
|
slew_start = now;
|
|
|
|
DEBUG_LOG("slew offset=%e corr_rate=%e base_freq=%f total_freq=%f slew_freq=%e duration=%f slew_error=%e",
|
|
offset_register, correction_rate, base_freq, total_freq, slew_freq,
|
|
duration, slew_error);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
handle_end_of_slew(void *anything)
|
|
{
|
|
slew_timeout_id = 0;
|
|
update_slew();
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static double
|
|
read_frequency(void)
|
|
{
|
|
return base_freq;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static double
|
|
set_frequency(double freq_ppm)
|
|
{
|
|
base_freq = freq_ppm;
|
|
update_slew();
|
|
|
|
return base_freq;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
accrue_offset(double offset, double corr_rate)
|
|
{
|
|
offset_register += offset;
|
|
correction_rate = corr_rate;
|
|
|
|
update_slew();
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* Determine the correction to generate the cooked time for given raw time */
|
|
|
|
static void
|
|
offset_convert(struct timespec *raw,
|
|
double *corr, double *err)
|
|
{
|
|
double duration, fastslew_corr, fastslew_err;
|
|
|
|
duration = UTI_DiffTimespecsToDouble(raw, &slew_start);
|
|
|
|
if (drv_get_offset_correction && fastslew_active) {
|
|
drv_get_offset_correction(raw, &fastslew_corr, &fastslew_err);
|
|
if (fastslew_corr == 0.0 && fastslew_err == 0.0)
|
|
fastslew_active = 0;
|
|
} else {
|
|
fastslew_corr = fastslew_err = 0.0;
|
|
}
|
|
|
|
*corr = slew_freq * duration + fastslew_corr - offset_register;
|
|
|
|
if (err) {
|
|
*err = fastslew_err;
|
|
if (fabs(duration) <= max_freq_change_delay)
|
|
*err += slew_error;
|
|
}
|
|
}
|
|
|
|
/* ================================================== */
|
|
/* Positive means currently fast of true time, i.e. jump backwards */
|
|
|
|
static int
|
|
apply_step_offset(double offset)
|
|
{
|
|
struct timespec old_time, new_time;
|
|
struct timeval new_time_tv;
|
|
double err;
|
|
|
|
LCL_ReadRawTime(&old_time);
|
|
UTI_AddDoubleToTimespec(&old_time, -offset, &new_time);
|
|
UTI_TimespecToTimeval(&new_time, &new_time_tv);
|
|
|
|
if (PRV_SetTime(&new_time_tv, NULL) < 0) {
|
|
DEBUG_LOG("settimeofday() failed");
|
|
return 0;
|
|
}
|
|
|
|
LCL_ReadRawTime(&old_time);
|
|
err = UTI_DiffTimespecsToDouble(&old_time, &new_time);
|
|
|
|
lcl_InvokeDispersionNotifyHandlers(fabs(err));
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
static void
|
|
set_sync_status(int synchronised, double est_error, double max_error)
|
|
{
|
|
double offset;
|
|
|
|
offset = fabs(offset_register);
|
|
if (est_error < offset)
|
|
est_error = offset;
|
|
max_error += offset;
|
|
|
|
if (drv_set_sync_status)
|
|
drv_set_sync_status(synchronised, est_error, max_error);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
SYS_Generic_CompleteFreqDriver(double max_set_freq_ppm, double max_set_freq_delay,
|
|
lcl_ReadFrequencyDriver sys_read_freq,
|
|
lcl_SetFrequencyDriver sys_set_freq,
|
|
lcl_ApplyStepOffsetDriver sys_apply_step_offset,
|
|
double min_fastslew_offset, double max_fastslew_rate,
|
|
lcl_AccrueOffsetDriver sys_accrue_offset,
|
|
lcl_OffsetCorrectionDriver sys_get_offset_correction,
|
|
lcl_SetLeapDriver sys_set_leap,
|
|
lcl_SetSyncStatusDriver sys_set_sync_status)
|
|
{
|
|
max_freq = max_set_freq_ppm;
|
|
max_freq_change_delay = max_set_freq_delay * (1.0 + max_freq / 1.0e6);
|
|
drv_read_freq = sys_read_freq;
|
|
drv_set_freq = sys_set_freq;
|
|
drv_accrue_offset = sys_accrue_offset;
|
|
drv_get_offset_correction = sys_get_offset_correction;
|
|
drv_set_sync_status = sys_set_sync_status;
|
|
|
|
base_freq = (*drv_read_freq)();
|
|
slew_freq = 0.0;
|
|
offset_register = 0.0;
|
|
|
|
max_corr_freq = CNF_GetMaxSlewRate() / 1.0e6;
|
|
|
|
fastslew_min_offset = min_fastslew_offset;
|
|
fastslew_max_rate = max_fastslew_rate / 1.0e6;
|
|
fastslew_active = 0;
|
|
|
|
lcl_RegisterSystemDrivers(read_frequency, set_frequency,
|
|
accrue_offset, sys_apply_step_offset ?
|
|
sys_apply_step_offset : apply_step_offset,
|
|
offset_convert, sys_set_leap, set_sync_status);
|
|
|
|
LCL_AddParameterChangeHandler(handle_step, NULL);
|
|
}
|
|
|
|
/* ================================================== */
|
|
|
|
void
|
|
SYS_Generic_Finalise(void)
|
|
{
|
|
struct timespec now;
|
|
|
|
/* Must *NOT* leave a slew running - clock could drift way off
|
|
if the daemon is not restarted */
|
|
|
|
SCH_RemoveTimeout(slew_timeout_id);
|
|
slew_timeout_id = 0;
|
|
|
|
(*drv_set_freq)(clamp_freq(base_freq));
|
|
|
|
LCL_ReadRawTime(&now);
|
|
stop_fastslew(&now);
|
|
|
|
LCL_RemoveParameterChangeHandler(handle_step, NULL);
|
|
}
|
|
|
|
/* ================================================== */
|