summaryrefslogtreecommitdiffstats
path: root/usr.sbin/xntpd/doc/README.kern
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/xntpd/doc/README.kern')
-rw-r--r--usr.sbin/xntpd/doc/README.kern775
1 files changed, 775 insertions, 0 deletions
diff --git a/usr.sbin/xntpd/doc/README.kern b/usr.sbin/xntpd/doc/README.kern
new file mode 100644
index 0000000..1b791c3
--- /dev/null
+++ b/usr.sbin/xntpd/doc/README.kern
@@ -0,0 +1,775 @@
+ Unix Kernel Modifications for Precision Timekeeping
+
+ Revised 3 December 1993
+
+Note: This information file is included in the distributions for the
+SunOS, Ultrix and OSF/1 kernels and in the NTP Version 3 distribution
+(xntp3.tar.Z) as the file README.kern. Availability of the kernel
+distributions, which involve licensed code, will be announced
+separately. The NTP Version 3 distribution can be obtained via anonymous
+ftp from louie.udel.edu in the directory pub/ntp. In order to utilize
+all features of this distribution, the NTP version number should be 3.3
+or later.
+
+1. Introduction
+
+This memo describes modifications to certain SunOS, Ultrix and OSF/1
+kernel software that manage the system clock and timer functions. They
+provide improved accuracy and stability through the use of a disciplined
+clock interface for use with the Network Time Protocol (NTP) or similar
+time-synchronization protocol. In addition, for the DEC 3000 AXP (Alpha)
+and DECstation 5000/240 machines, the modifications provide improved
+precision within one microsecond (us) (SunOS 4.1.x already does provide
+precision to this order). The NTP Version 3 daemon xntpd operates with
+these kernel modifications to provide synchronization in principle to
+within this order, but in practice this is limited by the short-term
+stability of the timer oscillator to within the order of 100 usec.
+
+This memo describes the principles behind the design and operation of
+the new software. There are three versions: one that operates with the
+SunOS 4.1.x kernels, a second that operates with the Ultrix 4.x kernels
+and a third that operates with the OSF/1 V1.x kernels. A detailed
+description of the variables and algorithms is given in the hope that
+similar functionality can be incorporated in Unix kernels for other
+machines. The algorithms involve only minor changes to the system clock
+and interval timer routines and include interfaces for application
+programs to learn the system clock status and certain statistics of the
+time-synchronization process. Detailed installation instructions are
+given in a companion README.install file included in the kernel
+distributions. The kernel software itself is not provided for public
+distribution, since it involves licensed code. Detailed instructions on
+how to obtain it for either SunOS, Ultrix or OSF/1 will be given
+separately.
+
+The principal feature added to the Unix kernels is to change the way the
+system clock is controlled, in order to provide precision time and
+frequency adjustments. Another feature utilizes an undocumented bus-
+cycle counter in the DEC 3000 AXP and DECstation 5000/240 to provide
+precise time to the microsecond. This feature can in principle be used
+with any DEC machine that has this counter, although this has not been
+verified. The addition of these features does not affect the operation
+of existing Unix system calls such as gettimeofday(), settimeofday() and
+adjtime(); however, if the new features are in use, the operations of
+adjtime() are controlled instead by a new system call ntp_adjtime().
+
+Most Unix programs read the system clock using the gettimeofday() system
+call, which returns only the system time and timezone data. For some
+applications it is useful to know the maximum error of the reported time
+due to all causes, including clock reading errors, oscillator frequency
+errors and accumulated latencies on the path to a primary reference
+source. However, the new software can adjust the system clock to
+compensate for its intrinsic frequency error, so that the timing errors
+expected in normal operation will usually be much less than the maximum
+error. The user application interface includes a new system call
+ntp_gettime(), which returns the system time, as well as the maximum
+error and estimated error. This interface is intended to support
+applications that need such things, including distributed file systems,
+multimedia teleconferencing and other real-time applications. The
+protocol daemon application interface includes a new system call
+ntp_adjtime(), which can be used to read and write kernel variables used
+for precision timekeeping, including time and frequency adjustments,
+controlling time constant, leap-second warning and related data.
+
+In this memo, NTP Version 3 and the Unix implementation xntpd are used
+as an example application of the new system calls for use by a protocol
+daemon. In principle, the new system calls can be used by other
+protocols and daemon implementations as well. Even in cases where the
+local time is maintained by periodic exchanges of messages at relatively
+long intervals, such as using the NIST Automated Computer Time Service,
+the ability to precisely adjust the local clock frequency simplifies the
+synchronization procedures and allows the call frequency to be
+considerably reduced.
+
+2. Design Principles
+
+In order to understand how the new software works, it is useful to
+consider how most Unix systems maintain the system time. In the original
+design a hardware timer interrupts the kernel at a fixed rate: 100 Hz in
+the SunOS kernel, 256 Hz in the Ultrix kernel and 1024 Hz in the OSF/1
+kernel. Since the Ultrix kernel rate does not evenly divide one second
+in microseconds, the kernel adds 64 microseconds once each second, so
+the timescale consists of 255 advances of 3906 usec plus one of 3970
+usec. Similarly, the OSF/1 kernel adds 576 usec once each second, so its
+timescale consists of 1023 advances of 976 usec plus one of 1552 usec.
+
+In all Unix kernels considered in this memo, it is possible to slew the
+system clock to a new offset using the standard Unix adjtime() system
+call. To do this the clock frequency is changed by adding or subtracting
+a fixed amount (tickadj) at each timer interrupt (tick) for a calculated
+number of ticks. Since this calculation involves dividing the requested
+offset by tickadj, it is possible to slew to a new offset with a
+precision only of tickadj, which is usually in the neighborhood of 5 us,
+but sometimes much higher. This results in an amortization error which
+can accumulate to unacceptable levels, so that special provisions must
+be made in the clock adjustment procedures of the protocol daemon.
+
+In order to maintain the system clock within specified bounds with this
+scheme, it is necessary to call adjtime() on a regular basis. For
+instance, let the bound be set at 100 usec, which is a reasonable value
+for NTP-synchronized hosts on a local network, and let the onboard
+oscillator tolerance be 100 parts-per-million (ppm), which is a
+reasonably conservative assumption. This requires that adjtime() be
+called at intervals not exceeding 1 second (s), which is in fact what
+the unmodified NTP software daemon does.
+
+In the new software this scheme is replaced by another that extends the
+low-order bits of the system clock to provide very precise clock
+adjustments. At each timer interrupt a precisely calibrated quantity is
+added to the composite time value and overflows handled as required. The
+quantity is computed from the measured clock offset and in addition a
+frequency adjustment, which is automatically calculated from previous
+time adjustments. This implementation operates as an adaptive-parameter
+first-order, type-II, phase-lock loop (PLL), which in principle provides
+precision control of the system clock phase to within +-1 us and
+frequency to within +-5 nanoseconds (ns) per day.
+
+This PLL model is identical to the one implemented in NTP, except that
+in NTP the software daemon has to simulate the PLL using only the
+original adjtime() system call. The daemon is considerably complicated
+by the need to parcel time adjustments at frequent intervals in order to
+maintain the accuracy to specified bounds. The modified kernel routines
+do this directly, allowing vast gobs of ugly daemon code to be avoided
+at the expense of only a small amount of new code in the kernel. In
+fact, the amount of code added to the kernel for the new scheme is about
+the amount needed to implement the old scheme. A new system call
+ntp_adjtime(), which operates in a way similar to the original
+adjtime(), is called only as each new time update is determined, which
+in NTP occurs at intervals of from 16 s to 1024 s. In addition, doing
+the frequency correction in the kernel means that the system time runs
+true even if the daemon were to cease operation or the network paths to
+the primary reference source fail. The addition of the new ntp_adjtime()
+system call does not affect the original adjtime() system call, which
+continues to operate in its traditional fashion. However, the two system
+calls canot be used at the same time; only one of the two should be used
+on any given system.
+
+It is the intent in the design that settimeofday() be used for changes
+in system time greater than +-128 ms. It has been the Internet
+experience that the need to change the system time in increments greater
+than +-128 milliseconds is extremely rare and is usually associated with
+a hardware or software malfunction or system reboot. Once the system
+clock has been set in this way, the ntp_adjtime() system call is used to
+provide periodic updates including the time offset, maximum error,
+estimated error and PLL time constant. With NTP the update interval
+depends on the measured error and time constant; however, the scheme is
+quite forgiving and neither moderate loss of updates nor variations in
+the length of the polling interval are serious.
+
+In addition, the kernel adjusts the maximum error to grow by an amount
+equal to the oscillator frequency tolerance times the elapsed time since
+the last update. The default engineering parameters have been optimized
+for intervals not greater than about 16 s. For longer intervals the PLL
+time constant can be adjusted to optimize the dynamic response up to
+intervals of 1024 s. Normally, this is automatically done by NTP. In any
+case, if updates are suspended, the PLL coasts at the frequency last
+determined, which usually results in errors increasing only to a few
+tens of milliseconds over a day.
+
+The new code needs to know the initial frequency offset and time
+constant for the PLL, and the daemon needs to know the current frequency
+offset computed by the kernel for monitoring purposes. These data are
+exchanged between the kernel and protocol daemon using ntp_adjtime() as
+documented later in this memo. Provisions are made to exchange related
+timing information, such as the maximum error and estimated error,
+between the kernel and daemon and between the kernel and application
+programs.
+
+In the DEC 3000 AXP, DECstation 5000/240 and possibly other DEC
+machines there is an undocumented hardware register that counts system
+bus cycles at a rate of 25 MHz. The new kernel microtime() routine tests
+for the CPU type and, in the case of these machines, use this register
+to interpolate system time between hardware timer interrupts. This
+results in a precision of +-1 us for all time values obtained via the
+gettimeofday() and ntp_gettime() system calls. These routines call the
+microtime() routine, which returns the actual interpolated value but
+does not change the kernel time variable. Therefore, other kernel
+routines that access the kernel time variable directly and do not call
+either gettimeofday(), ntp_gettime() or microtime() will continue their
+present behavior. The microtime() feature is independent of other
+features described here and is operative even if the kernel PLL or new
+system calls have not been implemented.
+
+While any protocol daemon can in principle be modified to use the new
+system calls, the most likely will be users of the NTP Version 3 daemon
+xntpd. The xntpd code determines whether the new system calls are
+implemented and automatically reconfigures as required. When
+implemented, the daemon reads the frequency offset from a file and
+provides it and the initial time constant via ntp_adjtime(). In
+subsequent calls to ntp_adjtime(), only the time adjustment and time
+constant are affected. The daemon reads the frequency from the kernel
+using ntp_adjtime() at intervals of about one hour and writes it to the
+system log file. This information is recovered when the daemon is
+restarted after reboot, for example, so the sometimes extensive training
+period to learn the frequency separately for each system can be avoided.
+
+3. Kernel Interfaces
+
+This section describes the kernel interfaces to the protocol daemon and
+user applications. The ideas are based on suggestions from Jeff Mogul
+and Philip Gladstone and a similar interface designed by the latter. It
+is important to point out that the functionality of the original Unix
+adjtime() system call is preserved, so that the modified kernel will
+work as the unmodified one should the kernel PLL not be in use. In this
+case the ntp_adjtime() system call can still be used to read and write
+kernel variables that might be used by a protocol daemon other than NTP,
+for example.
+
+3.1. The ntp_gettime() System Call
+
+The syntax and semantics of the ntp_gettime() call are given in the
+following fragment of the timex.h header file. This file is identical in
+the SunOS, Ultrix and OSF/1 kernel distributions. Note that the timex.h
+file calls the syscall.h system header file, which must be modified to
+define the SYS_ntp_gettime system call specific to each system type. The
+kernel distributions include directions on how to do this.
+
+/*
+ * This header file defines the Network Time Protocol (NTP) interfaces
+ * for user and daemon application programs. These are implemented using
+ * private system calls and data structures and require specific kernel
+ * support.
+ *
+ * NAME
+ * ntp_gettime - NTP user application interface
+ *
+ * SYNOPSIS
+ * #include <sys/timex.h>
+ *
+ * int system call(SYS_ntp_gettime, tptr)
+ *
+ * int SYS_ntp_gettime defined in syscall.h header file
+ * struct ntptimeval *tptr pointer to ntptimeval structure
+ *
+ * NTP user interface - used to read kernel clock values
+ * Note: maximum error = NTP synch distance = dispersion + delay / 2;
+ * estimated error = NTP dispersion.
+ */
+struct ntptimeval {
+ struct timeval time; /* current time */
+ long maxerror; /* maximum error (usec) */
+ long esterror; /* estimated error (usec) */
+};
+
+The ntp_gettime() system call returns three values in the ntptimeval
+structure: the current time in unix timeval format plus the maximum and
+estimated errors in microseconds. While the 32-bit long data type limits
+the error quantities to something more than an hour, in practice this is
+not significant, since the protocol itself will declare an
+unsynchronized condition well below that limit. If the protocol computes
+either of these values in excess of 16 seconds, they are clamped to that
+value and the local clock declared unsynchronized.
+
+Following is a detailed description of the ntptimeval structure members.
+
+struct timeval time;
+
+ This member is set to the current system time, expressed as a Unix
+ timeval structure. The timeval structure consists of two 32-bit
+ words, one for the number of seconds past 1 January 1970 and the
+ other the number of microseconds past the most recent second's
+ epoch.
+
+long maxerror;
+
+ This member is set to the value of the time_maxerror kernel
+ variable, which establishes the maximum error of the indicated time
+ relative to the primary reference source, in microseconds. This
+ variable can also be set and read by the ntp_adjtime() system call.
+ For NTP, the value is determined as the synchronization distance,
+ which is equal to the root dispersion plus one-half the root delay.
+ It is increased by a small amount (time_tolerance) each second to
+ reflect the clock frequency tolerance. This variable is computed by
+ the time-synchronization daemon and the kernel and returned in a
+ ntp_gettime() system call, but is otherwise not used by the kernel.
+
+long esterror;
+
+ This member is set to the value of the time_esterror kernel
+ variable, which establishes the expected error of the indicated
+ time relative to the primary reference source, in microseconds.
+ This variable can also be set and read by the ntp_adjtime() system
+ call. For NTP, the value is determined as the root dispersion,
+ which represents the best estimate of the actual error of the
+ system clock based on its past behavior, together with observations
+ of multiple clocks within the peer group. This variable is computed
+ by the time-synchronization daemon and returned in a ntp_gettime()
+ system call, but is otherwise not used by the kernel.
+
+3.2. The ntp_adjtime() System Call
+
+The syntax and semantics of the ntp_adjtime() call is given in the
+following fragment of the timex.h header file. Note that, as in the
+ntp_gettime() system call, the the syscall.h system header file must be
+modified to define the SYS_ntp_adjtime system call specific to each
+system type.
+
+/*
+ * NAME
+ * ntp_adjtime - NTP daemon application interface
+ *
+ * SYNOPSIS
+ * #include <sys/timex.h>
+ *
+ * int system call(SYS_ntp_adjtime, mode, tptr)
+ *
+ * int SYS_ntp_adjtime defined in syscall.h header file
+ * struct timex *tptr pointer to timex structure
+ *
+ * NTP daemon interface - used to discipline kernel clock oscillator
+ */
+struct timex {
+ int mode; /* mode selector */
+ long offset; /* time offset (usec) */
+ long frequency; /* frequency offset (scaled ppm) */
+ long maxerror; /* maximum error (usec) */
+ long esterror; /* estimated error (usec) */
+ int status; /* clock command/status */
+ long time_constant; /* pll time constant */
+ long precision; /* clock precision (usec) (read only) */
+ long tolerance; /* clock frequency tolerance (ppm)
+ * (read only)
+ */
+};
+
+The ntp_adjtime() system call is used to read and write certain time-
+related kernel variables summarized in this and subsequent sections.
+Writing these variables can only be done in superuser mode. To write a
+variable, the mode structure member is set with one or more bits, one of
+which is assigned each of the following variables in turn. The current
+values for all variables are returned in any case; therefore, a mode
+argument of zero means to return these values without changing anything.
+
+Following is a description of the timex structure members.
+
+int mode;
+
+ This is a bit-coded variable selecting one or more structure
+ members, with one bit assigned each member. If a bit is set, the
+ value of the associated member variable is copied to the
+ corresponding kernel variable; if not, the member is ignored. The
+ bits are assigned as given in the following fragment of the timex.h
+ header file. Note that the precision and tolerance are intrinsic
+ properties of the kernel configuration and cannot be changed.
+
+ /*
+ * Mode codes (timex.mode)
+ */
+ #define ADJ_OFFSET 0x0001 /* time offset */
+ #define ADJ_FREQUENCY 0x0002 /* frequency offset */
+ #define ADJ_MAXERROR 0x0004 /* maximum time error */
+ #define ADJ_ESTERROR 0x0008 /* estimated time error */
+ #define ADJ_STATUS 0x0010 /* clock status */
+ #define ADJ_TIMECONST 0x0020 /* pll time constant */
+
+long offset;
+
+ If selected, this member (scaled) replaces the value of the
+ time_offset kernel variable, which defines the current time offset
+ of the phase-lock loop. The value must be in the range +-512 ms in
+ the present implementation. If so, the clock status is
+ automatically set to TIME_OK.
+
+long time_constant;
+
+ If selected, this member replaces the value of the time_constant
+ kernel variable, which establishes the bandwidth of "stiffness" of
+ the kernel PLL. The value is used as a shift, with the effective
+ PLL time constant equal to a multiple of (1 << time_constant), in
+ seconds. The optimum value for the time_constant variable is
+ log2(update_interval) - 4, where update_interval is the nominal
+ interval between clock updates, in seconds. With an ordinary crystal
+ oscillator the optimum value for time_constant is about 2, giving
+ an update_interval of 4 (64 s). Values of time_constant between zero
+ and 2 can be used if quick convergence is necessary; values between
+ 2 and 6 can be used to reduce network load, but at a modest cost in
+ accuracy. Values above 6 are appropriate only if a precision
+ oscillator is available.
+
+long frequency;
+
+ If selected, this member (scaled) replaces the value of the
+ time_frequency kernel variable, which establishes the intrinsic
+ frequency of the local clock oscillator. This variable is scaled by
+ (1 << SHIFT_USEC) in parts-per-million (ppm), giving it a maximum
+ value of about +-31 ms/s and a minimum value (frequency resolution)
+ of about 2e-11, which is appropriate for even the best quartz
+ oscillator.
+
+long maxerror;
+
+ If selected, this member replaces the value of the time_maxerror
+ kernel variable, which establishes the maximum error of the
+ indicated time relative to the primary reference source, in
+ microseconds. This variable can also be read by the ntp_gettime()
+ system call. For NTP, the value is determined as the
+ synchronization distance, which is equal to the root dispersion
+ plus one-half the root delay. It is increased by a small amount
+ (time_tolerance) each second to reflect the clock frequency
+ tolerance. This variable is computed by the time-synchronization
+ daemon and the kernel and returned in a ntp_gettime() system call,
+ but is otherwise not used by the kernel.
+
+long esterror;
+
+ If selected, this member replaces the value of the time_esterror
+ kernel variable, which establishes the expected error of the
+ indicated time relative to the primary reference source, in
+ microseconds. This variable can also be read by the ntp_gettime()
+ system call. For NTP, the value is determined as the root
+ dispersion, which represents the best estimate of the actual error
+ of the system clock based on its past behavior, together with
+ observations of multiple clocks within the peer group. This
+ variable is computed by the time-synchronization daemon and
+ returned in a ntp_gettime() system call, but is otherwise not used
+ by the kernel.
+
+int status;
+
+ If selected, this member replaces the value of the time_status
+ kernel variable, which records whether the clock is synchronized,
+ waiting for a leap second, etc. In order to set this variable
+ explicitly, either (a) the current clock status is TIME_OK or (b)
+ the member value is TIME_BAD; that is, the ntp_adjtime() call can
+ always set the clock to the unsynchronized state or, if the clock
+ is running correctly, can set it to any state. In any case, the
+ ntp_adjtime() call always returns the current state in this member,
+ so the caller can determine whether or not the request succeeded.
+
+long precision;
+
+ This member is set equal to the time_precision kernel in
+ microseconds variable upon return from the system call. The
+ time_precision variable cannot be written. This variable represents
+ the maximum error in reading the system clock, which is ordinarily
+ equal to the kernel variable tick, 10000 usec in the SunOS kernel,
+ 3906 usec in Ultrix kernel and 976 usec in the OSF/1 kernel.
+ However, in cases where the time can be interpolated with
+ microsecond resolution, such as in the SunOS kernel and modified
+ Ultrix and OSF/1 kernels, the precision is specified as 1 usec.
+ This variable is computed by the kernel for use by the time-
+ synchronization daemon, but is otherwise not used by the kernel.
+
+long tolerance;
+
+ This member is set equal to the time_tolerance kernel variable in
+ parts-per-million (ppm) upon return from the system call. The
+ time_tolerance variable cannot be written. This variable represents
+ the maximum frequency error or tolerance of the particular platform
+ and is a property of the architecture and manufacturing process.
+
+3.3. Command/Status Codes
+
+The kernel routines use the system clock status variable time_status,
+which records whether the clock is synchronized, waiting for a leap
+second, etc. The value of this variable is returned as the result code
+by both the ntp_gettime() and ntp_adjtime() system calls. In addition,
+it can be explicitly read and written using the ntp_adjtime() system
+call, but can be written only in superuser mode. Values presently
+defined in the timex.h header file are as follows:
+
+/*
+ * Clock command/status codes (timex.status)
+ */
+#define TIME_OK 0 /* clock synchronized */
+#define TIME_INS 1 /* insert leap second */
+#define TIME_DEL 2 /* delete leap second */
+#define TIME_OOP 3 /* leap second in progress */
+#define TIME_BAD 4 /* clock not synchronized */
+
+A detailed description of these codes as used by the leap-second state
+machine is given later in this memo. In case of a negative result code,
+the kernel has intercepted an invalid address or (in case of the
+ntp_adjtime() system call), a superuser violation.
+
+4. Technical Summary
+
+In order to more fully understand the workings of the PLL, a stand-alone
+simulator kern.c is included in the kernel distributions. This is an
+implementation of an adaptive-parameter, first-order, type-II phase-lock
+loop. The system clock is implemented using a set of variables and
+algorithms defined in the simulator and driven by explicit offsets
+generated by the simulator. The algorithms include code fragments
+identical to those in the modified kernel routines and operate in the
+same way, but the operations can be understood separately from any
+licensed source code into which these fragments may be integrated. The
+code segments themselves are not derived from any licensed code.
+
+4.1. PLL Simulation
+
+In the simulator the hardupdate() fragment is called by ntp_adjtime() as
+each update is computed to adjust the system clock phase and frequency.
+Note that the time constant is in units of powers of two, so that
+multiplies can be done by simple shifts. The phase variable is computed
+as the offset multiplied by the time constant. Then, the time since the
+last update is computed and clamped to a maximum (for robustness) and to
+zero if initializing. The offset is multiplied (sorry about the ugly
+multiply) by the result and by the square of the time constant and then
+added to the frequency variable. Finally, the frequency variable is
+clamped not to exceed the tolerance. Note that all shifts are assumed to
+be positive and that a shift of a signed quantity to the right requires
+a little dance.
+
+With the defines given, the maximum time offset is determined by the
+size in bits of the long type (32) less the SHIFT_UPDATE scale factor or
+18 bits (signed). The scale factor is chosen so that there is no loss of
+significance in later steps, which may involve a right shift up to 14
+bits. This results in a maximum offset of about +-130 ms. Since
+time_constant must be greater than or equal to zero, the maximum
+frequency offset is determined by the SHIFT_KF (20) scale factor, or
+about +-130 ppm. In the addition step, the value of offset * mtemp is
+represented in 18 + 10 = 28 bits, which will not overflow a long add.
+There could be a loss of precision due to the right shift of up to eight
+bits, since time_constant is bounded at 6. This results in a net worst-
+case frequency error of about 2^-16 us or well down into the oscillator
+phase noise. While the time_offset value is assumed checked before
+entry, the time_phase variable is an accumulator, so is clamped to the
+tolerance on every call. This helps to damp transients before the
+oscillator frequency has been determined, as well as to satisfy the
+correctness assertions if the time-synchronization protocol comes
+unstuck.
+
+The hardclock() fragment is inserted in the hardware timer interrupt
+routine at the point the system clock is to be incremented. Previous to
+this fragment the time_update variable has been initialized to the value
+computed by the adjtime() system call in the stock Unix kernel, normally
+the value of tick plus/minus the tickadj value, which is usually in the
+order of 5 microseconds. When the kernel PLL is in use, adjtime() is
+not, so the time_update value at this point is the value of tick. This
+value, the phase adjustment (time_adj) and the clock phase (time_phase)
+are summed and the total tested for overflow of the microsecond. If an
+overflow occurs, the microsecond (tick) is incremented or decremented,
+depending on the sign of the overflow.
+
+The second_overflow() fragment is inserted at the point where the
+microseconds field of the system time variable is being checked for
+overflow. On rollover of the second the maximum error is increased by
+the tolerance and the time offset is divided by the phase weight
+(SHIFT_KG) and time constant. The time offset is then reduced by the
+result and the result is scaled and becomes the value of the phase
+adjustment. The phase adjustment is then corrected for the calculated
+frequency offset and a fixed offset determined from the fixtick variable
+in some kernel implementations. On rollover of the day, the leap-warning
+indicator is checked and the apparent time adjusted +-1 s accordingly.
+The microtime() routine insures that the reported time is always
+monotonically increasing.
+
+The simulator has been used to check the PLL operation over the design
+envelope of +-128 ms in time error and +-100 ppm in frequency error.
+This confirms that no overflows occur and that the loop initially
+converges in about 15 minutes for timer interrupt rates from 50 Hz to
+1024 Hz. The loop has a normal overshoot of about seven percent and a
+final convergence time of several hours, depending on the initial time
+and frequency error.
+
+4.2. Leap Seconds
+
+It does not seem generally useful in the user application interface to
+provide additional details private to the kernel and synchronization
+protocol, such as stratum, reference identifier, reference timestamp and
+so forth. It would in principle be possible for the application to
+independently evaluate the quality of time and project into the future
+how long this time might be "valid." However, to do that properly would
+duplicate the functionality of the synchronization protocol and require
+knowledge of many mundane details of the platform architecture, such as
+the subnet configuration, reachability status and related variables.
+However, for the curious, the ntp_adjtime() system call can be used to
+reveal some of these mysteries.
+
+However, the user application may need to know whether a leap second is
+scheduled, since this might affect interval calculations spanning the
+event. A leap-warning condition is determined by the synchronization
+protocol (if remotely synchronized), by the timecode receiver (if
+available), or by the operator (if awake). This condition is set by the
+protocol daemon on the day the leap second is to occur (30 June or 31
+December, as announced) by specifying in a ntp_adjtime() system call a
+clock status of either TIME_DEL, if a second is to be deleted, or
+TIME_INS, if a second is to be inserted. Note that, on all occasions
+since the inception of the leap-second scheme, there has never been a
+deletion occasion. If the value is TIME_DEL, the kernel adds one second
+to the system time immediately following second 23:59:58 and resets the
+clock status to TIME_OK. If the value is TIME_INS, the kernel subtracts
+one second from the system time immediately following second 23:59:59
+and resets the clock status to TIME_OOP, in effect causing system time
+to repeat second 59. Immediately following the repeated second, the
+kernel resets the clock status to TIME_OK.
+
+Depending upon the system call implementation, the reported time during
+a leap second may repeat (with the TIME_OOP return code set to advertise
+that fact) or be monotonically adjusted until system time "catches up"
+to reported time. With the latter scheme the reported time will be
+correct before and shortly after the leap second (depending on the
+number of microtime() calls during the leap second itself), but freeze
+or slowly advance during the leap second itself. However, Most programs
+will probably use the ctime() library routine to convert from timeval
+(seconds, microseconds) format to tm format (seconds, minutes,...). If
+this routine is modified to use the ntp_gettime() system call and
+inspect the return code, it could simply report the leap second as
+second 60.
+
+To determine local midnight without fuss, the kernel simply finds the
+residue of the time.tv_sec value mod 86,400, but this requires a messy
+divide. Probably a better way to do this is to initialize an auxiliary
+counter in the settimeofday() routine using an ugly divide and increment
+the counter at the same time the time.tv_sec is incremented in the timer
+interrupt routine. For future embellishment.
+
+4.2. Kernel Variables
+
+The following kernel variables are defined by the new code:
+
+long time_offset = 0; /* time adjustment (us) */
+
+ This variable is used by the PLL to adjust the system time in small
+ increments. It is scaled by (1 << SHIFT_UPDATE) in binary
+ microseconds. The maximum value that can be represented is about +-
+ 512 ms and the minimum value or precision is one microsecond.
+
+long time_constant = 0; /* pll time constant */
+
+ This variable determines the bandwidth or "stiffness" of the PLL.
+ It is used as a shift, with the effective value in positive powers
+ of two. The default value (0) corresponds to a PLL time constant of
+ about 4 minutes.
+
+long time_tolerance = MAXFREQ; /* frequency tolerance (ppm) */
+
+ This variable represents the maximum frequency error or tolerance
+ of the particular platform and is a property of the architecture.
+ It is expressed as a positive number greater than zero in parts-
+ per-million (ppm). The default MAXFREQ (100) is appropriate for
+ conventional workstations.
+
+long time_precision = 1000000 / HZ; /* clock precision (us) */
+
+ This variable represents the maximum error in reading the system
+ clock. It is expressed as a positive number greater than zero in
+ microseconds and is usually based on the number of microseconds
+ between timer interrupts, 3906 usec for the Ultrix kernel, 976 usec
+ for the OSF/1 kernel. However, in cases where the time can be
+ interpolated between timer interrupts with microsecond resolution,
+ such as in the unmodified SunOS kernel and modified Ultrix and
+ OSF/1 kernels, the precision is specified as 1 usec. This variable
+ is computed by the kernel for use by the time-synchronization
+ daemon, but is otherwise not used by the kernel.
+
+long time_maxerror; /* maximum error */
+
+ This variable establishes the maximum error of the indicated time
+ relative to the primary reference source, in microseconds. For NTP,
+ the value is determined as the synchronization distance, which is
+ equal to the root dispersion plus one-half the root delay. It is
+ increased by a small amount (time_tolerance) each second to reflect
+ the clock frequency tolerance. This variable is computed by the
+ time-synchronization daemon and the kernel, but is otherwise not
+ used by the kernel.
+
+long time_esterror; /* estimated error */
+
+ This variable establishes the expected error of the indicated time
+ relative to the primary reference source, in microseconds. For NTP,
+ the value is determined as the root dispersion, which represents
+ the best estimate of the actual error of the system clock based on
+ its past behavior, together with observations of multiple clocks
+ within the peer group. This variable is computed by the time-
+ synchronization daemon and returned in system calls, but is
+ otherwise not used by the kernel.
+
+long time_phase = 0; /* phase offset (scaled us) */
+long time_freq = 0; /* frequency offset (scaled ppm) */
+time_adj = 0; /* tick adjust (scaled 1 / HZ) */
+
+ These variables control the phase increment and the frequency
+ increment of the system clock at each tick. The time_phase variable
+ is scaled by (1 << SHIFT_SCALE) (24) in microseconds, giving a
+ maximum adjustment of about +-128 us/tick and a resolution of about
+ 60 femtoseconds/tick. The time_freq variable is scaled by (1 <<
+ SHIFT_KF) in parts-per-million (ppm), giving it a maximum value of
+ over +-2000 ppm and a minimum value (frequency resolution) of about
+ 1e-5 ppm. The time_adj variable is the actual phase increment in
+ scaled microseconds to add to time_phase once each tick. It is
+ computed from time_phase and time_freq once per second.
+
+long time_reftime = 0; /* time at last adjustment (s) */
+
+ This variable is the second's portion of the system time on the
+ last call to adjtime(). It is used to adjust the time_freq variable
+ as the time since the last update increases.
+
+int fixtick = 1000000 % HZ; /* amortization factor */
+
+ In some systems such as the Ultrix and OSF/1 kernels, the local
+ clock runs at some frequency that does not divide the number of
+ microseconds in the second. In order that the clock runs at a
+ precise rate, it is necessary to introduce an amortization factor
+ into the local timescale, in effect a leap-multimicrosecond. This
+ is not a new kernel variable, but a new use of an existing kernel
+ variable.
+
+4.3. Architecture Constants
+
+Following is a list of the important architecture constants that
+establish the response and stability of the PLL and provide maximum
+bounds on behavior in order to satisfy correctness assertions made in
+the protocol specification.
+
+#define HZ 256 /* timer interrupt frequency (Hz) */
+#define SHIFT_HZ 8 /* log2(HZ) */
+
+ The HZ define (a variable in some kernels) establishes the timer
+ interrupt frequency, 100 Hz for the SunOS kernel, 256 Hz for the
+ Ultrix kernel and 1024 Hz for the OSF/1 kernel. The SHIFT_HZ define
+ expresses the same value as the nearest power of two in order to
+ avoid hardware multiply operations. These are the only parameters
+ that need to be changed for different kernel timer interrupt
+ frequencies.
+
+#define SHIFT_KG 6 /* shift for phase increment */
+#define SHIFT_KF 16 /* shift for frequency increment */
+#define MAXTC 6 /* maximum time constant (shift) */
+
+ These defines establish the response and stability characteristics
+ of the PLL model. The SHIFT_KG and SHIFT_KF defines establish the
+ damping of the PLL and are chosen by analysis for a slightly
+ underdamped convergence characteristic. The MAXTC define
+ establishes the maximum time constant of the PLL.
+
+#define SHIFT_SCALE (SHIFT_KF + SHIFT_HZ) /* shift for scale factor */
+#define SHIFT_UPDATE (SHIFT_KG + MAXTC) /* shift for offset scale
+ * factor */
+#define SHIFT_USEC 16 /* shift for 1 us in external units */
+#define FINEUSEC (1 << SHIFT_SCALE) /* 1 us in scaled units */
+
+ The SHIFT_SCALE define establishes the decimal point on the
+ time_phase variable which serves as a an extension to the low-order
+ bits of the system clock variable. The SHIFT_UPDATE define
+ establishes the decimal point of the phase portion of the
+ ntp_adjtime() update. The SHIFT_USEC define represents 1 us in
+ external units (shift), while the FINEUSEC define represents 1 us
+ in internal units.
+
+#define MAXPHASE 128000 /* max phase error (usec) */
+#define MAXFREQ 100 /* max frequency error (ppm) */
+#define MINSEC 16 /* min interval between updates (s) */
+#define MAXSEC 1200 /* max interval between updates (s) */
+
+ These defines establish the performance envelope of the PLL, one to
+ bound the maximum phase error, another to bound the maximum
+ frequency error and two others to bound the minimum and maximum
+ time between updates. The intent of these bounds is to force the
+ PLL to operate within predefined limits in order to conform to the
+ correctness models assumed by time-synchronization protocols like
+ NTP and DTSS. 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. Note that the MAXPHASE define conforms to the maximum offset
+ allowed in NTP before the system time is reset (by settimeofday(),
+ rather than incrementally adjusted (by ntp_adjtime().
+
+David L. Mills <mills@udel.edu>
+Electrical Engineering Department
+University of Delaware
+Newark, DE 19716
+302 831 8247 fax 302 831 4316
+
+1 April 1992
OpenPOWER on IntegriCloud