diff options
author | wollman <wollman@FreeBSD.org> | 1993-12-21 18:36:48 +0000 |
---|---|---|
committer | wollman <wollman@FreeBSD.org> | 1993-12-21 18:36:48 +0000 |
commit | 8e51e9f1429efc498f923bce8b25b20f47d7c075 (patch) | |
tree | 9db10264d45dc397a38276190303093a450d769e /usr.sbin/xntpd/kernel | |
download | FreeBSD-src-8e51e9f1429efc498f923bce8b25b20f47d7c075.zip FreeBSD-src-8e51e9f1429efc498f923bce8b25b20f47d7c075.tar.gz |
xntpd 3.3b from UDel
Diffstat (limited to 'usr.sbin/xntpd/kernel')
-rw-r--r-- | usr.sbin/xntpd/kernel/Makefile.tmpl | 39 | ||||
-rw-r--r-- | usr.sbin/xntpd/kernel/README | 90 | ||||
-rw-r--r-- | usr.sbin/xntpd/kernel/README.kern | 596 | ||||
-rw-r--r-- | usr.sbin/xntpd/kernel/README.streams | 86 | ||||
-rw-r--r-- | usr.sbin/xntpd/kernel/tty_chu.c | 276 | ||||
-rw-r--r-- | usr.sbin/xntpd/kernel/tty_chu_STREAMS.c | 444 | ||||
-rw-r--r-- | usr.sbin/xntpd/kernel/tty_clk.c | 303 | ||||
-rw-r--r-- | usr.sbin/xntpd/kernel/tty_clk_STREAMS.c | 265 |
8 files changed, 2099 insertions, 0 deletions
diff --git a/usr.sbin/xntpd/kernel/Makefile.tmpl b/usr.sbin/xntpd/kernel/Makefile.tmpl new file mode 100644 index 0000000..700efa6 --- /dev/null +++ b/usr.sbin/xntpd/kernel/Makefile.tmpl @@ -0,0 +1,39 @@ +# +# /src/NTP/REPOSITORY/v3/kernel/Makefile.tmpl,v 3.4 1993/11/05 23:51:26 kardel Exp +# +# +# parse routine that could be used in two places +# +COMPILER= cc +COPTS= -O +AUTHDEFS=-DDES +LIBDEFS= -DBIG_ENDIAN +RANLIB= ranlib +INSTALL= install +CLOCKDEFS= +DEFS= +DEFS_OPT= +DEFS_LOCAL= +# +INCL=-I../include +CFLAGS= $(COPTS) $(DEFS) $(DEFS_LOCAL) $(INCL) +CC= $(COMPILER) +# + +all: + @echo $(DEFS) $(DEFS_LOCAL) $(CLOCKDEFS) | \ + awk '/-DREFCLOCK/ && ( /-D.*CLK/ || /-D.*PPS/ ) { makeit=1; }\ + END { if (makeit) \ + { print ""; \ + print "### STREAMS kernel modules ppsclock, ppsclocd or line disciplines must"; \ + print "### be installed manually if needed"; \ + print "### see kernel/README* for details"; \ + print "### The parse reclock implementation has their own support in"; \ + print "### parse/*."; } }' + +clean: + -@rm -f *~ *.o *.out *.ln make.log Makefile.bak \ + lintlib.errs lint.errs + +distclean: clean + -@rm -f *.orig *.rej .version Makefile diff --git a/usr.sbin/xntpd/kernel/README b/usr.sbin/xntpd/kernel/README new file mode 100644 index 0000000..cf69b13 --- /dev/null +++ b/usr.sbin/xntpd/kernel/README @@ -0,0 +1,90 @@ +This directory contains code for two line disciplines which may +work with BSD-style terminal drivers. While I'll try to cover +installation details for the more useful one here as best I can, +you really should know what you are doing before attempting to +put one of these in your kernel since the details seem to vary +from BSD variant to BSD variant. + +Tty_clk.c contains a generic clock support line discipline. +The terminal driver is actually run in raw mode, giving you an +eight bit data path. Instead of delivering the data +character-by-character, however, the line discipline collects +characters until one of two magic characters (your current erase +and kill characters. Don't throw up) is received. A timestamp +is then taken (by calling microtime()), inserted in the input +buffer after the magic character, and the whole mess made available +for input by the application. Both select() and SIGIO are supported +by the discipline. + +Tty_chu.c is a special purpose line discipline for receiving +the CHU time code. It understands enough about the format of the +code CHU transmits to filter out errors, and delivers an entire +ten character code group to the application all at once, including +a timestamp for each character. The structure the code group is +delivered in is defined in chudefs.h. Note that this line discipline +is old and could use some rewriting for better portability. Please +drop me a line if you are interested in using this. + +To install the clock line discipline, do something like the following: + +(1) Copy tty_clk.c into /sys/sys + +(2) Edit /sys/sys/tty_conf.c. You will want to include some facsimile + of the following lines: + +#include "clk.h" +#if NCLK > 0 +int clkopen(), clkclose(), clkwrite(), clkinput(), clkioctl(); +#endif + +#if NCLK > 0 + { clkopen, clkclose, ttread, clkwrite, clkioctl, + clkinput, nodev, nulldev, ttstart, nullmodem, /* 10- CLKLDISC */ + ttselect }, +#else + { nodev, nodev, nodev, nodev, nodev, + nodev, nodev, nodev, nodev, nodev, + nodev }, +#endif + + In Ultrix 4.2a and 4.3 the file to edit is /sys/data/tty_conf_data.c. + The lines should be + +#if NCLK > 0 + clkopen, clkclose, ttread, clkwrite, clkioctl, /* 10 */ + clkinput, nodev, nulldev, ttstart, nulldev, +#else + nodev, nodev, nodev, nodev, nodev, + nodev, nodev, nodev, nodev, nodev, +#endif + + Note that if your kernel doesn't include the ??select() entry in + the structure (i.e. there are only 10 entry points in the structure) + just leave it out. Also note that the number you give the line + discipline (10 in my kernel) will be specific to your kernel and + will depend on what is in there already. The entries sould be in + order with no missing space; that is, if there are only seven + disciplines already defined and you want to use 10 for good reason, + you should define a dummy 9th entry like this + + nodev, nodev, nodev, nodev, nodev, /* 9 */ + nodev, nodev, nodev, nodev, nodev, + +(3) Edit /sys/h/ioctl.h and include a line (somewhere near where other + line disciplines are defined) like: + +#define CLKLDISC 10 /* clock line discipline */ + + The `10' should match what you used in /sys/sys/tty_conf.c. + +(4) Edit /sys/conf/files and add a line which looks like: + +sys/tty_clk.c optional clk + +(5) Edit the configuration file for the machine you want to use + the clock line discipline on to include the following: + +pseudo-device clk 4 + +(6) Run config, then make clean, then make depend, then make vmunix. + Then reboot the new kernel. diff --git a/usr.sbin/xntpd/kernel/README.kern b/usr.sbin/xntpd/kernel/README.kern new file mode 100644 index 0000000..64ba9c5 --- /dev/null +++ b/usr.sbin/xntpd/kernel/README.kern @@ -0,0 +1,596 @@ +Precision Time and Frequency Synchronization Using Modified Kernels + +1. Introduction + +This memo describes replacements for certain SunOS and Ultrix kernel +routines 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 certain models of the +DECstation 5000 product line, the new routines provide improved +precision to +-1 microsecond (us) (SunOS 4.1.1 already does provide +precision to +-1 us). The current public NTP distribution cooperates +with these kernel routines to provide synchronization in principle to +within a microsecond, but in practice this is limited by the short-term +stability of the oscillator that drives the timer interrupt. + +This memo describes the principles behind the design and operation of +the software. There are two versions of the software, one that operates +with the SunOS 4.1.1 kernel and the other that operates with the Ultrix +4.2a kernel (and probably the 4.3 kernel, although this has not been +tested). A detailed description of the variables and algorithms is given +in the hope that similar improvements can be incorporated in Unix +kernels for other machines. The software itself is not included in this +memo, since it involves licensed code. Detailed instructions on where to +obtain it for either SunOS or Ultrix will be given separately. + +The principle function added to the SunOS and Ultrix kernels is to +change the way the system clock is controlled, in order to provide +precision time and frequency adjustments. Another function utilizes an +undocumented counter in the DECstation hardware to provide precise time +to the microsecond. This function can be used only with the DECstation +5000/240 and possibly others that use the same input/output chipset. + +2. Design Principles + +In order to understand how these routines work, it is useful to consider +how most Unix systems maintain the system clock. In the original design +a hardware timer interrupts the kernel at some fixed rate, such as 100 +Hz in the SunOS kernel and 256 Hz in the Ultrix kernel. Since 256 does +not evenly divide the second in microseconds, the kernel inserts 64 us +once each second so that the system clock stays in step with real time. +The time returned by the gettimeofday() routine is thus characterized by +255 advances of 3906 us plus one of 3970 us. + +Also in the original design it is possible to slew the system clock to a +new offset using the 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. + +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 us, which is a reasonable value +for NTP-synchronized hosts on a local network, and let the onboard +oscillator tolerance be 100 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 modified kernel routines 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 time +adjustment is added to the composite time value and overflows handled as +required. The quantity to add is computed from the adjtime() call 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 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 removed for the old scheme. The new adjtime() routine needs to be +called only as each new time update is determined, which in NTP occurs +at intervals of from 64 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. + +Note that the degree to which the adjtime() adjustment can be made is +limited to a specific maximum value, presently +-128 milliseconds (ms), +in order to achieve microsecond resolution. 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. Nevertheless, the limit applies to each adjtime() call and +it is possible, but not recommended, that this routine is called at +intervals smaller than 64 seconds, which is the NTP lower limit. + +For the most accurate and stable operation, adjtime() should be called +at specified intervals; however, the PLL is quite forgiving and neither +moderate loss of updates nor variations in the length of the interval is +serious. The current engineering parameters have been optimized for +intervals not greater than about 64 s. For larger 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 +determinated, 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. This is provided +by a small change in the second argument of the kernel adjtime() calling +sequence, which is documented later in this memo. Ordinarily, only the +daemon will call the adjtime() routine, so the modified calling sequence +is easily accommodated. Other than this change, the operation of +adjtime() is transparent to the original. + +In the DECstation 5000/240 and possibly other models there happens to be +an undocumented hardware register that counts system bus cycles at a +rate of 25 MHz. The new kernel routines test for the CPU type and, in +the case of the '240, 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() system call. This +routine calls the kernel routine microtime(), 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() or microtime() will +continue their present behavior. + +The new kernel routines include provisions for error statistics (maximum +error and estimated error), leap seconds and system clock status. These +are intended to support applications that need such things; however, +there are no applications other than the time-synchronization daemon +itself that presently use them. At issue is the manner in which these +data can be provided to application clients, such as new system calls +and data interfaces. While a proposed interface is described later in +this memo, it has not yet been implemented. This is an area for further +study. + +While any time-synchronization daemon can in principle be modified to +use the new code, the most likely will be users of the xntp3 +distribution of NTP. The code in the xntp3 distribution determines +whether the new kernel code is in use and automatically reconfigures as +required. When the new code is in use, the daemon reads the frequency +offset from a file and provides it and the initial time constant via +adjtime(). In subsequent calls to adjtime(), only the time adjustment +and time constant are affected. The daemon reads the frequency from the +kernel (returned as the second argument of adjtime()) at intervals of +one hour and writes it to the file. + +3. Technical Description + +Following is a technical description of how the new scheme works in +terms of the variables and algorithms involved. These components are +discussed as a distinct entity and do not involve coding details +specific to the Ultrix kernel. The algorithms involve only minor changes +to the system clock and interval timer routines, but do not in +themselves provide a conduit for application programs to learn the +system clock status or statistics of the time-synchronization process. +In a later section a number of new system calls are proposed to do this, +along with an interface specification. + +The new scheme works like the companion simulator called kern.c and +included in this directory. This stand-alone simulator includes code +fragments identical to those in the modified kernel routines and +operates in the same way. The system clock is implemented in the kernel +using a set of variables and algorithms defined below and in the +simulator. The algorithms are driven by explicit calls from the +synchronization protocol as each time update is computed. The clock is +read and set using the gettimeofday() and settimeofday() system calls, +which operate in the same way as the originals, but return a status word +describing the state of the system clock. + +Once the system clock has been set, the adjtime() system call is used to +provide periodic updates including the time offset and possibly +frequency offset and time constant. With NTP this occurs at intervals of +from 64 s to 1024 s, deending on the time constant value. The kernel +implements an adaptive-parameter, first-order, type-II, phase-lock loop +(PLL) in order to integrate this offset into the phase and frequency of +the system clock. The kernel keeps track of the time of the last update +and adjusts the maximum error to grow by an amount equal to the +oscillator frequency tolerance times the elapsed time since the last +update. + +Occasionally, it is necessary to adjust the PLL parameters in response +to environmental conditions, such as leap-second warning and oscillator +stability observations. While the interface to do this has not yet been +implemented, proposals to to that are included in a later section. A +system call (setloop()) is used on such occasions to communicate these +data. In addition, a system call (getloop())) is used to extract these +data from the kernel for monitoring purposes. + +All programs utilize 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 by each system call. It can +be set explicitly by the setloop() system call and implicitly by the +settimeofday() system call and in the timer-interrupt routine. Values +presently defined in the header file timex.h are as follows: + +int time_status = TIME_BAD; /* clock synchronization status */ + +#define TIME_UNS 0 /* unspecified or unknown */ +#define TIME_OK 1 /* operation succeeded */ +#define TIME_INS 1 /* insert leap second at end of current day */ +#define TIME_DEL 2 /* delete leap second at end of current day */ +#define TIME_OOP 3 /* leap second in progress */ +#define TIME_BAD 4 /* system clock is not synchronized */ +#define TIME_ADR -1 /* operation failed: invalid address */ +#define TIME_VAL -2 /* operation failed: invalid argument */ +#define TIME_PRV -3 /* operation failed: priviledged operation */ + +In case of a negative result code, the operation has failed; however, +some variables may have been modified before the error was detected. +Note that the new system calls never return a value of zero, so it is +possible to determine whether the old routines or the new ones are in +use. The syntax of the modified adjtime() is as follows: + +/* + * adjtime - adjuts system time + */ +#include <sys/timex.h> + +int gettimexofday(tp, fiddle) + +struct timeval *tp; /* system time adjustment*/ +struct timeval *fiddle; /* sneak path */ + +On entry the "timeval" sneak path is coded: + +struct timeval { + long tv_sec = time_constant; /* time constant */ + long tv_usec = time_freq; /* new frequency offset */ +} + +However, the sneak is ignored if fiddle is the null pointer and the new +frequency offset is ignored if zero. + +The value returned on exit is the system clock status defined above. The +"timeval" sneak path is modified as follows: + +struct timeval { + long tv_sec = time_precision; /* system clock precision */ + long tv_usec = time_freq; /* current frequency offset */ +} + +3.1. Kernel Variables + +The following variables are used 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 +-130 ms and the +minimum value or precision is about one nanosecond. + +long time_constant = SHIFT_TAU; /* 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 +optimum value for this variable is equal to 1/64 times the update +interval. The default value SHIFT_TAU (0) corresponds to a PLL time +constant of about one hour or an update interval of about one minute, +which is appropriate for typical uncompensated quartz oscillators used +in most computing equipment. Values larger than four are not useful, +unless the local clock timebase is derived from a precision oscillator. + +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, in the case of the Ultrix kernel, 3906. However, in cases +where the time can be interpolated between timer interrupts with +microsecond resolution, the precision is specified as 1. This variable +is computed by the kernel for use by the time-synchronization daemon, +but is otherwise not used by the kernel. + +struct timeval time_maxerror; /* maximum error */ + +This variable represents the maximum error, expressed as a Unix timeval, +of the system clock. For NTP, it is computed as the synchronization +distance, which is equal to one-half the root delay plus the root +dispersion. 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 for use by +the application program, but is otherwise not used by the kernel. + +struct timeval time_esterror; /* estimated error */ + +This variable represents the best estimate of the actual error, +expressed as a Unix timeval, 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 for +use by the application program, but is otherwise not used by the kernel. + +The PLL itself is controlled by the following variables: + +long time_phase = 0; /* phase offset (scaled us) */ +long time_freq = 0; /* frequency offset (scaled ppm) */long +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 of the clock. The time_phase variable +is scaled by (1 << SHIFT_SCALE) in binary microseconds, giving a minimum +value (time resolution) of 9.3e-10 us. The time_freq variable is scaled +by (1 << SHIFT_KF) in parts-per-million (ppm), giving it a maximum value +of about +-130 ppm and a minimum value (frequency resolution) of 6e-8 +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. + +The HZ define establishes the timer interrupt frequency, 256 Hz for the +Ultrix kernel and 100 Hz for the SunOS 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 timer interrupt rates. + +#define HZ 256 /* timer interrupt frequency (Hz) */ +#define SHIFT_HZ 8 /* log2(HZ) */ + +The following defines establish the engineering parameters of the PLL +model. They are chosen for an initial convergence time of about an hour, +an overshoot of about seven percent and a final convergence time of +several hours, depending on initial frequency error. + +#define SHIFT_KG 10 /* shift for phase increment */ +#define SHIFT_KF 24 /* shift for frequency increment */ +#define SHIFT_TAU 0 /* default time constant (shift) */ + +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 adjtime() update. The FINEUSEC define +represents 1 us in scaled units. + +#define SHIFT_SCALE 28 /* shift for scale factor */ +#define SHIFT_UPDATE 14 /* shift for offset scale factor */ +#define FINEUSEC (1 << SHIFT_SCALE) /* 1 us in scaled units */ + +The FINETUNE define represents the residual, in ppm, to be added to the +system clock variable in addition to the integral 1-us value given by +tick. This allows a systematic frequency offset in cases where the timer +interrupt frequency does not exactly divide the second in microseconds. + +#define FINETUNE (1000000 - (1000000 / HZ) * HZ) /* frequency adjustment + * for non-isochronous HZ (ppm) */ + +The following four defines establish the performance envelope of the +PLL, one to bound the maximum phase error, another to bound the maximum +frequency error and the last two 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, rather than incrementally adjusted. + +#define MAXPHASE 128000 /* max phase error (us) */ +#define MINSEC 64 /* min interval between updates (s) */ +#define MAXFREQ 100 /* max frequency error (ppm) */ +#define MAXSEC 1024 /* max interval between updates (s) */ + +3.2. Code Segments + +The code segments illustrated in the simulator should make clear the +operations at various points in the code. These segments are not derived +from any licensed code. The hardupdate() fragment is called by adjtime() +to update the system clock phase and frequency. This is an +implementation of an adaptive-parameter, first-order, type-II phase-lock +loop. 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 litle 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 (14) 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 +the time_constant must be greater than or equal to zero, the maximum +frequency offset is determined by the SHIFT_KF (24) 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 four. 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. The phase +adjustment (time_adj) is added to the clock phase (time_phase) and +tested for overflow of the microsecond. If an overflow occurs, the +microsecond (tick) in incremented or decremented. + +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. 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 FINETUNE which is a property of the architecture. On +rollover of the day the leap-warning indicator is checked and the +apparent time adjusted +-1 s accordingly. The gettimeofday() routine +insures that the reported time is always monotonically increasing. + +The simulator can be used to check the loop operation over the design +range 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 50-60 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 initional frequency +error. + +3.3. Leap Seconds + +The leap-warning condition is determined by the synchronization protocol +(if remotely synchronized), by the timecode receiver (if available), or +by the operator (if awake). The time_status value must be set on the day +the leap event is to occur (30 June or 31 December) and is automatically +reset after the event. If the value is TIME_DEL, the kernel adds one +second to the system time immediately following second 23:59:58 and +resets time_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 time_status to TIME_OOP, in effect causing system +time to repeat second 59. Immediately following the repeated second, the +kernel resets time_status to TIME_OK. + +Depending upon the system call implementation, the reported time during +a leap second may repeat (with a 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 after the leap second, 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 +inspect the return code of the gettimeofday() routine, 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. Proposed Application Program Interface + +Most programs read the system clock using the gettimeofday() system +call, which returns the system time and time-zone data. In the modified +5000/240 kernel, the gettimeofday() routine calls the microtime() +routine, which interpolates between hardware timer interrupts to a +precision of +-1 microsecond. However, the synchronization protocol +provides additional information that will be of interest in many +applications. For some applications it is necessary to know the maximum +error of the reported time due to all causes, including those due to the +system clock reading error, oscillator frequency error and accumulated +errors due to intervening time servers on the path to a primary +reference source. However, for those protocols that adjust the system +clock frequency as well as the time offset, the errors expected in +actual use will almost always be much less than the maximum error. +Therefore, it is useful to report the estimated error, as well as the +maximum error. + +It does not seem useful 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 tick value, reachability +status and related variables. Therefore, the application interface does +not reveal anything except the time, timezone and error data. + +With respect to NTP, the data maintained by the protocol include the +roundtrip delay and total dispersion to the source of synchronization. +In terms of the above, the maximum error is computed as half the delay +plus the dispersion, while the estimated error is equal to the +dispersion. These are reported in timeval structures. A new system call +is proposed that includes all the data in the gettimeofday() plus the +two new timeval structures. + +The proposed interface involves modifications to the gettimeofday(), +settimeofday() and adjtime() system calls, as well as new system calls +to get and set various system parameters. In order to minimize +confusion, by convention the new system calls are named with an "x" +following the "time"; e.g., adjtime() becomes adjtimex(). The operation +of the modified gettimexofday(), settimexofday() and adjtimex() system +calls is identical to that of their prototypes, except for the error +quantities and certain other side effects, as documented below. By +convention, a NULL pointer can be used in place of any argument, in +which case the argument is ignored. + +The synchronization protocol daemon needs to set and adjust the system +clock and certain other kernel variables. It needs to read these +variables for monitoring purposes as well. The present list of these +include a subset of the variables defined previously: + +long time_precision +long time_timeconstant +long time_tolerance +long time_freq +long time_status + +/* + * gettimexofday, settimexofday - get/set date and time + */ +#include <sys/timex.h> + +int gettimexofday(tp, tzp, tmaxp, testp) + +struct timeval *tp; /* system time */ +struct timezone *tzp; /* timezone */ +struct timeval *tmaxp; /* maximum error */ +struct timeval *testp; /* estimated error */ + +The settimeofday() syntax is identical. Note that a call to +settimexofday() automatically results in the system being declared +unsynchronized (TIME_BAD return code), since the synchronization +condition can only be achieved by the synchronization daemon using an +internal or external primary reference source and the adjtimex() system +call. + +/* + * adjtimex - adjust system time + */ +#include <sys/timex.h> + +int adjtimex(tp, tzp, freq, tc) + +struct timeval *tp; /* system time */ +struct timezone *tzp; /* timezone */ +long freq; /* frequency adjustment */ +long tc; /* time constant */ + +/* + * getloop, setloop - get/set kernel time variables + */ +#include <sys/timex.h> + +int getloop(code, argp) + +int code; /* operation code */ +long *argp; /* argument pointer */ + +The paticular kernal variables affected by these routines are selected +by the operation code. Values presently defined in the header file +timex.h are as follows: + +#define TIME_PREC 1 /* precision (log2(sec)) */ +#define TIME_TCON 2 /* time constant (log2(sec) */ +#define TIME_FREQ 3 /* frequency tolerance */ +#define TIME_FREQ 4 /* frequency offset (scaled) */ +#define TIME_STAT 5 /* status (see return codes) */ + +The getloop() syntax is identical. + +Comments welcome, but very little support is available: + +David L. Mills +Electrical Engineering Department +University of Delaware +Newark, DE 19716 +302 831 8247 fax 302 831 4316 +mills@udel.edu diff --git a/usr.sbin/xntpd/kernel/README.streams b/usr.sbin/xntpd/kernel/README.streams new file mode 100644 index 0000000..26c2825 --- /dev/null +++ b/usr.sbin/xntpd/kernel/README.streams @@ -0,0 +1,86 @@ +Some kernels don't support additional user defined line disciplines. +Especially notable in this regard is SunOS and System V. They +provide similar support in the form of "Streams". Accordingly, +included in this directory is a pair of STREAMS modules to +replace the line disciplines that provide clock support for +xntpd. Notice that the "clkdefs.h" file is not used in the +original line discipline, but the "chudefs.h" file is the +same one used in the original line discipline. + +TO INSTALL A NEW STREAMS DRIVER: + +1. Copy your choice to /sys/os, removing the "_STREAMS" in the +filename. + +2. Copy the appropriate *defs.h file to /usr/include/sys, +then link it (with ln) to /sys/sys. + +In the following steps, substitute "clk" for "chu" if you're +installing the clk driver. + +3. Append to /sys/conf.common/files.cmn: + +os/tty_chu.c optional chu + +4. Edit /sys/sun/str_conf.c. You'll want to add lines in three +places. It'll be sort of clear where when you see the file. + +. +. +. +#include "chu.h" +. +. +. +#if NCHU > 0 +extern struct streamtab chuinfo; +#endif +. +. +. +#if NCHU > 0 + { "chu", &chuinfo }, +#endif +. +. +. + +At this point, the kernel-making "config [k-name] ; cd ../[k-name] ; make" +should produce a kernel just as it did before. If it fouls up, +something's wrong. + +5. Edit /sys/[arch]/conf/[k-name] (substituting the architecture and +kernel name) to stick in: + +pseudo-device chu4 # CHU clock support + +You can change 4 to anything you like. It will limit the number +of instantiations of the chu driver you can use at the same time. + +6. Make a new kernel and boot it. + +HOW TO USE THE CHU STREAMS DRIVER: + +The driver should act exactly like the line discipline. +After setting the raw mode, and exclusive access (if you want), +pop off all the extra streams, then push the chu module +on. From then on, any reads from the file in question +will return chucode structures as defined in chudefs.h. +Depending on the settings of PEDANTIC and ANAL_RETENTIVE +used when compiling the kernel, some checking of the +data may or may not be necessary. + +HOW TO USE THE CLK STREAMS DRIVER: + +First, it should be noted that a new ioctl() has been defined. +The CLK_SETSTR ioctl takes a pointer to a string of no more +than CLK_MAXSTRSIZE characters. Until the first CLK_SETSTR +is performed, the driver will simply pass through characters. +Once it is passed a string, then any character in that string +will be immediately followed by a struct timeval. You can +change the string whenever you want by doing another +CLK_SETSTR. The character must be an exact, 8 bit match. +The character '\000' cannot, unfortunately, be stamped. +Passing an empty string to CLK_SETSTR turns off stamping. +Passing NULL will produce undefined results. + diff --git a/usr.sbin/xntpd/kernel/tty_chu.c b/usr.sbin/xntpd/kernel/tty_chu.c new file mode 100644 index 0000000..4615875 --- /dev/null +++ b/usr.sbin/xntpd/kernel/tty_chu.c @@ -0,0 +1,276 @@ +/* tty_chu.c,v 3.1 1993/07/06 01:07:30 jbj Exp + * tty_chu.c - CHU line driver + */ + +#include "chu.h" +#if NCHU > 0 + +#include "../h/param.h" +#include "../h/types.h" +#include "../h/systm.h" +#include "../h/dir.h" +#include "../h/user.h" +#include "../h/ioctl.h" +#include "../h/tty.h" +#include "../h/proc.h" +#include "../h/file.h" +#include "../h/conf.h" +#include "../h/buf.h" +#include "../h/uio.h" + +#include "../h/chudefs.h" + +/* + * Line discipline for receiving CHU time codes. + * Does elementary noise elimination, takes time stamps after + * the arrival of each character, returns a buffer full of the + * received 10 character code and the associated time stamps. + */ +#define NUMCHUBUFS 3 + +struct chudata { + u_char used; /* Set to 1 when structure in use */ + u_char lastindex; /* least recently used buffer */ + u_char curindex; /* buffer to use */ + u_char sleeping; /* set to 1 when we're sleeping on a buffer */ + struct chucode chubuf[NUMCHUBUFS]; +} chu_data[NCHU]; + +/* + * Number of microseconds we allow between + * character arrivals. The speed is 300 baud + * so this should be somewhat more than 30 msec + */ +#define CHUMAXUSEC (50*1000) /* 50 msec */ + +int chu_debug = 0; + +/* + * Open as CHU time discipline. Called when discipline changed + * with ioctl, and changes the interpretation of the information + * in the tty structure. + */ +/*ARGSUSED*/ +chuopen(dev, tp) + dev_t dev; + register struct tty *tp; +{ + register struct chudata *chu; + + /* + * Don't allow multiple opens. This will also protect us + * from someone opening /dev/tty + */ + if (tp->t_line == CHULDISC) + return (EBUSY); + ttywflush(tp); + for (chu = chu_data; chu < &chu_data[NCHU]; chu++) + if (!chu->used) + break; + if (chu >= &chu[NCHU]) + return (EBUSY); + chu->used++; + chu->lastindex = chu->curindex = 0; + chu->sleeping = 0; + chu->chubuf[0].ncodechars = 0; + tp->T_LINEP = (caddr_t) chu; + return (0); +} + +/* + * Break down... called when discipline changed or from device + * close routine. + */ +chuclose(tp) + register struct tty *tp; +{ + register int s = spl5(); + + ((struct chudata *) tp->T_LINEP)->used = 0; + tp->t_cp = 0; + tp->t_inbuf = 0; + tp->t_rawq.c_cc = 0; /* clear queues -- paranoid */ + tp->t_canq.c_cc = 0; + tp->t_line = 0; /* paranoid: avoid races */ + splx(s); +} + +/* + * Read a CHU buffer. Sleep on the current buffer + */ +churead(tp, uio) + register struct tty *tp; + struct uio *uio; +{ + register struct chudata *chu; + register struct chucode *chucode; + register int s; + + if ((tp->t_state&TS_CARR_ON)==0) + return (EIO); + + chu = (struct chudata *) (tp->T_LINEP); + + s = spl5(); + chucode = &(chu->chubuf[chu->lastindex]); + while (chu->curindex == chu->lastindex) { + chu->sleeping = 1; + sleep((caddr_t)chucode, TTIPRI); + } + chu->sleeping = 0; + if (++(chu->lastindex) >= NUMCHUBUFS) + chu->lastindex = 0; + splx(s); + + return (uiomove((caddr_t)chucode, sizeof(*chucode), UIO_READ, uio)); +} + +/* + * Low level character input routine. + * If the character looks okay, grab a time stamp. If the stuff in + * the buffer is too old, dump it and start fresh. If the character is + * non-BCDish, everything in the buffer too. + */ +chuinput(c, tp) + register int c; + register struct tty *tp; +{ + register struct chudata *chu = (struct chudata *) tp->T_LINEP; + register struct chucode *chuc; + register int i; + long sec, usec; + struct timeval tv; + + /* + * Do a check on the BSDness of the character. This delays + * the time stamp a bit but saves a fair amount of overhead + * when the static is bad. + */ + if (((c) & 0xf) > 9 || (((c)>>4) & 0xf) > 9) { + chuc = &(chu->chubuf[chu->curindex]); + chuc->ncodechars = 0; /* blow all previous away */ + return; + } + + /* + * Call microtime() to get the current time of day + */ + microtime(&tv); + + /* + * Compute the difference in this character's time stamp + * and the last. If it exceeds the margin, blow away all + * the characters currently in the buffer. + */ + chuc = &(chu->chubuf[chu->curindex]); + i = (int)chuc->ncodechars; + if (i > 0) { + sec = tv.tv_sec - chuc->codetimes[i-1].tv_sec; + usec = tv.tv_usec - chuc->codetimes[i-1].tv_usec; + if (usec < 0) { + sec -= 1; + usec += 1000000; + } + if (sec != 0 || usec > CHUMAXUSEC) { + i = 0; + chuc->ncodechars = 0; + } + } + + /* + * Store the character. If we're done, have to tell someone + */ + chuc->codechars[i] = (u_char)c; + chuc->codetimes[i] = tv; + + if (++i < NCHUCHARS) { + /* + * Not much to do here. Save the count and wait + * for another character. + */ + chuc->ncodechars = (u_char)i; + } else { + /* + * Mark this buffer full and point at next. If the + * next buffer is full we overwrite it by bumping the + * next pointer. + */ + chuc->ncodechars = NCHUCHARS; + if (++(chu->curindex) >= NUMCHUBUFS) + chu->curindex = 0; + if (chu->curindex == chu->lastindex) + if (++(chu->lastindex) >= NUMCHUBUFS) + chu->lastindex = 0; + chu->chubuf[chu->curindex].ncodechars = 0; + + /* + * Wake up anyone sleeping on this. Also wake up + * selectors and/or deliver a SIGIO as required. + */ + if (tp->t_rsel) { + selwakeup(tp->t_rsel, tp->t_state&TS_RCOLL); + tp->t_state &= ~TS_RCOLL; + tp->t_rsel = 0; + } + if (tp->t_state & TS_ASYNC) + gsignal(tp->t_pgrp, SIGIO); + if (chu->sleeping) + (void) wakeup((caddr_t)chuc); + } +} + +/* + * Handle ioctls. We reject all tty-style except those that + * change the line discipline. + */ +chuioctl(tp, cmd, data, flag) + struct tty *tp; + int cmd; + caddr_t data; + int flag; +{ + + if ((cmd>>8) != 't') + return (-1); + switch (cmd) { + case TIOCSETD: + case TIOCGETD: + case TIOCGETP: + case TIOCGETC: + return (-1); + } + return (ENOTTY); /* not quite appropriate */ +} + + +chuselect(dev, rw) + dev_t dev; + int rw; +{ + register struct tty *tp = &cdevsw[major(dev)].d_ttys[minor(dev)]; + struct chudata *chu; + int s = spl5(); + + chu = (struct chudata *) (tp->T_LINEP); + + switch (rw) { + + case FREAD: + if (chu->curindex != chu->lastindex) + goto win; + if (tp->t_rsel && tp->t_rsel->p_wchan == (caddr_t)&selwait) + tp->t_state |= TS_RCOLL; + else + tp->t_rsel = u.u_procp; + break; + + case FWRITE: + goto win; + } + splx(s); + return (0); +win: + splx(s); + return (1); +} +#endif NCHU diff --git a/usr.sbin/xntpd/kernel/tty_chu_STREAMS.c b/usr.sbin/xntpd/kernel/tty_chu_STREAMS.c new file mode 100644 index 0000000..eea792d --- /dev/null +++ b/usr.sbin/xntpd/kernel/tty_chu_STREAMS.c @@ -0,0 +1,444 @@ +/* tty_chu_STREAMS.c,v 3.1 1993/07/06 01:07:32 jbj Exp + * CHU STREAMS module for SunOS 4.1.x + * + * Version 2.1 + * + * Copyright 1991-1993, Nick Sayer + * + * Special thanks to Greg Onufer for his debug assists. + * Special thanks to Matthias Urlichs for the loadable driver support + * code. + * + * Should be PUSHed directly on top of a serial I/O channel. + * Provides complete chucode structures to user space. + * + * COMPILATION: + * + * To make a SunOS 4.1.x compatable loadable module (from the ntp kernel + * directory): + * + * % cc -c -I../include -DLOADABLE tty_chu_STREAMS.c + * + * The resulting .o file is the loadable module. Modload it + * with -entry _chuinit. + * + * You can also add it into the kernel by hacking it into the streams + * table in the kernel, then adding it to config: + * + * pseudo-device chuN + * + * where N is the maximum number of concurent chu sessions you expect + * to have. + * + * HISTORY: + * + * v2.1 - Added 'sixth byte' heuristics. + * v2.0 - first version with an actual version number. + * Added support for new CHU 'second 31' data format. + * Deleted PEDANTIC and ANAL_RETENTIVE. + * + */ + +#ifndef LOADABLE +# include "chu.h" +#else +# ifndef NCHU +# define NCHU 3 +# define KERNEL +# endif +#endif + +#if NCHU > 0 + +/* + * Number of microseconds we allow between + * character arrivals. The speed is 300 baud + * so this should be somewhat more than 30 msec + */ +#define CHUMAXUSEC (60*1000) /* 60 msec */ + +#include <sys/types.h> +#include <sys/stream.h> +#include <sys/param.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/errno.h> +#include <sys/user.h> + +#include <sys/chudefs.h> + +static struct module_info rminfo = { 0, "chu", 0, INFPSZ, 0, 0 }; +static struct module_info wminfo = { 0, "chu", 0, INFPSZ, 0, 0 }; +static int chuopen(), churput(), chuwput(), chuclose(); + +static struct qinit rinit = { churput, NULL, chuopen, chuclose, NULL, + &rminfo, NULL }; + +static struct qinit winit = { chuwput, NULL, NULL, NULL, NULL, + &wminfo, NULL }; + +struct streamtab chuinfo = { &rinit, &winit, NULL, NULL }; + +/* + * Here's our private data type and structs + */ +struct priv_data +{ + char in_use; + struct chucode chu_struct; +} our_priv_data[NCHU]; + +#ifdef LOADABLE + +#ifdef sun +#include <sys/conf.h> +#include <sys/buf.h> +#include <sundev/mbvar.h> +#include <sun/autoconf.h> +#include <sun/vddrv.h> + +static struct vdldrv vd = +{ + VDMAGIC_PSEUDO, + "chu", + NULL, NULL, NULL, 0, 0, NULL, NULL, 0, 0, +}; + +static struct fmodsw *chu_fmod; + +/*ARGSUSED*/ +chuinit (fc, vdp, vdi, vds) + unsigned int fc; + struct vddrv *vdp; + addr_t vdi; + struct vdstat *vds; +{ + switch (fc) { + case VDLOAD: + { + int dev, i; + + /* Find free entry in fmodsw */ + for (dev = 0; dev < fmodcnt; dev++) { + if (fmodsw[dev].f_str == NULL) + break; + } + if (dev == fmodcnt) + return (ENODEV); + chu_fmod = &fmodsw[dev]; + + /* If you think a kernel would have strcpy() you're mistaken. */ + for (i = 0; i <= FMNAMESZ; i++) + chu_fmod->f_name[i] = wminfo.mi_idname[i]; + + chu_fmod->f_str = &chuinfo; + } + vdp->vdd_vdtab = (struct vdlinkage *) & vd; + + { + int i; + + for (i=0; i<NCHU; i++) + our_priv_data[i].in_use=0; + } + + return 0; + case VDUNLOAD: + { + int dev; + + for (dev = 0; dev < NCHU; dev++) + if (our_priv_data[dev].in_use) { + /* One of the modules is still open */ + return (EBUSY); + } + } + chu_fmod->f_name[0] = '\0'; + chu_fmod->f_str = NULL; + return 0; + case VDSTAT: + return 0; + default: + return EIO; + } +} + +#endif + +#else + +char chu_first_open=1; + +#endif + +/*ARGSUSED*/ +static int chuopen(q, dev, flag, sflag) +queue_t *q; +dev_t dev; +int flag; +int sflag; +{ + int i; + +#ifndef LOADABLE + if (chu_first_open) + { + chu_first_open=0; + + for(i=0;i<NCHU;i++) + our_priv_data[i].in_use=0; + } +#endif + + for(i=0;i<NCHU;i++) + if (!our_priv_data[i].in_use) + { + ((struct priv_data *) (q->q_ptr))=&(our_priv_data[i]); + our_priv_data[i].in_use++; + our_priv_data[i].chu_struct.ncodechars = 0; + return 0; + } + + u.u_error = EBUSY; + return (OPENFAIL); + +} + +/*ARGSUSED*/ +static int chuclose(q, flag) +queue_t *q; +int flag; +{ + ((struct priv_data *) (q->q_ptr))->in_use=0; + + return (0); +} + +/* + * Now the crux of the biscuit. + * + * We will be passed data from the man downstairs. If it's not a data + * packet, it must be important, so pass it along unmunged. If, however, + * it is a data packet, we're gonna do special stuff to it. We're going + * to pass each character we get to the old line discipline code we + * include below for just such an occasion. When the old ldisc code + * gets a full chucode struct, we'll hand it back upstairs. + * + * chuinput takes a single character and q (as quickly as possible). + * passback takes a pointer to a chucode struct and q and sends it upstream. + */ + +void chuinput(); +void passback(); + +static int churput(q, mp) +queue_t *q; +mblk_t *mp; +{ + mblk_t *bp; + + switch(mp->b_datap->db_type) + { + case M_DATA: + for(bp=mp; bp!=NULL; bp=bp->b_cont) + { + while(bp->b_rptr < bp->b_wptr) + chuinput( ((u_char)*(bp->b_rptr++)) , q ); + } + freemsg(mp); + break; + default: + putnext(q,mp); + break; + } + +} + +/* + * Writing to a chu device doesn't make sense, but we'll pass them + * through in case they're important. + */ + +static int chuwput(q, mp) +queue_t *q; +mblk_t *mp; +{ + putnext(q,mp); +} + +/* + * Take a pointer to a filled chucode struct and a queue and + * send the chucode stuff upstream + */ + +void passback(outdata,q) +struct chucode *outdata; +queue_t *q; +{ + mblk_t *mp; + int j; + + mp=(mblk_t*) allocb(sizeof(struct chucode),BPRI_LO); + + if (mp==NULL) + { + log(LOG_ERR,"chu: cannot allocate message"); + return; + } + + for(j=0;j<sizeof(struct chucode); j++) + *mp->b_wptr++ = *( ((char*)outdata) + j ); + + putnext(q,mp); +} + +/* + * This routine was copied nearly verbatim from the old line discipline. + */ +void chuinput(c,q) +register u_char c; +queue_t *q; +{ + register struct chucode *chuc; + register int i; + long sec, usec; + struct timeval tv; + + /* + * Quick, Batman, get a timestamp! We need to do this + * right away. The time between the end of the stop bit + * and this point is critical, and should be as nearly + * constant and as short as possible. (Un)fortunately, + * the Sun's clock granularity is so big this isn't a + * major problem. + * + * uniqtime() is totally undocumented, but there you are. + */ + uniqtime(&tv); + + /* + * Now, locate the chu struct once so we don't have to do it + * over and over. + */ + chuc=&(((struct priv_data *) (q->q_ptr))->chu_struct); + + /* + * Compute the difference in this character's time stamp + * and the last. If it exceeds the margin, blow away all + * the characters currently in the buffer. + */ + i = (int)chuc->ncodechars; + if (i > 0) + { + sec = tv.tv_sec - chuc->codetimes[i-1].tv_sec; + usec = tv.tv_usec - chuc->codetimes[i-1].tv_usec; + if (usec < 0) + { + sec -= 1; + usec += 1000000; + } + if (sec != 0 || usec > CHUMAXUSEC) + { + i = 0; + chuc->ncodechars = 0; + } + } + + /* + * Store the character. + */ + chuc->codechars[i] = (u_char)c; + chuc->codetimes[i] = tv; + + /* + * Now we perform the 'sixth byte' heuristics. + * + * This is a long story. + * + * We used to be able to count on the first byte of the code + * having a '6' in the LSD. This prevented most code framing + * errors (garbage before the first byte wouldn't typically + * have a 6 in the LSD). That's no longer the case. + * + * We can get around this, however, by noting that the 6th byte + * must be either equal to or one's complement of the first. + * If we get a sixth byte that ISN'T like that, then it may + * well be that the first byte is garbage. The right thing + * to do is to left-shift the whole buffer one count and + * continue to wait for the sixth byte. + */ + if (i == NCHUCHARS/2) + { + register u_char temp_byte; + + temp_byte=chuc->codechars[i] ^ chuc->codechars[0]; + + if ( (temp_byte) && (temp_byte!=0xff) ) + { + register int t; + /* + * No match. Left-shift the buffer and try again + */ + for(t=0;t<=NCHUCHARS/2;t++) + { + chuc->codechars[t]=chuc->codechars[t+1]; + chuc->codetimes[t]=chuc->codetimes[t+1]; + } + + i--; /* This is because of the ++i immediately following */ + } + } + + /* + * We done yet? + */ + if (++i < NCHUCHARS) + { + /* + * We're not done. Not much to do here. Save the count and wait + * for another character. + */ + chuc->ncodechars = (u_char)i; + } + else + { + /* + * We are done. Mark this buffer full and pass it along. + */ + chuc->ncodechars = NCHUCHARS; + + /* + * Now we have a choice. Either the front half and back half + * have to match, or be one's complement of each other. + * + * So let's try the first byte and see + */ + + if(chuc->codechars[0] == chuc->codechars[NCHUCHARS/2]) + { + chuc->chutype = CHU_TIME; + for( i=0; i<(NCHUCHARS/2); i++) + if (chuc->codechars[i] != chuc->codechars[i+(NCHUCHARS/2)]) + { + chuc->ncodechars = 0; + return; + } + } + else + { + chuc->chutype = CHU_YEAR; + for( i=0; i<(NCHUCHARS/2); i++) + if (((chuc->codechars[i] ^ chuc->codechars[i+(NCHUCHARS/2)]) & 0xff) + != 0xff ) + { + chuc->ncodechars = 0; + return; + } + } + + passback(chuc,q); /* We're done! */ + chuc->ncodechars = 0; /* Start all over again! */ + } +} + +#endif diff --git a/usr.sbin/xntpd/kernel/tty_clk.c b/usr.sbin/xntpd/kernel/tty_clk.c new file mode 100644 index 0000000..d1b4bbe --- /dev/null +++ b/usr.sbin/xntpd/kernel/tty_clk.c @@ -0,0 +1,303 @@ +/* tty_clk.c,v 3.1 1993/07/06 01:07:33 jbj Exp + * tty_clk.c - Generic line driver for receiving radio clock timecodes + */ + +#include "clk.h" +#if NCLK > 0 + +#include "../h/param.h" +#include "../h/types.h" +#include "../h/systm.h" +#include "../h/dir.h" +#include "../h/user.h" +#include "../h/ioctl.h" +#include "../h/tty.h" +#include "../h/proc.h" +#include "../h/file.h" +#include "../h/conf.h" +#include "../h/buf.h" +#include "../h/uio.h" +#include "../h/clist.h" + +/* + * This line discipline is intended to provide well performing + * generic support for the reception and time stamping of radio clock + * timecodes. Most radio clock devices return a string where a + * particular character in the code (usually a \r) is on-time + * synchronized with the clock. The idea here is to collect characters + * until (one of) the synchronization character(s) (we allow two) is seen. + * When the magic character arrives we take a timestamp by calling + * microtime() and insert the eight bytes of struct timeval into the + * buffer after the magic character. We then wake up anyone waiting + * for the buffer and return the whole mess on the next read. + * + * To use this the calling program is expected to first open the + * port, and then to set the port into raw mode with the speed + * set appropriately with a TIOCSETP ioctl(), with the erase and kill + * characters set to those to be considered magic (yes, I know this + * is gross, but they were so convenient). If only one character is + * magic you can set then both the same, or perhaps to the alternate + * parity versions of said character. After getting all this set, + * change the line discipline to CLKLDISC and you are on your way. + * + * The only other bit of magic we do in here is to flush the receive + * buffers on writes if the CRMOD flag is set (hack, hack). + */ + +/* + * We run this very much like a raw mode terminal, with the exception + * that we store up characters locally until we hit one of the + * magic ones and then dump it into the rawq all at once. We keep + * the buffered data in clists since we can then often move it to + * the rawq without copying. For sanity we limit the number of + * characters between specials, and the total number of characters + * before we flush the rawq, as follows. + */ +#define CLKLINESIZE (256) +#define NCLKCHARS (CLKLINESIZE*4) + +struct clkdata { + int inuse; + struct clist clkbuf; +}; +#define clk_cc clkbuf.c_cc +#define clk_cf clkbuf.c_cf +#define clk_cl clkbuf.c_cl + +struct clkdata clk_data[NCLK]; + +/* + * Routine for flushing the internal clist + */ +#define clk_bflush(clk) (ndflush(&((clk)->clkbuf), (clk)->clk_cc)) + +int clk_debug = 0; + +/*ARGSUSED*/ +clkopen(dev, tp) + dev_t dev; + register struct tty *tp; +{ + register struct clkdata *clk; + + /* + * Don't allow multiple opens. This will also protect us + * from someone opening /dev/tty + */ + if (tp->t_line == CLKLDISC) + return (EBUSY); + ttywflush(tp); + for (clk = clk_data; clk < &clk_data[NCLK]; clk++) + if (!clk->inuse) + break; + if (clk >= &clk_data[NCLK]) + return (EBUSY); + clk->inuse++; + clk->clk_cc = 0; + clk->clk_cf = clk->clk_cl = NULL; + tp->T_LINEP = (caddr_t) clk; + return (0); +} + + +/* + * Break down... called when discipline changed or from device + * close routine. + */ +clkclose(tp) + register struct tty *tp; +{ + register struct clkdata *clk; + register int s = spltty(); + + clk = (struct clkdata *)tp->T_LINEP; + if (clk->clk_cc > 0) + clk_bflush(clk); + clk->inuse = 0; + tp->t_line = 0; /* paranoid: avoid races */ + splx(s); +} + + +/* + * Receive a write request. We pass these requests on to the terminal + * driver, except that if the CRMOD bit is set in the flags we + * first flush the input queues. + */ +clkwrite(tp, uio) + register struct tty *tp; + struct uio *uio; +{ + if (tp->t_flags & CRMOD) { + register struct clkdata *clk; + int s; + + s = spltty(); + if (tp->t_rawq.c_cc > 0) + ndflush(&tp->t_rawq, tp->t_rawq.c_cc); + clk = (struct clkdata *) tp->T_LINEP; + if (clk->clk_cc > 0) + clk_bflush(clk); + (void)splx(s); + } + ttwrite(tp, uio); +} + + +/* + * Low level character input routine. + * If the character looks okay, grab a time stamp. If the stuff in + * the buffer is too old, dump it and start fresh. If the character is + * non-BCDish, everything in the buffer too. + */ +clkinput(c, tp) + register int c; + register struct tty *tp; +{ + register struct clkdata *clk; + register int i; + register long s; + struct timeval tv; + + /* + * Check to see whether this isn't the magic character. If not, + * save the character and return. + */ +#ifdef ultrix + if (c != tp->t_cc[VERASE] && c != tp->t_cc[VKILL]) { +#else + if (c != tp->t_erase && c != tp->t_kill) { +#endif + clk = (struct clkdata *) tp->T_LINEP; + if (clk->clk_cc >= CLKLINESIZE) + clk_bflush(clk); + if (putc(c, &clk->clkbuf) == -1) { + /* + * Hopeless, no clists. Flush what we have + * and hope things improve. + */ + clk_bflush(clk); + } + return; + } + + /* + * Here we have a magic character. Get a timestamp and store + * everything. + */ + microtime(&tv); + clk = (struct clkdata *) tp->T_LINEP; + + if (putc(c, &clk->clkbuf) == -1) + goto flushout; + + s = tv.tv_sec; + for (i = 0; i < sizeof(long); i++) { + if (putc((s >> 24) & 0xff, &clk->clkbuf) == -1) + goto flushout; + s <<= 8; + } + + s = tv.tv_usec; + for (i = 0; i < sizeof(long); i++) { + if (putc((s >> 24) & 0xff, &clk->clkbuf) == -1) + goto flushout; + s <<= 8; + } + + /* + * If the length of the rawq exceeds our sanity limit, dump + * all the old crap in there before copying this in. + */ + if (tp->t_rawq.c_cc > NCLKCHARS) + ndflush(&tp->t_rawq, tp->t_rawq.c_cc); + + /* + * Now copy the buffer in. There is a special case optimization + * here. If there is nothing on the rawq at present we can + * just copy the clists we own over. Otherwise we must concatenate + * the present data on the end. + */ + s = (long)spltty(); + if (tp->t_rawq.c_cc <= 0) { + tp->t_rawq = clk->clkbuf; + clk->clk_cc = 0; + clk->clk_cl = clk->clk_cf = NULL; + (void) splx((int)s); + } else { + (void) splx((int)s); + catq(&clk->clkbuf, &tp->t_rawq); + clk_bflush(clk); + } + + /* + * Tell the world + */ + ttwakeup(tp); + return; + +flushout: + /* + * It would be nice if this never happened. Flush the + * internal clists and hope someone else frees some of them + */ + clk_bflush(clk); + return; +} + + +/* + * Handle ioctls. We reject most tty-style except those that + * change the line discipline and a couple of others.. + */ +clkioctl(tp, cmd, data, flag) + struct tty *tp; + int cmd; + caddr_t data; + int flag; +{ + int flags; + struct sgttyb *sg; + + if ((cmd>>8) != 't') + return (-1); + switch (cmd) { + case TIOCSETD: + case TIOCGETD: + case TIOCGETP: + case TIOCGETC: + case TIOCOUTQ: + return (-1); + + case TIOCSETP: + /* + * He likely wants to set new magic characters in. + * Do this part. + */ + sg = (struct sgttyb *)data; +#ifdef ultrix + tp->t_cc[VERASE] = sg->sg_erase; + tp->t_cc[VKILL] = sg->sg_kill; +#else + tp->t_erase = sg->sg_erase; + tp->t_kill = sg->sg_kill; +#endif + return (0); + + case TIOCFLUSH: + flags = *(int *)data; + if (flags == 0 || (flags & FREAD)) { + register struct clkdata *clk; + + clk = (struct clkdata *) tp->T_LINEP; + if (clk->clk_cc > 0) + clk_bflush(clk); + } + return (-1); + + default: + break; + } + return (ENOTTY); /* not quite appropriate */ +} +#endif NCLK diff --git a/usr.sbin/xntpd/kernel/tty_clk_STREAMS.c b/usr.sbin/xntpd/kernel/tty_clk_STREAMS.c new file mode 100644 index 0000000..f41af28 --- /dev/null +++ b/usr.sbin/xntpd/kernel/tty_clk_STREAMS.c @@ -0,0 +1,265 @@ +/* tty_clk_STREAMS.c,v 3.1 1993/07/06 01:07:34 jbj Exp + * Timestamp STREAMS module for SunOS 4.1 + * + * Copyright 1991, Nick Sayer + * + * Special thanks to Greg Onufer for his debug assists. + * + * Should be PUSHed directly on top of a serial I/O channel. + * For any character in a user-designated set, adds a kernel + * timestamp to that character. + * + * BUGS: + * + * Only so many characters can be timestamped. This number, however, + * is adjustable. + * + * The null character ($00) cannot be timestamped. + * + * The M_DATA messages passed upstream will not be the same + * size as when they arrive from downstream, even if no + * timestamp character is in the message. This, however, + * should not affect anything. + * + */ + +#include "clk.h" +#if NCLK > 0 +/* + * How big should the messages we pass upstream be? + */ +#define MESSAGE_SIZE 128 + +#include <string.h> +#include <sys/types.h> +#include <sys/stream.h> +#include <sys/param.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/user.h> +#include <sys/errno.h> + +#include <sys/clkdefs.h> + +static struct module_info rminfo = { 0, "clk", 0, INFPSZ, 0, 0 }; +static struct module_info wminfo = { 0, "clk", 0, INFPSZ, 0, 0 }; +static int clkopen(), clkrput(), clkwput(), clkclose(); + +static struct qinit rinit = { clkrput, NULL, clkopen, clkclose, NULL, + &rminfo, NULL }; + +static struct qinit winit = { clkwput, NULL, NULL, NULL, NULL, + &wminfo, NULL }; + +struct streamtab clkinfo = { &rinit, &winit, NULL, NULL }; + +struct priv_data_type +{ + char in_use; + char string[CLK_MAXSTRSIZE]; +} priv_data[NCLK]; + +char first_open=1; + +/* + * God only knows why, but linking with strchr() and index() fail + * on my system, so here's a renamed copy. + */ + +u_char *str_chr(s,c) +u_char *s; +int c; +{ + while (*s) + if(*s++ == c) + return (s-1); + return NULL; +} + +/*ARGSUSED*/ +static int clkopen(q, dev, flag, sflag) +queue_t *q; +dev_t dev; +int flag; +int sflag; +{ + int i; + +/* Damn it! We can't even have the global data struct properly + initialized! So we have a mark to tell us to init the global + data on the first open */ + + if (first_open) + { + first_open=0; + + for(i=0;i<NCLK;i++) + priv_data[i].in_use=0; + } + + for(i=0;i<NCLK;i++) + if(!priv_data[i].in_use) + { + priv_data[i].in_use++; + ((struct priv_data_type *) (q->q_ptr))=priv_data+i; + priv_data[i].string[0]=0; + return (0); + } + u.u_error = EBUSY; + return (OPENFAIL); +} + +/*ARGSUSED*/ +static int clkclose(q, flag) +queue_t *q; +int flag; +{ + ((struct priv_data_type *) (q->q_ptr))->in_use=0; + + return (0); +} + +/* + * Now the crux of the biscuit. + * + * If it's an M_DATA package, we take each character and pass + * it to clkchar. + */ + +void clkchar(); + +static int clkrput(q, mp) +queue_t *q; +mblk_t *mp; +{ + mblk_t *bp; + + switch(mp->b_datap->db_type) + { + case M_DATA: + clkchar(0,q,2); + for(bp=mp; bp!=NULL; bp=bp->b_cont) + { + while(bp->b_rptr < bp->b_wptr) + clkchar( ((u_char)*(bp->b_rptr++)) , q , 0 ); + } + clkchar(0,q,1); + freemsg(mp); + break; + default: + putnext(q,mp); + break; + } + +} + +/* + * If it's a matching M_IOCTL, handle it. + */ + +static int clkwput(q, mp) +queue_t *q; +mblk_t *mp; +{ + struct iocblk *iocp; + + switch(mp->b_datap->db_type) + { + case M_IOCTL: + iocp=(struct iocblk*) mp->b_rptr; + if (iocp->ioc_cmd==CLK_SETSTR) + { + strncpy( ((struct priv_data_type *) (RD(q)->q_ptr))->string, + (char *) mp->b_cont->b_rptr,CLK_MAXSTRSIZE); + /* make sure it's null terminated */ + ((struct priv_data_type *) (RD(q)->q_ptr))->string[CLK_MAXSTRSIZE-1]=0; + mp->b_datap->db_type = M_IOCACK; + qreply(q,mp); + } + else + putnext(q,mp); + break; + default: + putnext(q,mp); + break; + } +} + +/* + * Now clkchar. It takes a character, a queue pointer and an action + * flag and depending on the flag either: + * + * 0 - adds the character to the current message. If there's a + * timestamp to be done, do that too. If the message is less than + * 8 chars from being full, link in a new one, and set it up for + * the next call. + * + * 1 - sends the whole mess to Valhala. + * + * 2 - set things up. + * + * Yeah, it's an ugly hack. Complaints may be filed with /dev/null. + */ + + +void clkchar(c,q,f) + register u_char c; + queue_t *q; + char f; +{ + static char error; + static mblk_t *message,*mp; + struct timeval tv; + +/* Get a timestamp ASAP! */ + uniqtime(&tv); + + switch(f) + { + case 1: + if (!error) + putnext(q,message); + break; + case 2: + mp=message= (mblk_t*) allocb(MESSAGE_SIZE,BPRI_LO); + error=(message==NULL); + if (error) + log(LOG_ERR,"clk: cannot allocate message - data lost"); + break; + case 0: + if (error) /* If we had an error, forget it. */ + return; + + *mp->b_wptr++=c; /* Put the char away first. + + /* If it's in the special string, append a struct timeval */ + + if (str_chr( ((struct priv_data_type *) (q->q_ptr))->string , + c )!=NULL) + { + int i; + + for (i=0;i<sizeof(struct timeval);i++) + *mp->b_wptr++= *( ((char*)&tv) + i ); + } + + /* If we don't have space for a complete struct timeval, and a + char, it's time for a new mp block */ + + if (((mp->b_wptr-mp->b_rptr)+sizeof(struct timeval)+2)>MESSAGE_SIZE) + { + mp->b_cont= (mblk_t*) allocb(MESSAGE_SIZE,BPRI_LO); + error=(mp->b_cont==NULL); + if (error) + { + log(LOG_ERR,"clk: cannot allocate message - data lost"); + freemsg(message); + } + mp=mp->b_cont; + } + + break; + } +} + +#endif |