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