if SOLARIS
libtimeSync_la_SOURCES += slewAdjtime.c
+libtimeSync_la_SOURCES += pllNone.c
endif
if FREEBSD
libtimeSync_la_SOURCES += slewAdjtime.c
+libtimeSync_la_SOURCES += pllNone.c
endif
if LINUX
libtimeSync_la_SOURCES += slewLinux.c
+libtimeSync_la_SOURCES += pllLinux.c
endif
--- /dev/null
+/*********************************************************
+ * Copyright (C) 2010 VMware, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * 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 Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *********************************************************/
+
+/**
+ * @file pllLinux.c
+ *
+ * Implementation of the NTP PLL using Linux's adjtimex system call.
+ */
+
+#include "timeSync.h"
+#include "timeSyncPosix.h"
+
+#include <errno.h>
+#include <glib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/timex.h>
+#include "vm_assert.h"
+
+
+static void
+TimeSyncLogPLLState(const char *prefix, struct timex *tx)
+{
+ g_debug("%s : off %ld freq %ld maxerr %ld esterr %ld status %d "
+ "const %ld precision %ld tolerance %ld tick %ld\n",
+ prefix, tx->offset, tx->freq, tx->maxerror, tx->esterror,
+ tx->status, tx->constant, tx->precision, tx->tolerance, tx->tick);
+}
+
+/*
+ ******************************************************************************
+ * TimeSync_PLLSupported -- */ /**
+ *
+ * Report whether the platform supports an NTP style Type-II Phase Locked
+ * Loop for correcting the time.
+ *
+ * @return TRUE iff NTP Phase Locked Loop is supported.
+ *
+ ******************************************************************************
+ */
+
+Bool
+TimeSync_PLLSupported(void)
+{
+ return TRUE;
+}
+
+
+/*
+ ******************************************************************************
+ * TimeSync_PLLSetFrequency -- */ /**
+ *
+ * Set the frequency of the PLL.
+ *
+ * @param[in] ppmCorrection The parts per million error that should be
+ * corrected. This value is the ppm shifted
+ * left by 16 to match NTP.
+ *
+ * @return TRUE on success.
+ *
+ ******************************************************************************
+ */
+
+Bool
+TimeSync_PLLSetFrequency(int64 ppmCorrection)
+{
+ struct timex tx;
+ int error;
+
+ tx.modes = ADJ_FREQUENCY;
+ tx.freq = ppmCorrection;
+
+ error = adjtimex(&tx);
+ if (error == -1) {
+ g_debug("%s: adjtimex failed: %d %s\n", __FUNCTION__,
+ error, strerror(errno));
+ return FALSE;
+ }
+ TimeSyncLogPLLState(__FUNCTION__, &tx);
+
+ return TRUE;
+}
+
+
+/*
+ ******************************************************************************
+ * TimeSync_PLLUpdate -- */ /**
+ *
+ * Updates the PLL with a new offset.
+ *
+ * @param[in] offset The offset between the host and the guest.
+ *
+ * @return TRUE on success.
+ *
+ ******************************************************************************
+ */
+
+Bool
+TimeSync_PLLUpdate(int64 offset)
+{
+ struct timex tx;
+ int error;
+
+ if (offset < -500000) {
+ offset = -500000;
+ g_debug("%s: clamped offset at -500000\n", __FUNCTION__);
+ }
+ if (offset > 500000) {
+ offset = 500000;
+ g_debug("%s: clamped offset at 500000\n", __FUNCTION__);
+ }
+
+
+ tx.modes = ADJ_OFFSET | ADJ_MAXERROR | ADJ_ESTERROR;
+ tx.offset = offset;
+ tx.esterror = 0;
+ tx.maxerror = 0;
+
+ error = adjtimex(&tx);
+ if (error == -1) {
+ g_debug("%s: adjtimex set offset failed: %d %s\n", __FUNCTION__,
+ error, strerror(errno));
+ return FALSE;
+ }
+ TimeSyncLogPLLState(__FUNCTION__, &tx);
+
+ /* Ensure that the kernel discipline is in the right mode. STA_PLLs
+ * should be set and STA_UNSYNC should not be set.
+ *
+ * The time constant is a bit trickier. In "Computer Network Time
+ * Synchronization" the terms used are "time constant" and "poll
+ * exponent" where time constant = 2 ^ poll exponent. Valid values for
+ * the poll exponent are 4 through 17, corresponding to a range of 16s
+ * to 131072s (36 hours). On linux things are a bit different though:
+ * tx.constant appears to be the poll exponent and when trying to set
+ * the poll exponent, tx.constant should be set to poll exponent - 4.
+ *
+ * We want to set the time constant to as low a value as possible. The
+ * core NTP PLL that the kernel discipline implements is built assuming
+ * that there is a clock filter with a variable delay of up to 8.
+ * Since TimeSyncReadHostAndGuest retries if the error is large, we
+ * don't need to implement the clock filter. Hence we want a time
+ * constant of 60/8 = 7, but settle for the lowest available: 16. This
+ * allows us to react to changes relatively fast.
+ */
+ if (tx.constant != 4) {
+ tx.modes = ADJ_TIMECONST;
+ tx.constant = 0;
+ error = adjtimex(&tx);
+ if (error == -1) {
+ g_debug("%s: adjtimex set time constant failed: %d %s\n", __FUNCTION__,
+ error, strerror(errno));
+ return FALSE;
+ }
+ g_debug("Set PLL time constant\n");
+ TimeSyncLogPLLState(__FUNCTION__, &tx);
+ }
+ if ((tx.status & STA_PLL) != STA_PLL || (tx.status & STA_UNSYNC) != 0) {
+ tx.modes = ADJ_STATUS;
+ tx.status = STA_PLL;
+ error = adjtimex(&tx);
+ if (error == -1) {
+ g_debug("%s: adjtimex set status failed: %d %s\n", __FUNCTION__,
+ error, strerror(errno));
+ return FALSE;
+ }
+ g_debug("Set PLL status\n");
+ TimeSyncLogPLLState(__FUNCTION__, &tx);
+ }
+ return TRUE;
+}
--- /dev/null
+/*********************************************************
+ * Copyright (C) 2010 VMware, Inc. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * 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 Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *********************************************************/
+
+/**
+ * @file pllNone.c
+ *
+ * Null implementation of the NTP PLL.
+ */
+
+#include "timeSync.h"
+
+#include "vm_assert.h"
+
+
+/*
+ ******************************************************************************
+ * TimeSync_PLLSupported -- */ /**
+ *
+ * Report whether the platform supports an NTP style Type-II Phase Locked
+ * Loop for correcting the time.
+ *
+ * @return TRUE iff NTP Phase Locked Loop is supported.
+ *
+ ******************************************************************************
+ */
+
+Bool
+TimeSync_PLLSupported(void)
+{
+ return FALSE;
+}
+
+
+/*
+ ******************************************************************************
+ * TimeSync_PLLSetFrequency -- */ /**
+ *
+ * Set the frequency of the PLL.
+ *
+ * @param[in] ppmCorrection The parts per million error that should be
+ * corrected. This value is the ppm shifted
+ * left by 16 to match NTP.
+ *
+ * @return FALSE
+ *
+ ******************************************************************************
+ */
+
+Bool
+TimeSync_PLLSetFrequency(int64 ppmCorrection)
+{
+ NOT_IMPLEMENTED();
+ return FALSE;
+}
+
+
+/*
+ ******************************************************************************
+ * TimeSync_PLLUpdate -- */ /**
+ *
+ * Updates the PLL with a new offset.
+ *
+ * @param[in] offset The offset between the host and the guest.
+ *
+ * @return FALSE
+ *
+ ******************************************************************************
+ */
+
+Bool
+TimeSync_PLLUpdate(int64 offset)
+{
+ NOT_IMPLEMENTED();
+ return FALSE;
+}
/*
******************************************************************************
- * TimeSync_EnableTimeSlew -- */ /**
+ * TimeSync_Slew -- */ /**
*
* Slew the clock, correcting 'delta' microseconds. timeSyncPeriod is
- * ignored by this implementation.
+ * ignored by this implementation. Report the amount of the previous
+ * correction that has not been applied.
*
- * @param[in] delta Time difference in us.
- * @param[in] timeSyncPeriod Time interval in us.
+ * @param[in] delta Correction to apply in us.
+ * @param[in] timeSyncPeriod Time interval in us.
+ * @param[out] remaining Amount of previous correction not applied.
*
* @return TRUE on success.
*
*/
Bool
-TimeSync_EnableTimeSlew(int64 delta,
- int64 timeSyncPeriod)
+TimeSync_Slew(int64 delta,
+ int64 timeSyncPeriod,
+ int64 *remaining)
{
struct timeval tx;
struct timeval oldTx;
return FALSE;
}
g_debug("time slew start.\n");
+
+ *remaining = (int64)oldTx.tv_sec * US_PER_SEC + (int64)oldTx.tv_usec;
+
return TRUE;
}
/*
******************************************************************************
- * TimeSync_EnableTimeSlew -- */ /**
+ * TimeSync_Slew -- */ /**
*
* Slew the clock so that the time difference is covered within the
- * timeSyncPeriod. timeSyncPeriod is the interval of the time sync loop and we
- * intend to catch up delta us.
+ * timeSyncPeriod. timeSyncPeriod is the interval of the time sync loop
+ * and we intend to catch up delta us. Report the amount of the previous
+ * correction that has not been applied (this may be negative if more than
+ * timeSyncPeriod elapsed since the last call).
*
* This changes the tick frequency and hence needs to be reset after the time
* sync is achieved.
*
- * @param[in] delta Time difference in us.
- * @param[in] timeSyncPeriod Time interval in us.
+ * All times are in microseconds.
+ *
+ * @param[in] delta Correction to apply.
+ * @param[in] timeSyncPeriod Time interval.
+ * @param[out] remaining Amount of previous correction not applied.
*
* @return TRUE on success.
*
*/
Bool
-TimeSync_EnableTimeSlew(int64 delta,
- int64 timeSyncPeriod)
+TimeSync_Slew(int64 delta,
+ int64 timeSyncPeriod,
+ int64 *remaining)
{
+ static int64 startTime = 0;
+ static int64 tickLength;
+ static int64 deltaRequested;
+
struct timex tx;
int error;
- int64 tick;
+ int64 now;
ASSERT(timeSyncPeriod > 0);
+ if (!TimeSync_GetCurrentTime(&now)) {
+ return FALSE;
+ }
+
+ if (startTime != 0) {
+ int64 ticksElapsed = (now - startTime) / tickLength;
+ int64 deltaApplied = ticksElapsed * (tickLength - TICK_INCR_NOMINAL);
+ *remaining = deltaRequested - deltaApplied;
+ }
+
/*
- * Set the tick so that delta time is corrected in timeSyncPeriod period.
- * tick is the number of microseconds added per clock tick. We adjust this
- * so that we get the desired delta + the timeSyncPeriod in timeSyncPeriod
- * interval.
+ * Set the tick length so that delta time is corrected in
+ * timeSyncPeriod period. tick is the number of microseconds added per
+ * clock tick. We adjust this so that we get the desired delta + the
+ * timeSyncPeriod in timeSyncPeriod interval.
*/
- tx.modes = ADJ_TICK;
- tick = (timeSyncPeriod + delta) /
- ((timeSyncPeriod / US_PER_SEC) * USER_HZ);
- if (tick > TICK_INCR_MAX) {
- tick = TICK_INCR_MAX;
- } else if (tick < TICK_INCR_MIN) {
- tick = TICK_INCR_MIN;
+ tickLength =
+ (timeSyncPeriod + delta) / ((timeSyncPeriod / US_PER_SEC) * USER_HZ);
+ if (tickLength > TICK_INCR_MAX) {
+ tickLength = TICK_INCR_MAX;
+ } else if (tickLength < TICK_INCR_MIN) {
+ tickLength = TICK_INCR_MIN;
}
- tx.tick = tick;
+ tx.tick = tickLength;
+ tx.modes = ADJ_TICK;
+
+ ASSERT(delta != 0 || tickLength == TICK_INCR_NOMINAL);
+
+ startTime = now;
+ deltaRequested = delta;
error = adjtimex(&tx);
if (error == -1) {
+ startTime = 0;
g_debug("adjtimex failed: %s\n", strerror(errno));
return FALSE;
}
#define TIMESYNC_MAX_SAMPLES 4
#define TIMESYNC_GOOD_SAMPLE_THRESHOLD 2000
+/* Once the error drops below TIMESYNC_PLL_ACTIVATE, activate the PLL.
+ * 500ppm error acumulated over a 60 second interval can produce 30ms of
+ * error. */
+#define TIMESYNC_PLL_ACTIVATE (30 * 1000) /* 30ms. */
+/* If the error goes above TIMESYNC_PLL_UNSYNC, deactivate the PLL. */
+#define TIMESYNC_PLL_UNSYNC (2 * TIMESYNC_PLL_ACTIVATE)
+/* Period during which the frequency error of guest time is measured. */
+#define TIMESYNC_CALIBRATION_DURATION (15 * 60 * US_PER_SEC) /* 15min. */
+
typedef enum TimeSyncState {
TIMESYNC_INITIALIZING,
TIMESYNC_STOPPED,
TIMESYNC_RUNNING,
} TimeSyncState;
+typedef enum TimeSyncSlewState {
+ TimeSyncUncalibrated,
+ TimeSyncCalibrating,
+ TimeSyncPLL,
+} TimeSyncSlewState;
+
typedef struct TimeSyncData {
- gboolean slewActive;
- gboolean slewCorrection;
- uint32 slewPercentCorrection;
- uint32 timeSyncPeriod; /* In seconds. */
- TimeSyncState state;
- GSource *timer;
+ gboolean slewActive;
+ gboolean slewCorrection;
+ uint32 slewPercentCorrection;
+ uint32 timeSyncPeriod; /* In seconds. */
+ TimeSyncState state;
+ TimeSyncSlewState slewState;
+ GSource *timer;
} TimeSyncData;
static void TimeSyncSetSlewState(TimeSyncData *data, gboolean active);
+static void TimeSyncResetSlew(TimeSyncData *data);
/**
* Read the time reported by the Host OS.
/**
- * Slew the guest OS time advancement to correct the time. Only correct a
- * portion of the error to avoid overcorrection.
+ * Slew the guest OS time advancement to correct the time.
+ *
+ * In addition to standard slewing (implemented via TimeSync_Slew), we
+ * also support using an NTP style PLL to slew the time. The PLL can take
+ * a while to end up with an accurate measurement of the frequency error,
+ * so before entering PLL mode we calibrate the frequency error over a
+ * period of TIMESYNC_PLL_ACTIVATE seconds.
+ *
+ * When using standard slewing, only correct slewPercentCorrection of the
+ * error. This is to avoid overcorrection when the error is mis-measured,
+ * or overcorrection caused by the daemon waking up later than it is
+ * supposed to leaving the slew in place for longer than anticpiated.
*
* @param[in] data Structure tracking time sync state.
* @param[in] adjustment Amount to correct the guest time.
static gboolean
TimeSyncSlewTime(TimeSyncData *data, int64 adjustment)
{
+ static int64 calibrationStart;
+ static int64 calibrationAdjustment;
+
+ int64 now;
+ int64 remaining = 0;
int64 timeSyncPeriodUS = data->timeSyncPeriod * US_PER_SEC;
int64 slewDiff = (adjustment * data->slewPercentCorrection) / 100;
- return TimeSync_EnableTimeSlew(slewDiff, timeSyncPeriodUS);
+ if (!TimeSync_GetCurrentTime(&now)) {
+ return FALSE;
+ }
+
+ if (adjustment > TIMESYNC_PLL_UNSYNC &&
+ data->slewState != TimeSyncUncalibrated) {
+ g_debug("Adjustment too large (%"FMT64"d), resetting PLL state.\n",
+ adjustment);
+ data->slewState = TimeSyncUncalibrated;
+ }
+
+ if (data->slewState == TimeSyncUncalibrated) {
+ g_debug("Slewing time: adjustment %"FMT64"d\n", adjustment);
+ if (!TimeSync_Slew(slewDiff, timeSyncPeriodUS, &remaining)) {
+ data->slewState = TimeSyncUncalibrated;
+ return FALSE;
+ }
+ if (adjustment < TIMESYNC_PLL_ACTIVATE && TimeSync_PLLSupported()) {
+ g_debug("Starting PLL calibration.\n");
+ calibrationStart = now;
+ /* Starting out the calibration period we are adjustment behind,
+ * but have already requested to correct slewDiff of that. */
+ calibrationAdjustment = slewDiff - adjustment;
+ data->slewState = TimeSyncCalibrating;
+ }
+ } else if (data->slewState == TimeSyncCalibrating) {
+ if (now > calibrationStart + TIMESYNC_CALIBRATION_DURATION) {
+ int64 ppmErr;
+ /* Reset slewing to nominal and find out remaining slew. */
+ TimeSync_Slew(0, timeSyncPeriodUS, &remaining);
+ calibrationAdjustment += adjustment;
+ calibrationAdjustment -= remaining;
+ ppmErr = ((1000000 * calibrationAdjustment) << 16) /
+ (now - calibrationStart);
+ if (ppmErr >> 16 < 500 && ppmErr >> 16 > -500) {
+ g_debug("Activating PLL ppmEst=%"FMT64"d (%"FMT64"d)\n",
+ ppmErr >> 16, ppmErr);
+ TimeSync_PLLUpdate(adjustment);
+ TimeSync_PLLSetFrequency(ppmErr);
+ data->slewState = TimeSyncPLL;
+ } else {
+ /* PPM error is too large to try the PLL. */
+ g_debug("PPM error too large: %"FMT64"d (%"FMT64"d) "
+ "not activating PLL\n", ppmErr >> 16, ppmErr);
+ data->slewState = TimeSyncUncalibrated;
+ }
+ } else {
+ g_debug("Calibrating error: adjustment %"FMT64"d\n", adjustment);
+ if (!TimeSync_Slew(slewDiff, timeSyncPeriodUS, &remaining)) {
+ return FALSE;
+ }
+ calibrationAdjustment += slewDiff;
+ calibrationAdjustment -= remaining;
+ }
+ } else {
+ ASSERT(data->slewState == TimeSyncPLL);
+ g_debug("Updating PLL: adjustment %"FMT64"d\n", adjustment);
+ if (!TimeSync_PLLUpdate(adjustment)) {
+ TimeSyncResetSlew(data);
+ }
+ }
+ return TRUE;
}
static void
TimeSyncResetSlew(TimeSyncData *data)
{
- TimeSyncSlewTime(data, 0);
+ int64 remaining;
+ int64 timeSyncPeriodUS = data->timeSyncPeriod * US_PER_SEC;
+ data->slewState = TimeSyncUncalibrated;
+ TimeSync_Slew(0, timeSyncPeriodUS, &remaining);
+ if (TimeSync_PLLSupported()) {
+ TimeSync_PLLUpdate(0);
+ TimeSync_PLLSetFrequency(0);
+ }
}
data->slewCorrection = FALSE;
data->slewPercentCorrection = TIMESYNC_PERCENT_CORRECTION;
data->state = TIMESYNC_INITIALIZING;
+ data->slewState = TimeSyncUncalibrated;
data->timeSyncPeriod = TIMESYNC_TIME;
data->timer = NULL;
TimeSync_AddToCurrentTime(int64 delta);
Bool
-TimeSync_EnableTimeSlew(int64 delta,
- int64 timeSyncPeriod);
+TimeSync_Slew(int64 delta,
+ int64 timeSyncPeriod,
+ int64 *remaining);
Bool
TimeSync_DisableTimeSlew(void);
+Bool
+TimeSync_PLLUpdate(int64 offset);
+
+Bool
+TimeSync_PLLSetFrequency(int64 ppmCorrection);
+
+Bool
+TimeSync_PLLSupported(void);
+
#endif /* _TIMESYNC_INT_H_ */