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
22106163Sroberto#ifdef HAVE_SYS_TERMIOS_H
23106163Sroberto# include <sys/termios.h>
24106163Sroberto#endif
25106163Sroberto#ifdef HAVE_SYS_PPSCLOCK_H
26106163Sroberto# include <sys/ppsclock.h>
27106163Sroberto#endif
28106163Sroberto
29106163Sroberto/*
30106163Sroberto * This driver provides support for the TOD serial port of a Zyfer GPStarplus.
31106163Sroberto * This clock also provides PPS as well as IRIG outputs.
32106163Sroberto * Precision is limited by the serial driver, etc.
33106163Sroberto *
34106163Sroberto * If I was really brave I'd hack/generalize the serial driver to deal
35106163Sroberto * with arbitrary on-time characters.  This clock *begins* the stream with
36106163Sroberto * `!`, the on-time character, and the string is *not* EOL-terminated.
37106163Sroberto *
38106163Sroberto * Configure the beast for 9600, 8N1.  While I see leap-second stuff
39106163Sroberto * in the documentation, the published specs on the TOD format only show
40106163Sroberto * the seconds going to '59'.  I see no leap warning in the TOD format.
41106163Sroberto *
42106163Sroberto * The clock sends the following message once per second:
43106163Sroberto *
44106163Sroberto *	!TIME,2002,017,07,59,32,2,4,1
45106163Sroberto *	      YYYY DDD HH MM SS m T O
46106163Sroberto *
47106163Sroberto *	!		On-time character
48106163Sroberto *	YYYY		Year
49106163Sroberto *	DDD	001-366	Day of Year
50106163Sroberto *	HH	00-23	Hour
51106163Sroberto *	MM	00-59	Minute
52106163Sroberto *	SS	00-59	Second (probably 00-60)
53106163Sroberto *	m	1-5	Time Mode:
54106163Sroberto *			1 = GPS time
55106163Sroberto *			2 = UTC time
56106163Sroberto *			3 = LGPS time (Local GPS)
57106163Sroberto *			4 = LUTC time (Local UTC)
58106163Sroberto *			5 = Manual time
59106163Sroberto *	T	4-9	Time Figure Of Merit:
60106163Sroberto *			4         x <= 1us
61106163Sroberto *			5   1us < x <= 10 us
62106163Sroberto *			6  10us < x <= 100us
63106163Sroberto *			7 100us < x <= 1ms
64106163Sroberto *			8   1ms < x <= 10ms
65106163Sroberto *			9  10ms < x
66106163Sroberto *	O	0-4	Operation Mode:
67106163Sroberto *			0 Warm-up
68106163Sroberto *			1 Time Locked
69106163Sroberto *			2 Coasting
70106163Sroberto *			3 Recovering
71106163Sroberto *			4 Manual
72106163Sroberto *
73106163Sroberto */
74106163Sroberto
75106163Sroberto/*
76106163Sroberto * Interface definitions
77106163Sroberto */
78106163Sroberto#define	DEVICE		"/dev/zyfer%d" /* device name and unit */
79106163Sroberto#define	SPEED232	B9600	/* uart speed (9600 baud) */
80106163Sroberto#define	PRECISION	(-20)	/* precision assumed (about 1 us) */
81106163Sroberto#define	REFID		"GPS\0"	/* reference ID */
82106163Sroberto#define	DESCRIPTION	"Zyfer GPStarplus" /* WRU */
83106163Sroberto
84106163Sroberto#define	LENZYFER	29	/* timecode length */
85106163Sroberto
86106163Sroberto/*
87106163Sroberto * Unit control structure
88106163Sroberto */
89106163Srobertostruct zyferunit {
90106163Sroberto	u_char	Rcvbuf[LENZYFER + 1];
91106163Sroberto	u_char	polled;		/* poll message flag */
92106163Sroberto	int	pollcnt;
93106163Sroberto	l_fp    tstamp;         /* timestamp of last poll */
94106163Sroberto	int	Rcvptr;
95106163Sroberto};
96106163Sroberto
97106163Sroberto/*
98106163Sroberto * Function prototypes
99106163Sroberto */
100106163Srobertostatic	int	zyfer_start	P((int, struct peer *));
101106163Srobertostatic	void	zyfer_shutdown	P((int, struct peer *));
102106163Srobertostatic	void	zyfer_receive	P((struct recvbuf *));
103106163Srobertostatic	void	zyfer_poll	P((int, struct peer *));
104106163Sroberto
105106163Sroberto/*
106106163Sroberto * Transfer vector
107106163Sroberto */
108106163Srobertostruct	refclock refclock_zyfer = {
109106163Sroberto	zyfer_start,		/* start up driver */
110106163Sroberto	zyfer_shutdown,		/* shut down driver */
111106163Sroberto	zyfer_poll,		/* transmit poll message */
112106163Sroberto	noentry,		/* not used (old zyfer_control) */
113106163Sroberto	noentry,		/* initialize driver (not used) */
114106163Sroberto	noentry,		/* not used (old zyfer_buginfo) */
115106163Sroberto	NOFLAGS			/* not used */
116106163Sroberto};
117106163Sroberto
118106163Sroberto
119106163Sroberto/*
120106163Sroberto * zyfer_start - open the devices and initialize data for processing
121106163Sroberto */
122106163Srobertostatic int
123106163Srobertozyfer_start(
124106163Sroberto	int unit,
125106163Sroberto	struct peer *peer
126106163Sroberto	)
127106163Sroberto{
128106163Sroberto	register struct zyferunit *up;
129106163Sroberto	struct refclockproc *pp;
130106163Sroberto	int fd;
131106163Sroberto	char device[20];
132106163Sroberto
133106163Sroberto	/*
134106163Sroberto	 * Open serial port.
135106163Sroberto	 * Something like LDISC_ACTS that looked for ! would be nice...
136106163Sroberto	 */
137106163Sroberto	(void)sprintf(device, DEVICE, unit);
138106163Sroberto	if ( !(fd = refclock_open(device, SPEED232, LDISC_RAW)) )
139106163Sroberto	    return (0);
140106163Sroberto
141106163Sroberto	msyslog(LOG_NOTICE, "zyfer(%d) fd: %d dev <%s>", unit, fd, device);
142106163Sroberto
143106163Sroberto	/*
144106163Sroberto	 * Allocate and initialize unit structure
145106163Sroberto	 */
146106163Sroberto	if (!(up = (struct zyferunit *)
147106163Sroberto	      emalloc(sizeof(struct zyferunit)))) {
148106163Sroberto		(void) close(fd);
149106163Sroberto		return (0);
150106163Sroberto	}
151106163Sroberto	memset((char *)up, 0, sizeof(struct zyferunit));
152106163Sroberto	pp = peer->procptr;
153106163Sroberto	pp->io.clock_recv = zyfer_receive;
154106163Sroberto	pp->io.srcclock = (caddr_t)peer;
155106163Sroberto	pp->io.datalen = 0;
156106163Sroberto	pp->io.fd = fd;
157106163Sroberto	if (!io_addclock(&pp->io)) {
158106163Sroberto		(void) close(fd);
159106163Sroberto		free(up);
160106163Sroberto		return (0);
161106163Sroberto	}
162106163Sroberto	pp->unitptr = (caddr_t)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;
190106163Sroberto	up = (struct zyferunit *)pp->unitptr;
191106163Sroberto	io_closeclock(&pp->io);
192106163Sroberto	free(up);
193106163Sroberto}
194106163Sroberto
195106163Sroberto
196106163Sroberto/*
197106163Sroberto * zyfer_receive - receive data from the serial interface
198106163Sroberto */
199106163Srobertostatic void
200106163Srobertozyfer_receive(
201106163Sroberto	struct recvbuf *rbufp
202106163Sroberto	)
203106163Sroberto{
204106163Sroberto	register struct zyferunit *up;
205106163Sroberto	struct refclockproc *pp;
206106163Sroberto	struct peer *peer;
207106163Sroberto	int tmode;		/* Time mode */
208106163Sroberto	int tfom;		/* Time Figure Of Merit */
209106163Sroberto	int omode;		/* Operation mode */
210106163Sroberto	u_char *p;
211106163Sroberto#ifdef PPS
212106163Sroberto	struct ppsclockev ppsev;
213106163Sroberto	int request;
214106163Sroberto#ifdef HAVE_CIOGETEV
215106163Sroberto        request = CIOGETEV;
216106163Sroberto#endif
217106163Sroberto#ifdef HAVE_TIOCGPPSEV
218106163Sroberto        request = TIOCGPPSEV;
219106163Sroberto#endif
220106163Sroberto#endif /* PPS */
221106163Sroberto
222106163Sroberto	peer = (struct peer *)rbufp->recv_srcclock;
223106163Sroberto	pp = peer->procptr;
224106163Sroberto	up = (struct zyferunit *)pp->unitptr;
225106163Sroberto	p = (u_char *) &rbufp->recv_space;
226106163Sroberto	/*
227106163Sroberto	 * If lencode is 0:
228106163Sroberto	 * - if *rbufp->recv_space is !
229106163Sroberto	 * - - call refclock_gtlin to get things going
230106163Sroberto	 * - else flush
231106163Sroberto	 * else stuff it on the end of lastcode
232106163Sroberto	 * If we don't have LENZYFER bytes
233106163Sroberto	 * - wait for more data
234106163Sroberto	 * Crack the beast, and if it's OK, process it.
235106163Sroberto	 *
236106424Sroberto	 * We use refclock_gtlin() because we might use LDISC_CLK.
237106163Sroberto	 *
238106163Sroberto	 * Under FreeBSD, we get the ! followed by two 14-byte packets.
239106163Sroberto	 */
240106163Sroberto
241106163Sroberto	if (pp->lencode >= LENZYFER)
242106163Sroberto		pp->lencode = 0;
243106163Sroberto
244106163Sroberto	if (!pp->lencode) {
245106163Sroberto		if (*p == '!')
246106163Sroberto			pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode,
247106163Sroberto						     BMAX, &pp->lastrec);
248106163Sroberto		else
249106163Sroberto			return;
250106163Sroberto	} else {
251106163Sroberto		memcpy(pp->a_lastcode + pp->lencode, p, rbufp->recv_length);
252106163Sroberto		pp->lencode += rbufp->recv_length;
253106163Sroberto		pp->a_lastcode[pp->lencode] = '\0';
254106163Sroberto	}
255106163Sroberto
256106163Sroberto	if (pp->lencode < LENZYFER)
257106163Sroberto		return;
258106163Sroberto
259106163Sroberto	record_clock_stats(&peer->srcadr, pp->a_lastcode);
260106163Sroberto
261106163Sroberto	/*
262106163Sroberto	 * We get down to business, check the timecode format and decode
263106163Sroberto	 * its contents. If the timecode has invalid length or is not in
264106163Sroberto	 * proper format, we declare bad format and exit.
265106163Sroberto	 */
266106163Sroberto
267106163Sroberto	if (pp->lencode != LENZYFER) {
268106163Sroberto		refclock_report(peer, CEVNT_BADTIME);
269106163Sroberto		return;
270106163Sroberto	}
271106163Sroberto
272106163Sroberto	/*
273106163Sroberto	 * Timecode sample: "!TIME,2002,017,07,59,32,2,4,1"
274106163Sroberto	 */
275106163Sroberto	if (sscanf(pp->a_lastcode, "!TIME,%4d,%3d,%2d,%2d,%2d,%d,%d,%d",
276106163Sroberto		   &pp->year, &pp->day, &pp->hour, &pp->minute, &pp->second,
277106163Sroberto		   &tmode, &tfom, &omode) != 8) {
278106163Sroberto		refclock_report(peer, CEVNT_BADREPLY);
279106163Sroberto		return;
280106163Sroberto	}
281106163Sroberto
282106163Sroberto	if (tmode != 2) {
283106163Sroberto		refclock_report(peer, CEVNT_BADTIME);
284106163Sroberto		return;
285106163Sroberto	}
286106163Sroberto
287106163Sroberto	/* Should we make sure tfom is 4? */
288106163Sroberto
289106163Sroberto	if (omode != 1) {
290106163Sroberto		pp->leap = LEAP_NOTINSYNC;
291106163Sroberto		return;
292106163Sroberto	}
293106163Sroberto#ifdef PPS
294106163Sroberto	if(ioctl(fdpps,request,(caddr_t) &ppsev) >=0) {
295106163Sroberto		ppsev.tv.tv_sec += (u_int32) JAN_1970;
296106163Sroberto		TVTOTS(&ppsev.tv,&up->tstamp);
297106163Sroberto	}
298106163Sroberto	/* record the last ppsclock event time stamp */
299106163Sroberto	pp->lastrec = up->tstamp;
300106163Sroberto#endif /* PPS */
301106163Sroberto	if (!refclock_process(pp)) {
302106163Sroberto		refclock_report(peer, CEVNT_BADTIME);
303106163Sroberto		return;
304106163Sroberto        }
305106163Sroberto
306106163Sroberto	/*
307106163Sroberto	 * Good place for record_clock_stats()
308106163Sroberto	 */
309106163Sroberto	up->pollcnt = 2;
310106163Sroberto
311106163Sroberto	if (up->polled) {
312106163Sroberto		up->polled = 0;
313106163Sroberto		refclock_receive(peer);
314106163Sroberto	}
315106163Sroberto}
316106163Sroberto
317106163Sroberto
318106163Sroberto/*
319106163Sroberto * zyfer_poll - called by the transmit procedure
320106163Sroberto */
321106163Srobertostatic void
322106163Srobertozyfer_poll(
323106163Sroberto	int unit,
324106163Sroberto	struct peer *peer
325106163Sroberto	)
326106163Sroberto{
327106163Sroberto	register struct zyferunit *up;
328106163Sroberto	struct refclockproc *pp;
329106163Sroberto
330106163Sroberto	/*
331106163Sroberto	 * We don't really do anything here, except arm the receiving
332106163Sroberto	 * side to capture a sample and check for timeouts.
333106163Sroberto	 */
334106163Sroberto	pp = peer->procptr;
335106163Sroberto	up = (struct zyferunit *)pp->unitptr;
336106163Sroberto	if (!up->pollcnt)
337106163Sroberto		refclock_report(peer, CEVNT_TIMEOUT);
338106163Sroberto	else
339106163Sroberto		up->pollcnt--;
340106163Sroberto	pp->polls++;
341106163Sroberto	up->polled = 1;
342106163Sroberto}
343106163Sroberto
344106163Sroberto#else
345106163Srobertoint refclock_zyfer_bs;
346106163Sroberto#endif /* REFCLOCK */
347