154359Sroberto/* 2182007Sroberto * refclock_acts - clock driver for the NIST/USNO/PTB/NPL Computer Time 3182007Sroberto * Services 454359Sroberto */ 554359Sroberto#ifdef HAVE_CONFIG_H 654359Sroberto#include <config.h> 754359Sroberto#endif 854359Sroberto 9290001Sglebius#if defined(REFCLOCK) && defined(CLOCK_ACTS) 1054359Sroberto 1154359Sroberto#include "ntpd.h" 1254359Sroberto#include "ntp_io.h" 1354359Sroberto#include "ntp_unixtime.h" 1454359Sroberto#include "ntp_refclock.h" 1554359Sroberto#include "ntp_stdlib.h" 1654359Sroberto#include "ntp_control.h" 1754359Sroberto 1882498Sroberto#include <stdio.h> 1982498Sroberto#include <ctype.h> 2082498Sroberto#ifdef HAVE_SYS_IOCTL_H 2182498Sroberto# include <sys/ioctl.h> 2282498Sroberto#endif /* HAVE_SYS_IOCTL_H */ 2382498Sroberto 24290001Sglebius#ifdef SYS_WINNT 25290001Sglebius#undef write /* ports/winnt/include/config.h: #define write _write */ 26290001Sglebiusextern int async_write(int, const void *, unsigned int); 27290001Sglebius#define write(fd, data, octets) async_write(fd, data, octets) 28290001Sglebius#endif 29290001Sglebius 3054359Sroberto/* 31182007Sroberto * This driver supports the US (NIST, USNO) and European (PTB, NPL, 32182007Sroberto * etc.) modem time services, as well as Spectracom GPS and WWVB 33182007Sroberto * receivers connected via a modem. The driver periodically dials a 34182007Sroberto * number from a telephone list, receives the timecode data and 35182007Sroberto * calculates the local clock correction. It is designed primarily for 36182007Sroberto * use as backup when neither a radio clock nor connectivity to Internet 37182007Sroberto * time servers is available. 3854359Sroberto * 39182007Sroberto * This driver requires a modem with a Hayes-compatible command set and 40182007Sroberto * control over the modem data terminal ready (DTR) control line. The 41182007Sroberto * modem setup string is hard-coded in the driver and may require 42290001Sglebius * changes for nonstandard modems or special circumstances. 4354359Sroberto * 44290001Sglebius * When enabled, the calling program dials the first number in the 45290001Sglebius * phones file. If that call fails, it dials the second number and 46290001Sglebius * so on. The phone number is specified by the Hayes ATDT prefix 47290001Sglebius * followed by the number itself, including the long-distance prefix 48290001Sglebius * and delay code, if necessary. The calling program is enabled 49290001Sglebius * when (a) fudge flag1 is set by ntpdc, (b) at each poll interval 50290001Sglebius * when no other synchronization sources are present, and (c) at each 51290001Sglebius * poll interval whether or not other synchronization sources are 52290001Sglebius * present. The calling program disconnects if (a) the called party 53290001Sglebius * is busy or does not answer, (b) the called party disconnects 54290001Sglebius * before a sufficient nuimber of timecodes have been received. 5554359Sroberto * 56182007Sroberto * The driver is transparent to each of the modem time services and 57182007Sroberto * Spectracom radios. It selects the parsing algorithm depending on the 58182007Sroberto * message length. There is some hazard should the message be corrupted. 59182007Sroberto * However, the data format is checked carefully and only if all checks 60182007Sroberto * succeed is the message accepted. Corrupted lines are discarded 61182007Sroberto * without complaint. 6254359Sroberto * 63182007Sroberto * Fudge controls 6454359Sroberto * 65182007Sroberto * flag1 force a call in manual mode 66182007Sroberto * flag2 enable port locking (not verified) 67290001Sglebius * flag3 not used 68182007Sroberto * flag4 not used 6954359Sroberto * 70182007Sroberto * time1 offset adjustment (s) 7154359Sroberto * 72290001Sglebius * Ordinarily, the serial port is connected to a modem and the phones 73290001Sglebius * list is defined. If no phones list is defined, the port can be 74290001Sglebius * connected directly to a device or another computer. In this case the 75290001Sglebius * driver will send a single character 'T' at each poll event. If 76290001Sglebius * fudge flag2 is enabled, port locking allows the modem to be shared 77290001Sglebius * when not in use by this driver. 7854359Sroberto */ 7954359Sroberto/* 80182007Sroberto * National Institute of Science and Technology (NIST) 8154359Sroberto * 82182007Sroberto * Phone: (303) 494-4774 (Boulder, CO); (808) 335-4721 (Hawaii) 8354359Sroberto * 84182007Sroberto * Data Format 85182007Sroberto * 8654359Sroberto * National Institute of Standards and Technology 8754359Sroberto * Telephone Time Service, Generator 3B 8854359Sroberto * Enter question mark "?" for HELP 8954359Sroberto * D L D 9054359Sroberto * MJD YR MO DA H M S ST S UT1 msADV <OTM> 91182007Sroberto * 47999 90-04-18 21:39:15 50 0 +.1 045.0 UTC(NIST) *<CR><LF> 92182007Sroberto * ... 9354359Sroberto * 94182007Sroberto * MJD, DST, DUT1 and UTC are not used by this driver. The "*" or "#" is 95182007Sroberto * the on-time markers echoed by the driver and used by NIST to measure 96290001Sglebius * and correct for the propagation delay. Note: the ACTS timecode has 97290001Sglebius * recently been changed to eliminate the * on-time indicator. The 98290001Sglebius * reason for this and the long term implications are not clear. 9954359Sroberto * 100182007Sroberto * US Naval Observatory (USNO) 10154359Sroberto * 102182007Sroberto * Phone: (202) 762-1594 (Washington, DC); (719) 567-6742 (Boulder, CO) 10354359Sroberto * 104182007Sroberto * Data Format (two lines, repeating at one-second intervals) 10554359Sroberto * 106182007Sroberto * jjjjj nnn hhmmss UTC<CR><LF> 107182007Sroberto * *<CR><LF> 10854359Sroberto * 109182007Sroberto * jjjjj modified Julian day number (not used) 110182007Sroberto * nnn day of year 111182007Sroberto * hhmmss second of day 112182007Sroberto * * on-time marker for previous timecode 113182007Sroberto * ... 11454359Sroberto * 115182007Sroberto * USNO does not correct for the propagation delay. A fudge time1 of 116182007Sroberto * about .06 s is advisable. 11754359Sroberto * 118182007Sroberto * European Services (PTB, NPL, etc.) 11954359Sroberto * 120182007Sroberto * PTB: +49 531 512038 (Germany) 121182007Sroberto * NPL: 0906 851 6333 (UK only) 12254359Sroberto * 123182007Sroberto * Data format (see the documentation for phone numbers and formats.) 12454359Sroberto * 125182007Sroberto * 1995-01-23 20:58:51 MEZ 10402303260219950123195849740+40000500<CR><LF> 12654359Sroberto * 127182007Sroberto * Spectracom GPS and WWVB Receivers 12854359Sroberto * 129182007Sroberto * If a modem is connected to a Spectracom receiver, this driver will 130182007Sroberto * call it up and retrieve the time in one of two formats. As this 131182007Sroberto * driver does not send anything, the radio will have to either be 132182007Sroberto * configured in continuous mode or be polled by another local driver. 13354359Sroberto */ 13454359Sroberto/* 13554359Sroberto * Interface definitions 13654359Sroberto */ 137182007Sroberto#define DEVICE "/dev/acts%d" /* device name and unit */ 138290001Sglebius#define SPEED232 B19200 /* uart speed (19200 bps) */ 13954359Sroberto#define PRECISION (-10) /* precision assumed (about 1 ms) */ 140290001Sglebius#define LOCKFILE "/var/spool/lock/LCK..cua%d" 141182007Sroberto#define DESCRIPTION "Automated Computer Time Service" /* WRU */ 142182007Sroberto#define REFID "NONE" /* default reference ID */ 143182007Sroberto#define MSGCNT 20 /* max message count */ 144290001Sglebius#define MAXPHONE 10 /* max number of phone numbers */ 145182007Sroberto 146182007Sroberto/* 147290001Sglebius * Calling program modes (mode) 148182007Sroberto */ 149290001Sglebius#define MODE_BACKUP 0 /* backup mode */ 150290001Sglebius#define MODE_AUTO 1 /* automatic mode */ 15154359Sroberto#define MODE_MANUAL 2 /* manual mode */ 15254359Sroberto 153182007Sroberto/* 154290001Sglebius * Service identifiers (message length) 155182007Sroberto */ 156182007Sroberto#define REFACTS "NIST" /* NIST reference ID */ 157290001Sglebius#define LENACTS 50 /* NIST format A */ 158182007Sroberto#define REFUSNO "USNO" /* USNO reference ID */ 159182007Sroberto#define LENUSNO 20 /* USNO */ 160182007Sroberto#define REFPTB "PTB\0" /* PTB/NPL reference ID */ 161182007Sroberto#define LENPTB 78 /* PTB/NPL format */ 162182007Sroberto#define REFWWVB "WWVB" /* WWVB reference ID */ 163182007Sroberto#define LENWWVB0 22 /* WWVB format 0 */ 164182007Sroberto#define LENWWVB2 24 /* WWVB format 2 */ 165182007Sroberto#define LF 0x0a /* ASCII LF */ 16654359Sroberto 16754359Sroberto/* 168290001Sglebius * Modem setup strings. These may have to be changed for 169290001Sglebius * some modems. 17054359Sroberto * 17154359Sroberto * AT command prefix 172182007Sroberto * B1 US answer tone 173182007Sroberto * &C0 disable carrier detect 17454359Sroberto * &D2 hang up and return to command mode on DTR transition 17554359Sroberto * E0 modem command echo disabled 176290001Sglebius * L1 set modem speaker volume to low level 177182007Sroberto * M1 speaker enabled until carrier detect 17854359Sroberto * Q0 return result codes 17954359Sroberto * V1 return result codes as English words 180290001Sglebius * Y1 enable long-space disconnect 18154359Sroberto */ 182290001Sglebiusconst char def_modem_setup[] = "ATB1&C0&D2E0L1M1Q0V1Y1"; 183290001Sglebiusconst char *modem_setup = def_modem_setup; 18454359Sroberto 18554359Sroberto/* 186182007Sroberto * Timeouts (all in seconds) 18754359Sroberto */ 188182007Sroberto#define SETUP 3 /* setup timeout */ 189290001Sglebius#define REDIAL 30 /* redial timeout */ 190182007Sroberto#define ANSWER 60 /* answer timeout */ 191290001Sglebius#define TIMECODE 60 /* message timeout */ 192290001Sglebius#define MAXCODE 20 /* max timecodes */ 19354359Sroberto 19454359Sroberto/* 195182007Sroberto * State machine codes 19654359Sroberto */ 197290001Sglebiustypedef enum { 198290001Sglebius S_IDLE, /* wait for poll */ 199290001Sglebius S_SETUP, /* send modem setup */ 200290001Sglebius S_CONNECT, /* wait for answer */ 201290001Sglebius S_MSG /* wait for timecode */ 202290001Sglebius} teModemState; 20354359Sroberto 20454359Sroberto/* 20554359Sroberto * Unit control structure 20654359Sroberto */ 20754359Srobertostruct actsunit { 208182007Sroberto int unit; /* unit number */ 20954359Sroberto int state; /* the first one was Delaware */ 210182007Sroberto int timer; /* timeout counter */ 211182007Sroberto int retry; /* retry index */ 212182007Sroberto int msgcnt; /* count of messages received */ 213182007Sroberto l_fp tstamp; /* on-time timestamp */ 214290001Sglebius char *bufptr; /* next incoming char stored here */ 215290001Sglebius char buf[BMAX]; /* bufptr roams within buf[] */ 21654359Sroberto}; 21754359Sroberto 21854359Sroberto/* 21954359Sroberto * Function prototypes 22054359Sroberto */ 221290001Sglebiusstatic int acts_start (int, struct peer *); 222290001Sglebiusstatic void acts_shutdown (int, struct peer *); 223290001Sglebiusstatic void acts_receive (struct recvbuf *); 224290001Sglebiusstatic void acts_message (struct peer *, const char *); 225290001Sglebiusstatic void acts_timecode (struct peer *, const char *); 226290001Sglebiusstatic void acts_poll (int, struct peer *); 227290001Sglebiusstatic void acts_timeout (struct peer *, teModemState); 228290001Sglebiusstatic void acts_timer (int, struct peer *); 229290001Sglebiusstatic void acts_close (struct peer *); 23054359Sroberto 23154359Sroberto/* 23254359Sroberto * Transfer vector (conditional structure name) 23354359Sroberto */ 234290001Sglebiusstruct refclock refclock_acts = { 23554359Sroberto acts_start, /* start up driver */ 23654359Sroberto acts_shutdown, /* shut down driver */ 23754359Sroberto acts_poll, /* transmit poll message */ 238182007Sroberto noentry, /* not used */ 239182007Sroberto noentry, /* not used */ 240182007Sroberto noentry, /* not used */ 241182007Sroberto acts_timer /* housekeeping timer */ 24254359Sroberto}; 24354359Sroberto 24454359Sroberto/* 245182007Sroberto * Initialize data for processing 24654359Sroberto */ 24754359Srobertostatic int 248290001Sglebiusacts_start( 249182007Sroberto int unit, 25054359Sroberto struct peer *peer 25154359Sroberto ) 25254359Sroberto{ 253182007Sroberto struct actsunit *up; 25454359Sroberto struct refclockproc *pp; 255290001Sglebius const char *setup; 25654359Sroberto 25754359Sroberto /* 258182007Sroberto * Allocate and initialize unit structure 25954359Sroberto */ 260290001Sglebius up = emalloc_zero(sizeof(struct actsunit)); 261182007Sroberto up->unit = unit; 26254359Sroberto pp = peer->procptr; 263290001Sglebius pp->unitptr = up; 26454359Sroberto pp->io.clock_recv = acts_receive; 265290001Sglebius pp->io.srcclock = peer; 26654359Sroberto pp->io.datalen = 0; 267290001Sglebius pp->io.fd = -1; 26854359Sroberto 26954359Sroberto /* 27054359Sroberto * Initialize miscellaneous variables 27154359Sroberto */ 27254359Sroberto peer->precision = PRECISION; 27354359Sroberto pp->clockdesc = DESCRIPTION; 274290001Sglebius memcpy(&pp->refid, REFID, 4); 27554359Sroberto peer->sstclktype = CTL_SST_TS_TELEPHONE; 276290001Sglebius up->bufptr = up->buf; 277290001Sglebius if (def_modem_setup == modem_setup) { 278290001Sglebius setup = get_ext_sys_var("modemsetup"); 279290001Sglebius if (setup != NULL) 280290001Sglebius modem_setup = estrdup(setup); 281290001Sglebius } 282290001Sglebius 28354359Sroberto return (1); 28454359Sroberto} 28554359Sroberto 28654359Sroberto 28754359Sroberto/* 28854359Sroberto * acts_shutdown - shut down the clock 28954359Sroberto */ 29054359Srobertostatic void 291290001Sglebiusacts_shutdown( 292182007Sroberto int unit, 29354359Sroberto struct peer *peer 29454359Sroberto ) 29554359Sroberto{ 296182007Sroberto struct actsunit *up; 29754359Sroberto struct refclockproc *pp; 29854359Sroberto 299182007Sroberto /* 300182007Sroberto * Warning: do this only when a call is not in progress. 301182007Sroberto */ 30254359Sroberto pp = peer->procptr; 303290001Sglebius up = pp->unitptr; 304290001Sglebius acts_close(peer); 30554359Sroberto free(up); 30654359Sroberto} 30754359Sroberto 30854359Sroberto 30954359Sroberto/* 31054359Sroberto * acts_receive - receive data from the serial interface 31154359Sroberto */ 31254359Srobertostatic void 313290001Sglebiusacts_receive( 31454359Sroberto struct recvbuf *rbufp 31554359Sroberto ) 31654359Sroberto{ 317182007Sroberto struct actsunit *up; 31854359Sroberto struct refclockproc *pp; 31954359Sroberto struct peer *peer; 320290001Sglebius char tbuf[sizeof(up->buf)]; 321290001Sglebius char * tptr; 322290001Sglebius int octets; 323182007Sroberto 32454359Sroberto /* 325182007Sroberto * Initialize pointers and read the timecode and timestamp. Note 326182007Sroberto * we are in raw mode and victim of whatever the terminal 327182007Sroberto * interface kicks up; so, we have to reassemble messages from 328182007Sroberto * arbitrary fragments. Capture the timecode at the beginning of 329182007Sroberto * the message and at the '*' and '#' on-time characters. 33054359Sroberto */ 331290001Sglebius peer = rbufp->recv_peer; 33254359Sroberto pp = peer->procptr; 333290001Sglebius up = pp->unitptr; 334290001Sglebius octets = sizeof(up->buf) - (up->bufptr - up->buf); 335290001Sglebius refclock_gtraw(rbufp, tbuf, octets, &pp->lastrec); 336182007Sroberto for (tptr = tbuf; *tptr != '\0'; tptr++) { 337182007Sroberto if (*tptr == LF) { 338290001Sglebius if (up->bufptr == up->buf) { 339182007Sroberto up->tstamp = pp->lastrec; 340182007Sroberto continue; 341182007Sroberto } else { 342182007Sroberto *up->bufptr = '\0'; 343290001Sglebius up->bufptr = up->buf; 344290001Sglebius acts_message(peer, up->buf); 345182007Sroberto } 346290001Sglebius } else if (!iscntrl((unsigned char)*tptr)) { 347182007Sroberto *up->bufptr++ = *tptr; 348182007Sroberto if (*tptr == '*' || *tptr == '#') { 349182007Sroberto up->tstamp = pp->lastrec; 350290001Sglebius if (write(pp->io.fd, tptr, 1) < 0) 351290001Sglebius msyslog(LOG_ERR, "acts: write echo fails %m"); 352182007Sroberto } 353182007Sroberto } 35454359Sroberto } 355182007Sroberto} 356182007Sroberto 357182007Sroberto 358182007Sroberto/* 359182007Sroberto * acts_message - process message 360182007Sroberto */ 361182007Srobertovoid 362182007Srobertoacts_message( 363290001Sglebius struct peer *peer, 364290001Sglebius const char *msg 365182007Sroberto ) 366182007Sroberto{ 367182007Sroberto struct actsunit *up; 368182007Sroberto struct refclockproc *pp; 369290001Sglebius char tbuf[BMAX]; 370290001Sglebius int dtr = TIOCM_DTR; 371182007Sroberto 372290001Sglebius DPRINTF(1, ("acts: %d %s\n", (int)strlen(msg), msg)); 373290001Sglebius 374182007Sroberto /* 375182007Sroberto * What to do depends on the state and the first token in the 376290001Sglebius * message. 377182007Sroberto */ 378182007Sroberto pp = peer->procptr; 379290001Sglebius up = pp->unitptr; 380290001Sglebius 381290001Sglebius /* 382290001Sglebius * Extract the first token in the line. 383290001Sglebius */ 384290001Sglebius strlcpy(tbuf, msg, sizeof(tbuf)); 385182007Sroberto strtok(tbuf, " "); 386290001Sglebius switch (up->state) { 38754359Sroberto 388182007Sroberto /* 389182007Sroberto * We are waiting for the OK response to the modem setup 390290001Sglebius * command. When this happens, dial the number followed. 391290001Sglebius * If anything other than OK is received, just ignore it 392290001Sglebius * and wait for timeoue. 393182007Sroberto */ 394290001Sglebius case S_SETUP: 395182007Sroberto if (strcmp(tbuf, "OK") != 0) { 396290001Sglebius /* 397290001Sglebius * We disable echo with MODEM_SETUP's E0 but 398290001Sglebius * if the modem was previously E1, we will 399290001Sglebius * see MODEM_SETUP echoed before the OK/ERROR. 400290001Sglebius * Ignore it. 401290001Sglebius */ 402290001Sglebius if (!strcmp(tbuf, modem_setup)) 403290001Sglebius return; 404290001Sglebius break; 405182007Sroberto } 406290001Sglebius 407290001Sglebius mprintf_event(PEVNT_CLOCK, peer, "DIAL #%d %s", 408290001Sglebius up->retry, sys_phone[up->retry]); 409290001Sglebius if (ioctl(pp->io.fd, TIOCMBIS, &dtr) < 0) 410290001Sglebius msyslog(LOG_ERR, "acts: ioctl(TIOCMBIS) failed: %m"); 411290001Sglebius if (write(pp->io.fd, sys_phone[up->retry], 412290001Sglebius strlen(sys_phone[up->retry])) < 0) 413290001Sglebius msyslog(LOG_ERR, "acts: write DIAL fails %m"); 414290001Sglebius write(pp->io.fd, "\r", 1); 415290001Sglebius up->retry++; 416290001Sglebius up->state = S_CONNECT; 417290001Sglebius up->timer = ANSWER; 41854359Sroberto return; 41954359Sroberto 420182007Sroberto /* 421290001Sglebius * We are waiting for the CONNECT response to the dial 422290001Sglebius * command. When this happens, listen for timecodes. If 423290001Sglebius * somthing other than CONNECT is received, like BUSY 424290001Sglebius * or NO CARRIER, abort the call. 425182007Sroberto */ 426182007Sroberto case S_CONNECT: 427290001Sglebius if (strcmp(tbuf, "CONNECT") != 0) 428290001Sglebius break; 429290001Sglebius 430290001Sglebius report_event(PEVNT_CLOCK, peer, msg); 431290001Sglebius up->state = S_MSG; 432290001Sglebius up->timer = TIMECODE; 433290001Sglebius return; 434290001Sglebius 435290001Sglebius /* 436290001Sglebius * We are waiting for a timecode response. Pass it to 437290001Sglebius * the parser. If NO CARRIER is received, save the 438290001Sglebius * messages and abort the call. 439290001Sglebius */ 440290001Sglebius case S_MSG: 441290001Sglebius if (strcmp(tbuf, "NO") == 0) 442290001Sglebius report_event(PEVNT_CLOCK, peer, msg); 443290001Sglebius if (up->msgcnt < MAXCODE) 444290001Sglebius acts_timecode(peer, msg); 445290001Sglebius else 446290001Sglebius acts_timeout(peer, S_MSG); 447290001Sglebius return; 448290001Sglebius } 449290001Sglebius 450290001Sglebius /* 451290001Sglebius * Other response. Tell us about it. 452290001Sglebius */ 453290001Sglebius report_event(PEVNT_CLOCK, peer, msg); 454290001Sglebius acts_close(peer); 455290001Sglebius} 456290001Sglebius 457290001Sglebius 458290001Sglebius/* 459290001Sglebius * acts_timeout - called on timeout 460290001Sglebius */ 461290001Sglebiusstatic void 462290001Sglebiusacts_timeout( 463290001Sglebius struct peer *peer, 464290001Sglebius teModemState dstate 465290001Sglebius ) 466290001Sglebius{ 467290001Sglebius struct actsunit *up; 468290001Sglebius struct refclockproc *pp; 469290001Sglebius int fd; 470290001Sglebius int rc; 471290001Sglebius char device[20]; 472290001Sglebius char lockfile[128], pidbuf[8]; 473290001Sglebius 474290001Sglebius /* 475290001Sglebius * The state machine is driven by messages from the modem, 476290001Sglebius * when first started and at timeout. 477290001Sglebius */ 478290001Sglebius pp = peer->procptr; 479290001Sglebius up = pp->unitptr; 480290001Sglebius switch (dstate) { 481290001Sglebius 482290001Sglebius /* 483290001Sglebius * System poll event. Lock the modem port, open the device 484290001Sglebius * and send the setup command. 485290001Sglebius */ 486290001Sglebius case S_IDLE: 487290001Sglebius if (-1 != pp->io.fd) 488290001Sglebius return; /* port is already open */ 489290001Sglebius 490290001Sglebius /* 491290001Sglebius * Lock the modem port. If busy, retry later. Note: if 492290001Sglebius * something fails between here and the close, the lock 493290001Sglebius * file may not be removed. 494290001Sglebius */ 495290001Sglebius if (pp->sloppyclockflag & CLK_FLAG2) { 496290001Sglebius snprintf(lockfile, sizeof(lockfile), LOCKFILE, 497290001Sglebius up->unit); 498290001Sglebius fd = open(lockfile, O_WRONLY | O_CREAT | O_EXCL, 499290001Sglebius 0644); 500290001Sglebius if (fd < 0) { 501290001Sglebius report_event(PEVNT_CLOCK, peer, "acts: port busy"); 502290001Sglebius return; 503290001Sglebius } 504290001Sglebius snprintf(pidbuf, sizeof(pidbuf), "%d\n", 505290001Sglebius (u_int)getpid()); 506290001Sglebius if (write(fd, pidbuf, strlen(pidbuf)) < 0) 507290001Sglebius msyslog(LOG_ERR, "acts: write lock fails %m"); 508290001Sglebius close(fd); 509290001Sglebius } 510290001Sglebius 511290001Sglebius /* 512290001Sglebius * Open the device in raw mode and link the I/O. 513290001Sglebius */ 514290001Sglebius snprintf(device, sizeof(device), DEVICE, 515290001Sglebius up->unit); 516290001Sglebius fd = refclock_open(device, SPEED232, LDISC_ACTS | 517290001Sglebius LDISC_RAW | LDISC_REMOTE); 518290001Sglebius if (fd < 0) { 519290001Sglebius msyslog(LOG_ERR, "acts: open fails %m"); 52054359Sroberto return; 52154359Sroberto } 522290001Sglebius pp->io.fd = fd; 523290001Sglebius if (!io_addclock(&pp->io)) { 524290001Sglebius msyslog(LOG_ERR, "acts: addclock fails"); 525290001Sglebius close(fd); 526290001Sglebius pp->io.fd = -1; 527290001Sglebius return; 528290001Sglebius } 529290001Sglebius up->msgcnt = 0; 530290001Sglebius up->bufptr = up->buf; 531290001Sglebius 532290001Sglebius /* 533290001Sglebius * If the port is directly connected to the device, skip 534290001Sglebius * the modem business and send 'T' for Spectrabum. 535290001Sglebius */ 536290001Sglebius if (sys_phone[up->retry] == NULL) { 537290001Sglebius if (write(pp->io.fd, "T", 1) < 0) 538290001Sglebius msyslog(LOG_ERR, "acts: write T fails %m"); 539290001Sglebius up->state = S_MSG; 540290001Sglebius up->timer = TIMECODE; 541290001Sglebius return; 542290001Sglebius } 543290001Sglebius 544290001Sglebius /* 545290001Sglebius * Initialize the modem. This works with Hayes- 546290001Sglebius * compatible modems. 547290001Sglebius */ 548290001Sglebius mprintf_event(PEVNT_CLOCK, peer, "SETUP %s", 549290001Sglebius modem_setup); 550290001Sglebius rc = write(pp->io.fd, modem_setup, strlen(modem_setup)); 551290001Sglebius if (rc < 0) 552290001Sglebius msyslog(LOG_ERR, "acts: write SETUP fails %m"); 553290001Sglebius write(pp->io.fd, "\r", 1); 554290001Sglebius up->state = S_SETUP; 555290001Sglebius up->timer = SETUP; 55654359Sroberto return; 55754359Sroberto 558182007Sroberto /* 559290001Sglebius * In SETUP state the modem did not respond OK to setup string. 560182007Sroberto */ 561290001Sglebius case S_SETUP: 562290001Sglebius report_event(PEVNT_CLOCK, peer, "no modem"); 563290001Sglebius break; 564290001Sglebius 565290001Sglebius /* 566290001Sglebius * In CONNECT state the call did not complete. Abort the call. 567290001Sglebius */ 568290001Sglebius case S_CONNECT: 569290001Sglebius report_event(PEVNT_CLOCK, peer, "no answer"); 570290001Sglebius break; 571290001Sglebius 572290001Sglebius /* 573290001Sglebius * In MSG states no further timecodes are expected. If any 574290001Sglebius * timecodes have arrived, update the clock. In any case, 575290001Sglebius * terminate the call. 576290001Sglebius */ 577182007Sroberto case S_MSG: 578290001Sglebius if (up->msgcnt == 0) { 579290001Sglebius report_event(PEVNT_CLOCK, peer, "no timecodes"); 580290001Sglebius } else { 581290001Sglebius pp->lastref = pp->lastrec; 582290001Sglebius record_clock_stats(&peer->srcadr, pp->a_lastcode); 583290001Sglebius refclock_receive(peer); 584290001Sglebius } 585182007Sroberto break; 58654359Sroberto } 587290001Sglebius acts_close(peer); 588182007Sroberto} 58954359Sroberto 590290001Sglebius 591182007Sroberto/* 592290001Sglebius * acts_close - close and prepare for next call. 593290001Sglebius * 594290001Sglebius * In ClOSE state no further protocol actions are required 595290001Sglebius * other than to close and release the device and prepare to 596290001Sglebius * dial the next number if necessary. 597290001Sglebius */ 598290001Sglebiusvoid 599290001Sglebiusacts_close( 600290001Sglebius struct peer *peer 601290001Sglebius ) 602290001Sglebius{ 603290001Sglebius struct actsunit *up; 604290001Sglebius struct refclockproc *pp; 605290001Sglebius char lockfile[128]; 606290001Sglebius int dtr; 607290001Sglebius 608290001Sglebius pp = peer->procptr; 609290001Sglebius up = pp->unitptr; 610290001Sglebius if (pp->io.fd != -1) { 611290001Sglebius report_event(PEVNT_CLOCK, peer, "close"); 612290001Sglebius dtr = TIOCM_DTR; 613290001Sglebius if (ioctl(pp->io.fd, TIOCMBIC, &dtr) < 0) 614290001Sglebius msyslog(LOG_ERR, "acts: ioctl(TIOCMBIC) failed: %m"); 615290001Sglebius io_closeclock(&pp->io); 616290001Sglebius pp->io.fd = -1; 617290001Sglebius } 618290001Sglebius if (pp->sloppyclockflag & CLK_FLAG2) { 619290001Sglebius snprintf(lockfile, sizeof(lockfile), 620290001Sglebius LOCKFILE, up->unit); 621290001Sglebius unlink(lockfile); 622290001Sglebius } 623290001Sglebius if (up->msgcnt == 0 && up->retry > 0) { 624290001Sglebius if (sys_phone[up->retry] != NULL) { 625290001Sglebius up->state = S_IDLE; 626290001Sglebius up->timer = REDIAL; 627290001Sglebius return; 628290001Sglebius } 629290001Sglebius } 630290001Sglebius up->state = S_IDLE; 631290001Sglebius up->timer = 0; 632290001Sglebius} 633290001Sglebius 634290001Sglebius 635290001Sglebius/* 636290001Sglebius * acts_poll - called by the transmit routine 637290001Sglebius */ 638290001Sglebiusstatic void 639290001Sglebiusacts_poll( 640290001Sglebius int unit, 641290001Sglebius struct peer *peer 642290001Sglebius ) 643290001Sglebius{ 644290001Sglebius struct actsunit *up; 645290001Sglebius struct refclockproc *pp; 646290001Sglebius 647290001Sglebius /* 648290001Sglebius * This routine is called at every system poll. All it does is 649290001Sglebius * set flag1 under certain conditions. The real work is done by 650290001Sglebius * the timeout routine and state machine. 651290001Sglebius */ 652290001Sglebius pp = peer->procptr; 653290001Sglebius up = pp->unitptr; 654290001Sglebius switch (peer->ttl) { 655290001Sglebius 656290001Sglebius /* 657290001Sglebius * In manual mode the calling program is activated by the ntpdc 658290001Sglebius * program using the enable flag (fudge flag1), either manually 659290001Sglebius * or by a cron job. 660290001Sglebius */ 661290001Sglebius case MODE_MANUAL: 662290001Sglebius return; 663290001Sglebius 664290001Sglebius /* 665290001Sglebius * In automatic mode the calling program runs continuously at 666290001Sglebius * intervals determined by the poll event or specified timeout. 667290001Sglebius */ 668290001Sglebius case MODE_AUTO: 669290001Sglebius break; 670290001Sglebius 671290001Sglebius /* 672290001Sglebius * In backup mode the calling program runs continuously as long 673290001Sglebius * as either no peers are available or this peer is selected. 674290001Sglebius */ 675290001Sglebius case MODE_BACKUP: 676290001Sglebius if (!(sys_peer == NULL || sys_peer == peer)) 677290001Sglebius return; 678290001Sglebius 679290001Sglebius break; 680290001Sglebius } 681290001Sglebius pp->polls++; 682290001Sglebius if (S_IDLE == up->state) { 683290001Sglebius up->retry = 0; 684290001Sglebius acts_timeout(peer, S_IDLE); 685290001Sglebius } 686290001Sglebius} 687290001Sglebius 688290001Sglebius 689290001Sglebius/* 690290001Sglebius * acts_timer - called at one-second intervals 691290001Sglebius */ 692290001Sglebiusstatic void 693290001Sglebiusacts_timer( 694290001Sglebius int unit, 695290001Sglebius struct peer *peer 696290001Sglebius ) 697290001Sglebius{ 698290001Sglebius struct actsunit *up; 699290001Sglebius struct refclockproc *pp; 700290001Sglebius 701290001Sglebius /* 702290001Sglebius * This routine implments a timeout which runs for a programmed 703290001Sglebius * interval. The counter is initialized by the state machine and 704290001Sglebius * counts down to zero. Upon reaching zero, the state machine is 705290001Sglebius * called. If flag1 is set while timer is zero, force a call. 706290001Sglebius */ 707290001Sglebius pp = peer->procptr; 708290001Sglebius up = pp->unitptr; 709290001Sglebius if (up->timer == 0) { 710290001Sglebius if (pp->sloppyclockflag & CLK_FLAG1) { 711290001Sglebius pp->sloppyclockflag &= ~CLK_FLAG1; 712290001Sglebius acts_timeout(peer, S_IDLE); 713290001Sglebius } 714290001Sglebius } else { 715290001Sglebius up->timer--; 716290001Sglebius if (up->timer == 0) 717290001Sglebius acts_timeout(peer, up->state); 718290001Sglebius } 719290001Sglebius} 720290001Sglebius 721290001Sglebius/* 722182007Sroberto * acts_timecode - identify the service and parse the timecode message 723182007Sroberto */ 724182007Srobertovoid 725182007Srobertoacts_timecode( 726290001Sglebius struct peer * peer, /* peer structure pointer */ 727290001Sglebius const char * str /* timecode string */ 728182007Sroberto ) 729182007Sroberto{ 730182007Sroberto struct actsunit *up; 731182007Sroberto struct refclockproc *pp; 732182007Sroberto int day; /* day of the month */ 733182007Sroberto int month; /* month of the year */ 734182007Sroberto u_long mjd; /* Modified Julian Day */ 735182007Sroberto double dut1; /* DUT adjustment */ 736182007Sroberto 737182007Sroberto u_int dst; /* ACTS daylight/standard time */ 738182007Sroberto u_int leap; /* ACTS leap indicator */ 739182007Sroberto double msADV; /* ACTS transmit advance (ms) */ 740182007Sroberto char utc[10]; /* ACTS timescale */ 741182007Sroberto char flag; /* ACTS on-time character (* or #) */ 742182007Sroberto 743182007Sroberto char synchar; /* WWVB synchronized indicator */ 744182007Sroberto char qualchar; /* WWVB quality indicator */ 745182007Sroberto char leapchar; /* WWVB leap indicator */ 746182007Sroberto char dstchar; /* WWVB daylight/savings indicator */ 747182007Sroberto int tz; /* WWVB timezone */ 748182007Sroberto 749290001Sglebius int leapmonth; /* PTB/NPL month of leap */ 750182007Sroberto char leapdir; /* PTB/NPL leap direction */ 751182007Sroberto 75254359Sroberto /* 753182007Sroberto * The parser selects the modem format based on the message 754182007Sroberto * length. Since the data are checked carefully, occasional 755182007Sroberto * errors due noise are forgivable. 75654359Sroberto */ 757182007Sroberto pp = peer->procptr; 758290001Sglebius up = pp->unitptr; 759182007Sroberto pp->nsec = 0; 760290001Sglebius switch (strlen(str)) { 76154359Sroberto 76254359Sroberto /* 763182007Sroberto * For USNO format on-time character '*', which is on a line by 764182007Sroberto * itself. Be sure a timecode has been received. 76554359Sroberto */ 766182007Sroberto case 1: 767182007Sroberto if (*str == '*' && up->msgcnt > 0) 768182007Sroberto break; 769182007Sroberto 77054359Sroberto return; 771182007Sroberto 77254359Sroberto /* 773290001Sglebius * ACTS format A: "jjjjj yy-mm-dd hh:mm:ss ds l uuu aaaaa 774290001Sglebius * UTC(NIST) *". 77554359Sroberto */ 776182007Sroberto case LENACTS: 777182007Sroberto if (sscanf(str, 778182007Sroberto "%5ld %2d-%2d-%2d %2d:%2d:%2d %2d %1d %3lf %5lf %9s %c", 779182007Sroberto &mjd, &pp->year, &month, &day, &pp->hour, 780182007Sroberto &pp->minute, &pp->second, &dst, &leap, &dut1, 781182007Sroberto &msADV, utc, &flag) != 13) { 782182007Sroberto refclock_report(peer, CEVNT_BADREPLY); 783182007Sroberto return; 784182007Sroberto } 785182007Sroberto pp->day = ymd2yd(pp->year, month, day); 786182007Sroberto pp->leap = LEAP_NOWARNING; 787182007Sroberto if (leap == 1) 788290001Sglebius pp->leap = LEAP_ADDSECOND; 789290001Sglebius else if (leap == 2) 790290001Sglebius pp->leap = LEAP_DELSECOND; 791182007Sroberto memcpy(&pp->refid, REFACTS, 4); 792182007Sroberto up->msgcnt++; 793290001Sglebius if (flag != '#' && up->msgcnt < 10) 794290001Sglebius return; 795290001Sglebius 796182007Sroberto break; 797182007Sroberto 79854359Sroberto /* 799182007Sroberto * USNO format: "jjjjj nnn hhmmss UTC" 80054359Sroberto */ 801182007Sroberto case LENUSNO: 802182007Sroberto if (sscanf(str, "%5ld %3d %2d%2d%2d %3s", 803182007Sroberto &mjd, &pp->day, &pp->hour, &pp->minute, 804182007Sroberto &pp->second, utc) != 6) { 805182007Sroberto refclock_report(peer, CEVNT_BADREPLY); 806182007Sroberto return; 807182007Sroberto } 80854359Sroberto 809182007Sroberto /* 810182007Sroberto * Wait for the on-time character, which follows in a 811182007Sroberto * separate message. There is no provision for leap 812182007Sroberto * warning. 813182007Sroberto */ 814182007Sroberto pp->leap = LEAP_NOWARNING; 815182007Sroberto memcpy(&pp->refid, REFUSNO, 4); 816182007Sroberto up->msgcnt++; 817290001Sglebius break; 818182007Sroberto 81954359Sroberto /* 820182007Sroberto * PTB/NPL format: "yyyy-mm-dd hh:mm:ss MEZ" 82154359Sroberto */ 822182007Sroberto case LENPTB: 823182007Sroberto if (sscanf(str, 824182007Sroberto "%*4d-%*2d-%*2d %*2d:%*2d:%2d %*5c%*12c%4d%2d%2d%2d%2d%5ld%2lf%c%2d%3lf%*15c%c", 825182007Sroberto &pp->second, &pp->year, &month, &day, &pp->hour, 826182007Sroberto &pp->minute, &mjd, &dut1, &leapdir, &leapmonth, 827182007Sroberto &msADV, &flag) != 12) { 828182007Sroberto refclock_report(peer, CEVNT_BADREPLY); 829182007Sroberto return; 830182007Sroberto } 831182007Sroberto pp->leap = LEAP_NOWARNING; 832182007Sroberto if (leapmonth == month) { 833182007Sroberto if (leapdir == '+') 834290001Sglebius pp->leap = LEAP_ADDSECOND; 835182007Sroberto else if (leapdir == '-') 836290001Sglebius pp->leap = LEAP_DELSECOND; 837182007Sroberto } 838182007Sroberto pp->day = ymd2yd(pp->year, month, day); 839182007Sroberto memcpy(&pp->refid, REFPTB, 4); 840182007Sroberto up->msgcnt++; 841182007Sroberto break; 84282498Sroberto 843182007Sroberto 84482498Sroberto /* 845182007Sroberto * WWVB format 0: "I ddd hh:mm:ss DTZ=nn" 84682498Sroberto */ 847182007Sroberto case LENWWVB0: 848182007Sroberto if (sscanf(str, "%c %3d %2d:%2d:%2d %cTZ=%2d", 849182007Sroberto &synchar, &pp->day, &pp->hour, &pp->minute, 850182007Sroberto &pp->second, &dstchar, &tz) != 7) { 851182007Sroberto refclock_report(peer, CEVNT_BADREPLY); 85254359Sroberto return; 85354359Sroberto } 854182007Sroberto pp->leap = LEAP_NOWARNING; 855182007Sroberto if (synchar != ' ') 856182007Sroberto pp->leap = LEAP_NOTINSYNC; 857182007Sroberto memcpy(&pp->refid, REFWWVB, 4); 858182007Sroberto up->msgcnt++; 859182007Sroberto break; 860182007Sroberto 861182007Sroberto /* 862182007Sroberto * WWVB format 2: "IQyy ddd hh:mm:ss.mmm LD" 863182007Sroberto */ 864182007Sroberto case LENWWVB2: 865182007Sroberto if (sscanf(str, "%c%c%2d %3d %2d:%2d:%2d.%3ld%c%c%c", 866182007Sroberto &synchar, &qualchar, &pp->year, &pp->day, 867182007Sroberto &pp->hour, &pp->minute, &pp->second, &pp->nsec, 868182007Sroberto &dstchar, &leapchar, &dstchar) != 11) { 869182007Sroberto refclock_report(peer, CEVNT_BADREPLY); 87054359Sroberto return; 87154359Sroberto } 872182007Sroberto pp->nsec *= 1000000; 873182007Sroberto pp->leap = LEAP_NOWARNING; 874182007Sroberto if (synchar != ' ') 875182007Sroberto pp->leap = LEAP_NOTINSYNC; 876182007Sroberto else if (leapchar == 'L') 877182007Sroberto pp->leap = LEAP_ADDSECOND; 878182007Sroberto memcpy(&pp->refid, REFWWVB, 4); 879182007Sroberto up->msgcnt++; 880182007Sroberto break; 88154359Sroberto 88254359Sroberto /* 883182007Sroberto * None of the above. Just forget about it and wait for the next 884182007Sroberto * message or timeout. 88554359Sroberto */ 886182007Sroberto default: 887182007Sroberto return; 88854359Sroberto } 88954359Sroberto 89054359Sroberto /* 891182007Sroberto * We have a valid timecode. The fudge time1 value is added to 892182007Sroberto * each sample by the main line routines. Note that in current 893182007Sroberto * telephone networks the propatation time can be different for 894182007Sroberto * each call and can reach 200 ms for some calls. 89554359Sroberto */ 896182007Sroberto peer->refid = pp->refid; 897182007Sroberto pp->lastrec = up->tstamp; 898290001Sglebius if (up->msgcnt == 0) 899290001Sglebius return; 900290001Sglebius 901290001Sglebius strlcpy(pp->a_lastcode, str, sizeof(pp->a_lastcode)); 902290001Sglebius pp->lencode = strlen(pp->a_lastcode); 903182007Sroberto if (!refclock_process(pp)) { 904182007Sroberto refclock_report(peer, CEVNT_BADTIME); 905182007Sroberto return; 906290001Sglebius } 907132451Sroberto pp->lastref = pp->lastrec; 90854359Sroberto} 90954359Sroberto#else 91054359Srobertoint refclock_acts_bs; 91154359Sroberto#endif /* REFCLOCK */ 912