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