1/*
2 * refclock_trak - clock driver for the TRAK 8810 GPS Station Clock
3 *
4 * Tomoaki TSURUOKA <tsuruoka@nc.fukuoka-u.ac.jp>
5 *	original version  Dec, 1993
6 *	revised  version  Sep, 1994 for ntp3.4e or later
7 */
8
9#ifdef HAVE_CONFIG_H
10#include <config.h>
11#endif
12
13#if defined(REFCLOCK) && defined(CLOCK_TRAK) && defined(PPS)
14
15#include "ntpd.h"
16#include "ntp_io.h"
17#include "ntp_refclock.h"
18#include "ntp_stdlib.h"
19#include "ntp_unixtime.h"
20
21#include <stdio.h>
22#include <ctype.h>
23
24#ifdef 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 supports the TRAK 8810/8820 GPS Station Clock. The claimed
33 * accuracy at the 1-pps output is 200-300 ns relative to the broadcast
34 * signal; however, in most cases the actual accuracy is limited by the
35 * precision of the timecode and the latencies of the serial interface
36 * and operating system.
37 *
38 * For best accuracy, this radio requires the LDISC_ACTS line
39 * discipline, which captures a timestamp at the '*' on-time character
40 * of the timecode. Using this discipline the jitter is in the order of
41 * 1 ms and systematic error about 0.5 ms. If unavailable, the buffer
42 * timestamp is used, which is captured at the \r ending the timecode
43 * message. This introduces a systematic error of 23 character times, or
44 * about 24 ms at 9600 bps, together with a jitter well over 8 ms on Sun
45 * IPC-class machines.
46 *
47 * Using the memus, the radio should be set for 9600 bps, one stop bit
48 * and no parity. It should be set to operate in computer (no echo)
49 * mode. The timecode format includes neither the year nor leap-second
50 * warning. No provisions are included in this preliminary version of
51 * the driver to read and record detailed internal radio status.
52 *
53 * In operation, this driver sends a RQTS\r request to the radio at
54 * initialization in order to put it in continuous time output mode. The
55 * radio then sends the following message once each second:
56 *
57 *	*RQTS U,ddd:hh:mm:ss.0,q<cr><lf>
58 *
59 *	on-time = '*' *	ddd = day of year
60 *	hh:mm:ss = hours, minutes, seconds
61 *	q = quality indicator (phase error), 0-6:
62 * 		0 > 20 us
63 *		6 > 10 us
64 *		5 > 1 us
65 *		4 > 100 ns
66 *		3 > 10 ns
67 *		2 < 10 ns
68 *
69 * The alarm condition is indicated by '0' at Q, which means the radio
70 * has a phase error than 20 usec relative to the broadcast time. The
71 * absence of year, DST and leap-second warning in this format is also
72 * alarming.
73 *
74 * The continuous time mode is disabled using the RQTX<cr> request,
75 * following which the radio sends a RQTX DONE<cr><lf> response. In the
76 * normal mode, other control and status requests are effective,
77 * including the leap-second status request RQLS<cr>. The radio responds
78 * wtih RQLS yy,mm,dd<cr><lf>, where yy,mm,dd are the year, month and
79 * day. Presumably, this gives the epoch of the next leap second,
80 * RQLS 00,00,00 if none is specified in the GPS message. Specified in
81 * this form, the information is generally useless and is ignored by
82 * the driver.
83 *
84 * Fudge Factors
85 *
86 * There are no special fudge factors other than the generic.
87 */
88
89/*
90 * Interface definitions
91 */
92#define	DEVICE		"/dev/trak%d" /* device name and unit */
93#define	SPEED232	B9600	/* uart speed (9600 baud) */
94#define	PRECISION	(-20)	/* precision assumed (about 1 us) */
95#define	REFID		"GPS\0"	/* reference ID */
96#define	DESCRIPTION	"TRACK 8810/8820 Station Clock" /* WRU */
97
98#define	LENTRAK		24	/* timecode length */
99#define C_CTO		"RQTS\r" /* start continuous time output */
100
101/*
102 * Unit control structure
103 */
104struct trakunit {
105	int	polled;		/* poll message flag */
106	l_fp    tstamp;         /* timestamp of last poll */
107};
108
109/*
110 * Function prototypes
111 */
112static	int	trak_start	P((int, struct peer *));
113static	void	trak_shutdown	P((int, struct peer *));
114static	void	trak_receive	P((struct recvbuf *));
115static	void	trak_poll	P((int, struct peer *));
116
117/*
118 * Transfer vector
119 */
120struct	refclock refclock_trak = {
121	trak_start,		/* start up driver */
122	trak_shutdown,		/* shut down driver */
123	trak_poll,		/* transmit poll message */
124	noentry,		/* not used (old trak_control) */
125	noentry,		/* initialize driver (not used) */
126	noentry,		/* not used (old trak_buginfo) */
127	NOFLAGS			/* not used */
128};
129
130
131/*
132 * trak_start - open the devices and initialize data for processing
133 */
134static int
135trak_start(
136	int unit,
137	struct peer *peer
138	)
139{
140	register struct trakunit *up;
141	struct refclockproc *pp;
142	int fd;
143	char device[20];
144
145	/*
146	 * Open serial port. The LDISC_ACTS line discipline inserts a
147	 * timestamp following the "*" on-time character of the
148	 * timecode.
149	 */
150	(void)sprintf(device, DEVICE, unit);
151	if (
152#ifdef PPS
153		!(fd = refclock_open(device, SPEED232, LDISC_CLK))
154#else
155		!(fd = refclock_open(device, SPEED232, 0))
156#endif /* PPS */
157		)
158	    return (0);
159
160	/*
161	 * Allocate and initialize unit structure
162	 */
163	if (!(up = (struct trakunit *)
164	      emalloc(sizeof(struct trakunit)))) {
165		(void) close(fd);
166		return (0);
167	}
168	memset((char *)up, 0, sizeof(struct trakunit));
169	pp = peer->procptr;
170	pp->io.clock_recv = trak_receive;
171	pp->io.srcclock = (caddr_t)peer;
172	pp->io.datalen = 0;
173	pp->io.fd = fd;
174	if (!io_addclock(&pp->io)) {
175		(void) close(fd);
176		free(up);
177		return (0);
178	}
179	pp->unitptr = (caddr_t)up;
180
181	/*
182	 * Initialize miscellaneous variables
183	 */
184	peer->precision = PRECISION;
185	pp->clockdesc = DESCRIPTION;
186	memcpy((char *)&pp->refid, REFID, 4);
187	up->polled = 0;
188
189	/*
190	 * Start continuous time output. If something breaks, fold the
191	 * tent and go home.
192	 */
193	if (write(pp->io.fd, C_CTO, sizeof(C_CTO)) != sizeof(C_CTO)) {
194		refclock_report(peer, CEVNT_FAULT);
195		(void) close(fd);
196		free(up);
197		return (0);
198	}
199	return (1);
200}
201
202
203/*
204 * trak_shutdown - shut down the clock
205 */
206static void
207trak_shutdown(
208	int unit,
209	struct peer *peer
210	)
211{
212	register struct trakunit *up;
213	struct refclockproc *pp;
214
215	pp = peer->procptr;
216	up = (struct trakunit *)pp->unitptr;
217	io_closeclock(&pp->io);
218	free(up);
219}
220
221
222/*
223 * trak_receive - receive data from the serial interface
224 */
225static void
226trak_receive(
227	struct recvbuf *rbufp
228	)
229{
230	register struct trakunit *up;
231	struct refclockproc *pp;
232	struct peer *peer;
233	l_fp trtmp;
234	char *dpt, *dpend;
235	char qchar;
236#ifdef PPS
237	struct ppsclockev ppsev;
238	int request;
239#ifdef HAVE_CIOGETEV
240        request = CIOGETEV;
241#endif
242#ifdef HAVE_TIOCGPPSEV
243        request = TIOCGPPSEV;
244#endif
245#endif /* PPS */
246
247	/*
248	 * Initialize pointers and read the timecode and timestamp. We
249	 * then chuck out everything, including runts, except one
250	 * message each poll interval.
251	 */
252	peer = (struct peer *)rbufp->recv_srcclock;
253	pp = peer->procptr;
254	up = (struct trakunit *)pp->unitptr;
255	pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX,
256				     &pp->lastrec);
257
258	/*
259	 * We get a buffer and timestamp following the '*' on-time
260	 * character. If a valid timestamp, we use that in place of the
261	 * buffer timestamp and edit out the timestamp for prettyprint
262	 * billboards.
263	 */
264	dpt = pp->a_lastcode;
265	dpend = dpt + pp->lencode;
266	if (*dpt == '*' && buftvtots(dpt + 1, &trtmp)) {
267		if (trtmp.l_i == pp->lastrec.l_i || trtmp.l_i ==
268		    pp->lastrec.l_i + 1) {
269			pp->lastrec = trtmp;
270			dpt += 9;
271			while (dpt < dpend) {
272				*(dpt - 8) = *dpt;
273				++dpt;
274			}
275		}
276	}
277	if (up->polled == 0) return;
278	up->polled = 0;
279#ifndef PPS
280	get_systime(&up->tstamp);
281#endif
282	record_clock_stats(&peer->srcadr, pp->a_lastcode);
283#ifdef DEBUG
284	if (debug)
285	    printf("trak: timecode %d %s\n", pp->lencode,
286		   pp->a_lastcode);
287#endif
288
289	/*
290	 * We get down to business, check the timecode format and decode
291	 * its contents. If the timecode has invalid length or is not in
292	 * proper format, we declare bad format and exit.
293	 */
294	if (pp->lencode < LENTRAK) {
295		refclock_report(peer, CEVNT_BADREPLY);
296		return;
297	}
298
299	/*
300	 * Timecode format: "*RQTS U,ddd:hh:mm:ss.0,q"
301	 */
302	if (sscanf(pp->a_lastcode, "*RQTS U,%3d:%2d:%2d:%2d.0,%c",
303		   &pp->day, &pp->hour, &pp->minute, &pp->second, &qchar) != 5) {
304		refclock_report(peer, CEVNT_BADREPLY);
305		return;
306	}
307
308	/*
309	 * Decode quality and leap characters. If unsynchronized, set
310	 * the leap bits accordingly and exit.
311	 */
312	if (qchar == '0') {
313		pp->leap = LEAP_NOTINSYNC;
314		return;
315	}
316#ifdef PPS
317	if(ioctl(fdpps,request,(caddr_t) &ppsev) >=0) {
318		ppsev.tv.tv_sec += (u_int32) JAN_1970;
319		TVTOTS(&ppsev.tv,&up->tstamp);
320	}
321#endif /* PPS */
322	/* record the last ppsclock event time stamp */
323	pp->lastrec = up->tstamp;
324	if (!refclock_process(pp)) {
325		refclock_report(peer, CEVNT_BADTIME);
326		return;
327        }
328	pp->lastref = pp->lastrec;
329	refclock_receive(peer);
330}
331
332
333/*
334 * trak_poll - called by the transmit procedure
335 */
336static void
337trak_poll(
338	int unit,
339	struct peer *peer
340	)
341{
342	register struct trakunit *up;
343	struct refclockproc *pp;
344
345	/*
346	 * We don't really do anything here, except arm the receiving
347	 * side to capture a sample and check for timeouts.
348	 */
349	pp = peer->procptr;
350	up = (struct trakunit *)pp->unitptr;
351	if (up->polled)
352	    refclock_report(peer, CEVNT_TIMEOUT);
353	pp->polls++;
354	up->polled = 1;
355}
356
357#else
358int refclock_trak_bs;
359#endif /* REFCLOCK */
360