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