]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
refclock: add local option
authorMiroslav Lichvar <mlichvar@redhat.com>
Tue, 22 Feb 2022 10:24:00 +0000 (11:24 +0100)
committerMiroslav Lichvar <mlichvar@redhat.com>
Wed, 23 Feb 2022 13:43:39 +0000 (14:43 +0100)
Add "local" option to specify that the reference clock is an
unsynchronized clock which is more stable than the system clock (e.g.
TCXO, OCXO, or atomic clock) and it should be used as a local standard
to stabilize the system clock.

Handle the local refclock as a PPS refclock locked to itself which gives
the unsynchronized status to be ignored in the source selection. Wait
for the refclock to get at least minsamples samples and adjust the clock
directly to follow changes in the refclock's sourcestats frequency and
offset.

There should be at most one refclock specified with this option.

conf.c
doc/chrony.conf.adoc
refclock.c
refclock.h
test/simulation/106-refclock

diff --git a/conf.c b/conf.c
index 06ea1e51f54486b3b4f2174bd4ee392f2fde388d..199b63c803721d80a10ed3c3e792cf9479cdcb04 100644 (file)
--- a/conf.c
+++ b/conf.c
@@ -861,7 +861,7 @@ static void
 parse_refclock(char *line)
 {
   int n, poll, dpoll, filter_length, pps_rate, min_samples, max_samples, sel_options;
-  int max_lock_age, pps_forced, stratum, tai;
+  int local, max_lock_age, pps_forced, stratum, tai;
   uint32_t ref_id, lock_ref_id;
   double offset, delay, precision, max_dispersion, pulse_width;
   char *p, *cmd, *name, *param;
@@ -871,6 +871,7 @@ parse_refclock(char *line)
   poll = 4;
   dpoll = 0;
   filter_length = 64;
+  local = 0;
   pps_forced = 0;
   pps_rate = 0;
   min_samples = SRC_DEFAULT_MINSAMPLES;
@@ -929,6 +930,9 @@ parse_refclock(char *line)
       if (sscanf(line, "%d%n", &filter_length, &n) != 1) {
         break;
       }
+    } else if (!strcasecmp(cmd, "local")) {
+      n = 0;
+      local = 1;
     } else if (!strcasecmp(cmd, "rate")) {
       if (sscanf(line, "%d%n", &pps_rate, &n) != 1)
         break;
@@ -995,6 +999,7 @@ parse_refclock(char *line)
   refclock->driver_poll = dpoll;
   refclock->poll = poll;
   refclock->filter_length = filter_length;
+  refclock->local = local;
   refclock->pps_forced = pps_forced;
   refclock->pps_rate = pps_rate;
   refclock->min_samples = min_samples;
index 21d2ce6a5eaefe34bf7db815b4c4081910334f56..6cecc1b34e5207f24244e49f5da5754f557a6373 100644 (file)
@@ -624,6 +624,13 @@ and that *chronyd* should correct its offset by the current TAI-UTC offset. The
 <<leapsectz,*leapsectz*>> directive must be used with this option and the
 database must be kept up to date in order for this correction to work as
 expected. This option does not make sense with PPS refclocks.
+*local*:::
+This option specifies that the reference clock is an unsynchronised clock which
+is more stable than the system clock (e.g. TCXO, OCXO, or atomic clock) and
+it should be used as a local standard to stabilise the system clock. The
+refclock will bypass the source selection. There should be at most one refclock
+specified with this option and it should have the shortest polling interval
+among all configured sources.
 *minsamples* _samples_:::
 Set the minimum number of samples kept for this source. This overrides the
 <<minsamples,*minsamples*>> directive.
index 968caa74b04f7f7a5d892d91090e705bf4245d8e..f0e2f106f6d206657c0e602571410d60e0a47a6a 100644 (file)
@@ -79,6 +79,7 @@ struct RCL_Instance_Record {
   int driver_polled;
   int poll;
   int leap_status;
+  int local;
   int pps_forced;
   int pps_rate;
   int pps_active;
@@ -190,6 +191,7 @@ RCL_AddRefclock(RefclockParameters *params)
   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;
@@ -231,6 +233,12 @@ RCL_AddRefclock(RefclockParameters *params)
     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;
+  }
+
   if (inst->driver->poll) {
     int max_samples;
 
@@ -558,8 +566,16 @@ RCL_AddCookedPulse(RCL_Instance instance, struct timespec *cooked_time,
     lock_refclock = get_refclock(instance->lock_ref);
 
     if (!SPF_GetLastSample(lock_refclock->filter, &ref_sample)) {
-      DEBUG_LOG("refclock pulse ignored no ref sample");
-      return 0;
+      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);
@@ -693,6 +709,46 @@ pps_stratum(RCL_Instance instance, struct timespec *ts)
   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);
+}
+
 static void
 poll_timeout(void *arg)
 {
@@ -713,17 +769,28 @@ poll_timeout(void *arg)
     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);
index 69a015238f48eaacb93a5f35307c20415ef9acf2..985f0a83f95f1ec5f11cbaa5b28d7e0aff094259 100644 (file)
@@ -37,6 +37,7 @@ typedef struct {
   int driver_poll;
   int poll;
   int filter_length;
+  int local;
   int pps_forced;
   int pps_rate;
   int min_samples;
index c8ccb724a0d0c750aa9f757710c0458a3df6fef6..26adac1b520ed7a480c3a9ae3540b651db7da29a 100755 (executable)
@@ -106,4 +106,31 @@ Root delay      : 0\.000000001 seconds
        rm -f tmp/refclocks.log
 fi
 
+refclock_offset="(+ 0.399 (sum 1e-3))"
+refclock_jitter=1e-6
+servers=1
+freq_offset="(* 1e-4 (sine 1000))"
+base_delay="(* -1.0 (equal 0.1 (min time 5000) 5000))"
+client_server_options="minpoll 4 maxpoll 4 filter 5 minsamples 64"
+client_conf="
+refclock PHC /dev/ptp0 local poll 2
+logdir tmp
+log refclocks tracking"
+chronyc_conf=""
+limit=10000
+max_sync_time=5000
+time_max_limit=1e-3
+time_rms_limit=5e-4
+freq_max_limit=2e-5
+freq_rms_limit=5e-6
+
+run_test || test_fail
+check_chronyd_exit || test_fail
+check_sync || test_fail
+
+check_file_messages "20.* PHC0 .* [0-9] ? " 9999 10001 refclocks.log || test_fail
+check_file_messages "20.* PHC0 .* - ? " 2499 2501 refclocks.log || test_fail
+check_file_messages "20.* PHC0 " 0 0 tracking.log || test_fail
+rm -f tmp/refclocks.log tmp/tracking.log
+
 test_pass