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