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);
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);
+ UTI_AddDoubleToTimeval(&old_time, -offset, &new_time);
- if (settimeofday(&new_time, NULL) < 0) {
- LOG_FATAL(LOGF_SysLinux, "settimeofday() failed");
- }
+ if (settimeofday(&new_time, NULL) < 0) {
+ LOG_FATAL(LOGF_SysLinux, "settimeofday() failed");
+ }
- if (gettimeofday(&old_time, NULL) < 0) {
- LOG_FATAL(LOGF_SysLinux, "gettimeofday() 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_DiffTimevalsToDouble(&err, &old_time, &new_time);
+ lcl_InvokeDispersionNotifyHandlers(fabs(err));
+ }
initiate_slew();
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) {
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 */
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