diff --git a/configure b/configure index abdbf2d..c75f6c9 100755 --- a/configure +++ b/configure @@ -394,7 +394,7 @@ case $SYSTEM in esac ;; Linux* ) - EXTRA_OBJECTS="sys_generic.o sys_linux.o wrap_adjtimex.o" + EXTRA_OBJECTS="sys_generic.o sys_linux.o sys_timex.o" [ $try_libcap != "0" ] && try_libcap=1 try_rtc=1 [ $try_seccomp != "0" ] && try_seccomp=1 diff --git a/sys_linux.c b/sys_linux.c index 285dee2..f554331 100644 --- a/sys_linux.c +++ b/sys_linux.c @@ -62,14 +62,21 @@ #endif #endif -#include "sys_generic.h" #include "sys_linux.h" +#include "sys_timex.h" #include "conf.h" #include "logging.h" -#include "wrap_adjtimex.h" -/* The threshold for adjtimex maxerror when the kernel sets the UNSYNC flag */ -#define UNSYNC_MAXERROR 16.0 +/* Frequency scale to convert from ppm to the timex freq */ +#define FREQ_SCALE (double)(1 << 16) + +/* Definitions used if missed in the system headers */ +#ifndef ADJ_SETOFFSET +#define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */ +#endif +#ifndef ADJ_NANO +#define ADJ_NANO 0x2000 /* select nanosecond resolution */ +#endif /* This is the uncompensated system tick value */ static int nominal_tick; @@ -113,11 +120,19 @@ our_round(double x) static int apply_step_offset(double offset) { - if (TMX_ApplyStepOffset(-offset) < 0) { - DEBUG_LOG(LOGF_SysLinux, "adjtimex() failed"); - return 0; + struct timex txc; + + txc.modes = ADJ_SETOFFSET | ADJ_NANO; + txc.time.tv_sec = -offset; + txc.time.tv_usec = 1.0e9 * (-offset - txc.time.tv_sec); + if (txc.time.tv_usec < 0) { + txc.time.tv_sec--; + txc.time.tv_usec += 1000000000; } + if (SYS_Timex_Adjust(&txc, 1) < 0) + return 0; + return 1; } @@ -131,6 +146,7 @@ apply_step_offset(double offset) static double set_frequency(double freq_ppm) { + struct timex txc; long required_tick; double required_freq; int required_delta_tick; @@ -154,14 +170,15 @@ set_frequency(double freq_ppm) required_freq = -(freq_ppm - dhz * required_delta_tick); required_tick = nominal_tick - required_delta_tick; - if (TMX_SetFrequency(&required_freq, required_tick) < 0) { - LOG_FATAL(LOGF_SysLinux, "adjtimex failed for set_frequency, freq_ppm=%10.4e required_freq=%10.4e required_tick=%ld", - freq_ppm, required_freq, required_tick); - } + txc.modes = ADJ_TICK | ADJ_FREQUENCY; + txc.freq = required_freq * FREQ_SCALE; + txc.tick = required_tick; + + SYS_Timex_Adjust(&txc, 0); current_delta_tick = required_delta_tick; - return dhz * current_delta_tick - required_freq; + return dhz * current_delta_tick - txc.freq / FREQ_SCALE; } /* ================================================== */ @@ -170,60 +187,15 @@ set_frequency(double freq_ppm) static double read_frequency(void) { - long tick; - double freq; + struct timex txc; - if (TMX_GetFrequency(&freq, &tick) < 0) { - LOG_FATAL(LOGF_SysLinux, "adjtimex() failed"); - } + txc.modes = 0; - current_delta_tick = nominal_tick - tick; - - return dhz * current_delta_tick - freq; -} + SYS_Timex_Adjust(&txc, 0); -/* ================================================== */ + current_delta_tick = nominal_tick - txc.tick; -static void -set_leap(int leap) -{ - int applied; - - applied = 0; - if (!leap && TMX_GetLeapApplied(&applied) < 0) { - LOG_FATAL(LOGF_SysLinux, "adjtimex() failed in set_leap"); - } - - if (TMX_SetLeap(leap) < 0) { - LOG_FATAL(LOGF_SysLinux, "adjtimex() failed in set_leap"); - } - - LOG(LOGS_INFO, LOGF_SysLinux, "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) -{ - if (synchronised) { - if (est_error > UNSYNC_MAXERROR) - est_error = UNSYNC_MAXERROR; - if (max_error >= UNSYNC_MAXERROR) { - max_error = UNSYNC_MAXERROR; - synchronised = 0; - } - } else { - est_error = max_error = UNSYNC_MAXERROR; - } - - /* Clear the UNSYNC flag only if rtcsync is enabled */ - if (!CNF_GetRtcSync()) - synchronised = 0; - - TMX_SetSync(synchronised, est_error, max_error); + return dhz * current_delta_tick - txc.freq / FREQ_SCALE; } /* ================================================== */ @@ -234,10 +206,16 @@ set_sync_status(int synchronised, double est_error, double max_error) * a +/- 10% movement of tick away from the nominal value 1e6/USER_HZ. */ static int -guess_hz(int tick) +guess_hz(void) { - int i, tick_lo, tick_hi, ihz; + struct timex txc; + int i, tick, tick_lo, tick_hi, ihz; double tick_nominal; + + txc.modes = 0; + SYS_Timex_Adjust(&txc, 0); + tick = txc.tick; + /* Pick off the hz=100 case first */ if (tick >= 9000 && tick <= 11000) { return 100; @@ -255,6 +233,8 @@ guess_hz(int tick) } /* oh dear. doomed. */ + LOG_FATAL(LOGF_SysLinux, "Can't determine hz from tick %d", tick); + return 0; } @@ -296,21 +276,12 @@ static void get_version_specific_details(void) { int major, minor, patch; - long tick; - double freq; struct utsname uts; hz = get_hz(); - if (!hz) { - if (TMX_GetFrequency(&freq, &tick) < 0) - LOG_FATAL(LOGF_SysLinux, "adjtimex() failed"); - - hz = guess_hz(tick); - - if (!hz) - LOG_FATAL(LOGF_SysLinux, "Can't determine hz from tick %ld", tick); - } + if (!hz) + hz = guess_hz(); dhz = (double) hz; nominal_tick = (1000000L + (hz/2))/hz; /* Mirror declaration in kernel */ @@ -353,6 +324,48 @@ get_version_specific_details(void) hz, nominal_tick, max_tick_bias); } +/* ================================================== */ + +static void +reset_adjtime_offset(void) +{ + struct timex txc; + + /* Reset adjtime() offset */ + txc.modes = ADJ_OFFSET_SINGLESHOT; + txc.offset = 0; + + SYS_Timex_Adjust(&txc, 0); +} + +/* ================================================== */ + +static int +test_step_offset(void) +{ + struct timex txc; + + /* Zero maxerror and check it's reset to a maximum after ADJ_SETOFFSET. + This seems to be the only way how to verify that the kernel really + supports the ADJ_SETOFFSET mode as it doesn't return an error on unknown + mode. */ + + txc.modes = MOD_MAXERROR; + txc.maxerror = 0; + + if (SYS_Timex_Adjust(&txc, 1) < 0 || txc.maxerror != 0) + return 0; + + txc.modes = ADJ_SETOFFSET | ADJ_NANO; + txc.time.tv_sec = 0; + txc.time.tv_usec = 0; + + if (SYS_Timex_Adjust(&txc, 1) < 0 || txc.maxerror < 100000) + return 0; + + return 1; +} + /* ================================================== */ /* Initialisation code for this module */ @@ -361,20 +374,17 @@ SYS_Linux_Initialise(void) { get_version_specific_details(); - if (TMX_ResetOffset() < 0) { - LOG_FATAL(LOGF_SysLinux, "adjtimex() failed"); - } + reset_adjtime_offset(); - if (have_setoffset && TMX_TestStepOffset() < 0) { + if (have_setoffset && !test_step_offset()) { LOG(LOGS_INFO, LOGF_SysLinux, "adjtimex() doesn't support ADJ_SETOFFSET"); have_setoffset = 0; } - SYS_Generic_CompleteFreqDriver(1.0e6 * max_tick_bias / nominal_tick, - 1.0 / tick_update_hz, - read_frequency, set_frequency, - have_setoffset ? apply_step_offset : NULL, - set_leap, set_sync_status); + SYS_Timex_InitialiseWithFunctions(1.0e6 * max_tick_bias / nominal_tick, + 1.0 / tick_update_hz, + read_frequency, set_frequency, + have_setoffset ? apply_step_offset : NULL); } /* ================================================== */ @@ -383,7 +393,7 @@ SYS_Linux_Initialise(void) void SYS_Linux_Finalise(void) { - SYS_Generic_Finalise(); + SYS_Timex_Finalise(); } /* ================================================== */ diff --git a/wrap_adjtimex.c b/wrap_adjtimex.c deleted file mode 100644 index b543718..0000000 --- a/wrap_adjtimex.c +++ /dev/null @@ -1,198 +0,0 @@ -/* - chronyd/chronyc - Programs for keeping computer clocks accurate. - - ********************************************************************** - * Copyright (C) Richard P. Curnow 1997-2002 - * Copyright (C) Miroslav Lichvar 2011-2012, 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. - * - ********************************************************************** - - ======================================================================= - - This is a wrapper around the Linux adjtimex system call. - - */ - -#include "config.h" - -#include "wrap_adjtimex.h" - -#include - -/* Definitions used if missing in the system headers */ -#ifndef ADJ_TAI -#define ADJ_TAI 0x0080 /* set TAI offset */ -#endif -#ifndef ADJ_SETOFFSET -#define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */ -#endif -#ifndef ADJ_NANO -#define ADJ_NANO 0x2000 /* select nanosecond resolution */ -#endif -#ifndef ADJ_OFFSET_SS_READ -#define ADJ_OFFSET_SS_READ 0xa001 /* read-only adjtime */ -#endif - -/* Frequency offset scale (shift) */ -#define SHIFT_USEC 16 - -static int status = 0; - -int -TMX_ResetOffset(void) -{ - struct timex txc; - - /* Reset adjtime() offset */ - txc.modes = ADJ_OFFSET_SINGLESHOT; - txc.offset = 0; - if (adjtimex(&txc) < 0) - return -1; - - /* Reset PLL offset */ - txc.modes = ADJ_OFFSET | ADJ_STATUS; - txc.status = STA_PLL; - txc.offset = 0; - if (adjtimex(&txc) < 0) - return -1; - - /* Set status back */ - txc.modes = ADJ_STATUS; - txc.status = status; - if (adjtimex(&txc) < 0) - return -1; - - return 0; -} - -int -TMX_SetFrequency(double *freq, long tick) -{ - struct timex txc; - - txc.modes = ADJ_TICK | ADJ_FREQUENCY; - - txc.freq = (long)(*freq * (double)(1 << SHIFT_USEC)); - *freq = txc.freq / (double)(1 << SHIFT_USEC); - txc.tick = tick; - - return adjtimex(&txc); -} - -int -TMX_GetFrequency(double *freq, long *tick) -{ - struct timex txc; - int result; - txc.modes = 0; /* pure read */ - result = adjtimex(&txc); - *freq = txc.freq / (double)(1 << SHIFT_USEC); - *tick = txc.tick; - return result; -} - -int -TMX_SetLeap(int leap) -{ - struct timex txc; - - status &= ~(STA_INS | STA_DEL); - - if (leap > 0) { - status |= STA_INS; - } else if (leap < 0) { - status |= STA_DEL; - } - - txc.modes = ADJ_STATUS; - txc.status = status; - - return adjtimex(&txc); -} - -int -TMX_GetLeapApplied(int *applied) -{ - struct timex txc; - int state; - - txc.modes = 0; - state = adjtimex(&txc); - if (state < 0) - return -1; - - *applied = state == TIME_WAIT; - - return 0; -} - -int TMX_SetSync(int sync, double est_error, double max_error) -{ - struct timex txc; - - if (sync) { - status &= ~STA_UNSYNC; - } else { - status |= STA_UNSYNC; - } - - txc.modes = ADJ_STATUS | ADJ_ESTERROR | ADJ_MAXERROR; - txc.status = status; - txc.esterror = est_error * 1.0e6; - txc.maxerror = max_error * 1.0e6; - - return adjtimex(&txc); -} - -int -TMX_TestStepOffset(void) -{ - struct timex txc; - - /* Zero maxerror and check it's reset to a maximum after ADJ_SETOFFSET. - This seems to be the only way how to verify that the kernel really - supports the ADJ_SETOFFSET mode as it doesn't return an error on unknown - mode. */ - - txc.modes = ADJ_MAXERROR; - txc.maxerror = 0; - if (adjtimex(&txc) < 0 || txc.maxerror != 0) - return -1; - - txc.modes = ADJ_SETOFFSET | ADJ_NANO; - txc.time.tv_sec = 0; - txc.time.tv_usec = 0; - if (adjtimex(&txc) < 0 || txc.maxerror < 100000) - return -1; - - return 0; -} - -int -TMX_ApplyStepOffset(double offset) -{ - struct timex txc; - - txc.modes = ADJ_SETOFFSET | ADJ_NANO; - txc.time.tv_sec = offset; - txc.time.tv_usec = 1.0e9 * (offset - txc.time.tv_sec); - if (txc.time.tv_usec < 0) { - txc.time.tv_sec--; - txc.time.tv_usec += 1000000000; - } - - return adjtimex(&txc); -} diff --git a/wrap_adjtimex.h b/wrap_adjtimex.h deleted file mode 100644 index 97d4164..0000000 --- a/wrap_adjtimex.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - chronyd/chronyc - Programs for keeping computer clocks accurate. - - ********************************************************************** - * Copyright (C) Richard P. Curnow 1997-2002 - * - * 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. - * - ********************************************************************** - - ======================================================================= - - The header file for the adjtimex wrapper - */ - -#ifndef GOT_WRAP_ADJTIMEX_H -#define GOT_WRAP_ADJTIMEX_H - -int TMX_ResetOffset(void); -int TMX_SetFrequency(double *freq, long tick); -int TMX_GetFrequency(double *freq, long *tick); -int TMX_SetLeap(int leap); -int TMX_GetLeapApplied(int *applied); -int TMX_SetSync(int sync, double est_error, double max_error); -int TMX_TestStepOffset(void); -int TMX_ApplyStepOffset(double offset); - -#endif /* GOT_WRAP_ADJTIMEX_H */ -