summaryrefslogtreecommitdiff
path: root/ntpd/refclock_atom.c
diff options
context:
space:
mode:
Diffstat (limited to 'ntpd/refclock_atom.c')
-rw-r--r--ntpd/refclock_atom.c504
1 files changed, 504 insertions, 0 deletions
diff --git a/ntpd/refclock_atom.c b/ntpd/refclock_atom.c
new file mode 100644
index 000000000000..51153aeb5f1a
--- /dev/null
+++ b/ntpd/refclock_atom.c
@@ -0,0 +1,504 @@
+
+/*
+ * refclock_atom - clock driver for 1-pps signals
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+
+#include "ntpd.h"
+#include "ntp_io.h"
+#include "ntp_unixtime.h"
+#include "ntp_refclock.h"
+#include "ntp_stdlib.h"
+
+#if defined(REFCLOCK) && defined(CLOCK_ATOM)
+
+#ifdef HAVE_PPSAPI
+# ifdef HAVE_TIMEPPS_H
+# include <timepps.h>
+# else
+# ifdef HAVE_SYS_TIMEPPS_H
+# include <sys/timepps.h>
+# endif
+# endif
+#endif /* HAVE_PPSAPI */
+
+/*
+ * This driver furnishes an interface for pulse-per-second (PPS) signals
+ * produced by a cesium clock, timing receiver or related equipment. It
+ * can be used to remove accumulated jitter and retime a secondary
+ * server when synchronized to a primary server over a congested, wide-
+ * area network and before redistributing the time to local clients.
+ *
+ * Before this driver becomes active, the local clock must be set to
+ * within +-500 ms by another means, such as a radio clock or NTP
+ * itself. There are two ways to connect the PPS signal, normally at TTL
+ * levels, to the computer. One is to shift to EIA levels and connect to
+ * pin 8 (DCD) of a serial port. This requires a level converter and
+ * may require a one-shot flipflop to lengthen the pulse. The other is
+ * to connect the PPS signal directly to pin 10 (ACK) of a PC paralell
+ * port. These methods are architecture dependent.
+ *
+ * Both methods require a modified device driver and kernel interface
+ * compatible with the Pulse-per-Second API for Unix-like Operating
+ * Systems, Version 1.0, RFC-2783 (PPSAPI). Implementations are
+ * available for FreeBSD, Linux, SunOS, Solaris and Alpha. However, at
+ * present only the Alpha implementation provides the full generality of
+ * the API with multiple PPS drivers and multiple handles per driver.
+ *
+ * In many configurations a single port is used for the radio timecode
+ * and PPS signal. In order to provide for this configuration and others
+ * involving dedicated multiple serial/parallel ports, the driver first
+ * attempts to open the device /dev/pps%d, where %d is the unit number.
+ * If this fails, the driver attempts to open the device specified by
+ * the pps configuration command. If a port is to be shared, the pps
+ * command must be placed before the radio device(s) and the radio
+ * device(s) must be placed before the PPS driver(s) in the
+ * configuration file.
+ *
+ * This driver normally uses the PLL/FLL clock discipline implemented in
+ * the ntpd code. If kernel support is available, the kernel PLL/FLL
+ * clock discipline is used instead. The default configuration is not to
+ * use the kernel PPS discipline, if present. The kernel PPS discipline
+ * can be enabled using the pps command.
+ *
+ * Fudge Factors
+ *
+ * There are no special fudge factors other than the generic. The fudge
+ * time1 parameter can be used to compensate for miscellaneous device
+ * driver and OS delays.
+ */
+/*
+ * Interface definitions
+ */
+#ifdef HAVE_PPSAPI
+#define DEVICE "/dev/pps%d" /* device name and unit */
+#endif /* HAVE_PPSAPI */
+
+#define PRECISION (-20) /* precision assumed (about 1 us) */
+#define REFID "PPS\0" /* reference ID */
+#define DESCRIPTION "PPS Clock Discipline" /* WRU */
+#define NANOSECOND 1000000000 /* one second (ns) */
+#define RANGEGATE 500000 /* range gate (ns) */
+#define ASTAGE 8 /* filter stages */
+
+static struct peer *pps_peer; /* atom driver for PPS sources */
+
+#ifdef HAVE_PPSAPI
+/*
+ * PPS unit control structure
+ */
+struct ppsunit {
+ struct timespec ts; /* last timestamp */
+ int fddev; /* pps device descriptor */
+ pps_params_t pps_params; /* pps parameters */
+ pps_info_t pps_info; /* last pps data */
+ pps_handle_t handle; /* pps handlebars */
+};
+#endif /* HAVE_PPSAPI */
+
+/*
+ * Function prototypes
+ */
+static int atom_start P((int, struct peer *));
+static void atom_poll P((int, struct peer *));
+#ifdef HAVE_PPSAPI
+static void atom_shutdown P((int, struct peer *));
+static void atom_control P((int, struct refclockstat *, struct
+ refclockstat *, struct peer *));
+static int atom_pps P((struct peer *));
+static int atom_ppsapi P((struct peer *, int, int));
+#endif /* HAVE_PPSAPI */
+
+/*
+ * Transfer vector
+ */
+struct refclock refclock_atom = {
+ atom_start, /* start up driver */
+#ifdef HAVE_PPSAPI
+ atom_shutdown, /* shut down driver */
+#else
+ noentry, /* shut down driver */
+#endif /* HAVE_PPSAPI */
+ atom_poll, /* transmit poll message */
+#ifdef HAVE_PPSAPI
+ atom_control, /* fudge control */
+#else
+ noentry, /* fudge control */
+#endif /* HAVE_PPSAPI */
+ noentry, /* initialize driver */
+ noentry, /* not used (old atom_buginfo) */
+ NOFLAGS /* not used */
+};
+
+
+/*
+ * atom_start - initialize data for processing
+ */
+static int
+atom_start(
+ int unit, /* unit number (not used) */
+ struct peer *peer /* peer structure pointer */
+ )
+{
+ struct refclockproc *pp;
+#ifdef HAVE_PPSAPI
+ register struct ppsunit *up;
+ char device[80];
+#endif /* HAVE_PPSAPI */
+
+ /*
+ * Allocate and initialize unit structure
+ */
+ pps_peer = peer;
+ pp = peer->procptr;
+ peer->precision = PRECISION;
+ pp->clockdesc = DESCRIPTION;
+ pp->stratum = STRATUM_UNSPEC;
+ memcpy((char *)&pp->refid, REFID, 4);
+ peer->burst = ASTAGE;
+#ifdef HAVE_PPSAPI
+ up = emalloc(sizeof(struct ppsunit));
+ memset(up, 0, sizeof(struct ppsunit));
+ pp->unitptr = (caddr_t)up;
+
+ /*
+ * Open PPS device. If this fails and some driver has already
+ * opened the associated radio device, fdpps has the file
+ * descriptor for it.
+ */
+ sprintf(device, DEVICE, unit);
+ up->fddev = open(device, O_RDWR, 0777);
+ if (up->fddev <= 0 && fdpps > 0) {
+ strcpy(device, pps_device);
+ up->fddev = fdpps;
+ }
+ if (up->fddev <= 0) {
+ msyslog(LOG_ERR,
+ "refclock_atom: %s: %m", device);
+ return (0);
+ }
+
+ /*
+ * Light off the PPSAPI interface. If this PPS device is shared
+ * with the radio device, take the default options from the pps
+ * command. This is for legacy purposes.
+ */
+ if (time_pps_create(up->fddev, &up->handle) < 0) {
+ msyslog(LOG_ERR,
+ "refclock_atom: time_pps_create failed: %m");
+ return (0);
+ }
+ return (atom_ppsapi(peer, 0, 0));
+#else /* HAVE_PPSAPI */
+ return (1);
+#endif /* HAVE_PPSAPI */
+}
+
+
+#ifdef HAVE_PPSAPI
+/*
+ * atom_control - fudge control
+ */
+static void
+atom_control(
+ int unit, /* unit (not used */
+ struct refclockstat *in, /* input parameters (not uded) */
+ struct refclockstat *out, /* output parameters (not used) */
+ struct peer *peer /* peer structure pointer */
+ )
+{
+ struct refclockproc *pp;
+
+ pp = peer->procptr;
+ atom_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2,
+ pp->sloppyclockflag & CLK_FLAG3);
+}
+
+
+/*
+ * Initialize PPSAPI
+ */
+int
+atom_ppsapi(
+ struct peer *peer, /* peer structure pointer */
+ int enb_clear, /* clear enable */
+ int enb_hardpps /* hardpps enable */
+ )
+{
+ struct refclockproc *pp;
+ register struct ppsunit *up;
+ int capability;
+
+ pp = peer->procptr;
+ up = (struct ppsunit *)pp->unitptr;
+ if (time_pps_getcap(up->handle, &capability) < 0) {
+ msyslog(LOG_ERR,
+ "refclock_atom: time_pps_getcap failed: %m");
+ return (0);
+ }
+ memset(&up->pps_params, 0, sizeof(pps_params_t));
+ if (enb_clear)
+ up->pps_params.mode = capability & PPS_CAPTURECLEAR;
+ else
+ up->pps_params.mode = capability & PPS_CAPTUREASSERT;
+ if (!up->pps_params.mode) {
+ msyslog(LOG_ERR,
+ "refclock_atom: invalid capture edge %d",
+ enb_clear);
+ return (0);
+ }
+ up->pps_params.mode |= PPS_TSFMT_TSPEC;
+ if (time_pps_setparams(up->handle, &up->pps_params) < 0) {
+ msyslog(LOG_ERR,
+ "refclock_atom: time_pps_setparams failed: %m");
+ return (0);
+ }
+ if (enb_hardpps) {
+ if (time_pps_kcbind(up->handle, PPS_KC_HARDPPS,
+ up->pps_params.mode & ~PPS_TSFMT_TSPEC,
+ PPS_TSFMT_TSPEC) < 0) {
+ msyslog(LOG_ERR,
+ "refclock_atom: time_pps_kcbind failed: %m");
+ return (0);
+ }
+ pps_enable = 1;
+ }
+#if DEBUG
+ if (debug) {
+ time_pps_getparams(up->handle, &up->pps_params);
+ printf(
+ "refclock_ppsapi: fd %d capability 0x%x version %d mode 0x%x kern %d\n",
+ up->fddev, capability, up->pps_params.api_version,
+ up->pps_params.mode, enb_hardpps);
+ }
+#endif
+ return (1);
+}
+
+
+/*
+ * atom_shutdown - shut down the clock
+ */
+static void
+atom_shutdown(
+ int unit, /* unit number (not used) */
+ struct peer *peer /* peer structure pointer */
+ )
+{
+ struct refclockproc *pp;
+ register struct ppsunit *up;
+
+ pp = peer->procptr;
+ up = (struct ppsunit *)pp->unitptr;
+ if (up->fddev > 0)
+ close(up->fddev);
+ if (up->handle != 0)
+ time_pps_destroy(up->handle);
+ if (pps_peer == peer)
+ pps_peer = 0;
+ free(up);
+}
+
+
+/*
+ * atom_pps - receive data from the PPSAPI interface
+ *
+ * This routine is called once per second when the PPSAPI interface is
+ * present. It snatches the PPS timestamp from the kernel and saves the
+ * sign-extended fraction in a circular buffer for processing at the
+ * next poll event.
+ */
+static int
+atom_pps(
+ struct peer *peer /* peer structure pointer */
+ )
+{
+ register struct ppsunit *up;
+ struct refclockproc *pp;
+ pps_info_t pps_info;
+ struct timespec timeout, ts;
+ double dtemp;
+
+ /*
+ * Convert the timespec nanoseconds field to signed double and
+ * save in the median filter. for billboards. No harm is done if
+ * previous data are overwritten. If the discipline comes bum or
+ * the data grow stale, just forget it. A range gate rejects new
+ * samples if less than a jiggle time from the next second.
+ */
+ pp = peer->procptr;
+ up = (struct ppsunit *)pp->unitptr;
+ if (up->handle == 0)
+ return (-1);
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 0;
+ memcpy(&pps_info, &up->pps_info, sizeof(pps_info_t));
+ if (time_pps_fetch(up->handle, PPS_TSFMT_TSPEC, &up->pps_info,
+ &timeout) < 0)
+ return (-1);
+ if (up->pps_params.mode & PPS_CAPTUREASSERT) {
+ if (pps_info.assert_sequence ==
+ up->pps_info.assert_sequence)
+ return (1);
+ ts = up->pps_info.assert_timestamp;
+ } else if (up->pps_params.mode & PPS_CAPTURECLEAR) {
+ if (pps_info.clear_sequence ==
+ up->pps_info.clear_sequence)
+ return (1);
+ ts = up->pps_info.clear_timestamp;
+ } else {
+ return (-1);
+ }
+ if (!((ts.tv_sec == up->ts.tv_sec && ts.tv_nsec -
+ up->ts.tv_nsec > NANOSECOND - RANGEGATE) ||
+ (ts.tv_sec - up->ts.tv_sec == 1 && ts.tv_nsec -
+ up->ts.tv_nsec < RANGEGATE))) {
+ up->ts = ts;
+ return (1);
+ }
+ up->ts = ts;
+ pp->lastrec.l_ui = ts.tv_sec + JAN_1970;
+ dtemp = ts.tv_nsec * FRAC / 1e9;
+ if (dtemp >= FRAC)
+ pp->lastrec.l_ui++;
+ pp->lastrec.l_uf = (u_int32)dtemp;
+ if (ts.tv_nsec > NANOSECOND / 2)
+ ts.tv_nsec -= NANOSECOND;
+ dtemp = -(double)ts.tv_nsec / NANOSECOND;
+ SAMPLE(dtemp + pp->fudgetime1);
+#ifdef DEBUG
+ if (debug > 1)
+ printf("atom_pps %f %f\n", dtemp, pp->fudgetime1);
+#endif
+ return (0);
+}
+#endif /* HAVE_PPSAPI */
+
+
+/*
+ * pps_sample - receive PPS data from some other clock driver
+ *
+ * This routine is called once per second when the external clock driver
+ * processes PPS information. It processes the PPS timestamp and saves
+ * the sign-extended fraction in a circular buffer for processing at the
+ * next poll event. This works only for a single PPS device.
+ */
+int
+pps_sample(
+ l_fp *offset /* PPS offset */
+ )
+{
+ register struct peer *peer;
+ struct refclockproc *pp;
+ l_fp lftmp;
+ double doffset;
+
+ peer = pps_peer;
+ if (peer == 0) /* nobody home */
+ return (1);
+ pp = peer->procptr;
+
+ /*
+ * Convert the timeval to l_fp and save for billboards. Sign-
+ * extend the fraction and stash in the buffer. No harm is done
+ * if previous data are overwritten. If the discipline comes bum
+ * or the data grow stale, just forget it.
+ */
+ pp->lastrec = *offset;
+ L_CLR(&lftmp);
+ L_ADDF(&lftmp, pp->lastrec.l_f);
+ LFPTOD(&lftmp, doffset);
+ SAMPLE(-doffset + pp->fudgetime1);
+ return (0);
+}
+
+/*
+ * atom_poll - called by the transmit procedure
+ *
+ * This routine is called once per second when in burst mode to save PPS
+ * sample offsets in the median filter. At the end of the burst period
+ * the samples are processed as a heap and the clock filter updated.
+ */
+static void
+atom_poll(
+ int unit, /* unit number (not used) */
+ struct peer *peer /* peer structure pointer */
+ )
+{
+ struct refclockproc *pp;
+#ifdef HAVE_PPSAPI
+ int err;
+#endif /* HAVE_PPSAPI */
+
+ /*
+ * Accumulate samples in the median filter. If a noise sample,
+ * return with no prejudice; if a protocol error, get mean;
+ * otherwise, cool. At the end of each poll interval, do a
+ * little bookeeping and process the surviving samples.
+ */
+ pp = peer->procptr;
+ pp->polls++;
+#ifdef HAVE_PPSAPI
+ err = atom_pps(peer);
+ if (err < 0) {
+ refclock_report(peer, CEVNT_FAULT);
+ return;
+ }
+#endif /* HAVE_PPSAPI */
+
+ /*
+ * Valid time is returned only if the prefer peer has survived
+ * the intersection algorithm and within clock_max of local time
+ * and not too long ago. This ensures the PPS time is within
+ * +-0.5 s of the local time and the seconds numbering is
+ * unambiguous. Note that the leap bits are set no-warning on
+ * the first valid update and the stratum is set at the prefer
+ * peer, unless overriden by a fudge command.
+ */
+ if (peer->burst > 0)
+ return;
+ peer->leap = LEAP_NOTINSYNC;
+ if (pp->codeproc == pp->coderecv) {
+ refclock_report(peer, CEVNT_TIMEOUT);
+ peer->burst = ASTAGE;
+ return;
+
+ } else if (sys_prefer == NULL) {
+ pp->codeproc = pp->coderecv;
+ peer->burst = ASTAGE;
+ return;
+
+ } else if (fabs(sys_prefer->offset) > clock_max) {
+ pp->codeproc = pp->coderecv;
+ peer->burst = ASTAGE;
+ return;
+ }
+ pp->leap = LEAP_NOWARNING;
+ if (pp->stratum >= STRATUM_UNSPEC)
+ peer->stratum = sys_prefer->stratum;
+ else
+ peer->stratum = pp->stratum;
+ if (peer->stratum == STRATUM_REFCLOCK || peer->stratum ==
+ STRATUM_UNSPEC)
+ peer->refid = pp->refid;
+ else
+ peer->refid = addr2refid(&sys_prefer->srcadr);
+ pp->lastref = pp->lastrec;
+ refclock_receive(peer);
+ peer->burst = ASTAGE;
+}
+#else
+int refclock_atom_bs;
+int
+pps_sample(
+ l_fp *offset /* PPS offset */
+ )
+{
+ return 1;
+}
+#endif /* REFCLOCK */