]> git.ipfire.org Git - thirdparty/chrony.git/blame - sys_generic.c
doc: update description of refclock options
[thirdparty/chrony.git] / sys_generic.c
CommitLineData
fc235a3f
ML
1/*
2 chronyd/chronyc - Programs for keeping computer clocks accurate.
3
4 **********************************************************************
5 * Copyright (C) Miroslav Lichvar 2014
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"
37#include "sched.h"
38#include "util.h"
39
40/* ================================================== */
41
42/* System clock frequency drivers */
43static lcl_ReadFrequencyDriver drv_read_freq;
44static lcl_SetFrequencyDriver drv_set_freq;
45
46/* Current frequency as requested by the local module (in ppm) */
47static double base_freq;
48
49/* Maximum frequency that can be set by drv_set_freq (in ppm) */
50static double max_freq;
51
52/* Maximum expected delay in the actual frequency change (e.g. kernel ticks)
53 in local time */
54static double max_freq_change_delay;
55
9cf78b97
ML
56/* Maximum allowed frequency offset relative to the base frequency */
57static double max_corr_freq;
58
fc235a3f
ML
59/* Amount of outstanding offset to process */
60static double offset_register;
61
62/* Minimum offset to correct */
63#define MIN_OFFSET_CORRECTION 1.0e-9
64
65/* Current frequency offset between base_freq and the real clock frequency
66 as set by drv_set_freq (not in ppm) */
67static double slew_freq;
68
fc235a3f
ML
69/* Time (raw) of last update of slewing frequency and offset */
70static struct timeval slew_start;
71
72/* Limits for the slew timeout */
73#define MIN_SLEW_TIMEOUT 1.0
74#define MAX_SLEW_TIMEOUT 1.0e4
75
76/* Scheduler timeout ID and flag if the timer is currently running */
77static SCH_TimeoutID slew_timeout_id;
78static int slew_timer_running;
79
80/* Suggested offset correction rate (correction time * offset) */
81static double correction_rate;
82
83/* Maximum expected offset correction error caused by delayed change in the
84 real frequency of the clock */
85static double slew_error;
86
87/* ================================================== */
88
89static void handle_end_of_slew(void *anything);
90
91/* ================================================== */
92/* Adjust slew_start on clock step */
93
94static void
95handle_step(struct timeval *raw, struct timeval *cooked, double dfreq,
96 double doffset, int is_step_change, void *anything)
97{
98 if (is_step_change)
99 UTI_AddDoubleToTimeval(&slew_start, -doffset, &slew_start);
100}
101
102/* ================================================== */
103/* End currently running slew and start a new one */
104
105static void
106update_slew(void)
107{
108 struct timeval now, end_of_slew;
109 double old_slew_freq, total_freq, corr_freq, duration;
110
111 /* Remove currently running timeout */
112 if (slew_timer_running)
113 SCH_RemoveTimeout(slew_timeout_id);
114
115 LCL_ReadRawTime(&now);
116
117 /* Adjust the offset register by achieved slew */
118 UTI_DiffTimevalsToDouble(&duration, &now, &slew_start);
119 offset_register -= slew_freq * duration;
120
121 /* Estimate how long should the next slew take */
122 if (fabs(offset_register) < MIN_OFFSET_CORRECTION) {
123 duration = MAX_SLEW_TIMEOUT;
124 } else {
125 duration = correction_rate / fabs(offset_register);
126 if (duration < MIN_SLEW_TIMEOUT)
127 duration = MIN_SLEW_TIMEOUT;
128 }
129
130 /* Get frequency offset needed to slew the offset in the duration
131 and clamp it to the allowed maximum */
132 corr_freq = offset_register / duration;
9cf78b97
ML
133 if (corr_freq < -max_corr_freq)
134 corr_freq = -max_corr_freq;
135 else if (corr_freq > max_corr_freq)
136 corr_freq = max_corr_freq;
fc235a3f
ML
137
138 /* Get the new real frequency and clamp it */
139 total_freq = base_freq + corr_freq * (1.0e6 - base_freq);
140 if (total_freq > max_freq)
141 total_freq = max_freq;
142 else if (total_freq < -max_freq)
143 total_freq = -max_freq;
144
145 /* Set the new frequency (the actual frequency returned by the call may be
146 slightly different from the requested frequency due to rounding) */
147 total_freq = (*drv_set_freq)(total_freq);
148
149 /* Compute the new slewing frequency, it's relative to the real frequency to
150 make the calculation in offset_convert() cheaper */
151 old_slew_freq = slew_freq;
152 slew_freq = (total_freq - base_freq) / (1.0e6 - total_freq);
153
154 /* Compute the dispersion introduced by changing frequency and add it
155 to all statistics held at higher levels in the system */
156 slew_error = fabs((old_slew_freq - slew_freq) * max_freq_change_delay);
157 lcl_InvokeDispersionNotifyHandlers(slew_error);
158
159 /* Compute the duration of the slew and clamp it. If the slewing frequency
160 is zero or has wrong sign (e.g. due to rounding in the frequency driver or
161 when base_freq is larger than max_freq), use maximum timeout and try again
162 on the next update. */
f88a712d
ML
163 if (fabs(offset_register) < MIN_OFFSET_CORRECTION ||
164 offset_register * slew_freq <= 0.0) {
fc235a3f
ML
165 duration = MAX_SLEW_TIMEOUT;
166 } else {
167 duration = offset_register / slew_freq;
168 if (duration < MIN_SLEW_TIMEOUT)
169 duration = MIN_SLEW_TIMEOUT;
170 else if (duration > MAX_SLEW_TIMEOUT)
171 duration = MAX_SLEW_TIMEOUT;
172 }
173
174 /* Restart timer for the next update */
175 UTI_AddDoubleToTimeval(&now, duration, &end_of_slew);
176 slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL);
177
178 slew_start = now;
179 slew_timer_running = 1;
180
181 DEBUG_LOG(LOGF_SysGeneric, "slew offset=%e corr_rate=%e base_freq=%f total_freq=%f slew_freq=%e duration=%f",
182 offset_register, correction_rate, base_freq, total_freq, slew_freq, duration);
183}
184
185/* ================================================== */
186
187static void
188handle_end_of_slew(void *anything)
189{
190 slew_timer_running = 0;
191 update_slew();
192}
193
194/* ================================================== */
195
196static double
197read_frequency(void)
198{
199 return base_freq;
200}
201
202/* ================================================== */
203
204static double
205set_frequency(double freq_ppm)
206{
207 base_freq = freq_ppm;
208 update_slew();
209
210 return base_freq;
211}
212
213/* ================================================== */
214
215static void
216accrue_offset(double offset, double corr_rate)
217{
218 offset_register += offset;
219 correction_rate = corr_rate;
220
221 update_slew();
222}
223
224/* ================================================== */
225/* Determine the correction to generate the cooked time for given raw time */
226
227static void
228offset_convert(struct timeval *raw,
229 double *corr, double *err)
230{
231 double duration;
232
233 UTI_DiffTimevalsToDouble(&duration, raw, &slew_start);
234
235 *corr = slew_freq * duration - offset_register;
236 if (err)
237 *err = fabs(duration) <= max_freq_change_delay ? slew_error : 0.0;
238}
239
cf3c7b3b
ML
240/* ================================================== */
241/* Positive means currently fast of true time, i.e. jump backwards */
242
243static void
244apply_step_offset(double offset)
245{
246 struct timeval old_time, new_time;
247 double err;
248
249 LCL_ReadRawTime(&old_time);
250 UTI_AddDoubleToTimeval(&old_time, -offset, &new_time);
251
252 if (settimeofday(&new_time, NULL) < 0) {
253 LOG_FATAL(LOGF_SysGeneric, "settimeofday() failed");
254 }
255
256 LCL_ReadRawTime(&old_time);
257 UTI_DiffTimevalsToDouble(&err, &old_time, &new_time);
258
259 lcl_InvokeDispersionNotifyHandlers(fabs(err));
260}
261
fc235a3f
ML
262/* ================================================== */
263
264void
265SYS_Generic_CompleteFreqDriver(double max_set_freq_ppm, double max_set_freq_delay,
266 lcl_ReadFrequencyDriver sys_read_freq,
267 lcl_SetFrequencyDriver sys_set_freq,
268 lcl_ApplyStepOffsetDriver sys_apply_step_offset,
269 lcl_SetLeapDriver sys_set_leap)
270{
271 max_freq = max_set_freq_ppm;
272 max_freq_change_delay = max_set_freq_delay * (1.0 + max_freq / 1.0e6);
273 drv_read_freq = sys_read_freq;
274 drv_set_freq = sys_set_freq;
275
276 base_freq = (*drv_read_freq)();
277 slew_freq = 0.0;
278 offset_register = 0.0;
279
9cf78b97
ML
280 max_corr_freq = CNF_GetMaxSlewRate() / 1.0e6;
281
fc235a3f 282 lcl_RegisterSystemDrivers(read_frequency, set_frequency,
cf3c7b3b
ML
283 accrue_offset, sys_apply_step_offset ?
284 sys_apply_step_offset : apply_step_offset,
fc235a3f
ML
285 offset_convert, sys_set_leap);
286
287 LCL_AddParameterChangeHandler(handle_step, NULL);
288}
289
290/* ================================================== */
291
292void
293SYS_Generic_Finalise(void)
294{
295 /* Must *NOT* leave a slew running - clock could drift way off
296 if the daemon is not restarted */
297 if (slew_timer_running) {
298 SCH_RemoveTimeout(slew_timeout_id);
299 slew_timer_running = 0;
300 }
301
302 (*drv_set_freq)(base_freq);
303}
304
305/* ================================================== */