refclock_wwvb.c revision 290001
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 i, 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 i, 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	LENWWVB2	24	/* format 2 timecode length */
124#define LENWWVB3	29	/* format 3 timecode length */
125#define MONLIN		15	/* number of monitoring lines */
126
127/*
128 * WWVB unit control structure
129 */
130struct wwvbunit {
131#ifdef HAVE_PPSAPI
132	struct refclock_atom atom; /* PPSAPI structure */
133	int	ppsapi_tried;	/* attempt PPSAPI once */
134	int	ppsapi_lit;	/* time_pps_create() worked */
135	int	tcount;		/* timecode sample counter */
136	int	pcount;		/* PPS sample counter */
137#endif /* HAVE_PPSAPI */
138	l_fp	laststamp;	/* last <CR> timestamp */
139	int	prev_eol_cr;	/* was last EOL <CR> (not <LF>)? */
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, const 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	snprintf(device, sizeof(device), DEVICE, unit);
192	fd = refclock_open(device, SPEED232, LDISC_CLK);
193	if (fd <= 0)
194		return (0);
195
196	/*
197	 * Allocate and initialize unit structure
198	 */
199	up = emalloc_zero(sizeof(*up));
200	pp = peer->procptr;
201	pp->io.clock_recv = wwvb_receive;
202	pp->io.srcclock = peer;
203	pp->io.datalen = 0;
204	pp->io.fd = fd;
205	if (!io_addclock(&pp->io)) {
206		close(fd);
207		pp->io.fd = -1;
208		free(up);
209		return (0);
210	}
211	pp->unitptr = up;
212
213	/*
214	 * Initialize miscellaneous variables
215	 */
216	peer->precision = PRECISION;
217	pp->clockdesc = DESCRIPTION;
218	memcpy(&pp->refid, REFID, 4);
219	return (1);
220}
221
222
223/*
224 * wwvb_shutdown - shut down the clock
225 */
226static void
227wwvb_shutdown(
228	int unit,
229	struct peer *peer
230	)
231{
232	struct refclockproc *	pp;
233	struct wwvbunit *	up;
234
235	pp = peer->procptr;
236	up = pp->unitptr;
237	if (-1 != pp->io.fd)
238		io_closeclock(&pp->io);
239	if (NULL != up)
240		free(up);
241}
242
243
244/*
245 * wwvb_receive - receive data from the serial interface
246 */
247static void
248wwvb_receive(
249	struct recvbuf *rbufp
250	)
251{
252	struct wwvbunit *up;
253	struct refclockproc *pp;
254	struct peer *peer;
255
256	l_fp	trtmp;		/* arrival timestamp */
257	int	tz;		/* time zone */
258	int	day, month;	/* ddd conversion */
259	int	temp;		/* int temp */
260	char	syncchar;	/* synchronization indicator */
261	char	qualchar;	/* quality indicator */
262	char	leapchar;	/* leap indicator */
263	char	dstchar;	/* daylight/standard indicator */
264	char	tmpchar;	/* trashbin */
265
266	/*
267	 * Initialize pointers and read the timecode and timestamp
268	 */
269	peer = rbufp->recv_peer;
270	pp = peer->procptr;
271	up = pp->unitptr;
272	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
273
274	/*
275	 * Note we get a buffer and timestamp for both a <cr> and <lf>,
276	 * but only the <cr> timestamp is retained. Note: in format 0 on
277	 * a Netclock/2 or upgraded 8170 the start bit is delayed 100
278	 * +-50 us relative to the pps; however, on an unmodified 8170
279	 * the start bit can be delayed up to 10 ms. In format 2 the
280	 * reading precision is only to the millisecond. Thus, unless
281	 * you have a PPS gadget and don't have to have the year, format
282	 * 0 provides the lowest jitter.
283	 * Save the timestamp of each <CR> in up->laststamp.  Lines with
284	 * no characters occur for every <LF>, and for some <CR>s when
285	 * format 0 is used. Format 0 starts and ends each cycle with a
286	 * <CR><LF> pair, format 2 starts each cycle with its only pair.
287	 * The preceding <CR> is the on-time character for both formats.
288	 * The timestamp provided with non-empty lines corresponds to
289	 * the <CR> following the timecode, which is ultimately not used
290	 * with format 0 and is used for the following timecode for
291	 * format 2.
292	 */
293	if (temp == 0) {
294		if (up->prev_eol_cr) {
295			DPRINTF(2, ("wwvb: <LF> @ %s\n",
296				    prettydate(&trtmp)));
297		} else {
298			up->laststamp = trtmp;
299			DPRINTF(2, ("wwvb: <CR> @ %s\n",
300				    prettydate(&trtmp)));
301		}
302		up->prev_eol_cr = !up->prev_eol_cr;
303		return;
304	}
305	pp->lencode = temp;
306	pp->lastrec = up->laststamp;
307	up->laststamp = trtmp;
308	up->prev_eol_cr = TRUE;
309	DPRINTF(2, ("wwvb: code @ %s\n"
310		    "       using %s minus one char\n",
311		    prettydate(&trtmp), prettydate(&pp->lastrec)));
312	if (L_ISZERO(&pp->lastrec))
313		return;
314
315	/*
316	 * We get down to business, check the timecode format and decode
317	 * its contents. This code uses the timecode length to determine
318	 * format 0, 2 or 3. If the timecode has invalid length or is
319	 * not in proper format, we declare bad format and exit.
320	 */
321	syncchar = qualchar = leapchar = dstchar = ' ';
322	tz = 0;
323	switch (pp->lencode) {
324
325	case LENWWVB0:
326
327		/*
328		 * Timecode format 0: "I  ddd hh:mm:ss DTZ=nn"
329		 */
330		if (sscanf(pp->a_lastcode,
331		    "%c %3d %2d:%2d:%2d%c%cTZ=%2d",
332		    &syncchar, &pp->day, &pp->hour, &pp->minute,
333		    &pp->second, &tmpchar, &dstchar, &tz) == 8) {
334			pp->nsec = 0;
335			break;
336		}
337		goto bad_format;
338
339	case LENWWVB2:
340
341		/*
342		 * Timecode format 2: "IQyy ddd hh:mm:ss.mmm LD" */
343		if (sscanf(pp->a_lastcode,
344		    "%c%c %2d %3d %2d:%2d:%2d.%3ld %c",
345		    &syncchar, &qualchar, &pp->year, &pp->day,
346		    &pp->hour, &pp->minute, &pp->second, &pp->nsec,
347		    &leapchar) == 9) {
348			pp->nsec *= 1000000;
349			break;
350		}
351		goto bad_format;
352
353	case LENWWVB3:
354
355		/*
356		 * Timecode format 3: "0003I yyyymmdd hhmmss+0000SL#"
357		 * WARNING: Undocumented, and the on-time character # is
358		 * not yet handled correctly by this driver.  It may be
359		 * as simple as compensating for an additional 1/960 s.
360		 */
361		if (sscanf(pp->a_lastcode,
362		    "0003%c %4d%2d%2d %2d%2d%2d+0000%c%c",
363		    &syncchar, &pp->year, &month, &day, &pp->hour,
364		    &pp->minute, &pp->second, &dstchar, &leapchar) == 8)
365		    {
366			pp->day = ymd2yd(pp->year, month, day);
367			pp->nsec = 0;
368			break;
369		}
370		goto bad_format;
371
372	default:
373	bad_format:
374
375		/*
376		 * Unknown format: If dumping internal table, record
377		 * stats; otherwise, declare bad format.
378		 */
379		if (up->linect > 0) {
380			up->linect--;
381			record_clock_stats(&peer->srcadr,
382			    pp->a_lastcode);
383		} else {
384			refclock_report(peer, CEVNT_BADREPLY);
385		}
386		return;
387	}
388
389	/*
390	 * Decode synchronization, quality and leap characters. If
391	 * unsynchronized, set the leap bits accordingly and exit.
392	 * Otherwise, set the leap bits according to the leap character.
393	 * Once synchronized, the dispersion depends only on the
394	 * quality character.
395	 */
396	switch (qualchar) {
397
398	case ' ':
399		pp->disp = .001;
400		pp->lastref = pp->lastrec;
401		break;
402
403	case 'A':
404		pp->disp = .01;
405		break;
406
407	case 'B':
408		pp->disp = .1;
409		break;
410
411	case 'C':
412		pp->disp = .5;
413		break;
414
415	case 'D':
416		pp->disp = MAXDISPERSE;
417		break;
418
419	default:
420		pp->disp = MAXDISPERSE;
421		refclock_report(peer, CEVNT_BADREPLY);
422		break;
423	}
424	if (syncchar != ' ')
425		pp->leap = LEAP_NOTINSYNC;
426	else if (leapchar == 'L')
427		pp->leap = LEAP_ADDSECOND;
428	else
429		pp->leap = LEAP_NOWARNING;
430
431	/*
432	 * Process the new sample in the median filter and determine the
433	 * timecode timestamp, but only if the PPS is not in control.
434	 */
435#ifdef HAVE_PPSAPI
436	up->tcount++;
437	if (peer->flags & FLAG_PPS)
438		return;
439
440#endif /* HAVE_PPSAPI */
441	if (!refclock_process_f(pp, pp->fudgetime2))
442		refclock_report(peer, CEVNT_BADTIME);
443}
444
445
446/*
447 * wwvb_timer - called once per second by the transmit procedure
448 */
449static void
450wwvb_timer(
451	int unit,
452	struct peer *peer
453	)
454{
455	register struct wwvbunit *up;
456	struct refclockproc *pp;
457	char	pollchar;	/* character sent to clock */
458#ifdef DEBUG
459	l_fp	now;
460#endif
461
462	/*
463	 * Time to poll the clock. The Spectracom clock responds to a
464	 * 'T' by returning a timecode in the format(s) specified above.
465	 * Note there is no checking on state, since this may not be the
466	 * only customer reading the clock. Only one customer need poll
467	 * the clock; all others just listen in.
468	 */
469	pp = peer->procptr;
470	up = pp->unitptr;
471	if (up->linect > 0)
472		pollchar = 'R';
473	else
474		pollchar = 'T';
475	if (write(pp->io.fd, &pollchar, 1) != 1)
476		refclock_report(peer, CEVNT_FAULT);
477#ifdef DEBUG
478	get_systime(&now);
479	if (debug)
480		printf("%c poll at %s\n", pollchar, prettydate(&now));
481#endif
482#ifdef HAVE_PPSAPI
483	if (up->ppsapi_lit &&
484	    refclock_pps(peer, &up->atom, pp->sloppyclockflag) > 0) {
485		up->pcount++,
486		peer->flags |= FLAG_PPS;
487		peer->precision = PPS_PRECISION;
488	}
489#endif /* HAVE_PPSAPI */
490}
491
492
493/*
494 * wwvb_poll - called by the transmit procedure
495 */
496static void
497wwvb_poll(
498	int unit,
499	struct peer *peer
500	)
501{
502	register struct wwvbunit *up;
503	struct refclockproc *pp;
504
505	/*
506	 * Sweep up the samples received since the last poll. If none
507	 * are received, declare a timeout and keep going.
508	 */
509	pp = peer->procptr;
510	up = pp->unitptr;
511	pp->polls++;
512
513	/*
514	 * If the monitor flag is set (flag4), we dump the internal
515	 * quality table at the first timecode beginning the day.
516	 */
517	if (pp->sloppyclockflag & CLK_FLAG4 && pp->hour <
518	    (int)up->lasthour)
519		up->linect = MONLIN;
520	up->lasthour = (u_char)pp->hour;
521
522	/*
523	 * Process median filter samples. If none received, declare a
524	 * timeout and keep going.
525	 */
526#ifdef HAVE_PPSAPI
527	if (up->pcount == 0) {
528		peer->flags &= ~FLAG_PPS;
529		peer->precision = PRECISION;
530	}
531	if (up->tcount == 0) {
532		pp->coderecv = pp->codeproc;
533		refclock_report(peer, CEVNT_TIMEOUT);
534		return;
535	}
536	up->pcount = up->tcount = 0;
537#else /* HAVE_PPSAPI */
538	if (pp->coderecv == pp->codeproc) {
539		refclock_report(peer, CEVNT_TIMEOUT);
540		return;
541	}
542#endif /* HAVE_PPSAPI */
543	refclock_receive(peer);
544	record_clock_stats(&peer->srcadr, pp->a_lastcode);
545#ifdef DEBUG
546	if (debug)
547		printf("wwvb: timecode %d %s\n", pp->lencode,
548		    pp->a_lastcode);
549#endif
550}
551
552
553/*
554 * wwvb_control - fudge parameters have been set or changed
555 */
556#ifdef HAVE_PPSAPI
557static void
558wwvb_control(
559	int unit,
560	const struct refclockstat *in_st,
561	struct refclockstat *out_st,
562	struct peer *peer
563	)
564{
565	register struct wwvbunit *up;
566	struct refclockproc *pp;
567
568	pp = peer->procptr;
569	up = pp->unitptr;
570
571	if (!(pp->sloppyclockflag & CLK_FLAG1)) {
572		if (!up->ppsapi_tried)
573			return;
574		up->ppsapi_tried = 0;
575		if (!up->ppsapi_lit)
576			return;
577		peer->flags &= ~FLAG_PPS;
578		peer->precision = PRECISION;
579		time_pps_destroy(up->atom.handle);
580		up->atom.handle = 0;
581		up->ppsapi_lit = 0;
582		return;
583	}
584
585	if (up->ppsapi_tried)
586		return;
587	/*
588	 * Light up the PPSAPI interface.
589	 */
590	up->ppsapi_tried = 1;
591	if (refclock_ppsapi(pp->io.fd, &up->atom)) {
592		up->ppsapi_lit = 1;
593		return;
594	}
595
596	msyslog(LOG_WARNING, "%s flag1 1 but PPSAPI fails",
597		refnumtoa(&peer->srcadr));
598}
599#endif	/* HAVE_PPSAPI */
600
601#else
602int refclock_wwvb_bs;
603#endif /* REFCLOCK */
604