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