xref: /netbsd-src/external/bsd/ntp/dist/ntpd/refclock_zyfer.c (revision eabc0478de71e4e011a5b4e0392741e01d491794)
1*eabc0478Schristos /*	$NetBSD: refclock_zyfer.c,v 1.6 2024/08/18 20:47:19 christos Exp $	*/
2abb0f93cSkardel 
3abb0f93cSkardel /*
4abb0f93cSkardel  * refclock_zyfer - clock driver for the Zyfer GPSTarplus Clock
5abb0f93cSkardel  *
6abb0f93cSkardel  * Harlan Stenn, Jan 2002
7abb0f93cSkardel  */
8abb0f93cSkardel 
9abb0f93cSkardel #ifdef HAVE_CONFIG_H
10abb0f93cSkardel #include <config.h>
11abb0f93cSkardel #endif
12abb0f93cSkardel 
13abb0f93cSkardel #if defined(REFCLOCK) && defined(CLOCK_ZYFER)
14abb0f93cSkardel 
15abb0f93cSkardel #include "ntpd.h"
16abb0f93cSkardel #include "ntp_io.h"
17abb0f93cSkardel #include "ntp_refclock.h"
18abb0f93cSkardel #include "ntp_stdlib.h"
19abb0f93cSkardel #include "ntp_unixtime.h"
20cdfa2a7eSchristos #include "ntp_calgps.h"
21abb0f93cSkardel 
22abb0f93cSkardel #include <stdio.h>
23abb0f93cSkardel #include <ctype.h>
24abb0f93cSkardel 
258585484eSchristos #if defined(HAVE_TERMIOS_H)
268585484eSchristos # include <termios.h>
278585484eSchristos #elif defined(HAVE_SYS_TERMIOS_H)
28abb0f93cSkardel # include <sys/termios.h>
29abb0f93cSkardel #endif
30abb0f93cSkardel #ifdef HAVE_SYS_PPSCLOCK_H
31abb0f93cSkardel # include <sys/ppsclock.h>
32abb0f93cSkardel #endif
33abb0f93cSkardel 
34abb0f93cSkardel /*
35abb0f93cSkardel  * This driver provides support for the TOD serial port of a Zyfer GPStarplus.
36abb0f93cSkardel  * This clock also provides PPS as well as IRIG outputs.
37abb0f93cSkardel  * Precision is limited by the serial driver, etc.
38abb0f93cSkardel  *
39abb0f93cSkardel  * If I was really brave I'd hack/generalize the serial driver to deal
40abb0f93cSkardel  * with arbitrary on-time characters.  This clock *begins* the stream with
41abb0f93cSkardel  * `!`, the on-time character, and the string is *not* EOL-terminated.
42abb0f93cSkardel  *
43abb0f93cSkardel  * Configure the beast for 9600, 8N1.  While I see leap-second stuff
44abb0f93cSkardel  * in the documentation, the published specs on the TOD format only show
45abb0f93cSkardel  * the seconds going to '59'.  I see no leap warning in the TOD format.
46abb0f93cSkardel  *
47abb0f93cSkardel  * The clock sends the following message once per second:
48abb0f93cSkardel  *
49abb0f93cSkardel  *	!TIME,2002,017,07,59,32,2,4,1
50abb0f93cSkardel  *	      YYYY DDD HH MM SS m T O
51abb0f93cSkardel  *
52abb0f93cSkardel  *	!		On-time character
53abb0f93cSkardel  *	YYYY		Year
54abb0f93cSkardel  *	DDD	001-366	Day of Year
55abb0f93cSkardel  *	HH	00-23	Hour
56abb0f93cSkardel  *	MM	00-59	Minute
57abb0f93cSkardel  *	SS	00-59	Second (probably 00-60)
58abb0f93cSkardel  *	m	1-5	Time Mode:
59abb0f93cSkardel  *			1 = GPS time
60abb0f93cSkardel  *			2 = UTC time
61abb0f93cSkardel  *			3 = LGPS time (Local GPS)
62abb0f93cSkardel  *			4 = LUTC time (Local UTC)
63abb0f93cSkardel  *			5 = Manual time
64abb0f93cSkardel  *	T	4-9	Time Figure Of Merit:
65abb0f93cSkardel  *			4         x <= 1us
66abb0f93cSkardel  *			5   1us < x <= 10 us
67abb0f93cSkardel  *			6  10us < x <= 100us
68abb0f93cSkardel  *			7 100us < x <= 1ms
69abb0f93cSkardel  *			8   1ms < x <= 10ms
70abb0f93cSkardel  *			9  10ms < x
71abb0f93cSkardel  *	O	0-4	Operation Mode:
72abb0f93cSkardel  *			0 Warm-up
73abb0f93cSkardel  *			1 Time Locked
74abb0f93cSkardel  *			2 Coasting
75abb0f93cSkardel  *			3 Recovering
76abb0f93cSkardel  *			4 Manual
77abb0f93cSkardel  *
78abb0f93cSkardel  */
79abb0f93cSkardel 
80abb0f93cSkardel /*
81abb0f93cSkardel  * Interface definitions
82abb0f93cSkardel  */
83abb0f93cSkardel #define	DEVICE		"/dev/zyfer%d" /* device name and unit */
84abb0f93cSkardel #define	SPEED232	B9600	/* uart speed (9600 baud) */
85abb0f93cSkardel #define	PRECISION	(-20)	/* precision assumed (about 1 us) */
86abb0f93cSkardel #define	REFID		"GPS\0"	/* reference ID */
87abb0f93cSkardel #define	DESCRIPTION	"Zyfer GPStarplus" /* WRU */
88abb0f93cSkardel 
89abb0f93cSkardel #define	LENZYFER	29	/* timecode length */
90abb0f93cSkardel 
91abb0f93cSkardel /*
92abb0f93cSkardel  * Unit control structure
93abb0f93cSkardel  */
94abb0f93cSkardel struct zyferunit {
95abb0f93cSkardel 	u_char	Rcvbuf[LENZYFER + 1];
96abb0f93cSkardel 	u_char	polled;		/* poll message flag */
97abb0f93cSkardel 	int	pollcnt;
98abb0f93cSkardel 	l_fp    tstamp;         /* timestamp of last poll */
99abb0f93cSkardel 	int	Rcvptr;
100abb0f93cSkardel };
101abb0f93cSkardel 
102abb0f93cSkardel /*
103abb0f93cSkardel  * Function prototypes
104abb0f93cSkardel  */
105abb0f93cSkardel static	int	zyfer_start	(int, struct peer *);
106abb0f93cSkardel static	void	zyfer_shutdown	(int, struct peer *);
107abb0f93cSkardel static	void	zyfer_receive	(struct recvbuf *);
108abb0f93cSkardel static	void	zyfer_poll	(int, struct peer *);
109abb0f93cSkardel 
110abb0f93cSkardel /*
111abb0f93cSkardel  * Transfer vector
112abb0f93cSkardel  */
113abb0f93cSkardel struct	refclock refclock_zyfer = {
114abb0f93cSkardel 	zyfer_start,		/* start up driver */
115abb0f93cSkardel 	zyfer_shutdown,		/* shut down driver */
116abb0f93cSkardel 	zyfer_poll,		/* transmit poll message */
117abb0f93cSkardel 	noentry,		/* not used (old zyfer_control) */
118abb0f93cSkardel 	noentry,		/* initialize driver (not used) */
119abb0f93cSkardel 	noentry,		/* not used (old zyfer_buginfo) */
120abb0f93cSkardel 	NOFLAGS			/* not used */
121abb0f93cSkardel };
122abb0f93cSkardel 
123abb0f93cSkardel 
124abb0f93cSkardel /*
125abb0f93cSkardel  * zyfer_start - open the devices and initialize data for processing
126abb0f93cSkardel  */
127abb0f93cSkardel static int
128abb0f93cSkardel zyfer_start(
129abb0f93cSkardel 	int unit,
130abb0f93cSkardel 	struct peer *peer
131abb0f93cSkardel 	)
132abb0f93cSkardel {
133abb0f93cSkardel 	register struct zyferunit *up;
134abb0f93cSkardel 	struct refclockproc *pp;
135abb0f93cSkardel 	int fd;
136abb0f93cSkardel 	char device[20];
137abb0f93cSkardel 
138abb0f93cSkardel 	/*
139abb0f93cSkardel 	 * Open serial port.
140abb0f93cSkardel 	 * Something like LDISC_ACTS that looked for ! would be nice...
141abb0f93cSkardel 	 */
1428585484eSchristos 	snprintf(device, sizeof(device), DEVICE, unit);
143*eabc0478Schristos 	fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_RAW);
1448585484eSchristos 	if (fd <= 0)
145abb0f93cSkardel 		return (0);
146abb0f93cSkardel 
147abb0f93cSkardel 	msyslog(LOG_NOTICE, "zyfer(%d) fd: %d dev <%s>", unit, fd, device);
148abb0f93cSkardel 
149abb0f93cSkardel 	/*
150abb0f93cSkardel 	 * Allocate and initialize unit structure
151abb0f93cSkardel 	 */
1528585484eSchristos 	up = emalloc(sizeof(struct zyferunit));
1538585484eSchristos 	memset(up, 0, sizeof(struct zyferunit));
154abb0f93cSkardel 	pp = peer->procptr;
155abb0f93cSkardel 	pp->io.clock_recv = zyfer_receive;
1568585484eSchristos 	pp->io.srcclock = peer;
157abb0f93cSkardel 	pp->io.datalen = 0;
158abb0f93cSkardel 	pp->io.fd = fd;
159abb0f93cSkardel 	if (!io_addclock(&pp->io)) {
1608585484eSchristos 		close(fd);
1618585484eSchristos 		pp->io.fd = -1;
162abb0f93cSkardel 		free(up);
163abb0f93cSkardel 		return (0);
164abb0f93cSkardel 	}
1658585484eSchristos 	pp->unitptr = up;
166abb0f93cSkardel 
167abb0f93cSkardel 	/*
168abb0f93cSkardel 	 * Initialize miscellaneous variables
169abb0f93cSkardel 	 */
170abb0f93cSkardel 	peer->precision = PRECISION;
171abb0f93cSkardel 	pp->clockdesc = DESCRIPTION;
172abb0f93cSkardel 	memcpy((char *)&pp->refid, REFID, 4);
173abb0f93cSkardel 	up->pollcnt = 2;
174abb0f93cSkardel 	up->polled = 0;		/* May not be needed... */
175abb0f93cSkardel 
176abb0f93cSkardel 	return (1);
177abb0f93cSkardel }
178abb0f93cSkardel 
179abb0f93cSkardel 
180abb0f93cSkardel /*
181abb0f93cSkardel  * zyfer_shutdown - shut down the clock
182abb0f93cSkardel  */
183abb0f93cSkardel static void
184abb0f93cSkardel zyfer_shutdown(
185abb0f93cSkardel 	int unit,
186abb0f93cSkardel 	struct peer *peer
187abb0f93cSkardel 	)
188abb0f93cSkardel {
189abb0f93cSkardel 	register struct zyferunit *up;
190abb0f93cSkardel 	struct refclockproc *pp;
191abb0f93cSkardel 
192abb0f93cSkardel 	pp = peer->procptr;
1938585484eSchristos 	up = pp->unitptr;
1948585484eSchristos 	if (pp->io.fd != -1)
195abb0f93cSkardel 		io_closeclock(&pp->io);
1968585484eSchristos 	if (up != NULL)
197abb0f93cSkardel 		free(up);
198abb0f93cSkardel }
199abb0f93cSkardel 
200abb0f93cSkardel 
201abb0f93cSkardel /*
202abb0f93cSkardel  * zyfer_receive - receive data from the serial interface
203abb0f93cSkardel  */
204abb0f93cSkardel static void
205abb0f93cSkardel zyfer_receive(
206abb0f93cSkardel 	struct recvbuf *rbufp
207abb0f93cSkardel 	)
208abb0f93cSkardel {
209abb0f93cSkardel 	register struct zyferunit *up;
210abb0f93cSkardel 	struct refclockproc *pp;
211abb0f93cSkardel 	struct peer *peer;
212abb0f93cSkardel 	int tmode;		/* Time mode */
213abb0f93cSkardel 	int tfom;		/* Time Figure Of Merit */
214abb0f93cSkardel 	int omode;		/* Operation mode */
215abb0f93cSkardel 	u_char *p;
216abb0f93cSkardel 
217cdfa2a7eSchristos 	TCivilDate	tsdoy;
218cdfa2a7eSchristos 	TNtpDatum	tsntp;
219cdfa2a7eSchristos 	l_fp		tfrac;
220cdfa2a7eSchristos 
2218585484eSchristos 	peer = rbufp->recv_peer;
222abb0f93cSkardel 	pp = peer->procptr;
2238585484eSchristos 	up = pp->unitptr;
224abb0f93cSkardel 	p = (u_char *) &rbufp->recv_space;
225abb0f93cSkardel 	/*
226abb0f93cSkardel 	 * If lencode is 0:
227abb0f93cSkardel 	 * - if *rbufp->recv_space is !
228abb0f93cSkardel 	 * - - call refclock_gtlin to get things going
229abb0f93cSkardel 	 * - else flush
230abb0f93cSkardel 	 * else stuff it on the end of lastcode
231abb0f93cSkardel 	 * If we don't have LENZYFER bytes
232abb0f93cSkardel 	 * - wait for more data
233abb0f93cSkardel 	 * Crack the beast, and if it's OK, process it.
234abb0f93cSkardel 	 *
235abb0f93cSkardel 	 * We use refclock_gtlin() because we might use LDISC_CLK.
236abb0f93cSkardel 	 *
237abb0f93cSkardel 	 * Under FreeBSD, we get the ! followed by two 14-byte packets.
238abb0f93cSkardel 	 */
239abb0f93cSkardel 
240abb0f93cSkardel 	if (pp->lencode >= LENZYFER)
241abb0f93cSkardel 		pp->lencode = 0;
242abb0f93cSkardel 
243abb0f93cSkardel 	if (!pp->lencode) {
244abb0f93cSkardel 		if (*p == '!')
245abb0f93cSkardel 			pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode,
246abb0f93cSkardel 						     BMAX, &pp->lastrec);
247abb0f93cSkardel 		else
248abb0f93cSkardel 			return;
249abb0f93cSkardel 	} else {
250abb0f93cSkardel 		memcpy(pp->a_lastcode + pp->lencode, p, rbufp->recv_length);
251abb0f93cSkardel 		pp->lencode += rbufp->recv_length;
252abb0f93cSkardel 		pp->a_lastcode[pp->lencode] = '\0';
253abb0f93cSkardel 	}
254abb0f93cSkardel 
255abb0f93cSkardel 	if (pp->lencode < LENZYFER)
256abb0f93cSkardel 		return;
257abb0f93cSkardel 
258abb0f93cSkardel 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
259abb0f93cSkardel 
260abb0f93cSkardel 	/*
261abb0f93cSkardel 	 * We get down to business, check the timecode format and decode
262abb0f93cSkardel 	 * its contents. If the timecode has invalid length or is not in
263abb0f93cSkardel 	 * proper format, we declare bad format and exit.
264abb0f93cSkardel 	 */
265abb0f93cSkardel 
266abb0f93cSkardel 	if (pp->lencode != LENZYFER) {
267abb0f93cSkardel 		refclock_report(peer, CEVNT_BADTIME);
268abb0f93cSkardel 		return;
269abb0f93cSkardel 	}
270abb0f93cSkardel 
271abb0f93cSkardel 	/*
272abb0f93cSkardel 	 * Timecode sample: "!TIME,2002,017,07,59,32,2,4,1"
273abb0f93cSkardel 	 */
274abb0f93cSkardel 	if (sscanf(pp->a_lastcode, "!TIME,%4d,%3d,%2d,%2d,%2d,%d,%d,%d",
275abb0f93cSkardel 		   &pp->year, &pp->day, &pp->hour, &pp->minute, &pp->second,
276abb0f93cSkardel 		   &tmode, &tfom, &omode) != 8) {
277abb0f93cSkardel 		refclock_report(peer, CEVNT_BADREPLY);
278abb0f93cSkardel 		return;
279abb0f93cSkardel 	}
280abb0f93cSkardel 
281abb0f93cSkardel 	if (tmode != 2) {
282abb0f93cSkardel 		refclock_report(peer, CEVNT_BADTIME);
283abb0f93cSkardel 		return;
284abb0f93cSkardel 	}
285abb0f93cSkardel 
286abb0f93cSkardel 	/* Should we make sure tfom is 4? */
287abb0f93cSkardel 
288abb0f93cSkardel 	if (omode != 1) {
289abb0f93cSkardel 		pp->leap = LEAP_NOTINSYNC;
290abb0f93cSkardel 		return;
291abb0f93cSkardel 	}
2928585484eSchristos 
293cdfa2a7eSchristos 	/* treat GPS input as subject to era warps */
294cdfa2a7eSchristos 	ZERO(tsdoy);
295cdfa2a7eSchristos 	ZERO(tfrac);
296cdfa2a7eSchristos 
297cdfa2a7eSchristos 	tsdoy.year    = pp->year;
298cdfa2a7eSchristos 	tsdoy.yearday = pp->day;
299cdfa2a7eSchristos 	tsdoy.hour    = pp->hour;
300cdfa2a7eSchristos 	tsdoy.minute  = pp->minute;
301cdfa2a7eSchristos 	tsdoy.second  = pp->second;
302cdfa2a7eSchristos 
303cdfa2a7eSchristos 	/* note: We kept 'month' and 'monthday' zero above. That forces
304cdfa2a7eSchristos 	 * day-of-year based calculation now:
305cdfa2a7eSchristos 	 */
306cdfa2a7eSchristos 	tsntp = gpsntp_from_calendar(&tsdoy, tfrac);
307cdfa2a7eSchristos 	tfrac = ntpfp_from_ntpdatum(&tsntp);
308cdfa2a7eSchristos 	refclock_process_offset(pp, tfrac, pp->lastrec, pp->fudgetime1);
309abb0f93cSkardel 
310abb0f93cSkardel 	/*
311abb0f93cSkardel 	 * Good place for record_clock_stats()
312abb0f93cSkardel 	 */
313abb0f93cSkardel 	up->pollcnt = 2;
314abb0f93cSkardel 
315abb0f93cSkardel 	if (up->polled) {
316abb0f93cSkardel 		up->polled = 0;
317abb0f93cSkardel 		refclock_receive(peer);
318abb0f93cSkardel 	}
319abb0f93cSkardel }
320abb0f93cSkardel 
321abb0f93cSkardel 
322abb0f93cSkardel /*
323abb0f93cSkardel  * zyfer_poll - called by the transmit procedure
324abb0f93cSkardel  */
325abb0f93cSkardel static void
326abb0f93cSkardel zyfer_poll(
327abb0f93cSkardel 	int unit,
328abb0f93cSkardel 	struct peer *peer
329abb0f93cSkardel 	)
330abb0f93cSkardel {
331abb0f93cSkardel 	register struct zyferunit *up;
332abb0f93cSkardel 	struct refclockproc *pp;
333abb0f93cSkardel 
334abb0f93cSkardel 	/*
335abb0f93cSkardel 	 * We don't really do anything here, except arm the receiving
336abb0f93cSkardel 	 * side to capture a sample and check for timeouts.
337abb0f93cSkardel 	 */
338abb0f93cSkardel 	pp = peer->procptr;
3398585484eSchristos 	up = pp->unitptr;
340abb0f93cSkardel 	if (!up->pollcnt)
341abb0f93cSkardel 		refclock_report(peer, CEVNT_TIMEOUT);
342abb0f93cSkardel 	else
343abb0f93cSkardel 		up->pollcnt--;
344abb0f93cSkardel 	pp->polls++;
345abb0f93cSkardel 	up->polled = 1;
346abb0f93cSkardel }
347abb0f93cSkardel 
348abb0f93cSkardel #else
349*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT
350abb0f93cSkardel #endif /* REFCLOCK */
351