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