1 /*
2 chronyd/chronyc - Programs for keeping computer clocks accurate.
3
4 **********************************************************************
5 * Copyright (C) Richard P. Curnow 1997-2003
6 * Copyright (C) Miroslav Lichvar 2011, 2014-2015
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of version 2 of the GNU General Public License as
10 * published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 **********************************************************************
22
23 =======================================================================
24
25 The routines in this file present a common local (system) clock
26 interface to the rest of the software.
27
28 They interface with the system specific driver files in sys_*.c
29 */
30
31 #include "config.h"
32
33 #include "sysincl.h"
34
35 #include "conf.h"
36 #include "local.h"
37 #include "localp.h"
38 #include "memory.h"
39 #include "smooth.h"
40 #include "util.h"
41 #include "logging.h"
42
43 /* ================================================== */
44
45 /* Variable to store the current frequency, in ppm */
46 static double current_freq_ppm;
47
48 /* Maximum allowed frequency, in ppm */
49 static double max_freq_ppm;
50
51 /* Temperature compensation, in ppm */
52 static double temp_comp_ppm;
53
54 /* ================================================== */
55 /* Store the system dependent drivers */
56
57 static lcl_ReadFrequencyDriver drv_read_freq;
58 static lcl_SetFrequencyDriver drv_set_freq;
59 static lcl_AccrueOffsetDriver drv_accrue_offset;
60 static lcl_ApplyStepOffsetDriver drv_apply_step_offset;
61 static lcl_OffsetCorrectionDriver drv_offset_convert;
62 static lcl_SetLeapDriver drv_set_leap;
63 static lcl_SetSyncStatusDriver drv_set_sync_status;
64
65 /* ================================================== */
66
67 /* Types and variables associated with handling the parameter change
68 list */
69
70 typedef struct _ChangeListEntry {
71 struct _ChangeListEntry *next;
72 struct _ChangeListEntry *prev;
73 LCL_ParameterChangeHandler handler;
74 void *anything;
75 } ChangeListEntry;
76
77 static ChangeListEntry change_list;
78
79 /* ================================================== */
80
81 /* Types and variables associated with handling the parameter change
82 list */
83
84 typedef struct _DispersionNotifyListEntry {
85 struct _DispersionNotifyListEntry *next;
86 struct _DispersionNotifyListEntry *prev;
87 LCL_DispersionNotifyHandler handler;
88 void *anything;
89 } DispersionNotifyListEntry;
90
91 static DispersionNotifyListEntry dispersion_notify_list;
92
93 /* ================================================== */
94
95 static int precision_log;
96 static double precision_quantum;
97
98 static double max_clock_error;
99
100 /* ================================================== */
101
102 /* Define the number of increments of the system clock that we want
103 to see to be fairly sure that we've got something approaching
104 the minimum increment. Even on a crummy implementation that can't
105 interpolate between 10ms ticks, we should get this done in
106 under 1s of busy waiting. */
107 #define NITERS 100
108
109 #define NSEC_PER_SEC 1000000000
110
111 static double
measure_clock_precision(void)112 measure_clock_precision(void)
113 {
114 struct timespec ts, old_ts;
115 int iters, diff, best;
116
117 LCL_ReadRawTime(&old_ts);
118
119 /* Assume we must be better than a second */
120 best = NSEC_PER_SEC;
121 iters = 0;
122
123 do {
124 LCL_ReadRawTime(&ts);
125
126 diff = NSEC_PER_SEC * (ts.tv_sec - old_ts.tv_sec) + (ts.tv_nsec - old_ts.tv_nsec);
127
128 old_ts = ts;
129 if (diff > 0) {
130 if (diff < best)
131 best = diff;
132 iters++;
133 }
134 } while (iters < NITERS);
135
136 assert(best > 0);
137
138 return 1.0e-9 * best;
139 }
140
141 /* ================================================== */
142
143 void
LCL_Initialise(void)144 LCL_Initialise(void)
145 {
146 change_list.next = change_list.prev = &change_list;
147
148 dispersion_notify_list.next = dispersion_notify_list.prev = &dispersion_notify_list;
149
150 /* Null out the system drivers, so that we die
151 if they never get defined before use */
152
153 drv_read_freq = NULL;
154 drv_set_freq = NULL;
155 drv_accrue_offset = NULL;
156 drv_offset_convert = NULL;
157
158 /* This ought to be set from the system driver layer */
159 current_freq_ppm = 0.0;
160 temp_comp_ppm = 0.0;
161
162 precision_quantum = CNF_GetClockPrecision();
163 if (precision_quantum <= 0.0)
164 precision_quantum = measure_clock_precision();
165
166 precision_quantum = CLAMP(1.0e-9, precision_quantum, 1.0);
167 precision_log = round(log(precision_quantum) / log(2.0));
168 /* NTP code doesn't support smaller log than -30 */
169 assert(precision_log >= -30);
170
171 DEBUG_LOG("Clock precision %.9f (%d)", precision_quantum, precision_log);
172
173 /* This is the maximum allowed frequency offset in ppm, the time must
174 never stop or run backwards */
175 max_freq_ppm = CNF_GetMaxDrift();
176 max_freq_ppm = CLAMP(0.0, max_freq_ppm, 500000.0);
177
178 max_clock_error = CNF_GetMaxClockError() * 1e-6;
179 }
180
181 /* ================================================== */
182
183 void
LCL_Finalise(void)184 LCL_Finalise(void)
185 {
186 /* Make sure all handlers have been removed */
187 if (change_list.next != &change_list)
188 assert(0);
189 if (dispersion_notify_list.next != &dispersion_notify_list)
190 assert(0);
191 }
192
193 /* ================================================== */
194
195 /* Routine to read the system precision as a log to base 2 value. */
196 int
LCL_GetSysPrecisionAsLog(void)197 LCL_GetSysPrecisionAsLog(void)
198 {
199 return precision_log;
200 }
201
202 /* ================================================== */
203 /* Routine to read the system precision in terms of the actual time step */
204
205 double
LCL_GetSysPrecisionAsQuantum(void)206 LCL_GetSysPrecisionAsQuantum(void)
207 {
208 return precision_quantum;
209 }
210
211 /* ================================================== */
212
213 double
LCL_GetMaxClockError(void)214 LCL_GetMaxClockError(void)
215 {
216 return max_clock_error;
217 }
218
219 /* ================================================== */
220
221 void
LCL_AddParameterChangeHandler(LCL_ParameterChangeHandler handler,void * anything)222 LCL_AddParameterChangeHandler(LCL_ParameterChangeHandler handler, void *anything)
223 {
224 ChangeListEntry *ptr, *new_entry;
225
226 /* Check that the handler is not already registered */
227 for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
228 if (!(ptr->handler != handler || ptr->anything != anything)) {
229 assert(0);
230 }
231 }
232
233 new_entry = MallocNew(ChangeListEntry);
234
235 new_entry->handler = handler;
236 new_entry->anything = anything;
237
238 /* Chain it into the list */
239 new_entry->next = &change_list;
240 new_entry->prev = change_list.prev;
241 change_list.prev->next = new_entry;
242 change_list.prev = new_entry;
243 }
244
245 /* ================================================== */
246
247 /* Remove a handler */
LCL_RemoveParameterChangeHandler(LCL_ParameterChangeHandler handler,void * anything)248 void LCL_RemoveParameterChangeHandler(LCL_ParameterChangeHandler handler, void *anything)
249 {
250
251 ChangeListEntry *ptr;
252 int ok;
253
254 ptr = NULL;
255 ok = 0;
256
257 for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
258 if (ptr->handler == handler && ptr->anything == anything) {
259 ok = 1;
260 break;
261 }
262 }
263
264 assert(ok);
265
266 /* Unlink entry from the list */
267 ptr->next->prev = ptr->prev;
268 ptr->prev->next = ptr->next;
269
270 Free(ptr);
271 }
272
273 /* ================================================== */
274
275 int
LCL_IsFirstParameterChangeHandler(LCL_ParameterChangeHandler handler)276 LCL_IsFirstParameterChangeHandler(LCL_ParameterChangeHandler handler)
277 {
278 return change_list.next->handler == handler;
279 }
280
281 /* ================================================== */
282
283 static void
invoke_parameter_change_handlers(struct timespec * raw,struct timespec * cooked,double dfreq,double doffset,LCL_ChangeType change_type)284 invoke_parameter_change_handlers(struct timespec *raw, struct timespec *cooked,
285 double dfreq, double doffset,
286 LCL_ChangeType change_type)
287 {
288 ChangeListEntry *ptr;
289
290 for (ptr = change_list.next; ptr != &change_list; ptr = ptr->next) {
291 (ptr->handler)(raw, cooked, dfreq, doffset, change_type, ptr->anything);
292 }
293 }
294
295 /* ================================================== */
296
297 void
LCL_AddDispersionNotifyHandler(LCL_DispersionNotifyHandler handler,void * anything)298 LCL_AddDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything)
299 {
300 DispersionNotifyListEntry *ptr, *new_entry;
301
302 /* Check that the handler is not already registered */
303 for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
304 if (!(ptr->handler != handler || ptr->anything != anything)) {
305 assert(0);
306 }
307 }
308
309 new_entry = MallocNew(DispersionNotifyListEntry);
310
311 new_entry->handler = handler;
312 new_entry->anything = anything;
313
314 /* Chain it into the list */
315 new_entry->next = &dispersion_notify_list;
316 new_entry->prev = dispersion_notify_list.prev;
317 dispersion_notify_list.prev->next = new_entry;
318 dispersion_notify_list.prev = new_entry;
319 }
320
321 /* ================================================== */
322
323 /* Remove a handler */
324 extern
LCL_RemoveDispersionNotifyHandler(LCL_DispersionNotifyHandler handler,void * anything)325 void LCL_RemoveDispersionNotifyHandler(LCL_DispersionNotifyHandler handler, void *anything)
326 {
327
328 DispersionNotifyListEntry *ptr;
329 int ok;
330
331 ptr = NULL;
332 ok = 0;
333
334 for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
335 if (ptr->handler == handler && ptr->anything == anything) {
336 ok = 1;
337 break;
338 }
339 }
340
341 assert(ok);
342
343 /* Unlink entry from the list */
344 ptr->next->prev = ptr->prev;
345 ptr->prev->next = ptr->next;
346
347 Free(ptr);
348 }
349
350 /* ================================================== */
351
352 void
LCL_ReadRawTime(struct timespec * ts)353 LCL_ReadRawTime(struct timespec *ts)
354 {
355 #if HAVE_CLOCK_GETTIME
356 if (clock_gettime(CLOCK_REALTIME, ts) < 0)
357 LOG_FATAL("clock_gettime() failed : %s", strerror(errno));
358 #else
359 struct timeval tv;
360
361 if (gettimeofday(&tv, NULL) < 0)
362 LOG_FATAL("gettimeofday() failed : %s", strerror(errno));
363
364 UTI_TimevalToTimespec(&tv, ts);
365 #endif
366 }
367
368 /* ================================================== */
369
370 void
LCL_ReadCookedTime(struct timespec * result,double * err)371 LCL_ReadCookedTime(struct timespec *result, double *err)
372 {
373 struct timespec raw;
374
375 LCL_ReadRawTime(&raw);
376 LCL_CookTime(&raw, result, err);
377 }
378
379 /* ================================================== */
380
381 void
LCL_CookTime(struct timespec * raw,struct timespec * cooked,double * err)382 LCL_CookTime(struct timespec *raw, struct timespec *cooked, double *err)
383 {
384 double correction;
385
386 LCL_GetOffsetCorrection(raw, &correction, err);
387 UTI_AddDoubleToTimespec(raw, correction, cooked);
388 }
389
390 /* ================================================== */
391
392 void
LCL_GetOffsetCorrection(struct timespec * raw,double * correction,double * err)393 LCL_GetOffsetCorrection(struct timespec *raw, double *correction, double *err)
394 {
395 /* Call system specific driver to get correction */
396 (*drv_offset_convert)(raw, correction, err);
397 }
398
399 /* ================================================== */
400 /* Return current frequency */
401
402 double
LCL_ReadAbsoluteFrequency(void)403 LCL_ReadAbsoluteFrequency(void)
404 {
405 double freq;
406
407 freq = current_freq_ppm;
408
409 /* Undo temperature compensation */
410 if (temp_comp_ppm != 0.0) {
411 freq = (freq + temp_comp_ppm) / (1.0 - 1.0e-6 * temp_comp_ppm);
412 }
413
414 return freq;
415 }
416
417 /* ================================================== */
418
419 static double
clamp_freq(double freq)420 clamp_freq(double freq)
421 {
422 if (freq <= max_freq_ppm && freq >= -max_freq_ppm)
423 return freq;
424
425 LOG(LOGS_WARN, "Frequency %.1f ppm exceeds allowed maximum", freq);
426
427 return CLAMP(-max_freq_ppm, freq, max_freq_ppm);
428 }
429
430 /* ================================================== */
431
432 static int
check_offset(struct timespec * now,double offset)433 check_offset(struct timespec *now, double offset)
434 {
435 /* Check if the time will be still sane with accumulated offset */
436 if (UTI_IsTimeOffsetSane(now, -offset))
437 return 1;
438
439 LOG(LOGS_WARN, "Adjustment of %.1f seconds is invalid", -offset);
440 return 0;
441 }
442
443 /* ================================================== */
444
445 /* This involves both setting the absolute frequency with the
446 system-specific driver, as well as calling all notify handlers */
447
448 void
LCL_SetAbsoluteFrequency(double afreq_ppm)449 LCL_SetAbsoluteFrequency(double afreq_ppm)
450 {
451 struct timespec raw, cooked;
452 double dfreq;
453
454 afreq_ppm = clamp_freq(afreq_ppm);
455
456 /* Apply temperature compensation */
457 if (temp_comp_ppm != 0.0) {
458 afreq_ppm = afreq_ppm * (1.0 - 1.0e-6 * temp_comp_ppm) - temp_comp_ppm;
459 }
460
461 /* Call the system-specific driver for setting the frequency */
462
463 afreq_ppm = (*drv_set_freq)(afreq_ppm);
464
465 dfreq = (afreq_ppm - current_freq_ppm) / (1.0e6 - current_freq_ppm);
466
467 LCL_ReadRawTime(&raw);
468 LCL_CookTime(&raw, &cooked, NULL);
469
470 /* Dispatch to all handlers */
471 invoke_parameter_change_handlers(&raw, &cooked, dfreq, 0.0, LCL_ChangeAdjust);
472
473 current_freq_ppm = afreq_ppm;
474
475 }
476
477 /* ================================================== */
478
479 void
LCL_AccumulateDeltaFrequency(double dfreq)480 LCL_AccumulateDeltaFrequency(double dfreq)
481 {
482 struct timespec raw, cooked;
483 double old_freq_ppm;
484
485 old_freq_ppm = current_freq_ppm;
486
487 /* Work out new absolute frequency. Note that absolute frequencies
488 are handled in units of ppm, whereas the 'dfreq' argument is in
489 terms of the gradient of the (offset) v (local time) function. */
490
491 current_freq_ppm += dfreq * (1.0e6 - current_freq_ppm);
492
493 current_freq_ppm = clamp_freq(current_freq_ppm);
494
495 /* Call the system-specific driver for setting the frequency */
496 current_freq_ppm = (*drv_set_freq)(current_freq_ppm);
497 dfreq = (current_freq_ppm - old_freq_ppm) / (1.0e6 - old_freq_ppm);
498
499 LCL_ReadRawTime(&raw);
500 LCL_CookTime(&raw, &cooked, NULL);
501
502 /* Dispatch to all handlers */
503 invoke_parameter_change_handlers(&raw, &cooked, dfreq, 0.0, LCL_ChangeAdjust);
504 }
505
506 /* ================================================== */
507
508 int
LCL_AccumulateOffset(double offset,double corr_rate)509 LCL_AccumulateOffset(double offset, double corr_rate)
510 {
511 struct timespec raw, cooked;
512
513 /* In this case, the cooked time to be passed to the notify clients
514 has to be the cooked time BEFORE the change was made */
515
516 LCL_ReadRawTime(&raw);
517 LCL_CookTime(&raw, &cooked, NULL);
518
519 if (!check_offset(&cooked, offset))
520 return 0;
521
522 (*drv_accrue_offset)(offset, corr_rate);
523
524 /* Dispatch to all handlers */
525 invoke_parameter_change_handlers(&raw, &cooked, 0.0, offset, LCL_ChangeAdjust);
526
527 return 1;
528 }
529
530 /* ================================================== */
531
532 int
LCL_ApplyStepOffset(double offset)533 LCL_ApplyStepOffset(double offset)
534 {
535 struct timespec raw, cooked;
536
537 /* In this case, the cooked time to be passed to the notify clients
538 has to be the cooked time BEFORE the change was made */
539
540 LCL_ReadRawTime(&raw);
541 LCL_CookTime(&raw, &cooked, NULL);
542
543 if (!check_offset(&raw, offset))
544 return 0;
545
546 if (!(*drv_apply_step_offset)(offset)) {
547 LOG(LOGS_ERR, "Could not step system clock");
548 return 0;
549 }
550
551 /* Reset smoothing on all clock steps */
552 SMT_Reset(&cooked);
553
554 /* Dispatch to all handlers */
555 invoke_parameter_change_handlers(&raw, &cooked, 0.0, offset, LCL_ChangeStep);
556
557 return 1;
558 }
559
560 /* ================================================== */
561
562 void
LCL_NotifyExternalTimeStep(struct timespec * raw,struct timespec * cooked,double offset,double dispersion)563 LCL_NotifyExternalTimeStep(struct timespec *raw, struct timespec *cooked,
564 double offset, double dispersion)
565 {
566 /* Dispatch to all handlers */
567 invoke_parameter_change_handlers(raw, cooked, 0.0, offset, LCL_ChangeUnknownStep);
568
569 lcl_InvokeDispersionNotifyHandlers(dispersion);
570 }
571
572 /* ================================================== */
573
574 void
LCL_NotifyLeap(int leap)575 LCL_NotifyLeap(int leap)
576 {
577 struct timespec raw, cooked;
578
579 LCL_ReadRawTime(&raw);
580 LCL_CookTime(&raw, &cooked, NULL);
581
582 /* Smooth the leap second out */
583 SMT_Leap(&cooked, leap);
584
585 /* Dispatch to all handlers as if the clock was stepped */
586 invoke_parameter_change_handlers(&raw, &cooked, 0.0, -leap, LCL_ChangeStep);
587 }
588
589 /* ================================================== */
590
591 int
LCL_AccumulateFrequencyAndOffset(double dfreq,double doffset,double corr_rate)592 LCL_AccumulateFrequencyAndOffset(double dfreq, double doffset, double corr_rate)
593 {
594 struct timespec raw, cooked;
595 double old_freq_ppm;
596
597 LCL_ReadRawTime(&raw);
598 /* Due to modifying the offset, this has to be the cooked time prior
599 to the change we are about to make */
600 LCL_CookTime(&raw, &cooked, NULL);
601
602 if (!check_offset(&cooked, doffset))
603 return 0;
604
605 old_freq_ppm = current_freq_ppm;
606
607 /* Work out new absolute frequency. Note that absolute frequencies
608 are handled in units of ppm, whereas the 'dfreq' argument is in
609 terms of the gradient of the (offset) v (local time) function. */
610 current_freq_ppm += dfreq * (1.0e6 - current_freq_ppm);
611
612 current_freq_ppm = clamp_freq(current_freq_ppm);
613
614 DEBUG_LOG("old_freq=%.3fppm new_freq=%.3fppm offset=%.6fsec",
615 old_freq_ppm, current_freq_ppm, doffset);
616
617 /* Call the system-specific driver for setting the frequency */
618 current_freq_ppm = (*drv_set_freq)(current_freq_ppm);
619 dfreq = (current_freq_ppm - old_freq_ppm) / (1.0e6 - old_freq_ppm);
620
621 (*drv_accrue_offset)(doffset, corr_rate);
622
623 /* Dispatch to all handlers */
624 invoke_parameter_change_handlers(&raw, &cooked, dfreq, doffset, LCL_ChangeAdjust);
625
626 return 1;
627 }
628
629 /* ================================================== */
630
631 void
lcl_InvokeDispersionNotifyHandlers(double dispersion)632 lcl_InvokeDispersionNotifyHandlers(double dispersion)
633 {
634 DispersionNotifyListEntry *ptr;
635
636 for (ptr = dispersion_notify_list.next; ptr != &dispersion_notify_list; ptr = ptr->next) {
637 (ptr->handler)(dispersion, ptr->anything);
638 }
639
640 }
641
642 /* ================================================== */
643
644 void
lcl_RegisterSystemDrivers(lcl_ReadFrequencyDriver read_freq,lcl_SetFrequencyDriver set_freq,lcl_AccrueOffsetDriver accrue_offset,lcl_ApplyStepOffsetDriver apply_step_offset,lcl_OffsetCorrectionDriver offset_convert,lcl_SetLeapDriver set_leap,lcl_SetSyncStatusDriver set_sync_status)645 lcl_RegisterSystemDrivers(lcl_ReadFrequencyDriver read_freq,
646 lcl_SetFrequencyDriver set_freq,
647 lcl_AccrueOffsetDriver accrue_offset,
648 lcl_ApplyStepOffsetDriver apply_step_offset,
649 lcl_OffsetCorrectionDriver offset_convert,
650 lcl_SetLeapDriver set_leap,
651 lcl_SetSyncStatusDriver set_sync_status)
652 {
653 drv_read_freq = read_freq;
654 drv_set_freq = set_freq;
655 drv_accrue_offset = accrue_offset;
656 drv_apply_step_offset = apply_step_offset;
657 drv_offset_convert = offset_convert;
658 drv_set_leap = set_leap;
659 drv_set_sync_status = set_sync_status;
660
661 current_freq_ppm = (*drv_read_freq)();
662
663 DEBUG_LOG("Local freq=%.3fppm", current_freq_ppm);
664 }
665
666 /* ================================================== */
667 /* Look at the current difference between the system time and the NTP
668 time, and make a step to cancel it. */
669
670 int
LCL_MakeStep(void)671 LCL_MakeStep(void)
672 {
673 struct timespec raw;
674 double correction;
675
676 LCL_ReadRawTime(&raw);
677 LCL_GetOffsetCorrection(&raw, &correction, NULL);
678
679 if (!check_offset(&raw, -correction))
680 return 0;
681
682 /* Cancel remaining slew and make the step */
683 LCL_AccumulateOffset(correction, 0.0);
684 if (!LCL_ApplyStepOffset(-correction))
685 return 0;
686
687 LOG(LOGS_WARN, "System clock was stepped by %.6f seconds", correction);
688
689 return 1;
690 }
691
692 /* ================================================== */
693
694 void
LCL_CancelOffsetCorrection(void)695 LCL_CancelOffsetCorrection(void)
696 {
697 struct timespec raw;
698 double correction;
699
700 LCL_ReadRawTime(&raw);
701 LCL_GetOffsetCorrection(&raw, &correction, NULL);
702 LCL_AccumulateOffset(correction, 0.0);
703 }
704
705 /* ================================================== */
706
707 int
LCL_CanSystemLeap(void)708 LCL_CanSystemLeap(void)
709 {
710 return drv_set_leap ? 1 : 0;
711 }
712
713 /* ================================================== */
714
715 void
LCL_SetSystemLeap(int leap,int tai_offset)716 LCL_SetSystemLeap(int leap, int tai_offset)
717 {
718 if (drv_set_leap) {
719 (drv_set_leap)(leap, tai_offset);
720 }
721 }
722
723 /* ================================================== */
724
725 double
LCL_SetTempComp(double comp)726 LCL_SetTempComp(double comp)
727 {
728 double uncomp_freq_ppm;
729
730 if (temp_comp_ppm == comp)
731 return comp;
732
733 /* Undo previous compensation */
734 current_freq_ppm = (current_freq_ppm + temp_comp_ppm) /
735 (1.0 - 1.0e-6 * temp_comp_ppm);
736
737 uncomp_freq_ppm = current_freq_ppm;
738
739 /* Apply new compensation */
740 current_freq_ppm = current_freq_ppm * (1.0 - 1.0e-6 * comp) - comp;
741
742 /* Call the system-specific driver for setting the frequency */
743 current_freq_ppm = (*drv_set_freq)(current_freq_ppm);
744
745 temp_comp_ppm = (uncomp_freq_ppm - current_freq_ppm) /
746 (1.0e-6 * uncomp_freq_ppm + 1.0);
747
748 return temp_comp_ppm;
749 }
750
751 /* ================================================== */
752
753 void
LCL_SetSyncStatus(int synchronised,double est_error,double max_error)754 LCL_SetSyncStatus(int synchronised, double est_error, double max_error)
755 {
756 if (drv_set_sync_status) {
757 (drv_set_sync_status)(synchronised, est_error, max_error);
758 }
759 }
760
761 /* ================================================== */
762