diff options
Diffstat (limited to 'contrib/ntp/ntpd/refclock_usno.c')
-rw-r--r-- | contrib/ntp/ntpd/refclock_usno.c | 674 |
1 files changed, 674 insertions, 0 deletions
diff --git a/contrib/ntp/ntpd/refclock_usno.c b/contrib/ntp/ntpd/refclock_usno.c new file mode 100644 index 0000000..cf404e7 --- /dev/null +++ b/contrib/ntp/ntpd/refclock_usno.c @@ -0,0 +1,674 @@ +/* + * refclock_usno - clock driver for the Naval Observatory dialup + * Michael Shields <shields@tembel.org> 1995/02/25 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_USNO) + +#include <stdio.h> +#include <ctype.h> +#include <sys/time.h> +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif /* HAVE_SYS_IOCTL_H */ + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_unixtime.h" +#include "ntp_refclock.h" +#include "ntp_stdlib.h" +#include "ntp_control.h" + +/* + * This driver supports the Naval Observatory dialup at +1 202 653 0351. + * It is a hacked-up version of the ACTS driver. + * + * This driver does not support the `phone' configuration because that + * is needlessly global; it would clash with the ACTS driver. + * + * The Naval Observatory does not support the echo-delay measurement scheme. + * + * However, this driver *does* support UUCP port locking, allowing the + * line to be shared with other processes when not actually dialing + * for time. + */ + +/* + * Interface definitions + */ + +#define DEVICE "/dev/cua%d" /* device name and unit */ +#define LOCKFILE "/var/lock/LCK..cua%d" +/* #define LOCKFILE "/usr/spool/uucp/LCK..cua%d" */ + +#define PHONE "atdt 202 653 0351" +/* #define PHONE "atdt 1 202 653 0351" */ + +#define SPEED232 B1200 /* uart speed (1200 cowardly baud) */ +#define PRECISION (-10) /* precision assumed (about 1 ms) */ +#define REFID "USNO" /* reference ID */ +#define DESCRIPTION "Naval Observatory dialup" + +#define MODE_AUTO 0 /* automatic mode */ +#define MODE_BACKUP 1 /* backup mode */ +#define MODE_MANUAL 2 /* manual mode */ + +#define MSGCNT 10 /* we need this many time messages */ +#define SMAX 80 /* max token string length */ +#define LENCODE 20 /* length of valid timecode string */ +#define USNO_MINPOLL 10 /* log2 min poll interval (1024 s) */ +#define USNO_MAXPOLL 14 /* log2 max poll interval (16384 s) */ +#define MAXOUTAGE 3600 /* max before USNO kicks in (s) */ + +/* + * Modem control strings. These may have to be changed for some modems. + * + * AT command prefix + * B1 initiate call negotiation using Bell 212A + * &C1 enable carrier detect + * &D2 hang up and return to command mode on DTR transition + * E0 modem command echo disabled + * l1 set modem speaker volume to low level + * M1 speaker enabled untill carrier detect + * Q0 return result codes + * V1 return result codes as English words + */ +#define MODEM_SETUP "ATB1&C1&D2E0L1M1Q0V1" /* modem setup */ +#define MODEM_HANGUP "ATH" /* modem disconnect */ + +/* + * Timeouts + */ +#define IDLE 60 /* idle timeout (s) */ +#define WAIT 2 /* wait timeout (s) */ +#define ANSWER 30 /* answer timeout (s) */ +#define CONNECT 10 /* connect timeout (s) */ +#define TIMECODE (MSGCNT+16) /* timecode timeout (s) */ + +/* + * Unit control structure + */ +struct usnounit { + int pollcnt; /* poll message counter */ + + int state; /* the first one was Delaware */ + int run; /* call program run switch */ + int msgcnt; /* count of time messages received */ + long redial; /* interval to next automatic call */ + int unit; /* unit number (= port) */ +}; + +/* + * Function prototypes + */ +static int usno_start P((int, struct peer *)); +static void usno_shutdown P((int, struct peer *)); +static void usno_poll P((int, struct peer *)); +static void usno_disc P((struct peer *)); +#if 0 +static void usno_timeout P((struct peer *)); +static void usno_receive P((struct recvbuf *)); +static int usno_write P((struct peer *, const char *)); +#endif /* 0 */ + +/* + * Transfer vector + */ +struct refclock refclock_usno = { + usno_start, /* start up driver */ + usno_shutdown, /* shut down driver */ + usno_poll, /* transmit poll message */ + noentry, /* not used (usno_control) */ + noentry, /* not used (usno_init) */ + noentry, /* not used (usno_buginfo) */ + NOFLAGS /* not used */ +}; + + +/* + * usno_start - open the devices and initialize data for processing + */ +static int +usno_start( + int unit, + struct peer *peer + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + + /* + * Initialize miscellaneous variables + */ + pp = peer->procptr; + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + peer->minpoll = USNO_MINPOLL; + peer->maxpoll = USNO_MAXPOLL; + peer->sstclktype = CTL_SST_TS_TELEPHONE; + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct usnounit *) + emalloc(sizeof(struct usnounit)))) + return (0); + memset((char *)up, 0, sizeof(struct usnounit)); + up->unit = unit; + pp->unitptr = (caddr_t)up; + + /* + * Set up the driver timeout + */ + peer->nextdate = current_time + WAIT; + return (1); +} + + +/* + * usno_shutdown - shut down the clock + */ +static void +usno_shutdown( + int unit, + struct peer *peer + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + +#ifdef DEBUG + if (debug) + printf("usno: clock %s shutting down\n", ntoa(&peer->srcadr)); +#endif + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + usno_disc(peer); + free(up); +} + + +#if 0 +/* + * usno_receive - receive data from the serial interface + */ +static void +usno_receive( + struct recvbuf *rbufp + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + struct peer *peer; + char str[SMAX]; + u_long mjd; /* Modified Julian Day */ + static int day, hour, minute, second; + + /* + * Initialize pointers and read the timecode and timestamp. If + * the OK modem status code, leave it where folks can find it. + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, + &pp->lastrec); + if (pp->lencode == 0) { + if (strcmp(pp->a_lastcode, "OK") == 0) + pp->lencode = 2; + return; + } +#ifdef DEBUG + if (debug) + printf("usno: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif + + switch (up->state) { + + case 0: + + /* + * State 0. We are not expecting anything. Probably + * modem disconnect noise. Go back to sleep. + */ + return; + + case 1: + + /* + * State 1. We are about to dial. Just drop it. + */ + return; + + case 2: + + /* + * State 2. We are waiting for the call to be answered. + * All we care about here is CONNECT as the first token + * in the string. If the modem signals BUSY, ERROR, NO + * ANSWER, NO CARRIER or NO DIALTONE, we immediately + * hang up the phone. If CONNECT doesn't happen after + * ANSWER seconds, hang up the phone. If everything is + * okay, start the connect timeout and slide into state + * 3. + */ + (void)strncpy(str, strtok(pp->a_lastcode, " "), SMAX); + if (strcmp(str, "BUSY") == 0 || strcmp(str, "ERROR") == + 0 || strcmp(str, "NO") == 0) { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s USNO modem status %s", + ntoa(&peer->srcadr), pp->a_lastcode); + usno_disc(peer); + } else if (strcmp(str, "CONNECT") == 0) { + peer->nextdate = current_time + CONNECT; + up->msgcnt = 0; + up->state++; + } else { + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_WARNING, + "clock %s USNO unknown modem status %s", + ntoa(&peer->srcadr), pp->a_lastcode); + } + return; + + case 3: + + /* + * State 3. The call has been answered and we are + * waiting for the first message. If this doesn't + * happen within the timecode timeout, hang up the + * phone. We probably got a wrong number or they are + * down. + */ + peer->nextdate = current_time + TIMECODE; + up->state++; + return; + + case 4: + + /* + * State 4. We are reading a timecode. It's an actual + * timecode, or it's the `*' OTM. + * + * jjjjj nnn hhmmss UTC + */ + if (pp->lencode == LENCODE) { + if (sscanf(pp->a_lastcode, "%5ld %3d %2d%2d%2d UTC", + &mjd, &day, &hour, &minute, &second) != 5) { +#ifdef DEBUG + if (debug) + printf("usno: bad timecode format\n"); +#endif + refclock_report(peer, CEVNT_BADREPLY); + } else + up->msgcnt++; + return; + } else if (pp->lencode != 1 || !up->msgcnt) + return; + /* else, OTM; drop out of switch */ + } + + pp->leap = LEAP_NOWARNING; + pp->day = day; + pp->hour = hour; + pp->minute = minute; + pp->second = second; + + /* + * Colossal hack here. We process each sample in a trimmed-mean + * filter and determine the reference clock offset and + * dispersion. The fudge time1 value is added to each sample as + * received. + */ + if (!refclock_process(pp)) { +#ifdef DEBUG + if (debug) + printf("usno: time rejected\n"); +#endif + refclock_report(peer, CEVNT_BADTIME); + return; + } else if (up->msgcnt < MSGCNT) + return; + + /* + * We have a filtered sample offset ready for peer processing. + * We use lastrec as both the reference time and receive time in + * order to avoid being cute, like setting the reference time + * later than the receive time, which may cause a paranoid + * protocol module to chuck out the data. Finaly, we unhook the + * timeout, arm for the next call, fold the tent and go home. + */ + record_clock_stats(&peer->srcadr, pp->a_lastcode); + refclock_receive(peer); + pp->sloppyclockflag &= ~CLK_FLAG1; + up->pollcnt = 0; + up->state = 0; + usno_disc(peer); +} +#endif /* 0 */ + + +/* + * usno_poll - called by the transmit routine + */ +static void +usno_poll( + int unit, + struct peer *peer + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + + /* + * If the driver is running, we set the enable flag (fudge + * flag1), which causes the driver timeout routine to initiate a + * call. If not, the enable flag can be set using + * ntpdc. If this is the sustem peer, then follow the system + * poll interval. + */ + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + if (up->run) { + pp->sloppyclockflag |= CLK_FLAG1; + if (peer == sys_peer) + peer->hpoll = sys_poll; + else + peer->hpoll = peer->minpoll; + } +} + + +#if 0 +/* + * usno_timeout - called by the timer interrupt + */ +static void +usno_timeout( + struct peer *peer + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + int fd; + char device[20]; + char lockfile[128], pidbuf[8]; + int dtr = TIOCM_DTR; + + /* + * If a timeout occurs in other than state 0, the call has + * failed. If in state 0, we just see if there is other work to + * do. + */ + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + if (up->state) { + if (up->state != 1) { + usno_disc(peer); + return; + } + /* + * Call, and start the answer timeout. We think it + * strange if the OK status has not been received from + * the modem, but plow ahead anyway. + * + * This code is *here* because we had to stick in a brief + * delay to let the modem settle down after raising DTR, + * and for the OK to be received. State machines are + * contorted. + */ + if (strcmp(pp->a_lastcode, "OK") != 0) + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, "clock %s USNO no modem status", + ntoa(&peer->srcadr)); + (void)usno_write(peer, PHONE); + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, "clock %s USNO calling %s\n", + ntoa(&peer->srcadr), PHONE); + up->state = 2; + up->pollcnt++; + pp->polls++; + peer->nextdate = current_time + ANSWER; + return; + } + switch (peer->ttl) { + + /* + * In manual mode the calling program is activated + * by the ntpdc program using the enable flag (fudge + * flag1), either manually or by a cron job. + */ + case MODE_MANUAL: + up->run = 0; + break; + + /* + * In automatic mode the calling program runs + * continuously at intervals determined by the sys_poll + * variable. + */ + case MODE_AUTO: + if (!up->run) + pp->sloppyclockflag |= CLK_FLAG1; + up->run = 1; + break; + + /* + * In backup mode the calling program is disabled, + * unless no system peer has been selected for MAXOUTAGE + * (3600 s). Once enabled, it runs until some other NTP + * peer shows up. + */ + case MODE_BACKUP: + if (!up->run && sys_peer == 0) { + if (current_time - last_time > MAXOUTAGE) { + up->run = 1; + peer->hpoll = peer->minpoll; + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s USNO backup started ", + ntoa(&peer->srcadr)); + } + } else if (up->run && sys_peer->sstclktype != CTL_SST_TS_TELEPHONE) { + peer->hpoll = peer->minpoll; + up->run = 0; + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s USNO backup stopped", + ntoa(&peer->srcadr)); + } + break; + + default: + msyslog(LOG_ERR, + "clock %s USNO invalid mode", ntoa(&peer->srcadr)); + + } + + /* + * The fudge flag1 is used as an enable/disable; if set either + * by the code or via ntpdc, the calling program is + * started; if reset, the phones stop ringing. + */ + if (!(pp->sloppyclockflag & CLK_FLAG1)) { + up->pollcnt = 0; + peer->nextdate = current_time + IDLE; + return; + } + + /* + * Lock the port. + */ + (void)sprintf(lockfile, LOCKFILE, up->unit); + fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, 0644); + if (fd < 0) { + msyslog(LOG_ERR, "clock %s USNO port busy", + ntoa(&peer->srcadr)); + return; + } + sprintf(pidbuf, "%d\n", (int) getpid()); + write(fd, pidbuf, strlen(pidbuf)); + close(fd); + + /* + * Open serial port. Use ACTS line discipline, if available. It + * pumps a timestamp into the data stream at every on-time + * character '*' found. Note: the port must have modem control + * or deep pockets for the phone bill. HP-UX 9.03 users should + * have very deep pockets. + */ + (void)sprintf(device, DEVICE, up->unit); + if (!(fd = refclock_open(device, SPEED232, LDISC_ACTS))) { + unlink(lockfile); + return; + } + if (ioctl(fd, TIOCMBIC, (char *)&dtr) < 0) + msyslog(LOG_WARNING, "usno_timeout: clock %s: couldn't clear DTR: %m", + ntoa(&peer->srcadr)); + + pp->io.clock_recv = usno_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + unlink(lockfile); + free(up); + return; + } + + /* + * Initialize modem and kill DTR. We skedaddle if this comes + * bum. + */ + if (!usno_write(peer, MODEM_SETUP)) { + msyslog(LOG_ERR, "clock %s USNO couldn't write", + ntoa(&peer->srcadr)); + io_closeclock(&pp->io); + unlink(lockfile); + free(up); + return; + } + + /* + * Initiate a call to the Observatory. If we wind up here in + * other than state 0, a successful call could not be completed + * within minpoll seconds. + */ + if (up->pollcnt) { + refclock_report(peer, CEVNT_TIMEOUT); + NLOG(NLOG_CLOCKINFO) /* conditional if clause for conditional syslog */ + msyslog(LOG_NOTICE, + "clock %s USNO calling program terminated", + ntoa(&peer->srcadr)); + pp->sloppyclockflag &= ~CLK_FLAG1; + up->pollcnt = 0; +#ifdef DEBUG + if (debug) + printf("usno: calling program terminated\n"); +#endif + usno_disc(peer); + return; + } + + /* + * Raise DTR, and let the modem settle. Then we'll dial. + */ + if (ioctl(pp->io.fd, TIOCMBIS, (char *)&dtr) < -1) + msyslog(LOG_INFO, "usno_timeout: clock %s: couldn't set DTR: %m", + ntoa(&peer->srcadr)); + up->state = 1; + peer->nextdate = current_time + WAIT; +} +#endif /* 0 */ + + +/* + * usno_disc - disconnect the call and wait for the ruckus to cool + */ +static void +usno_disc( + struct peer *peer + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + int dtr = TIOCM_DTR; + char lockfile[128]; + + /* + * We should never get here other than in state 0, unless a call + * has timed out. We drop DTR, which will reliably get the modem + * off the air, even while the modem is hammering away full tilt. + */ + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + + if (ioctl(pp->io.fd, TIOCMBIC, (char *)&dtr) < 0) + msyslog(LOG_INFO, "usno_disc: clock %s: couldn't clear DTR: %m", + ntoa(&peer->srcadr)); + + if (up->state > 0) { + up->state = 0; + msyslog(LOG_NOTICE, "clock %s USNO call failed %d", + ntoa(&peer->srcadr), up->state); +#ifdef DEBUG + if (debug) + printf("usno: call failed %d\n", up->state); +#endif + } + + io_closeclock(&pp->io); + sprintf(lockfile, LOCKFILE, up->unit); + unlink(lockfile); + + peer->nextdate = current_time + WAIT; +} + + +#if 0 +/* + * usno_write - write a message to the serial port + */ +static int +usno_write( + struct peer *peer, + const char *str + ) +{ + register struct usnounit *up; + struct refclockproc *pp; + int len; + int code; + char cr = '\r'; + + /* + * Not much to do here, other than send the message, handle + * debug and report faults. + */ + pp = peer->procptr; + up = (struct usnounit *)pp->unitptr; + len = strlen(str); +#ifdef DEBUG + if (debug) + printf("usno: state %d send %d %s\n", up->state, len, + str); +#endif + code = write(pp->io.fd, str, (unsigned)len) == len; + code |= write(pp->io.fd, &cr, 1) == 1; + if (!code) + refclock_report(peer, CEVNT_FAULT); + return (code); +} +#endif /* 0 */ + +#else +int refclock_usno_bs; +#endif /* REFCLOCK */ |