File: sys_generic.c

package info (click to toggle)
chrony 1.30-2%2Bdeb8u2
  • links: PTS, VCS
  • area: main
  • in suites: jessie
  • size: 2,312 kB
  • ctags: 3,032
  • sloc: ansic: 19,005; sh: 1,361; yacc: 871; perl: 323; makefile: 122
file content (314 lines) | stat: -rw-r--r-- 9,556 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/*
  chronyd/chronyc - Programs for keeping computer clocks accurate.

 **********************************************************************
 * Copyright (C) Miroslav Lichvar  2014
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 **********************************************************************

  =======================================================================

  Generic driver functions to complete system-specific drivers
  */

#include "config.h"

#include "sysincl.h"

#include "sys_generic.h"

#include "conf.h"
#include "local.h"
#include "localp.h"
#include "logging.h"
#include "sched.h"
#include "util.h"

/* ================================================== */

/* System clock frequency drivers */
static lcl_ReadFrequencyDriver drv_read_freq;
static lcl_SetFrequencyDriver drv_set_freq;

/* Current frequency as requested by the local module (in ppm) */
static double base_freq;

/* Maximum frequency that can be set by drv_set_freq (in ppm) */
static double max_freq;

/* Maximum expected delay in the actual frequency change (e.g. kernel ticks)
   in local time */
static double max_freq_change_delay;

/* Maximum allowed frequency offset relative to the base frequency */
static double max_corr_freq;

/* Amount of outstanding offset to process */
static double offset_register;

/* Minimum offset to correct */
#define MIN_OFFSET_CORRECTION 1.0e-9

/* Current frequency offset between base_freq and the real clock frequency
   as set by drv_set_freq (not in ppm) */
static double slew_freq;

/* Time (raw) of last update of slewing frequency and offset */
static struct timeval slew_start;

/* Limits for the slew timeout */
#define MIN_SLEW_TIMEOUT 1.0
#define MAX_SLEW_TIMEOUT 1.0e4

/* Scheduler timeout ID and flag if the timer is currently running */
static SCH_TimeoutID slew_timeout_id;
static int slew_timer_running;

/* Suggested offset correction rate (correction time * offset) */
static double correction_rate;

/* Maximum expected offset correction error caused by delayed change in the
   real frequency of the clock */
static double slew_error;

/* ================================================== */

static void handle_end_of_slew(void *anything);
static void update_slew(void);

/* ================================================== */
/* Adjust slew_start on clock step */

static void
handle_step(struct timeval *raw, struct timeval *cooked, double dfreq,
            double doffset, LCL_ChangeType change_type, void *anything)
{
  if (change_type == LCL_ChangeUnknownStep) {
    /* Reset offset and slewing */
    slew_start = *raw;
    offset_register = 0.0;
    update_slew();
  } else if (change_type == LCL_ChangeStep) {
    UTI_AddDoubleToTimeval(&slew_start, -doffset, &slew_start);
  }
}

/* ================================================== */
/* End currently running slew and start a new one */

static void
update_slew(void)
{
  struct timeval now, end_of_slew;
  double old_slew_freq, total_freq, corr_freq, duration;

  /* Remove currently running timeout */
  if (slew_timer_running)
    SCH_RemoveTimeout(slew_timeout_id);

  LCL_ReadRawTime(&now);

  /* Adjust the offset register by achieved slew */
  UTI_DiffTimevalsToDouble(&duration, &now, &slew_start);
  offset_register -= slew_freq * duration;

  /* Estimate how long should the next slew take */
  if (fabs(offset_register) < MIN_OFFSET_CORRECTION) {
    duration = MAX_SLEW_TIMEOUT;
  } else {
    duration = correction_rate / fabs(offset_register);
    if (duration < MIN_SLEW_TIMEOUT)
      duration = MIN_SLEW_TIMEOUT;
  }

  /* Get frequency offset needed to slew the offset in the duration
     and clamp it to the allowed maximum */
  corr_freq = offset_register / duration;
  if (corr_freq < -max_corr_freq)
    corr_freq = -max_corr_freq;
  else if (corr_freq > max_corr_freq)
    corr_freq = max_corr_freq;

  /* Get the new real frequency and clamp it */
  total_freq = base_freq + corr_freq * (1.0e6 - base_freq);
  if (total_freq > max_freq)
    total_freq = max_freq;
  else if (total_freq < -max_freq)
    total_freq = -max_freq;

  /* Set the new frequency (the actual frequency returned by the call may be
     slightly different from the requested frequency due to rounding) */
  total_freq = (*drv_set_freq)(total_freq);

  /* Compute the new slewing frequency, it's relative to the real frequency to
     make the calculation in offset_convert() cheaper */
  old_slew_freq = slew_freq;
  slew_freq = (total_freq - base_freq) / (1.0e6 - total_freq);

  /* Compute the dispersion introduced by changing frequency and add it
     to all statistics held at higher levels in the system */
  slew_error = fabs((old_slew_freq - slew_freq) * max_freq_change_delay);
  if (slew_error >= MIN_OFFSET_CORRECTION)
    lcl_InvokeDispersionNotifyHandlers(slew_error);

  /* Compute the duration of the slew and clamp it.  If the slewing frequency
     is zero or has wrong sign (e.g. due to rounding in the frequency driver or
     when base_freq is larger than max_freq), use maximum timeout and try again
     on the next update. */
  if (fabs(offset_register) < MIN_OFFSET_CORRECTION ||
      offset_register * slew_freq <= 0.0) {
    duration = MAX_SLEW_TIMEOUT;
  } else {
    duration = offset_register / slew_freq;
    if (duration < MIN_SLEW_TIMEOUT)
      duration = MIN_SLEW_TIMEOUT;
    else if (duration > MAX_SLEW_TIMEOUT)
      duration = MAX_SLEW_TIMEOUT;
  }

  /* Restart timer for the next update */
  UTI_AddDoubleToTimeval(&now, duration, &end_of_slew);
  slew_timeout_id = SCH_AddTimeout(&end_of_slew, handle_end_of_slew, NULL);

  slew_start = now;
  slew_timer_running = 1;

  DEBUG_LOG(LOGF_SysGeneric, "slew offset=%e corr_rate=%e base_freq=%f total_freq=%f slew_freq=%e duration=%f slew_error=%e",
      offset_register, correction_rate, base_freq, total_freq, slew_freq,
      duration, slew_error);
}

