1/*
2 * refclock_wwvb - clock driver for Spectracom WWVB and GPS receivers
3 */
4
5#ifdef HAVE_CONFIG_H
6#include <config.h>
7#endif
8
9#if defined(REFCLOCK) && defined(CLOCK_SPECTRACOM)
10
11#include "ntpd.h"
12#include "ntp_io.h"
13#include "ntp_refclock.h"
14#include "ntp_calendar.h"
15#include "ntp_stdlib.h"
16
17#include <stdio.h>
18#include <ctype.h>
19
20#ifdef HAVE_PPSAPI
21#include "ppsapi_timepps.h"
22#include "refclock_atom.h"
23#endif /* HAVE_PPSAPI */
24
25/*
26 * This driver supports the Spectracom Model 8170 and Netclock/2 WWVB
27 * Synchronized Clocks and the Netclock/GPS Master Clock. Both the WWVB
28 * and GPS clocks have proven reliable sources of time; however, the
29 * WWVB clocks have proven vulnerable to high ambient conductive RF
30 * interference. The claimed accuracy of the WWVB clocks is 100 us
31 * relative to the broadcast signal, while the claimed accuracy of the
32 * GPS clock is 50 ns; however, in most cases the actual accuracy is
33 * limited by the resolution of the timecode and the latencies of the
34 * serial interface and operating system.
35 *
36 * The WWVB and GPS clocks should be configured for 24-hour display,
37 * AUTO DST off, time zone 0 (UTC), data format 0 or 2 (see below) and
38 * baud rate 9600. If the clock is to used as the source for the IRIG
39 * Audio Decoder (refclock_irig.c in this distribution), it should be
40 * configured for AM IRIG output and IRIG format 1 (IRIG B with
41 * signature control). The GPS clock can be configured either to respond
42 * to a 'T' poll character or left running continuously.
43 *
44 * There are two timecode formats used by these clocks. Format 0, which
45 * is available with both the Netclock/2 and 8170, and format 2, which
46 * is available only with the Netclock/2, specially modified 8170 and
47 * GPS.
48 *
49 * Format 0 (22 ASCII printing characters):
50 *
51 * <cr><lf>i  ddd hh:mm:ss TZ=zz<cr><lf>
52 *
53 *	on-time = first <cr>
54 *	hh:mm:ss = hours, minutes, seconds
55 *	i = synchronization flag (' ' = in synch, '?' = out of synch)
56 *
57 * The alarm condition is indicated by other than ' ' at a, which occurs
58 * during initial synchronization and when received signal is lost for
59 * about ten hours.
60 *
61 * Format 2 (24 ASCII printing characters):
62 *
63 * <cr><lf>iqyy ddd hh:mm:ss.fff ld
64 *
65 *	on-time = <cr>
66 *	i = synchronization flag (' ' = in synch, '?' = out of synch)
67 *	q = quality indicator (' ' = locked, 'A'...'D' = unlocked)
68 *	yy = year (as broadcast)
69 *	ddd = day of year
70 *	hh:mm:ss.fff = hours, minutes, seconds, milliseconds
71 *
72 * The alarm condition is indicated by other than ' ' at a, which occurs
73 * during initial synchronization and when received signal is lost for
74 * about ten hours. The unlock condition is indicated by other than ' '
75 * at q.
76 *
77 * The q is normally ' ' when the time error is less than 1 ms and a
78 * character in the set 'A'...'D' when the time error is less than 10,
79 * 100, 500 and greater than 500 ms respectively. The l is normally ' ',
80 * but is set to 'L' early in the month of an upcoming UTC leap second
81 * and reset to ' ' on the first day of the following month. The d is
82 * set to 'S' for standard time 'I' on the day preceding a switch to
83 * daylight time, 'D' for daylight time and 'O' on the day preceding a
84 * switch to standard time. The start bit of the first <cr> is
85 * synchronized to the indicated time as returned.
86 *
87 * This driver does not need to be told which format is in use - it
88 * figures out which one from the length of the message. The driver
89 * makes no attempt to correct for the intrinsic jitter of the radio
90 * itself, which is a known problem with the older radios.
91 *
92 * PPS Signal Processing
93 *
94 * When PPS signal processing is enabled, and when the system clock has
95 * been set by this or another driver and the PPS signal offset is
96 * within 0.4 s of the system clock offset, the PPS signal replaces the
97 * timecode for as long as the PPS signal is active. If for some reason
98 * the PPS signal fails for one or more poll intervals, the driver
99 * reverts to the timecode. If the timecode fails for one or more poll
100 * intervals, the PPS signal is disconnected.
101 *
102 * Fudge Factors
103 *
104 * This driver can retrieve a table of quality data maintained
105 * internally by the Netclock/2 clock. If flag4 of the fudge
106 * configuration command is set to 1, the driver will retrieve this
107 * table and write it to the clockstats file when the first timecode
108 * message of a new day is received.
109 *
110 * PPS calibration fudge time 1: format 0 .003134, format 2 .004034
111 */
112/*
113 * Interface definitions
114 */
115#define	DEVICE		"/dev/wwvb%d" /* device name and unit */
116#define	SPEED232	B9600	/* uart speed (9600 baud) */
117#define	PRECISION	(-13)	/* precision assumed (about 100 us) */
118#define	PPS_PRECISION	(-13)	/* precision assumed (about 100 us) */
119#define	REFID		"WWVB"	/* reference ID */
120#define	DESCRIPTION	"Spectracom WWVB/GPS Receiver" /* WRU */
121
122#define	LENWWVB0	22	/* format 0 timecode length */
123#define LENWWVB1	22	/* format 1 timecode length */
124#define	LENWWVB2	24	/* format 2 timecode length */
125#define LENWWVB3        29      /* format 3 timecode length */
126#define MONLIN		15	/* number of monitoring lines */
127
128/*
129 * WWVB unit control structure
130 */
131struct wwvbunit {
132#ifdef HAVE_PPSAPI
133	struct refclock_atom atom; /* PPSAPI structure */
134	int	ppsapi_tried;	/* attempt PPSAPI once */
135	int	ppsapi_lit;	/* time_pps_create() worked */
136	int	tcount;		/* timecode sample counter */
137	int	pcount;		/* PPS sample counter */
138#endif /* HAVE_PPSAPI */
139	l_fp	laststamp;	/* last receive timestamp */
140	u_char	lasthour;	/* last hour (for monitor) */
141	u_char	linect;		/* count ignored lines (for monitor */
142};
143
144/*
145 * Function prototypes
146 */
147static	int	wwvb_start	(int, struct peer *);
148static	void	wwvb_shutdown	(int, struct peer *);
149static	void	wwvb_receive	(struct recvbuf *);
150static	void	wwvb_poll	(int, struct peer *);
151static	void	wwvb_timer	(int, struct peer *);
152#ifdef HAVE_PPSAPI
153static	void	wwvb_control	(int, struct refclockstat *,
154				 struct refclockstat *, struct peer *);
155#define		WWVB_CONTROL	wwvb_control
156#else
157#define		WWVB_CONTROL	noentry
158#endif /* HAVE_PPSAPI */
159
160/*
161 * Transfer vector
162 */
163struct	refclock refclock_wwvb = {
164	wwvb_start,		/* start up driver */
165	wwvb_shutdown,		/* shut down driver */
166	wwvb_poll,		/* transmit poll message */
167	WWVB_CONTROL,		/* fudge set/change notification */
168	noentry,		/* initialize driver (not used) */
169	noentry,		/* not used (old wwvb_buginfo) */
170	wwvb_timer		/* called once per second */
171};
172
173
174/*
175 * wwvb_start - open the devices and initialize data for processing
176 */
177static int
178wwvb_start(
179	int unit,
180	struct peer *peer
181	)
182{
183	register struct wwvbunit *up;
184	struct refclockproc *pp;
185	int fd;
186	char device[20];
187
188	/*
189	 * Open serial port. Use CLK line discipline, if available.
190	 */
191	sprintf(device, DEVICE, unit);
192	if (-1 == (fd = refclock_open(device, SPEED232, LDISC_CLK)))
193		return (0);
194
195	/*
196	 * Allocate and initialize unit structure
197	 */
198	up = (struct wwvbunit *)emalloc(sizeof(struct wwvbunit));
199	memset((char *)up, 0, sizeof(struct wwvbunit));
200	pp = peer->procptr;
201	pp->unitptr = (caddr_t)up;
202	pp->io.clock_recv = wwvb_receive;
203	pp->io.srcclock = (caddr_t)peer;
204	pp->io.datalen = 0;
205	pp->io.fd = fd;
206	if (!io_addclock(&pp->io)) {
207		close(fd);
208		free(up);
209		return (0);
210	}
211
212	/*
213	 * Initialize miscellaneous variables
214	 */
215	peer->precision = PRECISION;
216	pp->clockdesc = DESCRIPTION;
217	memcpy((char *)&pp->refid, REFID, 4);
218	return (1);
219}
220
221
222/*
223 * wwvb_shutdown - shut down the clock
224 */
225static void
226wwvb_shutdown(
227	int unit,
228	struct peer *peer
229	)
230{
231	register struct wwvbunit *up;
232	struct refclockproc *pp;
233
234	pp = peer->procptr;
235	up = (struct wwvbunit *)pp->unitptr;
236	io_closeclock(&pp->io);
237	free(up);
238}
239
240
241/*
242 * wwvb_receive - receive data from the serial interface
243 */
244static void
245wwvb_receive(
246	struct recvbuf *rbufp
247	)
248{
249	struct wwvbunit *up;
250	struct refclockproc *pp;
251	struct peer *peer;
252
253	l_fp	trtmp;		/* arrival timestamp */
254	int	tz;		/* time zone */
255	int	day, month;	/* ddd conversion */
256	int	temp;		/* int temp */
257	char	syncchar;	/* synchronization indicator */
258	char	qualchar;	/* quality indicator */
259	char	leapchar;	/* leap indicator */
260	char	dstchar;	/* daylight/standard indicator */
261	char	tmpchar;	/* trashbin */
262
263	/*
264	 * Initialize pointers and read the timecode and timestamp
265	 */
266	peer = (struct peer *)rbufp->recv_srcclock;
267	pp = peer->procptr;
268	up = (struct wwvbunit *)pp->unitptr;
269	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
270
271	/*
272	 * Note we get a buffer and timestamp for both a <cr> and <lf>,
273	 * but only the <cr> timestamp is retained. Note: in format 0 on
274	 * a Netclock/2 or upgraded 8170 the start bit is delayed 100
275	 * +-50 us relative to the pps; however, on an unmodified 8170
276	 * the start bit can be delayed up to 10 ms. In format 2 the
277	 * reading precision is only to the millisecond. Thus, unless
278	 * you have a PPS gadget and don't have to have the year, format
279	 * 0 provides the lowest jitter.
280	 */
281	if (temp == 0) {
282		up->laststamp = trtmp;
283		return;
284	}
285	pp->lencode = temp;
286	pp->lastrec = up->laststamp;
287
288	/*
289	 * We get down to business, check the timecode format and decode
290	 * its contents. This code uses the timecode length to determine
291	 * format 0, 2 or 3. If the timecode has invalid length or is
292	 * not in proper format, we declare bad format and exit.
293	 */
294	syncchar = qualchar = leapchar = dstchar = ' ';
295	tz = 0;
296	switch (pp->lencode) {
297
298	case LENWWVB0:
299
300		/*
301		 * Timecode format 0: "I  ddd hh:mm:ss DTZ=nn"
302		 */
303		if (sscanf(pp->a_lastcode,
304		    "%c %3d %2d:%2d:%2d%c%cTZ=%2d",
305		    &syncchar, &pp->day, &pp->hour, &pp->minute,
306		    &pp->second, &tmpchar, &dstchar, &tz) == 8)
307			pp->nsec = 0;
308			break;
309
310	case LENWWVB2:
311
312		/*
313		 * Timecode format 2: "IQyy ddd hh:mm:ss.mmm LD" */
314		if (sscanf(pp->a_lastcode,
315		    "%c%c %2d %3d %2d:%2d:%2d.%3ld %c",
316		    &syncchar, &qualchar, &pp->year, &pp->day,
317		    &pp->hour, &pp->minute, &pp->second, &pp->nsec,
318		    &leapchar) == 9)
319			pp->nsec *= 1000000;
320			break;
321
322	case LENWWVB3:
323
324	   	/*
325		 * Timecode format 3: "0003I yyyymmdd hhmmss+0000SL#"
326		 */
327		if (sscanf(pp->a_lastcode,
328		    "0003%c %4d%2d%2d %2d%2d%2d+0000%c%c",
329		    &syncchar, &pp->year, &month, &day, &pp->hour,
330		    &pp->minute, &pp->second, &dstchar, &leapchar) == 8)
331		    {
332			pp->day = ymd2yd(pp->year, month, day);
333			pp->nsec = 0;
334			break;
335		}
336
337	default:
338
339		/*
340		 * Unknown format: If dumping internal table, record
341		 * stats; otherwise, declare bad format.
342		 */
343		if (up->linect > 0) {
344			up->linect--;
345			record_clock_stats(&peer->srcadr,
346			    pp->a_lastcode);
347		} else {
348			refclock_report(peer, CEVNT_BADREPLY);
349		}
350		return;
351	}
352
353	/*
354	 * Decode synchronization, quality and leap characters. If
355	 * unsynchronized, set the leap bits accordingly and exit.
356	 * Otherwise, set the leap bits according to the leap character.
357	 * Once synchronized, the dispersion depends only on the
358	 * quality character.
359	 */
360	switch (qualchar) {
361
362	    case ' ':
363		pp->disp = .001;
364		pp->lastref = pp->lastrec;
365		break;
366
367	    case 'A':
368		pp->disp = .01;
369		break;
370
371	    case 'B':
372		pp->disp = .1;
373		break;
374
375	    case 'C':
376		pp->disp = .5;
377		break;
378
379	    case 'D':
380		pp->disp = MAXDISPERSE;
381		break;
382
383	    default:
384		pp->disp = MAXDISPERSE;
385		refclock_report(peer, CEVNT_BADREPLY);
386		break;
387	}
388	if (syncchar != ' ')
389		pp->leap = LEAP_NOTINSYNC;
390	else if (leapchar == 'L')
391		pp->leap = LEAP_ADDSECOND;
392	else
393		pp->leap = LEAP_NOWARNING;
394
395	/*
396	 * Process the new sample in the median filter and determine the
397	 * timecode timestamp, but only if the PPS is not in control.
398	 */
399#ifdef HAVE_PPSAPI
400	up->tcount++;
401	if (peer->flags & FLAG_PPS)
402		return;
403
404#endif /* HAVE_PPSAPI */
405	if (!refclock_process_f(pp, pp->fudgetime2))
406		refclock_report(peer, CEVNT_BADTIME);
407}
408
409
410/*
411 * wwvb_timer - called once per second by the transmit procedure
412 */
413static void
414wwvb_timer(
415	int unit,
416	struct peer *peer
417	)
418{
419	register struct wwvbunit *up;
420	struct refclockproc *pp;
421	char	pollchar;	/* character sent to clock */
422
423	/*
424	 * Time to poll the clock. The Spectracom clock responds to a
425	 * 'T' by returning a timecode in the format(s) specified above.
426	 * Note there is no checking on state, since this may not be the
427	 * only customer reading the clock. Only one customer need poll
428	 * the clock; all others just listen in.
429	 */
430	pp = peer->procptr;
431	up = (struct wwvbunit *)pp->unitptr;
432	if (up->linect > 0)
433		pollchar = 'R';
434	else
435		pollchar = 'T';
436	if (write(pp->io.fd, &pollchar, 1) != 1)
437		refclock_report(peer, CEVNT_FAULT);
438#ifdef HAVE_PPSAPI
439	if (up->ppsapi_lit &&
440	    refclock_pps(peer, &up->atom, pp->sloppyclockflag) > 0) {
441		up->pcount++,
442		peer->flags |= FLAG_PPS;
443		peer->precision = PPS_PRECISION;
444	}
445#endif /* HAVE_PPSAPI */
446}
447
448
449/*
450 * wwvb_poll - called by the transmit procedure
451 */
452static void
453wwvb_poll(
454	int unit,
455	struct peer *peer
456	)
457{
458	register struct wwvbunit *up;
459	struct refclockproc *pp;
460
461	/*
462	 * Sweep up the samples received since the last poll. If none
463	 * are received, declare a timeout and keep going.
464	 */
465	pp = peer->procptr;
466	up = (struct wwvbunit *)pp->unitptr;
467	pp->polls++;
468
469	/*
470	 * If the monitor flag is set (flag4), we dump the internal
471	 * quality table at the first timecode beginning the day.
472	 */
473	if (pp->sloppyclockflag & CLK_FLAG4 && pp->hour <
474	    (int)up->lasthour)
475		up->linect = MONLIN;
476	up->lasthour = (u_char)pp->hour;
477
478	/*
479	 * Process median filter samples. If none received, declare a
480	 * timeout and keep going.
481	 */
482#ifdef HAVE_PPSAPI
483	if (up->pcount == 0) {
484		peer->flags &= ~FLAG_PPS;
485		peer->precision = PRECISION;
486	}
487	if (up->tcount == 0) {
488		pp->coderecv = pp->codeproc;
489		refclock_report(peer, CEVNT_TIMEOUT);
490		return;
491	}
492	up->pcount = up->tcount = 0;
493#else /* HAVE_PPSAPI */
494	if (pp->coderecv == pp->codeproc) {
495		refclock_report(peer, CEVNT_TIMEOUT);
496		return;
497	}
498#endif /* HAVE_PPSAPI */
499	refclock_receive(peer);
500	record_clock_stats(&peer->srcadr, pp->a_lastcode);
501#ifdef DEBUG
502	if (debug)
503		printf("wwvb: timecode %d %s\n", pp->lencode,
504		    pp->a_lastcode);
505#endif
506}
507
508
509/*
510 * wwvb_control - fudge parameters have been set or changed
511 */
512#ifdef HAVE_PPSAPI
513static void
514wwvb_control(
515	int unit,
516	struct refclockstat *in_st,
517	struct refclockstat *out_st,
518	struct peer *peer
519	)
520{
521	register struct wwvbunit *up;
522	struct refclockproc *pp;
523
524	pp = peer->procptr;
525	up = (struct wwvbunit *)pp->unitptr;
526
527	if (!(pp->sloppyclockflag & CLK_FLAG1)) {
528		if (!up->ppsapi_tried)
529			return;
530		up->ppsapi_tried = 0;
531		if (!up->ppsapi_lit)
532			return;
533		peer->flags &= ~FLAG_PPS;
534		peer->precision = PRECISION;
535		time_pps_destroy(up->atom.handle);
536		up->atom.handle = 0;
537		up->ppsapi_lit = 0;
538		return;
539	}
540
541	if (up->ppsapi_tried)
542		return;
543	/*
544	 * Light up the PPSAPI interface.
545	 */
546	up->ppsapi_tried = 1;
547	if (refclock_ppsapi(pp->io.fd, &up->atom)) {
548		up->ppsapi_lit = 1;
549		return;
550	}
551
552	NLOG(NLOG_CLOCKINFO)
553		msyslog(LOG_WARNING, "%s flag1 1 but PPSAPI fails",
554			refnumtoa(&peer->srcadr));
555}
556#endif	/* HAVE_PPSAPI */
557
558#else
559int refclock_wwvb_bs;
560#endif /* REFCLOCK */
561