diff options
Diffstat (limited to 'contrib/ntp/ntpd/refclock_true.c')
-rw-r--r-- | contrib/ntp/ntpd/refclock_true.c | 852 |
1 files changed, 852 insertions, 0 deletions
diff --git a/contrib/ntp/ntpd/refclock_true.c b/contrib/ntp/ntpd/refclock_true.c new file mode 100644 index 0000000..b841f72 --- /dev/null +++ b/contrib/ntp/ntpd/refclock_true.c @@ -0,0 +1,852 @@ +/* + * refclock_true - clock driver for the Kinemetrics Truetime receivers + * Receiver Version 3.0C - tested plain, with CLKLDISC + * Developement work being done: + * - Properly handle varying satellite positions (more acurately) + * - Integrate GPSTM and/or OMEGA and/or TRAK and/or ??? drivers + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(REFCLOCK) && defined(CLOCK_TRUETIME) + +#include <stdio.h> +#include <ctype.h> +#include <sys/time.h> + +#include "ntpd.h" +#include "ntp_io.h" +#include "ntp_refclock.h" +#include "ntp_unixtime.h" +#include "ntp_stdlib.h" + +/* This should be an atom clock but those are very hard to build. + * + * The PCL720 from P C Labs has an Intel 8253 lookalike, as well as a bunch + * of TTL input and output pins, all brought out to the back panel. If you + * wire a PPS signal (such as the TTL PPS coming out of a GOES or other + * Kinemetrics/Truetime clock) to the 8253's GATE0, and then also wire the + * 8253's OUT0 to the PCL720's INPUT3.BIT0, then we can read CTR0 to get the + * number of uSecs since the last PPS upward swing, mediated by reading OUT0 + * to find out if the counter has wrapped around (this happens if more than + * 65535us (65ms) elapses between the PPS event and our being called.) + */ +#ifdef CLOCK_PPS720 +# undef min /* XXX */ +# undef max /* XXX */ +# include <machine/inline.h> +# include <sys/pcl720.h> +# include <sys/i8253.h> +# define PCL720_IOB 0x2a0 /* XXX */ +# define PCL720_CTR 0 /* XXX */ +#endif + +/* + * Support for Kinemetrics Truetime Receivers + * GOES + * GPS/TM-TMD + * XL-DC (a 151-602-210, reported by the driver as a GPS/TM-TMD) + * GPS-800 TCU (an 805-957 with the RS232 Talker/Listener module) + * OM-DC: getting stale ("OMEGA") + * + * Most of this code is originally from refclock_wwvb.c with thanks. + * It has been so mangled that wwvb is not a recognizable ancestor. + * + * Timcode format: ADDD:HH:MM:SSQCL + * A - control A (this is stripped before we see it) + * Q - Quality indication (see below) + * C - Carriage return + * L - Line feed + * + * Quality codes indicate possible error of + * 468-DC GOES Receiver: + * GPS-TM/TMD Receiver: + * ? +/- 500 milliseconds # +/- 50 milliseconds + * * +/- 5 milliseconds . +/- 1 millisecond + * space less than 1 millisecond + * OM-DC OMEGA Receiver: + * > >+- 5 seconds + * ? >+/- 500 milliseconds # >+/- 50 milliseconds + * * >+/- 5 milliseconds . >+/- 1 millisecond + * A-H less than 1 millisecond. Character indicates which station + * is being received as follows: + * A = Norway, B = Liberia, C = Hawaii, D = North Dakota, + * E = La Reunion, F = Argentina, G = Australia, H = Japan. + * + * The carriage return start bit begins on 0 seconds and extends to 1 bit time. + * + * Notes on 468-DC and OMEGA receiver: + * + * Send the clock a 'R' or 'C' and once per second a timestamp will + * appear. Send a 'P' to get the satellite position once (GOES only.) + * + * Notes on the 468-DC receiver: + * + * Since the old east/west satellite locations are only historical, you can't + * set your clock propagation delay settings correctly and still use + * automatic mode. The manual says to use a compromise when setting the + * switches. This results in significant errors. The solution; use fudge + * time1 and time2 to incorporate corrections. If your clock is set for + * 50 and it should be 58 for using the west and 46 for using the east, + * use the line + * + * fudge 127.127.5.0 time1 +0.008 time2 -0.004 + * + * This corrects the 4 milliseconds advance and 8 milliseconds retard + * needed. The software will ask the clock which satellite it sees. + * + * Ntp.conf parameters: + * time1 - offset applied to samples when reading WEST satellite (default = 0) + * time2 - offset applied to samples when reading EAST satellite (default = 0) + * val1 - stratum to assign to this clock (default = 0) + * val2 - refid assigned to this clock (default = "TRUE", see below) + * flag1 - will silence the clock side of ntpd, just reading the clock + * without trying to write to it. (default = 0) + * flag2 - generate a debug file /tmp/true%d. + * flag3 - enable ppsclock streams module + * flag4 - use the PCL-720 (BSD/OS only) + */ + +/* + * Definitions + */ +#define DEVICE "/dev/true%d" +#define SPEED232 B9600 /* 9600 baud */ + +/* + * Radio interface parameters + */ +#define PRECISION (-10) /* precision assumed (about 1 ms) */ +#define REFID "TRUE" /* reference id */ +#define DESCRIPTION "Kinemetrics/TrueTime Receiver" + +/* + * Tags which station (satellite) we see + */ +#define GOES_WEST 0 /* Default to WEST satellite and apply time1 */ +#define GOES_EAST 1 /* until you discover otherwise */ + +/* + * used by the state machine + */ +enum true_event {e_Init, e_Huh, e_F18, e_F50, e_F51, e_Satellite, + e_Poll, e_Location, e_TS, e_Max}; +const char *events[] = {"Init", "Huh", "F18", "F50", "F51", "Satellite", + "Poll", "Location", "TS"}; +#define eventStr(x) (((int)x<(int)e_Max) ? events[(int)x] : "?") + +enum true_state {s_Base, s_InqTM, s_InqTCU, s_InqOmega, s_InqGOES, + s_Init, s_F18, s_F50, s_Start, s_Auto, s_Max}; +const char *states[] = {"Base", "InqTM", "InqTCU", "InqOmega", "InqGOES", + "Init", "F18", "F50", "Start", "Auto"}; +#define stateStr(x) (((int)x<(int)s_Max) ? states[(int)x] : "?") + +enum true_type {t_unknown, t_goes, t_tm, t_tcu, t_omega, t_Max}; +const char *types[] = {"unknown", "goes", "tm", "tcu", "omega"}; +#define typeStr(x) (((int)x<(int)t_Max) ? types[(int)x] : "?") + +/* + * unit control structure + */ +struct true_unit { + unsigned int pollcnt; /* poll message counter */ + unsigned int station; /* which station we are on */ + unsigned int polled; /* Hand in a time sample? */ + enum true_state state; /* state machine */ + enum true_type type; /* what kind of clock is it? */ + int unit; /* save an extra copy of this */ + FILE *debug; /* debug logging file */ +#ifdef CLOCK_PPS720 + int pcl720init; /* init flag for PCL 720 */ +#endif +}; + +/* + * Function prototypes + */ +static int true_start P((int, struct peer *)); +static void true_shutdown P((int, struct peer *)); +static void true_receive P((struct recvbuf *)); +static void true_poll P((int, struct peer *)); +static void true_send P((struct peer *, const char *)); +static void true_doevent P((struct peer *, enum true_event)); + +#ifdef CLOCK_PPS720 +static u_long true_sample720 P((void)); +#endif + +/* + * Transfer vector + */ +struct refclock refclock_true = { + true_start, /* start up driver */ + true_shutdown, /* shut down driver */ + true_poll, /* transmit poll message */ + noentry, /* not used (old true_control) */ + noentry, /* initialize driver (not used) */ + noentry, /* not used (old true_buginfo) */ + NOFLAGS /* not used */ +}; + + +#if !defined(__STDC__) +# define true_debug (void) +#else +static void +true_debug(struct peer *peer, const char *fmt, ...) +{ + va_list ap; + int want_debugging, now_debugging; + struct refclockproc *pp; + struct true_unit *up; + + va_start(ap, fmt); + pp = peer->procptr; + up = (struct true_unit *)pp->unitptr; + + want_debugging = (pp->sloppyclockflag & CLK_FLAG2) != 0; + now_debugging = (up->debug != NULL); + if (want_debugging != now_debugging) + { + if (want_debugging) { + char filename[20]; + + sprintf(filename, "/tmp/true%d.debug", up->unit); + up->debug = fopen(filename, "w"); + if (up->debug) { +#ifdef HAVE_SETVBUF + static char buf[BUFSIZ]; + setvbuf(up->debug, buf, _IOLBF, BUFSIZ); +#else + setlinebuf(up->debug); +#endif + } + } else { + fclose(up->debug); + up->debug = NULL; + } + } + + if (up->debug) { + fprintf(up->debug, "true%d: ", up->unit); + vfprintf(up->debug, fmt, ap); + } +} +#endif /*STDC*/ + +/* + * true_start - open the devices and initialize data for processing + */ +static int +true_start( + int unit, + struct peer *peer + ) +{ + register struct true_unit *up; + struct refclockproc *pp; + char device[20]; + int fd; + + /* + * Open serial port + */ + (void)sprintf(device, DEVICE, unit); + if (!(fd = refclock_open(device, SPEED232, LDISC_CLK))) + return (0); + + /* + * Allocate and initialize unit structure + */ + if (!(up = (struct true_unit *) + emalloc(sizeof(struct true_unit)))) { + (void) close(fd); + return (0); + } + memset((char *)up, 0, sizeof(struct true_unit)); + pp = peer->procptr; + pp->io.clock_recv = true_receive; + pp->io.srcclock = (caddr_t)peer; + pp->io.datalen = 0; + pp->io.fd = fd; + if (!io_addclock(&pp->io)) { + (void) close(fd); + free(up); + return (0); + } + pp->unitptr = (caddr_t)up; + + /* + * Initialize miscellaneous variables + */ + peer->precision = PRECISION; + pp->clockdesc = DESCRIPTION; + memcpy((char *)&pp->refid, REFID, 4); + up->pollcnt = 2; + up->type = t_unknown; + up->state = s_Base; + true_doevent(peer, e_Init); + return (1); +} + +/* + * true_shutdown - shut down the clock + */ +static void +true_shutdown( + int unit, + struct peer *peer + ) +{ + register struct true_unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct true_unit *)pp->unitptr; + io_closeclock(&pp->io); + free(up); +} + + +/* + * true_receive - receive data from the serial interface on a clock + */ +static void +true_receive( + struct recvbuf *rbufp + ) +{ + register struct true_unit *up; + struct refclockproc *pp; + struct peer *peer; + u_short new_station; + char synced; + int i; + int lat, lon, off; /* GOES Satellite position */ + + /* + * Get the clock this applies to and pointers to the data. + */ + peer = (struct peer *)rbufp->recv_srcclock; + pp = peer->procptr; + up = (struct true_unit *)pp->unitptr; + + /* + * Read clock output. Automatically handles STREAMS, CLKLDISC. + */ + pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &pp->lastrec); + + /* + * There is a case where <cr><lf> generates 2 timestamps. + */ + if (pp->lencode == 0) + return; + pp->a_lastcode[pp->lencode] = '\0'; + true_debug(peer, "receive(%s) [%d]\n", pp->a_lastcode, pp->lencode); + + up->pollcnt = 2; + record_clock_stats(&peer->srcadr, pp->a_lastcode); + + /* + * We get down to business, check the timecode format and decode + * its contents. This code decodes a multitude of different + * clock messages. Timecodes are processed if needed. All replies + * will be run through the state machine to tweak driver options + * and program the clock. + */ + + /* + * Clock misunderstood our last command? + */ + if (pp->a_lastcode[0] == '?') { + true_doevent(peer, e_Huh); + return; + } + + /* + * Timecode: "nnnnn+nnn-nnn" + * (from GOES clock when asked about satellite position) + */ + if ((pp->a_lastcode[5] == '+' || pp->a_lastcode[5] == '-') && + (pp->a_lastcode[9] == '+' || pp->a_lastcode[9] == '-') && + sscanf(pp->a_lastcode, "%5d%*c%3d%*c%3d", &lon, &lat, &off) == 3 + ) { + const char *label = "Botch!"; + + /* + * This is less than perfect. Call the (satellite) + * either EAST or WEST and adjust slop accodingly + * Perfectionists would recalculate the exact delay + * and adjust accordingly... + */ + if (lon > 7000 && lon < 14000) { + if (lon < 10000) { + new_station = GOES_EAST; + label = "EAST"; + } else { + new_station = GOES_WEST; + label = "WEST"; + } + + if (new_station != up->station) { + double dtemp; + + dtemp = pp->fudgetime1; + pp->fudgetime1 = pp->fudgetime2; + pp->fudgetime2 = dtemp; + up->station = new_station; + } + } + else { + refclock_report(peer, CEVNT_BADREPLY); + label = "UNKNOWN"; + } + true_debug(peer, "GOES: station %s\n", label); + true_doevent(peer, e_Satellite); + return; + } + + /* + * Timecode: "Fnn" + * (from TM/TMD clock when it wants to tell us what it's up to.) + */ + if (sscanf(pp->a_lastcode, "F%2d", &i) == 1 && i > 0 && i < 80) { + switch (i) { + case 50: + true_doevent(peer, e_F50); + break; + case 51: + true_doevent(peer, e_F51); + break; + default: + true_debug(peer, "got F%02d - ignoring\n", i); + break; + } + return; + } + + /* + * Timecode: " TRUETIME Mk III" + * (from a TM/TMD clock during initialization.) + */ + if (strcmp(pp->a_lastcode, " TRUETIME Mk III") == 0) { + true_doevent(peer, e_F18); + NLOG(NLOG_CLOCKSTATUS) { + msyslog(LOG_INFO, "TM/TMD: %s", pp->a_lastcode); + } + return; + } + + /* + * Timecode: "N03726428W12209421+000033" + * 1 2 + * 0123456789012345678901234 + * (from a TCU during initialization) + */ + if ((pp->a_lastcode[0] == 'N' || pp->a_lastcode[0] == 'S') && + (pp->a_lastcode[9] == 'W' || pp->a_lastcode[9] == 'E') && + pp->a_lastcode[18] == '+') { + true_doevent(peer, e_Location); + NLOG(NLOG_CLOCKSTATUS) { + msyslog(LOG_INFO, "TCU-800: %s", pp->a_lastcode); + } + return; + } + /* + * Timecode: "ddd:hh:mm:ssQ" + * (from all clocks supported by this driver.) + */ + if (pp->a_lastcode[3] == ':' && + pp->a_lastcode[6] == ':' && + pp->a_lastcode[9] == ':' && + sscanf(pp->a_lastcode, "%3d:%2d:%2d:%2d%c", + &pp->day, &pp->hour, &pp->minute, + &pp->second, &synced) == 5) { + + /* + * Adjust the synchronize indicator according to timecode + */ + if (synced != ' ' && synced != '.' && synced != '*') + pp->leap = LEAP_NOTINSYNC; + + true_doevent(peer, e_TS); + +#ifdef CLOCK_PPS720 + /* If it's taken more than 65ms to get here, we'll lose. */ + if ((pp->sloppyclockflag & CLK_FLAG4) && up->pcl720init) { + l_fp off; + +#ifdef CLOCK_ATOM + /* + * find out what time it really is. Include + * the count from the PCL720 + */ + if (!clocktime(pp->day, pp->hour, pp->minute, + pp->second, GMT, pp->lastrec.l_ui, + &pp->yearstart, &off.l_ui)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } +#endif + + pp->usec = true_sample720(); +#ifdef CLOCK_ATOM + TVUTOTSF(pp->usec, off.l_uf); +#endif + + /* + * Stomp all over the timestamp that was pulled out + * of the input stream. It's irrelevant since we've + * adjusted the input time to reflect now (via pp->usec) + * rather than when the data was collected. + */ + get_systime(&pp->lastrec); +#ifdef CLOCK_ATOM + /* + * Create a true offset for feeding to pps_sample() + */ + L_SUB(&off, &pp->lastrec); + + pps_sample(peer, &off); +#endif + true_debug(peer, "true_sample720: %luus\n", pp->usec); + } +#endif + + /* + * The clock will blurt a timecode every second but we only + * want one when polled. If we havn't been polled, bail out. + */ + if (!up->polled) + return; + + true_doevent(peer, e_Poll); + if (!refclock_process(pp)) { + refclock_report(peer, CEVNT_BADTIME); + return; + } + refclock_receive(peer); + + /* + * We have succedded in answering the poll. + * Turn off the flag and return + */ + up->polled = 0; + + return; + } + + /* + * No match to known timecodes, report failure and return + */ + refclock_report(peer, CEVNT_BADREPLY); + return; +} + + +/* + * true_send - time to send the clock a signal to cough up a time sample + */ +static void +true_send( + struct peer *peer, + const char *cmd + ) +{ + struct refclockproc *pp; + + pp = peer->procptr; + if (!(pp->sloppyclockflag & CLK_FLAG1)) { + register int len = strlen(cmd); + + true_debug(peer, "Send '%s'\n", cmd); + if (write(pp->io.fd, cmd, (unsigned)len) != len) + refclock_report(peer, CEVNT_FAULT); + else + pp->polls++; + } +} + + +/* + * state machine for initializing and controlling a clock + */ +static void +true_doevent( + struct peer *peer, + enum true_event event + ) +{ + struct true_unit *up; + struct refclockproc *pp; + + pp = peer->procptr; + up = (struct true_unit *)pp->unitptr; + if (event != e_TS) { + NLOG(NLOG_CLOCKSTATUS) { + msyslog(LOG_INFO, "TRUE: clock %s, state %s, event %s", + typeStr(up->type), + stateStr(up->state), + eventStr(event)); + } + } + true_debug(peer, "clock %s, state %s, event %s\n", + typeStr(up->type), stateStr(up->state), eventStr(event)); + switch (up->type) { + case t_goes: + switch (event) { + case e_Init: /* FALLTHROUGH */ + case e_Satellite: + /* + * Switch back to on-second time codes and return. + */ + true_send(peer, "C"); + up->state = s_Start; + break; + case e_Poll: + /* + * After each poll, check the station (satellite). + */ + true_send(peer, "P"); + /* No state change needed. */ + break; + default: + break; + } + /* FALLTHROUGH */ + case t_omega: + switch (event) { + case e_Init: + true_send(peer, "C"); + up->state = s_Start; + break; + case e_TS: + if (up->state != s_Start && up->state != s_Auto) { + true_send(peer, "\03\r"); + break; + } + up->state = s_Auto; + break; + default: + break; + } + break; + case t_tm: + switch (event) { + case e_Init: + true_send(peer, "F18\r"); + up->state = s_Init; + break; + case e_F18: + true_send(peer, "F50\r"); + up->state = s_F18; + break; + case e_F50: + true_send(peer, "F51\r"); + up->state = s_F50; + break; + case e_F51: + true_send(peer, "F08\r"); + up->state = s_Start; + break; + case e_TS: + if (up->state != s_Start && up->state != s_Auto) { + true_send(peer, "\03\r"); + break; + } + up->state = s_Auto; + break; + default: + break; + } + break; + case t_tcu: + switch (event) { + case e_Init: + true_send(peer, "MD3\r"); /* GPS Synch'd Gen. */ + true_send(peer, "TSU\r"); /* UTC, not GPS. */ + true_send(peer, "AU\r"); /* Auto Timestamps. */ + up->state = s_Start; + break; + case e_TS: + if (up->state != s_Start && up->state != s_Auto) { + true_send(peer, "\03\r"); + break; + } + up->state = s_Auto; + break; + default: + break; + } + break; + case t_unknown: + switch (up->state) { + case s_Base: + if (event != e_Init) + abort(); + true_send(peer, "P\r"); + up->state = s_InqGOES; + break; + case s_InqGOES: + switch (event) { + case e_Satellite: + up->type = t_goes; + true_doevent(peer, e_Init); + break; + case e_Init: /*FALLTHROUGH*/ + case e_Huh: /*FALLTHROUGH*/ + case e_TS: + up->state = s_InqOmega; + true_send(peer, "C\r"); + break; + default: + abort(); + } + break; + case s_InqOmega: + switch (event) { + case e_TS: + up->type = t_omega; + up->state = s_Auto; /* Inq side-effect. */ + break; + case e_Init: /*FALLTHROUGH*/ + case e_Huh: + up->state = s_InqTM; + true_send(peer, "F18\r"); + break; + default: + abort(); + } + break; + case s_InqTM: + switch (event) { + case e_F18: + up->type = t_tm; + true_doevent(peer, e_Init); + break; + case e_Init: /*FALLTHROUGH*/ + case e_Huh: + true_send(peer, "PO\r"); + up->state = s_InqTCU; + break; + default: + abort(); + } + break; + case s_InqTCU: + switch (event) { + case e_Location: + up->type = t_tcu; + true_doevent(peer, e_Init); + break; + case e_Init: /*FALLTHROUGH*/ + case e_Huh: + up->state = s_Base; + sleep(1); /* XXX */ + break; + default: + abort(); + } + break; + /* + * An expedient hack to prevent lint complaints, + * these don't actually need to be used here... + */ + case s_Init: + case s_F18: + case s_F50: + case s_Start: + case s_Auto: + case s_Max: + msyslog(LOG_INFO, "TRUE: state %s is unexpected!", stateStr(up->state)); + } + break; + default: + abort(); + /* NOTREACHED */ + } + +#ifdef CLOCK_PPS720 + if ((pp->sloppyclockflag & CLK_FLAG4) && !up->pcl720init) { + /* Make counter trigger on gate0, count down from 65535. */ + pcl720_load(PCL720_IOB, PCL720_CTR, i8253_oneshot, 65535); + /* + * (These constants are OK since + * they represent hardware maximums.) + */ + NLOG(NLOG_CLOCKINFO) { + msyslog(LOG_NOTICE, "PCL-720 initialized"); + } + up->pcl720init++; + } +#endif + + +} + +/* + * true_poll - called by the transmit procedure + */ +static void +true_poll( + int unit, + struct peer *peer + ) +{ + struct true_unit *up; + struct refclockproc *pp; + + /* + * You don't need to poll this clock. It puts out timecodes + * once per second. If asked for a timestamp, take note. + * The next time a timecode comes in, it will be fed back. + */ + pp = peer->procptr; + up = (struct true_unit *)pp->unitptr; + if (up->pollcnt > 0) + up->pollcnt--; + else { + true_doevent(peer, e_Init); + refclock_report(peer, CEVNT_TIMEOUT); + } + + /* + * polled every 64 seconds. Ask true_receive to hand in a + * timestamp. + */ + up->polled = 1; + pp->polls++; +} + +#ifdef CLOCK_PPS720 +/* + * true_sample720 - sample the PCL-720 + */ +static u_long +true_sample720(void) +{ + unsigned long f; + + /* We wire the PCL-720's 8253.OUT0 to bit 0 of connector 3. + * If it is not being held low now, we did not get called + * within 65535us. + */ + if (inb(pcl720_data_16_23(PCL720_IOB)) & 0x01) { + NLOG(NLOG_CLOCKINFO) { + msyslog(LOG_NOTICE, "PCL-720 out of synch"); + } + return (0); + } + f = (65536 - pcl720_read(PCL720_IOB, PCL720_CTR)); +#ifdef PPS720_DEBUG + msyslog(LOG_DEBUG, "PCL-720: %luus", f); +#endif + return (f); +} +#endif + +#else +int refclock_true_bs; +#endif /* REFCLOCK */ |