]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
sys: introduce generic driver
authorMiroslav Lichvar <mlichvar@redhat.com>
Wed, 14 May 2014 15:06:18 +0000 (17:06 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Tue, 20 May 2014 14:05:42 +0000 (16:05 +0200)
This driver is intended to complete system-specific drivers that don't
have implemented all required driver functionality. Currently, it
implements offset functions working on top of system-specific frequency
functions. Offsets are corrected by changing frequency, similarly to
fast slewing implemented in the Linux driver.

logging.h
sys_generic.c [new file with mode: 0644]
sys_generic.h [new file with mode: 0644]

index 9d7cbfbd9d497ec8c65208dd7d4ce067138aafd8..6f19997b58a26e91b658a04250582540a663518c 100644 (file)
--- a/logging.h
+++ b/logging.h
@@ -81,6 +81,7 @@ typedef enum {
   LOGF_Rtc,
   LOGF_Regress,
   LOGF_Sys,
+  LOGF_SysGeneric,
   LOGF_SysLinux,
   LOGF_SysNetBSD,
   LOGF_SysSolaris,
diff --git a/sys_generic.c b/sys_generic.c
new file mode 100644 (file)
index 0000000..5232b76
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar  2014
+ * 
+ * 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 "local.h"
+#include "localp.h"
+#include "logging.h"
+#include "sched.h"
+#include "util.h"
+
+/* ================================================== */
+
+/* System clock frequency drivers */
+static lcl_ReadFrequencyDriver drv_read_freq;
+static lcl_SetFrequencyDriver drv_set_freq;
+
+/* 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;
+
+/* 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;
+
+/* Maximum allowed slewing frequency */
+#define MAX_SLEW_FREQ (1 / 12.0)
+
+/* Time (raw) of last update of slewing frequency and offset */
+static struct timeval slew_start;
+
+/* Limits for the slew timeout */
+#define MIN_SLEW_TIMEOUT 1.0
+#define MAX_SLEW_TIMEOUT 1.0e4
+
+/* Scheduler timeout ID and flag if the timer is currently running */
+static SCH_TimeoutID slew_timeout_id;
+static int slew_timer_running;
+
+/* 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;
+
+/* ================================================== */
+
+static void handle_end_of_slew(void *anything);
+
+/* ================================================== */
+/* Adjust slew_start on clock step */
+
+static void
+handle_step(struct timeval *raw, struct timeval *cooked, double dfreq,
+            double doffset, int is_step_change, void *anything)
+{
+  if (is_step_change)
+    UTI_AddDoubleToTimeval(&slew_start, -doffset, &slew_start);
+}
+
+/* ================================================== */
+/* End currently running slew and start a new one */
+
+static void
+update_slew(void)
+{
+  struct timeval now, end_of_slew;
+  double old_slew_freq, total_freq, corr_freq, duration;
+
+  /* Remove currently running timeout */
+  if (slew_timer_running)
+    SCH_RemoveTimeout(slew_timeout_id);
+
+  LCL_ReadRawTime(&now);
+
+  /* Adjust the offset register by achieved slew */
+  UTI_DiffTimevalsToDouble(&duration, &now, &slew_start);
+  offset_register -= slew_freq * duration;
+
+  /* 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_SLEW_FREQ)
+    corr_freq = -MAX_SLEW_FREQ;
+  else if (corr_freq > MAX_SLEW_FREQ)
+    corr_freq = MAX_SLEW_FREQ;
+
+  /* Get the new real frequency and clamp it */
+  total_freq = base_freq + corr_freq * (1.0e6 - base_freq);
+  if (total_freq > max_freq)
+    total_freq = max_freq;
+  else if (total_freq < -max_freq)
+    total_freq = -max_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);
+  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), use maximum timeout and try again
+     on the next update. */
+  if (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_AddDoubleToTimeval(&now, duration, &end_of_slew);
+  slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL);
+
+  slew_start = now;
+  slew_timer_running = 1;
+
+  DEBUG_LOG(LOGF_SysGeneric, "slew offset=%e corr_rate=%e base_freq=%f total_freq=%f slew_freq=%e duration=%f",
+      offset_register, correction_rate, base_freq, total_freq, slew_freq, duration);
+}
+
+/* ================================================== */
+
+static void
+handle_end_of_slew(void *anything)
+{
+  slew_timer_running = 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 timeval *raw,
+               double *corr, double *err)
+{
+  double duration;
+
+  UTI_DiffTimevalsToDouble(&duration, raw, &slew_start);
+
+  *corr = slew_freq * duration - offset_register;
+  if (err)
+    *err = fabs(duration) <= max_freq_change_delay ? slew_error : 0.0;
+}
+
+/* ================================================== */
+
+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,
+                               lcl_SetLeapDriver sys_set_leap)
+{
+  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;
+
+  base_freq = (*drv_read_freq)();
+  slew_freq = 0.0;
+  offset_register = 0.0;
+
+  lcl_RegisterSystemDrivers(read_frequency, set_frequency,
+                            accrue_offset, sys_apply_step_offset,
+                            offset_convert, sys_set_leap);
+
+  LCL_AddParameterChangeHandler(handle_step, NULL);
+}
+
+/* ================================================== */
+
+void
+SYS_Generic_Finalise(void)
+{
+  /* Must *NOT* leave a slew running - clock could drift way off
+     if the daemon is not restarted */
+  if (slew_timer_running) {
+    SCH_RemoveTimeout(slew_timeout_id);
+    slew_timer_running = 0;
+  }
+
+  (*drv_set_freq)(base_freq);
+}
+
+/* ================================================== */
diff --git a/sys_generic.h b/sys_generic.h
new file mode 100644 (file)
index 0000000..45c14e2
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar  2014
+ * 
+ * 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.
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  Header file for generic driver
+  */
+
+#ifndef GOT_SYS_GENERIC_H
+#define GOT_SYS_GENERIC_H
+
+#include "localp.h"
+
+/* Register a completed driver that implements offset functions on top of
+   provided frequency functions */
+extern 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,
+                                           lcl_SetLeapDriver sys_set_leap);
+
+extern void SYS_Generic_Finalise(void);
+
+#endif  /* GOT_SYS_GENERIC_H */