source: trunk/third/xntp/xntpd/refclock_usno.c @ 10832

Revision 10832, 17.6 KB checked in by brlewis, 27 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r10831, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2 * refclock_usno - clock driver for the Naval Observatory dialup
3 * Michael Shields <shields@tembel.org> 1995/02/25
4 */
5
6#ifdef HAVE_CONFIG_H
7#include <config.h>
8#endif
9
10#if defined(REFCLOCK) && defined(USNO)
11
12#include <stdio.h>
13#include <ctype.h>
14#include <sys/time.h>
15#ifdef HAVE_SYS_IOCTL_H
16# include <sys/ioctl.h>
17#endif /* HAVE_SYS_IOCTL_H */
18
19#include "ntpd.h"
20#include "ntp_io.h"
21#include "ntp_unixtime.h"
22#include "ntp_refclock.h"
23#include "ntp_stdlib.h"
24#include "ntp_control.h"
25
26/*
27 * This driver supports the Naval Observatory dialup at +1 202 653 0351.
28 * It is a hacked-up version of the ACTS driver.
29 *
30 * This driver does not support the `phone' configuration because that
31 * is needlessly global; it would clash with the ACTS driver.
32 *
33 * The Naval Observatory does not support the echo-delay measurement scheme.
34 *
35 * However, this driver *does* support UUCP port locking, allowing the
36 * line to be shared with other processes when not actually dialing
37 * for time.
38 */
39
40/*
41 * Interface definitions
42 */
43
44#define DEVICE          "/dev/cua%d" /* device name and unit */
45#define LOCKFILE        "/var/lock/LCK..cua%d"
46/* #define LOCKFILE     "/usr/spool/uucp/LCK..cua%d" */
47
48#define PHONE           "atdt 202 653 0351"
49/* #define PHONE        "atdt 1 202 653 0351" */
50
51#define SPEED232        B1200   /* uart speed (1200 cowardly baud) */
52#define PRECISION       (-10)   /* precision assumed (about 1 ms) */
53#define REFID           "USNO"  /* reference ID */
54#define DESCRIPTION     "Naval Observatory dialup"
55
56#define MODE_AUTO       0       /* automatic mode */
57#define MODE_BACKUP     1       /* backup mode */
58#define MODE_MANUAL     2       /* manual mode */
59
60#define MSGCNT          10      /* we need this many time messages */
61#define SMAX            80      /* max token string length */
62#define LENCODE         20      /* length of valid timecode string */
63#define USNO_MINPOLL    10      /* log2 min poll interval (1024 s) */
64#define USNO_MAXPOLL    14      /* log2 max poll interval (16384 s) */
65#define MAXOUTAGE       3600    /* max before USNO kicks in (s) */
66
67/*
68 * Modem control strings. These may have to be changed for some modems.
69 *
70 * AT   command prefix
71 * B1   initiate call negotiation using Bell 212A
72 * &C1  enable carrier detect
73 * &D2  hang up and return to command mode on DTR transition
74 * E0   modem command echo disabled
75 * l1   set modem speaker volume to low level
76 * M1   speaker enabled untill carrier detect
77 * Q0   return result codes
78 * V1   return result codes as English words
79 */
80#define MODEM_SETUP     "ATB1&C1&D2E0L1M1Q0V1" /* modem setup */
81#define MODEM_HANGUP    "ATH"   /* modem disconnect */
82
83/*
84 * Timeouts
85 */
86#define IDLE            60      /* idle timeout (s) */
87#define WAIT            2       /* wait timeout (s) */
88#define ANSWER          30      /* answer timeout (s) */
89#define CONNECT         10      /* connect timeout (s) */
90#define TIMECODE        (MSGCNT+16)     /* timecode timeout (s) */
91
92/*
93 * Imported from ntp_timer module
94 */
95extern  u_long  current_time;   /* current time (s) */
96extern  u_long  last_time;      /* last clock update time (s) */
97extern  struct event timerqueue[]; /* inner space */
98
99/*
100 * Imported from ntpd module
101 */
102extern  int     debug;          /* global debug flag */
103
104/*
105 * Imported from ntp_proto module
106 */
107extern  struct peer *sys_peer;  /* who is running the show */
108extern  u_char sys_poll;        /* log2 of system poll interval */
109extern  struct peer *sys_peer;  /* system peer structure pointer */
110
111/*
112 * Unit control structure
113 */
114struct usnounit {
115        struct  event timer;    /* timeout timer */
116        int     pollcnt;        /* poll message counter */
117
118        int     state;          /* the first one was Delaware */
119        int     run;            /* call program run switch */
120        int     msgcnt;         /* count of time messages received */
121        long    redial;         /* interval to next automatic call */
122        int     unit;           /* unit number (= port) */
123};
124
125/*
126 * Function prototypes
127 */
128static  int     usno_start      P((int, struct peer *));
129static  void    usno_shutdown   P((int, struct peer *));
130static  void    usno_receive    P((struct recvbuf *));
131static  void    usno_poll       P((int, struct peer *));
132static  void    usno_timeout    P((struct peer *));
133static  void    usno_disc       P((struct peer *));
134static  int     usno_write      P((struct peer *, char *));
135
136/*
137 * Transfer vector
138 */
139struct  refclock refclock_usno = {
140        usno_start,             /* start up driver */
141        usno_shutdown,          /* shut down driver */
142        usno_poll,              /* transmit poll message */
143        noentry,                /* not used (usno_control) */
144        noentry,                /* not used (usno_init) */
145        noentry,                /* not used (usno_buginfo) */
146        NOFLAGS                 /* not used */
147};
148
149
150/*
151 * usno_start - open the devices and initialize data for processing
152 */
153static int
154usno_start(unit, peer)
155        int unit;
156        struct peer *peer;
157{
158        register struct usnounit *up;
159        struct refclockproc *pp;
160
161        /*
162         * Initialize miscellaneous variables
163         */
164        pp = peer->procptr;
165        peer->precision = PRECISION;
166        pp->clockdesc = DESCRIPTION;
167        memcpy((char *)&pp->refid, REFID, 4);
168        peer->minpoll = USNO_MINPOLL;
169        peer->maxpoll = USNO_MAXPOLL;
170        peer->sstclktype = CTL_SST_TS_TELEPHONE;
171
172        /*
173         * Allocate and initialize unit structure
174         */
175        if (!(up = (struct usnounit *)
176            emalloc(sizeof(struct usnounit))))
177                return (0);
178        memset((char *)up, 0, sizeof(struct usnounit));
179        up->unit = unit;
180        pp->unitptr = (caddr_t)up;
181
182        /*
183         * Set up the driver timeout
184         */
185        up->timer.peer = (struct peer *)peer;
186        up->timer.event_handler = usno_timeout;
187        up->timer.event_time = current_time + WAIT;
188        TIMER_INSERT(timerqueue, &up->timer);
189        return (1);
190}
191
192
193/*
194 * usno_shutdown - shut down the clock
195 */
196static void
197usno_shutdown(unit, peer)
198        int unit;
199        struct peer *peer;
200{
201        register struct usnounit *up;
202        struct refclockproc *pp;
203
204#ifdef DEBUG
205        if (debug)
206                printf("usno: clock %s shutting down\n", ntoa(&peer->srcadr));
207#endif
208        pp = peer->procptr;
209        up = (struct usnounit *)pp->unitptr;
210        TIMER_DEQUEUE(&up->timer);
211        usno_disc(peer);
212        TIMER_DEQUEUE(&up->timer);
213        free(up);
214}
215
216
217/*
218 * usno_receive - receive data from the serial interface
219 */
220static void
221usno_receive(rbufp)
222        struct recvbuf *rbufp;
223{
224        register struct usnounit *up;
225        struct refclockproc *pp;
226        struct peer *peer;
227        char str[SMAX];
228        u_fp disp;
229        u_long mjd;             /* Modified Julian Day */
230        static int day, hour, minute, second;
231
232        /*
233         * Initialize pointers and read the timecode and timestamp. If
234         * the OK modem status code, leave it where folks can find it.
235         */
236        peer = (struct peer *)rbufp->recv_srcclock;
237        pp = peer->procptr;
238        up = (struct usnounit *)pp->unitptr;
239        pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX,
240            &pp->lastrec);
241        if (pp->lencode == 0) {
242                if (strcmp(pp->a_lastcode, "OK") == 0)
243                        pp->lencode = 2;
244                return;
245        }
246#ifdef DEBUG
247        if (debug)
248                printf("usno: timecode %d %s\n", pp->lencode,
249                    pp->a_lastcode);
250#endif
251
252        switch (up->state) {
253
254                case 0:
255
256                /*
257                 * State 0. We are not expecting anything. Probably
258                 * modem disconnect noise. Go back to sleep.
259                 */
260                return;
261
262                case 1:
263
264                /*
265                 * State 1. We are about to dial. Just drop it.
266                 */
267                return;
268
269                case 2:
270
271                /*
272                 * State 2. We are waiting for the call to be answered.
273                 * All we care about here is CONNECT as the first token
274                 * in the string. If the modem signals BUSY, ERROR, NO
275                 * ANSWER, NO CARRIER or NO DIALTONE, we immediately
276                 * hang up the phone. If CONNECT doesn't happen after
277                 * ANSWER seconds, hang up the phone. If everything is
278                 * okay, start the connect timeout and slide into state
279                 * 3.
280                 */
281                (void)strncpy(str, strtok(pp->a_lastcode, " "), SMAX);
282                if (strcmp(str, "BUSY") == 0 || strcmp(str, "ERROR") ==
283                     0 || strcmp(str, "NO") == 0) {
284                        TIMER_DEQUEUE(&up->timer);
285                        NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
286                          msyslog(LOG_NOTICE,
287                            "clock %s USNO modem status %s",
288                            ntoa(&peer->srcadr), pp->a_lastcode);
289                        usno_disc(peer);
290                } else if (strcmp(str, "CONNECT") == 0) {
291                        TIMER_DEQUEUE(&up->timer);
292                        up->timer.event_time = current_time + CONNECT;
293                        TIMER_INSERT(timerqueue, &up->timer);
294                        up->msgcnt = 0;
295                        up->state++;
296                } else {
297                        NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
298                          msyslog(LOG_WARNING,
299                            "clock %s USNO unknown modem status %s",
300                            ntoa(&peer->srcadr), pp->a_lastcode);
301                }
302                return;
303
304                case 3:
305
306                /*
307                 * State 3. The call has been answered and we are
308                 * waiting for the first message. If this doesn't
309                 * happen within the timecode timeout, hang up the
310                 * phone. We probably got a wrong number or they are
311                 * down.
312                 */
313                TIMER_DEQUEUE(&up->timer);
314                up->timer.event_time = current_time + TIMECODE;
315                TIMER_INSERT(timerqueue, &up->timer);
316                up->state++;
317                return;
318
319                case 4:
320
321                /*
322                 * State 4. We are reading a timecode.  It's an actual
323                 * timecode, or it's the `*' OTM.
324                 *
325                 * jjjjj nnn hhmmss UTC
326                 */
327                if (pp->lencode == LENCODE) {
328                        if (sscanf(pp->a_lastcode, "%5ld %3d %2d%2d%2d UTC",
329                            &mjd, &day, &hour, &minute, &second) != 5) {
330#ifdef DEBUG
331                                if (debug)
332                                        printf("usno: bad timecode format\n");
333#endif
334                                refclock_report(peer, CEVNT_BADREPLY);
335                        } else
336                                up->msgcnt++;
337                        return;
338                } else if (pp->lencode != 1 || !up->msgcnt)
339                        return;
340                /* else, OTM; drop out of switch */
341        }
342
343        pp->leap = LEAP_NOWARNING;
344        pp->day = day;
345        pp->hour = hour;
346        pp->minute = minute;
347        pp->second = second;
348        pp->lasttime = current_time;
349
350        /*
351         * Colossal hack here. We process each sample in a trimmed-mean
352         * filter and determine the reference clock offset and
353         * dispersion. The fudge time1 value is added to each sample as
354         * received.
355         */
356        if (!refclock_process(pp, up->msgcnt, up->msgcnt - up->msgcnt / 3)) {
357#ifdef DEBUG
358                if (debug)
359                        printf("usno: time rejected\n");
360#endif
361                refclock_report(peer, CEVNT_BADTIME);
362                return;
363        } else if (up->msgcnt < MSGCNT)
364                return;
365
366        /*
367         * We have a filtered sample offset ready for peer processing.
368         * We use lastrec as both the reference time and receive time in
369         * order to avoid being cute, like setting the reference time
370         * later than the receive time, which may cause a paranoid
371         * protocol module to chuck out the data. Finaly, we unhook the
372         * timeout, arm for the next call, fold the tent and go home.
373         */
374        disp = LFPTOFP(&pp->fudgetime2);
375        record_clock_stats(&peer->srcadr, pp->a_lastcode);
376        refclock_receive(peer, &pp->offset, 0, pp->dispersion +
377            (u_fp)disp, &pp->lastrec, &pp->lastrec, pp->leap);
378        pp->sloppyclockflag &= ~CLK_FLAG1;
379        up->pollcnt = 0;
380        TIMER_DEQUEUE(&up->timer);
381        up->state = 0;
382        usno_disc(peer);
383}
384
385
386/*
387 * usno_poll - called by the transmit routine
388 */
389static void
390usno_poll(unit, peer)
391        int unit;
392        struct peer *peer;
393{
394        register struct usnounit *up;
395        struct refclockproc *pp;
396
397        /*
398         * If the driver is running, we set the enable flag (fudge
399         * flag1), which causes the driver timeout routine to initiate a
400         * call. If not, the enable flag can be set using
401         * xntpdc. If this is the sustem peer, then follow the system
402         * poll interval.
403         */
404        pp = peer->procptr;
405        up = (struct usnounit *)pp->unitptr;
406        if (up->run) {
407                pp->sloppyclockflag |= CLK_FLAG1;
408                if (peer == sys_peer)
409                        peer->hpoll = sys_poll;
410                else
411                        peer->hpoll = peer->minpoll;
412        }
413}
414
415
416/*
417 * usno_timeout - called by the timer interrupt
418 */
419static void
420usno_timeout(peer)
421        struct peer *peer;
422{
423        register struct usnounit *up;
424        struct refclockproc *pp;
425        int fd;
426        char device[20];
427        char lockfile[128], pidbuf[8];
428        int dtr = TIOCM_DTR;
429
430        /*
431         * If a timeout occurs in other than state 0, the call has
432         * failed. If in state 0, we just see if there is other work to
433         * do.
434         */
435        pp = peer->procptr;
436        up = (struct usnounit *)pp->unitptr;
437        if (up->state) {
438                if (up->state != 1) {
439                        usno_disc(peer);
440                        return;
441                }
442                /*
443                 * Call, and start the answer timeout. We think it
444                 * strange if the OK status has not been received from
445                 * the modem, but plow ahead anyway.
446                 *
447                 * This code is *here* because we had to stick in a brief
448                 * delay to let the modem settle down after raising DTR,
449                 * and for the OK to be received.  State machines are
450                 * contorted.
451                 */
452                if (strcmp(pp->a_lastcode, "OK") != 0)
453                        NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
454                          msyslog(LOG_NOTICE, "clock %s USNO no modem status",
455                            ntoa(&peer->srcadr));
456                (void)usno_write(peer, PHONE);
457                NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
458                  msyslog(LOG_NOTICE, "clock %s USNO calling %s\n",
459                    ntoa(&peer->srcadr), PHONE);
460                up->state = 2;
461                up->pollcnt++;
462                pp->polls++;
463                up->timer.event_time = current_time + ANSWER;
464                TIMER_INSERT(timerqueue, &up->timer);
465                return;
466        }
467        switch (peer->ttl) {
468
469                /*
470                 * In manual mode the calling program is activated
471                 * by the xntpdc program using the enable flag (fudge
472                 * flag1), either manually or by a cron job.
473                 */
474                case MODE_MANUAL:
475                up->run = 0;
476                break;
477
478                /*
479                 * In automatic mode the calling program runs
480                 * continuously at intervals determined by the sys_poll
481                 * variable.
482                 */
483                case MODE_AUTO:
484                if (!up->run)
485                        pp->sloppyclockflag |= CLK_FLAG1;
486                up->run = 1;
487                break;
488
489                /*
490                 * In backup mode the calling program is disabled,
491                 * unless no system peer has been selected for MAXOUTAGE
492                 * (3600 s). Once enabled, it runs until some other NTP
493                 * peer shows up.
494                 */
495                case MODE_BACKUP:
496                if (!up->run && sys_peer == 0) {
497                        if (current_time - last_time > MAXOUTAGE) {
498                                up->run = 1;
499                                peer->hpoll = peer->minpoll;
500                                NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
501                                  msyslog(LOG_NOTICE,
502                                    "clock %s USNO backup started ",
503                                    ntoa(&peer->srcadr));
504                        }
505                } else if (up->run && sys_peer->sstclktype != CTL_SST_TS_TELEPHONE) {
506                        peer->hpoll = peer->minpoll;
507                        up->run = 0;
508                        NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
509                          msyslog(LOG_NOTICE,
510                            "clock %s USNO backup stopped",
511                            ntoa(&peer->srcadr));
512                }
513                break;
514
515                default:
516                msyslog(LOG_ERR,
517                    "clock %s USNO invalid mode", ntoa(&peer->srcadr));
518               
519        }
520
521        /*
522         * The fudge flag1 is used as an enable/disable; if set either
523         * by the code or via xntpdc, the calling program is
524         * started; if reset, the phones stop ringing.
525         */
526        if (!(pp->sloppyclockflag & CLK_FLAG1)) {
527                up->pollcnt = 0;
528                up->timer.event_time = current_time + IDLE;
529                TIMER_INSERT(timerqueue, &up->timer);
530                return;
531        }
532
533        /*
534         * Lock the port.
535         */
536        (void)sprintf(lockfile, LOCKFILE, up->unit);
537        fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, 0644);
538        if (fd < 0) {
539                msyslog(LOG_ERR, "clock %s USNO port busy",
540                    ntoa(&peer->srcadr));
541                return;
542        }
543        sprintf(pidbuf, "%d\n", (int) getpid());
544        write(fd, pidbuf, strlen(pidbuf));
545        close(fd);
546
547        /*
548         * Open serial port. Use ACTS line discipline, if available. It
549         * pumps a timestamp into the data stream at every on-time
550         * character '*' found. Note: the port must have modem control
551         * or deep pockets for the phone bill. HP-UX 9.03 users should
552         * have very deep pockets.
553         */
554        (void)sprintf(device, DEVICE, up->unit);
555        if (!(fd = refclock_open(device, SPEED232, LDISC_ACTS))) {
556                unlink(lockfile);
557                return;
558        }
559        if (ioctl(fd, TIOCMBIC, (char *)&dtr) < 0)
560                msyslog(LOG_WARNING, "usno_timeout: clock %s: couldn't clear DTR: %m",
561                    ntoa(&peer->srcadr));
562
563        pp->io.clock_recv = usno_receive;
564        pp->io.srcclock = (caddr_t)peer;
565        pp->io.datalen = 0;
566        pp->io.fd = fd;
567        if (!io_addclock(&pp->io)) {
568                (void) close(fd);
569                unlink(lockfile);
570                free(up);
571                return;
572        }
573
574        /*
575         * Initialize modem and kill DTR. We skedaddle if this comes
576         * bum.
577         */
578        if (!usno_write(peer, MODEM_SETUP)) {
579                msyslog(LOG_ERR, "clock %s USNO couldn't write",
580                    ntoa(&peer->srcadr));
581                io_closeclock(&pp->io);
582                unlink(lockfile);
583                free(up);
584                return;
585        }
586
587        /*
588         * Initiate a call to the Observatory. If we wind up here in
589         * other than state 0, a successful call could not be completed
590         * within minpoll seconds.
591         */
592        if (up->pollcnt) {
593                refclock_report(peer, CEVNT_TIMEOUT);
594                NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */
595                  msyslog(LOG_NOTICE,
596                    "clock %s USNO calling program terminated",
597                    ntoa(&peer->srcadr));
598                pp->sloppyclockflag &= ~CLK_FLAG1;
599                up->pollcnt = 0;
600#ifdef DEBUG
601                if (debug)
602                        printf("usno: calling program terminated\n");
603#endif
604                usno_disc(peer);
605                return;
606        }
607
608        /*
609         * Raise DTR, and let the modem settle.  Then we'll dial.
610         */
611        if (ioctl(pp->io.fd, TIOCMBIS, (char *)&dtr) < -1)
612          msyslog(LOG_INFO, "usno_timeout: clock %s: couldn't set DTR: %m",
613                  ntoa(&peer->srcadr));
614        up->state = 1;
615        up->timer.event_time = current_time + WAIT;
616        TIMER_INSERT(timerqueue, &up->timer);
617}
618
619
620/*
621 * usno_disc - disconnect the call and wait for the ruckus to cool
622 */
623static void
624usno_disc(peer)
625        struct peer *peer;
626{
627        register struct usnounit *up;
628        struct refclockproc *pp;
629        int dtr = TIOCM_DTR;
630        char lockfile[128];
631
632        /*
633         * We should never get here other than in state 0, unless a call
634         * has timed out. We drop DTR, which will reliably get the modem
635         * off the air, even while the modem is hammering away full tilt.
636         */
637        pp = peer->procptr;
638        up = (struct usnounit *)pp->unitptr;
639
640        if (ioctl(pp->io.fd, TIOCMBIC, (char *)&dtr) < 0)
641          msyslog(LOG_INFO, "usno_disc: clock %s: couldn't clear DTR: %m",
642                  ntoa(&peer->srcadr));
643
644        if (up->state > 0) {
645                up->state = 0;
646                msyslog(LOG_NOTICE, "clock %s USNO call failed %d",
647                    ntoa(&peer->srcadr), up->state);
648#ifdef DEBUG
649                if (debug)
650                        printf("usno: call failed %d\n", up->state);
651#endif
652        }
653
654        io_closeclock(&pp->io);
655        sprintf(lockfile, LOCKFILE, up->unit);
656        unlink(lockfile);
657
658        up->timer.event_time = current_time + WAIT;
659        TIMER_INSERT(timerqueue, &up->timer);
660}
661
662
663/*
664 * usno_write - write a message to the serial port
665 */
666static int
667usno_write(peer, str)
668        struct peer *peer;
669        char *str;
670{
671        register struct usnounit *up;
672        struct refclockproc *pp;
673        int len;
674        int code;
675        char cr = '\r';
676
677        /*
678         * Not much to do here, other than send the message, handle
679         * debug and report faults.
680         */
681        pp = peer->procptr;
682        up = (struct usnounit *)pp->unitptr;
683        len = strlen(str);
684#ifdef DEBUG
685        if (debug)
686                printf("usno: state %d send %d %s\n", up->state, len,
687                    str);
688#endif
689        code = write(pp->io.fd, str, len) == len;
690        code |= write(pp->io.fd, &cr, 1) == 1;
691        if (!code)
692                refclock_report(peer, CEVNT_FAULT);
693        return (code);
694}
695
696#else /* not (REFCLOCK && USNO) */
697int refclock_usno_bs;
698#endif /* not (REFCLOCK && USNO) */
Note: See TracBrowser for help on using the repository browser.