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