/* ================================================== */

static void
handle_end_of_slew(void *anything)
{
  slew_timer_running = 0;
  update_slew();
}

/* ================================================== */

static double
read_frequency(void)
{
  return base_freq;
}

/* ================================================== */

static double
set_frequency(double freq_ppm)
{
  base_freq = freq_ppm;
  update_slew();

  return base_freq;
}

/* ================================================== */

static void
accrue_offset(double offset, double corr_rate)
{
  offset_register += offset;
  correction_rate = corr_rate;

  update_slew();
}

/* ================================================== */
/* Determine the correction to generate the cooked time for given raw time */

static void
offset_convert(struct timeval *raw,
               double *corr, double *err)
{
  double duration;

  UTI_DiffTimevalsToDouble(&duration, raw, &slew_start);

  *corr = slew_freq * duration - offset_register;
  if (err)
    *err = fabs(duration) <= max_freq_change_delay ? slew_error : 0.0;
}

/* ================================================== */
/* Positive means currently fast of true time, i.e. jump backwards */

static void
apply_step_offset(double offset)
{
  struct timeval old_time, new_time;
  double err;

  LCL_ReadRawTime(&old_time);
  UTI_AddDoubleToTimeval(&old_time, -offset, &new_time);

  if (settimeofday(&new_time, NULL) < 0) {
    LOG_FATAL(LOGF_SysGeneric, "settimeofday() failed");
  }

  LCL_ReadRawTime(&old_time);
  UTI_DiffTimevalsToDouble(&err, &old_time, &new_time);

  lcl_InvokeDispersionNotifyHandlers(fabs(err));
}

/* ================================================== */

void
SYS_Generic_CompleteFreqDriver(double max_set_freq_ppm, double max_set_freq_delay,
                               lcl_ReadFrequencyDriver sys_read_freq,
                               lcl_SetFrequencyDriver sys_set_freq,
                               lcl_ApplyStepOffsetDriver sys_apply_step_offset,
                               lcl_SetLeapDriver sys_set_leap)
{
  max_freq = max_set_freq_ppm;
  max_freq_change_delay = max_set_freq_delay * (1.0 + max_freq / 1.0e6);
  drv_read_freq = sys_read_freq;
  drv_set_freq = sys_set_freq;

  base_freq = (*drv_read_freq)();
  slew_freq = 0.0;
  offset_register = 0.0;

  max_corr_freq = CNF_GetMaxSlewRate() / 1.0e6;

  lcl_RegisterSystemDrivers(read_frequency, set_frequency,
                            accrue_offset, sys_apply_step_offset ?
                              sys_apply_step_offset : apply_step_offset,
                            offset_convert, sys_set_leap);

  LCL_AddParameterChangeHandler(handle_step, NULL);
}

/* ================================================== */

void
SYS_Generic_Finalise(void)
{
  /* Must *NOT* leave a slew running - clock could drift way off
     if the daemon is not restarted */
  if (slew_timer_running) {
    SCH_RemoveTimeout(slew_timeout_id);
    slew_timer_running = 0;
  }

  (*drv_set_freq)(base_freq);
}

/* ================================================== */