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