/* chronyd/chronyc - Programs for keeping computer clocks accurate. ********************************************************************** * Copyright (C) Richard P. Curnow 1997-2003 * Copyright (C) Miroslav Lichvar 2009-2012, 2014-2015, 2017 * * 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. * ********************************************************************** ======================================================================= Driver for systems that implement the adjtimex()/ntp_adjtime() system call */ #include "config.h" #include "sysincl.h" #include "conf.h" #include "privops.h" #include "sys_generic.h" #include "sys_timex.h" #include "logging.h" #ifdef PRIVOPS_ADJUSTTIMEX #define NTP_ADJTIME PRV_AdjustTimex #define NTP_ADJTIME_NAME "ntp_adjtime" #else #ifdef LINUX #define NTP_ADJTIME adjtimex #define NTP_ADJTIME_NAME "adjtimex" #else #define NTP_ADJTIME ntp_adjtime #define NTP_ADJTIME_NAME "ntp_adjtime" #endif #endif /* Maximum frequency offset accepted by the kernel (in ppm) */ #define MAX_FREQ 500.0 /* Frequency scale to convert from ppm to the timex freq */ #define FREQ_SCALE (double)(1 << 16) /* Threshold for the timex maxerror when the kernel sets the UNSYNC flag */ #define MAX_SYNC_ERROR 16.0 /* Minimum assumed rate at which the kernel updates the clock frequency */ #define MIN_TICK_RATE 100 /* Saved timex status */ static int sys_status; /* Saved TAI-UTC offset */ static int sys_tai_offset; /* ================================================== */ static double convert_timex_frequency(const struct timex *txc) { double freq_ppm; freq_ppm = txc->freq / FREQ_SCALE; #ifdef MACOSX /* Temporary workaround for Apple bug treating freq as unsigned number */ if (freq_ppm > 32767) { freq_ppm -= 65536; } #endif return -freq_ppm; } /* ================================================== */ static double read_frequency(void) { struct timex txc; txc.modes = 0; SYS_Timex_Adjust(&txc, 0); return convert_timex_frequency(&txc); } /* ================================================== */ static double set_frequency(double freq_ppm) { struct timex txc; txc.modes = MOD_FREQUENCY; txc.freq = freq_ppm * -FREQ_SCALE; SYS_Timex_Adjust(&txc, 0); return convert_timex_frequency(&txc); } /* ================================================== */ static void set_leap(int leap, int tai_offset) { struct timex txc; int applied, prev_status; txc.modes = 0; applied = SYS_Timex_Adjust(&txc, 0) == TIME_WAIT; prev_status = sys_status; sys_status &= ~(STA_INS | STA_DEL); if (leap > 0) sys_status |= STA_INS; else if (leap < 0) sys_status |= STA_DEL; txc.modes = MOD_STATUS; txc.status = sys_status; #ifdef MOD_TAI if (tai_offset) { txc.modes |= MOD_TAI; txc.constant = tai_offset; if (applied && !(sys_status & (STA_INS | STA_DEL))) sys_tai_offset += prev_status & STA_INS ? 1 : -1; if (sys_tai_offset != tai_offset) { sys_tai_offset = tai_offset; LOG(LOGS_INFO, "System clock TAI offset set to %d seconds", tai_offset); } } #endif SYS_Timex_Adjust(&txc, 0); if (prev_status != sys_status) { LOG(LOGS_INFO, "System clock status %s leap second", leap ? (leap > 0 ? "set to insert" : "set to delete") : (applied ? "reset after" : "set to not insert/delete")); } } /* ================================================== */ static void set_sync_status(int synchronised, double est_error, double max_error) { struct timex txc; if (synchronised) { if (est_error > MAX_SYNC_ERROR) est_error = MAX_SYNC_ERROR; if (max_error >= MAX_SYNC_ERROR) { max_error = MAX_SYNC_ERROR; synchronised = 0; } } else { est_error = max_error = MAX_SYNC_ERROR; } #ifdef LINUX /* On Linux clear the UNSYNC flag only if rtcsync is enabled */ if (!CNF_GetRtcSync()) synchronised = 0; #endif if (synchronised) sys_status &= ~STA_UNSYNC; else sys_status |= STA_UNSYNC; txc.modes = MOD_STATUS | MOD_ESTERROR | MOD_MAXERROR; txc.status = sys_status; txc.esterror = est_error * 1.0e6; txc.maxerror = max_error * 1.0e6; if (SYS_Timex_Adjust(&txc, 1) < 0) ; } /* ================================================== */ static void initialise_timex(void) { struct timex txc; sys_status = STA_UNSYNC; sys_tai_offset = 0; /* Reset PLL offset */ txc.modes = MOD_OFFSET | MOD_STATUS; txc.status = STA_PLL | sys_status; txc.offset = 0; SYS_Timex_Adjust(&txc, 0); /* Turn PLL off */ txc.modes = MOD_STATUS; txc.status = sys_status; SYS_Timex_Adjust(&txc, 0); } /* ================================================== */ void SYS_Timex_Initialise(void) { SYS_Timex_InitialiseWithFunctions(MAX_FREQ, 1.0 / MIN_TICK_RATE, NULL, NULL, NULL, 0.0, 0.0, NULL, NULL); } /* ================================================== */ void SYS_Timex_InitialiseWithFunctions(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, double min_fastslew_offset, double max_fastslew_rate, lcl_AccrueOffsetDriver sys_accrue_offset, lcl_OffsetCorrectionDriver sys_get_offset_correction) { initialise_timex(); SYS_Generic_CompleteFreqDriver(max_set_freq_ppm, max_set_freq_delay, sys_read_freq ? sys_read_freq : read_frequency, sys_set_freq ? sys_set_freq : set_frequency, sys_apply_step_offset, min_fastslew_offset, max_fastslew_rate, sys_accrue_offset, sys_get_offset_correction, set_leap, set_sync_status); } /* ================================================== */ void SYS_Timex_Finalise(void) { SYS_Generic_Finalise(); } /* ================================================== */ int SYS_Timex_Adjust(struct timex *txc, int ignore_error) { int state; #ifdef SOLARIS /* The kernel seems to check the constant even when it's not being set */ if (!(txc->modes & MOD_TIMECONST)) txc->constant = 10; #endif state = NTP_ADJTIME(txc); if (state < 0) { LOG(ignore_error ? LOGS_DEBUG : LOGS_FATAL, NTP_ADJTIME_NAME"(0x%x) failed : %s", txc->modes, strerror(errno)); } return state; }