xref: /freebsd/contrib/ntp/ntpd/refclock_wwvb.c (revision 7773002178c8dbc52b44e4d705f07706409af8e4)
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	LENWWVB2	24	/* format 2 timecode length */
107 #define LENWWVB3        29      /* format 3 timecode length */
108 #define MONLIN		15	/* number of monitoring lines */
109 
110 /*
111  * WWVB unit control structure
112  */
113 struct wwvbunit {
114 	u_char	tcswitch;	/* timecode switch */
115 	l_fp	laststamp;	/* last receive timestamp */
116 	u_char	lasthour;	/* last hour (for monitor) */
117 	u_char	linect;		/* count ignored lines (for monitor */
118 };
119 
120 /*
121  * Function prototypes
122  */
123 static	int	wwvb_start	P((int, struct peer *));
124 static	void	wwvb_shutdown	P((int, struct peer *));
125 static	void	wwvb_receive	P((struct recvbuf *));
126 static	void	wwvb_poll	P((int, struct peer *));
127 
128 /*
129  * Transfer vector
130  */
131 struct	refclock refclock_wwvb = {
132 	wwvb_start,		/* start up driver */
133 	wwvb_shutdown,		/* shut down driver */
134 	wwvb_poll,		/* transmit poll message */
135 	noentry,		/* not used (old wwvb_control) */
136 	noentry,		/* initialize driver (not used) */
137 	noentry,		/* not used (old wwvb_buginfo) */
138 	NOFLAGS			/* not used */
139 };
140 
141 
142 /*
143  * wwvb_start - open the devices and initialize data for processing
144  */
145 static int
146 wwvb_start(
147 	int unit,
148 	struct peer *peer
149 	)
150 {
151 	register struct wwvbunit *up;
152 	struct refclockproc *pp;
153 	int fd;
154 	char device[20];
155 
156 	/*
157 	 * Open serial port. Use CLK line discipline, if available.
158 	 */
159 	(void)sprintf(device, DEVICE, unit);
160 	if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
161 		return (0);
162 
163 	/*
164 	 * Allocate and initialize unit structure
165 	 */
166 	if (!(up = (struct wwvbunit *)
167 	      emalloc(sizeof(struct wwvbunit)))) {
168 		(void) close(fd);
169 		return (0);
170 	}
171 	memset((char *)up, 0, sizeof(struct wwvbunit));
172 	pp = peer->procptr;
173 	pp->unitptr = (caddr_t)up;
174 	pp->io.clock_recv = wwvb_receive;
175 	pp->io.srcclock = (caddr_t)peer;
176 	pp->io.datalen = 0;
177 	pp->io.fd = fd;
178 	if (!io_addclock(&pp->io)) {
179 		(void) close(fd);
180 		free(up);
181 		return (0);
182 	}
183 
184 	/*
185 	 * Initialize miscellaneous variables
186 	 */
187 	peer->precision = PRECISION;
188 	pp->clockdesc = DESCRIPTION;
189 	memcpy((char *)&pp->refid, REFID, 4);
190 	peer->burst = NSTAGE;
191 	return (1);
192 }
193 
194 
195 /*
196  * wwvb_shutdown - shut down the clock
197  */
198 static void
199 wwvb_shutdown(
200 	int unit,
201 	struct peer *peer
202 	)
203 {
204 	register struct wwvbunit *up;
205 	struct refclockproc *pp;
206 
207 	pp = peer->procptr;
208 	up = (struct wwvbunit *)pp->unitptr;
209 	io_closeclock(&pp->io);
210 	free(up);
211 }
212 
213 
214 /*
215  * wwvb_receive - receive data from the serial interface
216  */
217 static void
218 wwvb_receive(
219 	struct recvbuf *rbufp
220 	)
221 {
222 	struct wwvbunit *up;
223 	struct refclockproc *pp;
224 	struct peer *peer;
225 
226 	l_fp	trtmp;		/* arrival timestamp */
227 	int	tz;		/* time zone */
228 	int	day, month;	/* ddd conversion */
229 	int	temp;		/* int temp */
230 	char	syncchar;	/* synchronization indicator */
231 	char	qualchar;	/* quality indicator */
232 	char	leapchar;	/* leap indicator */
233 	char	dstchar;	/* daylight/standard indicator */
234 	char	tmpchar;	/* trashbin */
235 
236 	/*
237 	 * Initialize pointers and read the timecode and timestamp
238 	 */
239 	peer = (struct peer *)rbufp->recv_srcclock;
240 	pp = peer->procptr;
241 	up = (struct wwvbunit *)pp->unitptr;
242 	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
243 
244 	/*
245 	 * Note we get a buffer and timestamp for both a <cr> and <lf>,
246 	 * but only the <cr> timestamp is retained. Note: in format 0 on
247 	 * a Netclock/2 or upgraded 8170 the start bit is delayed 100
248 	 * +-50 us relative to the pps; however, on an unmodified 8170
249 	 * the start bit can be delayed up to 10 ms. In format 2 the
250 	 * reading precision is only to the millisecond. Thus, unless
251 	 * you have a pps gadget and don't have to have the year, format
252 	 * 0 provides the lowest jitter.
253 	 */
254 	if (temp == 0) {
255 		if (up->tcswitch == 0) {
256 			up->tcswitch = 1;
257 			up->laststamp = trtmp;
258 		} else
259 		    up->tcswitch = 0;
260 		return;
261 	}
262 	pp->lencode = temp;
263 	pp->lastrec = up->laststamp;
264 	up->laststamp = trtmp;
265 	up->tcswitch = 1;
266 #ifdef DEBUG
267 	if (debug)
268 		printf("wwvb: timecode %d %s\n", pp->lencode,
269 		    pp->a_lastcode);
270 #endif
271 
272 	/*
273 	 * We get down to business, check the timecode format and decode
274 	 * its contents. This code uses the timecode length to determine
275 	 * format 0, 2 or 3. If the timecode has invalid length or is
276 	 * not in proper format, we declare bad format and exit.
277 	 */
278 	syncchar = qualchar = leapchar = dstchar = ' ';
279 	tz = 0;
280 	pp->msec = 0;
281 	switch (pp->lencode) {
282 
283 		case LENWWVB0:
284 
285 		/*
286 		 * Timecode format 0: "I  ddd hh:mm:ss DTZ=nn"
287 		 */
288 		if (sscanf(pp->a_lastcode,
289 		    "%c %3d %2d:%2d:%2d%c%cTZ=%2d",
290 		    &syncchar, &pp->day, &pp->hour, &pp->minute,
291 		    &pp->second, &tmpchar, &dstchar, &tz) == 8)
292 			break;
293 
294 		case LENWWVB2:
295 
296 		/*
297 		 * Timecode format 2: "IQyy ddd hh:mm:ss.mmm LD" */
298 		if (sscanf(pp->a_lastcode,
299 		    "%c%c %2d %3d %2d:%2d:%2d.%3d %c",
300 		    &syncchar, &qualchar, &pp->year, &pp->day,
301 		    &pp->hour, &pp->minute, &pp->second, &pp->msec,
302 		    &leapchar) == 9)
303 			break;
304 
305 		case LENWWVB3:
306 
307 	   	/*
308 		 * Timecode format 3: "0003I yyyymmdd hhmmss+0000SL#"
309 		 */
310 		if (sscanf(pp->a_lastcode,
311 		    "0003%c %4d%2d%2d %2d%2d%2d+0000%c%c",
312 		    &syncchar, &pp->year, &month, &day, &pp->hour,
313 		    &pp->minute, &pp->second, &dstchar, &leapchar) == 8)
314 		    {
315 			pp->day = ymd2yd(pp->year, month, day);
316 			break;
317 		}
318 
319 		default:
320 
321 		/*
322 		 * Unknown format: If dumping internal table, record
323 		 * stats; otherwise, declare bad format.
324 		 */
325 		if (up->linect > 0) {
326 			up->linect--;
327 			record_clock_stats(&peer->srcadr,
328 			    pp->a_lastcode);
329 		} else {
330 			refclock_report(peer, CEVNT_BADREPLY);
331 		}
332 		return;
333 	}
334 
335 	/*
336 	 * Decode synchronization, quality and leap characters. If
337 	 * unsynchronized, set the leap bits accordingly and exit.
338 	 * Otherwise, set the leap bits according to the leap character.
339 	 * Once synchronized, the dispersion depends only on the
340 	 * quality character.
341 	 */
342 	switch (qualchar) {
343 
344 	    case ' ':
345 		pp->disp = .001;
346 		break;
347 
348 	    case 'A':
349 		pp->disp = .01;
350 		break;
351 
352 	    case 'B':
353 		pp->disp = .1;
354 		break;
355 
356 	    case 'C':
357 		pp->disp = .5;
358 		break;
359 
360 	    case 'D':
361 		pp->disp = MAXDISPERSE;
362 		break;
363 
364 	    default:
365 		pp->disp = MAXDISPERSE;
366 		refclock_report(peer, CEVNT_BADREPLY);
367 		break;
368 	}
369 	if (syncchar != ' ')
370 		pp->leap = LEAP_NOTINSYNC;
371 	else if (leapchar == 'L')
372 		pp->leap = LEAP_ADDSECOND;
373 	else
374 		pp->leap = LEAP_NOWARNING;
375 
376 	/*
377 	 * Process the new sample in the median filter and determine the
378 	 * timecode timestamp.
379 	 */
380 	if (!refclock_process(pp))
381 		refclock_report(peer, CEVNT_BADTIME);
382 }
383 
384 
385 /*
386  * wwvb_poll - called by the transmit procedure
387  */
388 static void
389 wwvb_poll(
390 	int unit,
391 	struct peer *peer
392 	)
393 {
394 	register struct wwvbunit *up;
395 	struct refclockproc *pp;
396 	char	pollchar;	/* character sent to clock */
397 
398 	/*
399 	 * Time to poll the clock. The Spectracom clock responds to a
400 	 * 'T' by returning a timecode in the format(s) specified above.
401 	 * Note there is no checking on state, since this may not be the
402 	 * only customer reading the clock. Only one customer need poll
403 	 * the clock; all others just listen in. If the clock becomes
404 	 * unreachable, declare a timeout and keep going.
405 	 */
406 	pp = peer->procptr;
407 	up = (struct wwvbunit *)pp->unitptr;
408 	if (up->linect > 0)
409 		pollchar = 'R';
410 	else
411 		pollchar = 'T';
412 	if (write(pp->io.fd, &pollchar, 1) != 1)
413 		refclock_report(peer, CEVNT_FAULT);
414 	else
415 		pp->polls++;
416 	if (peer->burst > 0)
417 		return;
418 	if (pp->coderecv == pp->codeproc) {
419 		refclock_report(peer, CEVNT_TIMEOUT);
420 		return;
421 	}
422 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
423 	refclock_receive(peer);
424 	peer->burst = NSTAGE;
425 
426 	/*
427 	 * If the monitor flag is set (flag4), we dump the internal
428 	 * quality table at the first timecode beginning the day.
429 	 */
430 	if (pp->sloppyclockflag & CLK_FLAG4 && pp->hour <
431 	    (int)up->lasthour)
432 		up->linect = MONLIN;
433 	up->lasthour = pp->hour;
434 }
435 
436 #else
437 int refclock_wwvb_bs;
438 #endif /* REFCLOCK */
439