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