]> git.ipfire.org Git - thirdparty/chrony.git/blame - sys_generic.c
ntp: add server support for KoD RATE
[thirdparty/chrony.git] / sys_generic.c
CommitLineData
fc235a3f
ML
1/*
2 chronyd/chronyc - Programs for keeping computer clocks accurate.
3
4 **********************************************************************
33967780 5 * Copyright (C) Miroslav Lichvar 2014-2015
fc235a3f
ML
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of version 2 of the GNU General Public License as
9 * published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 *
20 **********************************************************************
21
22 =======================================================================
23
24 Generic driver functions to complete system-specific drivers
25 */
26
27#include "config.h"
28
29#include "sysincl.h"
30
31#include "sys_generic.h"
32
9cf78b97 33#include "conf.h"
fc235a3f
ML
34#include "local.h"
35#include "localp.h"
36#include "logging.h"
400820d3 37#include "privops.h"
fc235a3f
ML
38#include "sched.h"
39#include "util.h"
40
41/* ================================================== */
42
02cbe5e1 43/* System clock drivers */
fc235a3f
ML
44static lcl_ReadFrequencyDriver drv_read_freq;
45static lcl_SetFrequencyDriver drv_set_freq;
02cbe5e1 46static lcl_SetSyncStatusDriver drv_set_sync_status;
d6fdae5f
ML
47static lcl_AccrueOffsetDriver drv_accrue_offset;
48static lcl_OffsetCorrectionDriver drv_get_offset_correction;
fc235a3f
ML
49
50/* Current frequency as requested by the local module (in ppm) */
51static double base_freq;
52
53/* Maximum frequency that can be set by drv_set_freq (in ppm) */
54static double max_freq;
55
56/* Maximum expected delay in the actual frequency change (e.g. kernel ticks)
57 in local time */
58static double max_freq_change_delay;
59
9cf78b97
ML
60/* Maximum allowed frequency offset relative to the base frequency */
61static double max_corr_freq;
62
fc235a3f
ML
63/* Amount of outstanding offset to process */
64static double offset_register;
65
66/* Minimum offset to correct */
67#define MIN_OFFSET_CORRECTION 1.0e-9
68
69/* Current frequency offset between base_freq and the real clock frequency
70 as set by drv_set_freq (not in ppm) */
71static double slew_freq;
72
fc235a3f 73/* Time (raw) of last update of slewing frequency and offset */
d0dfa1de 74static struct timespec slew_start;
fc235a3f 75
af8e4a51
ML
76/* Limits for the slew length */
77#define MIN_SLEW_DURATION 1.0
78#define MAX_SLEW_DURATION 1.0e4
fc235a3f 79
0076458e 80/* Scheduler timeout ID for ending of the currently running slew */
fc235a3f 81static SCH_TimeoutID slew_timeout_id;
fc235a3f 82
2ed88c31
ML
83/* Scheduled duration of the currently running slew */
84static double slew_duration;
85
86/* Expected delay in ending of the slew due to process scheduling and
87 execution time, tracked as a decaying maximum value */
88static double slew_excess_duration;
89
90/* Maximum accepted excess duration to ignore large jumps after resuming
91 suspended system and other reasons (which should be handled in the
92 scheduler), a constant to determine the minimum slew duration to avoid
93 oscillations due to the excess, and the decay constant */
94#define MAX_SLEW_EXCESS_DURATION 100.0
95#define MIN_SLEW_DURATION_EXCESS_RATIO 5.0
96#define SLEW_EXCESS_DURATION_DECAY 0.9
97
fc235a3f
ML
98/* Suggested offset correction rate (correction time * offset) */
99static double correction_rate;
100
101/* Maximum expected offset correction error caused by delayed change in the
102 real frequency of the clock */
103static double slew_error;
104
d6fdae5f
ML
105/* Minimum offset that the system driver can slew faster than the maximum
106 frequency offset that it allows to be set directly */
107static double fastslew_min_offset;
108
109/* Maximum slew rate of the system driver */
110static double fastslew_max_rate;
111
112/* Flag indicating that the system driver is currently slewing */
113static int fastslew_active;
114
fc235a3f
ML
115/* ================================================== */
116
117static void handle_end_of_slew(void *anything);
a33a9551 118static void update_slew(void);
fc235a3f
ML
119
120/* ================================================== */
121/* Adjust slew_start on clock step */
122
123static void
d0dfa1de 124handle_step(struct timespec *raw, struct timespec *cooked, double dfreq,
44c9744d 125 double doffset, LCL_ChangeType change_type, void *anything)
fc235a3f 126{
9cc609c4 127 if (change_type == LCL_ChangeStep) {
d0dfa1de 128 UTI_AddDoubleToTimespec(&slew_start, -doffset, &slew_start);
a33a9551 129 }
fc235a3f
ML
130}
131
ad942e35
ML
132/* ================================================== */
133
d6fdae5f
ML
134static void
135start_fastslew(void)
136{
137 if (!drv_accrue_offset)
138 return;
139
140 drv_accrue_offset(offset_register, 0.0);
141
f282856c 142 DEBUG_LOG("fastslew offset=%e", offset_register);
d6fdae5f
ML
143
144 offset_register = 0.0;
145 fastslew_active = 1;
146}
147
148/* ================================================== */
149
150static void
d0dfa1de 151stop_fastslew(struct timespec *now)
d6fdae5f
ML
152{
153 double corr;
154
155 if (!drv_get_offset_correction || !fastslew_active)
156 return;
157
158 /* Cancel the remaining offset */
159 drv_get_offset_correction(now, &corr, NULL);
160 drv_accrue_offset(corr, 0.0);
161 offset_register -= corr;
162}
163
164/* ================================================== */
165
ad942e35
ML
166static double
167clamp_freq(double freq)
168{
169 if (freq > max_freq)
170 return max_freq;
171 if (freq < -max_freq)
172 return -max_freq;
173 return freq;
174}
175
fc235a3f
ML
176/* ================================================== */
177/* End currently running slew and start a new one */
178
179static void
180update_slew(void)
181{
2ed88c31 182 double old_slew_freq, total_freq, corr_freq, duration, excess_duration;
d0dfa1de 183 struct timespec now, end_of_slew;
fc235a3f
ML
184
185 /* Remove currently running timeout */
0076458e 186 SCH_RemoveTimeout(slew_timeout_id);
fc235a3f
ML
187
188 LCL_ReadRawTime(&now);
189
190 /* Adjust the offset register by achieved slew */
cfe706f0 191 duration = UTI_DiffTimespecsToDouble(&now, &slew_start);
fc235a3f
ML
192 offset_register -= slew_freq * duration;
193
d6fdae5f
ML
194 stop_fastslew(&now);
195
2ed88c31
ML
196 /* Update the maximum excess duration, decaying even when the slew did
197 not time out (i.e. frequency was set or offset accrued), but add a small
198 value to avoid denormals */
199 slew_excess_duration = (slew_excess_duration + 1.0e-9) * SLEW_EXCESS_DURATION_DECAY;
200 excess_duration = duration - slew_duration;
201 if (slew_excess_duration < excess_duration &&
202 excess_duration <= MAX_SLEW_EXCESS_DURATION)
203 slew_excess_duration = excess_duration;
204
205 /* Calculate the duration of the new slew, considering the current correction
206 rate and previous delays in stopping of the slew */
fc235a3f 207 if (fabs(offset_register) < MIN_OFFSET_CORRECTION) {
af8e4a51 208 duration = MAX_SLEW_DURATION;
fc235a3f
ML
209 } else {
210 duration = correction_rate / fabs(offset_register);
af8e4a51
ML
211 if (duration < MIN_SLEW_DURATION)
212 duration = MIN_SLEW_DURATION;
2ed88c31
ML
213 if (duration < MIN_SLEW_DURATION_EXCESS_RATIO * slew_excess_duration)
214 duration = MIN_SLEW_DURATION_EXCESS_RATIO * slew_excess_duration;
fc235a3f
ML
215 }
216
217 /* Get frequency offset needed to slew the offset in the duration
218 and clamp it to the allowed maximum */
219 corr_freq = offset_register / duration;
9cf78b97
ML
220 if (corr_freq < -max_corr_freq)
221 corr_freq = -max_corr_freq;
222 else if (corr_freq > max_corr_freq)
223 corr_freq = max_corr_freq;
fc235a3f 224
d6fdae5f
ML
225 /* Let the system driver perform the slew if the requested frequency
226 offset is too large for the frequency driver */
227 if (drv_accrue_offset && fabs(corr_freq) >= fastslew_max_rate &&
228 fabs(offset_register) > fastslew_min_offset) {
229 start_fastslew();
230 corr_freq = 0.0;
231 }
232
fc235a3f 233 /* Get the new real frequency and clamp it */
ad942e35 234 total_freq = clamp_freq(base_freq + corr_freq * (1.0e6 - base_freq));
fc235a3f
ML
235
236 /* Set the new frequency (the actual frequency returned by the call may be
237 slightly different from the requested frequency due to rounding) */
238 total_freq = (*drv_set_freq)(total_freq);
239
240 /* Compute the new slewing frequency, it's relative to the real frequency to
241 make the calculation in offset_convert() cheaper */
242 old_slew_freq = slew_freq;
243 slew_freq = (total_freq - base_freq) / (1.0e6 - total_freq);
244
245 /* Compute the dispersion introduced by changing frequency and add it
246 to all statistics held at higher levels in the system */
247 slew_error = fabs((old_slew_freq - slew_freq) * max_freq_change_delay);
e8bb95ba
ML
248 if (slew_error >= MIN_OFFSET_CORRECTION)
249 lcl_InvokeDispersionNotifyHandlers(slew_error);
fc235a3f
ML
250
251 /* Compute the duration of the slew and clamp it. If the slewing frequency
252 is zero or has wrong sign (e.g. due to rounding in the frequency driver or
d6fdae5f
ML
253 when base_freq is larger than max_freq, or fast slew is active), use the
254 maximum timeout and try again on the next update. */
f88a712d
ML
255 if (fabs(offset_register) < MIN_OFFSET_CORRECTION ||
256 offset_register * slew_freq <= 0.0) {
af8e4a51 257 duration = MAX_SLEW_DURATION;
fc235a3f
ML
258 } else {
259 duration = offset_register / slew_freq;
af8e4a51
ML
260 if (duration < MIN_SLEW_DURATION)
261 duration = MIN_SLEW_DURATION;
262 else if (duration > MAX_SLEW_DURATION)
263 duration = MAX_SLEW_DURATION;
fc235a3f
ML
264 }
265
266 /* Restart timer for the next update */
d0dfa1de 267 UTI_AddDoubleToTimespec(&now, duration, &end_of_slew);
fc235a3f 268 slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL);
fc235a3f 269 slew_start = now;
2ed88c31 270 slew_duration = duration;
fc235a3f 271
2ed88c31
ML
272 DEBUG_LOG("slew offset=%e corr_rate=%e base_freq=%f total_freq=%f slew_freq=%e"
273 " duration=%f excess=%f slew_error=%e",
274 offset_register, correction_rate, base_freq, total_freq, slew_freq,
275 slew_duration, slew_excess_duration, slew_error);
fc235a3f
ML
276}
277
278/* ================================================== */
279
280static void
281handle_end_of_slew(void *anything)
282{
0076458e 283 slew_timeout_id = 0;
fc235a3f
ML
284 update_slew();
285}
286
287/* ================================================== */
288
289static double
290read_frequency(void)
291{
292 return base_freq;
293}
294
295/* ================================================== */
296
297static double
298set_frequency(double freq_ppm)
299{
300 base_freq = freq_ppm;
301 update_slew();
302
303 return base_freq;
304}
305
306/* ================================================== */
307
308static void
309accrue_offset(double offset, double corr_rate)
310{
311 offset_register += offset;
312 correction_rate = corr_rate;
313
314 update_slew();
315}
316
317/* ================================================== */
318/* Determine the correction to generate the cooked time for given raw time */
319
320static void
d0dfa1de 321offset_convert(struct timespec *raw,
fc235a3f
ML
322 double *corr, double *err)
323{
d6fdae5f 324 double duration, fastslew_corr, fastslew_err;
fc235a3f 325
cfe706f0 326 duration = UTI_DiffTimespecsToDouble(raw, &slew_start);
fc235a3f 327
d6fdae5f
ML
328 if (drv_get_offset_correction && fastslew_active) {
329 drv_get_offset_correction(raw, &fastslew_corr, &fastslew_err);
330 if (fastslew_corr == 0.0 && fastslew_err == 0.0)
331 fastslew_active = 0;
332 } else {
333 fastslew_corr = fastslew_err = 0.0;
334 }
335
336 *corr = slew_freq * duration + fastslew_corr - offset_register;
337
338 if (err) {
339 *err = fastslew_err;
340 if (fabs(duration) <= max_freq_change_delay)
341 *err += slew_error;
342 }
fc235a3f
ML
343}
344
cf3c7b3b
ML
345/* ================================================== */
346/* Positive means currently fast of true time, i.e. jump backwards */
347
f6a9c5c1 348static int
cf3c7b3b
ML
349apply_step_offset(double offset)
350{
d0dfa1de
ML
351 struct timespec old_time, new_time;
352 struct timeval new_time_tv;
cf3c7b3b
ML
353 double err;
354
355 LCL_ReadRawTime(&old_time);
d0dfa1de
ML
356 UTI_AddDoubleToTimespec(&old_time, -offset, &new_time);
357 UTI_TimespecToTimeval(&new_time, &new_time_tv);
cf3c7b3b 358
d0dfa1de 359 if (PRV_SetTime(&new_time_tv, NULL) < 0) {
f282856c 360 DEBUG_LOG("settimeofday() failed");
f6a9c5c1 361 return 0;
cf3c7b3b
ML
362 }
363
364 LCL_ReadRawTime(&old_time);
cfe706f0 365 err = UTI_DiffTimespecsToDouble(&old_time, &new_time);
cf3c7b3b
ML
366
367 lcl_InvokeDispersionNotifyHandlers(fabs(err));
f6a9c5c1
ML
368
369 return 1;
cf3c7b3b
ML
370}
371
fc235a3f
ML
372/* ================================================== */
373
02cbe5e1
ML
374static void
375set_sync_status(int synchronised, double est_error, double max_error)
376{
377 double offset;
378
379 offset = fabs(offset_register);
380 if (est_error < offset)
381 est_error = offset;
382 max_error += offset;
383
384 if (drv_set_sync_status)
385 drv_set_sync_status(synchronised, est_error, max_error);
386}
387
388/* ================================================== */
389
fc235a3f
ML
390void
391SYS_Generic_CompleteFreqDriver(double max_set_freq_ppm, double max_set_freq_delay,
392 lcl_ReadFrequencyDriver sys_read_freq,
393 lcl_SetFrequencyDriver sys_set_freq,
394 lcl_ApplyStepOffsetDriver sys_apply_step_offset,
d6fdae5f
ML
395 double min_fastslew_offset, double max_fastslew_rate,
396 lcl_AccrueOffsetDriver sys_accrue_offset,
397 lcl_OffsetCorrectionDriver sys_get_offset_correction,
e14a03a1
ML
398 lcl_SetLeapDriver sys_set_leap,
399 lcl_SetSyncStatusDriver sys_set_sync_status)
fc235a3f
ML
400{
401 max_freq = max_set_freq_ppm;
402 max_freq_change_delay = max_set_freq_delay * (1.0 + max_freq / 1.0e6);
403 drv_read_freq = sys_read_freq;
404 drv_set_freq = sys_set_freq;
d6fdae5f
ML
405 drv_accrue_offset = sys_accrue_offset;
406 drv_get_offset_correction = sys_get_offset_correction;
02cbe5e1 407 drv_set_sync_status = sys_set_sync_status;
fc235a3f
ML
408
409 base_freq = (*drv_read_freq)();
410 slew_freq = 0.0;
411 offset_register = 0.0;
2ed88c31 412 slew_excess_duration = 0.0;
fc235a3f 413
9cf78b97
ML
414 max_corr_freq = CNF_GetMaxSlewRate() / 1.0e6;
415
d6fdae5f
ML
416 fastslew_min_offset = min_fastslew_offset;
417 fastslew_max_rate = max_fastslew_rate / 1.0e6;
418 fastslew_active = 0;
419
fc235a3f 420 lcl_RegisterSystemDrivers(read_frequency, set_frequency,
cf3c7b3b
ML
421 accrue_offset, sys_apply_step_offset ?
422 sys_apply_step_offset : apply_step_offset,
02cbe5e1 423 offset_convert, sys_set_leap, set_sync_status);
fc235a3f
ML
424
425 LCL_AddParameterChangeHandler(handle_step, NULL);
426}
427
428/* ================================================== */
429
430void
431SYS_Generic_Finalise(void)
432{
d0dfa1de 433 struct timespec now;
d6fdae5f 434
fc235a3f
ML
435 /* Must *NOT* leave a slew running - clock could drift way off
436 if the daemon is not restarted */
0076458e
ML
437
438 SCH_RemoveTimeout(slew_timeout_id);
439 slew_timeout_id = 0;
fc235a3f 440
ad942e35 441 (*drv_set_freq)(clamp_freq(base_freq));
d6fdae5f
ML
442
443 LCL_ReadRawTime(&now);
444 stop_fastslew(&now);
5f6f265f
ML
445
446 LCL_RemoveParameterChangeHandler(handle_step, NULL);
fc235a3f
ML
447}
448
449/* ================================================== */