Added vfs_update_interval variable and UPDATE_INTERVAL configuration
[unix-history] / sys / kern / kern_clock.c
index 69d0830..0616bba 100644 (file)
  * SUCH DAMAGE.
  *
  *     from: @(#)kern_clock.c  7.16 (Berkeley) 5/9/91
  * SUCH DAMAGE.
  *
  *     from: @(#)kern_clock.c  7.16 (Berkeley) 5/9/91
- *     $Id: kern_clock.c,v 1.6 1993/10/25 02:02:51 davidg Exp $
+ *     $Id: kern_clock.c,v 1.12 1994/03/01 23:21:44 phk Exp $
  */
 
  */
 
+/* Portions of this software are covered by the following: */
+/******************************************************************************
+ *                                                                            *
+ * Copyright (c) David L. Mills 1993, 1994                                    *
+ *                                                                            *
+ * Permission to use, copy, modify, and distribute this software and its      *
+ * documentation for any purpose and without fee is hereby granted, provided  *
+ * that the above copyright notice appears in all copies and that both the    *
+ * copyright notice and this permission notice appear in supporting           *
+ * documentation, and that the name University of Delaware not be used in     *
+ * advertising or publicity pertaining to distribution of the software        *
+ * without specific, written prior permission.  The University of Delaware    *
+ * makes no representations about the suitability this software for any       *
+ * purpose.  It is provided "as is" without express or implied warranty.      *
+ *                                                                            *
+ *****************************************************************************/
+
+
 #include "param.h"
 #include "systm.h"
 #include "dkstat.h"
 #include "callout.h"
 #include "kernel.h"
 #include "proc.h"
 #include "param.h"
 #include "systm.h"
 #include "dkstat.h"
 #include "callout.h"
 #include "kernel.h"
 #include "proc.h"
+#include "signalvar.h"
 #include "resourcevar.h"
 #include "resourcevar.h"
+#include "timex.h"
 
 #include "machine/cpu.h"
 
 
 #include "machine/cpu.h"
 
@@ -51,6 +71,8 @@
 #include "gprof.h"
 #endif
 
 #include "gprof.h"
 #endif
 
+static void gatherstats(clockframe *);
+
 /* From callout.h */
 struct callout *callfree, *callout, calltodo;
 int ncallout;
 /* From callout.h */
 struct callout *callfree, *callout, calltodo;
 int ncallout;
@@ -89,24 +111,256 @@ int ncallout;
        } \
 }
 
        } \
 }
 
