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 */
89struct ppsunit {
90	struct refclock_atom atom; /* atom structure pointer */
91	int	fddev;		/* file descriptor */
92};
93
94/*
95 * Function prototypes
96 */
97static	int	atom_start	(int, struct peer *);
98static	void	atom_shutdown	(int, struct peer *);
99static	void	atom_poll	(int, struct peer *);
100static	void	atom_timer	(int, struct peer *);
101
102/*
103 * Transfer vector
104 */
105struct	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 */
119static int
120atom_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 */
163static void
164atom_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 */
182void
183atom_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 */
214static void
215atom_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
240NONEMPTY_TRANSLATION_UNIT
241#endif /* REFCLOCK */
242