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