+/*
+ * Phase-lock loop (PLL) definitions
+ *
+ * The following defines establish the performance envelope of the PLL.
+ * They specify the maximum phase error (MAXPHASE), maximum frequency
+ * error (MAXFREQ), minimum interval between updates (MINSEC) and
+ * maximum interval between updates (MAXSEC). The intent of these bounds
+ * is to force the PLL to operate within predefined limits in order to
+ * satisfy correctness assertions. An excursion which exceeds these
+ * bounds is clamped to the bound and operation proceeds accordingly. In
+ * practice, this can occur only if something has failed or is operating
+ * out of tolerance, but otherwise the PLL continues to operate in a
+ * stable mode.
+ *
+ * MAXPHASE must be set greater than or equal to CLOCK.MAX (128 ms), as
+ * defined in the NTP specification. CLOCK.MAX establishes the maximum
+ * time offset allowed before the system time is reset, rather than
+ * incrementally adjusted. Here, the maximum offset is clamped to
+ * MAXPHASE only in order to prevent overflow errors due to defective
+ * protocol implementations.
+ *
+ * MAXFREQ reflects the manufacturing frequency tolerance of the CPU
+ * clock oscillator plus the maximum slew rate allowed by the protocol.
+ * It should be set to at least the frequency tolerance of the
+ * oscillator plus 100 ppm for vernier frequency adjustments. If the
+ * kernel frequency discipline code is installed (PPS_SYNC), the CPU
+ * oscillator frequency is disciplined to an external source, presumably
+ * with negligible frequency error, and MAXFREQ can be reduced.
+ */
+#define MAXPHASE 512000L       /* max phase error (us) */
+#ifdef PPS_SYNC
+#define MAXFREQ (100L << SHIFT_USEC) /* max freq error (scaled ppm) */
+#else
+#define MAXFREQ (200L << SHIFT_USEC) /* max freq error (scaled ppm) */
+#endif /* PPS_SYNC */
+#define MINSEC 16L             /* min interval between updates (s) */
+#define MAXSEC 1200L           /* max interval between updates (s) */
+
+/*
+ * The following variables are read and set by the ntp_adjtime() system
+ * call. The ntp_pll.status variable defines the synchronization status of
+ * the system clock, with codes defined in the timex.h header file. The
+ * time_offset variable is used by the PLL to adjust the system time in
+ * small increments. The time_constant variable determines the bandwidth
+ * or "stiffness" of the PLL. The time_tolerance variable is the maximum
+ * frequency error or tolerance of the CPU clock oscillator and is a
+ * property of the architecture; however, in principle it could change
+ * as result of the presence of external discipline signals, for
+ * instance. The time_precision variable is usually equal to the kernel
+ * tick variable; however, in cases where a precision clock counter or
+ * external clock is available, the resolution can be much less than
+ * this and depend on whether the external clock is working or not. The
+ * time_maxerror variable is initialized by a ntp_adjtime() call and
+ * increased by the kernel once each second to reflect the maximum error
+ * bound growth. The time_esterror variable is set and read by the
+ * ntp_adjtime() call, but otherwise not used by the kernel.
+ */
+/* - use appropriate fields in ntp_pll instead */
+#if 0
+int ntp_pll.status = TIME_BAD; /* clock synchronization status */
+long time_offset = 0;          /* time adjustment (us) */
+long time_constant = 0;                /* pll time constant */
+long time_tolerance = MAXFREQ; /* frequency tolerance (scaled ppm) */
+long time_precision = 1;       /* clock precision (us) */
+long time_maxerror = MAXPHASE; /* maximum error (us) */
+long time_esterror = MAXPHASE; /* estimated error (us) */
+#endif
+
+/*
+ * The following variables establish the state of the PLL and the
+ * residual time and frequency offset of the local clock. The time_phase
+ * variable is the phase increment and the ntp_pll.frequency variable is the
+ * frequency increment of the kernel time variable at each tick of the
+ * clock. The ntp_pll.frequency variable is set via ntp_adjtime() from a value
+ * stored in a file when the synchronization daemon is first started.
+ * Its value is retrieved via ntp_adjtime() and written to the file
+ * about once per hour by the daemon. The time_adj variable is the
+ * adjustment added to the value of tick at each timer interrupt and is
+ * recomputed at each timer interrupt. The time_reftime variable is the
+ * second's portion of the system time on the last call to
+ * ntp_adjtime(). It is used to adjust the ntp_pll.frequency variable and to
+ * increase the time_maxerror as the time since last update increases.
+ * The scale factors are defined in the timex.h header file.
+ */
+long time_phase = 0;           /* phase offset (scaled us) */
+#if 0
+long ntp_pll.frequency = 0;            /* frequency offset (scaled ppm) */
+#endif
+long time_adj = 0;             /* tick adjust (scaled 1 / hz) */
+long time_reftime;     /* time at last adjustment (s) */
+
+#ifdef PPS_SYNC
+/*
+ * The following defines and declarations are used only if a pulse-per-
+ * second (PPS) signal is available and connected via a modem control
+ * lead, such as produced by the optional ppsclock feature incorporated
+ * in the asynch driver. They establish the design parameters of the PPS
+ * frequency-lock loop used to discipline the CPU clock oscillator to
+ * the PPS signal. PPS_AVG is the averaging factor for the frequency
+ * loop. PPS_SHIFT and PPS_SHIFTMAX specify the minimum and maximum
+ * intervals, respectively, in seconds as a power of two. The
+ * PPS_DISPINC is the initial increment to pps_disp at each second.
+ */
+#define PPS_AVG 2              /* pps averaging constant (shift) */
+#define PPS_SHIFT 2            /* min interval duration (s) (shift) */
+#define PPS_SHIFTMAX 8         /* max interval duration (s) (shift) */
+#define PPS_DISPINC 0L         /* dispersion increment (us/s) */
+
+/*
+ * The pps_time variable contains the time at each calibration as read
+ * by microtime(). The pps_usec variable is latched from a high
+ * resolution counter or external clock at pps_time. Here we want the
+ * hardware counter contents only, not the contents plus the
+ * time_tv.usec as usual. The pps_ybar variable is the current CPU
+ * oscillator frequency offset estimate relative to the PPS signal. The
+ * pps_disp variable is the current error estimate, which is increased
+ * pps_dispinc once each second. Frequency updates are permitted only
+ * when pps_disp is below the pps_dispmax threshold. The pps-mf[] array
+ * is used as a median filter for the frequency estimate and to derive
+ * the error estimate.
+ */
+struct timeval pps_time;       /* kernel time at last interval */
+long pps_usec = 0;             /* usec counter at last interval */
+#if 0
+long pps_ybar = 0;             /* frequency estimate (scaled ppm) */
+long pps_disp = MAXFREQ;       /* dispersion estimate (scaled ppm) */
+#endif
+long pps_dispmax = MAXFREQ / 2;        /* dispersion threshold */
+long pps_dispinc = PPS_DISPINC;        /* pps dispersion increment/sec */
+long pps_mf[] = {0, 0, 0};     /* pps median filter */
+
+/*
+ * The pps_count variable counts the seconds of the calibration
+ * interval, the duration of which is pps_shift (s) in powers of two.
+ * The pps_intcnt variable counts the calibration intervals for use in
+ * the interval-adaptation algorithm. It's just too complicated for
+ * words.
+ */
+int pps_count = 0;             /* calibration interval counter (s) */
+#if 0
+int pps_shift = PPS_SHIFT;     /* interval duration (s) (shift) */
+#endif
+int pps_intcnt = 0;            /* intervals at current duration */
+
+/*
+ * PPS signal quality monitors
+ */
+#if 0
+long pps_calcnt;               /* calibration intervals */
+long pps_jitcnt;               /* jitter limit exceeded */
+long pps_discnt;               /* dispersion limit exceeded */
+#endif
+#endif /* PPS_SYNC */
+
+struct timex ntp_pll = {
+       0,                      /* mode */
+       0,                      /* offset */
+       0,                      /* frequency */
+       MAXPHASE,               /* maxerror */
+       MAXPHASE,               /* esterror */
+       TIME_BAD,               /* status */
+       0,                      /* time_constant */
+       1,                      /* precision */
+       MAXFREQ,                /* tolerance */
+       0,                      /* ybar */
+#ifdef PPS_SYNC
+       MAXFREQ,                /* disp */
+       PPS_SHIFT,              /* shift */
+       0,                      /* calcnt */
+       0,                      /* jitcnt */
+       0                       /* discnt */
+#endif
+};
+
+/*
+ * hardupdate() - local clock update
+ *
+ * This routine is called by ntp_adjtime() to update the local clock
+ * phase and frequency. This is used to implement an adaptive-parameter,
+ * first-order, type-II phase-lock loop. The code computes the time
+ * since the last update and clamps to a maximum (for robustness). Then
+ * it multiplies by the offset (sorry about the ugly multiply), scales
+ * by the time constant, and adds to the frequency variable. Then, it
+ * computes the phase variable as the offset scaled by the time
+ * constant. Note that all shifts are assumed to be positive. Only
+ * enough error checking is done to prevent bizarre behavior due to
+ * overflow problems.
+ *
+ * For default SHIFT_UPDATE = 12, the offset is limited to +-512 ms, the
+ * maximum interval between updates is 4096 s and the maximum frequency
+ * offset is +-31.25 ms/s.
+ */
+void
+hardupdate(offset)
+       long offset;
+{
+       long mtemp;
+
+       if (offset > MAXPHASE)
+               ntp_pll.offset = MAXPHASE << SHIFT_UPDATE;
+       else if (offset < -MAXPHASE)
+               ntp_pll.offset = -(MAXPHASE << SHIFT_UPDATE);
+       else
+               ntp_pll.offset = offset << SHIFT_UPDATE;
+       mtemp = time.tv_sec - time_reftime;
+       time_reftime = time.tv_sec;
+       if (mtemp > MAXSEC)
+               mtemp = 0;
+
+       /* ugly multiply should be replaced */
+       if (offset < 0)
+         ntp_pll.frequency -= 
+           (-offset * mtemp) >> (ntp_pll.time_constant
+                                 + ntp_pll.time_constant
+                                 + SHIFT_KF 
+                                 - SHIFT_USEC);
+       else
+         ntp_pll.frequency +=
+           (offset * mtemp) >> (ntp_pll.time_constant 
+                                + ntp_pll.time_constant
+                                + SHIFT_KF
+                                - SHIFT_USEC);
+       if (ntp_pll.frequency > ntp_pll.tolerance)
+               ntp_pll.frequency = ntp_pll.tolerance;
+       else if (ntp_pll.frequency < -ntp_pll.tolerance)
+               ntp_pll.frequency = -ntp_pll.tolerance;
+       if (ntp_pll.status == TIME_BAD)
+               ntp_pll.status = TIME_OK;
+}
+
 /*
  * The hz hardware interval timer.
  * We update the events relating to real time.
  * If this timer is also being used to gather statistics,
  * we run through the statistics gathering routine as well.
  */
 /*
  * The hz hardware interval timer.
  * We update the events relating to real time.
  * If this timer is also being used to gather statistics,
  * we run through the statistics gathering routine as well.
  */
