/* $NetBSD: refclock_usno.c,v 1.2 2003/12/04 16:23:37 drochner Exp $ */ /* * refclock_usno - clock driver for the Naval Observatory dialup * Michael Shields 1995/02/25 */ #ifdef HAVE_CONFIG_H #include #endif #if defined(REFCLOCK) && defined(CLOCK_USNO) #include "ntpd.h" #include "ntp_io.h" #include "ntp_unixtime.h" #include "ntp_refclock.h" #include "ntp_stdlib.h" #include "ntp_control.h" #include #include #ifdef HAVE_SYS_IOCTL_H # include #endif /* HAVE_SYS_IOCTL_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. */ pp->lastref = pp->lastrec; refclock_receive(peer); record_clock_stats(&peer->srcadr, pp->a_lastcode); 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 */