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