xref: /freebsd/contrib/ntp/util/kern.c (revision 77a0943ded95b9e6438f7db70c4a28e4d93946d4)
1 /*
2  * This program simulates a first-order, type-II phase-lock loop using
3  * actual code segments from modified kernel distributions for SunOS,
4  * Ultrix and OSF/1 kernels. These segments do not use any licensed code.
5  */
6 #ifdef HAVE_CONFIG_H
7 # include <config.h>
8 #endif
9 
10 #include <stdio.h>
11 #include <ctype.h>
12 #include <math.h>
13 #include <sys/time.h>
14 
15 #ifdef HAVE_TIMEX_H
16 # include "timex.h"
17 #endif
18 
19 /*
20  * Phase-lock loop definitions
21  */
22 #define HZ 100			/* timer interrupt frequency (Hz) */
23 #define MAXPHASE 512000		/* max phase error (us) */
24 #define MAXFREQ 200		/* max frequency error (ppm) */
25 #define TAU 2			/* time constant (shift 0 - 6) */
26 #define POLL 16			/* interval between updates (s) */
27 #define MAXSEC 1200		/* max interval between updates (s) */
28 
29 /*
30  * Function declarations
31  */
32 void hardupdate();
33 void hardclock();
34 void second_overflow();
35 
36 /*
37  * Kernel variables
38  */
39 int tick;			/* timer interrupt period (us) */
40 int fixtick;			/* amortization constant (ppm) */
41 struct timeval timex;		/* ripoff of kernel time variable */
42 
43 /*
44  * Phase-lock loop variables
45  */
46 int time_status = TIME_BAD;	/* clock synchronization status */
47 long time_offset = 0;		/* time adjustment (us) */
48 long time_constant = 0;		/* pll time constant */
49 long time_tolerance = MAXFREQ;	/* frequency tolerance (ppm) */
50 long time_precision = 1000000 / HZ; /* clock precision (us) */
51 long time_maxerror = MAXPHASE;	/* maximum error (us) */
52 long time_esterror = MAXPHASE;	/* estimated error (us) */
53 long time_phase = 0;		/* phase offset (scaled us) */
54 long time_freq = 0;		/* frequency offset (scaled ppm) */
55 long time_adj = 0;		/* tick adjust (scaled 1 / HZ) */
56 long time_reftime = 0;		/* time at last adjustment (s) */
57 
58 /*
59  * Simulation variables
60  */
61 double timey = 0;		/* simulation time (us) */
62 long timez = 0;			/* current error (us) */
63 long poll_interval = 0;		/* poll counter */
64 
65 /*
66  * Simulation test program
67  */
68 int
69 main(
70 	int argc,
71 	char *argv[]
72 	)
73 {
74 	tick = 1000000 / HZ;
75 	fixtick = 1000000 % HZ;
76 	timex.tv_sec = 0;
77 	timex.tv_usec = MAXPHASE;
78 	time_freq = 0;
79 	time_constant = TAU;
80 	printf("tick %d us, fixtick %d us\n", tick, fixtick);
81 	printf("      time    offset      freq   _offset     _freq      _adj\n");
82 
83 	/*
84 	 * Grind the loop until ^C
85 	 */
86 	while (1) {
87 		timey += (double)(1000000) / HZ;
88 		if (timey >= 1000000)
89 		    timey -= 1000000;
90 		hardclock();
91 		if (timex.tv_usec >= 1000000) {
92 			timex.tv_usec -= 1000000;
93 			timex.tv_sec++;
94 			second_overflow();
95 			poll_interval++;
96 			if (!(poll_interval % POLL)) {
97 				timez = (long)timey - timex.tv_usec;
98 				if (timez > 500000)
99 				    timez -= 1000000;
100 				if (timez < -500000)
101 				    timez += 1000000;
102 				hardupdate(timez);
103 				printf("%10li%10li%10.2f  %08lx  %08lx  %08lx\n",
104 				       timex.tv_sec, timez,
105 				       (double)time_freq / (1 << SHIFT_KF),
106 				       time_offset, time_freq, time_adj);
107 			}
108 		}
109 	}
110 }
111 
112 /*
113  * This routine simulates the ntp_adjtime() call
114  *
115  * For default SHIFT_UPDATE = 12, offset is limited to +-512 ms, the
116  * maximum interval between updates is 4096 s and the maximum frequency
117  * offset is +-31.25 ms/s.
118  */
119 void
120 hardupdate(
121 	long offset
122 	)
123 {
124 	long ltemp, mtemp;
125 
126 	time_offset = offset << SHIFT_UPDATE;
127 	mtemp = timex.tv_sec - time_reftime;
128 	time_reftime = timex.tv_sec;
129 	if (mtemp > MAXSEC)
130 	    mtemp = 0;
131 
132 	/* ugly multiply should be replaced */
133 	if (offset < 0)
134 	    time_freq -= (-offset * mtemp) >>
135 		    (time_constant + time_constant);
136 	else
137 	    time_freq += (offset * mtemp) >>
138 		    (time_constant + time_constant);
139 	ltemp = time_tolerance << SHIFT_KF;
140 	if (time_freq > ltemp)
141 	    time_freq = ltemp;
142 	else if (time_freq < -ltemp)
143 	    time_freq = -ltemp;
144 	if (time_status == TIME_BAD)
145 	    time_status = TIME_OK;
146 }
147 
148 /*
149  * This routine simulates the timer interrupt
150  */
151 void
152 hardclock(void)
153 {
154 	int ltemp, time_update;
155 
156 	time_update = tick;	/* computed by adjtime() */
157 	time_phase += time_adj;
158 	if (time_phase < -FINEUSEC) {
159 		ltemp = -time_phase >> SHIFT_SCALE;
160 		time_phase += ltemp << SHIFT_SCALE;
161 		time_update -= ltemp;
162 	}
163 	else if (time_phase > FINEUSEC) {
164 		ltemp = time_phase >> SHIFT_SCALE;
165 		time_phase -= ltemp << SHIFT_SCALE;
166 		time_update += ltemp;
167 	}
168 	timex.tv_usec += time_update;
169 }
170 
171 /*
172  * This routine simulates the overflow of the microsecond field
173  *
174  * With SHIFT_SCALE = 23, the maximum frequency adjustment is +-256 us
175  * per tick, or 25.6 ms/s at a clock frequency of 100 Hz. The time
176  * contribution is shifted right a minimum of two bits, while the frequency
177  * contribution is a right shift. Thus, overflow is prevented if the
178  * frequency contribution is limited to half the maximum or 15.625 ms/s.
179  */
180 void
181 second_overflow(void)
182 {
183 	int ltemp;
184 
185 	time_maxerror += time_tolerance;
186 	if (time_offset < 0) {
187 		ltemp = -time_offset >>
188 			(SHIFT_KG + time_constant);
189 		time_offset += ltemp;
190 		time_adj = -(ltemp <<
191 			     (SHIFT_SCALE - SHIFT_HZ - SHIFT_UPDATE));
192 	} else {
193 		ltemp = time_offset >>
194 			(SHIFT_KG + time_constant);
195 		time_offset -= ltemp;
196 		time_adj = ltemp <<
197 			(SHIFT_SCALE - SHIFT_HZ - SHIFT_UPDATE);
198 	}
199 	if (time_freq < 0)
200 	    time_adj -= -time_freq >> (SHIFT_KF + SHIFT_HZ - SHIFT_SCALE);
201 	else
202 	    time_adj += time_freq >> (SHIFT_KF + SHIFT_HZ - SHIFT_SCALE);
203 	time_adj += fixtick << (SHIFT_SCALE - SHIFT_HZ);
204 
205 	/* ugly divide should be replaced */
206 	if (timex.tv_sec % 86400 == 0) {
207 		switch (time_status) {
208 
209 		    case TIME_INS:
210 			timex.tv_sec--; /* !! */
211 			time_status = TIME_OOP;
212 			break;
213 
214 		    case TIME_DEL:
215 			timex.tv_sec++;
216 			time_status = TIME_OK;
217 			break;
218 
219 		    case TIME_OOP:
220 			time_status = TIME_OK;
221 			break;
222 		}
223 	}
224 }
225