diff options
Diffstat (limited to 'usr.sbin/xntpd/xntpd/refclock_chu.c')
-rw-r--r-- | usr.sbin/xntpd/xntpd/refclock_chu.c | 1135 |
1 files changed, 1135 insertions, 0 deletions
diff --git a/usr.sbin/xntpd/xntpd/refclock_chu.c b/usr.sbin/xntpd/xntpd/refclock_chu.c new file mode 100644 index 0000000..05379b6 --- /dev/null +++ b/usr.sbin/xntpd/xntpd/refclock_chu.c @@ -0,0 +1,1135 @@ +/* + * refclock_chu - clock driver for the CHU time code + */ +#if defined(REFCLOCK) && (defined(CHU) || defined(CHUCLK) || defined(CHUPPS)) + +#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" + +#if defined(HAVE_BSD_TTYS) +#include <sgtty.h> +#endif /* HAVE_BSD_TTYS */ + +#if defined(HAVE_SYSV_TTYS) +#include <termio.h> +#endif /* HAVE_SYSV_TTYS */ + +#if defined(STREAM) +#include <termios.h> +#include <stropts.h> +#endif /* STREAM */ + +#if defined (CHUPPS) +#include <sys/ppsclock.h> +#endif /* CHUPPS */ + +#include <sys/chudefs.h> + +#include "ntp_stdlib.h" + +/* + * The CHU time signal includes a time code which is modulated at the + * standard Bell 103 frequencies (i.e. mark=2225Hz, space=2025Hz). + * and formatted into 8 bit characters with one start bit and two + * stop bits. The time code is composed of 10 8-bit characters. + * The second 5 bytes of the timecode are a redundancy check, and + * are a copy of the first 5 bytes. + * + * It is assumed that you have built or modified a Bell 103 standard + * modem, attached the input to the output of a radio and cabled the + * output to a serial port on your computer, i.e. what you are receiving + * is essentially the output of your radio. It is also assumed you have + * installed a special CHU line discipline to condition the output from + * the terminal driver and take accurate time stamps. + * + * There are two types of timecodes. One is sent in the 32nd + * through 39th second of the minute. + * + * 6dddhhmmss6dddhhmmss + * + * where ddd is the day of the year, hh is the hour (in UTC), mm is + * the minute and ss the second. The 6 is a constant. Note that + * the code is sent twice. + * + * The second sort of timecode is sent only during the 31st second + * past the minute. + * + * xdyyyyttabXDYYYYTTAB + * + * In this case, the second part of the code is the one's complement + * of the code. This differentiates it from the other timecode + * format. + * + * d is the absolute value of DUT (in tenths of a second). yyyy + * is the year. tt is the difference between UTC and TAI. a is + * a canadian daylight time flag and b is a serial number. + * x is a bitwise field. The least significant bit of x is + * one if DUT is negative. The 2nd bit is set if a leap second + * will be added at the next opportunity. The 3rd bit is set if + * a leap second will be deleted at the next opportunity. + * The 4th bit is an even parity bit for the other three bits + * in this nibble. + * + * The start bit in each character has a precise relationship to + * the on-time second. Most often UART's synchronize themselves to the + * start bit and will post an interrupt at the center of the first stop + * bit. Thus each character's interrupt should occur at a fixed offset + * from the on-time second. This means that a timestamp taken at the + * arrival of each character in the code will provide an independent + * estimate of the offset. Since there are 10 characters in the time + * code and the code is sent 9 times per minute, this means you potentially + * get 90 offset samples per minute. Much of the code in here is dedicated + * to producing a single offset estimate from these samples. + * + * A note about the line discipline. It is possible to receive the + * CHU time code in raw mode, but this has disadvantages. In particular, + * this puts a lot of code between the interrupt and the time you freeze + * a time stamp, decreasing precision. It is also expensive in terms of + * context switches, and made even more expensive by the way I do I/O. + * Worse, since you are listening directly to the output of your radio, + * CHU is noisy and will make you spend a lot of time receiving noise. + * + * The line discipline fixes a lot of this. It knows that the CHU time + * code consists of 10 bytes which arrive with an intercharacter + * spacing of about 37 ms, and that the data is BCD, and filters on this + * basis. It delivers block of ten characters plus their associated time + * stamps all at once. The time stamps are hence about as accurate as + * a Unix machine can get them, and much of the noise disappears in the + * kernel with no context switching cost. + * + * The kernel module also will insure that the packets that are + * delivered have the correct redundancy bytes, and will return + * a flag in chutype to differentiate one sort of packet from + * the other. + */ + +/* + * Definitions + */ +#define MAXUNITS 4 /* maximum number of CHU units permitted */ +#define CHUDEV "/dev/chu%d" /* device we open. %d is unit number */ +#define SPEED232 B300 /* uart speed (300 baud) */ +#define NCHUCODES 8 /* expect 8 CHU codes per minute */ +#define CHULDISC 10 /* XXX temp CHU line discipline */ + +/* + * To compute a quality for the estimate (a pseudo dispersion) we add a + * fixed 10 ms for each missing code in the minute and add to this + * the sum of the differences between the remaining offsets and the + * estimated sample offset. + */ +#define CHUDELAYPENALTY 0x0000028f + +/* + * Other constant stuff + */ +#define CHUPRECISION (-9) /* what the heck */ +#define CHUREFID "CHU\0" +#define CHUDESCRIPTION "Direct synchronized to CHU timecode" +#define CHUHSREFID 0x7f7f070a /* 127.127.7.10 refid for hi strata */ + +/* + * Default fudge factors + */ +#define DEFPROPDELAY 0x00624dd3 /* 0.0015 seconds, 1.5 ms */ +#define DEFFILTFUDGE 0x000d1b71 /* 0.0002 seconds, 200 us */ + +/* + * Hacks to avoid excercising the multiplier. I have no pride. + */ +#define MULBY10(x) (((x)<<3) + ((x)<<1)) +#define MULBY60(x) (((x)<<6) - ((x)<<2)) /* watch overflow */ +#define MULBY24(x) (((x)<<4) + ((x)<<3)) + +/* + * Constants for use when multiplying by 0.1. ZEROPTONE is 0.1 + * as an l_fp fraction, NZPOBITS is the number of significant bits + * in ZEROPTONE. + */ +#define ZEROPTONE 0x1999999a +#define NZPOBITS 29 + +static char hexstring[]="0123456789abcdef"; + +/* + * CHU unit control structure. + */ +struct chuunit { + struct peer *peer; /* associated peer structure */ + struct event chutimer; /* timeout timer structure */ + struct refclockio chuio; /* given to the I/O handler */ + l_fp offsets[NCHUCODES]; /* offsets computed from each code */ + l_fp rectimes[NCHUCODES]; /* times we received this stuff */ + U_LONG reftimes[NCHUCODES]; /* time of last code received */ + u_char lastcode[NCHUCHARS*4]; /* last code we received */ + u_char asciicode[NCHUCHARS*4+1]; /* last code translated to ascii */ + u_char expect; /* the next offset expected */ + u_char unit; /* unit number for this guy */ + u_short haveoffset; /* flag word indicating valid offsets */ + u_short flags; /* operational flags */ + u_char status; /* clock status */ + u_char lastevent; /* last clock event */ + u_char unused[2]; + U_LONG lastupdate; /* last time data received */ + U_LONG responses; /* number of responses */ + U_LONG badformat; /* number of bad format responses */ + U_LONG baddata; /* number of invalid time codes */ + U_LONG timestarted; /* time we started this */ + u_char leap; /* leap status */ +}; + +#define CHUTIMERSET 0x1 /* timer is set to fire */ + + +/* + * The CHU table. This gives the expected time of arrival of each + * character after the on-time second and is computed as follows: + * The CHU time code is sent at 300 bps. Your average UART will + * synchronize at the edge of the start bit and will consider the + * character complete at the middle of the first stop bit, i.e. + * 0.031667 ms later (some UARTS may complete the character at the + * end of the stop bit instead of the middle, but you can fudge this). + * Thus the expected time of each interrupt is the start bit time plus + * 0.031667 seconds. These times are in chutable[]. To this we add + * such things as propagation delay and delay fudge factor. + */ +#define CHARDELAY 0x081b4e82 + +static U_LONG chutable[NCHUCHARS] = { + 0x22222222 + CHARDELAY, /* 0.1333333333 */ + 0x2b851eb8 + CHARDELAY, /* 0.170 (exactly) */ + 0x34e81b4e + CHARDELAY, /* 0.2066666667 */ + 0x3f92c5f9 + CHARDELAY, /* 0.2483333333 */ + 0x47ae147b + CHARDELAY, /* 0.280 (exactly) */ + 0x51111111 + CHARDELAY, /* 0.3166666667 */ + 0x5a740da7 + CHARDELAY, /* 0.3533333333 */ + 0x63d70a3d + CHARDELAY, /* 0.390 (exactly) */ + 0x6d3a06d4 + CHARDELAY, /* 0.4266666667 */ + 0x769d0370 + CHARDELAY, /* 0.4633333333 */ +}; + +/* + * Data space for the unit structures. Note that we allocate these on + * the fly, but never give them back. + */ +static struct chuunit *chuunits[MAXUNITS]; +static u_char unitinuse[MAXUNITS]; + +/* + * Keep the fudge factors separately so they can be set even + * when no clock is configured. + */ +static l_fp propagation_delay[MAXUNITS]; +static l_fp fudgefactor[MAXUNITS]; +static l_fp offset_fudge[MAXUNITS]; +static u_char stratumtouse[MAXUNITS]; +static u_char sloppyclockflag[MAXUNITS]; + +/* + * We keep track of the start of the year, watching for changes. + * We also keep track of whether the year is a leap year or not. + * All because stupid CHU doesn't include the year in the time code. + */ +static U_LONG yearstart; + +/* + * Imported from the timer module + */ +extern U_LONG current_time; +extern struct event timerqueue[]; + +/* + * Imported from ntp_loopfilter module + */ +extern int fdpps; /* pps file descriptor */ + +/* + * Imported from ntpd module + */ +extern int debug; /* global debug flag */ + +/* + * Event reporting. This optimizes things a little. + */ +#define chu_event(chu, evcode) \ + do { \ + if ((chu)->status != (u_char)(evcode)) \ + chu_report_event((chu), (evcode)); \ + } while (0) + +static void chu_init P((void)); +static int chu_start P((u_int, struct peer *)); +static void chu_shutdown P((int)); +static void chu_report_event P((struct chuunit *, int)); +static void chu_receive P((struct recvbuf *)); +static void chu_process P((struct chuunit *)); +static void chu_poll P((int, struct peer *)); +static void chu_control P((u_int, struct refclockstat *, struct refclockstat *)); +static void chu_timeout P((struct peer *)); + +/* + * Transfer vector + */ +struct refclock refclock_chu = { + chu_start, chu_shutdown, chu_poll, + chu_control, chu_init, noentry, NOFLAGS +}; + +/* + * chu_init - initialize internal chu driver data + */ +static void +chu_init() +{ + register int i; + /* + * Just zero the data arrays + */ + bzero((char *)chuunits, sizeof chuunits); + bzero((char *)unitinuse, sizeof unitinuse); + + /* + * Initialize fudge factors to default. + */ + for (i = 0; i < MAXUNITS; i++) { + propagation_delay[i].l_ui = 0; + propagation_delay[i].l_uf = DEFPROPDELAY; + fudgefactor[i].l_ui = 0; + fudgefactor[i].l_uf = DEFFILTFUDGE; + offset_fudge[i] = propagation_delay[i]; + L_ADD(&offset_fudge[i], &fudgefactor[i]); + stratumtouse[i] = 0; + sloppyclockflag[i] = 0; + } +} + + +/* + * chu_start - open the CHU device and initialize data for processing + */ +static int +chu_start(unit, peer) + u_int unit; + struct peer *peer; +{ + register struct chuunit *chu; + register int i; + int fd232; + char chudev[20]; + l_fp ts; + + /* + * Check configuration info + */ + if (unit >= MAXUNITS) { + syslog(LOG_ERR, "chu_start: unit %d invalid", unit); + return 0; + } + if (unitinuse[unit]) { + syslog(LOG_ERR, "chu_start: unit %d in use", unit); + return 0; + } + + /* + * Open serial port + */ + (void) sprintf(chudev, CHUDEV, unit); + fd232 = open(chudev, O_RDONLY, 0777); + if (fd232 == -1) { + syslog(LOG_ERR, "chu_start: open of %s: %m", chudev); + return 0; + } + +#if defined(HAVE_SYSV_TTYS) + /* + * System V serial line parameters (termio interface) + */ + CHU SUPPORT NOT AVAILABLE IN TERMIO INTERFACE +#endif /* HAVE_SYSV_TTYS */ +#if defined(STREAM) + /* + * POSIX/STREAMS serial line parameters (termios interface) + * + * The CHUCLK support uses a 300-baud modem and level converter + * (gadget box). It requires the chu_clk streams module and + * SunOS 4.1.1 or later. + * + * The CHUPPS option provides timestamping at the driver level. + * It uses a 1-pps signal and level converter (gadget box) and + * requires the ppsclock streams module and SunOS 4.1.1 or + * later. + */ + { struct termios ttyb, *ttyp; + + ttyp = &ttyb; + if (tcgetattr(fd232, ttyp) < 0) { + syslog(LOG_ERR, + "chu_start: tcgetattr(%s): %m", chudev); + goto screwed; + } + ttyp->c_iflag = IGNBRK|IGNPAR; + ttyp->c_oflag = 0; + ttyp->c_cflag = SPEED232|CS8|CLOCAL|CREAD; + ttyp->c_lflag = 0; + ttyp->c_cc[VERASE] = ttyp->c_cc[VKILL] = '\0'; + ttyp->c_cc[VMIN] = 1; + ttyp->c_cc[VTIME] = 0; + if (tcsetattr(fd232, TCSANOW, ttyp) < 0) { + syslog(LOG_ERR, + "chu_start: tcsetattr(%s): %m", chudev); + goto screwed; + } + if (tcflush(fd232, TCIOFLUSH) < 0) { + syslog(LOG_ERR, + "chu_start: tcflush(%s): %m", chudev); + goto screwed; + } + while (ioctl(fd232, I_POP, 0 ) >= 0) ; + if (ioctl(fd232, I_PUSH, "chu" ) < 0) { + syslog(LOG_ERR, + "chu_start: ioctl(%s, I_PUSH, chu): %m", chudev); + goto screwed; + } +#if defined(CHUPPS) + if (ioctl(fd232, I_PUSH, "ppsclock") < 0) + syslog(LOG_ERR, + "chu_start: ioctl(%s, I_PUSH, ppsclock): %m", chudev); + else + fdpps = fd232; +#endif /* CHUPPS */ + } +#endif /* STREAM */ +#if defined(HAVE_BSD_TTYS) + /* + * 4.3bsd serial line parameters (sgttyb interface) + * + * The CHUCLK support uses a 300-baud modem and level converter + * (gadget box). It requires the chu_clk streams module and + * 4.3bsd or later. + */ + { struct sgttyb ttyb; + int ldisc = CHULDISC; + + if (ioctl(fd232, TIOCGETP, &ttyb) < 0) { + syslog(LOG_ERR, + "chu_start: ioctl(%s, TIOCGETP): %m", chudev); + goto screwed; + } + ttyb.sg_ispeed = ttyb.sg_ospeed = SPEED232; + ttyb.sg_erase = ttyb.sg_kill = '\r'; + ttyb.sg_flags = RAW; + if (ioctl(fd232, TIOCSETP, &ttyb) < 0) { + syslog(LOG_ERR, + "chu_start: ioctl(%s, TIOCSETP): %m", chudev); + goto screwed; + } + if (ioctl(fd232, TIOCSETD, &ldisc) < 0) { + syslog(LOG_ERR, + "chu_start: ioctl(%s, TIOCSETD): %m",chudev); + goto screwed; + } + } +#endif /* HAVE_BSD_TTYS */ + + /* + * Allocate unit structure + */ + if (chuunits[unit] != 0) { + chu = chuunits[unit]; /* The one we want is okay */ + } else { + for (i = 0; i < MAXUNITS; i++) { + if (!unitinuse[i] && chuunits[i] != 0) + break; + } + if (i < MAXUNITS) { + /* + * Reclaim this one + */ + chu = chuunits[i]; + chuunits[i] = 0; + } else { + chu = (struct chuunit *)emalloc(sizeof(struct chuunit)); + } + } + bzero((char *)chu, sizeof(struct chuunit)); + chuunits[unit] = chu; + + /* + * Set up the structure + */ + chu->peer = peer; + chu->unit = (u_char)unit; + chu->timestarted = current_time; + + chu->chutimer.peer = (struct peer *)chu; + chu->chutimer.event_handler = chu_timeout; + + chu->chuio.clock_recv = chu_receive; + chu->chuio.srcclock = (caddr_t)chu; + chu->chuio.datalen = sizeof(struct chucode); + chu->chuio.fd = fd232; + + /* + * Initialize the year from the system time in case this is the + * first open. + */ + get_systime(&ts); + yearstart = calyearstart(ts.l_ui); + if (!io_addclock(&chu->chuio)) { + goto screwed; + } + + /* + * All done. Initialize a few random peer variables, then + * return success. + */ + peer->precision = CHUPRECISION; + peer->rootdelay = 0; + peer->rootdispersion = 0; + peer->stratum = stratumtouse[unit]; + if (stratumtouse[unit] <= 1) + bcopy(CHUREFID, (char *)&peer->refid, 4); + else + peer->refid = htonl(CHUHSREFID); + unitinuse[unit] = 1; + return 1; + + /* + * Something broke; abandon ship. + */ +screwed: + (void) close(fd232); + return (0); +} + + +/* + * chu_shutdown - shut down a CHU clock + */ +static void +chu_shutdown(unit) + int unit; +{ + register struct chuunit *chu; + + if (unit >= MAXUNITS) { + syslog(LOG_ERR, "chu_shutdown: unit %d invalid", unit); + return; + } + if (!unitinuse[unit]) { + syslog(LOG_ERR, "chu_shutdown: unit %d not in use", unit); + return; + } + + /* + * Tell the I/O module to turn us off, and dequeue timer + * if any. We're history. + */ + chu = chuunits[unit]; + if (chu->flags & CHUTIMERSET) + TIMER_DEQUEUE(&chu->chutimer); + io_closeclock(&chu->chuio); + unitinuse[unit] = 0; +} + +/* + * chu_report_event - record an event and report it + */ +static void +chu_report_event(chu, code) + struct chuunit *chu; + int code; +{ + /* + * Trap support isn't up to handling this, so just + * record it. + */ + if (chu->status != (u_char)code) { + chu->status = (u_char)code; + if (code != CEVNT_NOMINAL) + chu->lastevent = (u_char)code; + } +} + + +/* + * chu_receive - receive data from a CHU clock, do format checks and compute + * an estimate from the sample data + */ +static void +chu_receive(rbufp) + struct recvbuf *rbufp; +{ + register int i; + register U_LONG date_ui; + register U_LONG tmp; + register u_char *code; + register struct chuunit *chu; + register struct chucode *chuc; + int isneg; + U_LONG reftime; + l_fp off[NCHUCHARS]; + int day, hour, minute, second; + + /* + * Do a length check on the data. Should be what we asked for. + */ + if (rbufp->recv_length != sizeof(struct chucode)) { + syslog(LOG_ERR, "chu_receive: received %d bytes, expected %d", + rbufp->recv_length, sizeof(struct chucode)); + return; + } + + /* + * Get the clock this applies to and a pointer to the data + */ + chu = (struct chuunit *)rbufp->recv_srcclock; + chuc = (struct chucode *)&rbufp->recv_space; + chu->responses++; + chu->lastupdate = current_time; + + /* + * At this point we're assured that both halves of the + * data match because of what the kernel has done. + * But there's more than one data format. We need to + * check chutype to see what to do now. If it's a + * year packet, then we fiddle with it specially. + */ + + if (chuc->chutype == CHU_YEAR) + { + u_char leapbits,parity; + + /* + * Break out the code into the BCD nibbles. + * Put it in the half of lastcode. + */ + code = chu->lastcode; + code += 2*NCHUCHARS; + for (i = 0; i < NCHUCHARS; i++) { + *code++ = chuc->codechars[i] & 0xf; + *code++ = (chuc->codechars[i] >> 4) & 0xf; + } + + leapbits = chuc->codechars[0]&0xf; + + /* + * Now make sure that the leap nibble + * is even parity. + */ + + parity = (leapbits ^ (leapbits >> 2))&0x3; + parity = (parity ^ (parity>>1))&0x1; + if (parity) + { + chu->badformat++; + chu_event(chu, CEVNT_BADREPLY); + return; + } + + /* + * This just happens to work. :-) + */ + + chu->leap = (leapbits >> 1) & 0x3; + + return; + } + + if (chuc->chutype != CHU_TIME) + { + chu->badformat++; + chu_event(chu, CEVNT_BADREPLY); + return; + } + + /* + * Break out the code into the BCD nibbles. Only need to fiddle + * with the first half since both are identical. Note the first + * BCD character is the low order nibble, the second the high order. + */ + code = chu->lastcode; + for (i = 0; i < NCHUCHARS; i++) { + *code++ = chuc->codechars[i] & 0xf; + *code++ = (chuc->codechars[i] >> 4) & 0xf; + } + + /* + * Format check. Make sure the two halves match. + * There's really no need for this, but it can't hurt. + */ + for (i = 0; i < NCHUCHARS/2; i++) + if (chuc->codechars[i] != chuc->codechars[i+(NCHUCHARS/2)]) { + chu->badformat++; + chu_event(chu, CEVNT_BADREPLY); + return; + } + + /* + * If the first nibble isn't a 6, we're up the creek + */ + code = chu->lastcode; + if (*code++ != 6) { + chu->badformat++; + chu_event(chu, CEVNT_BADREPLY); + return; + } + + /* + * Collect the day, the hour, the minute and the second. + */ + day = *code++; + day = MULBY10(day) + *code++; + day = MULBY10(day) + *code++; + hour = *code++; + hour = MULBY10(hour) + *code++; + minute = *code++; + minute = MULBY10(minute) + *code++; + second = *code++; + second = MULBY10(second) + *code++; + + /* + * Sanity check the day and time. Note that this + * only occurs on the 32st through the 39th second + * of the minute. + */ + if (day < 1 || day > 366 + || hour > 23 || minute > 59 + || second < 32 || second > 39) { + chu->baddata++; + if (day < 1 || day > 366) { + chu_event(chu, CEVNT_BADDATE); + } else { + chu_event(chu, CEVNT_BADTIME); + } + return; + } + + /* + * Compute the NTP date from the input data and the + * receive timestamp. If this doesn't work, mark the + * date as bad and forget it. + */ + if (!clocktime(day, hour, minute, second, 0, + rbufp->recv_time.l_ui, &yearstart, &reftime)) { + chu_event(chu, CEVNT_BADDATE); + return; + } + date_ui = reftime;; + + /* + * We've now got the integral seconds part of the time code (we hope). + * The fractional part comes from the table. We next compute + * the offsets for each character. + */ + for (i = 0; i < NCHUCHARS; i++) { + register U_LONG tmp2; + + off[i].l_ui = date_ui; + off[i].l_uf = chutable[i]; + tmp = chuc->codetimes[i].tv_sec + JAN_1970; + TVUTOTSF(chuc->codetimes[i].tv_usec, tmp2); + M_SUB(off[i].l_ui, off[i].l_uf, tmp, tmp2); + } + + if (!sloppyclockflag[chu->unit]) { + u_short ord[NCHUCHARS]; + /* + * In here we assume the clock has adequate bits + * to take timestamps with reasonable accuracy. + * Note that the time stamps may contain errors + * for a couple of reasons. Timing is actually + * referenced to the start bit in each character + * in the time code. If this is obscured by static + * you can still get a valid character but have the + * timestamp offset by +-1.5 ms. Also, we may suffer + * from interrupt delays if the interrupt is being + * held off when the character arrives. Note the + * latter error is always in the form of a delay. + * + * After fiddling I arrived at the following scheme. + * We sort the times into order by offset. We then + * drop the most positive 2 offset values (which may + * correspond to a character arriving early due to + * static) and the most negative 4 (which may correspond + * to delayed characters, either from static or from + * interrupt latency). We then take the mean of the + * remaining 4 offsets as our estimate. + */ + + /* + * Set up the order array. + */ + for (i = 0; i < NCHUCHARS; i++) + ord[i] = (u_short)i; + + /* + * Sort them into order. Reuse variables with abandon. + */ + for (tmp = 0; tmp < (NCHUCHARS-1); tmp++) { + for (i = (int)tmp+1; i < NCHUCHARS; i++) { + if (!L_ISGEQ(&off[ord[i]], &off[ord[tmp]])) { + date_ui = (U_LONG)ord[i]; + ord[i] = ord[tmp]; + ord[tmp] = (u_short)date_ui; + } + } + } + + /* + * Done the sort. We drop 0, 1, 2 and 3 at the negative + * end, and 8 and 9 at the positive. Take the sum of + * 4, 5, 6 and 7. + */ + date_ui = off[ord[4]].l_ui; + tmp = off[ord[4]].l_uf; + for (i = 5; i <= 7; i++) + M_ADD(date_ui, tmp, off[ord[i]].l_ui, off[ord[i]].l_uf); + + /* + * Round properly, then right shift two bits for the + * divide by four. + */ + if (tmp & 0x2) + M_ADDUF(date_ui, tmp, 0x4); + M_RSHIFT(date_ui, tmp); + M_RSHIFT(date_ui, tmp); + } else { + /* + * Here is a *big* problem. On a machine where the + * low order bit in the clock is on the order of half + * a millisecond or more we don't really have enough + * precision to make intelligent choices about which + * samples might be in error and which aren't. More + * than this, in the case of error free data we can + * pick up a few bits of precision by taking the mean + * of the whole bunch. This is what we do. The problem + * comes when it comes time to divide the 64 bit sum of + * the 10 samples by 10, a procedure which really sucks. + * Oh, well, grin and bear it. Compute the sum first. + */ + date_ui = 0; + tmp = 0; + for (i = 0; i < NCHUCHARS; i++) + M_ADD(date_ui, tmp, off[i].l_ui, off[i].l_uf); + if (M_ISNEG(date_ui, tmp)) + isneg = 1; + else + isneg = 0; + + /* + * Here is a multiply-by-0.1 optimization that should apply + * just about everywhere. If the magnitude of the sum + * is less than 9 we don't have to worry about overflow + * out of a 64 bit product, even after rounding. + */ + if (date_ui < 9 || date_ui > 0xfffffff7) { + register U_LONG prod_ui; + register U_LONG prod_uf; + + prod_ui = prod_uf = 0; + /* + * This code knows the low order bit in 0.1 is zero + */ + for (i = 1; i < NZPOBITS; i++) { + M_LSHIFT(date_ui, tmp); + if (ZEROPTONE & (1<<i)) + M_ADD(prod_ui, prod_uf, date_ui, tmp); + } + + /* + * Done, round it correctly. Prod_ui contains the + * fraction. + */ + if (prod_uf & 0x80000000) + prod_ui++; + if (isneg) + date_ui = 0xffffffff; + else + date_ui = 0; + tmp = prod_ui; + /* + * date_ui is integral part, tmp is fraction. + */ + } else { + register U_LONG prod_ovr; + register U_LONG prod_ui; + register U_LONG prod_uf; + register U_LONG highbits; + + prod_ovr = prod_ui = prod_uf = 0; + if (isneg) + highbits = 0xffffffff; /* sign extend */ + else + highbits = 0; + /* + * This code knows the low order bit in 0.1 is zero + */ + for (i = 1; i < NZPOBITS; i++) { + M_LSHIFT3(highbits, date_ui, tmp); + if (ZEROPTONE & (1<<i)) + M_ADD3(prod_ovr, prod_uf, prod_ui, + highbits, date_ui, tmp); + } + + if (prod_uf & 0x80000000) + M_ADDUF(prod_ovr, prod_ui, (U_LONG)1); + date_ui = prod_ovr; + tmp = prod_ui; + } + } + + /* + * At this point we have the mean offset, with the integral + * part in date_ui and the fractional part in tmp. Store + * it in the structure. + */ + i = second - 32; /* gives a value 0 through 8 */ + if (i < (int)chu->expect) { + /* + * This shouldn't actually happen, but might if a single + * bit error occurred in the code which fooled us. + * Throw away all previous data. + */ + chu->expect = 0; + chu->haveoffset = 0; + if (chu->flags & CHUTIMERSET) { + TIMER_DEQUEUE(&chu->chutimer); + chu->flags &= ~CHUTIMERSET; + } + } + + /* + * Add in fudge factor. + */ + M_ADD(date_ui, tmp, offset_fudge[chu->unit].l_ui, + offset_fudge[chu->unit].l_uf); + + chu->offsets[i].l_ui = date_ui; + chu->offsets[i].l_uf = tmp; + chu->rectimes[i] = rbufp->recv_time; + chu->reftimes[i] = reftime; + + chu->expect = i + 1; + chu->haveoffset |= (1<<i); + + if (chu->expect >= NCHUCODES) { + /* + * Got a full second's worth. Dequeue timer and + * process this. + */ + if (chu->flags & CHUTIMERSET) { + TIMER_DEQUEUE(&chu->chutimer); + chu->flags &= ~CHUTIMERSET; + } + chu_process(chu); + } else if (!(chu->flags & CHUTIMERSET)) { + /* + * Try to take an interrupt sometime after the + * 42 second mark (leaves an extra 2 seconds for + * slop). Round it up to an even multiple of + * 4 seconds. + */ + chu->chutimer.event_time = + current_time + (U_LONG)(10 - i) + (1<<EVENT_TIMEOUT); + chu->chutimer.event_time &= ~((1<<EVENT_TIMEOUT) - 1); + TIMER_INSERT(timerqueue, &chu->chutimer); + chu->flags |= CHUTIMERSET; + } +} + + +/* + * chu_timeout - process a timeout event + */ +static void +chu_timeout(fakepeer) + struct peer *fakepeer; +{ + /* + * If we got here it means we received some time codes + * but didn't get the one which should have arrived on + * the 39th second. Process what we have. + */ + ((struct chuunit *)fakepeer)->flags &= ~CHUTIMERSET; + chu_process((struct chuunit *)fakepeer); +} + + +/* + * chu_process - process the raw offset estimates we have and pass + * the results on to the NTP clock filters. + */ +static void +chu_process(chu) + register struct chuunit *chu; +{ + register int i; + register s_fp bestoff; + register s_fp tmpoff; + u_fp dispersion; + int imax; + l_fp ts; + + /* + * The most positive offset. + */ + imax = NCHUCODES; + for (i = 0; i < NCHUCODES; i++) + if (chu->haveoffset & (1<<i)) + if (i < imax || L_ISGEQ(&chu->offsets[i], + &chu->offsets[imax])) + imax = i; + + /* + * The most positive estimate is our best bet. Go through + * the list again computing the dispersion. + */ + bestoff = LFPTOFP(&chu->offsets[imax]); + dispersion = 0; + for (i = 0; i < NCHUCODES; i++) { + if (chu->haveoffset & (1<<i)) { + tmpoff = LFPTOFP(&chu->offsets[i]); + dispersion += (bestoff - tmpoff); + } else { + dispersion += CHUDELAYPENALTY; + } + } + + /* + * Make up a reference time stamp, then give it to the + * reference clock support code for further processing. + */ + ts.l_ui = chu->reftimes[imax]; + ts.l_uf = chutable[NCHUCHARS-1]; + + for (i = 0; i < NCHUCHARS*4; i++) { + chu->asciicode[i] = hexstring[chu->lastcode[i]]; + } + chu->asciicode[i] = '\0'; + record_clock_stats(&(chu->peer->srcadr), chu->asciicode); + refclock_receive(chu->peer, &chu->offsets[imax], 0, + dispersion, &ts, &chu->rectimes[imax], chu->leap); + + /* + * Zero out unit for next code series + */ + chu->haveoffset = 0; + chu->expect = 0; + chu_event(chu, CEVNT_NOMINAL); +} + + +/* + * chu_poll - called by the transmit procedure + */ +static void +chu_poll(unit, peer) + int unit; + struct peer *peer; +{ + if (unit >= MAXUNITS) { + syslog(LOG_ERR, "chu_poll: unit %d invalid", unit); + return; + } + if (!unitinuse[unit]) { + syslog(LOG_ERR, "chu_poll: unit %d not in use", unit); + return; + } + + if ((current_time - chuunits[unit]->lastupdate) > 150) { + chu_event(chuunits[unit], CEVNT_PROP); + } +} + + + +/* + * chu_control - set fudge factors, return statistics + */ +static void +chu_control(unit, in, out) + u_int unit; + struct refclockstat *in; + struct refclockstat *out; +{ + register struct chuunit *chu; + U_LONG npolls; + + if (unit >= MAXUNITS) { + syslog(LOG_ERR, "chu_control: unit %d invalid", unit); + return; + } + + if (in != 0) { + if (in->haveflags & CLK_HAVETIME1) + propagation_delay[unit] = in->fudgetime1; + if (in->haveflags & CLK_HAVETIME2) + fudgefactor[unit] = in->fudgetime2; + offset_fudge[unit] = propagation_delay[unit]; + L_ADD(&offset_fudge[unit], &fudgefactor[unit]); + if (in->haveflags & CLK_HAVEVAL1) { + stratumtouse[unit] = (u_char)(in->fudgeval1 & 0xf); + if (unitinuse[unit]) { + struct peer *peer; + + /* + * Should actually reselect clock, but + * will wait for the next timecode + */ + peer = chuunits[unit]->peer; + peer->stratum = stratumtouse[unit]; + if (stratumtouse[unit] <= 1) + bcopy(CHUREFID, (char *)&peer->refid,4); + else + peer->refid = htonl(CHUHSREFID); + } + } + if (in->haveflags & CLK_HAVEFLAG1) { + sloppyclockflag[unit] = in->flags & CLK_FLAG1; + } + } + + if (out != 0) { + out->type = REFCLK_CHU; + out->flags = 0; + out->haveflags + = CLK_HAVETIME1|CLK_HAVETIME2|CLK_HAVEVAL1| + CLK_HAVEVAL2|CLK_HAVEFLAG1; + out->clockdesc = CHUDESCRIPTION; + out->fudgetime1 = propagation_delay[unit]; + out->fudgetime2 = fudgefactor[unit]; + out->fudgeval1 = (long)stratumtouse[unit]; + out->flags = sloppyclockflag[unit]; + if (unitinuse[unit]) { + chu = chuunits[unit]; + out->lencode = NCHUCHARS*4; + out->fudgeval2 = chu->lastcode[2*NCHUCHARS+1]; + out->fudgeval2 *= (chu->lastcode[2*NCHUCHARS]&1)?-1:1; + out->lastcode = chu->asciicode; + out->timereset = current_time - chu->timestarted; + npolls = out->timereset / 6; /* **divide** */ + out->polls = npolls; + out->noresponse = (npolls - chu->responses); + out->badformat = chu->badformat; + out->baddata = chu->baddata; + out->lastevent = chu->lastevent; + out->currentstatus = chu->status; + } else { + out->fudgeval2 = 0; + out->lencode = 0; + out->lastcode = ""; + out->polls = out->noresponse = 0; + out->badformat = out->baddata = 0; + out->timereset = 0; + out->currentstatus = out->lastevent = CEVNT_NOMINAL; + } + } +} +#endif |