]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
Add PHC refclock driver
authorMiroslav Lichvar <mlichvar@redhat.com>
Wed, 14 Aug 2013 16:13:39 +0000 (18:13 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Wed, 14 Aug 2013 16:52:23 +0000 (18:52 +0200)
Implement a driver which allows using PTP hardware clock (PHC) as a
reference clock. It uses the PTP_SYS_OFFSET ioctl or clock_gettime()
to measure the offset between the PTP clock and the system clock. Ten
readings are made for every driver poll and the fastest one is returned.

As PHCs are typically kept in TAI instead of UTC, it's necessary to set
the TAI/UTC offset manually by the offset option. This could be improved
by obtaining the offset automatically from the right/UTC timezone.

Makefile.in
chrony.texi.in
configure
refclock.c
refclock_phc.c [new file with mode: 0644]

index ecb036c8003f64592da1e3c757efae81b871e6c0..58af97b054fb28dee6d3cf95e60020a11424916d 100644 (file)
@@ -44,8 +44,8 @@ OBJS = util.o sched.o regress.o local.o \
        logging.o conf.o cmdmon.o keys.o \
        nameserv.o acquire.o manual.o addrfilt.o \
        cmdparse.o mkdirpp.o rtc.o pktlength.o clientlog.o \
-       broadcast.o refclock.o refclock_shm.o refclock_sock.o \
-       refclock_pps.o tempcomp.o $(HASH_OBJ)
+       broadcast.o refclock.o refclock_phc.o refclock_pps.o \
+       refclock_shm.o refclock_sock.o tempcomp.o $(HASH_OBJ)
 
 EXTRA_OBJS=@EXTRA_OBJECTS@
 
index d7ff1ad354f41804730007b9c8a935ce3760366e..e4558105707cf0ba4e67d31360a3c225dc63f909 100644 (file)
@@ -2462,7 +2462,7 @@ can function as a stratum 1 server.  They are specified by the
 @code{refclock} directive.  It has two mandatory parameters, a refclock driver
 name and a driver specific parameter.
 
-There are currently three drivers included:
+There are currently four drivers included:
 
 @table @code
 @item PPS
@@ -2512,6 +2512,17 @@ protocol.  The path where the socket should be created is described in the
 refclock SOCK /var/run/chrony.ttyS0.sock
 @end example
 
+@item PHC
+PTP hardware clock (PHC) driver.  The parameter is the path to the device of
+the PTP clock, which can be synchronised by a PTP daemon (e.g. @code{ptp4l}
+from the @uref{http://linuxptp.sourceforge.net/, Linux PTP project}.  The PTP
+clocks are typically kept in TAI instead of UTC.  The @code{offset} option can
+be used to compensate for the current UTC/TAI offset.  For example:
+
+@example
+refclock PHC /dev/ptp0 poll 3 dpoll -2 offset -35
+@end example
+
 @end table
 
 The @code{refclock} command also supports a number of subfields (which
index 3c9559875e9546575fd615aa7c738205a9306abb..e508f597970278d44b369a45cf64ac98e6e49e94 100755 (executable)
--- a/configure
+++ b/configure
@@ -108,6 +108,7 @@ For better control, use the options below.
   --without-nss          Don't use NSS even if it is available
   --without-tomcrypt     Don't use libtomcrypt even if it is available
   --disable-ipv6         Disable IPv6 support
+  --disable-phc          Disable PHC support
   --disable-pps          Disable PPS API support
   --disable-rtc          Don't include RTC even on Linux
   --disable-linuxcaps    Disable Linux capabilities support
@@ -184,6 +185,8 @@ readline_lib=""
 readline_inc=""
 ncurses_lib=""
 feat_ipv6=1
+feat_phc=1
+try_phc=0
 feat_pps=1
 try_setsched=0
 try_lockmem=0
@@ -253,6 +256,9 @@ do
     --disable-ipv6)
       feat_ipv6=0
     ;;
+    --disable-phc)
+      feat_phc=0
+    ;;
     --disable-pps)
       feat_pps=0
     ;;
@@ -315,6 +321,7 @@ case $SYSTEM in
         try_rtc=1
         try_setsched=1
         try_lockmem=1
+        try_phc=1
         add_def LINUX
         echo "Configuring for " $SYSTEM
         if [ "${MACHINE}" = "alpha" ]; then
@@ -443,6 +450,13 @@ then
     add_def FEAT_RTC
 fi
 
+if [ $feat_phc = "1" ] && [ $try_phc = "1" ] && \
+  test_code '<linux/ptp_clock.h>' 'sys/ioctl.h linux/ptp_clock.h' '' '' \
+    'ioctl(1, PTP_CLOCK_GETCAPS, 0);'
+then
+    add_def FEAT_PHC
+fi
+
 if [ $try_setsched = "1" ] && \
   test_code \
     'sched_setscheduler()' \
index 3e472844312a3bf4c6278b29815c55bdc716556d..89319d5e0006c6e1d65b0a08c3659accb55d875c 100644 (file)
@@ -42,6 +42,7 @@
 extern RefclockDriver RCL_SHM_driver;
 extern RefclockDriver RCL_SOCK_driver;
 extern RefclockDriver RCL_PPS_driver;
+extern RefclockDriver RCL_PHC_driver;
 
 struct FilterSample {
   double offset;
@@ -166,6 +167,9 @@ RCL_AddRefclock(RefclockParameters *params)
     inst->driver = &RCL_PPS_driver;
     inst->precision = 1e-9;
     pps_source = 1;
+  } else if (strcmp(params->driver_name, "PHC") == 0) {
+    inst->driver = &RCL_PHC_driver;
+    inst->precision = 1e-9;
   } else {
     LOG_FATAL(LOGF_Refclock, "unknown refclock driver %s", params->driver_name);
     return 0;
diff --git a/refclock_phc.c b/refclock_phc.c
new file mode 100644 (file)
index 0000000..a1bfd3d
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+  chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar  2013
+ * 
+ * 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.
+ * 
+ **********************************************************************
+
+  =======================================================================
+
+  PTP hardware clock (PHC) refclock driver.
+
+  */
+
+#include "config.h"
+
+#include "refclock.h"
+
+#ifdef FEAT_PHC
+
+#include "sysincl.h"
+
+#include <linux/ptp_clock.h>
+
+#include "refclock.h"
+#include "logging.h"
+#include "util.h"
+
+/* From linux/include/linux/posix-timers.h */
+#define CPUCLOCK_MAX            3
+#define CLOCKFD                 CPUCLOCK_MAX
+#define CLOCKFD_MASK            (CPUCLOCK_PERTHREAD_MASK|CPUCLOCK_CLOCK_MASK)
+
+#define FD_TO_CLOCKID(fd)       ((~(clockid_t) (fd) << 3) | CLOCKFD)
+
+#define NUM_READINGS 10
+
+static int no_sys_offset_ioctl = 0;
+
+struct phc_reading {
+  struct timespec sys_ts1;
+  struct timespec phc_ts;;
+  struct timespec sys_ts2;
+};
+
+static double diff_ts(struct timespec *ts1, struct timespec *ts2)
+{
+  return (ts1->tv_sec - ts2->tv_sec) + (ts1->tv_nsec - ts2->tv_nsec) / 1e9;
+}
+
+static int read_phc_ioctl(struct phc_reading *readings, int phc_fd, int n)
+{
+#ifdef PTP_SYS_OFFSET
+  struct ptp_sys_offset sys_off;
+  int i;
+
+  /* Silence valgrind */
+  memset(&sys_off, 0, sizeof (sys_off));
+
+  sys_off.n_samples = n;
+  if (ioctl(phc_fd, PTP_SYS_OFFSET, &sys_off)) {
+    LOG(LOGS_ERR, LOGF_Refclock, "ioctl(PTP_SYS_OFFSET) failed : %s", strerror(errno));
+    return 0;
+  }
+
+  for (i = 0; i < n; i++) {
+    readings[i].sys_ts1.tv_sec = sys_off.ts[i * 2].sec;
+    readings[i].sys_ts1.tv_nsec = sys_off.ts[i * 2].nsec;
+    readings[i].phc_ts.tv_sec = sys_off.ts[i * 2 + 1].sec;
+    readings[i].phc_ts.tv_nsec = sys_off.ts[i * 2 + 1].nsec;
+    readings[i].sys_ts2.tv_sec = sys_off.ts[i * 2 + 2].sec;
+    readings[i].sys_ts2.tv_nsec = sys_off.ts[i * 2 + 2].nsec;
+  }
+
+  return 1;
+#else
+  /* Not available */
+  return 0;
+#endif
+}
+
+static int read_phc_user(struct phc_reading *readings, int phc_fd, int n)
+{
+  clockid_t phc_id;
+  int i;
+
+  phc_id = FD_TO_CLOCKID(phc_fd);
+
+  for (i = 0; i < n; i++) {
+    if (clock_gettime(CLOCK_REALTIME, &readings[i].sys_ts1) ||
+        clock_gettime(phc_id, &readings[i].phc_ts) ||
+        clock_gettime(CLOCK_REALTIME, &readings[i].sys_ts2)) {
+      LOG(LOGS_ERR, LOGF_Refclock, "clock_gettime() failed : %s", strerror(errno));
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int phc_initialise(RCL_Instance instance)
+{
+  struct ptp_clock_caps caps;
+  int phc_fd;
+  char *path;
+
+  path = RCL_GetDriverParameter(instance);
+  phc_fd = open(path, O_RDONLY);
+  if (phc_fd < 0) {
+    LOG_FATAL(LOGF_Refclock, "open() failed on %s", path);
+    return 0;
+  }
+
+  /* Make sure it is a PHC */
+  if (ioctl(phc_fd, PTP_CLOCK_GETCAPS, &caps)) {
+    LOG_FATAL(LOGF_Refclock, "ioctl(PTP_CLOCK_GETCAPS) failed : %s", strerror(errno));
+    return 0;
+  }
+
+  UTI_FdSetCloexec(phc_fd);
+
+  RCL_SetDriverData(instance, (void *)(long)phc_fd);
+  return 1;
+}
+
+static void phc_finalise(RCL_Instance instance)
+{
+  close((long)RCL_GetDriverData(instance));
+}
+
+static int phc_poll(RCL_Instance instance)
+{
+  struct phc_reading readings[NUM_READINGS];
+  struct timeval tv;
+  double offset = 0.0, delay, best_delay = 0.0;
+  int i, phc_fd, best;
+  phc_fd = (long)RCL_GetDriverData(instance);
+
+  if (!no_sys_offset_ioctl && NUM_READINGS <= PTP_MAX_SAMPLES) {
+    if (!read_phc_ioctl(readings, phc_fd, NUM_READINGS)) {
+      no_sys_offset_ioctl = 1;
+      return 0;
+    }
+  } else {
+    if (!read_phc_user(readings, phc_fd, NUM_READINGS))
+      return 0;
+  }
+
+  /* Find the fastest reading */
+  for (i = 0; i < NUM_READINGS; i++) {
+    delay = diff_ts(&readings[i].sys_ts2, &readings[i].sys_ts1);
+
+    if (!i || best_delay > delay) {
+      best = i;
+      best_delay = delay;
+    }
+  }
+
+  offset = diff_ts(&readings[best].phc_ts, &readings[best].sys_ts2) + best_delay / 2.0;
+  tv.tv_sec = readings[best].sys_ts2.tv_sec;
+  tv.tv_usec = readings[best].sys_ts2.tv_nsec / 1000;
+
+#ifdef TRACEON
+  LOG(LOGS_INFO, LOGF_Refclock, "PHC offset: %+.9f delay: %.9f", offset, best_delay);
+#endif
+
+  return RCL_AddSample(instance, &tv, offset, LEAP_Normal);
+}
+
+RefclockDriver RCL_PHC_driver = {
+  phc_initialise,
+  phc_finalise,
+  phc_poll
+};
+
+#else
+
+RefclockDriver RCL_PHC_driver = { NULL, NULL, NULL };
+
+#endif