+void
 hardclock(frame)
        clockframe frame;
 {
        register struct callout *p1;
        register struct proc *p = curproc;
 hardclock(frame)
        clockframe frame;
 {
        register struct callout *p1;
        register struct proc *p = curproc;
-       register struct pstats *pstats;
+       register struct pstats *pstats = 0;
        register struct rusage *ru;
        register struct vmspace *vm;
        register int s;
        int needsoft = 0;
        extern int tickdelta;
        extern long timedelta;
        register struct rusage *ru;
        register struct vmspace *vm;
        register int s;
        int needsoft = 0;
        extern int tickdelta;
        extern long timedelta;
+       long ltemp, time_update = 0;
 
        /*
         * Update real-time timeout queue.
 
        /*
         * Update real-time timeout queue.
@@ -230,30 +484,108 @@ hardclock(frame)
         * so we don't keep the relatively high clock interrupt
         * priority any longer than necessary.
         */
         * so we don't keep the relatively high clock interrupt
         * priority any longer than necessary.
         */
-       if (timedelta == 0)
-               BUMPTIME(&time, tick)
-       else {
-               register delta;
-
-               if (timedelta < 0) {
-                       delta = tick - tickdelta;
-                       timedelta += tickdelta;
+       {
+               int delta;
+               if (timedelta == 0) {
+                 delta = tick;
                } else {
                } else {
-                       delta = tick + tickdelta;
-                       timedelta -= tickdelta;
+                       if (timedelta < 0) {
+                               delta = tick - tickdelta;
+                               timedelta += tickdelta;
+                       } else {
+                               delta = tick + tickdelta;
+                               timedelta -= tickdelta;
+                       }
                }
                }
-               BUMPTIME(&time, delta);
-       }
-#ifdef DCFCLK
-       /*
-        * This is lousy, but until I can get the $&^%&^(!!! signal onto one
-        * of the interrupt's I'll have to poll it.  No, it will not work if
-        * you attempt -DHZ=1000, things break.
-        * But keep the NDCFCLK low, to avoid waste of cycles...
-        * phk@data.fls.dk
-        */
-       dcfclk_worker();
+               /*
+                * Logic from ``Precision Time and Frequency Synchronization
+                * Using Modified Kernels'' by David L. Mills, University
+                * of Delaware.
+                */
+               time_phase += time_adj;
+               if(time_phase <= -FINEUSEC) {
+                       ltemp = -time_phase >> SHIFT_SCALE;
+                       time_phase += ltemp << SHIFT_SCALE;
+                       time_update -= ltemp;
+               } else if(time_phase >= FINEUSEC) {
+                       ltemp = time_phase >> SHIFT_SCALE;
+                       time_phase -= ltemp << SHIFT_SCALE;
+                       time_update += ltemp;
+               }
+
+               time.tv_usec += delta + time_update;
+               /*
+                * On rollover of the second the phase adjustment to be used for
+                * the next second is calculated. Also, the maximum error is
+                * increased by the tolerance. If the PPS frequency discipline
+                * code is present, the phase is increased to compensate for the
+                * CPU clock oscillator frequency error.
+                *
+                * With SHIFT_SCALE = 23, the maximum frequency adjustment is
+                * +-256 us per tick, or 25.6 ms/s at a clock frequency of 100
+                * Hz. The time contribution is shifted right a minimum of two
+                * bits, while the frequency contribution is a right shift.
+                * Thus, overflow is prevented if the frequency contribution is
+                * limited to half the maximum or 15.625 ms/s.
+                */
+               if (time.tv_usec >= 1000000) {
+                       time.tv_usec -= 1000000;
+                       time.tv_sec++;
+                       ntp_pll.maxerror += ntp_pll.tolerance >> SHIFT_USEC;
+                       if (ntp_pll.offset < 0) {
+                               ltemp = -ntp_pll.offset >>
+                           (SHIFT_KG + ntp_pll.time_constant);
+                               ntp_pll.offset += ltemp;
+                       time_adj = -ltemp <<
+                         (SHIFT_SCALE - SHIFT_HZ - SHIFT_UPDATE);
+                       } else {
+                               ltemp = ntp_pll.offset >>
+                                 (SHIFT_KG + ntp_pll.time_constant);
+                               ntp_pll.offset -= ltemp;
+                               time_adj = ltemp <<
+                                 (SHIFT_SCALE - SHIFT_HZ - SHIFT_UPDATE);
+                       }
+#ifdef PPS_SYNC
+                       /*
+                        * Grow the pps error by pps_dispinc ppm and clamp to
+                        * MAXFREQ. The hardpps() routine will pull it down as
+                        * long as the PPS signal is good.
+                        */
+                       ntp_pll.disp += pps_dispinc;
+                       if (ntp_pll.disp > MAXFREQ)
+                         ntp_pll.disp = MAXFREQ;
+                       ltemp = ntp_pll.frequency + ntp_pll.ybar;
+#else
+                       ltemp = ntp_pll.frequency;
+#endif /* PPS_SYNC */
+                       if (ltemp < 0)
+                         time_adj -= -ltemp >>
+                           (SHIFT_USEC + SHIFT_HZ - SHIFT_SCALE);
+                       else
+                         time_adj += ltemp >>
+                           (SHIFT_USEC + SHIFT_HZ - SHIFT_SCALE);
+#if 0
+                       time_adj += fixtick << (SHIFT_SCALE - SHIFT_HZ);
 #endif
 #endif
+
+                       /*
+                        * When the CPU clock oscillator frequency is not a
+                        * power of two in Hz, the SHIFT_HZ is only an
+                        * approximate scale factor. In the SunOS kernel, this
+                        * results in a PLL gain factor of 1/1.28 = 0.78 what it
+                        * should be. In the following code the overall gain is
+                        * increased by a factor of 1.25, which results in a
+                        * residual error less than 3 percent.
+                        */
+                       if (hz == 100) {
+                               if (time_adj < 0)
+                                 time_adj -= -time_adj >> 2;
+                               else
+                                 time_adj += time_adj >> 2;
+                       }
+               }
+       }
+
        if (needsoft) {
 #if 0
 /*
        if (needsoft) {
 #if 0
 /*
@@ -286,6 +618,7 @@ int dk_ndrive = DK_NDRIVE;
  * or idle state) for the entire last time interval, and
  * update statistics accordingly.
  */
  * or idle state) for the entire last time interval, and
  * update statistics accordingly.
  */
+void
 gatherstats(framep)
        clockframe *framep;
 {
 gatherstats(framep)
        clockframe *framep;
 {
@@ -339,6 +672,7 @@ gatherstats(framep)
  * Run periodic events from timeout queue.
  */
 /*ARGSUSED*/
  * Run periodic events from timeout queue.
  */
 /*ARGSUSED*/
+void
 softclock(frame)
        clockframe frame;
 {
 softclock(frame)
        clockframe frame;
 {
@@ -346,7 +680,7 @@ softclock(frame)
        for (;;) {
                register struct callout *p1;
                register caddr_t arg;
        for (;;) {
                register struct callout *p1;
                register caddr_t arg;
-               register int (*func)();
+               register timeout_func_t func;
                register int a, s;
 
                s = splhigh();
                register int a, s;
 
                s = splhigh();
@@ -393,8 +727,9 @@ softclock(frame)
 /*
  * Arrange that (*func)(arg) is called in t/hz seconds.
  */
 /*
  * Arrange that (*func)(arg) is called in t/hz seconds.
  */
+void
 timeout(func, arg, t)
 timeout(func, arg, t)
-       int (*func)();
+       timeout_func_t func;
        caddr_t arg;
        register int t;
 {
        caddr_t arg;
        register int t;
 {
@@ -424,8 +759,9 @@ timeout(func, arg, t)
  * untimeout is called to remove a function timeout call
  * from the callout structure.
  */
  * untimeout is called to remove a function timeout call
  * from the callout structure.
  */
+void
 untimeout(func, arg)
 untimeout(func, arg)
-       int (*func)();
+       timeout_func_t func;
        caddr_t arg;
 {
        register struct callout *p1, *p2;
        caddr_t arg;
 {
        register struct callout *p1, *p2;
@@ -450,30 +786,224 @@ untimeout(func, arg)
  * Used to compute third argument to timeout() from an
  * absolute time.
  */
  * Used to compute third argument to timeout() from an
  * absolute time.
  */
+
+/* XXX clock_t */
+u_long
 hzto(tv)
        struct timeval *tv;
 {
 hzto(tv)
        struct timeval *tv;
 {
-       register long ticks;
+       register unsigned long ticks;
        register long sec;
        register long sec;
-       int s = splhigh();
+       register long usec;
+       int s;
 
        /*
 
        /*
-        * If number of milliseconds will fit in 32 bit arithmetic,
-        * then compute number of milliseconds to time and scale to
-        * ticks.  Otherwise just compute number of hz in time, rounding
-        * times greater than representible to maximum value.
+        * If the number of usecs in the whole seconds part of the time
+        * difference fits in a long, then the total number of usecs will
+        * fit in an unsigned long.  Compute the total and convert it to
+        * ticks, rounding up and adding 1 to allow for the current tick
+        * to expire.  Rounding also depends on unsigned long arithmetic
+        * to avoid overflow.
+        *
+        * Otherwise, if the number of ticks in the whole seconds part of
+        * the time difference fits in a long, then convert the parts to
+        * ticks separately and add, using similar rounding methods and
+        * overflow avoidance.  This method would work in the previous
+        * case but it is slightly slower and assumes that hz is integral.
         *
         *
-        * Delta times less than 25 days can be computed ``exactly''.
-        * Maximum value for any timeout in 10ms ticks is 250 days.
+        * Otherwise, round the time difference down to the maximum
+        * representable value.
+        *
+        * Maximum value for any timeout in 10ms ticks is 248 days.
         */
         */
+       s = splhigh();
        sec = tv->tv_sec - time.tv_sec;
        sec = tv->tv_sec - time.tv_sec;
-       if (sec <= 0x7fffffff / 1000 - 1000)
-               ticks = ((tv->tv_sec - time.tv_sec) * 1000 +
-                       (tv->tv_usec - time.tv_usec) / 1000) / (tick / 1000);
-       else if (sec <= 0x7fffffff / hz)
-               ticks = sec * hz;
-       else
-               ticks = 0x7fffffff;
+       usec = tv->tv_usec - time.tv_usec;
        splx(s);
        splx(s);
+       if (usec < 0) {
+               sec--;
+               usec += 1000000;
+       }
+       if (sec < 0) {
+#ifdef DIAGNOSTIC
+               printf("hzto: negative time difference %ld sec %ld usec\n",
+                      sec, usec);
+#endif
+               ticks = 1;
+       } else if (sec <= LONG_MAX / 1000000)
+               ticks = (sec * 1000000 + (unsigned long)usec + (tick - 1))
+                       / tick + 1;
+       else if (sec <= LONG_MAX / hz)
+               ticks = sec * hz
+                       + ((unsigned long)usec + (tick - 1)) / tick + 1;
+       else
+               ticks = LONG_MAX;
+#define        CLOCK_T_MAX     INT_MAX /* XXX should be ULONG_MAX */
+       if (ticks > CLOCK_T_MAX)
+               ticks = CLOCK_T_MAX;
        return (ticks);
 }
        return (ticks);
 }
+
+#ifdef PPS_SYNC
+/*
+ * hardpps() - discipline CPU clock oscillator to external pps signal
+ *
+ * This routine is called at each PPS interrupt in order to discipline
+ * the CPU clock oscillator to the PPS signal. It integrates successive
+ * phase differences between the two oscillators and calculates the
+ * frequency offset. This is used in hardclock() to discipline the CPU
+ * clock oscillator so that intrinsic frequency error is cancelled out.
+ * The code requires the caller to capture the time and hardware
+ * counter value at the designated PPS signal transition.
+ */
+void
+hardpps(tvp, usec)
+       struct timeval *tvp;            /* time at PPS */
+       long usec;                      /* hardware counter at PPS */
+{
+       long u_usec, v_usec, bigtick;
+       long cal_sec, cal_usec;
+
+       /*
+        * During the calibration interval adjust the starting time when
+        * the tick overflows. At the end of the interval compute the
+        * duration of the interval and the difference of the hardware
+        * counters at the beginning and end of the interval. This code
+        * is deliciously complicated by the fact valid differences may
+        * exceed the value of tick when using long calibration
+        * intervals and small ticks. Note that the counter can be
+        * greater than tick if caught at just the wrong instant, but
+        * the values returned and used here are correct.
+        */
+       bigtick = (long)tick << SHIFT_USEC;
+       pps_usec -= ntp_pll.ybar;
+       if (pps_usec >= bigtick)
+               pps_usec -= bigtick;
+       if (pps_usec < 0)
+               pps_usec += bigtick;
+       pps_time.tv_sec++;
+       pps_count++;
+       if (pps_count < (1 << pps_shift))
+               return;
+       pps_count = 0;
+       ntp_pll.calcnt++;
+       u_usec = usec << SHIFT_USEC;
+       v_usec = pps_usec - u_usec;
+       if (v_usec >= bigtick >> 1)
+               v_usec -= bigtick;
+       if (v_usec < -(bigtick >> 1))
+               v_usec += bigtick;
+       if (v_usec < 0)
+               v_usec = -(-v_usec >> ntp_pll.shift);
+       else
+               v_usec = v_usec >> ntp_pll.shift;
+       pps_usec = u_usec;
+       cal_sec = tvp->tv_sec;
+       cal_usec = tvp->tv_usec;
+       cal_sec -= pps_time.tv_sec;
+       cal_usec -= pps_time.tv_usec;
+       if (cal_usec < 0) {
+               cal_usec += 1000000;
+               cal_sec--;
+       }
+       pps_time = *tvp;
+
+       /*
+        * Check for lost interrupts, noise, excessive jitter and
+        * excessive frequency error. The number of timer ticks during
+        * the interval may vary +-1 tick. Add to this a margin of one
+        * tick for the PPS signal jitter and maximum frequency
+        * deviation. If the limits are exceeded, the calibration
+        * interval is reset to the minimum and we start over.
+        */
+       u_usec = (long)tick << 1;
+       if (!((cal_sec == -1 && cal_usec > (1000000 - u_usec))
+           || (cal_sec == 0 && cal_usec < u_usec))
+           || v_usec > ntp_pll.tolerance || v_usec < -ntp_pll.tolerance) {
+               ntp_pll.jitcnt++;
+               ntp_pll.shift = NTP_PLL.SHIFT;
+               pps_dispinc = PPS_DISPINC;
+               ntp_pll.intcnt = 0;
+               return;
+       }
+
+       /*
+        * A three-stage median filter is used to help deglitch the pps
+        * signal. The median sample becomes the offset estimate; the
+        * difference between the other two samples becomes the
+        * dispersion estimate.
+        */
+       pps_mf[2] = pps_mf[1];
+       pps_mf[1] = pps_mf[0];
+       pps_mf[0] = v_usec;
+       if (pps_mf[0] > pps_mf[1]) {
+               if (pps_mf[1] > pps_mf[2]) {
+                       u_usec = pps_mf[1];             /* 0 1 2 */
+                       v_usec = pps_mf[0] - pps_mf[2];
+               } else if (pps_mf[2] > pps_mf[0]) {
+                       u_usec = pps_mf[0];             /* 2 0 1 */
+                       v_usec = pps_mf[2] - pps_mf[1];
+               } else {
+                       u_usec = pps_mf[2];             /* 0 2 1 */
+                       v_usec = pps_mf[0] - pps_mf[1];
+               }
+       } else {
+               if (pps_mf[1] < pps_mf[2]) {
+                       u_usec = pps_mf[1];             /* 2 1 0 */
+                       v_usec = pps_mf[2] - pps_mf[0];
+               } else  if (pps_mf[2] < pps_mf[0]) {
+                       u_usec = pps_mf[0];             /* 1 0 2 */
+                       v_usec = pps_mf[1] - pps_mf[2];
+               } else {
+                       u_usec = pps_mf[2];             /* 1 2 0 */
+                       v_usec = pps_mf[1] - pps_mf[0];
+               }
+       }
+
+       /*
+        * Here the dispersion average is updated. If it is less than
+        * the threshold pps_dispmax, the frequency average is updated
+        * as well, but clamped to the tolerance.
+        */
+       v_usec = (v_usec >> 1) - ntp_pll.disp;
+       if (v_usec < 0)
+               ntp_pll.disp -= -v_usec >> PPS_AVG;
+       else
+               ntp_pll.disp += v_usec >> PPS_AVG;
+       if (ntp_pll.disp > pps_dispmax) {
+               ntp_pll.discnt++;
+               return;
+       }
+       if (u_usec < 0) {
+               ntp_pll.ybar -= -u_usec >> PPS_AVG;
+               if (ntp_pll.ybar < -ntp_pll.tolerance)
+                       ntp_pll.ybar = -ntp_pll.tolerance;
+               u_usec = -u_usec;
+       } else {
+               ntp_pll.ybar += u_usec >> PPS_AVG;
+               if (ntp_pll.ybar > ntp_pll.tolerance)
+                       ntp_pll.ybar = ntp_pll.tolerance;
+       }
+
+       /*
+        * Here the calibration interval is adjusted. If the maximum
+        * time difference is greater than tick/4, reduce the interval
+        * by half. If this is not the case for four consecutive
+        * intervals, double the interval.
+        */
+       if (u_usec << ntp_pll.shift > bigtick >> 2) {
+               ntp_pll.intcnt = 0;
+               if (ntp_pll.shift > NTP_PLL.SHIFT) {
+                       ntp_pll.shift--;
+                       pps_dispinc <<= 1;
+               }
+       } else if (ntp_pll.intcnt >= 4) {
+               ntp_pll.intcnt = 0;
+               if (ntp_pll.shift < NTP_PLL.SHIFTMAX) {
+                       ntp_pll.shift++;
+                       pps_dispinc >>= 1;
+               }
+       } else
+               ntp_pll.intcnt++;
+}
+#endif /* PPS_SYNC */