1106163Sroberto/*
2106163Sroberto * refclock_zyfer - clock driver for the Zyfer GPSTarplus Clock
3106163Sroberto *
4106163Sroberto * Harlan Stenn, Jan 2002
5106163Sroberto */
6106163Sroberto
7106163Sroberto#ifdef HAVE_CONFIG_H
8106163Sroberto#include <config.h>
9106163Sroberto#endif
10106163Sroberto
11106163Sroberto#if defined(REFCLOCK) && defined(CLOCK_ZYFER)
12106163Sroberto
13106163Sroberto#include "ntpd.h"
14106163Sroberto#include "ntp_io.h"
15106163Sroberto#include "ntp_refclock.h"
16106163Sroberto#include "ntp_stdlib.h"
17106163Sroberto#include "ntp_unixtime.h"
18106163Sroberto
19106163Sroberto#include <stdio.h>
20106163Sroberto#include <ctype.h>
21106163Sroberto
22285612Sdelphij#if defined(HAVE_TERMIOS_H)
23285612Sdelphij# include <termios.h>
24285612Sdelphij#elif defined(HAVE_SYS_TERMIOS_H)
25106163Sroberto# include <sys/termios.h>
26106163Sroberto#endif
27106163Sroberto#ifdef HAVE_SYS_PPSCLOCK_H
28106163Sroberto# include <sys/ppsclock.h>
29106163Sroberto#endif
30106163Sroberto
31106163Sroberto/*
32106163Sroberto * This driver provides support for the TOD serial port of a Zyfer GPStarplus.
33106163Sroberto * This clock also provides PPS as well as IRIG outputs.
34106163Sroberto * Precision is limited by the serial driver, etc.
35106163Sroberto *
36106163Sroberto * If I was really brave I'd hack/generalize the serial driver to deal
37106163Sroberto * with arbitrary on-time characters.  This clock *begins* the stream with
38106163Sroberto * `!`, the on-time character, and the string is *not* EOL-terminated.
39106163Sroberto *
40106163Sroberto * Configure the beast for 9600, 8N1.  While I see leap-second stuff
41106163Sroberto * in the documentation, the published specs on the TOD format only show
42106163Sroberto * the seconds going to '59'.  I see no leap warning in the TOD format.
43106163Sroberto *
44106163Sroberto * The clock sends the following message once per second:
45106163Sroberto *
46106163Sroberto *	!TIME,2002,017,07,59,32,2,4,1
47106163Sroberto *	      YYYY DDD HH MM SS m T O
48106163Sroberto *
49106163Sroberto *	!		On-time character
50106163Sroberto *	YYYY		Year
51106163Sroberto *	DDD	001-366	Day of Year
52106163Sroberto *	HH	00-23	Hour
53106163Sroberto *	MM	00-59	Minute
54106163Sroberto *	SS	00-59	Second (probably 00-60)
55106163Sroberto *	m	1-5	Time Mode:
56106163Sroberto *			1 = GPS time
57106163Sroberto *			2 = UTC time
58106163Sroberto *			3 = LGPS time (Local GPS)
59106163Sroberto *			4 = LUTC time (Local UTC)
60106163Sroberto *			5 = Manual time
61106163Sroberto *	T	4-9	Time Figure Of Merit:
62106163Sroberto *			4         x <= 1us
63106163Sroberto *			5   1us < x <= 10 us
64106163Sroberto *			6  10us < x <= 100us
65106163Sroberto *			7 100us < x <= 1ms
66106163Sroberto *			8   1ms < x <= 10ms
67106163Sroberto *			9  10ms < x
68106163Sroberto *	O	0-4	Operation Mode:
69106163Sroberto *			0 Warm-up
70106163Sroberto *			1 Time Locked
71106163Sroberto *			2 Coasting
72106163Sroberto *			3 Recovering
73106163Sroberto *			4 Manual
74106163Sroberto *
75106163Sroberto */
76106163Sroberto
77106163Sroberto/*
78106163Sroberto * Interface definitions
79106163Sroberto */
80106163Sroberto#define	DEVICE		"/dev/zyfer%d" /* device name and unit */
81106163Sroberto#define	SPEED232	B9600	/* uart speed (9600 baud) */
82106163Sroberto#define	PRECISION	(-20)	/* precision assumed (about 1 us) */
83106163Sroberto#define	REFID		"GPS\0"	/* reference ID */
84106163Sroberto#define	DESCRIPTION	"Zyfer GPStarplus" /* WRU */
85106163Sroberto
86106163Sroberto#define	LENZYFER	29	/* timecode length */
87106163Sroberto
88106163Sroberto/*
89106163Sroberto * Unit control structure
90106163Sroberto */
91106163Srobertostruct zyferunit {
92106163Sroberto	u_char	Rcvbuf[LENZYFER + 1];
93106163Sroberto	u_char	polled;		/* poll message flag */
94106163Sroberto	int	pollcnt;
95106163Sroberto	l_fp    tstamp;         /* timestamp of last poll */
96106163Sroberto	int	Rcvptr;
97106163Sroberto};
98106163Sroberto
99106163Sroberto/*
100106163Sroberto * Function prototypes
101106163Sroberto */
102285612Sdelphijstatic	int	zyfer_start	(int, struct peer *);
103285612Sdelphijstatic	void	zyfer_shutdown	(int, struct peer *);
104285612Sdelphijstatic	void	zyfer_receive	(struct recvbuf *);
105285612Sdelphijstatic	void	zyfer_poll	(int, struct peer *);
106106163Sroberto
107106163Sroberto/*
108106163Sroberto * Transfer vector
109106163Sroberto */
110106163Srobertostruct	refclock refclock_zyfer = {
111106163Sroberto	zyfer_start,		/* start up driver */
112106163Sroberto	zyfer_shutdown,		/* shut down driver */
113106163Sroberto	zyfer_poll,		/* transmit poll message */
114106163Sroberto	noentry,		/* not used (old zyfer_control) */
115106163Sroberto	noentry,		/* initialize driver (not used) */
116106163Sroberto	noentry,		/* not used (old zyfer_buginfo) */
117106163Sroberto	NOFLAGS			/* not used */
118106163Sroberto};
119106163Sroberto
120106163Sroberto
121106163Sroberto/*
122106163Sroberto * zyfer_start - open the devices and initialize data for processing
123106163Sroberto */
124106163Srobertostatic int
125106163Srobertozyfer_start(
126106163Sroberto	int unit,
127106163Sroberto	struct peer *peer
128106163Sroberto	)
129106163Sroberto{
130106163Sroberto	register struct zyferunit *up;
131106163Sroberto	struct refclockproc *pp;
132106163Sroberto	int fd;
133106163Sroberto	char device[20];
134106163Sroberto
135106163Sroberto	/*
136106163Sroberto	 * Open serial port.
137106163Sroberto	 * Something like LDISC_ACTS that looked for ! would be nice...
138106163Sroberto	 */
139285612Sdelphij	snprintf(device, sizeof(device), DEVICE, unit);
140285612Sdelphij	fd = refclock_open(device, SPEED232, LDISC_RAW);
141285612Sdelphij	if (fd <= 0)
142285612Sdelphij		return (0);
143106163Sroberto
144106163Sroberto	msyslog(LOG_NOTICE, "zyfer(%d) fd: %d dev <%s>", unit, fd, device);
145106163Sroberto
146106163Sroberto	/*
147106163Sroberto	 * Allocate and initialize unit structure
148106163Sroberto	 */
149285612Sdelphij	up = emalloc(sizeof(struct zyferunit));
150285612Sdelphij	memset(up, 0, sizeof(struct zyferunit));
151106163Sroberto	pp = peer->procptr;
152106163Sroberto	pp->io.clock_recv = zyfer_receive;
153285612Sdelphij	pp->io.srcclock = peer;
154106163Sroberto	pp->io.datalen = 0;
155106163Sroberto	pp->io.fd = fd;
156106163Sroberto	if (!io_addclock(&pp->io)) {
157285612Sdelphij		close(fd);
158285612Sdelphij		pp->io.fd = -1;
159106163Sroberto		free(up);
160106163Sroberto		return (0);
161106163Sroberto	}
162285612Sdelphij	pp->unitptr = up;
163106163Sroberto
164106163Sroberto	/*
165106163Sroberto	 * Initialize miscellaneous variables
166106163Sroberto	 */
167106163Sroberto	peer->precision = PRECISION;
168106163Sroberto	pp->clockdesc = DESCRIPTION;
169106163Sroberto	memcpy((char *)&pp->refid, REFID, 4);
170106163Sroberto	up->pollcnt = 2;
171106163Sroberto	up->polled = 0;		/* May not be needed... */
172106163Sroberto
173106163Sroberto	return (1);
174106163Sroberto}
175106163Sroberto
176106163Sroberto
177106163Sroberto/*
178106163Sroberto * zyfer_shutdown - shut down the clock
179106163Sroberto */
180106163Srobertostatic void
181106163Srobertozyfer_shutdown(
182106163Sroberto	int unit,
183106163Sroberto	struct peer *peer
184106163Sroberto	)
185106163Sroberto{
186106163Sroberto	register struct zyferunit *up;
187106163Sroberto	struct refclockproc *pp;
188106163Sroberto
189106163Sroberto	pp = peer->procptr;
190285612Sdelphij	up = pp->unitptr;
191285612Sdelphij	if (pp->io.fd != -1)
192285612Sdelphij		io_closeclock(&pp->io);
193285612Sdelphij	if (up != NULL)
194285612Sdelphij		free(up);
195106163Sroberto}
196106163Sroberto
197106163Sroberto
198106163Sroberto/*
199106163Sroberto * zyfer_receive - receive data from the serial interface
200106163Sroberto */
201106163Srobertostatic void
202106163Srobertozyfer_receive(
203106163Sroberto	struct recvbuf *rbufp
204106163Sroberto	)
205106163Sroberto{
206106163Sroberto	register struct zyferunit *up;
207106163Sroberto	struct refclockproc *pp;
208106163Sroberto	struct peer *peer;
209106163Sroberto	int tmode;		/* Time mode */
210106163Sroberto	int tfom;		/* Time Figure Of Merit */
211106163Sroberto	int omode;		/* Operation mode */
212106163Sroberto	u_char *p;
213106163Sroberto
214285612Sdelphij	peer = rbufp->recv_peer;
215106163Sroberto	pp = peer->procptr;
216285612Sdelphij	up = pp->unitptr;
217106163Sroberto	p = (u_char *) &rbufp->recv_space;
218106163Sroberto	/*
219106163Sroberto	 * If lencode is 0:
220106163Sroberto	 * - if *rbufp->recv_space is !
221106163Sroberto	 * - - call refclock_gtlin to get things going
222106163Sroberto	 * - else flush
223106163Sroberto	 * else stuff it on the end of lastcode
224106163Sroberto	 * If we don't have LENZYFER bytes
225106163Sroberto	 * - wait for more data
226106163Sroberto	 * Crack the beast, and if it's OK, process it.
227106163Sroberto	 *
228106424Sroberto	 * We use refclock_gtlin() because we might use LDISC_CLK.
229106163Sroberto	 *
230106163Sroberto	 * Under FreeBSD, we get the ! followed by two 14-byte packets.
231106163Sroberto	 */
232106163Sroberto
233106163Sroberto	if (pp->lencode >= LENZYFER)
234106163Sroberto		pp->lencode = 0;
235106163Sroberto
236106163Sroberto	if (!pp->lencode) {
237106163Sroberto		if (*p == '!')
238106163Sroberto			pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode,
239106163Sroberto						     BMAX, &pp->lastrec);
240106163Sroberto		else
241106163Sroberto			return;
242106163Sroberto	} else {
243106163Sroberto		memcpy(pp->a_lastcode + pp->lencode, p, rbufp->recv_length);
244106163Sroberto		pp->lencode += rbufp->recv_length;
245106163Sroberto		pp->a_lastcode[pp->lencode] = '\0';
246106163Sroberto	}
247106163Sroberto
248106163Sroberto	if (pp->lencode < LENZYFER)
249106163Sroberto		return;
250106163Sroberto
251106163Sroberto	record_clock_stats(&peer->srcadr, pp->a_lastcode);
252106163Sroberto
253106163Sroberto	/*
254106163Sroberto	 * We get down to business, check the timecode format and decode
255106163Sroberto	 * its contents. If the timecode has invalid length or is not in
256106163Sroberto	 * proper format, we declare bad format and exit.
257106163Sroberto	 */
258106163Sroberto
259106163Sroberto	if (pp->lencode != LENZYFER) {
260106163Sroberto		refclock_report(peer, CEVNT_BADTIME);
261106163Sroberto		return;
262106163Sroberto	}
263106163Sroberto
264106163Sroberto	/*
265106163Sroberto	 * Timecode sample: "!TIME,2002,017,07,59,32,2,4,1"
266106163Sroberto	 */
267106163Sroberto	if (sscanf(pp->a_lastcode, "!TIME,%4d,%3d,%2d,%2d,%2d,%d,%d,%d",
268106163Sroberto		   &pp->year, &pp->day, &pp->hour, &pp->minute, &pp->second,
269106163Sroberto		   &tmode, &tfom, &omode) != 8) {
270106163Sroberto		refclock_report(peer, CEVNT_BADREPLY);
271106163Sroberto		return;
272106163Sroberto	}
273106163Sroberto
274106163Sroberto	if (tmode != 2) {
275106163Sroberto		refclock_report(peer, CEVNT_BADTIME);
276106163Sroberto		return;
277106163Sroberto	}
278106163Sroberto
279106163Sroberto	/* Should we make sure tfom is 4? */
280106163Sroberto
281106163Sroberto	if (omode != 1) {
282106163Sroberto		pp->leap = LEAP_NOTINSYNC;
283106163Sroberto		return;
284106163Sroberto	}
285285612Sdelphij
286106163Sroberto	if (!refclock_process(pp)) {
287106163Sroberto		refclock_report(peer, CEVNT_BADTIME);
288106163Sroberto		return;
289106163Sroberto        }
290106163Sroberto
291106163Sroberto	/*
292106163Sroberto	 * Good place for record_clock_stats()
293106163Sroberto	 */
294106163Sroberto	up->pollcnt = 2;
295106163Sroberto
296106163Sroberto	if (up->polled) {
297106163Sroberto		up->polled = 0;
298106163Sroberto		refclock_receive(peer);
299106163Sroberto	}
300106163Sroberto}
301106163Sroberto
302106163Sroberto
303106163Sroberto/*
304106163Sroberto * zyfer_poll - called by the transmit procedure
305106163Sroberto */
306106163Srobertostatic void
307106163Srobertozyfer_poll(
308106163Sroberto	int unit,
309106163Sroberto	struct peer *peer
310106163Sroberto	)
311106163Sroberto{
312106163Sroberto	register struct zyferunit *up;
313106163Sroberto	struct refclockproc *pp;
314106163Sroberto
315106163Sroberto	/*
316106163Sroberto	 * We don't really do anything here, except arm the receiving
317106163Sroberto	 * side to capture a sample and check for timeouts.
318106163Sroberto	 */
319106163Sroberto	pp = peer->procptr;
320285612Sdelphij	up = pp->unitptr;
321106163Sroberto	if (!up->pollcnt)
322106163Sroberto		refclock_report(peer, CEVNT_TIMEOUT);
323106163Sroberto	else
324106163Sroberto		up->pollcnt--;
325106163Sroberto	pp->polls++;
326106163Sroberto	up->polled = 1;
327106163Sroberto}
328106163Sroberto
329106163Sroberto#else
330106163Srobertoint refclock_zyfer_bs;
331106163Sroberto#endif /* REFCLOCK */
332