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 */
101struct ulinkunit {
102	u_char	tcswitch;	/* timecode switch */
103	l_fp	laststamp;	/* last receive timestamp */
104};
105
106/*
107 * Function prototypes
108 */
109static	int	ulink_start	(int, struct peer *);
110static	void	ulink_shutdown	(int, struct peer *);
111static	void	ulink_receive	(struct recvbuf *);
112static	void	ulink_poll	(int, struct peer *);
113
114/*
115 * Transfer vector
116 */
117struct	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 */
131static int
132ulink_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 */
184static void
185ulink_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 */
203static void
204ulink_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
554static void
555ulink_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
588int refclock_ulink_bs;
589#endif /* REFCLOCK */
590