/* * refclock_pst - driver for the PSTI 1010/1020 WWV clock */ #include #include #include #include #include #include #include #include #include #include "ntp_syslog.h" #include "ntp_fp.h" #include "ntp.h" #include "ntp_refclock.h" #include "ntp_unixtime.h" #if defined(REFCLOCK) && defined(PST) /* * This driver is in good measure due to David Schachter, who wrote * the firmware for the PST clock. Not that he is to blame for * any of this, but he kindly loaned me a clock to allow me to * debug this. * * Postscript: * * The strategy in here is actually pretty good, especially if * you try to support the clock on something lacking low order * clock bits like a Sun, since all the business which is done * before taking a time stamp tends to randomize the taking of * the stamp with respect to the timer interrupt. It is, however, * a big cpu hog, and in some ways is a bit of a waste since, as * it turns out, the PST clock can give you no better than a * millisecond precision and it doesn't pay to try to push it * harder. * * In any event, like the first waffle off the iron, this one * should probably be tossed. My current preference would be * to retain the 12-a-minute schedule, but to use the QU command * instead of the QD and QT, and to only send a QM command with * the 12th poll of the minute to get the minutes-since-sync * and the station. Need to get a clock which supports QU, * however. * * End postscript * * This driver polls the clock using the QM, QT and QD commands. * Ntpd actually uses QU instead of the last two, something I would * like to have done as well since it gives you the day and time * atom, but the firmware in the clock I had (X04.01.999) didn't know * about this command. * * The QM command produces output like: * * O6B532352823C00270322 * b c deeee * * We use (b) for the time zone, (c) to see whether time is available, * (d) to tell whether we are sync'd to WWV or WWVH, and (e) to determine * the number of minutes since the last signal was received. We * don't trust the clock for more than about 20 minutes on its own. * After this, we keep taking the time but mark the clock unsynchronized. * * The QT command returns something that looks like this: * * 18:57:50.263D * * Note that this particular sample is in 24 hour format, local time * (daylight savings time even). We allow just about anything for * this (sigh) since this leaves the clock owner free to set the * display mode in whatever way he finds convenient for setting * his watch. * * The QD command returns: * * 89/10/19/292 * * We actually only use the day-of-the-year here. We use the year * only to determine whether the PST clock thinks the current year * has 365 or 366 days in it. * * At the current writing, this code expects to be using a BSD-style * terminal driver. It will compile code which uses the CLKLDISC * line discipline if it thinks this is available, but use cooked * mode otherwise. The cooked mode stuff may not have been tested. */ /* * Definitions */ #define MAXUNITS 4 /* maximum number of PST units permitted */ #define PSTDEV "/dev/pst%d" /* device we open. %d is unit number */ #define NPSTSAMPS 12 /* take 12 PST samples per minute */ /* * When the PST clock is operating optimally we want the primary clock * distance to come out at 300 ms. Thus, peer.distance in the PST peer * structure is set to 290 ms and we compute delays which are at least * 10 ms long. The following are 290 ms and 10 ms expressed in u_fp format */ #define PSTDISTANCE 0x00004a3d #define PSTBASEDELAY 0x0000028f /* * Other constant stuff */ #define PSTPRECISION (-9) /* what the heck */ #define WWVREFID "WWV\0" #define WWVHREFID "WWVH" #define PSTHSREFID 0x7f7f030a /* 127.127.3.10 refid for hi strata */ /* * Parameters for the clock */ #define PSTBAUD B9600 #ifdef CLKLDISC #define PSTMAGIC1 '\r' #define PSTMAGIC2 ('\r' | 0x80) #define PSTEOL '\r' #else #define PSTEOL '\n' #endif /* * Description of clock. We fill in whether it is a 1010 or 1020, * and the firmware revision, using the QV command. */ #define PSTDESCLEN 64 #define PSTDESCRIPTION "%s %s (%s) WWV/H Receiver" #define PSTDEFDESC "PSTI/Traconex 10?0 (V??.??) WWV/H Receiver" /* * Length of the PST time code. This must be the length of the output * of the QM command, plus QT, plus QD, plus two spaces. We make it * big just on principle. */ #define PSTCODELEN (128) /* * Minimum and maximum lengths */ #define PSTMINQVLEN (16) #define PSTMAXQVLEN (24) #define PSTMINQMLEN (19) #define PSTMAXQMLEN (32) #define PSTMINQDLEN (12) #define PSTMAXQDLEN (12) #define PSTMINQTLEN (14) #define PSTMAXQTLEN (14) /* * It turns out that the QT command does *not* adjust for transmission * delays. Since the QT command returns 15 characters at 9600 baud, * the adjustment for this should be 15.6 ms. We'll default to this, * but don't let this stop you from fiddling with the fudge factors * to make things come out right */ #define PSTQTFUDGE 0x04000000 /* about 15.6 ms */ /* * Default propagation delays. About right for Toronto */ #define DEFWWVPROP 0x01eb851f /* about 7.5 ms */ #define DEFWWVHPROP 0x06c8b439 /* about 26.5 ms */ /* * Maximum propagation delay we believe. 125 ms as an l_fp fraction */ #define PSTMAXPROP 0x20000000 /* * Default minutes since an update. */ #define DEFMAXFREERUN (20) /* * Hold time after a leap second occurs */ #define PSTLEAPHOLD (3*60*60) /* 3 hours */ /* * Hack to avoid excercising the multiplier. I have no pride. */ #define MULBY10(x) (((x)<<3) + ((x)<<1)) /* * PST unit control structure. */ struct pstunit { struct peer *peer; /* associated peer structure */ struct event psttimer; /* timeout timer structure */ struct refclockio pstio; /* given to the I/O handler */ l_fp rectimes[NPSTSAMPS]; /* times we received this stuff */ l_fp reftimes[NPSTSAMPS]; /* times of codes received */ l_fp lastrec; /* last receive time */ l_fp lastref; /* last reference time */ char description[PSTDESCLEN]; /* description of clock */ char lastcode[PSTCODELEN]; /* last code we received */ u_char lencode; /* length of the last code */ u_char nextsample; /* the next offset expected */ u_char unit; /* unit number for this guy */ u_char state; /* what we're waiting for */ s_char station; /* WWV or WWVH? */ u_char dontsync; /* something detected to prevent sync */ u_char flags; /* flag byte */ u_char status; /* clock status */ u_char lastevent; /* last clock event */ u_char timezone; /* hour offset to time zone */ u_char errors; /* number of errors detected */ u_char year; /* year reported by clock */ u_char month; /* month, from clock */ u_char monthday; /* day, from clock */ u_char hour; /* hour of day */ u_char minute; /* minute of day */ u_char second; /* second of day */ s_char tzoffset; /* time zone offset */ u_char reason; /* reason for failure */ u_short millisecond; /* millisecond of day */ u_short yearday; /* day of the year */ u_short timesincesync; /* time since radio got sample */ u_long yearstart; /* NTP time at year start */ u_long leapend; /* time of ending of leap event */ u_long lastupdate; /* last time data received */ u_long polls; /* number of polls */ u_long noreply; /* number of time outs */ u_long badformat; /* number of bad format responses */ u_long baddata; /* number of invalid time codes */ u_long timestarted; /* time we started this */ }; /* * States we might be in */ #define STATE_IDLE 0 /* not doing anything in particular */ #define STATE_QV 1 /* trying to get version */ #define STATE_QM 2 /* sent QM */ #define STATE_QD 3 /* sent QD */ #define STATE_QT 4 /* send QT */ /* * Status flags */ #define PST_LEAPYEAR 0x1 /* pst clock thinks it is a leap year */ #define PST_SIGFAULT 0x2 /* signal fault */ #define PST_HARDERR 0x4 /* hardware error */ #define PST_NOTIME 0x8 /* no time available */ #define PST_WWVH 0x10 /* synchronized to WWVH */ #define PST_DOQV 0x20 /* get version, reinit delays */ #define PST_DORESET 0x40 /* reset the clock */ /* * The PST often encodes stuff by adding an ASCII '0' to it. The * largest range of values encoded this way is 0 through 31, or '0' * through 'O'. These macroes manipulate these values. */ #define ISVALIDPST(c) ((c) >= '0' && (c) <= 'O') #define PSTTOBIN(c) ((int)(c) - '0') #define BINTOPST(c) ((char)((c) + '0')) /* * Status bits. Look at the QM command */ #define SIGFAULT 0x1 #define HARDFAULT 0x2 #define OUTOFSPEC 0x4 #define TIMEAVAILABLE 0x8 /* * Module reason codes */ #define QVREASON 20 #define QMREASON 40 #define QDREASON 60 #define QTREASON 80 /* * Station i.d. characters in QM output */ #define WWV_CHAR 'C' #define WWVH_CHAR 'H' /* * We allow a few errors, but if we get more than 12 seconds behind * the schedule we start from sample 0 again. 4 seconds is the minimum * time between time out routine executions. */ #define PSTMAXDELAY 12 #define PSTMINTIMEOUT 4 /* * The PST polling schedule. We poll 12 times per 64 seconds (far too * many, but what the heck). The polls are scheduled to finish in this * time with the assumption that the timer is good for no better than * 4 second resolution. If we get too far behind (due to bad samples * or no responses) we start over. */ struct pstsched { u_short nextinterval; u_short tooold; }; static struct pstsched psttab[NPSTSAMPS] = { { 4, PSTMAXDELAY+1 }, { 4, PSTMAXDELAY+1+4 }, { 8, PSTMAXDELAY+1+4+4 }, { 4, PSTMAXDELAY+1+4+4+8 }, { 8, PSTMAXDELAY+1+4+4+8+4 }, { 4, PSTMAXDELAY+1+4+4+8+4+8 }, { 4, PSTMAXDELAY+1+4+4+8+4+8+4 }, { 8, PSTMAXDELAY+1+4+4+8+4+8+4+4 }, { 4, PSTMAXDELAY+1+4+4+8+4+8+4+4+8 }, { 8, PSTMAXDELAY+1+4+4+8+4+8+4+4+8+4 }, { 4, PSTMAXDELAY+1+4+4+8+4+8+4+4+8+4+8 }, { 4, PSTMAXDELAY+1+4+4+8+4+8+4+4+8+4+8+4 } }; /* * Data space for the unit structures. Note that we allocate these on * the fly, but never give them back. */ static struct pstunit *pstunits[MAXUNITS]; static u_char unitinuse[MAXUNITS]; /* * Structure to keep processed propagation data in. */ struct pst_propagate { u_long remainder; /* left over submillisecond remainder */ char msbchar; /* character for high order bits */ char lsbchar; /* character for low order bits */ }; /* * Keep the fudge factors separately so they can be set even * when no clock is configured. */ static l_fp wwv_prop_delay[MAXUNITS]; static l_fp wwvh_prop_delay[MAXUNITS]; static struct pst_propagate wwv_prop_data[MAXUNITS]; static struct pst_propagate wwvh_prop_data[MAXUNITS]; static u_char stratumtouse[MAXUNITS]; static u_char sloppyclock[MAXUNITS]; static u_short freerun[MAXUNITS]; /* * Pointer to the default description */ static char *pstdefdesc = PSTDEFDESC; /* * macro for writing to the clock, printing an error if we fail */ #define pst_send(pst, str, len) \ if (write((pst)->pstio.fd, (str), (len)) < 0) \ pst_write_error((pst)) /* * macro for resetting the clock structure to zero */ #define pst_reset(pst) \ do { \ pst->nextsample = 0; \ pst->station = 0; \ pst->dontsync = 0; \ } while (0) /* * macro for event reporting */ #define pst_event(pst, evnt_code) \ do { \ if ((pst)->status != (u_char)(evnt_code)) \ pst_do_event((pst), (evnt_code)); \ } while (0) /* * Imported from the timer module */ extern u_long current_time; extern struct event timerqueue[]; /* * Time conversion tables imported from the library */ extern u_long msutotsflo[]; extern u_long msutotsfhi[]; /* * pst_init - initialize internal PST driver data */ void pst_init() { register int i; void pst_compute_delay(); /* * Just zero the data arrays */ bzero((char *)pstunits, sizeof pstunits); bzero((char *)unitinuse, sizeof unitinuse); /* * Initialize fudge factors to default. */ for (i = 0; i < MAXUNITS; i++) { wwv_prop_delay[i].l_ui = 0; wwv_prop_delay[i].l_uf = DEFWWVPROP; pst_compute_delay(DEFWWVPROP, &wwv_prop_data[i]); wwvh_prop_delay[i].l_ui = 0; wwvh_prop_delay[i].l_uf = DEFWWVHPROP; pst_compute_delay(DEFWWVHPROP, &wwvh_prop_data[i]); stratumtouse[i] = 0; sloppyclock[i] = 0; freerun[i] = DEFMAXFREERUN; } } /* * pst_start - open the PST device and initialize data for processing */ int pst_start(unit, peer) u_int unit; struct peer *peer; { register struct pstunit *pst; register int i; int fd; int ldisc; char pstdev[20]; struct sgttyb ttyb; void pst_timeout(); void pst_receive(); extern int io_addclock(); extern char *emalloc(); if (unit >= MAXUNITS) { syslog(LOG_ERR, "pst clock: unit number %d invalid (max %d)", unit, MAXUNITS-1); return 0; } if (unitinuse[unit]) { syslog(LOG_ERR, "pst clock: unit number %d in use", unit); return 0; } /* * Unit okay, attempt to open the device. */ (void) sprintf(pstdev, PSTDEV, unit); fd = open(pstdev, O_RDWR, 0777); if (fd == -1) { syslog(LOG_ERR, "pst clock: open of %s failed: %m", pstdev); return 0; } /* * Set for exclusive use */ if (ioctl(fd, TIOCEXCL, (char *)0) < 0) { syslog(LOG_ERR, "pst clock: ioctl(%s, TIOCEXCL): %m", pstdev); (void) close(fd); return 0; } /* * Set to raw mode */ ttyb.sg_ispeed = ttyb.sg_ospeed = PSTBAUD; #ifdef CLKLDISC ttyb.sg_erase = PSTMAGIC1; ttyb.sg_kill = PSTMAGIC2; ttyb.sg_flags = EVENP|ODDP|RAW|CRMOD; #else ttyb.sg_erase = ttyb.sg_kill = 0; ttyb.sg_flags = EVENP|ODDP|CRMOD; #endif if (ioctl(fd, TIOCSETP, (char *)&ttyb) < 0) { syslog(LOG_ERR, "pst clock: ioctl(%s, TIOCSETP): %m", pstdev); return 0; } /* * Looks like this might succeed. Find memory for the structure. * Look to see if there are any unused ones, if not we malloc() * one. */ if (pstunits[unit] != 0) { pst = pstunits[unit]; /* The one we want is okay */ } else { for (i = 0; i < MAXUNITS; i++) { if (!unitinuse[i] && pstunits[i] != 0) break; } if (i < MAXUNITS) { /* * Reclaim this one */ pst = pstunits[i]; pstunits[i] = 0; } else { pst = (struct pstunit *)emalloc(sizeof(struct pstunit)); } } bzero((char *)pst, sizeof(struct pstunit)); pstunits[unit] = pst; /* * Set up the structure */ pst->peer = peer; pst->unit = (u_char)unit; pst->state = STATE_IDLE; pst->flags |= PST_DOQV; pst->timestarted = current_time; (void) strcpy(pst->description, pstdefdesc); pst->psttimer.peer = (struct peer *)pst; pst->psttimer.event_handler = pst_timeout; pst->pstio.clock_recv = pst_receive; pst->pstio.srcclock = (caddr_t)pst; pst->pstio.datalen = 0; pst->pstio.fd = fd; /* * Okay. Set the line discipline to the clock line discipline, * if we have it, then give it to the I/O code to start receiving * stuff. */ #ifdef CLKLDISC ldisc = CLKLDISC; if (ioctl(fd, TIOCSETD, (char *)&ldisc) < 0) { syslog(LOG_ERR, "pst clock: ioctl(%s, TIOCSETD): %m", pstdev); (void) close(fd); return 0; } #else ldisc = 0; if (ioctl(fd, TIOCFLUSH, (char *)&ldisc) < 0) { syslog(LOG_ERR, "pst clock: ioctl(%s, TIOCFLUSH): %m", pstdev); (void) close(fd); return 0; } #endif if (!io_addclock(&pst->pstio)) { /* * Oh shit. Just close and return. */ (void) close(fd); return 0; } /* * All done. Initialize a few random peer variables, then * start the timer and return success. */ peer->distance = PSTDISTANCE; peer->precision = PSTPRECISION; peer->stratum = stratumtouse[unit]; if (stratumtouse[unit] <= 1) bcopy(WWVREFID, (char *)&peer->refid, 4); else peer->refid = htonl(PSTHSREFID); pst->psttimer.event_time = current_time + PSTMINTIMEOUT; TIMER_ENQUEUE(timerqueue, &pst->psttimer); unitinuse[unit] = 1; return 1; } /* * pst_shutdown - shut down a PST clock */ void pst_shutdown(unit) int unit; { register struct pstunit *pst; extern void io_closeclock(); if (unit >= MAXUNITS) { syslog(LOG_ERR, "pst clock: INTERNAL ERROR, unit number %d invalid (max %d)", unit, MAXUNITS-1); return; } if (!unitinuse[unit]) { syslog(LOG_ERR, "pst clock: INTERNAL ERROR, unit number %d not in use", unit); return; } /* * Tell the I/O module to turn us off, and dequeue timer * if any. We're history. */ pst = pstunits[unit]; TIMER_DEQUEUE(&pst->psttimer); io_closeclock(&pst->pstio); unitinuse[unit] = 0; } /* * pst_write_error - complain about writes to the clock */ static void pst_write_error(pst) struct pstunit *pst; { /* * This will fill syslog is something is really wrong. Should * throttle it back. */ syslog(LOG_ERR, "pst clock: write error on unit %d: %m", pst->unit); } /* * pst_timeout - process a timeout event */ void pst_timeout(fakepeer) struct peer *fakepeer; { register struct pstunit *pst; u_long poll; /* * The timeout routine always initiates a chain of * query-responses from the clock, by sending either * a QV command (if we need to (re)set the propagation * delays into the clock), a QM command or an SRY * command (after a leap second). The pst_receive() * routine should complete the set of queries on its own * long before the next time out is due, so if we see any * state in here other than idle it means the clock hasn't * responded. */ pst = (struct pstunit *)fakepeer; switch(pst->state) { case STATE_IDLE: poll = (u_long)psttab[pst->nextsample].nextinterval; break; /* all is well */ case STATE_QV: pst->flags |= PST_DOQV; /* no response, do QV again */ /*FALLSTHROUGH*/ case STATE_QM: case STATE_QD: case STATE_QT: pst->noreply++; /* mark the lack of response */ poll = PSTMINTIMEOUT; /* minimum time poll */ break; default: syslog(LOG_ERR, "pst clock: INTERNAL ERROR unit %d invalid state %d", pst->unit, pst->state); poll = PSTMINTIMEOUT; /* minimum time poll */ break; } if (pst->flags & PST_DORESET) { /* * Do a reset. At the next interrupt, start with * a QV command to set in the delays. */ pst->flags &= ~PST_DORESET; pst->flags |= PST_DOQV; pst->state = STATE_IDLE; pst_send(pst, "\003SRY", 4); } else if (pst->flags & PST_DOQV) { pst->polls++; pst->flags &= ~PST_DOQV; pst->state = STATE_QV; pst_send(pst, "\003QV", 3); } else { pst->polls++; pst->state = STATE_QM; pst_send(pst, "\003QM", 3); } pst->psttimer.event_time += poll; TIMER_ENQUEUE(timerqueue, &pst->psttimer); } /* * pst_QV_process - decode the results of a QV poll and insert fudge * factors into the clock. */ static int pst_QV_process(pst, rbufp) register struct pstunit *pst; struct recvbuf *rbufp; { register char *cp; register char *bp; register int len; char *model; char *company; char buf[20]; static char wwvdelay[6] = { 'S', 'C', '\0', 'S', 'E', '\0' }; static char wwvhdelay[6] = { 'S', 'H', '\0', 'S', 'G', '\0' }; /* * The output of the QV command looks like: * * PSTI ITS V04.01.000\r * * or * * TRAC ITS V04.01.000\r * * The minimum length of the string is about 16 characters. * The maximum length is sort of unbounded, but we get suspicious * if it is more than 34. */ len = rbufp->recv_length; if (len > PSTMAXQVLEN + 10) len = PSTMAXQVLEN + 10; bp = rbufp->recv_buffer; cp = pst->lastcode; while (len-- > 0) { *cp = (*bp++) & 0x7f; /* strip parity */ if (!isprint(*cp)) break; cp++; } pst->lencode = (u_char)(cp - pst->lastcode); /* * Okay, got all printable characters from the string * copied. We expect to have been terminated by the * EOL character. If not, forget it. If the length * is insane, forget it. */ if (len < 0 || *cp != PSTEOL || pst->lencode < PSTMINQVLEN || pst->lencode > PSTMAXQVLEN) { pst->reason = QVREASON + 1; return 0; } /* * Now, format check what we can. Dump it at the least * sign of trouble. */ cp = pst->lastcode; if (*cp++ != 'P' || *cp++ != 'S' || *cp++ != 'T' || *cp++ != 'I' || *cp++ != ' ') { cp = pst->lastcode; if (*cp++ != 'T' || *cp++ != 'R' || *cp++ != 'A' || *cp++ != 'C' || *cp++ != ' ') { pst->reason = QVREASON + 2; return 0; } company = "Traconex"; } else { company = "Precision Standard Time"; } if (*cp == 'M') model = "1010"; else if (*cp == 'I') model = "1020"; else { pst->reason = QVREASON + 3; return 0; } cp++; if (*cp++ != 'T' || *cp++ != 'S' || *cp++ != ' ') { pst->reason = QVREASON + 4; return 0; } if (*cp != 'X' && *cp != 'V') { pst->reason = QVREASON + 5; return 0; } /* * Next is the version. Copy it into the buffer. */ bp = buf; *bp++ = *cp++; while (isdigit(*cp) || *cp == '.') *bp++ = *cp++; *bp++ = '\0'; /* * Final bit of fluff is to set the description */ (void) sprintf(pst->description, PSTDESCRIPTION, company, model, buf); /* * Now the serious stuff. Since we are now sure that the * clock is there, we can be fairly sure that the delay * setting commands will take. Send them. */ wwvdelay[2] = wwv_prop_data[pst->unit].msbchar; wwvdelay[5] = wwv_prop_data[pst->unit].lsbchar; pst_send(pst, wwvdelay, 6); /* * Same thing for WWVH */ wwvhdelay[2] = wwvh_prop_data[pst->unit].msbchar; wwvhdelay[5] = wwvh_prop_data[pst->unit].lsbchar; pst_send(pst, wwvhdelay, 6); /* * Should be okay. Return positive response. */ return 1; } /* * pst_QM_process - process the output of a QM command */ static int pst_QM_process(pst, rbufp) register struct pstunit *pst; struct recvbuf *rbufp; { register char *cp; register char *bp; register int n; /* * The output of the QM command looks like: * * O6B532352823C00270322 * * The minimum length of the string is 19 characters. * The maximum length is sort of unbounded, but we get suspicious * if it is more than 42. */ n = rbufp->recv_length; if (n > PSTMAXQMLEN + 10) n = PSTMAXQMLEN + 10; bp = rbufp->recv_buffer; cp = pst->lastcode; while (n-- > 0) { *cp = (*bp++) & 0x7f; /* strip parity */ if (!isprint(*cp)) break; cp++; } pst->lencode = (u_char)(cp - pst->lastcode); /* * Okay, got all printable characters from the string * copied. We expect to have been terminated by the * EOL character. If not, forget it. If the length * is insane, forget it. */ if (n < 0 || *cp != PSTEOL || pst->lencode < PSTMINQMLEN || pst->lencode > PSTMAXQMLEN) { pst->reason = QMREASON + 1; return 0; } /* * Ensure that the first PSTMINQMLEN characters are valid with * respect to the way the clock encodes binary data. */ cp = pst->lastcode; n = pst->lencode; while (n-- > 0) { if (!ISVALIDPST(*cp)) { pst->reason = QMREASON + 2; return 0; } cp++; } /* * Collect information we are interested in. */ cp = pst->lastcode; pst->timezone = PSTTOBIN(cp[3]); if (pst->timezone > 23) { pst->reason = QMREASON + 3; return 0; } pst->flags &= ~(PST_LEAPYEAR|PST_SIGFAULT|PST_HARDERR|PST_NOTIME|PST_WWVH); n = PSTTOBIN(cp[4]); if (n > 15) { pst->reason = QMREASON + 4; return 0; } if (((n + 2) & 0x3) == 0) pst->flags |= PST_LEAPYEAR; n = PSTTOBIN(cp[9]); if (n > 15) { pst->reason = QMREASON + 5; return 0; } if (n & SIGFAULT) pst->flags |= PST_SIGFAULT; if (n & HARDFAULT) pst->flags |= PST_HARDERR; if (!(n & TIMEAVAILABLE)) pst->flags |= PST_NOTIME; if (cp[12] == 'H') { pst->flags |= PST_WWVH; } else if (cp[12] == 'C') { pst->flags &= ~PST_WWVH; } else { pst->reason = QMREASON + 6; return 0; } if (wwv_prop_data[pst->unit].msbchar != cp[5] || wwv_prop_data[pst->unit].lsbchar != cp[6] || wwvh_prop_data[pst->unit].msbchar != cp[7] || wwvh_prop_data[pst->unit].lsbchar != cp[8]) pst->flags |= PST_DOQV; bp = cp + 13; pst->timesincesync = 0; while (bp < (cp + 17)) { if (!isdigit(*bp)) { pst->reason = QMREASON + 6; return 0; } pst->timesincesync = MULBY10(pst->timesincesync) + PSTTOBIN(*bp); bp++; } /* * That's about all we can do. Return success. */ return 1; } /* * pst_QD_process - process the output of a QD command */ static int pst_QD_process(pst, rbufp) register struct pstunit *pst; struct recvbuf *rbufp; { register char *cp; register char *bp; register int n; char *cpstart; int len; /* * The output of the QM command looks like: * * 88/05/17/138\r * * The minimum length of the string is 12 characters as is * the maximum length. */ n = rbufp->recv_length; if (n > PSTMAXQDLEN + 10) n = PSTMAXQDLEN + 10; bp = rbufp->recv_buffer; cp = &pst->lastcode[pst->lencode]; *cp++ = ' '; cpstart = cp; while (n-- > 0) { *cp = (*bp++) & 0x7f; /* strip parity */ if (!isprint(*cp)) break; cp++; } len = (cp - cpstart); pst->lencode = (u_char)(cp - pst->lastcode); /* * Okay, got all printable characters from the string * copied. We expect to have been terminated by the * EOL character. If not, forget it. If the length * is insane, forget it. */ if (n < 0 || *cp != PSTEOL || len < PSTMINQDLEN || len > PSTMAXQDLEN) { pst->reason = QDREASON + 1; return 0; } /* * Ensure that the characters are formatted validly. They * are either digits or '/'s. */ cp = cpstart; if (!isdigit(cp[0]) || !isdigit(cp[1]) || cp[2] != '/' || !isdigit(cp[3]) || !isdigit(cp[4]) || cp[5] != '/' || !isdigit(cp[6]) || !isdigit(cp[7]) || cp[8] != '/' || !isdigit(cp[9]) || !isdigit(cp[10]) || !isdigit(cp[11])) { pst->reason = QDREASON + 2; return 0; } /* * Decode into year, month, day and year day */ pst->year = MULBY10(PSTTOBIN(cp[0])) + PSTTOBIN(cp[1]); pst->month = MULBY10(PSTTOBIN(cp[3])) + PSTTOBIN(cp[4]); pst->monthday = MULBY10(PSTTOBIN(cp[6])) + PSTTOBIN(cp[7]); pst->yearday = MULBY10(PSTTOBIN(cp[9])) + PSTTOBIN(cp[10]); pst->yearday = MULBY10(pst->yearday) + PSTTOBIN(cp[11]); /* * Format check these. */ if (pst->month > 12 || pst->monthday > 31 || pst->yearday > 366) { pst->reason = QDREASON + 3; return 0; } if (!(pst->flags & PST_LEAPYEAR) && pst->yearday > 365) { pst->reason = QDREASON + 4; return 0; } /* * Done all we can. */ return 1; } /* * pst_QT_process - process the output of a QT command, return the times */ static int pst_QT_process(pst, rbufp, tsclk, tsrec) register struct pstunit *pst; struct recvbuf *rbufp; l_fp *tsclk; l_fp *tsrec; { register char *cp; register char *bp; register int n; char *cpstart; int len; int hour; int minute; int second; int msec; int tzoff; extern int buftvtots(); /* * The output of the QT command looks like: * * A09:57:50.263D * * The minimum length of the string is 14 characters as is * the maximum length. */ n = rbufp->recv_length; if (n > PSTMAXQTLEN + 10) n = PSTMAXQTLEN + 10; bp = rbufp->recv_buffer; cp = &pst->lastcode[pst->lencode]; *cp++ = ' '; cpstart = cp; while (n-- > 0) { *cp = (*bp++) & 0x7f; /* strip parity */ if (!isprint(*cp)) break; cp++; } len = (cp - cpstart); pst->lencode = (u_char)(cp - pst->lastcode); /* * Okay, got all printable characters from the string * copied. We expect to have been terminated by the * EOL character. If not, forget it. If the length * is insane, forget it. */ if (n < 0 || *cp != PSTEOL || len < PSTMINQTLEN || len > PSTMAXQTLEN) { pst->reason = QTREASON + 1; return 0; } #ifdef CLKLDISC /* * Receive time stamp should be in buffer after the code. * Make sure we have enough characters in there. */ if (&rbufp->recv_buffer[rbufp->recv_length] - bp < 8) { pst->reason = QTREASON + 2; return 0; } if (!buftvtots(bp, tsrec)) { pst->reason = QTREASON + 3; return 0; } #else /* * Use the timestamp collected with the input. */ *tsrec = rbufp->recv_time; #endif /* * Ensure that the characters are formatted validly. Mostly * digits, but the occasional `:' and `.'. */ cp = cpstart; if (!isdigit(cp[1]) || !isdigit(cp[2]) || cp[3] != ':' || !isdigit(cp[4]) || !isdigit(cp[5]) || cp[6] != ':' || !isdigit(cp[7]) || !isdigit(cp[8]) || cp[9] != '.' || !isdigit(cp[10]) || !isdigit(cp[11]) || !isdigit(cp[12])) { pst->reason = QTREASON + 4; return 0; } /* * Extract the hour, minute, second and millisecond */ hour = MULBY10(PSTTOBIN(cp[1])) + PSTTOBIN(cp[2]); minute = MULBY10(PSTTOBIN(cp[4])) + PSTTOBIN(cp[5]); second = MULBY10(PSTTOBIN(cp[7])) + PSTTOBIN(cp[8]); msec = MULBY10(PSTTOBIN(cp[10])) + PSTTOBIN(cp[11]); msec = MULBY10(msec) + PSTTOBIN(cp[12]); if (minute > 59 || second > 59) { pst->reason = QTREASON + 5; return 0; } /* * Trouble here. Adjust the hours for AM/PM, if this is * on, and for daylight saving time. */ if (*cp == 'A') { if (hour > 12 || hour == 0) { pst->reason = QTREASON + 5; return 0; } if (hour == 12) hour = 0; } else if (*cp == 'P') { if (hour > 12 || hour == 0) return 0; if (hour < 12) hour += 12; } else if (*cp != ' ') { pst->reason = QTREASON + 6; return 0; } if (cp[13] == 'D') tzoff = -1; else if (cp[13] == ' ') tzoff = 0; else { pst->reason = QTREASON + 7; return 0; } /* * Adjust for the timezone. The PST manual is screwy here. * it says the timezone is an integer in the range 0 to 23, * but this doesn't allow us to tell the difference between * +12 and -12. Assume the 12 hour timezone is west of * GMT. */ if (pst->timezone <= 12) tzoff += pst->timezone; else tzoff -= (24 - pst->timezone); /* * Record for posterity */ pst->hour = (u_char)hour; pst->minute = (u_char)minute; pst->second = (u_char)second; pst->millisecond = (u_short)msec; pst->tzoffset = (s_char)tzoff; /* * All that to get the day-hour-minute-second. Turn this * into the seconds part of a time stamp. Also use the * milliseconds part directly as the fractional part. */ MSUTOTSF(msec, tsclk->l_uf); if (!clocktime((int)pst->yearday, hour, minute, second, tzoff, tsrec->l_ui, &pst->yearstart, &tsclk->l_ui)) { pst->reason = QTREASON + 8; return 0; } /* * Add in the fudge */ if (pst->flags & PST_WWVH) L_ADDUF(tsclk, wwvh_prop_data[pst->unit].remainder); else L_ADDUF(tsclk, wwv_prop_data[pst->unit].remainder); /* * Glad that's over with */ return 1; } /* * pst_do_event - update our status and report any changes */ static void pst_do_event(pst, evnt_code) register struct pstunit *pst; int evnt_code; { if (pst->status != (u_char)evnt_code) { pst->status = (u_char)evnt_code; if (evnt_code != CEVNT_NOMINAL) pst->lastevent = (u_char)evnt_code; /* * Should trap this, but the trap code isn't up to * it yet. */ } } /* * pst_process - process the data collected to produce an offset estimate */ static void pst_process(pst) register struct pstunit *pst; { register int i; register int n; register u_long tmp_ui; register u_long tmp_uf; register u_long date_ui; register u_long date_uf; u_fp delay; l_fp off[NPSTSAMPS]; extern void refclock_receive(); /* * Compute offsets from the raw data. Sort them into * ascending order. */ for (i = 0; i < NPSTSAMPS; i++) { tmp_ui = pst->reftimes[i].l_ui; tmp_uf = pst->reftimes[i].l_uf; M_SUB(tmp_ui, tmp_uf, pst->rectimes[i].l_ui, pst->rectimes[i].l_uf); for (n = i; n > 0; n--) { if (M_ISGEQ(tmp_ui, tmp_uf, off[n-1].l_ui, off[n-1].l_uf)) break; off[n] = off[n-1]; } off[n].l_ui = tmp_ui; off[n].l_uf = tmp_uf; } /* * Reject the furthest from the median until 8 samples left */ i = 0; n = NPSTSAMPS; while ((n - i) > 8) { tmp_ui = off[n-1].l_ui; tmp_uf = off[n-1].l_uf; date_ui = off[(n+i)/2].l_ui; date_uf = off[(n+i)/2].l_uf; M_SUB(tmp_ui, tmp_uf, date_ui, date_uf); M_SUB(date_ui, date_uf, off[i].l_ui, off[i].l_uf); if (M_ISHIS(date_ui, date_uf, tmp_ui, tmp_uf)) { /* * reject low end */ i++; } else { /* * reject high end */ n--; } } /* * Compute the delay based on the difference between the * extremes of the remaining offsets. */ tmp_ui = off[n-1].l_ui; tmp_uf = off[n-1].l_uf; M_SUB(tmp_ui, tmp_uf, off[i].l_ui, off[i].l_uf); delay = PSTBASEDELAY + MFPTOFP(tmp_ui, tmp_uf); /* * Now compute the offset estimate. If the sloppy clock * flag is set, average the remainder, otherwise pick the * median. */ if (sloppyclock[pst->unit]) { tmp_ui = tmp_uf = 0; while (i < n) { M_ADD(tmp_ui, tmp_uf, off[i].l_ui, off[i].l_uf); i++; } M_RSHIFT(tmp_ui, tmp_uf); M_RSHIFT(tmp_ui, tmp_uf); M_RSHIFT(tmp_ui, tmp_uf); i = 0; off[0].l_ui = tmp_ui; off[0].l_uf = tmp_uf; } else { i = (n+i)/2; } /* * Add the default PST QT delay into this. */ L_ADDUF(&off[i], PSTQTFUDGE); /* * Set the reference ID to the appropriate station */ if (stratumtouse[pst->unit] <= 1) { if (pst->station >= 0) bcopy(WWVREFID, (char *)&pst->peer->refid, 4); else bcopy(WWVHREFID, (char *)&pst->peer->refid, 4); } /* * Give the data to the reference clock support code */ refclock_receive(pst->peer, &off[i], delay, &pst->reftimes[NPSTSAMPS-1], &pst->rectimes[NPSTSAMPS-1], (pst->dontsync == 0)); /* * If the don't-sync flag isn't on, we're nominal. */ if (pst->dontsync == 0) pst_event(pst, CEVNT_NOMINAL); pst_reset(pst); } /* * pst_receive - receive data from a PST clock, call the appropriate * routine to process it, and advance the state. */ void pst_receive(rbufp) struct recvbuf *rbufp; { register struct pstunit *pst; register u_long tmp; void pst_process(); pst = (struct pstunit *)rbufp->recv_srcclock; /* * Process based on the current state. */ switch(pst->state) { case STATE_IDLE: return; /* Ignore the input */ case STATE_QV: if (!pst_QV_process(pst, rbufp)) { /* * Set the state to idle, but request another * QV poll. */ pst->badformat++; pst_event(pst, CEVNT_BADREPLY); pst->state = STATE_IDLE; pst->flags |= PST_DOQV; } else { /* * This went okay. Advance the state to * QM and send the request. */ pst->state = STATE_QM; pst_send(pst, "QM", 2); } return; case STATE_QM: if (!pst_QM_process(pst, rbufp)) { /* * Idle us and note the error */ pst->badformat++; pst_event(pst, CEVNT_BADREPLY); pst->state = STATE_IDLE; return; } if (pst->flags & PST_NOTIME) { /* * Here we aren't getting any time because the * clock is still searching. Don't bother * looking for anything. Remove any leap * second hold, however, since this should * ensure the clock is sensible. */ pst_event(pst, CEVNT_FAULT); pst->state = STATE_IDLE; pst->leapend = 0; if (pst->nextsample > 0) pst_reset(pst); /* Make sure rate low */ return; } /* * Next is QD. Do it. */ pst->state = STATE_QD; pst_send(pst, "QD", 2); return; case STATE_QD: if (!pst_QD_process(pst, rbufp)) { /* * Idle us and note the error */ pst->badformat++; pst_event(pst, CEVNT_BADDATE); pst->state = STATE_IDLE; } else { /* * Last step is QT. */ pst->state = STATE_QT; pst_send(pst, "QT", 2); } return; case STATE_QT: pst->state = STATE_IDLE; if (!pst_QT_process(pst, rbufp, &pst->lastref, &pst->lastrec)) { /* * Note the error */ pst->baddata++; pst_event(pst, CEVNT_BADTIME); return; } break; default: syslog(LOG_ERR, "pst clock: INTERNAL ERROR invalid state %d, unit %d, in receive", pst->state, pst->unit); return; } /* * You may not have noticed this, but the only way we end up * out here is if we've completed polling and have a couple of * valid time stamps. First see if we should reset the * structure. */ if (pst->nextsample > 0) { tmp = pst->lastrec.l_ui - pst->rectimes[0].l_ui; if (tmp > (u_long)psttab[pst->nextsample].tooold) pst_reset(pst); } pst->rectimes[pst->nextsample] = pst->lastrec; pst->reftimes[pst->nextsample] = pst->lastref; pst->nextsample++; if (pst->flags & PST_WWVH) pst->station--; else pst->station++; if (pst->flags & (PST_SIGFAULT|PST_HARDERR)) { pst_event(pst, CEVNT_FAULT); pst->dontsync++; } else if (pst->timesincesync > freerun[pst->unit]) { pst_event(pst, CEVNT_PROP); pst->dontsync++; } else if (pst->leapend > current_time) { pst->dontsync++; } if (pst->nextsample >= NPSTSAMPS) pst_process(pst); } /* * pst_leap - called when a leap second occurs */ void pst_leap() { register int i; /* * This routine should be entered a few seconds after * midnight UTC when a leap second occurs. To ensure we * don't get foolish time from the clock(s) we reset it * (them). We also set a 3 hour hold on each clock in * case the reset doesn't take, though this will be canceled * if the reset succeeds. */ for (i = 0; i < MAXUNITS; i++) { if (unitinuse[i]) { pstunits[i]->leapend = current_time + PSTLEAPHOLD; pstunits[i]->flags |= PST_DORESET; } } } /* * pst_compute_delay - compute appropriate things to tell clock about delays */ void pst_compute_delay(prop_delay, prop_data) u_long prop_delay; struct pst_propagate *prop_data; { register int code; register u_long tsf; extern int tsftomsu(); /* * Convert (truncate) the delay to milliseconds. Save the * characters needed to send this to the clock and compute * the remainder to be added in later. */ code = tsftomsu(prop_delay, 0); MSUTOTSF(code, tsf); prop_data->remainder = prop_delay - tsf; if (prop_data->remainder & 0x80000000) prop_data->remainder = 0; prop_data->msbchar = BINTOPST((code >> 2) & 0x1f); prop_data->lsbchar = BINTOPST(code & 0x3); } /* * pst_control - set fudge factors, return statistics */ void pst_control(unit, in, out) u_int unit; struct refclockstat *in; struct refclockstat *out; { register struct pstunit *pst; void pst_compute_delay(); if (unit >= MAXUNITS) { syslog(LOG_ERR, "pst clock: unit %d invalid (max %d)", unit, MAXUNITS-1); return; } if (in != 0) { int doqv = 0; if (in->haveflags & CLK_HAVETIME1) if (in->fudgetime1.l_ui == 0 && in->fudgetime1.l_uf <= PSTMAXPROP) { wwv_prop_delay[unit] = in->fudgetime1; doqv = 1; pst_compute_delay(wwv_prop_delay[unit].l_uf, &wwv_prop_data[unit]); } if (in->haveflags & CLK_HAVETIME2) if (in->fudgetime2.l_ui == 0 && in->fudgetime2.l_uf <= PSTMAXPROP) { wwvh_prop_delay[unit] = in->fudgetime2; doqv = 1; pst_compute_delay(wwvh_prop_delay[unit].l_uf, &wwvh_prop_data[unit]); } if (in->haveflags & CLK_HAVEVAL1) { stratumtouse[unit] = (u_char)(in->fudgeval1 & 0xf); } if (in->haveflags & CLK_HAVEVAL2) { if (in->fudgeval2 > 0 && in->fudgeval2 < 9990) freerun[unit] = (u_short)in->fudgeval2; } if (in->haveflags & CLK_HAVEFLAG1) { sloppyclock[unit] = in->flags & CLK_FLAG1; } if (unitinuse[unit]) { /* * Should actually reselect clock, but * will wait for the next timecode */ if (in->haveflags & CLK_HAVEVAL1) { pstunits[unit]->peer->stratum = stratumtouse[unit]; if (stratumtouse[unit] > 1) pstunits[unit]->peer->refid = htonl(PSTHSREFID); } if ((in->haveflags & CLK_HAVEFLAG3) && (in->flags & CLK_FLAG3)) { pstunits[unit]->flags |= PST_DORESET; } else if (doqv || ((in->haveflags & CLK_HAVEFLAG2) && (in->flags & CLK_FLAG2))) { pstunits[unit]->flags |= PST_DOQV; } } } if (out != 0) { out->type = REFCLK_WWV_PST; out->flags = 0; out->haveflags = CLK_HAVETIME1|CLK_HAVETIME2|CLK_HAVEVAL1| CLK_HAVEVAL2|CLK_HAVEFLAG1; out->fudgetime1 = wwv_prop_delay[unit]; out->fudgetime2 = wwvh_prop_delay[unit]; out->fudgeval1 = (long)stratumtouse[unit]; out->fudgeval2 = (long)freerun[unit]; out->flags = sloppyclock[unit]; if (unitinuse[unit]) { pst = pstunits[unit]; out->clockdesc = pst->description; out->lencode = pst->lencode; out->lastcode = pst->lastcode; out->timereset = current_time - pst->timestarted; out->polls = pst->polls; out->noresponse = pst->noreply; out->badformat = pst->badformat; out->baddata = pst->baddata; out->lastevent = pst->lastevent; out->currentstatus = pst->status; } else { out->clockdesc = pstdefdesc; out->lencode = 0; out->lastcode = ""; out->polls = out->noresponse = 0; out->badformat = out->baddata = 0; out->timereset = 0; out->currentstatus = out->lastevent = CEVNT_NOMINAL; } } } /* * pst_buginfo - return clock dependent debugging info */ void pst_buginfo(unit, bug) int unit; register struct refclockbug *bug; { register struct pstunit *pst; register int i; bug->nvalues = bug->ntimes = 0; if (unit >= MAXUNITS) { syslog(LOG_ERR, "pst clock: unit %d invalid (max %d)", unit, MAXUNITS-1); return; } if (!unitinuse[unit]) return; pst = pstunits[unit]; bug->nvalues = 14; bug->svalues = (1<<10); bug->values[0] = (u_long)pst->nextsample; bug->values[1] = (u_long)pst->state; bug->values[2] = (u_long)pst->reason; bug->values[3] = (u_long)pst->flags; bug->values[4] = (u_long)pst->yearday; bug->values[5] = (u_long)pst->hour; bug->values[6] = (u_long)pst->minute; bug->values[7] = (u_long)pst->second; bug->values[8] = (u_long)pst->millisecond; bug->values[9] = (u_long)pst->timezone; bug->values[10] = (u_long)((long)pst->tzoffset); bug->values[11] = (u_long)pst->timesincesync; bug->values[12] = pst->yearstart; if (pst->leapend > current_time) bug->values[13] = pst->leapend - current_time; else bug->values[13] = 0; bug->ntimes = ((NPSTSAMPS*2)+2) > NCLKBUGTIMES ? NCLKBUGTIMES : ((NPSTSAMPS*2)+2); bug->stimes = 0; for (i = 0; i < (bug->ntimes-2)/2; i++) { bug->times[2*i] = pst->rectimes[i]; bug->times[(2*i) + 1] = pst->reftimes[i]; } bug->times[bug->ntimes - 2] = pst->lastrec; bug->times[bug->ntimes - 1] = pst->lastref; } #endif