1*eabc0478Schristos /* $NetBSD: refclock_acts.c,v 1.13 2024/08/18 20:47:18 christos Exp $ */ 2abb0f93cSkardel 3abb0f93cSkardel /* 4abb0f93cSkardel * refclock_acts - clock driver for the NIST/USNO/PTB/NPL Computer Time 5abb0f93cSkardel * Services 6abb0f93cSkardel */ 7abb0f93cSkardel #ifdef HAVE_CONFIG_H 8abb0f93cSkardel #include <config.h> 9abb0f93cSkardel #endif 10abb0f93cSkardel 112950cc38Schristos #if defined(REFCLOCK) && defined(CLOCK_ACTS) 12abb0f93cSkardel 13abb0f93cSkardel #include "ntpd.h" 14abb0f93cSkardel #include "ntp_io.h" 15abb0f93cSkardel #include "ntp_unixtime.h" 16abb0f93cSkardel #include "ntp_refclock.h" 17abb0f93cSkardel #include "ntp_stdlib.h" 18abb0f93cSkardel #include "ntp_control.h" 19abb0f93cSkardel 20abb0f93cSkardel #include <stdio.h> 21abb0f93cSkardel #include <ctype.h> 22abb0f93cSkardel #ifdef HAVE_SYS_IOCTL_H 23abb0f93cSkardel # include <sys/ioctl.h> 24abb0f93cSkardel #endif /* HAVE_SYS_IOCTL_H */ 25abb0f93cSkardel 26abb0f93cSkardel /* 27abb0f93cSkardel * This driver supports the US (NIST, USNO) and European (PTB, NPL, 28abb0f93cSkardel * etc.) modem time services, as well as Spectracom GPS and WWVB 29abb0f93cSkardel * receivers connected via a modem. The driver periodically dials a 30abb0f93cSkardel * number from a telephone list, receives the timecode data and 31abb0f93cSkardel * calculates the local clock correction. It is designed primarily for 32abb0f93cSkardel * use as backup when neither a radio clock nor connectivity to Internet 33abb0f93cSkardel * time servers is available. 34abb0f93cSkardel * 35abb0f93cSkardel * This driver requires a modem with a Hayes-compatible command set and 36abb0f93cSkardel * control over the modem data terminal ready (DTR) control line. The 37abb0f93cSkardel * modem setup string is hard-coded in the driver and may require 382950cc38Schristos * changes for nonstandard modems or special circumstances. 39abb0f93cSkardel * 402950cc38Schristos * When enabled, the calling program dials the first number in the 412950cc38Schristos * phones file. If that call fails, it dials the second number and 422950cc38Schristos * so on. The phone number is specified by the Hayes ATDT prefix 432950cc38Schristos * followed by the number itself, including the long-distance prefix 442950cc38Schristos * and delay code, if necessary. The calling program is enabled 452950cc38Schristos * when (a) fudge flag1 is set by ntpdc, (b) at each poll interval 462950cc38Schristos * when no other synchronization sources are present, and (c) at each 472950cc38Schristos * poll interval whether or not other synchronization sources are 482950cc38Schristos * present. The calling program disconnects if (a) the called party 492950cc38Schristos * is busy or does not answer, (b) the called party disconnects 502950cc38Schristos * before a sufficient nuimber of timecodes have been received. 51abb0f93cSkardel * 52abb0f93cSkardel * The driver is transparent to each of the modem time services and 53abb0f93cSkardel * Spectracom radios. It selects the parsing algorithm depending on the 54abb0f93cSkardel * message length. There is some hazard should the message be corrupted. 55abb0f93cSkardel * However, the data format is checked carefully and only if all checks 56abb0f93cSkardel * succeed is the message accepted. Corrupted lines are discarded 57abb0f93cSkardel * without complaint. 58abb0f93cSkardel * 59abb0f93cSkardel * Fudge controls 60abb0f93cSkardel * 61abb0f93cSkardel * flag1 force a call in manual mode 62abb0f93cSkardel * flag2 enable port locking (not verified) 632950cc38Schristos * flag3 not used 64abb0f93cSkardel * flag4 not used 65abb0f93cSkardel * 66abb0f93cSkardel * time1 offset adjustment (s) 67abb0f93cSkardel * 682950cc38Schristos * Ordinarily, the serial port is connected to a modem and the phones 692950cc38Schristos * list is defined. If no phones list is defined, the port can be 702950cc38Schristos * connected directly to a device or another computer. In this case the 712950cc38Schristos * driver will send a single character 'T' at each poll event. If 722950cc38Schristos * fudge flag2 is enabled, port locking allows the modem to be shared 732950cc38Schristos * when not in use by this driver. 74abb0f93cSkardel */ 75abb0f93cSkardel /* 76abb0f93cSkardel * National Institute of Science and Technology (NIST) 77abb0f93cSkardel * 78abb0f93cSkardel * Phone: (303) 494-4774 (Boulder, CO); (808) 335-4721 (Hawaii) 79abb0f93cSkardel * 80abb0f93cSkardel * Data Format 81abb0f93cSkardel * 82abb0f93cSkardel * National Institute of Standards and Technology 83abb0f93cSkardel * Telephone Time Service, Generator 3B 84abb0f93cSkardel * Enter question mark "?" for HELP 85abb0f93cSkardel * D L D 86abb0f93cSkardel * MJD YR MO DA H M S ST S UT1 msADV <OTM> 87abb0f93cSkardel * 47999 90-04-18 21:39:15 50 0 +.1 045.0 UTC(NIST) *<CR><LF> 88abb0f93cSkardel * ... 89abb0f93cSkardel * 90abb0f93cSkardel * MJD, DST, DUT1 and UTC are not used by this driver. The "*" or "#" is 91abb0f93cSkardel * the on-time markers echoed by the driver and used by NIST to measure 922950cc38Schristos * and correct for the propagation delay. Note: the ACTS timecode has 932950cc38Schristos * recently been changed to eliminate the * on-time indicator. The 942950cc38Schristos * reason for this and the long term implications are not clear. 95abb0f93cSkardel * 96abb0f93cSkardel * US Naval Observatory (USNO) 97abb0f93cSkardel * 98abb0f93cSkardel * Phone: (202) 762-1594 (Washington, DC); (719) 567-6742 (Boulder, CO) 99abb0f93cSkardel * 100abb0f93cSkardel * Data Format (two lines, repeating at one-second intervals) 101abb0f93cSkardel * 102abb0f93cSkardel * jjjjj nnn hhmmss UTC<CR><LF> 103abb0f93cSkardel * *<CR><LF> 104abb0f93cSkardel * 105abb0f93cSkardel * jjjjj modified Julian day number (not used) 106abb0f93cSkardel * nnn day of year 107abb0f93cSkardel * hhmmss second of day 108abb0f93cSkardel * * on-time marker for previous timecode 109abb0f93cSkardel * ... 110abb0f93cSkardel * 111abb0f93cSkardel * USNO does not correct for the propagation delay. A fudge time1 of 112abb0f93cSkardel * about .06 s is advisable. 113abb0f93cSkardel * 114abb0f93cSkardel * European Services (PTB, NPL, etc.) 115abb0f93cSkardel * 116abb0f93cSkardel * PTB: +49 531 512038 (Germany) 117abb0f93cSkardel * NPL: 0906 851 6333 (UK only) 118abb0f93cSkardel * 119abb0f93cSkardel * Data format (see the documentation for phone numbers and formats.) 120abb0f93cSkardel * 121abb0f93cSkardel * 1995-01-23 20:58:51 MEZ 10402303260219950123195849740+40000500<CR><LF> 122abb0f93cSkardel * 123abb0f93cSkardel * Spectracom GPS and WWVB Receivers 124abb0f93cSkardel * 125abb0f93cSkardel * If a modem is connected to a Spectracom receiver, this driver will 126abb0f93cSkardel * call it up and retrieve the time in one of two formats. As this 127abb0f93cSkardel * driver does not send anything, the radio will have to either be 128abb0f93cSkardel * configured in continuous mode or be polled by another local driver. 129abb0f93cSkardel */ 130abb0f93cSkardel /* 131abb0f93cSkardel * Interface definitions 132abb0f93cSkardel */ 133abb0f93cSkardel #define DEVICE "/dev/acts%d" /* device name and unit */ 1342950cc38Schristos #define SPEED232 B19200 /* uart speed (19200 bps) */ 135abb0f93cSkardel #define PRECISION (-10) /* precision assumed (about 1 ms) */ 1362950cc38Schristos #define LOCKFILE "/var/spool/lock/LCK..cua%d" 137abb0f93cSkardel #define DESCRIPTION "Automated Computer Time Service" /* WRU */ 138abb0f93cSkardel #define REFID "NONE" /* default reference ID */ 139abb0f93cSkardel #define MSGCNT 20 /* max message count */ 140abb0f93cSkardel #define MAXPHONE 10 /* max number of phone numbers */ 141abb0f93cSkardel 142abb0f93cSkardel /* 1432950cc38Schristos * Calling program modes (mode) 144abb0f93cSkardel */ 1452950cc38Schristos #define MODE_BACKUP 0 /* backup mode */ 1462950cc38Schristos #define MODE_AUTO 1 /* automatic mode */ 147abb0f93cSkardel #define MODE_MANUAL 2 /* manual mode */ 148abb0f93cSkardel 149abb0f93cSkardel /* 1502950cc38Schristos * Service identifiers (message length) 151abb0f93cSkardel */ 152abb0f93cSkardel #define REFACTS "NIST" /* NIST reference ID */ 1532950cc38Schristos #define LENACTS 50 /* NIST format A */ 154abb0f93cSkardel #define REFUSNO "USNO" /* USNO reference ID */ 155abb0f93cSkardel #define LENUSNO 20 /* USNO */ 156abb0f93cSkardel #define REFPTB "PTB\0" /* PTB/NPL reference ID */ 157abb0f93cSkardel #define LENPTB 78 /* PTB/NPL format */ 158abb0f93cSkardel #define REFWWVB "WWVB" /* WWVB reference ID */ 159abb0f93cSkardel #define LENWWVB0 22 /* WWVB format 0 */ 160abb0f93cSkardel #define LENWWVB2 24 /* WWVB format 2 */ 161abb0f93cSkardel #define LF 0x0a /* ASCII LF */ 162abb0f93cSkardel 163abb0f93cSkardel /* 1642950cc38Schristos * Modem setup strings. These may have to be changed for 1652950cc38Schristos * some modems. 166abb0f93cSkardel * 167abb0f93cSkardel * AT command prefix 168abb0f93cSkardel * B1 US answer tone 169abb0f93cSkardel * &C0 disable carrier detect 170abb0f93cSkardel * &D2 hang up and return to command mode on DTR transition 171abb0f93cSkardel * E0 modem command echo disabled 1722950cc38Schristos * L1 set modem speaker volume to low level 173abb0f93cSkardel * M1 speaker enabled until carrier detect 174abb0f93cSkardel * Q0 return result codes 175abb0f93cSkardel * V1 return result codes as English words 1762950cc38Schristos * Y1 enable long-space disconnect 177abb0f93cSkardel */ 1782950cc38Schristos const char def_modem_setup[] = "ATB1&C0&D2E0L1M1Q0V1Y1"; 1792950cc38Schristos const char *modem_setup = def_modem_setup; 180abb0f93cSkardel 181abb0f93cSkardel /* 182abb0f93cSkardel * Timeouts (all in seconds) 183abb0f93cSkardel */ 184abb0f93cSkardel #define SETUP 3 /* setup timeout */ 1852950cc38Schristos #define REDIAL 30 /* redial timeout */ 186abb0f93cSkardel #define ANSWER 60 /* answer timeout */ 1872950cc38Schristos #define TIMECODE 60 /* message timeout */ 1882950cc38Schristos #define MAXCODE 20 /* max timecodes */ 189abb0f93cSkardel 190abb0f93cSkardel /* 191abb0f93cSkardel * State machine codes 192abb0f93cSkardel */ 1932950cc38Schristos typedef enum { 1942950cc38Schristos S_IDLE, /* wait for poll */ 1952950cc38Schristos S_SETUP, /* send modem setup */ 1962950cc38Schristos S_CONNECT, /* wait for answer */ 1972950cc38Schristos S_MSG /* wait for timecode */ 1982950cc38Schristos } teModemState; 199abb0f93cSkardel 200abb0f93cSkardel /* 201abb0f93cSkardel * Unit control structure 202abb0f93cSkardel */ 203abb0f93cSkardel struct actsunit { 204abb0f93cSkardel int unit; /* unit number */ 205abb0f93cSkardel int state; /* the first one was Delaware */ 206abb0f93cSkardel int timer; /* timeout counter */ 207abb0f93cSkardel int retry; /* retry index */ 208abb0f93cSkardel int msgcnt; /* count of messages received */ 209abb0f93cSkardel l_fp tstamp; /* on-time timestamp */ 2102950cc38Schristos char *bufptr; /* next incoming char stored here */ 2112950cc38Schristos char buf[BMAX]; /* bufptr roams within buf[] */ 212abb0f93cSkardel }; 213abb0f93cSkardel 214abb0f93cSkardel /* 215abb0f93cSkardel * Function prototypes 216abb0f93cSkardel */ 217abb0f93cSkardel static int acts_start (int, struct peer *); 218abb0f93cSkardel static void acts_shutdown (int, struct peer *); 219abb0f93cSkardel static void acts_receive (struct recvbuf *); 2202950cc38Schristos static void acts_message (struct peer *, const char *); 2212950cc38Schristos static void acts_timecode (struct peer *, const char *); 222abb0f93cSkardel static void acts_poll (int, struct peer *); 2232950cc38Schristos static void acts_timeout (struct peer *, teModemState); 224abb0f93cSkardel static void acts_timer (int, struct peer *); 2252950cc38Schristos static void acts_close (struct peer *); 226abb0f93cSkardel 227abb0f93cSkardel /* 228abb0f93cSkardel * Transfer vector (conditional structure name) 229abb0f93cSkardel */ 230abb0f93cSkardel struct refclock refclock_acts = { 231abb0f93cSkardel acts_start, /* start up driver */ 232abb0f93cSkardel acts_shutdown, /* shut down driver */ 233abb0f93cSkardel acts_poll, /* transmit poll message */ 234abb0f93cSkardel noentry, /* not used */ 235abb0f93cSkardel noentry, /* not used */ 236abb0f93cSkardel noentry, /* not used */ 237abb0f93cSkardel acts_timer /* housekeeping timer */ 238abb0f93cSkardel }; 239abb0f93cSkardel 240abb0f93cSkardel /* 241abb0f93cSkardel * Initialize data for processing 242abb0f93cSkardel */ 243abb0f93cSkardel static int 244abb0f93cSkardel acts_start( 245abb0f93cSkardel int unit, 246abb0f93cSkardel struct peer *peer 247abb0f93cSkardel ) 248abb0f93cSkardel { 249abb0f93cSkardel struct actsunit *up; 250abb0f93cSkardel struct refclockproc *pp; 2512950cc38Schristos const char *setup; 252abb0f93cSkardel 253abb0f93cSkardel /* 254abb0f93cSkardel * Allocate and initialize unit structure 255abb0f93cSkardel */ 2562950cc38Schristos up = emalloc_zero(sizeof(struct actsunit)); 257abb0f93cSkardel up->unit = unit; 258abb0f93cSkardel pp = peer->procptr; 2592950cc38Schristos pp->unitptr = up; 260abb0f93cSkardel pp->io.clock_recv = acts_receive; 2612950cc38Schristos pp->io.srcclock = peer; 262abb0f93cSkardel pp->io.datalen = 0; 2632950cc38Schristos pp->io.fd = -1; 264abb0f93cSkardel 265abb0f93cSkardel /* 266abb0f93cSkardel * Initialize miscellaneous variables 267abb0f93cSkardel */ 268abb0f93cSkardel peer->precision = PRECISION; 269abb0f93cSkardel pp->clockdesc = DESCRIPTION; 2702950cc38Schristos memcpy(&pp->refid, REFID, 4); 271abb0f93cSkardel peer->sstclktype = CTL_SST_TS_TELEPHONE; 2722950cc38Schristos up->bufptr = up->buf; 2732950cc38Schristos if (def_modem_setup == modem_setup) { 2742950cc38Schristos setup = get_ext_sys_var("modemsetup"); 2752950cc38Schristos if (setup != NULL) 2762950cc38Schristos modem_setup = estrdup(setup); 2772950cc38Schristos } 2782950cc38Schristos 279abb0f93cSkardel return (1); 280abb0f93cSkardel } 281abb0f93cSkardel 282abb0f93cSkardel 283abb0f93cSkardel /* 284abb0f93cSkardel * acts_shutdown - shut down the clock 285abb0f93cSkardel */ 286abb0f93cSkardel static void 287abb0f93cSkardel acts_shutdown( 288abb0f93cSkardel int unit, 289abb0f93cSkardel struct peer *peer 290abb0f93cSkardel ) 291abb0f93cSkardel { 292abb0f93cSkardel struct actsunit *up; 293abb0f93cSkardel struct refclockproc *pp; 294abb0f93cSkardel 295abb0f93cSkardel /* 296abb0f93cSkardel * Warning: do this only when a call is not in progress. 297abb0f93cSkardel */ 298abb0f93cSkardel pp = peer->procptr; 2992950cc38Schristos up = pp->unitptr; 3002950cc38Schristos acts_close(peer); 301abb0f93cSkardel free(up); 302abb0f93cSkardel } 303abb0f93cSkardel 304abb0f93cSkardel 305abb0f93cSkardel /* 306abb0f93cSkardel * acts_receive - receive data from the serial interface 307abb0f93cSkardel */ 308abb0f93cSkardel static void 309abb0f93cSkardel acts_receive( 310abb0f93cSkardel struct recvbuf *rbufp 311abb0f93cSkardel ) 312abb0f93cSkardel { 313abb0f93cSkardel struct actsunit *up; 314abb0f93cSkardel struct refclockproc *pp; 315abb0f93cSkardel struct peer *peer; 3162950cc38Schristos char tbuf[sizeof(up->buf)]; 317abb0f93cSkardel char * tptr; 3182950cc38Schristos int octets; 319abb0f93cSkardel 320abb0f93cSkardel /* 321abb0f93cSkardel * Initialize pointers and read the timecode and timestamp. Note 322abb0f93cSkardel * we are in raw mode and victim of whatever the terminal 323abb0f93cSkardel * interface kicks up; so, we have to reassemble messages from 324abb0f93cSkardel * arbitrary fragments. Capture the timecode at the beginning of 325abb0f93cSkardel * the message and at the '*' and '#' on-time characters. 326abb0f93cSkardel */ 3272950cc38Schristos peer = rbufp->recv_peer; 328abb0f93cSkardel pp = peer->procptr; 3292950cc38Schristos up = pp->unitptr; 3302950cc38Schristos octets = sizeof(up->buf) - (up->bufptr - up->buf); 3312950cc38Schristos refclock_gtraw(rbufp, tbuf, octets, &pp->lastrec); 332abb0f93cSkardel for (tptr = tbuf; *tptr != '\0'; tptr++) { 333abb0f93cSkardel if (*tptr == LF) { 3342950cc38Schristos if (up->bufptr == up->buf) { 335abb0f93cSkardel up->tstamp = pp->lastrec; 336abb0f93cSkardel continue; 337abb0f93cSkardel } else { 338abb0f93cSkardel *up->bufptr = '\0'; 3392950cc38Schristos up->bufptr = up->buf; 3402950cc38Schristos acts_message(peer, up->buf); 341abb0f93cSkardel } 342eff9ac27Schristos } else if (!iscntrl((unsigned char)*tptr)) { 343abb0f93cSkardel *up->bufptr++ = *tptr; 344abb0f93cSkardel if (*tptr == '*' || *tptr == '#') { 345abb0f93cSkardel up->tstamp = pp->lastrec; 346*eabc0478Schristos refclock_write(peer, tptr, 1, "data"); 347abb0f93cSkardel } 348abb0f93cSkardel } 349abb0f93cSkardel } 350abb0f93cSkardel } 351abb0f93cSkardel 352abb0f93cSkardel 353abb0f93cSkardel /* 354abb0f93cSkardel * acts_message - process message 355abb0f93cSkardel */ 356abb0f93cSkardel void 357abb0f93cSkardel acts_message( 3582950cc38Schristos struct peer *peer, 3592950cc38Schristos const char *msg 3602950cc38Schristos ) 3612950cc38Schristos { 3622950cc38Schristos struct actsunit *up; 3632950cc38Schristos struct refclockproc *pp; 3642950cc38Schristos char tbuf[BMAX]; 3652950cc38Schristos int dtr = TIOCM_DTR; 3662950cc38Schristos 3672950cc38Schristos DPRINTF(1, ("acts: %d %s\n", (int)strlen(msg), msg)); 3682950cc38Schristos 3692950cc38Schristos /* 3702950cc38Schristos * What to do depends on the state and the first token in the 3712950cc38Schristos * message. 3722950cc38Schristos */ 3732950cc38Schristos pp = peer->procptr; 3742950cc38Schristos up = pp->unitptr; 3752950cc38Schristos 3762950cc38Schristos /* 3772950cc38Schristos * Extract the first token in the line. 3782950cc38Schristos */ 3792950cc38Schristos strlcpy(tbuf, msg, sizeof(tbuf)); 3802950cc38Schristos strtok(tbuf, " "); 3812950cc38Schristos switch (up->state) { 3822950cc38Schristos 3832950cc38Schristos /* 3842950cc38Schristos * We are waiting for the OK response to the modem setup 3852950cc38Schristos * command. When this happens, dial the number followed. 3862950cc38Schristos * If anything other than OK is received, just ignore it 3872950cc38Schristos * and wait for timeoue. 3882950cc38Schristos */ 3892950cc38Schristos case S_SETUP: 3902950cc38Schristos if (strcmp(tbuf, "OK") != 0) { 3912950cc38Schristos /* 3922950cc38Schristos * We disable echo with MODEM_SETUP's E0 but 3932950cc38Schristos * if the modem was previously E1, we will 3942950cc38Schristos * see MODEM_SETUP echoed before the OK/ERROR. 3952950cc38Schristos * Ignore it. 3962950cc38Schristos */ 3972950cc38Schristos if (!strcmp(tbuf, modem_setup)) 3982950cc38Schristos return; 3992950cc38Schristos break; 4002950cc38Schristos } 4012950cc38Schristos 4022950cc38Schristos mprintf_event(PEVNT_CLOCK, peer, "DIAL #%d %s", 4032950cc38Schristos up->retry, sys_phone[up->retry]); 4042950cc38Schristos if (ioctl(pp->io.fd, TIOCMBIS, &dtr) < 0) 4052950cc38Schristos msyslog(LOG_ERR, "acts: ioctl(TIOCMBIS) failed: %m"); 406*eabc0478Schristos refclock_write(peer, sys_phone[up->retry], 407*eabc0478Schristos strlen(sys_phone[up->retry]), 408*eabc0478Schristos "DIAL"); 409*eabc0478Schristos refclock_write(peer, "\r", 1, "CR"); 4102950cc38Schristos up->retry++; 4112950cc38Schristos up->state = S_CONNECT; 4122950cc38Schristos up->timer = ANSWER; 4132950cc38Schristos return; 4142950cc38Schristos 4152950cc38Schristos /* 4162950cc38Schristos * We are waiting for the CONNECT response to the dial 4172950cc38Schristos * command. When this happens, listen for timecodes. If 4182950cc38Schristos * somthing other than CONNECT is received, like BUSY 4192950cc38Schristos * or NO CARRIER, abort the call. 4202950cc38Schristos */ 4212950cc38Schristos case S_CONNECT: 4222950cc38Schristos if (strcmp(tbuf, "CONNECT") != 0) 4232950cc38Schristos break; 4242950cc38Schristos 4252950cc38Schristos report_event(PEVNT_CLOCK, peer, msg); 4262950cc38Schristos up->state = S_MSG; 4272950cc38Schristos up->timer = TIMECODE; 4282950cc38Schristos return; 4292950cc38Schristos 4302950cc38Schristos /* 4312950cc38Schristos * We are waiting for a timecode response. Pass it to 4322950cc38Schristos * the parser. If NO CARRIER is received, save the 4332950cc38Schristos * messages and abort the call. 4342950cc38Schristos */ 4352950cc38Schristos case S_MSG: 4362950cc38Schristos if (strcmp(tbuf, "NO") == 0) 4372950cc38Schristos report_event(PEVNT_CLOCK, peer, msg); 4382950cc38Schristos if (up->msgcnt < MAXCODE) 4392950cc38Schristos acts_timecode(peer, msg); 4402950cc38Schristos else 4412950cc38Schristos acts_timeout(peer, S_MSG); 4422950cc38Schristos return; 4432950cc38Schristos } 4442950cc38Schristos 4452950cc38Schristos /* 4462950cc38Schristos * Other response. Tell us about it. 4472950cc38Schristos */ 4482950cc38Schristos report_event(PEVNT_CLOCK, peer, msg); 4492950cc38Schristos acts_close(peer); 4502950cc38Schristos } 4512950cc38Schristos 4522950cc38Schristos 4532950cc38Schristos /* 4542950cc38Schristos * acts_timeout - called on timeout 4552950cc38Schristos */ 4562950cc38Schristos static void 4572950cc38Schristos acts_timeout( 4582950cc38Schristos struct peer *peer, 4592950cc38Schristos teModemState dstate 4602950cc38Schristos ) 4612950cc38Schristos { 4622950cc38Schristos struct actsunit *up; 4632950cc38Schristos struct refclockproc *pp; 4642950cc38Schristos int fd; 4652950cc38Schristos char device[20]; 4662950cc38Schristos char lockfile[128], pidbuf[8]; 4672950cc38Schristos 4682950cc38Schristos /* 4692950cc38Schristos * The state machine is driven by messages from the modem, 4702950cc38Schristos * when first started and at timeout. 4712950cc38Schristos */ 4722950cc38Schristos pp = peer->procptr; 4732950cc38Schristos up = pp->unitptr; 4742950cc38Schristos switch (dstate) { 4752950cc38Schristos 4762950cc38Schristos /* 4772950cc38Schristos * System poll event. Lock the modem port, open the device 4782950cc38Schristos * and send the setup command. 4792950cc38Schristos */ 4802950cc38Schristos case S_IDLE: 4812950cc38Schristos if (-1 != pp->io.fd) 4822950cc38Schristos return; /* port is already open */ 4832950cc38Schristos 4842950cc38Schristos /* 4852950cc38Schristos * Lock the modem port. If busy, retry later. Note: if 4862950cc38Schristos * something fails between here and the close, the lock 4872950cc38Schristos * file may not be removed. 4882950cc38Schristos */ 4892950cc38Schristos if (pp->sloppyclockflag & CLK_FLAG2) { 4902950cc38Schristos snprintf(lockfile, sizeof(lockfile), LOCKFILE, 4912950cc38Schristos up->unit); 4922950cc38Schristos fd = open(lockfile, O_WRONLY | O_CREAT | O_EXCL, 4932950cc38Schristos 0644); 4942950cc38Schristos if (fd < 0) { 4952950cc38Schristos report_event(PEVNT_CLOCK, peer, "acts: port busy"); 4962950cc38Schristos return; 4972950cc38Schristos } 4982950cc38Schristos snprintf(pidbuf, sizeof(pidbuf), "%d\n", 4992950cc38Schristos (u_int)getpid()); 5002950cc38Schristos if (write(fd, pidbuf, strlen(pidbuf)) < 0) 5012950cc38Schristos msyslog(LOG_ERR, "acts: write lock fails %m"); 5022950cc38Schristos close(fd); 5032950cc38Schristos } 5042950cc38Schristos 5052950cc38Schristos /* 5062950cc38Schristos * Open the device in raw mode and link the I/O. 5072950cc38Schristos */ 5082950cc38Schristos snprintf(device, sizeof(device), DEVICE, 5092950cc38Schristos up->unit); 510*eabc0478Schristos fd = refclock_open(&peer->srcadr, device, SPEED232, LDISC_ACTS | 5112950cc38Schristos LDISC_RAW | LDISC_REMOTE); 512f69b3e79Schristos if (fd < 0) { 5132950cc38Schristos msyslog(LOG_ERR, "acts: open fails %m"); 5142950cc38Schristos return; 5152950cc38Schristos } 5162950cc38Schristos pp->io.fd = fd; 5172950cc38Schristos if (!io_addclock(&pp->io)) { 5182950cc38Schristos msyslog(LOG_ERR, "acts: addclock fails"); 5192950cc38Schristos close(fd); 5202950cc38Schristos pp->io.fd = -1; 5212950cc38Schristos return; 5222950cc38Schristos } 5232950cc38Schristos up->msgcnt = 0; 5242950cc38Schristos up->bufptr = up->buf; 5252950cc38Schristos 5262950cc38Schristos /* 5272950cc38Schristos * If the port is directly connected to the device, skip 5282950cc38Schristos * the modem business and send 'T' for Spectrabum. 5292950cc38Schristos */ 5302950cc38Schristos if (sys_phone[up->retry] == NULL) { 531*eabc0478Schristos refclock_write(peer, "T", 1, "T"); 5322950cc38Schristos up->state = S_MSG; 5332950cc38Schristos up->timer = TIMECODE; 5342950cc38Schristos return; 5352950cc38Schristos } 5362950cc38Schristos 5372950cc38Schristos /* 5382950cc38Schristos * Initialize the modem. This works with Hayes- 5392950cc38Schristos * compatible modems. 5402950cc38Schristos */ 5412950cc38Schristos mprintf_event(PEVNT_CLOCK, peer, "SETUP %s", 5422950cc38Schristos modem_setup); 543*eabc0478Schristos refclock_write(peer, modem_setup, strlen(modem_setup), 544*eabc0478Schristos "SETUP"); 545*eabc0478Schristos refclock_write(peer, "\r", 1, "CR"); 5462950cc38Schristos up->state = S_SETUP; 5472950cc38Schristos up->timer = SETUP; 5482950cc38Schristos return; 5492950cc38Schristos 5502950cc38Schristos /* 5512950cc38Schristos * In SETUP state the modem did not respond OK to setup string. 5522950cc38Schristos */ 5532950cc38Schristos case S_SETUP: 5542950cc38Schristos report_event(PEVNT_CLOCK, peer, "no modem"); 5552950cc38Schristos break; 5562950cc38Schristos 5572950cc38Schristos /* 5582950cc38Schristos * In CONNECT state the call did not complete. Abort the call. 5592950cc38Schristos */ 5602950cc38Schristos case S_CONNECT: 5612950cc38Schristos report_event(PEVNT_CLOCK, peer, "no answer"); 5622950cc38Schristos break; 5632950cc38Schristos 5642950cc38Schristos /* 5652950cc38Schristos * In MSG states no further timecodes are expected. If any 5662950cc38Schristos * timecodes have arrived, update the clock. In any case, 5672950cc38Schristos * terminate the call. 5682950cc38Schristos */ 5692950cc38Schristos case S_MSG: 5702950cc38Schristos if (up->msgcnt == 0) { 5712950cc38Schristos report_event(PEVNT_CLOCK, peer, "no timecodes"); 5722950cc38Schristos } else { 5732950cc38Schristos pp->lastref = pp->lastrec; 5742950cc38Schristos record_clock_stats(&peer->srcadr, pp->a_lastcode); 5752950cc38Schristos refclock_receive(peer); 5762950cc38Schristos } 5772950cc38Schristos break; 5782950cc38Schristos } 5792950cc38Schristos acts_close(peer); 5802950cc38Schristos } 5812950cc38Schristos 5822950cc38Schristos 5832950cc38Schristos /* 5842950cc38Schristos * acts_close - close and prepare for next call. 5852950cc38Schristos * 5862950cc38Schristos * In ClOSE state no further protocol actions are required 5872950cc38Schristos * other than to close and release the device and prepare to 5882950cc38Schristos * dial the next number if necessary. 5892950cc38Schristos */ 5902950cc38Schristos void 5912950cc38Schristos acts_close( 592abb0f93cSkardel struct peer *peer 593abb0f93cSkardel ) 594abb0f93cSkardel { 595abb0f93cSkardel struct actsunit *up; 596abb0f93cSkardel struct refclockproc *pp; 5972950cc38Schristos char lockfile[128]; 5982950cc38Schristos int dtr; 599abb0f93cSkardel 600abb0f93cSkardel pp = peer->procptr; 6012950cc38Schristos up = pp->unitptr; 6022950cc38Schristos if (pp->io.fd != -1) { 6032950cc38Schristos report_event(PEVNT_CLOCK, peer, "close"); 6042950cc38Schristos dtr = TIOCM_DTR; 6052950cc38Schristos if (ioctl(pp->io.fd, TIOCMBIC, &dtr) < 0) 6062950cc38Schristos msyslog(LOG_ERR, "acts: ioctl(TIOCMBIC) failed: %m"); 6072950cc38Schristos io_closeclock(&pp->io); 6082950cc38Schristos pp->io.fd = -1; 6092950cc38Schristos } 6102950cc38Schristos if (pp->sloppyclockflag & CLK_FLAG2) { 6112950cc38Schristos snprintf(lockfile, sizeof(lockfile), 6122950cc38Schristos LOCKFILE, up->unit); 6132950cc38Schristos unlink(lockfile); 6142950cc38Schristos } 6152950cc38Schristos if (up->msgcnt == 0 && up->retry > 0) { 6162950cc38Schristos if (sys_phone[up->retry] != NULL) { 6172950cc38Schristos up->state = S_IDLE; 6182950cc38Schristos up->timer = REDIAL; 619abb0f93cSkardel return; 620abb0f93cSkardel } 621abb0f93cSkardel } 6222950cc38Schristos up->state = S_IDLE; 6232950cc38Schristos up->timer = 0; 624abb0f93cSkardel } 6252950cc38Schristos 6262950cc38Schristos 6272950cc38Schristos /* 6282950cc38Schristos * acts_poll - called by the transmit routine 6292950cc38Schristos */ 6302950cc38Schristos static void 6312950cc38Schristos acts_poll( 6322950cc38Schristos int unit, 6332950cc38Schristos struct peer *peer 6342950cc38Schristos ) 6352950cc38Schristos { 6362950cc38Schristos struct actsunit *up; 6372950cc38Schristos struct refclockproc *pp; 6382950cc38Schristos 6392950cc38Schristos /* 6402950cc38Schristos * This routine is called at every system poll. All it does is 6412950cc38Schristos * set flag1 under certain conditions. The real work is done by 6422950cc38Schristos * the timeout routine and state machine. 6432950cc38Schristos */ 6442950cc38Schristos pp = peer->procptr; 6452950cc38Schristos up = pp->unitptr; 6462950cc38Schristos switch (peer->ttl) { 6472950cc38Schristos 6482950cc38Schristos /* 6492950cc38Schristos * In manual mode the calling program is activated by the ntpdc 6502950cc38Schristos * program using the enable flag (fudge flag1), either manually 6512950cc38Schristos * or by a cron job. 6522950cc38Schristos */ 6532950cc38Schristos case MODE_MANUAL: 654abb0f93cSkardel return; 655abb0f93cSkardel 656abb0f93cSkardel /* 6572950cc38Schristos * In automatic mode the calling program runs continuously at 6582950cc38Schristos * intervals determined by the poll event or specified timeout. 659abb0f93cSkardel */ 6602950cc38Schristos case MODE_AUTO: 661abb0f93cSkardel break; 6622950cc38Schristos 6632950cc38Schristos /* 6642950cc38Schristos * In backup mode the calling program runs continuously as long 6652950cc38Schristos * as either no peers are available or this peer is selected. 6662950cc38Schristos */ 6672950cc38Schristos case MODE_BACKUP: 6682950cc38Schristos if (!(sys_peer == NULL || sys_peer == peer)) 6692950cc38Schristos return; 6702950cc38Schristos 6712950cc38Schristos break; 6722950cc38Schristos } 6732950cc38Schristos pp->polls++; 6742950cc38Schristos if (S_IDLE == up->state) { 6752950cc38Schristos up->retry = 0; 6762950cc38Schristos acts_timeout(peer, S_IDLE); 6772950cc38Schristos } 6782950cc38Schristos } 6792950cc38Schristos 6802950cc38Schristos 6812950cc38Schristos /* 6822950cc38Schristos * acts_timer - called at one-second intervals 6832950cc38Schristos */ 6842950cc38Schristos static void 6852950cc38Schristos acts_timer( 6862950cc38Schristos int unit, 6872950cc38Schristos struct peer *peer 6882950cc38Schristos ) 6892950cc38Schristos { 6902950cc38Schristos struct actsunit *up; 6912950cc38Schristos struct refclockproc *pp; 6922950cc38Schristos 6932950cc38Schristos /* 6942950cc38Schristos * This routine implments a timeout which runs for a programmed 6952950cc38Schristos * interval. The counter is initialized by the state machine and 6962950cc38Schristos * counts down to zero. Upon reaching zero, the state machine is 6972950cc38Schristos * called. If flag1 is set while timer is zero, force a call. 6982950cc38Schristos */ 6992950cc38Schristos pp = peer->procptr; 7002950cc38Schristos up = pp->unitptr; 7012950cc38Schristos if (up->timer == 0) { 7022950cc38Schristos if (pp->sloppyclockflag & CLK_FLAG1) { 7032950cc38Schristos pp->sloppyclockflag &= ~CLK_FLAG1; 7042950cc38Schristos acts_timeout(peer, S_IDLE); 7052950cc38Schristos } 7062950cc38Schristos } else { 7072950cc38Schristos up->timer--; 7082950cc38Schristos if (up->timer == 0) 7092950cc38Schristos acts_timeout(peer, up->state); 710abb0f93cSkardel } 711abb0f93cSkardel } 712abb0f93cSkardel 713abb0f93cSkardel /* 714abb0f93cSkardel * acts_timecode - identify the service and parse the timecode message 715abb0f93cSkardel */ 716abb0f93cSkardel void 717abb0f93cSkardel acts_timecode( 718abb0f93cSkardel struct peer * peer, /* peer structure pointer */ 7192950cc38Schristos const char * str /* timecode string */ 720abb0f93cSkardel ) 721abb0f93cSkardel { 722abb0f93cSkardel struct actsunit *up; 723abb0f93cSkardel struct refclockproc *pp; 724abb0f93cSkardel int day; /* day of the month */ 725abb0f93cSkardel int month; /* month of the year */ 726abb0f93cSkardel u_long mjd; /* Modified Julian Day */ 727abb0f93cSkardel double dut1; /* DUT adjustment */ 728abb0f93cSkardel 729abb0f93cSkardel u_int dst; /* ACTS daylight/standard time */ 730abb0f93cSkardel u_int leap; /* ACTS leap indicator */ 731abb0f93cSkardel double msADV; /* ACTS transmit advance (ms) */ 732abb0f93cSkardel char utc[10]; /* ACTS timescale */ 733abb0f93cSkardel char flag; /* ACTS on-time character (* or #) */ 734abb0f93cSkardel 735abb0f93cSkardel char synchar; /* WWVB synchronized indicator */ 736abb0f93cSkardel char qualchar; /* WWVB quality indicator */ 737abb0f93cSkardel char leapchar; /* WWVB leap indicator */ 738abb0f93cSkardel char dstchar; /* WWVB daylight/savings indicator */ 739abb0f93cSkardel int tz; /* WWVB timezone */ 740abb0f93cSkardel 741e19314b7Schristos int leapmonth; /* PTB/NPL month of leap */ 742abb0f93cSkardel char leapdir; /* PTB/NPL leap direction */ 743abb0f93cSkardel 744abb0f93cSkardel /* 745abb0f93cSkardel * The parser selects the modem format based on the message 746abb0f93cSkardel * length. Since the data are checked carefully, occasional 747abb0f93cSkardel * errors due noise are forgivable. 748abb0f93cSkardel */ 749abb0f93cSkardel pp = peer->procptr; 7502950cc38Schristos up = pp->unitptr; 751abb0f93cSkardel pp->nsec = 0; 752abb0f93cSkardel switch (strlen(str)) { 753abb0f93cSkardel 754abb0f93cSkardel /* 755abb0f93cSkardel * For USNO format on-time character '*', which is on a line by 756abb0f93cSkardel * itself. Be sure a timecode has been received. 757abb0f93cSkardel */ 758abb0f93cSkardel case 1: 759abb0f93cSkardel if (*str == '*' && up->msgcnt > 0) 760abb0f93cSkardel break; 761abb0f93cSkardel 762abb0f93cSkardel return; 763abb0f93cSkardel 764abb0f93cSkardel /* 7652950cc38Schristos * ACTS format A: "jjjjj yy-mm-dd hh:mm:ss ds l uuu aaaaa 7662950cc38Schristos * UTC(NIST) *". 767abb0f93cSkardel */ 768abb0f93cSkardel case LENACTS: 769abb0f93cSkardel if (sscanf(str, 770abb0f93cSkardel "%5ld %2d-%2d-%2d %2d:%2d:%2d %2d %1d %3lf %5lf %9s %c", 771abb0f93cSkardel &mjd, &pp->year, &month, &day, &pp->hour, 772abb0f93cSkardel &pp->minute, &pp->second, &dst, &leap, &dut1, 773abb0f93cSkardel &msADV, utc, &flag) != 13) { 774abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 775abb0f93cSkardel return; 776abb0f93cSkardel } 777abb0f93cSkardel pp->day = ymd2yd(pp->year, month, day); 778abb0f93cSkardel pp->leap = LEAP_NOWARNING; 779abb0f93cSkardel if (leap == 1) 780abb0f93cSkardel pp->leap = LEAP_ADDSECOND; 7812950cc38Schristos else if (leap == 2) 782abb0f93cSkardel pp->leap = LEAP_DELSECOND; 783abb0f93cSkardel memcpy(&pp->refid, REFACTS, 4); 784abb0f93cSkardel up->msgcnt++; 7852950cc38Schristos if (flag != '#' && up->msgcnt < 10) 7862950cc38Schristos return; 7872950cc38Schristos 788abb0f93cSkardel break; 789abb0f93cSkardel 790abb0f93cSkardel /* 791abb0f93cSkardel * USNO format: "jjjjj nnn hhmmss UTC" 792abb0f93cSkardel */ 793abb0f93cSkardel case LENUSNO: 794abb0f93cSkardel if (sscanf(str, "%5ld %3d %2d%2d%2d %3s", 795abb0f93cSkardel &mjd, &pp->day, &pp->hour, &pp->minute, 796abb0f93cSkardel &pp->second, utc) != 6) { 797abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 798abb0f93cSkardel return; 799abb0f93cSkardel } 800abb0f93cSkardel 801abb0f93cSkardel /* 802abb0f93cSkardel * Wait for the on-time character, which follows in a 803abb0f93cSkardel * separate message. There is no provision for leap 804abb0f93cSkardel * warning. 805abb0f93cSkardel */ 806abb0f93cSkardel pp->leap = LEAP_NOWARNING; 807abb0f93cSkardel memcpy(&pp->refid, REFUSNO, 4); 808abb0f93cSkardel up->msgcnt++; 8092950cc38Schristos break; 810abb0f93cSkardel 811abb0f93cSkardel /* 812abb0f93cSkardel * PTB/NPL format: "yyyy-mm-dd hh:mm:ss MEZ" 813abb0f93cSkardel */ 814abb0f93cSkardel case LENPTB: 815abb0f93cSkardel if (sscanf(str, 816abb0f93cSkardel "%*4d-%*2d-%*2d %*2d:%*2d:%2d %*5c%*12c%4d%2d%2d%2d%2d%5ld%2lf%c%2d%3lf%*15c%c", 817abb0f93cSkardel &pp->second, &pp->year, &month, &day, &pp->hour, 818abb0f93cSkardel &pp->minute, &mjd, &dut1, &leapdir, &leapmonth, 819abb0f93cSkardel &msADV, &flag) != 12) { 820abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 821abb0f93cSkardel return; 822abb0f93cSkardel } 823abb0f93cSkardel pp->leap = LEAP_NOWARNING; 824abb0f93cSkardel if (leapmonth == month) { 825abb0f93cSkardel if (leapdir == '+') 826abb0f93cSkardel pp->leap = LEAP_ADDSECOND; 827abb0f93cSkardel else if (leapdir == '-') 828abb0f93cSkardel pp->leap = LEAP_DELSECOND; 829abb0f93cSkardel } 830abb0f93cSkardel pp->day = ymd2yd(pp->year, month, day); 831abb0f93cSkardel memcpy(&pp->refid, REFPTB, 4); 832abb0f93cSkardel up->msgcnt++; 833abb0f93cSkardel break; 834abb0f93cSkardel 835abb0f93cSkardel 836abb0f93cSkardel /* 837abb0f93cSkardel * WWVB format 0: "I ddd hh:mm:ss DTZ=nn" 838abb0f93cSkardel */ 839abb0f93cSkardel case LENWWVB0: 840abb0f93cSkardel if (sscanf(str, "%c %3d %2d:%2d:%2d %cTZ=%2d", 841abb0f93cSkardel &synchar, &pp->day, &pp->hour, &pp->minute, 842abb0f93cSkardel &pp->second, &dstchar, &tz) != 7) { 843abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 844abb0f93cSkardel return; 845abb0f93cSkardel } 846abb0f93cSkardel pp->leap = LEAP_NOWARNING; 847abb0f93cSkardel if (synchar != ' ') 848abb0f93cSkardel pp->leap = LEAP_NOTINSYNC; 849abb0f93cSkardel memcpy(&pp->refid, REFWWVB, 4); 850abb0f93cSkardel up->msgcnt++; 851abb0f93cSkardel break; 852abb0f93cSkardel 853abb0f93cSkardel /* 854abb0f93cSkardel * WWVB format 2: "IQyy ddd hh:mm:ss.mmm LD" 855abb0f93cSkardel */ 856abb0f93cSkardel case LENWWVB2: 857abb0f93cSkardel if (sscanf(str, "%c%c%2d %3d %2d:%2d:%2d.%3ld%c%c%c", 858abb0f93cSkardel &synchar, &qualchar, &pp->year, &pp->day, 859abb0f93cSkardel &pp->hour, &pp->minute, &pp->second, &pp->nsec, 860abb0f93cSkardel &dstchar, &leapchar, &dstchar) != 11) { 861abb0f93cSkardel refclock_report(peer, CEVNT_BADREPLY); 862abb0f93cSkardel return; 863abb0f93cSkardel } 864abb0f93cSkardel pp->nsec *= 1000000; 865abb0f93cSkardel pp->leap = LEAP_NOWARNING; 866abb0f93cSkardel if (synchar != ' ') 867abb0f93cSkardel pp->leap = LEAP_NOTINSYNC; 868abb0f93cSkardel else if (leapchar == 'L') 869abb0f93cSkardel pp->leap = LEAP_ADDSECOND; 870abb0f93cSkardel memcpy(&pp->refid, REFWWVB, 4); 871abb0f93cSkardel up->msgcnt++; 872abb0f93cSkardel break; 873abb0f93cSkardel 874abb0f93cSkardel /* 875abb0f93cSkardel * None of the above. Just forget about it and wait for the next 876abb0f93cSkardel * message or timeout. 877abb0f93cSkardel */ 878abb0f93cSkardel default: 879abb0f93cSkardel return; 880abb0f93cSkardel } 881abb0f93cSkardel 882abb0f93cSkardel /* 883abb0f93cSkardel * We have a valid timecode. The fudge time1 value is added to 884abb0f93cSkardel * each sample by the main line routines. Note that in current 885abb0f93cSkardel * telephone networks the propatation time can be different for 886abb0f93cSkardel * each call and can reach 200 ms for some calls. 887abb0f93cSkardel */ 888abb0f93cSkardel peer->refid = pp->refid; 889abb0f93cSkardel pp->lastrec = up->tstamp; 8902950cc38Schristos if (up->msgcnt == 0) 8912950cc38Schristos return; 8922950cc38Schristos 8932950cc38Schristos strlcpy(pp->a_lastcode, str, sizeof(pp->a_lastcode)); 8942950cc38Schristos pp->lencode = strlen(pp->a_lastcode); 895abb0f93cSkardel if (!refclock_process(pp)) { 896abb0f93cSkardel refclock_report(peer, CEVNT_BADTIME); 897abb0f93cSkardel return; 898abb0f93cSkardel } 899abb0f93cSkardel pp->lastref = pp->lastrec; 900abb0f93cSkardel } 901abb0f93cSkardel #else 902*eabc0478Schristos NONEMPTY_TRANSLATION_UNIT 903abb0f93cSkardel #endif /* REFCLOCK */ 904