xref: /freebsd/contrib/ntp/ntpd/refclock_ulink.c (revision 7bd6fde3)
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 	switch (pp->lencode ) {
252 		case LEN33X:
253 		/*
254 		 * Model 33X decoder:
255 		 * Timecode format from January 29, 2001 datasheet is:
256 		 *   <CR><LF>S9+D 00 YYYY+DDDUTCS HH:MM:SSL+5
257 		 *   S      WWVB decoder sync indicator. S for in-sync(?)
258 		 *          or N for noisy signal.
259 		 *   9+     RF signal level in S-units, 0-9 followed by
260 		 *          a space (0x20). The space turns to '+' if the
261 		 *          level is over 9.
262 		 *   D      Data bit 0, 1, 2 (position mark), or
263 		 *          3 (unknown).
264 		 *   space  Space character (0x20)
265 		 *   00     Hours since last good WWVB frame sync. Will
266 		 *          be 00-23 hrs, or '1d' to '7d'. Will be 'Lk'
267                  *          if currently in sync.
268 		 *   space  Space character (0x20)
269 		 *   YYYY   Current year, 1990-2089
270 		 *   +      Leap year indicator. '+' if a leap year,
271 		 *          a space (0x20) if not.
272 		 *   DDD    Day of year, 001 - 366.
273 		 *   UTC    Timezone (always 'UTC').
274 		 *   S      Daylight savings indicator
275 		 *             S - standard time (STD) in effect
276 		 *             O - during STD to DST day 0000-2400
277 		 *             D - daylight savings time (DST) in effect
278 		 *             I - during DST to STD day 0000-2400
279 		 *   space  Space character (0x20)
280 		 *   HH     Hours 00-23
281 		 *   :      This is the REAL in sync indicator (: = insync)
282 		 *   MM     Minutes 00-59
283 		 *   :      : = in sync ? = NOT in sync
284 		 *   SS     Seconds 00-59
285 		 *   L      Leap second flag. Changes from space (0x20)
286 		 *          to '+' or '-' during month preceding leap
287 		 *          second adjustment.
288 		 *   +5     UT1 correction (sign + digit ))
289 		 */
290 
291 		if (sscanf(pp->a_lastcode,
292                     "%*4c %2c %4d%*c%3d%*4c %2d%c%2d:%2d%c%*2c",
293 		    char_quality, &pp->year, &pp->day,
294                     &pp->hour, &syncchar, &pp->minute, &pp->second,
295                     &leapchar) == 8) {
296 
297 			if (char_quality[0] == 'L') {
298 				quality = 0;
299 			} else if (char_quality[0] == '0') {
300 				quality = (char_quality[1] & 0x0f);
301 			} else  {
302 				quality = 99;
303 			}
304 
305 /*
306 #ifdef DEBUG
307 		if (debug) {
308 			printf("ulink: char_quality %c %c\n",
309                                char_quality[0], char_quality[1]);
310 			printf("ulink: quality %d\n", quality);
311 			printf("ulink: syncchar %x\n", syncchar);
312 			printf("ulink: leapchar %x\n", leapchar);
313                 }
314 #endif
315 */
316 
317 			break;
318 		}
319 
320 		case LEN320:
321 	        /*
322 		 * Model 320 Decoder
323 		 * The timecode format is:
324 		 *
325 		 *  <cr><lf>SQRYYYYDDD+HH:MM:SS.mmLT<cr>
326 		 *
327 		 * where:
328 		 *
329 		 * S = 'S' -- sync'd in last hour,
330 		 *     '0'-'9' - hours x 10 since last update,
331 		 *     '?' -- not in sync
332 		 * Q = Number of correlating time-frames, from 0 to 5
333 		 * R = 'R' -- reception in progress,
334 		 *     'N' -- Noisy reception,
335 		 *     ' ' -- standby mode
336 		 * YYYY = year from 1990 to 2089
337 		 * DDD = current day from 1 to 366
338 		 * + = '+' if current year is a leap year, else ' '
339 		 * HH = UTC hour 0 to 23
340 		 * MM = Minutes of current hour from 0 to 59
341 		 * SS = Seconds of current minute from 0 to 59
342 		 * mm = 10's milliseconds of the current second from 00 to 99
343 		 * L  = Leap second pending at end of month
344 		 *     'I' = insert, 'D'= delete
345 		 * T  = DST <-> STD transition indicators
346 		 *
347         	 */
348 		if (sscanf(pp->a_lastcode, "%c%1d%c%4d%3d%*c%2d:%2d:%2d.%2ld%c",
349 	               &syncchar, &quality, &modechar, &pp->year, &pp->day,
350         	       &pp->hour, &pp->minute, &pp->second,
351 			&pp->nsec, &leapchar) == 10) {
352 		pp->nsec *= 10000000; /* M320 returns 10's of msecs */
353 		if (leapchar == 'I' ) leapchar = '+';
354 		if (leapchar == 'D' ) leapchar = '-';
355 		if (syncchar != '?' ) syncchar = ':';
356 
357  		break;
358 		}
359 
360 		default:
361 		refclock_report(peer, CEVNT_BADREPLY);
362 		return;
363 	}
364 
365 
366 	/*
367 	 * Decode quality indicator
368 	 * For the 325 & 33x series, the lower the number the "better"
369 	 * the time is. I used the dispersion as the measure of time
370 	 * quality. The quality indicator in the 320 is the number of
371 	 * correlating time frames (the more the better)
372 	 */
373 
374 	/*
375 	 * The spec sheet for the 325 & 33x series states the clock will
376 	 * maintain +/-0.002 seconds accuracy when locked to WWVB. This
377 	 * is indicated by 'Lk' in the quality portion of the incoming
378 	 * string. When not in lock, a drift of +/-0.015 seconds should
379 	 * be allowed for.
380 	 * With the quality indicator decoding scheme above, the 'Lk'
381 	 * condition will produce a quality value of 0. If the quality
382 	 * indicator starts with '0' then the second character is the
383 	 * number of hours since we were last locked. If the first
384 	 * character is anything other than 'L' or '0' then we have been
385 	 * out of lock for more than 9 hours so we assume the worst and
386 	 * force a quality value that selects the 'default' maximum
387 	 * dispersion. The dispersion values below are what came with the
388 	 * driver. They're not unreasonable so they've not been changed.
389 	 */
390 
391 	if (pp->lencode == LEN33X) {
392 		switch (quality) {
393 			case 0 :
394 				pp->disp=.002;
395 				break;
396 			case 1 :
397 				pp->disp=.02;
398 				break;
399 			case 2 :
400 				pp->disp=.04;
401 				break;
402 			case 3 :
403 				pp->disp=.08;
404 				break;
405 			default:
406 				pp->disp=MAXDISPERSE;
407 				break;
408 		}
409 	} else {
410 		switch (quality) {
411 			case 5 :
412 				pp->disp=.002;
413 				break;
414 			case 4 :
415 				pp->disp=.02;
416 				break;
417 			case 3 :
418 				pp->disp=.04;
419 				break;
420 			case 2 :
421 				pp->disp=.08;
422 				break;
423 			case 1 :
424 				pp->disp=.16;
425 				break;
426 			default:
427 				pp->disp=MAXDISPERSE;
428 				break;
429 		}
430 
431 	}
432 
433 	/*
434 	 * Decode synchronization, and leap characters. If
435 	 * unsynchronized, set the leap bits accordingly and exit.
436 	 * Otherwise, set the leap bits according to the leap character.
437 	 */
438 
439 	if (syncchar != ':')
440 		pp->leap = LEAP_NOTINSYNC;
441 	else if (leapchar == '+')
442 		pp->leap = LEAP_ADDSECOND;
443 	else if (leapchar == '-')
444 		pp->leap = LEAP_DELSECOND;
445 	else
446 		pp->leap = LEAP_NOWARNING;
447 
448 	/*
449 	 * Process the new sample in the median filter and determine the
450 	 * timecode timestamp.
451 	 */
452 	if (!refclock_process(pp)) {
453 		refclock_report(peer, CEVNT_BADTIME);
454 	}
455 
456 }
457 
458 
459 /*
460  * ulink_poll - called by the transmit procedure
461  */
462 static void
463 ulink_poll(
464 	int unit,
465 	struct peer *peer
466 	)
467 {
468         struct refclockproc *pp;
469         char pollchar;
470 
471         pp = peer->procptr;
472         pollchar = 'T';
473 	if (pp->sloppyclockflag & CLK_FLAG1) {
474 	        if (write(pp->io.fd, &pollchar, 1) != 1)
475         	        refclock_report(peer, CEVNT_FAULT);
476         	else
477       	            pp->polls++;
478 	}
479 	else
480       	            pp->polls++;
481 
482         if (peer->burst > 0)
483                 return;
484         if (pp->coderecv == pp->codeproc) {
485                 refclock_report(peer, CEVNT_TIMEOUT);
486                 return;
487         }
488         pp->lastref = pp->lastrec;
489 	refclock_receive(peer);
490 	record_clock_stats(&peer->srcadr, pp->a_lastcode);
491         peer->burst = NSTAGE;
492 
493 }
494 
495 #else
496 int refclock_ulink_bs;
497 #endif /* REFCLOCK */
498