It was never used for anything and messages in debug output already include filenames, which can be easily grepped if there is a need to see log messages only from a particular file.
422 lines
12 KiB
C
422 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_ChangeUnknownStep) {
|
|
/* Reset offset and slewing */
|
|
slew_start = *raw;
|
|
offset_register = 0.0;
|
|
update_slew();
|
|
} else 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);
|
|
}
|
|
|
|
/* ================================================== */
|