diff --git a/Makefile.in b/Makefile.in index ecb036c..58af97b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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@ diff --git a/chrony.texi.in b/chrony.texi.in index d7ff1ad..e455810 100644 --- a/chrony.texi.in +++ b/chrony.texi.in @@ -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 diff --git a/configure b/configure index 3c95598..e508f59 100755 --- 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 '' '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()' \ diff --git a/refclock.c b/refclock.c index 3e47284..89319d5 100644 --- a/refclock.c +++ b/refclock.c @@ -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 index 0000000..a1bfd3d --- /dev/null +++ b/refclock_phc.c @@ -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 + +#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