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