The existing implementation of getting leap second information from a timezone in get_tz_leap() relies on non-portable C library behaviour. Specifically, mktime is not required to return '60' in the tm_sec field when a leap second is inserted leading to "Timezone right/UTC failed leap second check, ignoring" errors on musl based systems. This patch adds support for getting leap second information from the leap-seconds.list file included with tzdata and adds a new configuration directive leapseclist to switch on the feature.
862 lines
24 KiB
C
862 lines
24 KiB
C
/*
|
|
chronyd/chronyc - Programs for keeping computer clocks accurate.
|
|
|
|
**********************************************************************
|
|
* Copyright (C) Miroslav Lichvar 2009-2011, 2013-2014, 2016-2019, 2022
|
|
*
|
|
* 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.
|
|
*
|
|
**********************************************************************
|
|
|
|
=======================================================================
|
|
|
|
Routines implementing reference clocks.
|
|
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "array.h"
|
|
#include "refclock.h"
|
|
#include "reference.h"
|
|
#include "conf.h"
|
|
#include "local.h"
|
|
#include "memory.h"
|
|
#include "util.h"
|
|
#include "sources.h"
|
|
#include "logging.h"
|
|
#include "regress.h"
|
|
#include "samplefilt.h"
|
|
#include "sched.h"
|
|
|
|
/* Maximum offset of locked reference as a fraction of the PPS interval */
|
|
#define PPS_LOCK_LIMIT 0.4
|
|
|
|
/* list of refclock drivers */
|
|
extern RefclockDriver RCL_SHM_driver;
|
|
extern RefclockDriver RCL_SOCK_driver;
|
|
extern RefclockDriver RCL_PPS_driver;
|
|
extern RefclockDriver RCL_PHC_driver;
|
|
|
|
struct FilterSample {
|
|
double offset;
|
|
double dispersion;
|
|
struct timespec sample_time;
|
|
};
|
|
|
|
struct RCL_Instance_Record {
|
|
RefclockDriver *driver;
|
|
void *data;
|
|
char *driver_parameter;
|
|
int driver_parameter_length;
|
|
int driver_poll;
|
|
int driver_polled;
|
|
int poll;
|
|
int leap_status;
|
|
int local;
|
|
int pps_forced;
|
|
int pps_rate;
|
|
int pps_active;
|
|
int max_lock_age;
|
|
int stratum;
|
|
int tai;
|
|
uint32_t ref_id;
|
|
uint32_t lock_ref;
|
|
double offset;
|
|
double delay;
|
|
double precision;
|
|
double pulse_width;
|
|
SPF_Instance filter;
|
|
SCH_TimeoutID timeout_id;
|
|
SRC_Instance source;
|
|
};
|
|
|
|
/* Array of pointers to RCL_Instance_Record */
|
|
static ARR_Instance refclocks;
|
|
|
|
static LOG_FileID logfileid;
|
|
|
|
static int valid_sample_time(RCL_Instance instance, struct timespec *sample_time);
|
|
static int pps_stratum(RCL_Instance instance, struct timespec *ts);
|
|
static void poll_timeout(void *arg);
|
|
static void slew_samples(struct timespec *raw, struct timespec *cooked, double dfreq,
|
|
double doffset, LCL_ChangeType change_type, void *anything);
|
|
static void add_dispersion(double dispersion, void *anything);
|
|
static void log_sample(RCL_Instance instance, struct timespec *sample_time, int filtered, int pulse, double raw_offset, double cooked_offset, double dispersion);
|
|
|
|
static RCL_Instance
|
|
get_refclock(unsigned int index)
|
|
{
|
|
return *(RCL_Instance *)ARR_GetElement(refclocks, index);
|
|
}
|
|
|
|
void
|
|
RCL_Initialise(void)
|
|
{
|
|
refclocks = ARR_CreateInstance(sizeof (RCL_Instance));
|
|
|
|
CNF_AddRefclocks();
|
|
|
|
if (ARR_GetSize(refclocks) > 0) {
|
|
LCL_AddParameterChangeHandler(slew_samples, NULL);
|
|
LCL_AddDispersionNotifyHandler(add_dispersion, NULL);
|
|
}
|
|
|
|
logfileid = CNF_GetLogRefclocks() ? LOG_FileOpen("refclocks",
|
|
" Date (UTC) Time Refid DP L P Raw offset Cooked offset Disp.")
|
|
: -1;
|
|
}
|
|
|
|
void
|
|
RCL_Finalise(void)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARR_GetSize(refclocks); i++) {
|
|
RCL_Instance inst = get_refclock(i);
|
|
|
|
if (inst->driver->fini)
|
|
inst->driver->fini(inst);
|
|
|
|
SPF_DestroyInstance(inst->filter);
|
|
Free(inst->driver_parameter);
|
|
SRC_DestroyInstance(inst->source);
|
|
Free(inst);
|
|
}
|
|
|
|
if (ARR_GetSize(refclocks) > 0) {
|
|
LCL_RemoveParameterChangeHandler(slew_samples, NULL);
|
|
LCL_RemoveDispersionNotifyHandler(add_dispersion, NULL);
|
|
}
|
|
|
|
ARR_DestroyInstance(refclocks);
|
|
}
|
|
|
|
int
|
|
RCL_AddRefclock(RefclockParameters *params)
|
|
{
|
|
RCL_Instance inst;
|
|
|
|
inst = MallocNew(struct RCL_Instance_Record);
|
|
*(RCL_Instance *)ARR_GetNewElement(refclocks) = inst;
|
|
|
|
if (strcmp(params->driver_name, "SHM") == 0) {
|
|
inst->driver = &RCL_SHM_driver;
|
|
} else if (strcmp(params->driver_name, "SOCK") == 0) {
|
|
inst->driver = &RCL_SOCK_driver;
|
|
} else if (strcmp(params->driver_name, "PPS") == 0) {
|
|
inst->driver = &RCL_PPS_driver;
|
|
} else if (strcmp(params->driver_name, "PHC") == 0) {
|
|
inst->driver = &RCL_PHC_driver;
|
|
} else {
|
|
LOG_FATAL("unknown refclock driver %s", params->driver_name);
|
|
}
|
|
|
|
if (!inst->driver->init && !inst->driver->poll)
|
|
LOG_FATAL("refclock driver %s is not compiled in", params->driver_name);
|
|
|
|
if (params->tai && !CNF_GetLeapSecList() && !CNF_GetLeapSecTimezone())
|
|
LOG_FATAL("refclock tai option requires leapseclist or leapsectz");
|
|
|
|
inst->data = NULL;
|
|
inst->driver_parameter = Strdup(params->driver_parameter);
|
|
inst->driver_parameter_length = 0;
|
|
inst->driver_poll = params->driver_poll;
|
|
inst->poll = params->poll;
|
|
inst->driver_polled = 0;
|
|
inst->leap_status = LEAP_Normal;
|
|
inst->local = params->local;
|
|
inst->pps_forced = params->pps_forced;
|
|
inst->pps_rate = params->pps_rate;
|
|
inst->pps_active = 0;
|
|
inst->max_lock_age = params->max_lock_age;
|
|
inst->stratum = params->stratum;
|
|
inst->tai = params->tai;
|
|
inst->lock_ref = params->lock_ref_id;
|
|
inst->offset = params->offset;
|
|
inst->delay = params->delay;
|
|
inst->precision = LCL_GetSysPrecisionAsQuantum();
|
|
inst->precision = MAX(inst->precision, params->precision);
|
|
inst->pulse_width = params->pulse_width;
|
|
inst->timeout_id = -1;
|
|
inst->source = NULL;
|
|
|
|
if (inst->driver_parameter) {
|
|
int i;
|
|
|
|
inst->driver_parameter_length = strlen(inst->driver_parameter);
|
|
for (i = 0; i < inst->driver_parameter_length; i++)
|
|
if (inst->driver_parameter[i] == ':')
|
|
inst->driver_parameter[i] = '\0';
|
|
}
|
|
|
|
if (inst->pps_rate < 1)
|
|
inst->pps_rate = 1;
|
|
|
|
if (params->ref_id)
|
|
inst->ref_id = params->ref_id;
|
|
else {
|
|
unsigned char ref[5] = { 0, 0, 0, 0, 0 };
|
|
unsigned int index = ARR_GetSize(refclocks) - 1;
|
|
|
|
snprintf((char *)ref, sizeof (ref), "%3.3s", params->driver_name);
|
|
ref[3] = index % 10 + '0';
|
|
if (index >= 10)
|
|
ref[2] = (index / 10) % 10 + '0';
|
|
|
|
inst->ref_id = (uint32_t)ref[0] << 24 | ref[1] << 16 | ref[2] << 8 | ref[3];
|
|
}
|
|
|
|
if (inst->local) {
|
|
inst->pps_forced = 1;
|
|
inst->lock_ref = inst->ref_id;
|
|
inst->leap_status = LEAP_Unsynchronised;
|
|
inst->max_lock_age = MAX(inst->max_lock_age, 3);
|
|
}
|
|
|
|
if (inst->driver->poll) {
|
|
int max_samples;
|
|
|
|
if (inst->driver_poll > inst->poll)
|
|
inst->driver_poll = inst->poll;
|
|
|
|
max_samples = 1 << (inst->poll - inst->driver_poll);
|
|
if (max_samples < params->filter_length) {
|
|
if (max_samples < 4) {
|
|
LOG(LOGS_WARN, "Setting filter length for %s to %d",
|
|
UTI_RefidToString(inst->ref_id), max_samples);
|
|
}
|
|
params->filter_length = max_samples;
|
|
}
|
|
}
|
|
|
|
if (inst->driver->init && !inst->driver->init(inst))
|
|
LOG_FATAL("refclock %s initialisation failed", params->driver_name);
|
|
|
|
/* Require the filter to have at least 4 samples to produce a filtered
|
|
sample, or be full for shorter lengths, and combine 60% of samples
|
|
closest to the median */
|
|
inst->filter = SPF_CreateInstance(MIN(params->filter_length, 4), params->filter_length,
|
|
params->max_dispersion, 0.6);
|
|
|
|
inst->source = SRC_CreateNewInstance(inst->ref_id, SRC_REFCLOCK, 0, params->sel_options,
|
|
NULL, params->min_samples, params->max_samples,
|
|
0.0, 0.0);
|
|
|
|
DEBUG_LOG("refclock %s refid=%s poll=%d dpoll=%d filter=%d",
|
|
params->driver_name, UTI_RefidToString(inst->ref_id),
|
|
inst->poll, inst->driver_poll, params->filter_length);
|
|
|
|
return 1;
|
|
}
|
|
|
|
void
|
|
RCL_StartRefclocks(void)
|
|
{
|
|
unsigned int i, j, n, lock_index;
|
|
|
|
n = ARR_GetSize(refclocks);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
RCL_Instance inst = get_refclock(i);
|
|
|
|
SRC_SetActive(inst->source);
|
|
inst->timeout_id = SCH_AddTimeoutByDelay(0.0, poll_timeout, (void *)inst);
|
|
|
|
/* Replace lock refid with the refclock's index, or -1 if not valid */
|
|
|
|
lock_index = -1;
|
|
|
|
if (inst->lock_ref != 0) {
|
|
for (j = 0; j < n; j++) {
|
|
RCL_Instance inst2 = get_refclock(j);
|
|
|
|
if (inst->lock_ref != inst2->ref_id)
|
|
continue;
|
|
|
|
if (inst->driver->poll && inst2->driver->poll &&
|
|
(double)inst->max_lock_age / inst->pps_rate < UTI_Log2ToDouble(inst2->driver_poll))
|
|
LOG(LOGS_WARN, "%s maxlockage too small for %s",
|
|
UTI_RefidToString(inst->ref_id), UTI_RefidToString(inst2->ref_id));
|
|
|
|
lock_index = j;
|
|
break;
|
|
}
|
|
|
|
if (lock_index == -1 || (lock_index == i && !inst->local))
|
|
LOG(LOGS_WARN, "Invalid lock refid %s", UTI_RefidToString(inst->lock_ref));
|
|
}
|
|
|
|
inst->lock_ref = lock_index;
|
|
}
|
|
}
|
|
|
|
void
|
|
RCL_ReportSource(RPT_SourceReport *report, struct timespec *now)
|
|
{
|
|
unsigned int i;
|
|
uint32_t ref_id;
|
|
|
|
assert(report->ip_addr.family == IPADDR_INET4);
|
|
ref_id = report->ip_addr.addr.in4;
|
|
|
|
for (i = 0; i < ARR_GetSize(refclocks); i++) {
|
|
RCL_Instance inst = get_refclock(i);
|
|
if (inst->ref_id == ref_id) {
|
|
report->poll = inst->poll;
|
|
report->mode = RPT_LOCAL_REFERENCE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
RCL_SetDriverData(RCL_Instance instance, void *data)
|
|
{
|
|
instance->data = data;
|
|
}
|
|
|
|
void *
|
|
RCL_GetDriverData(RCL_Instance instance)
|
|
{
|
|
return instance->data;
|
|
}
|
|
|
|
char *
|
|
RCL_GetDriverParameter(RCL_Instance instance)
|
|
{
|
|
return instance->driver_parameter;
|
|
}
|
|
|
|
static char *
|
|
get_next_driver_option(RCL_Instance instance, char *option)
|
|
{
|
|
if (option == NULL)
|
|
option = instance->driver_parameter;
|
|
|
|
option += strlen(option) + 1;
|
|
|
|
if (option >= instance->driver_parameter + instance->driver_parameter_length)
|
|
return NULL;
|
|
|
|
return option;
|
|
}
|
|
|
|
void
|
|
RCL_CheckDriverOptions(RCL_Instance instance, const char **options)
|
|
{
|
|
char *option;
|
|
int i, len;
|
|
|
|
for (option = get_next_driver_option(instance, NULL);
|
|
option;
|
|
option = get_next_driver_option(instance, option)) {
|
|
for (i = 0; options && options[i]; i++) {
|
|
len = strlen(options[i]);
|
|
if (!strncmp(options[i], option, len) &&
|
|
(option[len] == '=' || option[len] == '\0'))
|
|
break;
|
|
}
|
|
|
|
if (!options || !options[i])
|
|
LOG_FATAL("Invalid refclock driver option %s", option);
|
|
}
|
|
}
|
|
|
|
char *
|
|
RCL_GetDriverOption(RCL_Instance instance, char *name)
|
|
{
|
|
char *option;
|
|
int len;
|
|
|
|
len = strlen(name);
|
|
|
|
for (option = get_next_driver_option(instance, NULL);
|
|
option;
|
|
option = get_next_driver_option(instance, option)) {
|
|
if (!strncmp(name, option, len)) {
|
|
if (option[len] == '=')
|
|
return option + len + 1;
|
|
if (option[len] == '\0')
|
|
return option + len;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
convert_tai_offset(struct timespec *sample_time, double *offset)
|
|
{
|
|
struct timespec tai_ts, utc_ts;
|
|
int tai_offset;
|
|
|
|
/* Get approximate TAI-UTC offset for the reference time in TAI */
|
|
UTI_AddDoubleToTimespec(sample_time, *offset, &tai_ts);
|
|
tai_offset = REF_GetTaiOffset(&tai_ts);
|
|
|
|
/* Get TAI-UTC offset for the reference time in UTC +/- 1 second */
|
|
UTI_AddDoubleToTimespec(&tai_ts, -tai_offset, &utc_ts);
|
|
tai_offset = REF_GetTaiOffset(&utc_ts);
|
|
|
|
if (!tai_offset)
|
|
return 0;
|
|
|
|
*offset -= tai_offset;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
accumulate_sample(RCL_Instance instance, struct timespec *sample_time, double offset, double dispersion)
|
|
{
|
|
NTP_Sample sample;
|
|
|
|
sample.time = *sample_time;
|
|
sample.offset = offset;
|
|
sample.peer_delay = instance->delay;
|
|
sample.root_delay = instance->delay;
|
|
sample.peer_dispersion = dispersion;
|
|
sample.root_dispersion = dispersion;
|
|
|
|
return SPF_AccumulateSample(instance->filter, &sample);
|
|
}
|
|
|
|
int
|
|
RCL_AddSample(RCL_Instance instance, struct timespec *sample_time,
|
|
struct timespec *ref_time, int leap)
|
|
{
|
|
double correction, dispersion, raw_offset, offset;
|
|
struct timespec cooked_time;
|
|
|
|
if (instance->pps_forced)
|
|
return RCL_AddPulse(instance, sample_time,
|
|
1.0e-9 * (sample_time->tv_nsec - ref_time->tv_nsec));
|
|
|
|
raw_offset = UTI_DiffTimespecsToDouble(ref_time, sample_time);
|
|
|
|
LCL_GetOffsetCorrection(sample_time, &correction, &dispersion);
|
|
UTI_AddDoubleToTimespec(sample_time, correction, &cooked_time);
|
|
dispersion += instance->precision;
|
|
|
|
/* Make sure the timestamp and offset provided by the driver are sane */
|
|
if (!UTI_IsTimeOffsetSane(sample_time, raw_offset) ||
|
|
!valid_sample_time(instance, &cooked_time))
|
|
return 0;
|
|
|
|
switch (leap) {
|
|
case LEAP_Normal:
|
|
case LEAP_InsertSecond:
|
|
case LEAP_DeleteSecond:
|
|
instance->leap_status = leap;
|
|
break;
|
|
default:
|
|
DEBUG_LOG("refclock sample ignored bad leap %d", leap);
|
|
return 0;
|
|
}
|
|
|
|
/* Calculate offset = raw_offset - correction + instance->offset
|
|
in parts to avoid loss of precision if there are large differences */
|
|
offset = ref_time->tv_sec - sample_time->tv_sec -
|
|
(time_t)correction + (time_t)instance->offset;
|
|
offset += 1.0e-9 * (ref_time->tv_nsec - sample_time->tv_nsec) -
|
|
(correction - (time_t)correction) + (instance->offset - (time_t)instance->offset);
|
|
|
|
if (instance->tai && !convert_tai_offset(sample_time, &offset)) {
|
|
DEBUG_LOG("refclock sample ignored unknown TAI offset");
|
|
return 0;
|
|
}
|
|
|
|
if (!accumulate_sample(instance, &cooked_time, offset, dispersion))
|
|
return 0;
|
|
|
|
instance->pps_active = 0;
|
|
|
|
log_sample(instance, &cooked_time, 0, 0, raw_offset, offset, dispersion);
|
|
|
|
/* for logging purposes */
|
|
if (!instance->driver->poll)
|
|
instance->driver_polled++;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
RCL_AddPulse(RCL_Instance instance, struct timespec *pulse_time, double second)
|
|
{
|
|
double correction, dispersion;
|
|
struct timespec cooked_time;
|
|
|
|
LCL_GetOffsetCorrection(pulse_time, &correction, &dispersion);
|
|
UTI_AddDoubleToTimespec(pulse_time, correction, &cooked_time);
|
|
second += correction;
|
|
|
|
if (!UTI_IsTimeOffsetSane(pulse_time, 0.0))
|
|
return 0;
|
|
|
|
return RCL_AddCookedPulse(instance, &cooked_time, second, dispersion, correction);
|
|
}
|
|
|
|
static int
|
|
check_pulse_edge(RCL_Instance instance, double offset, double distance)
|
|
{
|
|
double max_error;
|
|
|
|
if (instance->pulse_width <= 0.0)
|
|
return 1;
|
|
|
|
max_error = 1.0 / instance->pps_rate - instance->pulse_width;
|
|
max_error = MIN(instance->pulse_width, max_error);
|
|
max_error *= 0.5;
|
|
|
|
if (fabs(offset) > max_error || distance > max_error) {
|
|
DEBUG_LOG("refclock pulse ignored offset=%.9f distance=%.9f max_error=%.9f",
|
|
offset, distance, max_error);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
RCL_AddCookedPulse(RCL_Instance instance, struct timespec *cooked_time,
|
|
double second, double dispersion, double raw_correction)
|
|
{
|
|
double offset;
|
|
int rate;
|
|
NTP_Leap leap;
|
|
|
|
if (!UTI_IsTimeOffsetSane(cooked_time, second) ||
|
|
!valid_sample_time(instance, cooked_time))
|
|
return 0;
|
|
|
|
leap = LEAP_Normal;
|
|
dispersion += instance->precision;
|
|
rate = instance->pps_rate;
|
|
|
|
offset = -second + instance->offset;
|
|
|
|
/* Adjust the offset to [-0.5/rate, 0.5/rate) interval */
|
|
offset -= (long)(offset * rate) / (double)rate;
|
|
if (offset < -0.5 / rate)
|
|
offset += 1.0 / rate;
|
|
else if (offset >= 0.5 / rate)
|
|
offset -= 1.0 / rate;
|
|
|
|
if (instance->lock_ref != -1) {
|
|
RCL_Instance lock_refclock;
|
|
NTP_Sample ref_sample;
|
|
double sample_diff, shift;
|
|
|
|
lock_refclock = get_refclock(instance->lock_ref);
|
|
|
|
if (!SPF_GetLastSample(lock_refclock->filter, &ref_sample)) {
|
|
if (instance->local) {
|
|
/* Make the first sample in order to lock to itself */
|
|
ref_sample.time = *cooked_time;
|
|
ref_sample.offset = offset;
|
|
ref_sample.peer_delay = ref_sample.peer_dispersion = 0;
|
|
ref_sample.root_delay = ref_sample.root_dispersion = 0;
|
|
} else {
|
|
DEBUG_LOG("refclock pulse ignored no ref sample");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ref_sample.root_dispersion += SPF_GetAvgSampleDispersion(lock_refclock->filter);
|
|
|
|
sample_diff = UTI_DiffTimespecsToDouble(cooked_time, &ref_sample.time);
|
|
if (fabs(sample_diff) >= (double)instance->max_lock_age / rate) {
|
|
DEBUG_LOG("refclock pulse ignored samplediff=%.9f", sample_diff);
|
|
|
|
/* Restart the local mode */
|
|
if (instance->local) {
|
|
LOG(LOGS_WARN, "Local refclock lost lock");
|
|
SPF_DropSamples(instance->filter);
|
|
SRC_ResetInstance(instance->source);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Align the offset to the reference sample */
|
|
shift = round((ref_sample.offset - offset) * rate) / rate;
|
|
|
|
offset += shift;
|
|
|
|
if (fabs(ref_sample.offset - offset) +
|
|
ref_sample.root_dispersion + dispersion > PPS_LOCK_LIMIT / rate) {
|
|
DEBUG_LOG("refclock pulse ignored offdiff=%.9f refdisp=%.9f disp=%.9f",
|
|
ref_sample.offset - offset, ref_sample.root_dispersion, dispersion);
|
|
return 0;
|
|
}
|
|
|
|
if (!check_pulse_edge(instance, ref_sample.offset - offset, 0.0))
|
|
return 0;
|
|
|
|
leap = lock_refclock->leap_status;
|
|
|
|
DEBUG_LOG("refclock pulse offset=%.9f offdiff=%.9f samplediff=%.9f",
|
|
offset, ref_sample.offset - offset, sample_diff);
|
|
} else {
|
|
struct timespec ref_time;
|
|
int is_synchronised, stratum;
|
|
double root_delay, root_dispersion, distance;
|
|
uint32_t ref_id;
|
|
|
|
/* Ignore the pulse if we are not well synchronized and the local
|
|
reference is not active */
|
|
|
|
REF_GetReferenceParams(cooked_time, &is_synchronised, &leap, &stratum,
|
|
&ref_id, &ref_time, &root_delay, &root_dispersion);
|
|
distance = fabs(root_delay) / 2 + root_dispersion;
|
|
|
|
if (leap == LEAP_Unsynchronised || distance >= 0.5 / rate) {
|
|
DEBUG_LOG("refclock pulse ignored offset=%.9f sync=%d dist=%.9f",
|
|
offset, leap != LEAP_Unsynchronised, distance);
|
|
/* Drop also all stored samples */
|
|
SPF_DropSamples(instance->filter);
|
|
return 0;
|
|
}
|
|
|
|
if (!check_pulse_edge(instance, offset, distance))
|
|
return 0;
|
|
}
|
|
|
|
if (!accumulate_sample(instance, cooked_time, offset, dispersion))
|
|
return 0;
|
|
|
|
instance->leap_status = leap;
|
|
instance->pps_active = 1;
|
|
|
|
log_sample(instance, cooked_time, 0, 1, offset + raw_correction - instance->offset,
|
|
offset, dispersion);
|
|
|
|
/* for logging purposes */
|
|
if (!instance->driver->poll)
|
|
instance->driver_polled++;
|
|
|
|
return 1;
|
|
}
|
|
|
|
double
|
|
RCL_GetPrecision(RCL_Instance instance)
|
|
{
|
|
return instance->precision;
|
|
}
|
|
|
|
int
|
|
RCL_GetDriverPoll(RCL_Instance instance)
|
|
{
|
|
return instance->driver_poll;
|
|
}
|
|
|
|
static int
|
|
valid_sample_time(RCL_Instance instance, struct timespec *sample_time)
|
|
{
|
|
struct timespec now;
|
|
double diff;
|
|
|
|
LCL_ReadCookedTime(&now, NULL);
|
|
diff = UTI_DiffTimespecsToDouble(&now, sample_time);
|
|
|
|
if (diff < 0.0 || diff > UTI_Log2ToDouble(instance->poll + 1)) {
|
|
DEBUG_LOG("%s refclock sample time %s not valid age=%.6f",
|
|
UTI_RefidToString(instance->ref_id),
|
|
UTI_TimespecToString(sample_time), diff);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
pps_stratum(RCL_Instance instance, struct timespec *ts)
|
|
{
|
|
struct timespec ref_time;
|
|
int is_synchronised, stratum;
|
|
unsigned int i;
|
|
double root_delay, root_dispersion;
|
|
NTP_Leap leap;
|
|
uint32_t ref_id;
|
|
RCL_Instance refclock;
|
|
|
|
REF_GetReferenceParams(ts, &is_synchronised, &leap, &stratum,
|
|
&ref_id, &ref_time, &root_delay, &root_dispersion);
|
|
|
|
/* Don't change our stratum if the local reference is active
|
|
or this is the current source */
|
|
if (ref_id == instance->ref_id ||
|
|
(!is_synchronised && leap != LEAP_Unsynchronised))
|
|
return stratum - 1;
|
|
|
|
/* Or the current source is another PPS refclock */
|
|
for (i = 0; i < ARR_GetSize(refclocks); i++) {
|
|
refclock = get_refclock(i);
|
|
if (refclock->ref_id == ref_id &&
|
|
refclock->pps_active && refclock->lock_ref == -1)
|
|
return stratum - 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
get_local_stats(RCL_Instance inst, struct timespec *ref, double *freq, double *offset)
|
|
{
|
|
double offset_sd, freq_sd, skew, root_delay, root_disp;
|
|
SST_Stats stats = SRC_GetSourcestats(inst->source);
|
|
|
|
if (SST_Samples(stats) < SST_GetMinSamples(stats)) {
|
|
UTI_ZeroTimespec(ref);
|
|
return;
|
|
}
|
|
|
|
SST_GetTrackingData(stats, ref, offset, &offset_sd, freq, &freq_sd,
|
|
&skew, &root_delay, &root_disp);
|
|
}
|
|
|
|
static void
|
|
follow_local(RCL_Instance inst, struct timespec *prev_ref_time, double prev_freq,
|
|
double prev_offset)
|
|
{
|
|
SST_Stats stats = SRC_GetSourcestats(inst->source);
|
|
double freq, dfreq, offset, doffset, elapsed;
|
|
struct timespec now, ref_time;
|
|
|
|
get_local_stats(inst, &ref_time, &freq, &offset);
|
|
|
|
if (UTI_IsZeroTimespec(prev_ref_time) || UTI_IsZeroTimespec(&ref_time))
|
|
return;
|
|
|
|
dfreq = (freq - prev_freq) / (1.0 - prev_freq);
|
|
elapsed = UTI_DiffTimespecsToDouble(&ref_time, prev_ref_time);
|
|
doffset = offset - elapsed * prev_freq - prev_offset;
|
|
|
|
if (!REF_AdjustReference(doffset, dfreq))
|
|
return;
|
|
|
|
LCL_ReadCookedTime(&now, NULL);
|
|
SST_SlewSamples(stats, &now, dfreq, doffset);
|
|
SPF_SlewSamples(inst->filter, &now, dfreq, doffset);
|
|
|
|
/* Keep the offset close to zero to not lose precision */
|
|
if (fabs(offset) >= 1.0) {
|
|
SST_CorrectOffset(stats, -round(offset));
|
|
SPF_CorrectOffset(inst->filter, -round(offset));
|
|
}
|
|
}
|
|
|
|
static void
|
|
poll_timeout(void *arg)
|
|
{
|
|
NTP_Sample sample;
|
|
int poll, stratum;
|
|
|
|
RCL_Instance inst = (RCL_Instance)arg;
|
|
|
|
poll = inst->poll;
|
|
|
|
if (inst->driver->poll) {
|
|
poll = inst->driver_poll;
|
|
inst->driver->poll(inst);
|
|
inst->driver_polled++;
|
|
}
|
|
|
|
if (!(inst->driver->poll && inst->driver_polled < (1 << (inst->poll - inst->driver_poll)))) {
|
|
inst->driver_polled = 0;
|
|
|
|
if (SPF_GetFilteredSample(inst->filter, &sample)) {
|
|
double local_freq, local_offset;
|
|
struct timespec local_ref_time;
|
|
|
|
/* Handle special case when PPS is used with the local reference */
|
|
if (inst->pps_active && inst->lock_ref == -1)
|
|
stratum = pps_stratum(inst, &sample.time);
|
|
else
|
|
stratum = inst->stratum;
|
|
|
|
if (inst->local) {
|
|
get_local_stats(inst, &local_ref_time, &local_freq, &local_offset);
|
|
inst->leap_status = LEAP_Unsynchronised;
|
|
}
|
|
|
|
SRC_UpdateReachability(inst->source, 1);
|
|
SRC_UpdateStatus(inst->source, stratum, inst->leap_status);
|
|
SRC_AccumulateSample(inst->source, &sample);
|
|
SRC_SelectSource(inst->source);
|
|
|
|
if (inst->local)
|
|
follow_local(inst, &local_ref_time, local_freq, local_offset);
|
|
|
|
log_sample(inst, &sample.time, 1, 0, 0.0, sample.offset, sample.peer_dispersion);
|
|
} else {
|
|
SRC_UpdateReachability(inst->source, 0);
|
|
}
|
|
}
|
|
|
|
inst->timeout_id = SCH_AddTimeoutByDelay(UTI_Log2ToDouble(poll), poll_timeout, arg);
|
|
}
|
|
|
|
static void
|
|
slew_samples(struct timespec *raw, struct timespec *cooked, double dfreq,
|
|
double doffset, LCL_ChangeType change_type, void *anything)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARR_GetSize(refclocks); i++) {
|
|
if (change_type == LCL_ChangeUnknownStep)
|
|
SPF_DropSamples(get_refclock(i)->filter);
|
|
else
|
|
SPF_SlewSamples(get_refclock(i)->filter, cooked, dfreq, doffset);
|
|
}
|
|
}
|
|
|
|
static void
|
|
add_dispersion(double dispersion, void *anything)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARR_GetSize(refclocks); i++)
|
|
SPF_AddDispersion(get_refclock(i)->filter, dispersion);
|
|
}
|
|
|
|
static void
|
|
log_sample(RCL_Instance instance, struct timespec *sample_time, int filtered, int pulse, double raw_offset, double cooked_offset, double dispersion)
|
|
{
|
|
char sync_stats[4] = {'N', '+', '-', '?'};
|
|
|
|
if (logfileid == -1)
|
|
return;
|
|
|
|
if (!filtered) {
|
|
LOG_FileWrite(logfileid, "%s.%06d %-5s %3d %1c %1d %13.6e %13.6e %10.3e",
|
|
UTI_TimeToLogForm(sample_time->tv_sec),
|
|
(int)sample_time->tv_nsec / 1000,
|
|
UTI_RefidToString(instance->ref_id),
|
|
instance->driver_polled,
|
|
sync_stats[instance->leap_status],
|
|
pulse,
|
|
raw_offset,
|
|
cooked_offset,
|
|
dispersion);
|
|
} else {
|
|
LOG_FileWrite(logfileid, "%s.%06d %-5s - %1c - - %13.6e %10.3e",
|
|
UTI_TimeToLogForm(sample_time->tv_sec),
|
|
(int)sample_time->tv_nsec / 1000,
|
|
UTI_RefidToString(instance->ref_id),
|
|
sync_stats[instance->leap_status],
|
|
cooked_offset,
|
|
dispersion);
|
|
}
|
|
}
|