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
/
ntp_loopfilter
< prev
next >
Wrap
Text File
|
1992-08-29
|
18KB
|
675 lines
/*
* ntp_loopfilter.c - implements the NTP loop filter algorithm
*/
#include <stdio.h>
#include <sys/types.h>
#include <netinet/in.h>
#include "ntp_syslog.h"
#include "ntp_fp.h"
#include "ntp.h"
#ifdef PPS
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sgtty.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "refclock.h"
#endif /* PPS */
#ifdef STREAM
#include <stropts.h>
#include <sys/clkdefs.h>
#endif /* STREAMS */
/*
* The loop filter is implemented in slavish adherence to the
* specification (Section 5), except that for consistency we
* mostly carry the quantities in the same units as appendix G.
*
* Note that the long values below are the fractional portion of
* a long fixed-point value. This limits these values to +-0.5
* seconds. When adjustments are capped inside this range (see
* CLOCK_MAX_{I,F}) both the clock_adjust and the compliance
* registers should be fine. (When the compliance is above 16, it
* will at most accumulate 2**CLOCK_MULT times the maximum offset,
* which means it fits in a s_fp.)
*
* The skew compensation is a special case. In version 2, it was
* kept in ms/4s (i.e., CLOCK_FREQ was 10). In version 3 (Section 5)
* it seems to be 2**-16ms/4s in a s_fp for a maximum of +-125ppm
* (stated maximum 100ppm). Since this seems about to change to a
* larger range, it will be kept in units of 2**-20 (CLOCK_DSCALE)
* in an s_fp (mainly because that's nearly the same as parts per
* million). Note that this is ``seconds per second'', whereas a
* clock adjustment is a 32-bit fraction of a second to be applied
* every 2**CLOCK_ADJ seconds; to find it, shift the drift right by
* (CLOCK_DSCALE-16-CLOCK_ADJ). When updating the drift, on the other
* hand, the CLOCK_FREQ factor from the spec assumes the value to be
* in ``seconds per 4 seconds''; to get our units, CLOCK_ADJ must be
* added to the shift.
*/
#define RSH_DRIFT_TO_FRAC (CLOCK_DSCALE - 16)
#define RSH_DRIFT_TO_ADJ (RSH_DRIFT_TO_FRAC - CLOCK_ADJ)
#define RSH_FRAC_TO_FREQ (CLOCK_FREQ + CLOCK_ADJ - RSH_DRIFT_TO_FRAC)
l_fp last_offset; /* last adjustment done */
long clock_adjust; /* clock adjust register (fraction only) */
s_fp drift_comp; /* drift compensation register */
s_fp max_comp; /* drift limit imposed by max host clock slew */
int time_constant; /* log2 of time constant (0 .. 4) */
u_long tcadj_time; /* last time-constant adjust time */
u_long watchdog_timer; /* watchdog timer, in seconds */
int first_adjustment; /* set to 1 if waiting for first adjustment */
int tc_counter; /* time-constant hold counter */
int pps_control; /* set to one if pps signal valid */
l_fp pps_fudge; /* pps tuning offset */
u_long pps_update; /* last pps update time */
#ifdef PPS
/*
* This module has support for a 1-pps signal to fine-tune the local
* clock. The signal is optional; when present and operating within
* given tolerances in frequency and jitter, it is used to discipline
* the local clock. In order for this to work, the local clock must be
* set to within +-500 ms by another means, such as a radio clock or
* NTP itself. The 1-pps signal is connected via a serial port and
* gadget box consisting of a one-shot and EIA level-converter. When
* operated at 38.4 kbps with a SPARCstation IPC, this arrangement has a
* worst-case jitter less than 26 us.
*/
#ifdef B38400
#define PPS_BAUD B38400 /* serial port speed */
#else
#define PPS_BAUD EXTB
#endif
#define PPS_MAXAGE 120 /* seconds after which we disbelieve pps */
#define PPS_MAXUPDATE 600 /* seconds after which we disbelieve timecode */
#define PPS_DEV "/dev/pps" /* pps port */
#define PPS_FAC 5 /* pps shift (log2 trimmed samples) */
#define NPPS 42 /* pps filter size (1<<PPS_FAC+2*PPS_TRIM) */
#define PPS_TRIM 5 /* samples trimmed from median filter */
#define PPS_DELAY 0x00103000 /* uart character time (about 247 us) */
#define PPS_XCPT "\377" /* intercept character */
struct refclockio io; /* given to the I/O handler */
l_fp pps_offset; /* filtered pps offset */
u_long pps_time; /* last pps sample time */
u_long nsamples; /* number of pps samples collected */
long samples[NPPS]; /* median filter for pps samples */
void pps_sample();
/*
* Imported from ntp_io.c
*/
extern struct interface *loopback_interface;
#endif /* PPS */
/*
* Debug flag importation
*/
extern int debug;
/*
* Imported from timer module
*/
extern u_long current_time; /* like it says, in seconds */
/*
* sys_poll and sys_refskew are set here
*/
extern int sys_poll; /* log2 of system poll interval */
extern l_fp sys_refskew; /* accumulated skew since last update */
extern u_fp sys_maxd; /* max dispersion of survivor list */
/*
* init_loopfilter - initialize loop filter data
*/
void
init_loopfilter()
{
extern unsigned long tsf_maxslew;
unsigned long tsf_limit;
#if defined(PPS) && defined(PPSDEV)
int fdpps;
#if !defined(HPUXGADGET)
struct sgttyb ttyb;
#endif
void pps_receive();
#if defined(HPUXGADGET)
extern int io_addclock_simple();
#else
extern int io_addclock();
#endif
#endif /* PPS && PPSDEV */
clock_adjust = 0;
drift_comp = 0;
time_constant = 0;
tcadj_time = 0;
sys_poll = NTP_MINPOLL;
watchdog_timer = 0;
tc_counter = 0;
last_offset.l_i = 0;
last_offset.l_f = 0;
first_adjustment = 1;
/*
* Limit for drift_comp, minimum of two values. The first is to avoid
* signed overflow, the second to keep within 75% of the maximum
* adjustment possible in adj_systime().
*/
max_comp = 0x7fff0000;
tsf_limit = ((tsf_maxslew >> 1) + (tsf_maxslew >> 2));
if ((max_comp >> RSH_DRIFT_TO_ADJ) > tsf_limit)
max_comp = tsf_limit << RSH_DRIFT_TO_ADJ;
pps_control = 0;
#if defined(PPS) && defined(PPSDEV)
pps_fudge.l_i = 0;
pps_fudge.l_f = PPS_DELAY;
pps_time = pps_update = 0;
nsamples = 0;
/*
* Open pps serial port, set for exclusive use, set line speed
* and raw mode. We don't really care if the pps device comes
* up; if not, we just use the timecode. Therefore, if anything
* goes wrong, just reclaim the resources and continue.
*/
fdpps = open(PPS_DEV, O_RDONLY);
if (fdpps == -1) {
syslog(LOG_ERR,
"init_loopfilter: open %s fails", PPS_DEV);
return;
}
#if !defined(HPUXGADGET)
if (ioctl(fdpps, TIOCEXCL, (char *)0) < 0) {
syslog(LOG_ERR,
"init_loopfilter: ioctl(%s, TIOCEXCL) fails", PPS_DEV);
(void) close(fdpps);
return;
}
ttyb.sg_ispeed = ttyb.sg_ospeed = PPS_BAUD;
ttyb.sg_erase = ttyb.sg_kill = 0;
ttyb.sg_flags = RAW;
if (ioctl(fdpps, TIOCSETP, (char *)&ttyb) < 0) {
syslog(LOG_ERR,
"init_loopfilter: ioctl(%s, TIOCSETP) fails", PPS_DEV);
(void) close(fdpps);
return;
}
#endif
#ifdef STREAM
/*
* Pop off existing streams modules and push on clk module
*/
while (ioctl(fdpps, I_POP, 0 ) >= 0) ;
if (ioctl(fdpps, I_PUSH, "clk" ) < 0) {
syslog(LOG_ERR,
"init_loopfilter: ioctl(%s, I_PUSH) fails", PPS_DEV);
(void) close(fdpps);
return;
}
/*
* Tickle the tty_clk streams module to timestamp the intercept
* character.
*/
if (ioctl(fdpps, CLK_SETSTR, PPS_XCPT) < 0) {
syslog(LOG_ERR,
"init_loopfilter: ioctl(%s, CLK_SETSTR) fails", PPS_DEV);
(void) close(fdpps);
return;
}
#else
/* Line-discipline folks invited to hack here */
#endif /* STREAM */
/*
* Insert in device list.
*/
io.clock_recv = pps_receive;
io.srcclock = (caddr_t)NULL;
io.datalen = 0;
io.fd = fdpps;
#if defined(HPUXGADGET)
if (!io_addclock_simple(&io)) {
#else
if (!io_addclock(&io)) {
#endif
syslog(LOG_ERR, "init_loopfilter: addclock %s fails", PPS_DEV);
(void) close(fdpps);
return;
}
#endif /* PPS && PPSDEV */
}
/*
* local_clock - the NTP logical clock loop filter. Returns 1 if the
* clock was stepped, 0 if it was slewed and -1 if it is
* hopeless.
*/
int
local_clock(fp_offset, from)
l_fp *fp_offset; /* best offset estimate */
struct sockaddr_in *from; /* who offset is from, for messages */
{
register long offset;
register u_long dispersion;
register u_long tmp_ui;
register u_long tmp_uf;
register long tmp;
int isneg;
extern void step_systime();
extern char *ntoa();
extern char *lfptoa();
extern char *mfptoa();
#ifdef DEBUG
if (debug > 1)
printf("local_clock(%s, %s)\n", lfptoa(fp_offset, 9),
ntoa(from));
#endif
/*
* Take the absolute value of the offset
*/
tmp_ui = fp_offset->l_ui;
tmp_uf = fp_offset->l_uf;
if (M_ISNEG(tmp_ui, tmp_uf)) {
M_NEG(tmp_ui, tmp_uf);
isneg = 1;
} else
isneg = 0;
/*
* If the clock is way off, don't tempt fate by correcting it.
*/
if (tmp_ui >= CLOCK_WAYTOOBIG) {
syslog(LOG_ERR,
"Clock appears to be %u seconds %s, something may be wrong",
tmp_ui, isneg>0?"fast":"slow");
#ifndef BIGTIMESTEP
return (-1);
#endif BIGTIMESTEP
}
/*
* Save this offset for later perusal
*/
last_offset = *fp_offset;
/*
* If the magnitude of the offset is greater than CLOCK.MAX, step
* the time and reset the registers.
*/
if (tmp_ui > CLOCK_MAX_I || (tmp_ui == CLOCK_MAX_I
&& (u_long)tmp_uf >= (u_long)CLOCK_MAX_F)) {
if (watchdog_timer < CLOCK_MINSTEP) {
/* Mustn't step yet, pretend we adjusted. */
syslog(LOG_INFO,
"adjust: STEP dropped (%s offset %s)\n",
ntoa(from), lfptoa(fp_offset, 9));
return (0);
}
syslog(LOG_INFO, "adjust: STEP %s offset %s\n",
ntoa(from), lfptoa(fp_offset, 9));
step_systime(fp_offset);
clock_adjust = 0;
watchdog_timer = 0;
first_adjustment = 1;
pps_update = 0;
return (1);
}
/*
* Here we've got an offset small enough to slew. Note that
* since the offset is small we don't have to carry the damned
* high order longword in our calculations.
*
* The time constant and sample interval are approximated with
* shifts, as in Section 5 of the v3 spec. The spec procedure
* looks strange, as an interval of 64 to 127 seconds seems to
* cause multiplication with 128 (and so on). This code lowers
* the multiplier by one bit.
*
* The time constant update goes after adjust and skew updates,
* as in appendix G.
*/
#ifdef PPS
/*
* If pps samples are valid, update offset, root delay and
* root dispersion. This may be a dramatic surprise to high-
* stratum clients, since all of a sudden this server looks
* like a stratum-1 clock.
*/
if (pps_control)
last_offset = pps_offset;
#endif
offset = last_offset.l_f;
clock_adjust = offset >> time_constant;
/*
* Calculate the new frequency error. The factor given in the
* spec gives the adjustment per 2**CLOCK_ADJ seconds, but we
* want it as a (scaled) pure ratio, so we include that factor
* now and remove it later.
*/
if (first_adjustment) {
first_adjustment = 0;
} else {
/*
* Clamp the integration interval to NTP_MAXPOLL.
* The bitcounting in Section 5 gives (n+1)-6 for 2**n,
* but has a factor 2**6 missing from CLOCK_FREQ.
* We make 2**n give n instead. If watchdog_timer is zero,
* pretend it's one.
*/
tmp = NTP_MAXPOLL;
tmp_uf = watchdog_timer;
if (tmp_uf == 0)
tmp_uf = 1;
while (tmp_uf < (1<<NTP_MAXPOLL)) {
tmp--;
tmp_uf <<= 1;
}
/*
* We apply the frequency scaling at the same time as
* the sample interval; this ensures a safe right-shift.
* (as long as it keeps below 31 bits, which current
* parameters should ensure.
*/
tmp = (RSH_FRAC_TO_FREQ - tmp) + time_constant + time_constant;
if (offset < 0)
tmp = -((-offset) >> tmp);
else
tmp = offset >> tmp;
#ifdef DEBUG
if (debug > 2)
printf("watchdog %u, gain %u, change %s\n",
watchdog_timer, 1<<time_constant,
fptoa(tmp, 5));
#endif
drift_comp += tmp;
/*
* Check that the result is in the possible interval
*/
if (drift_comp >= max_comp || drift_comp <= -max_comp) {
syslog(LOG_ERR,
"Drift exceeds %s%sppm, cannot cope",
drift_comp>0?"+":"-", fptoa(max_comp, 0));
exit(1);
}
}
watchdog_timer = 0;
/*
* Determine when to adjust the time constant.
*/
if (current_time - tcadj_time >= (1 << sys_poll)) {
tmp = offset;
if (tmp < 0) tmp = -tmp;
tmp = tmp >> (16 + CLOCK_WEIGHTTC - time_constant);
tcadj_time = current_time;
if (tmp > sys_maxd) {
tc_counter = 0;
if (time_constant > 0) time_constant--;
}
else {
tc_counter++;
if (tc_counter > CLOCK_HOLDTC) {
tc_counter = 0;
if (time_constant < CLOCK_MAXTC)
time_constant++;
}
}
}
sys_poll = NTP_MINPOLL + time_constant;
#ifdef DEBUG
if (debug > 1)
printf("adj %s, drft %s, tau %3i\n",
mfptoa((clock_adjust<0?-1:0), clock_adjust, 9),
fptoa(drift_comp, 9), time_constant);
#endif
(void) record_loop_stats(&last_offset, &drift_comp, time_constant);
/*
* Whew. I've had enough.
*/
return (0);
}
/*
* adj_host_clock - Called every 2**CLOCK_ADJ seconds to update host clock
*/
void
adj_host_clock()
{
register long adjustment;
extern void adj_systime();
#ifdef PPS
if (pps_time != 0 && current_time - pps_time > PPS_MAXAGE)
pps_time = 0;
if (pps_update != 0 && current_time - pps_update > PPS_MAXUPDATE)
pps_update = 0;
if (pps_time != 0 && pps_update != 0) {
if (!pps_control)
syslog(LOG_INFO, "pps synch");
pps_control = 1;
} else {
if (pps_control)
syslog(LOG_INFO, "pps synch lost");
pps_control = 0;
}
#endif
if (sys_refskew.l_i >= NTP_MAXSKEW)
sys_refskew.l_f = 0; /* clamp it */
else
L_ADDUF(&sys_refskew, NTP_SKEWINC);
adjustment = clock_adjust;
if (adjustment < 0)
adjustment = -((-adjustment) >> CLOCK_PHASE);
else
adjustment >>= CLOCK_PHASE;
clock_adjust -= adjustment;
if (drift_comp < 0)
adjustment -= ((-drift_comp) >> RSH_DRIFT_TO_ADJ);
else
adjustment += drift_comp >> RSH_DRIFT_TO_ADJ;
adj_systime(adjustment);
watchdog_timer += (1<<CLOCK_ADJ);
if (watchdog_timer >= NTP_MAXAGE) {
first_adjustment = 1; /* don't use next offset for freq */
}
}
/*
* loop_config - configure the loop filter
*/
void
loop_config(item, value)
int item;
l_fp *value;
{
s_fp tmp;
switch (item) {
case LOOP_DRIFTCOMP:
tmp = LFPTOFP(value);
if (tmp >= max_comp || tmp <= -max_comp) {
syslog(LOG_ERR,
"loop_config: skew compensation %s too large",
fptoa(tmp, 5));
} else {
drift_comp = tmp;
}
break;
case LOOP_PPSDELAY:
pps_fudge = *value;
break;
default:
/* sigh */
break;
}
}
#if defined(PPS) && defined(PPSDEV)
/*
* pps_receive - compute and store 1-pps signal offset
*
* This routine is called once per second when the 1-pps signal is
* present. It calculates the offset of the local clock relative to the
* 1-pps signal and saves in a circular buffer for later use.
*/
void pps_receive(rbufp)
struct recvbuf *rbufp;
{
u_char *dpt; /* buffer pointer */
u_char ctmp; /* intercept char */
l_fp ts; /* l_fp temps */
/*
* Set up pointers, check the buffer length, discard intercept
* character and convert unix timeval to timestamp format.
*/
dpt = (u_char *)&rbufp->recv_space;
#if defined(HPUXGADGET)
if (rbufp->recv_length != sizeof(struct timeval)) {
#else
ctmp = *dpt++;
if (rbufp->recv_length != sizeof(struct timeval) + 1) {
#endif
#ifdef DEBUG
if (debug)
printf("pps_receive: bad length %d %c\n",
rbufp->recv_length, ctmp);
#endif
return;
}
if (!buftvtots(dpt, &ts)) {
#ifdef DEBUG
if (debug)
printf("pps_receive: buftvtots failed\n");
#endif
return;
}
/*
* Correct for uart delay and process sample offset.
*/
L_SUB(&ts, &pps_fudge);
pps_sample(&ts);
}
#endif
#ifdef PPS
/*
* pps_sample - process pps sample offset
*/
void pps_sample(tsr)
l_fp *tsr;
{
int i, j; /* temp ints */
long sort[NPPS]; /* temp array for sorting */
l_fp lftemp, ts; /* l_fp temps */
long ltemp; /* long temp */
extern void record_stats();
extern u_short ctlsysstatus();
extern u_char sys_stratum;
extern s_fp sys_offset;
extern s_fp sys_rootdelay;
extern u_fp sys_rootdispersion;
/*
* Note the seconds offset is already in the low-order timestamp
* doubleword, so all we have to do is sign-extend and invert it.
* The resulting offset is believed only if within CLOCK_MAX.
*/
pps_time = current_time;
ts = *tsr;
lftemp.l_i = lftemp.l_f = 0;
M_ADDF(lftemp.l_i, lftemp.l_f, ts.l_f);
L_NEG(&lftemp);
if (ts.l_f <= -CLOCK_MAX_F || ts.l_f >= CLOCK_MAX_F)
return;
/*
* Save the sample in a circular buffer for later processing.
*/
nsamples++;
i = ((int)(nsamples)) % NPPS;
samples[i] = lftemp.l_f;
if (i != NPPS-1)
return;
/*
* When the buffer fills up, construct an array of sorted
* samples.
*/
for (i = 0; i < NPPS; i++) {
sort[i] = samples[i];
for (j = 0; j < i; j++) {
if (sort[j] > sort[i]) {
ltemp = sort[j];
sort[j] = sort[i];
sort[i] = ltemp;
}
}
}
/*
* Compute offset as the average of all samples in the filter
* less PPS_TRIM samples trimmed from the beginning and end,
* dispersion as the difference between max and min of samples
* retained. The system stratum, root delay and root dispersion
* are also set here.
*/
pps_offset.l_i = pps_offset.l_f = 0;
for (i = PPS_TRIM; i < NPPS - PPS_TRIM; i++)
M_ADDF(pps_offset.l_i, pps_offset.l_f, sort[i]);
if (L_ISNEG(&pps_offset)) {
L_NEG(&pps_offset);
for (i = 0; i < PPS_FAC; i++)
L_RSHIFT(&pps_offset);
L_NEG(&pps_offset);
} else {
for (i = 0; i < PPS_FAC; i++)
L_RSHIFT(&pps_offset);
}
sys_stratum = 1;
sys_rootdelay = 0;
lftemp.l_i = 0;
lftemp.l_f = sort[NPPS-1-PPS_TRIM] - sort[PPS_TRIM];
sys_maxd = LFPTOFP(&lftemp);
sys_rootdispersion = sys_maxd;
#ifdef DEBUG
if (debug)
printf("pps_filter: %s %s %s\n", lfptoa(&pps_fudge, 6),
lfptoa(&pps_offset, 6), lfptoa(&lftemp, 5));
#endif
record_stats(loopback_interface, ctlsysstatus(), &pps_offset,
sys_rootdelay, sys_rootdispersion);
return;
}
#endif /* PPS */