Home | History | Annotate | Line # | Download | only in ntpd
      1 /*	$NetBSD: refclock_atom.c,v 1.6 2024/08/18 20:47:18 christos Exp $	*/
      2 
      3 /*
      4  * refclock_atom - clock driver for 1-pps signals
      5  */
      6 #ifdef HAVE_CONFIG_H
      7 #include <config.h>
      8 #endif
      9 
     10 #include <stdio.h>
     11 #include <ctype.h>
     12 
     13 #include "ntpd.h"
     14 #include "ntp_io.h"
     15 #include "ntp_unixtime.h"
     16 #include "ntp_refclock.h"
     17 #include "ntp_stdlib.h"
     18 
     19 /*
     20  * This driver requires the PPSAPI interface (RFC 2783)
     21  */
     22 #if defined(REFCLOCK) && defined(CLOCK_ATOM) && defined(HAVE_PPSAPI)
     23 #include "ppsapi_timepps.h"
     24 #include "refclock_atom.h"
     25 
     26 /*
     27  * This driver furnishes an interface for pulse-per-second (PPS) signals
     28  * produced by a cesium clock, timing receiver or related equipment. It
     29  * can be used to remove accumulated jitter over a congested link and
     30  * retime a server before redistributing the time to clients. It can
     31  *also be used as a holdover should all other synchronization sources
     32  * beconme unreachable.
     33  *
     34  * Before this driver becomes active, the local clock must be set to
     35  * within +-0.4 s by another means, such as a radio clock or NTP
     36  * itself. There are two ways to connect the PPS signal, normally at TTL
     37  * levels, to the computer. One is to shift to EIA levels and connect to
     38  * pin 8 (DCD) of a serial port. This requires a level converter and
     39  * may require a one-shot flipflop to lengthen the pulse. The other is
     40  * to connect the PPS signal directly to pin 10 (ACK) of a PC paralell
     41  * port. These methods are architecture dependent.
     42  *
     43  * This driver requires the Pulse-per-Second API for Unix-like Operating
     44  * Systems, Version 1.0, RFC-2783 (PPSAPI). Implementations are
     45  * available for FreeBSD, Linux, SunOS, Solaris and Tru64. However, at
     46  * present only the Tru64 implementation provides the full generality of
     47  * the API with multiple PPS drivers and multiple handles per driver. If
     48  * the PPSAPI is normally implemented in the /usr/include/sys/timepps.h
     49  * header file and kernel support specific to each operating system.
     50  *
     51  * This driver normally uses the PLL/FLL clock discipline implemented in
     52  * the ntpd code. Ordinarily, this is the most accurate means, as the
     53  * median filter in the driver interface is much larger than in the
     54  * kernel. However, if the systemic clock frequency error is large (tens
     55  * to hundreds of PPM), it's better to used the kernel support, if
     56  * available.
     57  *
     58  * This deriver is subject to the mitigation rules described in the
     59  * "mitigation rulse and the prefer peer" page. However, there is an
     60  * important difference. If this driver becomes the PPS driver according
     61  * to these rules, it is acrive only if (a) a prefer peer other than
     62  * this driver is among the survivors or (b) there are no survivors and
     63  * the minsane option of the tos command is zero. This is intended to
     64  * support space missions where updates from other spacecraft are
     65  * infrequent, but a reliable PPS signal, such as from an Ultra Stable
     66  * Oscillator (USO) is available.
     67  *
     68  * Fudge Factors
     69  *
     70  * The PPS timestamp is captured on the rising (assert) edge if flag2 is
     71  * dim (default) and on the falling (clear) edge if lit. If flag3 is dim
     72  * (default), the kernel PPS support is disabled; if lit it is enabled.
     73  * If flag4 is lit, each timesampt is copied to the clockstats file for
     74  * later analysis. This can be useful when constructing Allan deviation
     75  * plots. The time1 parameter can be used to compensate for
     76  * miscellaneous device driver and OS delays.
     77  */
     78 /*
     79  * Interface definitions
     80  */
     81 #define DEVICE		"/dev/pps%d" /* device name and unit */
     82 #define	PRECISION	(-20)	/* precision assumed (about 1 us) */
     83 #define	REFID		"PPS\0"	/* reference ID */
     84 #define	DESCRIPTION	"PPS Clock Discipline" /* WRU */
     85 
     86 /*
     87  * PPS unit control structure
     88  */
     89 struct ppsunit {
     90 	struct refclock_atom atom; /* atom structure pointer */
     91 	int	fddev;		/* file descriptor */
     92 };
     93 
     94 /*
     95  * Function prototypes
     96  */
     97 static	int	atom_start	(int, struct peer *);
     98 static	void	atom_shutdown	(int, struct peer *);
     99 static	void	atom_poll	(int, struct peer *);
    100 static	void	atom_timer	(int, struct peer *);
    101 
    102 /*
    103  * Transfer vector
    104  */
    105 struct	refclock refclock_atom = {
    106 	atom_start,		/* start up driver */
    107 	atom_shutdown,		/* shut down driver */
    108 	atom_poll,		/* transmit poll message */
    109 	noentry,		/* control (not used) */
    110 	noentry,		/* initialize driver (not used) */
    111 	noentry,		/* buginfo (not used) */
    112 	atom_timer,		/* called once per second */
    113 };
    114 
    115 
    116 /*
    117  * atom_start - initialize data for processing
    118  */
    119 static int
    120 atom_start(
    121 	int unit,		/* unit number (not used) */
    122 	struct peer *peer	/* peer structure pointer */
    123 	)
    124 {
    125 	struct refclockproc *pp;
    126 	struct ppsunit *up;
    127 	char	device[80];
    128 
    129 	/*
    130 	 * Allocate and initialize unit structure
    131 	 */
    132 	pp = peer->procptr;
    133 	peer->precision = PRECISION;
    134 	pp->clockdesc = DESCRIPTION;
    135 	pp->stratum = STRATUM_UNSPEC;
    136 	memcpy((char *)&pp->refid, REFID, 4);
    137 	up = emalloc(sizeof(struct ppsunit));
    138 	memset(up, 0, sizeof(struct ppsunit));
    139 	pp->unitptr = up;
    140 
    141 	/*
    142 	 * Open PPS device. This can be any serial or parallel port and
    143 	 * not necessarily the port used for the associated radio.
    144 	 */
    145 	snprintf(device, sizeof(device), DEVICE, unit);
    146 	up->fddev = tty_open(device, O_RDWR, 0777);
    147 	if (up->fddev <= 0) {
    148 		msyslog(LOG_ERR,
    149 			"refclock_atom: %s: %m", device);
    150 		return (0);
    151 	}
    152 
    153 	/*
    154 	 * Light up the PPSAPI interface.
    155 	 */
    156 	return (refclock_ppsapi(up->fddev, &up->atom));
    157 }
    158 
    159 
    160 /*
    161  * atom_shutdown - shut down the clock
    162  */
    163 static void
    164 atom_shutdown(
    165 	int unit,		/* unit number (not used) */
    166 	struct peer *peer	/* peer structure pointer */
    167 	)
    168 {
    169 	struct refclockproc *pp;
    170 	struct ppsunit *up;
    171 
    172 	pp = peer->procptr;
    173 	up = pp->unitptr;
    174 	if (up->fddev > 0)
    175 		close(up->fddev);
    176 	free(up);
    177 }
    178 
    179 /*
    180  * atom_timer - called once per second
    181  */
    182 void
    183 atom_timer(
    184 	int	unit,		/* unit pointer (not used) */
    185 	struct peer *peer	/* peer structure pointer */
    186 	)
    187 {
    188 	struct ppsunit *up;
    189 	struct refclockproc *pp;
    190 	char	tbuf[80];
    191 
    192 	pp = peer->procptr;
    193 	up = pp->unitptr;
    194 	if (refclock_pps(peer, &up->atom, pp->sloppyclockflag) <= 0)
    195 		return;
    196 
    197 	peer->flags |= FLAG_PPS;
    198 
    199 	/*
    200 	 * If flag4 is lit, record each second offset to clockstats.
    201 	 * That's so we can make awesome Allan deviation plots.
    202 	 */
    203 	if (pp->sloppyclockflag & CLK_FLAG4) {
    204 		snprintf(tbuf, sizeof(tbuf), "%.9f",
    205 			 pp->filter[pp->coderecv]);
    206 		record_clock_stats(&peer->srcadr, tbuf);
    207 	}
    208 }
    209 
    210 
    211 /*
    212  * atom_poll - called by the transmit procedure
    213  */
    214 static void
    215 atom_poll(
    216 	int unit,		/* unit number (not used) */
    217 	struct peer *peer	/* peer structure pointer */
    218 	)
    219 {
    220 	struct refclockproc *pp;
    221 
    222 	/*
    223 	 * Don't wiggle the clock until some other driver has numbered
    224 	 * the seconds.
    225 	 */
    226 	if (sys_leap == LEAP_NOTINSYNC)
    227 		return;
    228 
    229 	pp = peer->procptr;
    230 	pp->polls++;
    231 	if (pp->codeproc == pp->coderecv) {
    232 		peer->flags &= ~FLAG_PPS;
    233 		refclock_report(peer, CEVNT_TIMEOUT);
    234 		return;
    235 	}
    236 	pp->lastref = pp->lastrec;
    237 	refclock_receive(peer);
    238 }
    239 #else
    240 NONEMPTY_TRANSLATION_UNIT
    241 #endif /* REFCLOCK */
    242