diff --git a/Makefile.in b/Makefile.in index 308ff25..57ba2e4 100644 --- a/Makefile.in +++ b/Makefile.in @@ -41,7 +41,8 @@ OBJS = util.o sched.o regress.o local.o \ logging.o conf.o cmdmon.o md5.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 + broadcast.o refclock.o refclock_shm.o refclock_sock.o \ + refclock_pps.o EXTRA_OBJS=@EXTRA_OBJECTS@ diff --git a/conf.c b/conf.c index 050bef8..73e589b 100644 --- a/conf.c +++ b/conf.c @@ -429,7 +429,7 @@ parse_peer(const char *line) static void parse_refclock(const char *line) { - int i, n, poll, dpoll, filter_length; + int i, n, poll, dpoll, filter_length, pps_rate; unsigned long ref_id; double offset, delay; const char *tmp; @@ -443,6 +443,7 @@ parse_refclock(const char *line) poll = 4; dpoll = 0; filter_length = 15; + pps_rate = 0; offset = 0.0; delay = 1e-9; ref_id = 0; @@ -486,6 +487,9 @@ parse_refclock(const char *line) if (sscanf(line, "%d%n", &filter_length, &n) != 1) { break; } + } else if (!strncasecmp(cmd, "rate", 4)) { + if (sscanf(line, "%d%n", &pps_rate, &n) != 1) + break; } else if (!strncasecmp(cmd, "offset", 6)) { if (sscanf(line, "%lf%n", &offset, &n) != 1) break; @@ -504,6 +508,7 @@ parse_refclock(const char *line) refclock_sources[i].driver_poll = dpoll; refclock_sources[i].poll = poll; refclock_sources[i].filter_length = filter_length; + refclock_sources[i].pps_rate = pps_rate; refclock_sources[i].offset = offset; refclock_sources[i].delay = delay; refclock_sources[i].ref_id = ref_id; diff --git a/configure b/configure index d86efa1..d9f9f8a 100755 --- a/configure +++ b/configure @@ -132,6 +132,30 @@ EOF echo $result } #}}} +#{{{ test_for_ppsapi +test_for_ppsapi () { + cat >docheck.c < +int main(int argc, char **argv) { + pps_handle_t h; + pps_info_t i; + struct timespec ts; + return time_pps_fetch(&h, PPS_TSFMT_TSPEC, &i, &ts); +} +EOF + + ${MYCC} ${MYCFLAGS} -c -o docheck.o docheck.c >/dev/null 2>&1 + if [ $? -eq 0 ] + then + result=0 + else + result=1 + fi + + rm -f docheck.c docheck.o + echo $result +} +#}}} #{{{ usage usage () { cat <driver = &RCL_SHM_driver; } else if (strncmp(params->driver_name, "SOCK", 4) == 0) { inst->driver = &RCL_SOCK_driver; + } else if (strncmp(params->driver_name, "PPS", 4) == 0) { + inst->driver = &RCL_PPS_driver; + pps_source = 1; } else { LOG_FATAL(LOGF_Refclock, "unknown refclock driver %s", params->driver_name); return 0; } + if (!inst->driver->init && !inst->driver->poll) { + LOG_FATAL(LOGF_Refclock, "refclock driver %s is not compiled in", params->driver_name); + return 0; + } + inst->data = NULL; inst->driver_parameter = params->driver_parameter; inst->driver_poll = params->driver_poll; @@ -132,11 +146,19 @@ RCL_AddRefclock(RefclockParameters *params) inst->missed_samples = 0; inst->driver_polled = 0; inst->leap_status = 0; + inst->pps_rate = params->pps_rate; inst->offset = params->offset; inst->delay = params->delay; inst->timeout_id = -1; inst->source = NULL; + if (pps_source) { + if (inst->pps_rate < 1) + inst->pps_rate = 1; + } else { + inst->pps_rate = 0; + } + if (inst->driver_poll > inst->poll) inst->driver_poll = inst->poll; @@ -239,6 +261,85 @@ RCL_AddSample(RCL_Instance instance, struct timeval *sample_time, double offset, return 1; } +int +RCL_AddPulse(RCL_Instance instance, struct timeval *pulse_time, double second) +{ + double correction, offset; + struct timeval cooked_time; + int rate; + + struct timeval ref_time; + int is_synchronised, stratum; + double root_delay, root_dispersion, distance; + NTP_Leap leap; + unsigned long ref_id; + + correction = LCL_GetOffsetCorrection(pulse_time); + UTI_AddDoubleToTimeval(pulse_time, correction, &cooked_time); + + rate = instance->pps_rate; + assert(rate > 0); + + /* Ignore the pulse if we are not well synchronized */ + + REF_GetReferenceParams(&cooked_time, &is_synchronised, &leap, &stratum, + &ref_id, &ref_time, &root_delay, &root_dispersion); + distance = fabs(root_delay) / 2 + root_dispersion; + + if (!is_synchronised || distance >= 0.5 / rate) { +#if 0 + LOG(LOGS_INFO, LOGF_Refclock, "refclock pulse dropped second=%.9f sync=%d dist=%.9f", + second, is_synchronised, distance); +#endif + return 0; + } + + offset = -second - correction + 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 0 + LOG(LOGS_INFO, LOGF_Refclock, "refclock pulse second=%.9f offset=%.9f", + second, offset + instance->offset); +#endif + + filter_add_sample(&instance->filter, &cooked_time, offset); + instance->leap_status = LEAP_Normal; + + return 1; +} + +static int +pps_stratum(RCL_Instance instance, struct timeval *tv) +{ + struct timeval ref_time; + int is_synchronised, stratum, i; + double root_delay, root_dispersion; + NTP_Leap leap; + unsigned long ref_id; + + REF_GetReferenceParams(tv, &is_synchronised, &leap, &stratum, + &ref_id, &ref_time, &root_delay, &root_dispersion); + + /* Don't change our stratum if local stratum is active + or this is the current source */ + if (ref_id == instance->ref_id || REF_IsLocalActive()) + return stratum - 1; + + /* Or the current source is another PPS refclock */ + for (i = 0; i < n_sources; i++) { + if (refclocks[i].ref_id == ref_id && refclocks[i].pps_rate) + return stratum - 1; + } + + return 0; +} + static void poll_timeout(void *arg) { @@ -258,7 +359,7 @@ poll_timeout(void *arg) if (!(inst->driver->poll && inst->driver_polled < (1 << (inst->poll - inst->driver_poll)))) { double offset, dispersion; struct timeval sample_time; - int sample_ok; + int sample_ok, stratum; sample_ok = filter_get_sample(&inst->filter, &sample_time, &offset, &dispersion); filter_reset(&inst->filter); @@ -269,9 +370,16 @@ poll_timeout(void *arg) LOG(LOGS_INFO, LOGF_Refclock, "refclock filtered sample: offset=%.9f dispersion=%.9f [%s]", offset, dispersion, UTI_TimevalToString(&sample_time)); #endif + + if (inst->pps_rate) + /* Handle special case when PPS is used with local stratum */ + stratum = pps_stratum(inst, &sample_time); + else + stratum = 0; + SRC_SetReachable(inst->source); SRC_AccumulateSample(inst->source, &sample_time, offset, - inst->delay, dispersion, inst->delay, dispersion, 0, inst->leap_status); + inst->delay, dispersion, inst->delay, dispersion, stratum, inst->leap_status); inst->missed_samples = 0; } else { inst->missed_samples++; diff --git a/refclock.h b/refclock.h index d409886..1990095 100644 --- a/refclock.h +++ b/refclock.h @@ -37,6 +37,7 @@ typedef struct { int driver_poll; int poll; int filter_length; + int pps_rate; unsigned long ref_id; double offset; double delay; @@ -62,5 +63,6 @@ extern void RCL_SetDriverData(RCL_Instance instance, void *data); extern void *RCL_GetDriverData(RCL_Instance instance); extern char *RCL_GetDriverParameter(RCL_Instance instance); extern int RCL_AddSample(RCL_Instance instance, struct timeval *sample_time, double offset, NTP_Leap leap_status); +extern int RCL_AddPulse(RCL_Instance instance, struct timeval *pulse_time, double second); #endif diff --git a/refclock_pps.c b/refclock_pps.c new file mode 100644 index 0000000..d701b18 --- /dev/null +++ b/refclock_pps.c @@ -0,0 +1,167 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2009 + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * + ********************************************************************** + + ======================================================================= + + PPSAPI refclock driver. + + */ + +#include "refclock.h" + +#if HAVE_PPSAPI + +#include + +#include "logging.h" +#include "memory.h" +#include "util.h" + +struct pps_instance { + pps_handle_t handle; + unsigned long last_seq; + int edge_clear; +}; + +static int pps_initialise(RCL_Instance instance) { + pps_handle_t handle; + pps_params_t params; + struct pps_instance *pps; + int fd, edge_clear, mode; + char *path, *s; + + path = RCL_GetDriverParameter(instance); + + edge_clear = 0; + if ((s = strrchr(path, ':')) != NULL) { + *s = '\0'; + edge_clear = atoi(s + 1); + } + + fd = open(path, O_RDWR); + if (fd < 0) { + LOG_FATAL(LOGF_Refclock, "open() failed on %s", path); + return 0; + } + + if (time_pps_create(fd, &handle) < 0) { + LOG_FATAL(LOGF_Refclock, "time_pps_create() failed on %s", path); + return 0; + } + + if (time_pps_getcap(handle, &mode) < 0) { + LOG_FATAL(LOGF_Refclock, "time_pps_getcap() failed on %s", path); + return 0; + } + + if (time_pps_getparams(handle, ¶ms) < 0) { + LOG_FATAL(LOGF_Refclock, "time_pps_getparams() failed on %s", path); + return 0; + } + + if (!edge_clear) { + if (!(mode & PPS_CAPTUREASSERT)) { + LOG_FATAL(LOGF_Refclock, "CAPTUREASSERT not supported on %s", path); + return 0; + } + params.mode |= PPS_CAPTUREASSERT; + params.mode &= ~PPS_CAPTURECLEAR; + } else { + if (!(mode & PPS_CAPTURECLEAR)) { + LOG_FATAL(LOGF_Refclock, "CAPTURECLEAR not supported on %s", path); + return 0; + } + params.mode |= PPS_CAPTURECLEAR; + params.mode &= ~PPS_CAPTUREASSERT; + } + + if (time_pps_setparams(handle, ¶ms) < 0) { + LOG_FATAL(LOGF_Refclock, "time_pps_setparams() failed on %s", path); + return 0; + } + + pps = MallocNew(struct pps_instance); + pps->handle = handle; + pps->last_seq = 0; + pps->edge_clear = edge_clear; + + RCL_SetDriverData(instance, pps); + return 1; +} + +static void pps_finalise(RCL_Instance instance) +{ + struct pps_instance *pps; + + pps = (struct pps_instance *)RCL_GetDriverData(instance); + time_pps_destroy(pps->handle); + Free(pps); +} + +static int pps_poll(RCL_Instance instance) +{ + struct pps_instance *pps; + struct timespec ts; + struct timeval tv; + pps_info_t pps_info; + unsigned long seq; + + pps = (struct pps_instance *)RCL_GetDriverData(instance); + + ts.tv_sec = 0; + ts.tv_nsec = 0; + + if (time_pps_fetch(pps->handle, PPS_TSFMT_TSPEC, &pps_info, &ts) < 0) { +#if 0 + LOG(LOGS_INFO, LOGF_Refclock, "time_pps_fetch error"); +#endif + return 0; + } + + if (!pps->edge_clear) { + seq = pps_info.assert_sequence; + ts = pps_info.assert_timestamp; + } else { + seq = pps_info.clear_sequence; + ts = pps_info.clear_timestamp; + } + + if (seq == pps->last_seq || (ts.tv_sec == 0 && ts.tv_nsec == 0)) { + return 0; + } + + tv.tv_sec = ts.tv_sec; + tv.tv_usec = ts.tv_nsec / 1000; + + return RCL_AddPulse(instance, &tv, ts.tv_nsec / 1e9); +} + +RefclockDriver RCL_PPS_driver = { + pps_initialise, + pps_finalise, + pps_poll +}; + +#else + +RefclockDriver RCL_PPS_driver = { NULL, NULL, NULL }; + +#endif diff --git a/reference.c b/reference.c index 25e9cf6..e5c434b 100644 --- a/reference.c +++ b/reference.c @@ -695,6 +695,14 @@ REF_DisableLocal(void) /* ================================================== */ +int +REF_IsLocalActive(void) +{ + return !are_we_synchronised && enable_local_stratum; +} + +/* ================================================== */ + void REF_GetTrackingReport(RPT_TrackingReport *rep) { diff --git a/reference.h b/reference.h index cffadb9..b4458eb 100644 --- a/reference.h +++ b/reference.h @@ -140,6 +140,7 @@ extern void REF_ModifyMaxupdateskew(double new_max_update_skew); extern void REF_EnableLocal(int stratum); extern void REF_DisableLocal(void); +extern int REF_IsLocalActive(void); extern void REF_GetTrackingReport(RPT_TrackingReport *rep);