xref: /freebsd/contrib/ntp/ntpd/refclock_ulink.c (revision adeb92a24c57f97d5cd3c3c45be239cbb23aed68)
1 /*
2  * refclock_ulink - clock driver for Ultralink  WWVB receiver
3  *
4  */
5 
6 /***********************************************************************
7  *                                                                     *
8  * Copyright (c) David L. Mills 1992-1998                              *
9  *                                                                     *
10  * Permission to use, copy, modify, and distribute this software and   *
11  * its documentation for any purpose and without fee is hereby         *
12  * granted, provided that the above copyright notice appears in all    *
13  * copies and that both the copyright notice and this permission       *
14  * notice appear in supporting documentation, and that the name        *
15  * University of Delaware not be used in advertising or publicity      *
16  * pertaining to distribution of the software without specific,        *
17  * written prior permission. The University of Delaware makes no       *
18  * representations about the suitability this software for any         *
19  * purpose. It is provided "as is" without express or implied          *
20  * warranty.                                                           *
21  **********************************************************************/
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #if defined(REFCLOCK) && defined(CLOCK_ULINK)
28 
29 #include <stdio.h>
30 #include <ctype.h>
31 
32 #include "ntpd.h"
33 #include "ntp_io.h"
34 #include "ntp_refclock.h"
35 #include "ntp_calendar.h"
36 #include "ntp_stdlib.h"
37 
38 /*
39  * This driver supports ultralink Model 320,330,331,332 WWVB radios
40  *
41  * this driver was based on the refclock_wwvb.c driver
42  * in the ntp distribution.
43  *
44  * Fudge Factors
45  *
46  * fudge flag1 0 don't poll clock
47  *             1 send poll character
48  *
49  * revision history:
50  *		99/9/09 j.c.lang	original edit's
51  *		99/9/11 j.c.lang	changed timecode parse to
52  *                                      match what the radio actually
53  *                                      sends.
54  *              99/10/11 j.c.lang       added support for continous
55  *                                      time code mode (dipsw2)
56  *		99/11/26 j.c.lang	added support for 320 decoder
57  *                                      (taken from Dave Strout's
58  *                                      Model 320 driver)
59  *		99/11/29 j.c.lang	added fudge flag 1 to control
60  *					clock polling
61  *		99/12/15 j.c.lang	fixed 320 quality flag
62  *		01/02/21 s.l.smith	fixed 33x quality flag
63  *					added more debugging stuff
64  *					updated 33x time code explanation
65  *
66  * Questions, bugs, ideas send to:
67  *	Joseph C. Lang
68  *	tcnojl1@earthlink.net
69  *
70  *	Dave Strout
71  *	dstrout@linuxfoundry.com
72  *
73  *
74  * on the Ultralink model 33X decoder Dip switch 2 controls
75  * polled or continous timecode
76  * set fudge flag1 if using polled (needed for model 320)
77  * dont set fudge flag1 if dip switch 2 is set on model 33x decoder
78 */
79 
80 
81 /*
82  * Interface definitions
83  */
84 #define	DEVICE		"/dev/wwvb%d" /* device name and unit */
85 #define	SPEED232	B9600	/* uart speed (9600 baud) */
86 #define	PRECISION	(-10)	/* precision assumed (about 10 ms) */
87 #define	REFID		"WWVB"	/* reference ID */
88 #define	DESCRIPTION	"Ultralink WWVB Receiver" /* WRU */
89 
90 #define	LEN33X		32	/* timecode length Model 325 & 33X */
91 #define LEN320		24	/* timecode length Model 320 */
92 
93 /*
94  *  unit control structure
95  */
96 struct ulinkunit {
97 	u_char	tcswitch;	/* timecode switch */
98 	l_fp	laststamp;	/* last receive timestamp */
99 };
100 
101 /*
102  * Function prototypes
103  */
104 static	int	ulink_start	P((int, struct peer *));
105 static	void	ulink_shutdown	P((int, struct peer *));
106 static	void	ulink_receive	P((struct recvbuf *));
107 static	void	ulink_poll	P((int, struct peer *));
108 
109 /*
110  * Transfer vector
111  */
112 struct	refclock refclock_ulink = {
113 	ulink_start,		/* start up driver */
114 	ulink_shutdown,		/* shut down driver */
115 	ulink_poll,		/* transmit poll message */
116 	noentry,		/* not used  */
117 	noentry,		/* not used  */
118 	noentry,		/* not used  */
119 	NOFLAGS
120 };
121 
122 
123 /*
124  * ulink_start - open the devices and initialize data for processing
125  */
126 static int
127 ulink_start(
128 	int unit,
129 	struct peer *peer
130 	)
131 {
132 	register struct ulinkunit *up;
133 	struct refclockproc *pp;
134 	int fd;
135 	char device[20];
136 
137 	/*
138 	 * Open serial port. Use CLK line discipline, if available.
139 	 */
140 	(void)sprintf(device, DEVICE, unit);
141 	if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
142 		return (0);
143 
144 	/*
145 	 * Allocate and initialize unit structure
146 	 */
147 	if (!(up = (struct ulinkunit *)
148 	      emalloc(sizeof(struct ulinkunit)))) {
149 		(void) close(fd);
150 		return (0);
151 	}
152 	memset((char *)up, 0, sizeof(struct ulinkunit));
153 	pp = peer->procptr;
154 	pp->unitptr = (caddr_t)up;
155 	pp->io.clock_recv = ulink_receive;
156 	pp->io.srcclock = (caddr_t)peer;
157 	pp->io.datalen = 0;
158 	pp->io.fd = fd;
159 	if (!io_addclock(&pp->io)) {
160 		(void) close(fd);
161 		free(up);
162 		return (0);
163 	}
164 
165 	/*
166 	 * Initialize miscellaneous variables
167 	 */
168 	peer->precision = PRECISION;
169 	peer->burst = NSTAGE;
170 	pp->clockdesc = DESCRIPTION;
171 	memcpy((char *)&pp->refid, REFID, 4);
172 	return (1);
173 }
174 
175 
176 /*
177  * ulink_shutdown - shut down the clock
178  */
179 static void
180 ulink_shutdown(
181 	int unit,
182 	struct peer *peer
183 	)
184 {
185 	register struct ulinkunit *up;
186 	struct refclockproc *pp;
187 
188 	pp = peer->procptr;
189 	up = (struct ulinkunit *)pp->unitptr;
190 	io_closeclock(&pp->io);
191 	free(up);
192 }
193 
194 
195 /*
196  * ulink_receive - receive data from the serial interface
197  */
198 static void
199 ulink_receive(
200 	struct recvbuf *rbufp
201 	)
202 {
203 	struct ulinkunit *up;
204 	struct refclockproc *pp;
205 	struct peer *peer;
206 
207 	l_fp	trtmp;		/* arrival timestamp */
208 	int	quality;	/* quality indicator */
209 	int	temp;		/* int temp */
210 	char	syncchar;	/* synchronization indicator */
211 	char	leapchar;	/* leap indicator */
212 	char	modechar;	/* model 320 mode flag */
213 	char	char_quality[2];	/* temp quality flag */
214 
215 	/*
216 	 * Initialize pointers and read the timecode and timestamp
217 	 */
218 	peer = (struct peer *)rbufp->recv_srcclock;
219 	pp = peer->procptr;
220 	up = (struct ulinkunit *)pp->unitptr;
221 	temp = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
222 
223 	/*
224 	 * Note we get a buffer and timestamp for both a <cr> and <lf>,
225 	 * but only the <cr> timestamp is retained.
226 	 */
227 	if (temp == 0) {
228 		if (up->tcswitch == 0) {
229 			up->tcswitch = 1;
230 			up->laststamp = trtmp;
231 		} else
232 		    up->tcswitch = 0;
233 		return;
234 	}
235 	pp->lencode = temp;
236 	pp->lastrec = up->laststamp;
237 	up->laststamp = trtmp;
238 	up->tcswitch = 1;
239 #ifdef DEBUG
240 	if (debug)
241 		printf("ulink: timecode %d %s\n", pp->lencode,
242 		    pp->a_lastcode);
243 #endif
244 
245 	/*
246 	 * We get down to business, check the timecode format and decode
247 	 * its contents. If the timecode has invalid length or is not in
248 	 * proper format, we declare bad format and exit.
249 	 */
250 	syncchar = leapchar = modechar = ' ';
251 	pp->msec = 0;
252 
253 	switch (pp->lencode ) {
254 		case LEN33X:
255 		/*
256 		 * Model 33X decoder:
257 		 * Timecode format from January 29, 2001 datasheet is:
258 		 *   <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5
259 		 *   S      WWVB decoder sync indicator. S for in-sync(?)
260 		 *          or N for noisy signal.
261 		 *   9+     RF signal level in S-units, 0-9 followed by
262 		 *          a space (0x20). The space turns to '+' if the
263 		 *          level is over 9.
264 		 *   D      Data bit 0, 1, 2 (position mark), or
265 		 *          3 (unknown).
266 		 *   space  Space character (0x20)
267 		 *   00     Hours since last good WWVB frame sync. Will
268 		 *          be 00-23 hrs, or '1d' to '7d'. Will be 'Lk'
269                  *          if currently in sync.
270 		 *   space  Space character (0x20)
271 		 *   YYYY   Current year, 1990-2089
272 		 *   +      Leap year indicator. '+' if a leap year,
273 		 *          a space (0x20) if not.
274 		 *   DDD    Day of year, 001 - 366.
275 		 *   UTC    Timezone (always 'UTC').
276 		 *   S      Daylight savings indicator
277 		 *             S - standard time (STD) in effect
278 		 *             O - during STD to DST day 0000-2400
279 		 *             D - daylight savings time (DST) in effect
280 		 *             I - during DST to STD day 0000-2400
281 		 *   space  Space character (0x20)
282 		 *   HH     Hours 00-23
283 		 *   :      This is the REAL in sync indicator (: = insync)
284 		 *   MM     Minutes 00-59
285 		 *   :      : = in sync ? = NOT in sync
286 		 *   SS     Seconds 00-59
287 		 *   L      Leap second flag. Changes from space (0x20)
288 		 *          to '+' or '-' during month preceding leap
289 		 *          second adjustment.
290 		 *   +5     UT1 correction (sign + digit ))
291 		 */
292 
293 		if (sscanf(pp->a_lastcode,
294                     "%*4c %2c %4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c",
295 		    char_quality, &pp->year, &pp->day,
296                     &pp->hour, &syncchar, &pp->minute, &pp->second,
297                     &leapchar) == 8) {
298 
299 			if (char_quality[0] == 'L') {
300 				quality = 0;
301 			} else if (char_quality[0] == '0') {
302 				quality = (char_quality[1] & 0x0f);
303 			} else  {
304 				quality = 99;
305 			}
306 
307 /*
308 #ifdef DEBUG
309 		if (debug) {
310 			printf("ulink: char_quality %c %c\n",
311                                char_quality[0], char_quality[1]);
312 			printf("ulink: quality %d\n", quality);
313 			printf("ulink: syncchar %x\n", syncchar);
314 			printf("ulink: leapchar %x\n", leapchar);
315                 }
316 #endif
317 */
318 
319 			break;
320 		}
321 
322 		case LEN320:
323 	        /*
324 		 * Model 320 Decoder
325 		 * The timecode format is:
326 		 *
327 		 *  <cr><lf>SQRYYYYDDD+HH:MM:SS.mmLT<cr>
328 		 *
329 		 * where:
330 		 *
331 		 * S = 'S' -- sync'd in last hour,
332 		 *     '0'-'9' - hours x 10 since last update,
333 		 *     '?' -- not in sync
334 		 * Q = Number of correlating time-frames, from 0 to 5
335 		 * R = 'R' -- reception in progress,
336 		 *     'N' -- Noisy reception,
337 		 *     ' ' -- standby mode
338 		 * YYYY = year from 1990 to 2089
339 		 * DDD = current day from 1 to 366
340 		 * + = '+' if current year is a leap year, else ' '
341 		 * HH = UTC hour 0 to 23
342 		 * MM = Minutes of current hour from 0 to 59
343 		 * SS = Seconds of current minute from 0 to 59
344 		 * mm = 10's milliseconds of the current second from 00 to 99
345 		 * L  = Leap second pending at end of month
346 		 *     'I' = insert, 'D'= delete
347 		 * T  = DST <-> STD transition indicators
348 		 *
349         	 */
350 		if (sscanf(pp->a_lastcode, "%c%1d%c%4d%3d%*c%2d:%2d:%2d.%2d%c",
351 	               &syncchar, &quality, &modechar, &pp->year, &pp->day,
352         	       &pp->hour, &pp->minute, &pp->second,
353 			&pp->msec,&leapchar) == 10) {
354 		pp->msec *= 10; /* M320 returns 10's of msecs */
355 		if (leapchar == 'I' ) leapchar = '+';
356 		if (leapchar == 'D' ) leapchar = '-';
357 		if (syncchar != '?' ) syncchar = ':';
358 
359  		break;
360 		}
361 
362 		default:
363 		refclock_report(peer, CEVNT_BADREPLY);
364 		return;
365 	}
366 
367 
368 	/*
369 	 * Decode quality indicator
370 	 * For the 325 & 33x series, the lower the number the "better"
371 	 * the time is. I used the dispersion as the measure of time
372 	 * quality. The quality indicator in the 320 is the number of
373 	 * correlating time frames (the more the better)
374 	 */
375 
376 	/*
377 	 * The spec sheet for the 325 & 33x series states the clock will
378 	 * maintain +/-0.002 seconds accuracy when locked to WWVB. This
379 	 * is indicated by 'Lk' in the quality portion of the incoming
380 	 * string. When not in lock, a drift of +/-0.015 seconds should
381 	 * be allowed for.
382 	 * With the quality indicator decoding scheme above, the 'Lk'
383 	 * condition will produce a quality value of 0. If the quality
384 	 * indicator starts with '0' then the second character is the
385 	 * number of hours since we were last locked. If the first
386 	 * character is anything other than 'L' or '0' then we have been
387 	 * out of lock for more than 9 hours so we assume the worst and
388 	 * force a quality value that selects the 'default' maximum
389 	 * dispersion. The dispersion values below are what came with the
390 	 * driver. They're not unreasonable so they've not been changed.
391 	 */
392 
393 	if (pp->lencode == LEN33X) {
394 		switch (quality) {
395 			case 0 :
396 				pp->disp=.002;
397 				break;
398 			case 1 :
399 				pp->disp=.02;
400 				break;
401 			case 2 :
402 				pp->disp=.04;
403 				break;
404 			case 3 :
405 				pp->disp=.08;
406 				break;
407 			default:
408 				pp->disp=MAXDISPERSE;
409 				break;
410 		}
411 	} else {
412 		switch (quality) {
413 			case 5 :
414 				pp->disp=.002;
415 				break;
416 			case 4 :
417 				pp->disp=.02;
418 				break;
419 			case 3 :
420 				pp->disp=.04;
421 				break;
422 			case 2 :
423 				pp->disp=.08;
424 				break;
425 			case 1 :
426 				pp->disp=.16;
427 				break;
428 			default:
429 				pp->disp=MAXDISPERSE;
430 				break;
431 		}
432 
433 	}
434 
435 	/*
436 	 * Decode synchronization, and leap characters. If
437 	 * unsynchronized, set the leap bits accordingly and exit.
438 	 * Otherwise, set the leap bits according to the leap character.
439 	 */
440 
441 	if (syncchar != ':')
442 		pp->leap = LEAP_NOTINSYNC;
443 	else if (leapchar == '+')
444 		pp->leap = LEAP_ADDSECOND;
445 	else if (leapchar == '-')
446 		pp->leap = LEAP_DELSECOND;
447 	else
448 		pp->leap = LEAP_NOWARNING;
449 
450 	/*
451 	 * Process the new sample in the median filter and determine the
452 	 * timecode timestamp.
453 	 */
454 	if (!refclock_process(pp)) {
455 		refclock_report(peer, CEVNT_BADTIME);
456 	}
457 
458 }
459 
460 
461 /*
462  * ulink_poll - called by the transmit procedure
463  */
464 static void
465 ulink_poll(
466 	int unit,
467 	struct peer *peer
468 	)
469 {
470         struct refclockproc *pp;
471         char pollchar;
472 
473         pp = peer->procptr;
474         pollchar = 'T';
475 	if (pp->sloppyclockflag & CLK_FLAG1) {
476 	        if (write(pp->io.fd, &pollchar, 1) != 1)
477         	        refclock_report(peer, CEVNT_FAULT);
478         	else
479       	            pp->polls++;
480 	}
481 	else
482       	            pp->polls++;
483 
484         if (peer->burst > 0)
485                 return;
486         if (pp->coderecv == pp->codeproc) {
487                 refclock_report(peer, CEVNT_TIMEOUT);
488                 return;
489         }
490         record_clock_stats(&peer->srcadr, pp->a_lastcode);
491         refclock_receive(peer);
492         peer->burst = NSTAGE;
493 
494 }
495 
496 #else
497 int refclock_ulink_bs;
498 #endif /* REFCLOCK */
499