diff --git a/chrony_timex.h b/chrony_timex.h index 870b5a9..e262ff6 100644 --- a/chrony_timex.h +++ b/chrony_timex.h @@ -40,6 +40,7 @@ struct timex { #define ADJ_MAXERROR 0x0004 /* maximum time error */ #define ADJ_STATUS 0x0010 /* clock status */ #define ADJ_TIMECONST 0x0020 /* pll time constant */ +#define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */ #define ADJ_NANO 0x2000 /* select nanosecond resolution */ #define ADJ_TICK 0x4000 /* tick value */ #define ADJ_OFFSET_SINGLESHOT 0x8001 /* old-fashioned adjtime */ diff --git a/sys_linux.c b/sys_linux.c index c26023e..0734361 100644 --- a/sys_linux.c +++ b/sys_linux.c @@ -115,6 +115,9 @@ static int have_readonly_adjtime; adjustments. */ static int have_nanopll; +/* Flag indicating whether adjtimex() can step the clock */ +static int have_setoffset; + /* ================================================== */ static void handle_end_of_slew(void *anything); @@ -612,23 +615,29 @@ apply_step_offset(double offset) abort_slew(); } - if (gettimeofday(&old_time, NULL) < 0) { - LOG_FATAL(LOGF_SysLinux, "gettimeofday() failed"); + if (have_setoffset) { + if (TMX_ApplyStepOffset(-offset) < 0) { + LOG_FATAL(LOGF_SysLinux, "adjtimex() failed"); + } + } else { + if (gettimeofday(&old_time, NULL) < 0) { + LOG_FATAL(LOGF_SysLinux, "gettimeofday() failed"); + } + + UTI_AddDoubleToTimeval(&old_time, -offset, &new_time); + + if (settimeofday(&new_time, NULL) < 0) { + LOG_FATAL(LOGF_SysLinux, "settimeofday() failed"); + } + + if (gettimeofday(&old_time, NULL) < 0) { + LOG_FATAL(LOGF_SysLinux, "gettimeofday() failed"); + } + + UTI_DiffTimevalsToDouble(&err, &old_time, &new_time); + lcl_InvokeDispersionNotifyHandlers(fabs(err)); } - UTI_AddDoubleToTimeval(&old_time, -offset, &new_time); - - if (settimeofday(&new_time, NULL) < 0) { - LOG_FATAL(LOGF_SysLinux, "settimeofday() failed"); - } - - if (gettimeofday(&old_time, NULL) < 0) { - LOG_FATAL(LOGF_SysLinux, "gettimeofday() failed"); - } - - UTI_DiffTimevalsToDouble(&err, &old_time, &new_time); - lcl_InvokeDispersionNotifyHandlers(fabs(err)); - initiate_slew(); } @@ -1010,6 +1019,13 @@ get_version_specific_details(void) have_nanopll = 1; } + /* ADJ_SETOFFSET support */ + if (kernelvercmp(major, minor, patch, 2, 6, 39) < 0) { + have_setoffset = 0; + } else { + have_setoffset = 1; + } + /* Override freq_scale if it appears in conf file */ CNF_GetLinuxFreqScale(&set_config_freq_scale, &config_freq_scale); if (set_config_freq_scale) { @@ -1049,6 +1065,11 @@ SYS_Linux_Initialise(void) have_nanopll = 0; } + if (have_setoffset && TMX_TestStepOffset() < 0) { + LOG(LOGS_INFO, LOGF_SysLinux, "adjtimex() doesn't support ADJ_SETOFFSET"); + have_setoffset = 0; + } + TMX_SetSync(CNF_GetRTCSync()); /* Read current kernel frequency */ diff --git a/wrap_adjtimex.c b/wrap_adjtimex.c index 123cd32..ed93280 100644 --- a/wrap_adjtimex.c +++ b/wrap_adjtimex.c @@ -229,5 +229,53 @@ TMX_GetPLLOffsetLeft(long *offset) return result; } +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; + 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; + if (offset >= 0) { + txc.time.tv_sec = offset; + } else { + txc.time.tv_sec = offset - 1; + } + + /* ADJ_NANO changes the status even with ADJ_SETOFFSET, use it only when + STA_NANO is already enabled */ + if (status & STA_PLL) { + txc.modes |= ADJ_NANO; + txc.time.tv_usec = 1e9 * (offset - txc.time.tv_sec); + } else { + txc.time.tv_usec = 1e6 * (offset - txc.time.tv_sec); + } + + return adjtimex(&txc); +} + #endif diff --git a/wrap_adjtimex.h b/wrap_adjtimex.h index 6736f5c..e3b9cb0 100644 --- a/wrap_adjtimex.h +++ b/wrap_adjtimex.h @@ -75,6 +75,8 @@ int TMX_SetSync(int sync); int TMX_EnableNanoPLL(void); int TMX_ApplyPLLOffset(long offset); int TMX_GetPLLOffsetLeft(long *offset); +int TMX_TestStepOffset(void); +int TMX_ApplyStepOffset(double offset); #endif /* GOT_WRAP_ADJTIMEX_H */