]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/ntp/ntpd/refclock_heath.c
This commit was generated by cvs2svn to compensate for changes in r58310,
[FreeBSD/FreeBSD.git] / contrib / ntp / ntpd / refclock_heath.c
1 /*
2  * refclock_heath - clock driver for Heath GC-1000 Most Accurate Clock
3  */
4 #ifdef HAVE_CONFIG_H
5 #include <config.h>
6 #endif
7
8 #if defined(REFCLOCK) && defined(CLOCK_HEATH)
9
10 #include <stdio.h>
11 #include <ctype.h>
12 #ifdef TIME_WITH_SYS_TIME
13 # include <sys/time.h>
14 # include <time.h>
15 #else
16 # ifdef TM_IN_SYS_TIME
17 #  include <sys/time.h>
18 # else
19 #  include <time.h>
20 # endif
21 #endif
22 #ifdef HAVE_SYS_IOCTL_H
23 # include <sys/ioctl.h>
24 #endif /* not HAVE_SYS_IOCTL_H */
25
26 #include "ntpd.h"
27 #include "ntp_io.h"
28 #include "ntp_refclock.h"
29 #include "ntp_stdlib.h"
30
31 /*
32  * This driver supports the Heath GC-1000 Most Accurate Clock, with
33  * RS232C Output Accessory. This is a WWV/WWVH receiver somewhat less
34  * robust than other supported receivers. Its claimed accuracy is 100 ms
35  * when actually synchronized to the broadcast signal, but this doesn't
36  * happen even most of the time, due to propagation conditions, ambient
37  * noise sources, etc. When not synchronized, the accuracy is at the
38  * whim of the internal clock oscillator, which can wander into the
39  * sunset without warning. Since the indicated precision is 100 ms,
40  * expect a host synchronized only to this thing to wander to and fro,
41  * occasionally being rudely stepped when the offset exceeds the default
42  * clock_max of 128 ms. 
43  *
44  * The internal DIPswitches should be set to operate at 1200 baud in
45  * MANUAL mode and the current year. The external DIPswitches should be
46  * set to GMT and 24-hour format, or to the host local time zone (with
47  * DST) and 12-hour format. It is very important that the year be
48  * set correctly in the DIPswitches. Otherwise, the day of year will be
49  * incorrect after 28 April[?] of a normal or leap year.  In 12-hour mode
50  * with DST selected the clock will be incorrect by an hour for an
51  * indeterminate amount of time between 0000Z and 0200 on the day DST
52  * changes.
53  *
54  * In MANUAL mode the clock responds to a rising edge of the request to
55  * send (RTS) modem control line by sending the timecode. Therefore, it
56  * is necessary that the operating system implement the TIOCMBIC and
57  * TIOCMBIS ioctl system calls and TIOCM_RTS control bit. Present
58  * restrictions require the use of a POSIX-compatible programming
59  * interface, although other interfaces may work as well.
60  *
61  * A simple hardware modification to the clock can be made which
62  * prevents the clock hearing the request to send (RTS) if the HI SPEC
63  * lamp is out. Route the HISPEC signal to the tone decoder board pin
64  * 19, from the display, pin 19. Isolate pin 19 of the decoder board
65  * first, but maintain connection with pin 10. Also isolate pin 38 of
66  * the CPU on the tone board, and use half an added 7400 to gate the
67  * original signal to pin 38 with that from pin 19.
68  *
69  * The clock message consists of 23 ASCII printing characters in the
70  * following format:
71  *
72  * hh:mm:ss.f AM  dd/mm/yr<cr>
73  *
74  *      hh:mm:ss.f = hours, minutes, seconds
75  *      f = deciseconds ('?' when out of spec)
76  *      AM/PM/bb = blank in 24-hour mode
77  *      dd/mm/yr = day, month, year
78  *
79  * The alarm condition is indicated by '?', rather than a digit, at f.
80  * Note that 0?:??:??.? is displayed before synchronization is first
81  * established and hh:mm:ss.? once synchronization is established and
82  * then lost again for about a day.
83  *
84  * Fudge Factors
85  *
86  * A fudge time1 value of .04 s appears to center the clock offset
87  * residuals. The fudge time2 parameter is the local time offset east of
88  * Greenwich, which depends on DST. Sorry about that, but the clock
89  * gives no hint on what the DIPswitches say.
90  */
91
92 /*
93  * Interface definitions
94  */
95 #define DEVICE          "/dev/heath%d" /* device name and unit */
96 #define SPEED232        B1200   /* uart speed (1200 baud) */
97 #define PRECISION       (-4)    /* precision assumed (about 100 ms) */
98 #define REFID           "WWV\0" /* reference ID */
99 #define DESCRIPTION     "Heath GC-1000 Most Accurate Clock" /* WRU */
100
101 #define LENHEATH        23      /* min timecode length */
102
103 /*
104  * Tables to compute the ddd of year form icky dd/mm timecode. Viva la
105  * leap.
106  */
107 static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
108 static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
109
110 /*
111  * Unit control structure
112  */
113 struct heathunit {
114         int     pollcnt;        /* poll message counter */
115         l_fp    tstamp;         /* timestamp of last poll */
116 };
117
118 /*
119  * Function prototypes
120  */
121 static  int     heath_start     P((int, struct peer *));
122 static  void    heath_shutdown  P((int, struct peer *));
123 static  void    heath_receive   P((struct recvbuf *));
124 static  void    heath_poll      P((int, struct peer *));
125
126 /*
127  * Transfer vector
128  */
129 struct  refclock refclock_heath = {
130         heath_start,            /* start up driver */
131         heath_shutdown,         /* shut down driver */
132         heath_poll,             /* transmit poll message */
133         noentry,                /* not used (old heath_control) */
134         noentry,                /* initialize driver */
135         noentry,                /* not used (old heath_buginfo) */
136         NOFLAGS                 /* not used */
137 };
138
139 #if 0
140 /*
141  * Gee, Unix so thoughfully omitted code to convert from a struct tm to
142  * a long, so I'll just have to ferret out the inverse myself, the hard way.
143  * (Newton's method.)
144  */
145 #define timelocal(x) invert(x, localtime)
146 /*
147  * comparetm compares two tm structures and returns -1 if the first
148  * is less than the second, 0 if they are equal, and +1 if the first
149  * is greater than the second.  Only the year, month, day, hour, minute
150  * and second are compared.  The yearday (Julian), day of week, and isdst
151  * are not compared.
152  */
153
154 static int
155 comparetm(
156         struct tm *a,
157         struct tm *b
158         )
159 {
160         if (a->tm_year < b->tm_year ) return -1;
161         if (a->tm_year > b->tm_year ) return 1;
162         if (a->tm_mon < b->tm_mon ) return -1;
163         if (a->tm_mon > b->tm_mon ) return 1;
164         if (a->tm_mday < b->tm_mday ) return -1;
165         if (a->tm_mday > b->tm_mday ) return 1;
166         if (a->tm_hour < b->tm_hour ) return -1;
167         if (a->tm_hour > b->tm_hour ) return 1;
168         if (a->tm_min < b->tm_min ) return -1;
169         if (a->tm_min > b->tm_min ) return 1;
170         if (a->tm_sec < b->tm_sec ) return -1;
171         if (a->tm_sec > b->tm_sec ) return 1;
172         return 0;
173 }
174
175 static long
176 invert (
177        struct tm *x,
178        struct tm *(*func)()
179        )
180 {
181         struct tm *y;
182         int result;
183         long trial;
184         long lower=0L;
185         long upper=(long)((unsigned long)(~lower) >> 1);
186
187         do {
188                 trial = (upper + lower) / 2L;
189                 y = (*func)(&trial);
190                 result = comparetm(x, y);
191                 if (result < 0) upper = trial;
192                 if (result > 0) lower = trial;
193         } while (result != 0);
194         return trial;
195 }
196 #endif /* 0 */
197
198
199 /*
200  * heath_start - open the devices and initialize data for processing
201  */
202 static int
203 heath_start(
204         int unit,
205         struct peer *peer
206         )
207 {
208         register struct heathunit *up;
209         struct refclockproc *pp;
210         int fd;
211         char device[20];
212
213         /*
214          * Open serial port
215          */
216         (void)sprintf(device, DEVICE, unit);
217         if (!(fd = refclock_open(device, SPEED232, 0)))
218             return (0);
219
220         /*
221          * Allocate and initialize unit structure
222          */
223         if (!(up = (struct heathunit *)
224               emalloc(sizeof(struct heathunit)))) {
225                 (void) close(fd);
226                 return (0);
227         }
228         memset((char *)up, 0, sizeof(struct heathunit));
229         pp = peer->procptr;
230         pp->io.clock_recv = heath_receive;
231         pp->io.srcclock = (caddr_t)peer;
232         pp->io.datalen = 0;
233         pp->io.fd = fd;
234         if (!io_addclock(&pp->io)) {
235                 (void) close(fd);
236                 free(up);
237                 return (0);
238         }
239         pp->unitptr = (caddr_t)up;
240
241         /*
242          * Initialize miscellaneous variables
243          */
244         peer->precision = PRECISION;
245         peer->burst = NSTAGE;
246         pp->clockdesc = DESCRIPTION;
247         memcpy((char *)&pp->refid, REFID, 4);
248         up->pollcnt = 2;
249         return (1);
250 }
251
252
253 /*
254  * heath_shutdown - shut down the clock
255  */
256 static void
257 heath_shutdown(
258         int unit,
259         struct peer *peer
260         )
261 {
262         register struct heathunit *up;
263         struct refclockproc *pp;
264
265         pp = peer->procptr;
266         up = (struct heathunit *)pp->unitptr;
267         io_closeclock(&pp->io);
268         free(up);
269 }
270
271
272 /*
273  * heath_receive - receive data from the serial interface
274  */
275 static void
276 heath_receive(
277         struct recvbuf *rbufp
278         )
279 {
280         register struct heathunit *up;
281         struct refclockproc *pp;
282         struct peer *peer;
283         l_fp trtmp;
284         int month, day;
285         int i;
286         char dsec, a[5];
287
288         /*
289          * Initialize pointers and read the timecode and timestamp
290          */
291         peer = (struct peer *)rbufp->recv_srcclock;
292         pp = peer->procptr;
293         up = (struct heathunit *)pp->unitptr;
294         pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp);
295
296         /*
297          * We get a buffer and timestamp for each <cr>; however, we use
298          * the timestamp captured at the RTS modem control line toggle
299          * on the assumption that's what the radio bases the timecode
300          * on. Apparently, the radio takes about a second to make up its
301          * mind to send a timecode, so the receive timestamp is
302          * worthless.
303          */
304         pp->lastrec = up->tstamp;
305         up->pollcnt = 2;
306 #ifdef DEBUG
307         if (debug)
308             printf("heath: timecode %d %s\n", pp->lencode,
309                    pp->a_lastcode);
310 #endif
311
312         /*
313          * We get down to business, check the timecode format and decode
314          * its contents. If the timecode has invalid length or is not in
315          * proper format, we declare bad format and exit.
316          */
317         if (pp->lencode < LENHEATH) {
318                 refclock_report(peer, CEVNT_BADREPLY);
319                 return;
320         }
321
322         /*
323          * Timecode format: "hh:mm:ss.f AM  mm/dd/yy"
324          */
325         if (sscanf(pp->a_lastcode, "%2d:%2d:%2d.%c%5c%2d/%2d/%2d",
326                    &pp->hour, &pp->minute, &pp->second, &dsec, a, &month, &day,
327                    &pp->year) != 8) {
328                 refclock_report(peer, CEVNT_BADREPLY);
329                 return;
330         }
331
332         /*
333          * If AM or PM is received, assume the clock is displaying local
334          * time. First, convert to 24-hour format.
335          */
336
337         switch (a[1]) {
338             case 'P':
339                 if (12 > pp->hour)
340                     pp->hour += 12;
341                 break;
342
343             case 'A':
344                 if (12 == pp->hour)
345                     pp->hour -= 12;
346                 break;
347         }
348
349         /*
350          * Now make a struct tm out of it, convert to UTC, and
351          * repopulate pp->
352          */
353
354         if (' ' != a[1]) {
355                 struct tm t, *q;
356                 time_t l;
357
358                 t.tm_sec = pp->second;
359                 t.tm_min = pp->minute;
360                 t.tm_hour = pp->hour;
361                 t.tm_mday = day; /* not converted to yday yet */
362                 t.tm_mon = month-1; /* ditto */
363                 t.tm_year = pp->year;
364                 if ( t.tm_year < YEAR_PIVOT ) t.tm_year += 100; /* Y2KFixes */
365
366                 t.tm_wday = -1; /* who knows? */
367                 t.tm_yday = -1; /* who knows? */
368                 t.tm_isdst = -1; /* who knows? */
369
370                 l = mktime(&t);
371                 if (l == -1) {
372                         /* HMS: do we want to do this? */
373                         refclock_report(peer, CEVNT_BADTIME);
374                         return;
375                 }
376                 q = gmtime(&l);
377
378                 pp->year = q->tm_year;
379                 month = q->tm_mon+1;
380                 day = q->tm_mday; /* still not converted */
381                 pp->hour = q->tm_hour;
382                 /* pp->minute = q->tm_min;  GC-1000 cannot adjust timezone */
383                 /* pp->second = q->tm_sec;  by other than hour increments */
384         }
385
386   
387
388         /*
389          * We determine the day of the year from the DIPswitches. This
390          * should be fixed, since somebody might forget to set them.
391          * Someday this hazard will be fixed by a fiendish scheme that
392          * looks at the timecode and year the radio shows, then computes
393          * the residue of the seconds mod the seconds in a leap cycle.
394          * If in the third year of that cycle and the third and later
395          * months of that year, add one to the day. Then, correct the
396          * timecode accordingly. Icky pooh. This bit of nonsense could
397          * be avoided if the engineers had been required to write a
398          * device driver before finalizing the timecode format.
399          *
400          * Yes, I know this code incorrectly thinks that 2000 is a leap
401          * year; but, the latest year that can be set by the DIPswitches
402          * is 1997 anyay. Life is short.
403          *      Hey! Year 2000 IS a leap year!                     Y2KFixes
404          */
405         if (month < 1 || month > 12 || day < 1) {
406                 refclock_report(peer, CEVNT_BADTIME);
407                 return;
408         }
409         if (pp->year % 4) {
410                 if (day > day1tab[month - 1]) {
411                         refclock_report(peer, CEVNT_BADTIME);
412                         return;
413                 }
414                 for (i = 0; i < month - 1; i++)
415                     day += day1tab[i];
416         } else {
417                 if (day > day2tab[month - 1]) {
418                         refclock_report(peer, CEVNT_BADTIME);
419                         return;
420                 }
421                 for (i = 0; i < month - 1; i++)
422                     day += day2tab[i];
423         }
424         pp->day = day;
425
426         /*
427          * Determine synchronization and last update
428          */
429         if (!isdigit((int)dsec))
430                 pp->leap = LEAP_NOTINSYNC;
431         else {
432                 pp->msec = (dsec - '0') * 100;
433                 pp->leap = LEAP_NOWARNING;
434         }
435         if (!refclock_process(pp))
436                 refclock_report(peer, CEVNT_BADTIME);
437 }
438
439
440 /*
441  * heath_poll - called by the transmit procedure
442  */
443 static void
444 heath_poll(
445         int unit,
446         struct peer *peer
447         )
448 {
449         register struct heathunit *up;
450         struct refclockproc *pp;
451         int bits = TIOCM_RTS;
452
453         /*
454          * At each poll we check for timeout and toggle the RTS modem
455          * control line, then take a timestamp. Presumably, this is the
456          * event the radio captures to generate the timecode.
457          */
458         pp = peer->procptr;
459         up = (struct heathunit *)pp->unitptr;
460         pp->polls++;
461
462         /*
463          * We toggle the RTS modem control lead to kick a timecode loose
464          * from the radio. This code works only for POSIX and SYSV
465          * interfaces. With bsd you are on your own. We take a timestamp
466          * between the up and down edges to lengthen the pulse, which
467          * should be about 50 usec on a Sun IPC. With hotshot CPUs, the
468          * pulse might get too short. Later.
469          */
470         if (ioctl(pp->io.fd, TIOCMBIC, (char *)&bits) < 0)
471                 refclock_report(peer, CEVNT_FAULT);
472         get_systime(&up->tstamp);
473         ioctl(pp->io.fd, TIOCMBIS, (char *)&bits);
474         if (peer->burst > 0)
475                 return;
476         if (pp->coderecv == pp->codeproc) {
477                 refclock_report(peer, CEVNT_TIMEOUT);
478                 return;
479         }
480         record_clock_stats(&peer->srcadr, pp->a_lastcode);
481         refclock_receive(peer);
482         peer->burst = NSTAGE;
483 }
484
485 #else
486 int refclock_heath_bs;
487 #endif /* REFCLOCK */