]> git.ipfire.org Git - thirdparty/chrony.git/blame - sys_generic.c
sys_generic: rename slew constants
[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
ML
82
83/* Suggested offset correction rate (correction time * offset) */
84static double correction_rate;
85
86/* Maximum expected offset correction error caused by delayed change in the
87 real frequency of the clock */
88static double slew_error;
89
d6fdae5f
ML
90/* Minimum offset that the system driver can slew faster than the maximum
91 frequency offset that it allows to be set directly */
92static double fastslew_min_offset;
93
94/* Maximum slew rate of the system driver */
95static double fastslew_max_rate;
96
97/* Flag indicating that the system driver is currently slewing */
98static int fastslew_active;
99
fc235a3f
ML
100/* ================================================== */
101
102static void handle_end_of_slew(void *anything);
a33a9551 103static void update_slew(void);
fc235a3f
ML
104
105/* ================================================== */
106/* Adjust slew_start on clock step */
107
108static void
d0dfa1de 109handle_step(struct timespec *raw, struct timespec *cooked, double dfreq,
44c9744d 110 double doffset, LCL_ChangeType change_type, void *anything)
fc235a3f 111{
9cc609c4 112 if (change_type == LCL_ChangeStep) {
d0dfa1de 113 UTI_AddDoubleToTimespec(&slew_start, -doffset, &slew_start);
a33a9551 114 }
fc235a3f
ML
115}
116
ad942e35
ML
117/* ================================================== */
118
d6fdae5f
ML
119static void
120start_fastslew(void)
121{
122 if (!drv_accrue_offset)
123 return;
124
125 drv_accrue_offset(offset_register, 0.0);
126
f282856c 127 DEBUG_LOG("fastslew offset=%e", offset_register);
d6fdae5f
ML
128
129 offset_register = 0.0;
130 fastslew_active = 1;
131}
132
133/* ================================================== */
134
135static void
d0dfa1de 136stop_fastslew(struct timespec *now)
d6fdae5f
ML
137{
138 double corr;
139
140 if (!drv_get_offset_correction || !fastslew_active)
141 return;
142
143 /* Cancel the remaining offset */
144 drv_get_offset_correction(now, &corr, NULL);
145 drv_accrue_offset(corr, 0.0);
146 offset_register -= corr;
147}
148
149/* ================================================== */
150
ad942e35
ML
151static double
152clamp_freq(double freq)
153{
154 if (freq > max_freq)
155 return max_freq;
156 if (freq < -max_freq)
157 return -max_freq;
158 return freq;
159}
160
fc235a3f
ML
161/* ================================================== */
162/* End currently running slew and start a new one */
163
164static void
165update_slew(void)
166{
d0dfa1de 167 struct timespec now, end_of_slew;
fc235a3f
ML
168 double old_slew_freq, total_freq, corr_freq, duration;
169
170 /* Remove currently running timeout */
0076458e 171 SCH_RemoveTimeout(slew_timeout_id);
fc235a3f
ML
172
173 LCL_ReadRawTime(&now);
174
175 /* Adjust the offset register by achieved slew */
cfe706f0 176 duration = UTI_DiffTimespecsToDouble(&now, &slew_start);
fc235a3f
ML
177 offset_register -= slew_freq * duration;
178
d6fdae5f
ML
179 stop_fastslew(&now);
180
fc235a3f
ML
181 /* Estimate how long should the next slew take */
182 if (fabs(offset_register) < MIN_OFFSET_CORRECTION) {
af8e4a51 183 duration = MAX_SLEW_DURATION;
fc235a3f
ML
184 } else {
185 duration = correction_rate / fabs(offset_register);
af8e4a51
ML
186 if (duration < MIN_SLEW_DURATION)
187 duration = MIN_SLEW_DURATION;
fc235a3f
ML
188 }
189
190 /* Get frequency offset needed to slew the offset in the duration
191 and clamp it to the allowed maximum */
192 corr_freq = offset_register / duration;
9cf78b97
ML
193 if (corr_freq < -max_corr_freq)
194 corr_freq = -max_corr_freq;
195 else if (corr_freq > max_corr_freq)
196 corr_freq = max_corr_freq;
fc235a3f 197
d6fdae5f
ML
198 /* Let the system driver perform the slew if the requested frequency
199 offset is too large for the frequency driver */
200 if (drv_accrue_offset && fabs(corr_freq) >= fastslew_max_rate &&
201 fabs(offset_register) > fastslew_min_offset) {
202 start_fastslew();
203 corr_freq = 0.0;
204 }
205
fc235a3f 206 /* Get the new real frequency and clamp it */
ad942e35 207 total_freq = clamp_freq(base_freq + corr_freq * (1.0e6 - base_freq));
fc235a3f
ML
208
209 /* Set the new frequency (the actual frequency returned by the call may be
210 slightly different from the requested frequency due to rounding) */
211 total_freq = (*drv_set_freq)(total_freq);
212
213 /* Compute the new slewing frequency, it's relative to the real frequency to
214 make the calculation in offset_convert() cheaper */
215 old_slew_freq = slew_freq;
216 slew_freq = (total_freq - base_freq) / (1.0e6 - total_freq);
217
218 /* Compute the dispersion introduced by changing frequency and add it
219 to all statistics held at higher levels in the system */
220 slew_error = fabs((old_slew_freq - slew_freq) * max_freq_change_delay);
e8bb95ba
ML
221 if (slew_error >= MIN_OFFSET_CORRECTION)
222 lcl_InvokeDispersionNotifyHandlers(slew_error);
fc235a3f
ML
223
224 /* Compute the duration of the slew and clamp it. If the slewing frequency
225 is zero or has wrong sign (e.g. due to rounding in the frequency driver or
d6fdae5f
ML
226 when base_freq is larger than max_freq, or fast slew is active), use the
227 maximum timeout and try again on the next update. */
f88a712d
ML
228 if (fabs(offset_register) < MIN_OFFSET_CORRECTION ||
229 offset_register * slew_freq <= 0.0) {
af8e4a51 230 duration = MAX_SLEW_DURATION;
fc235a3f
ML
231 } else {
232 duration = offset_register / slew_freq;
af8e4a51
ML
233 if (duration < MIN_SLEW_DURATION)
234 duration = MIN_SLEW_DURATION;
235 else if (duration > MAX_SLEW_DURATION)
236 duration = MAX_SLEW_DURATION;
fc235a3f
ML
237 }
238
239 /* Restart timer for the next update */
d0dfa1de 240 UTI_AddDoubleToTimespec(&now, duration, &end_of_slew);
fc235a3f 241 slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL);
fc235a3f 242 slew_start = now;
fc235a3f 243
f282856c 244 DEBUG_LOG("slew offset=%e corr_rate=%e base_freq=%f total_freq=%f slew_freq=%e duration=%f slew_error=%e",
e8bb95ba
ML
245 offset_register, correction_rate, base_freq, total_freq, slew_freq,
246 duration, slew_error);
fc235a3f
ML
247}
248
249/* ================================================== */
250
251static void
252handle_end_of_slew(void *anything)
253{
0076458e 254 slew_timeout_id = 0;
fc235a3f
ML
255 update_slew();
256}
257
258/* ================================================== */
259
260static double
261read_frequency(void)
262{
263 return base_freq;
264}
265
266/* ================================================== */
267
268static double
269set_frequency(double freq_ppm)
270{
271 base_freq = freq_ppm;
272 update_slew();
273
274 return base_freq;
275}
276
277/* ================================================== */
278
279static void
280accrue_offset(double offset, double corr_rate)
281{
282 offset_register += offset;
283 correction_rate = corr_rate;
284
285 update_slew();
286}
287
288/* ================================================== */
289/* Determine the correction to generate the cooked time for given raw time */
290
291static void
d0dfa1de 292offset_convert(struct timespec *raw,
fc235a3f
ML
293 double *corr, double *err)
294{
d6fdae5f 295 double duration, fastslew_corr, fastslew_err;
fc235a3f 296
cfe706f0 297 duration = UTI_DiffTimespecsToDouble(raw, &slew_start);
fc235a3f 298
d6fdae5f
ML
299 if (drv_get_offset_correction && fastslew_active) {
300 drv_get_offset_correction(raw, &fastslew_corr, &fastslew_err);
301 if (fastslew_corr == 0.0 && fastslew_err == 0.0)
302 fastslew_active = 0;
303 } else {
304 fastslew_corr = fastslew_err = 0.0;
305 }
306
307 *corr = slew_freq * duration + fastslew_corr - offset_register;
308
309 if (err) {
310 *err = fastslew_err;
311 if (fabs(duration) <= max_freq_change_delay)
312 *err += slew_error;
313 }
fc235a3f
ML
314}
315
cf3c7b3b
ML
316/* ================================================== */
317/* Positive means currently fast of true time, i.e. jump backwards */
318
f6a9c5c1 319static int
cf3c7b3b
ML
320apply_step_offset(double offset)
321{
d0dfa1de
ML
322 struct timespec old_time, new_time;
323 struct timeval new_time_tv;
cf3c7b3b
ML
324 double err;
325
326 LCL_ReadRawTime(&old_time);
d0dfa1de
ML
327 UTI_AddDoubleToTimespec(&old_time, -offset, &new_time);
328 UTI_TimespecToTimeval(&new_time, &new_time_tv);
cf3c7b3b 329
d0dfa1de 330 if (PRV_SetTime(&new_time_tv, NULL) < 0) {
f282856c 331 DEBUG_LOG("settimeofday() failed");
f6a9c5c1 332 return 0;
cf3c7b3b
ML
333 }
334
335 LCL_ReadRawTime(&old_time);
cfe706f0 336 err = UTI_DiffTimespecsToDouble(&old_time, &new_time);
cf3c7b3b
ML
337
338 lcl_InvokeDispersionNotifyHandlers(fabs(err));
f6a9c5c1
ML
339
340 return 1;
cf3c7b3b
ML
341}
342
fc235a3f
ML
343/* ================================================== */
344
02cbe5e1
ML
345static void
346set_sync_status(int synchronised, double est_error, double max_error)
347{
348 double offset;
349
350 offset = fabs(offset_register);
351 if (est_error < offset)
352 est_error = offset;
353 max_error += offset;
354
355 if (drv_set_sync_status)
356 drv_set_sync_status(synchronised, est_error, max_error);
357}
358
359/* ================================================== */
360
fc235a3f
ML
361void
362SYS_Generic_CompleteFreqDriver(double max_set_freq_ppm, double max_set_freq_delay,
363 lcl_ReadFrequencyDriver sys_read_freq,
364 lcl_SetFrequencyDriver sys_set_freq,
365 lcl_ApplyStepOffsetDriver sys_apply_step_offset,
d6fdae5f
ML
366 double min_fastslew_offset, double max_fastslew_rate,
367 lcl_AccrueOffsetDriver sys_accrue_offset,
368 lcl_OffsetCorrectionDriver sys_get_offset_correction,
e14a03a1
ML
369 lcl_SetLeapDriver sys_set_leap,
370 lcl_SetSyncStatusDriver sys_set_sync_status)
fc235a3f
ML
371{
372 max_freq = max_set_freq_ppm;
373 max_freq_change_delay = max_set_freq_delay * (1.0 + max_freq / 1.0e6);
374 drv_read_freq = sys_read_freq;
375 drv_set_freq = sys_set_freq;
d6fdae5f
ML
376 drv_accrue_offset = sys_accrue_offset;
377 drv_get_offset_correction = sys_get_offset_correction;
02cbe5e1 378 drv_set_sync_status = sys_set_sync_status;
fc235a3f
ML
379
380 base_freq = (*drv_read_freq)();
381 slew_freq = 0.0;
382 offset_register = 0.0;
383
9cf78b97
ML
384 max_corr_freq = CNF_GetMaxSlewRate() / 1.0e6;
385
d6fdae5f
ML
386 fastslew_min_offset = min_fastslew_offset;
387 fastslew_max_rate = max_fastslew_rate / 1.0e6;
388 fastslew_active = 0;
389
fc235a3f 390 lcl_RegisterSystemDrivers(read_frequency, set_frequency,
cf3c7b3b
ML
391 accrue_offset, sys_apply_step_offset ?
392 sys_apply_step_offset : apply_step_offset,
02cbe5e1 393 offset_convert, sys_set_leap, set_sync_status);
fc235a3f
ML
394
395 LCL_AddParameterChangeHandler(handle_step, NULL);
396}
397
398/* ================================================== */
399
400void
401SYS_Generic_Finalise(void)
402{
d0dfa1de 403 struct timespec now;
d6fdae5f 404
fc235a3f
ML
405 /* Must *NOT* leave a slew running - clock could drift way off
406 if the daemon is not restarted */
0076458e
ML
407
408 SCH_RemoveTimeout(slew_timeout_id);
409 slew_timeout_id = 0;
fc235a3f 410
ad942e35 411 (*drv_set_freq)(clamp_freq(base_freq));
d6fdae5f
ML
412
413 LCL_ReadRawTime(&now);
414 stop_fastslew(&now);
5f6f265f
ML
415
416 LCL_RemoveParameterChangeHandler(handle_step, NULL);
fc235a3f
ML
417}
418
419/* ================================================== */