summaryrefslogtreecommitdiffstats
path: root/usr.sbin/xntpd/doc/README.kern
blob: 1b791c325ccf887cde13eaf41e6b2232705670ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
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