home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The World of Computer Software
/
World_Of_Computer_Software-02-385-Vol-1of3.iso
/
x
/
xntp3.zip
/
xntpd
/
refclock_pst.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-08-29
|
42KB
|
1,741 lines
/*
* refclock_pst - driver for the PSTI 1010/1020 WWV clock
*/
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#if defined(HPUX)
#include <termio.h>
#else
#include <sgtty.h>
#endif
#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 */
/*
* 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
#define PSTMAGIC2 ('\r' | 0x80) /* HP-UX uses this also now */
#ifdef CLKLDISC
#define PSTMAGIC1 '\r'
#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)
/*
* 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 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 */
u_char leap; /* leap indicators */
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 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->leap = 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];
#if defined(TCSETA) && !defined(ultrix)
struct termio ttyb;
#else
struct sgttyb ttyb;
#endif
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;
}
#if !defined(HPUX)
/*
* 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;
}
#endif
/*
* Set up file descriptor
* -> Those of us without CLKLDISC will have to do it cooked
*/
#if defined(HPUX)
if (ioctl(fd, TCGETA, (char *)&ttyb) < 0) {
syslog(LOG_ERR, "pst clock: ioctl(%s, TCGETA): %m", pstdev);
return 0;
}
ttyb.c_cflag = (PSTBAUD|CS8|CLOCAL|CREAD);
ttyb.c_iflag = ICRNL;
ttyb.c_oflag = 0;
ttyb.c_lflag = ICANON;
ttyb.c_cc[VINTR] = '\000';
ttyb.c_cc[VQUIT] = '\000';
ttyb.c_cc[VERASE] = '\000';
ttyb.c_cc[VKILL] = '\000';
ttyb.c_cc[VEOF] = '\000';
ttyb.c_cc[VEOL] = PSTMAGIC2;
ttyb.c_cc[VSWTCH] = '\000';
if (ioctl(fd, TCSETA, (char *)&ttyb) < 0) {
syslog(LOG_ERR, "pst clock: ioctl(%s, TCSETA): %m", pstdev);
return 0;
}
#else
ttyb.sg_ispeed = ttyb.sg_ospeed = PSTBAUD;
#ifdef CLKLDISC
/*
* Set to raw mode
*/
ttyb.sg_erase = PSTMAGIC1;
ttyb.sg_kill = PSTMAGIC2;
ttyb.sg_flags = EVENP|ODDP|RAW|CRMOD;
#else
/*
* Does this really work ?
*/
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;
}
#endif
/*
* 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
#if defined(HPUX)
if (ioctl(fd, TCFLSH, 2) < 0) {
syslog(LOG_ERR, "pst clock: ioctl(%s, TCFLSH): %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
#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->precision = PSTPRECISION;
peer->rootdelay = 0;
peer->rootdispersion = 0;
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 (*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 (*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 (*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 (*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;
s_fp delay;
u_fp dispersion;
int leap;
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 dispersion 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);
dispersion = 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], 0, dispersion, &pst->reftimes[NPSTSAMPS-1],
&pst->rectimes[NPSTSAMPS-1], pst->leap);
/*
* If the don't-sync flag isn't on, we're nominal.
*/
if (pst->leap == 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;
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->leap = LEAP_NOTINSYNC;
} else if (pst->timesincesync > freerun[pst->unit]) {
pst_event(pst, CEVNT_PROP);
pst->leap = LEAP_NOTINSYNC;
}
if (pst->nextsample >= NPSTSAMPS)
pst_process(pst);
}
/*
* 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;
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