From 2794995ad94f35b19284636224f1cfb9dca046a4 Mon Sep 17 00:00:00 2001 From: J William Piggott Date: Wed, 15 Oct 2014 14:21:13 -0400 Subject: hwclock: hctosys drift compensation II Allowing hctosys to drift compensate facilitates: More precise setting of the System Clock early in the boot process when --adjust cannot be used because the file system is not writeable. Applies sub second drift corrections immediately, where as --adjust cannot. Reduces boot time by not calling hwclock multiple times, e.g., --hctosys early before fsck when the file system is read-only, then --adjust later when the file system is read-write and --hctosys again for drift correction. Use of --adjust elsewhere may no longer be necessary. Part II After the original submission of this patch I realized that now all operations except --systz require drift corrected Hardware Clock time. Therefore, it should be done only once early in the process. Upon implementation of that premise many improvements were facilitated: * Adds drift correction to --hctosys. * Adds setting system time with sub-second precision. * Adds --get, a drift corrected 'show' operation. * Improves drift factor calculation precision while greatly simplifying its algorithm. * Fixes --show bug, printing integer sub-seconds, and now uses a more intuitive positive value. * Fixes --predict bug, drift correction must be negated to predict future RTC time. * Reduces the number of function arguments and lines of code. Signed-off-by: J William Piggott --- sys-utils/hwclock.c | 205 +++++++++++++++++++++++----------------------------- 1 file changed, 91 insertions(+), 114 deletions(-) (limited to 'sys-utils') diff --git a/sys-utils/hwclock.c b/sys-utils/hwclock.c index e7801583b..30db8b628 100644 --- a/sys-utils/hwclock.c +++ b/sys-utils/hwclock.c @@ -181,6 +181,18 @@ static void read_date_from_file(struct tm *tm) write_date_to_file(tm); } +/* + * time_t to timeval conversion. + */ +static struct timeval t2tv(time_t timet) +{ + struct timeval rettimeval; + + rettimeval.tv_sec = timet; + rettimeval.tv_usec = 0; + return rettimeval; +} + /* * The difference in seconds between two times in "timeval" format. */ @@ -679,8 +691,7 @@ set_hardware_clock_exact(const time_t sethwtime, * Include in the output the adjustment "sync_duration". */ static void -display_time(const bool hclock_valid, const time_t systime, - const double sync_duration) +display_time(const bool hclock_valid, struct timeval hwctime) { if (!hclock_valid) warnx(_ @@ -692,9 +703,9 @@ display_time(const bool hclock_valid, const time_t systime, char *format = "%c"; char ctime_now[200]; - lt = localtime(&systime); + lt = localtime(&hwctime.tv_sec); strftime(ctime_now, sizeof(ctime_now), format, lt); - printf(_("%s %.6f seconds\n"), ctime_now, -(sync_duration)); + printf(_("%s .%06d seconds\n"), ctime_now, (int)hwctime.tv_usec); } } @@ -808,7 +819,7 @@ static int interpret_date_string(const char *date_opt, time_t * const time_p) * have. */ static int -set_system_clock(const bool hclock_valid, const time_t newtime, +set_system_clock(const bool hclock_valid, const struct timeval newtime, const bool testing) { int retcode; @@ -819,15 +830,11 @@ set_system_clock(const bool hclock_valid, const time_t newtime, "we cannot set the System Time from it.")); retcode = 1; } else { - struct timeval tv; struct tm *broken; int minuteswest; int rc; - tv.tv_sec = newtime; - tv.tv_usec = 0; - - broken = localtime(&newtime); + broken = localtime(&newtime.tv_sec); #ifdef HAVE_TM_GMTOFF minuteswest = -broken->tm_gmtoff / 60; /* GNU extension */ #else @@ -839,7 +846,7 @@ set_system_clock(const bool hclock_valid, const time_t newtime, if (debug) { printf(_("Calling settimeofday:\n")); printf(_("\ttv.tv_sec = %ld, tv.tv_usec = %ld\n"), - (long)tv.tv_sec, (long)tv.tv_usec); + (long)newtime.tv_sec, (long)newtime.tv_usec); printf(_("\ttz.tz_minuteswest = %d\n"), minuteswest); } if (testing) { @@ -849,7 +856,7 @@ set_system_clock(const bool hclock_valid, const time_t newtime, } else { const struct timezone tz = { minuteswest, 0 }; - rc = settimeofday(&tv, &tz); + rc = settimeofday(&newtime, &tz); if (rc) { if (errno == EPERM) { warnx(_ @@ -975,9 +982,9 @@ static int set_system_clock_timezone(const bool universal, const bool testing) */ static void adjust_drift_factor(struct adjtime *adjtime_p, - const time_t nowtime, + const struct timeval nowtime, const bool hclock_valid, - const time_t hclocktime, const double sync_delay) + const struct timeval hclocktime) { if (!hclock_valid) { if (debug) @@ -990,7 +997,7 @@ adjust_drift_factor(struct adjtime *adjtime_p, "calibration time is zero,\n" "so history is bad and calibration startover " "is necessary.\n")); - } else if ((hclocktime - adjtime_p->last_calib_time) < 24 * 60 * 60) { + } else if ((hclocktime.tv_sec - adjtime_p->last_calib_time) < 24 * 60 * 60) { if (debug) printf(_("Not adjusting drift factor because it has " "been less than a day since the last " @@ -1010,39 +1017,16 @@ adjust_drift_factor(struct adjtime *adjtime_p, * but 195 days + 1 second equals 195 days in floats.) */ const double sec_per_day = 24.0 * 60.0 * 60.0; - double atime_per_htime; - double adj_days, cal_days; - double exp_drift, unc_drift; double factor_adjust; double drift_factor; + struct timeval last_calib; - /* Adjusted time units per hardware time unit */ - atime_per_htime = 1.0 + adjtime_p->drift_factor / sec_per_day; + last_calib = t2tv(adjtime_p->last_calib_time); - /* Days since last adjustment (in hardware clock time) */ - adj_days = (double)(hclocktime - adjtime_p->last_adj_time) - / sec_per_day; + factor_adjust = time_diff(nowtime, hclocktime) / + (time_diff(nowtime, last_calib) / sec_per_day); - /* Expected drift (sec) since last adjustment */ - exp_drift = adj_days * adjtime_p->drift_factor - + adjtime_p->not_adjusted; - - /* Uncorrected drift (sec) since last calibration */ - unc_drift = (double)(nowtime - hclocktime) - + sync_delay - exp_drift; - - /* Days since last calibration (in hardware clock time) */ - cal_days = ((double)(adjtime_p->last_adj_time - - adjtime_p->last_calib_time) - + adjtime_p->not_adjusted) - / (sec_per_day * atime_per_htime) + adj_days; - - /* Amount to add to previous drift factor */ - factor_adjust = unc_drift / cal_days; - - /* New drift factor */ drift_factor = adjtime_p->drift_factor + factor_adjust; - if (fabs(drift_factor) > MAX_DRIFT) { if (debug) printf(_("Clock drift factor was calculated as " @@ -1053,19 +1037,19 @@ adjust_drift_factor(struct adjtime *adjtime_p, } else { if (debug) printf(_("Clock drifted %.1f seconds in the past " - "%d seconds in spite of a drift factor of " + "%.1f seconds\nin spite of a drift factor of " "%f seconds/day.\n" "Adjusting drift factor by %f seconds/day\n"), - unc_drift, - (int)(nowtime - adjtime_p->last_calib_time), + time_diff(nowtime, hclocktime), + time_diff(nowtime, last_calib), adjtime_p->drift_factor, factor_adjust); } adjtime_p->drift_factor = drift_factor; } - adjtime_p->last_calib_time = nowtime; + adjtime_p->last_calib_time = nowtime.tv_sec; - adjtime_p->last_adj_time = nowtime; + adjtime_p->last_adj_time = nowtime.tv_sec; adjtime_p->not_adjusted = 0; @@ -1088,26 +1072,23 @@ static void calculate_adjustment(const double factor, const time_t last_time, const double not_adjusted, - const time_t systime, int *adjustment_p, double *retro_p) + const time_t systime, struct timeval *tdrift_p) { double exact_adjustment; exact_adjustment = ((double)(systime - last_time)) * factor / (24 * 60 * 60) + not_adjusted; - *adjustment_p = FLOOR(exact_adjustment); - - *retro_p = exact_adjustment - (double)*adjustment_p; + tdrift_p->tv_sec = FLOOR(exact_adjustment); + tdrift_p->tv_usec = (exact_adjustment - + (double)tdrift_p->tv_sec) * 1E6; if (debug) { printf(P_("Time since last adjustment is %d second\n", "Time since last adjustment is %d seconds\n", (int)(systime - last_time)), (int)(systime - last_time)); - printf(P_("Need to insert %d second and refer time back " - "%.6f seconds ago\n", - "Need to insert %d seconds and refer time back " - "%.6f seconds ago\n", *adjustment_p), - *adjustment_p, *retro_p); + printf(_("Calculated Hardware Clock drift is %ld.%06d seconds\n"), + (long)tdrift_p->tv_sec, (int)tdrift_p->tv_usec); } } @@ -1201,7 +1182,7 @@ static void save_adjtime(const struct adjtime adjtime, const bool testing) */ static void do_adjustment(struct adjtime *adjtime_p, - const bool hclock_valid, const time_t hclocktime, + const bool hclock_valid, const struct timeval hclocktime, const struct timeval read_time, const bool universal, const bool testing) { @@ -1221,27 +1202,13 @@ do_adjustment(struct adjtime *adjtime_p, printf(_("Not setting clock because drift factor %f is far too high.\n"), adjtime_p->drift_factor); } else { - int adjustment; - /* Number of seconds we must insert in the Hardware Clock */ - double retro; - /* - * Fraction of second we have to remove from clock after - * inserting whole seconds. - */ - calculate_adjustment(adjtime_p->drift_factor, - adjtime_p->last_adj_time, - adjtime_p->not_adjusted, - hclocktime, &adjustment, &retro); - if (adjustment > 0 || adjustment < -1) { - set_hardware_clock_exact(hclocktime + adjustment, - time_inc(read_time, -retro), - universal, testing); - adjtime_p->last_adj_time = hclocktime + adjustment; - adjtime_p->not_adjusted = 0; - adjtime_p->dirty = TRUE; - } else if (debug) - printf(_("Needed adjustment is less than one second, " - "so not setting clock.\n")); + set_hardware_clock_exact(hclocktime.tv_sec, + time_inc(read_time, + -(hclocktime.tv_usec / 1E6)), + universal, testing); + adjtime_p->last_adj_time = hclocktime.tv_sec; + adjtime_p->not_adjusted = 0; + adjtime_p->dirty = TRUE; } } @@ -1281,7 +1248,7 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, const bool hctosys, const bool systohc, const bool systz, const struct timeval startup_time, const bool utc, const bool local_opt, - const bool testing, const bool predict) + const bool testing, const bool predict, const bool get) { /* Contents of the adjtime file, or what they should be. */ struct adjtime adjtime; @@ -1300,7 +1267,8 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, * synchronized to its next clock tick when we * started up. Defined only if hclock_valid is true. */ - time_t hclocktime = 0; + struct timeval hclocktime = { 0, 0 }; + struct timeval tdrift; /* local return code */ int rc = 0; @@ -1310,13 +1278,12 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, return EX_NOPERM; } - if (!noadjfile - && (adjust || set || systohc || (!utc && !local_opt) || predict)) { + if (!noadjfile && !(systz && (utc || local_opt))) { rc = read_adjtime(&adjtime); if (rc) return rc; } else { - /* A little trick to avoid reading the file if we don't have to */ + /* A little trick to avoid writing the file if we don't have to */ adjtime.dirty = FALSE; } @@ -1328,7 +1295,7 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, adjtime.dirty = TRUE; } - if (show || adjust || hctosys || (!noadjfile && !systz && !predict)) { + if (show || get || adjust || hctosys || (!noadjfile && !systz && !predict)) { /* data from HW-clock are required */ rc = synchronize_to_clock_tick(); @@ -1351,26 +1318,40 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, */ if (!rc) { rc = read_hardware_clock(universal, - &hclock_valid, &hclocktime); + &hclock_valid, &hclocktime.tv_sec); if (rc && !set && !systohc) return EX_IOERR; } } - - if (show) { - display_time(hclock_valid, hclocktime, - time_diff(read_time, startup_time)); + hclocktime = predict ? t2tv(set_time) : hclocktime; + calculate_adjustment(adjtime.drift_factor, + adjtime.last_adj_time, + adjtime.not_adjusted, + hclocktime.tv_sec, &tdrift); + if (!show && !predict) + hclocktime = time_inc(tdrift, hclocktime.tv_sec); + if (predict) + hclocktime = time_inc(hclocktime, (double) + -(tdrift.tv_sec + tdrift.tv_sec / 1E6)); + if (show || get) { + display_time(hclock_valid, + time_inc(hclocktime, -time_diff + (read_time, startup_time))); } else if (set) { set_hardware_clock_exact(set_time, startup_time, universal, testing); if (!noadjfile) - adjust_drift_factor(&adjtime, set_time, - hclock_valid, - hclocktime, - time_diff(read_time, startup_time)); + adjust_drift_factor(&adjtime, + time_inc(t2tv(set_time), time_diff + (read_time, startup_time)), + hclock_valid, hclocktime); } else if (adjust) { - do_adjustment(&adjtime, hclock_valid, - hclocktime, read_time, universal, testing); + if (tdrift.tv_sec > 0 || tdrift.tv_sec < -1) + do_adjustment(&adjtime, hclock_valid, + hclocktime, read_time, universal, testing); + else + printf(_("Needed adjustment is less than one second, " + "so not setting clock.\n")); } else if (systohc) { struct timeval nowtime, reftime; /* @@ -1386,10 +1367,8 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, reftime.tv_sec, reftime, universal, testing); if (!noadjfile) - adjust_drift_factor(&adjtime, (time_t) - reftime.tv_sec, - hclock_valid, hclocktime, (double) - read_time.tv_usec / 1E6); + adjust_drift_factor(&adjtime, nowtime, + hclock_valid, hclocktime); } else if (hctosys) { rc = set_system_clock(hclock_valid, hclocktime, testing); if (rc) { @@ -1403,19 +1382,12 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, return rc; } } else if (predict) { - int adjustment; - double retro; - - calculate_adjustment(adjtime.drift_factor, - adjtime.last_adj_time, - adjtime.not_adjusted, - set_time, &adjustment, &retro); if (debug) { printf(_ ("At %ld seconds after 1969, RTC is predicted to read %ld seconds after 1969.\n"), - set_time, set_time + adjustment); + set_time, (long)hclocktime.tv_sec); } - display_time(TRUE, set_time + adjustment, -retro); + display_time(TRUE, hclocktime); } if (!noadjfile) save_adjtime(adjtime, testing); @@ -1644,7 +1616,7 @@ int main(int argc, char **argv) /* Variables set by various options; show may also be set later */ /* The options debug, badyear and epoch_option are global */ bool show, set, systohc, hctosys, systz, adjust, getepoch, setepoch, - predict, compare; + predict, compare, get; bool utc, testing, local_opt, noadjfile, directisa; char *date_opt; #ifdef __alpha__ @@ -1657,6 +1629,7 @@ int main(int argc, char **argv) OPT_DATE, OPT_DIRECTISA, OPT_EPOCH, + OPT_GET, OPT_GETEPOCH, OPT_LOCALTIME, OPT_NOADJFILE, @@ -1704,13 +1677,14 @@ int main(int argc, char **argv) {"adjfile", 1, 0, OPT_ADJFILE}, {"systz", 0, 0, OPT_SYSTZ}, {"predict-hc", 0, 0, OPT_PREDICT_HC}, + {"get", 0, 0, OPT_GET}, {NULL, 0, NULL, 0} }; static const ul_excl_t excl[] = { /* rows and cols in in ASCII order */ { 'a','r','s','w', - OPT_GETEPOCH, OPT_PREDICT_HC, OPT_SET, - OPT_SETEPOCH, OPT_SYSTZ }, + OPT_GET, OPT_GETEPOCH, OPT_PREDICT_HC, + OPT_SET, OPT_SETEPOCH, OPT_SYSTZ }, { 'u', OPT_LOCALTIME}, { OPT_ADJFILE, OPT_NOADJFILE }, { 0 } @@ -1747,7 +1721,7 @@ int main(int argc, char **argv) /* Set option defaults */ show = set = systohc = hctosys = systz = adjust = noadjfile = predict = - compare = FALSE; + compare = get = FALSE; getepoch = setepoch = utc = local_opt = directisa = testing = debug = FALSE; #ifdef __alpha__ ARCconsole = Jensen = SRM = funky_toy = badyear = FALSE; @@ -1837,6 +1811,9 @@ int main(int argc, char **argv) case OPT_PREDICT_HC: predict = TRUE; /* --predict-hc */ break; + case OPT_GET: + get = TRUE; /* --get */ + break; #ifdef __linux__ case 'f': rtc_dev_name = optarg; /* --rtc */ @@ -1894,7 +1871,7 @@ int main(int argc, char **argv) } if (!(show | set | systohc | hctosys | systz | adjust | getepoch - | setepoch | predict | compare)) + | setepoch | predict | compare | get)) show = 1; /* default to show */ if (getuid() == 0) @@ -1951,7 +1928,7 @@ int main(int argc, char **argv) } else rc = manipulate_clock(show, adjust, noadjfile, set, set_time, hctosys, systohc, systz, startup_time, utc, - local_opt, testing, predict); + local_opt, testing, predict, get); hwclock_exit(rc); return rc; /* Not reached */ -- cgit v1.2.3-55-g7522 From ede32597f5de3c51321ae86b96e6789c6eca5d8f Mon Sep 17 00:00:00 2001 From: J William Piggott Date: Wed, 15 Oct 2014 14:32:31 -0400 Subject: hwclock: hctosys drift compensation II COMMENTS Update source comments and --help output for the hwclock: hctosys drift compensation II patch. Signed-off-by: J William Piggott --- sys-utils/hwclock.c | 59 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 26 deletions(-) (limited to 'sys-utils') diff --git a/sys-utils/hwclock.c b/sys-utils/hwclock.c index 30db8b628..4befbab32 100644 --- a/sys-utils/hwclock.c +++ b/sys-utils/hwclock.c @@ -684,11 +684,9 @@ set_hardware_clock_exact(const time_t sethwtime, } /* - * Put the time "systime" on standard output in display format. Except if + * Put the time "hwctime" on standard output in display format. Except if * hclock_valid == false, just tell standard output that we don't know what * time it is. - * - * Include in the output the adjustment "sync_duration". */ static void display_time(const bool hclock_valid, struct timeval hwctime) @@ -1022,7 +1020,14 @@ adjust_drift_factor(struct adjtime *adjtime_p, struct timeval last_calib; last_calib = t2tv(adjtime_p->last_calib_time); - + /* + * Correction to apply to the current drift factor. + * + * Simplified: uncorrected_drift / days_since_calibration. + * + * hclocktime is fully corrected with the current drift factor. + * Its difference from nowtime is the missed drift correction. + */ factor_adjust = time_diff(nowtime, hclocktime) / (time_diff(nowtime, last_calib) / sec_per_day); @@ -1057,16 +1062,12 @@ adjust_drift_factor(struct adjtime *adjtime_p, } /* - * Do the drift adjustment calculation. - * - * The way we have to set the clock, we need the adjustment in two parts: + * Calculate the drift correction currently needed for the + * Hardware Clock based on the last time it was adjusted, + * and the current drift factor, as stored in the adjtime file. * - * 1) an integer number of seconds (return as *adjustment_p) - * 2) a positive fraction of a second (less than 1) (return as *retro_p) + * The total drift adjustment needed is stored at tdrift_p. * - * The sum of these two values is the adjustment needed. Positive means to - * advance the clock or insert seconds. Negative means to retard the clock - * or remove seconds. */ static void calculate_adjustment(const double factor, @@ -1161,24 +1162,21 @@ static void save_adjtime(const struct adjtime adjtime, const bool testing) * Do not update anything if the Hardware Clock does not currently present a * valid time. * - * Arguments and are current values from the adjtime - * file. + * means the Hardware Clock contains a valid time. * - * means the Hardware Clock contains a valid time, and that - * time is . + * is the drift corrected time read from the Hardware Clock. * - * is the current system time (to be precise, it is the system - * time at the time was read, which due to computational delay - * could be a short time ago). + * was the system time when the was read, which due + * to computational delay could be a short time ago. It is used to define a + * trigger point for setting the Hardware Clock. The fractional part of the + * Hardware clock set time is subtracted from read_time to 'refer back', or + * delay, the trigger point. Fractional parts must be accounted for in this + * way, because the Hardware Clock can only be set to a whole second. * * : the Hardware Clock is kept in UTC. * * : We are running in test mode (no updating of clock). * - * We do not bother to update the clock if the adjustment would be less than - * one second. This is to avoid cumulative error and needless CPU hogging - * (remember we use an infinite loop for some timing) if the user runs us - * frequently. */ static void do_adjustment(struct adjtime *adjtime_p, @@ -1263,11 +1261,11 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, */ bool hclock_valid = FALSE; /* - * The time the hardware clock had just after we - * synchronized to its next clock tick when we - * started up. Defined only if hclock_valid is true. + * Tick synchronized time read from the Hardware Clock and + * then drift correct for all operations except --show. */ struct timeval hclocktime = { 0, 0 }; + /* Total Hardware Clock drift correction needed. */ struct timeval tdrift; /* local return code */ int rc = 0; @@ -1323,6 +1321,14 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, return EX_IOERR; } } + /* + * Calculate Hardware Clock drift for --predict with the user + * supplied --date option time, and with the time read from the + * Hardware Clock for all other operations. Apply drift correction + * to the Hardware Clock time for everything except --show and + * --predict. For --predict negate the drift correction, because we + * want to 'predict' a future Hardware Clock time that includes drift. + */ hclocktime = predict ? t2tv(set_time) : hclocktime; calculate_adjustment(adjtime.drift_factor, adjtime.last_adj_time, @@ -1542,6 +1548,7 @@ static void usage(const char *fmt, ...) fputs(_("\nFunctions:\n"), usageto); fputs(_(" -h, --help show this help text and exit\n" " -r, --show read hardware clock and print result\n" + " --get read hardware clock and print drift corrected result\n" " --set set the RTC to the time given with --date\n"), usageto); fputs(_(" -s, --hctosys set the system time from the hardware clock\n" " -w, --systohc set the hardware clock from the current system time\n" -- cgit v1.2.3-55-g7522 From cb7efbc12de4bd9468344875af7b17cc19adcc50 Mon Sep 17 00:00:00 2001 From: J William Piggott Date: Wed, 15 Oct 2014 14:37:08 -0400 Subject: hwclock: hctosys drift compensation II MAN Update hwclock man page for the hwclock: hctosys drift compensation II patch. Signed-off-by: J William Piggott --- sys-utils/hwclock.8.in | 50 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 17 deletions(-) (limited to 'sys-utils') diff --git a/sys-utils/hwclock.8.in b/sys-utils/hwclock.8.in index b11b45c24..913da3745 100644 --- a/sys-utils/hwclock.8.in +++ b/sys-utils/hwclock.8.in @@ -61,8 +61,18 @@ in Coordinated Universal Time. See the option. Showing the Hardware Clock time is the default when no function is specified. .TP +.B \-\-get +Like +.B --show +only with drift correction applied to the time read. This is useful when the +Hardware Clock is not being periodically updated by something such as NTP's +11 minute mode or when not using +.BR --adjust . +.TP .BR \-s , \ \-\-hctosys -Set the System Time from the Hardware Clock. +Set the System Time from the Hardware Clock. The time read from the Hardware +Clock is compensated to account for systematic drift before using it to set the +System Clock. See the discussion below, under \fBThe Adjust Function\fR. .PP Also set the kernel's timezone value to the local timezone as indicated by the TZ environment variable and/or @@ -484,8 +494,9 @@ The Hardware Clock is usually not very accurate. However, much of its inaccuracy is completely predictable - it gains or loses the same amount of time every day. This is called systematic drift. .BR hwclock 's -"adjust" function lets you make systematic corrections to correct the -systematic drift. +.I \-\-adjust +function lets you apply systematic drift corrections to the +Hardware Clock. .PP It works like this: .B hwclock @@ -529,20 +540,25 @@ since the last calibration, how long it has been since the last adjustment, what drift rate was assumed in any intervening adjustments, and the amount by which the clock is presently off. .PP -A small amount of error creeps in any time -.B hwclock -sets the clock, so it refrains from making an adjustment that would be -less than 1 second. Later on, when you request an adjustment again, -the accumulated drift will be more than a second and -.B hwclock -will do the adjustment then. -.PP -It is good to do a -.I hwclock \-\-adjust -just before the -.I hwclock \-\-hctosys -at system startup time, and maybe periodically while the system is -running via cron. +A small amount of error creeps in when +the Hardware Clock is set, so +.I \-\-adjust +refrains from making any adjustment that is less +than 1 second. Later on, when you request an adjustment again, the accumulated +drift will be more than 1 second and +.I \-\-adjust +will make the adjustment including any fractional amount. +.PP +.IR "hwclock \-\-hctosys" +also uses the adjtime file data to compensate the value read from the Hardware +Clock before using it to set the System Time. It does not share the 1 second +limitation of --adjust, and will correct sub-second drift values immediately. +It does not change the Hardware Clock time or the adjtime file. This may +eliminate the need to use --adjust, unless something else on the system needs +the Hardware Clock to be compensated. The drift compensation can be inhibited +by using the +.B --noadjfile +option. .PP The adjtime file, while named for its historical purpose of controlling adjustments only, actually contains other information for use by hwclock -- cgit v1.2.3-55-g7522 From d17a12a3685945e7d5579db491543d223618d060 Mon Sep 17 00:00:00 2001 From: J William Piggott Date: Thu, 25 Sep 2014 07:38:25 -0400 Subject: hwclock: persistent_clock_is_local When hctosys is used at boot time, making it the first caller of settimeofday, the responsibility of setting persistent_clock_is_local is thrust upon it. Currently hctosys always leaves this variable uninitialized. This causes a Hardware Clock configured to use the local timescale to be clobbered with the UTC timescale by the kernel's NTP eleven minute mode. This patch fixes this hctosys bug, by having it properly set persistent_clock_is_local according to the time scale configured for the Hardware Clock. It does this via the kernel warp_clock function but this in inconsequential, because we set the system time immediately afterward. Signed-off-by: J William Piggott --- sys-utils/hwclock.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) (limited to 'sys-utils') diff --git a/sys-utils/hwclock.c b/sys-utils/hwclock.c index 4befbab32..dc8f9574e 100644 --- a/sys-utils/hwclock.c +++ b/sys-utils/hwclock.c @@ -809,6 +809,12 @@ static int interpret_date_string(const char *date_opt, time_t * const time_p) * environment variable and/or /usr/lib/zoneinfo/, interpreted as tzset() * would interpret them. * + * If this is the first call of settimeofday since boot, then this also sets + * the kernel variable persistent_clock_is_local so that NTP 11 minute mode + * will update the Hardware Clock with the proper timescale. If the Hardware + * Clock's timescale configuration is changed then a reboot is required for + * persistent_clock_is_local to be updated. + * * EXCEPT: if hclock_valid is false, just issue an error message saying * there is no valid time in the Hardware Clock to which to set the system * time. @@ -818,7 +824,7 @@ static int interpret_date_string(const char *date_opt, time_t * const time_p) */ static int set_system_clock(const bool hclock_valid, const struct timeval newtime, - const bool testing) + const bool testing, const bool universal) { int retcode; @@ -828,9 +834,10 @@ set_system_clock(const bool hclock_valid, const struct timeval newtime, "we cannot set the System Time from it.")); retcode = 1; } else { + const struct timeval *tv_null = NULL; struct tm *broken; int minuteswest; - int rc; + int rc = 0; broken = localtime(&newtime.tv_sec); #ifdef HAVE_TM_GMTOFF @@ -854,7 +861,14 @@ set_system_clock(const bool hclock_valid, const struct timeval newtime, } else { const struct timezone tz = { minuteswest, 0 }; - rc = settimeofday(&newtime, &tz); + /* Set kernel persistent_clock_is_local so that 11 minute + * mode does not clobber the Hardware Clock with UTC. This + * is only available on first call of settimeofday after boot. + */ + if (!universal) + rc = settimeofday(tv_null, &tz); + if (!rc) + rc = settimeofday(&newtime, &tz); if (rc) { if (errno == EPERM) { warnx(_ @@ -1376,7 +1390,8 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, adjust_drift_factor(&adjtime, nowtime, hclock_valid, hclocktime); } else if (hctosys) { - rc = set_system_clock(hclock_valid, hclocktime, testing); + rc = set_system_clock(hclock_valid, hclocktime, + testing, universal); if (rc) { printf(_("Unable to set system clock.\n")); return rc; -- cgit v1.2.3-55-g7522 From 8db424dcbcd9dde3c1ed6b3027e8e98b9fda3d21 Mon Sep 17 00:00:00 2001 From: J William Piggott Date: Thu, 25 Sep 2014 10:31:54 -0400 Subject: hwclock: persistent_clock_is_local MAN Update hwclock man page for the hwclock: persistent_clock_is_local patch. Signed-off-by: J William Piggott --- sys-utils/hwclock.8.in | 63 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 15 deletions(-) (limited to 'sys-utils') diff --git a/sys-utils/hwclock.8.in b/sys-utils/hwclock.8.in index 913da3745..d04a429df 100644 --- a/sys-utils/hwclock.8.in +++ b/sys-utils/hwclock.8.in @@ -84,7 +84,24 @@ The obsolete tz_dsttime field of the kernel's timezone value is set to DST_NONE. (For details on what this field used to mean, see .BR settimeofday (2).) .PP +When used in a startup script, making it the first caller of +.BR settimeofday (2) +from boot, it will set the NTP 11 minute mode time scale via the +.I persistent_clock_is_local +kernel variable. See the discussion below, under +.B Automatic Hardware Clock Synchronization by the +.BR Kernel. +.PP This is a good option to use in one of the system startup scripts. +.PP +This option should never be used on a running system. Jumping system time +will cause problems, such as, corrupted file system timestamps. +Also, if NTP 11 minute mode is active then +.B --hctosys +will set the time incorrectly by +including drift compensation. Drift compensation can be inhibited by using the +.B --noadjfile +option. .TP .B \-\-set Set the Hardware Clock to the time given by the @@ -393,11 +410,16 @@ use the TZ environment variable and/or the .I /usr/share/zoneinfo directory, as explained in the man page for .BR tzset (3). -However, some -programs and fringe parts of the Linux kernel such as filesystems use -the kernel timezone value. An example is the vfat filesystem. If the -kernel timezone value is wrong, the vfat filesystem will report and -set the wrong timestamps on files. +However, some programs and fringe parts of the Linux kernel such as filesystems +use the kernel timezone value. An example is the vfat filesystem. If the +kernel timezone value is wrong, the vfat filesystem will report and set the +wrong timestamps on files. Another example is the kernel's NTP 11 minute mode. +If the kernel's timezone value and/or the +.I persistent_clock_is_local +variable are wrong, then the Hardware Clock will be set incorrectly by 11 minute +mode. See the discussion below, under +.B Automatic Hardware Clock Synchronization by the +.BR Kernel. .PP .B hwclock sets the kernel timezone to the value indicated by TZ and/or @@ -601,20 +623,31 @@ your System Time synchronized either to a time server somewhere on the network or to a radio clock hooked up to your system. See RFC 1305.) .PP This mode (we'll call it "11 minute mode") is off until something -turns it on. The ntp daemon xntpd is one thing that turns it on. You +turns it on. The ntp daemon ntpd is one thing that turns it on. You can turn it off by running anything, including .IR "hwclock \-\-hctosys" , -that sets the System Time the old fashioned way. +that sets the System Time the old fashioned way. However, if the ntp daemon is +still running, it will turn 11 minute mode back on again the next time it +synchronizes the System Clock. .PP -If your system runs with 11 minute mode on, don't use -.I hwclock \-\-adjust -or -.IR "hwclock \-\-hctosys" . -You'll just make a mess. It is acceptable to use a +If your system runs with 11 minute mode on, it may need +.I hwclock \-\-hctosys +in a startup script, especially if the Hardware Clock is configured to to use +the local timescale. + +The first user space command to set the System Clock informs the +kernel what timescale the Hardware Clock is using. This happens via the +.I persistent_clock_is_local +kernel variable. If .I hwclock \-\-hctosys -at startup time to get a reasonable System Time until your system is -able to set the System Time from the external source and start 11 -minute mode. +is the first, it will set this variable according to the adjtime file or the +appropriate command line argument. Note that when using this capability and the +Hardware Clock timescale configuration is changed, then a reboot is required to +notify the kernel. + +Don't use +.I hwclock \-\-adjust +with 11 minute mode. You'll just make a mess. .SS ISA Hardware Clock Century value .PP -- cgit v1.2.3-55-g7522 From f276d71a3ef6a65299deb5e14a188e0a786ecebd Mon Sep 17 00:00:00 2001 From: J William Piggott Date: Wed, 15 Oct 2014 15:48:17 -0400 Subject: hwclock: Add --update-drift option There are cases where we need to refresh the timestamps in the adjtime file without updating the drift factor. For example, with ntpd and an Eleven Minute Mode kernel, we need to call systohc at shutdown to facilitate drift correction. With the current behavior hwclock will clobber the drift factor to near zero, because the Hardware Clock and System Clock are synced by Eleven Minute Mode. What actually needs to be done is refresh the adjtime file timestamps and not calculate a new drift factor. Because it is a manual process to craft a good Hardware Clock drift factor, that is, there is no automated method that will produce a good drift factor, this patch changes the default drift calculation behavior to off, and it is turned on by using the --update-drift option. Once we have a good drift factor for a given machine we do not want anything clobbering it, including an administrator forgetting to turn off recalculation. A system administrator should make a concious effort in telling hwclock with the --update-drift option that (s)he wants to recalculate the drift factor. Without using the --update-drift option with calibrate operations only the timestamps are refreshed in the adjtime file. With the --update-drift option the old default behavior of refreshing the timestamps and updating the drift factor is performed. Signed-off-by: J William Piggott --- sys-utils/hwclock.c | 56 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 21 deletions(-) (limited to 'sys-utils') diff --git a/sys-utils/hwclock.c b/sys-utils/hwclock.c index dc8f9574e..9a4a70629 100644 --- a/sys-utils/hwclock.c +++ b/sys-utils/hwclock.c @@ -981,12 +981,13 @@ static int set_system_clock_timezone(const bool universal, const bool testing) } /* - * Update the drift factor in <*adjtime_p> to reflect the fact that the - * Hardware Clock was calibrated to and before that was set to - * . + * Refresh the last calibrated and last adjusted timestamps in <*adjtime_p> + * to facilitate future drift calculations based on this set point. * - * We record in the adjtime file the time at which we last calibrated the - * clock so we can compute the drift rate each time we calibrate. + * With the --update-drift option: + * Update the drift factor in <*adjtime_p> based on the fact that the + * Hardware Clock was just calibrated to and before that was + * set to the time scale. * * EXCEPT: if is false, assume Hardware Clock was not set * before to anything meaningful and regular adjustments have not been done, @@ -996,9 +997,14 @@ static void adjust_drift_factor(struct adjtime *adjtime_p, const struct timeval nowtime, const bool hclock_valid, - const struct timeval hclocktime) + const struct timeval hclocktime, + const bool update) { - if (!hclock_valid) { + if (!update) { + if (debug) + printf(_("Not adjusting drift factor because the " + "--update-drift option was not used.\n")); + } else if (!hclock_valid) { if (debug) printf(_("Not adjusting drift factor because the " "Hardware Clock previously contained " @@ -1016,14 +1022,15 @@ adjust_drift_factor(struct adjtime *adjtime_p, "calibration.\n")); } else if (adjtime_p->last_calib_time != 0) { /* - * At adjustment time we adjust the hardware clock according - * to the contents of /etc/adjtime. + * At adjustment time we drift correct the hardware clock + * according to the contents of the adjtime file and refresh + * its last adjusted timestamp. * - * At calibration time we set the hardware clock and update - * /etc/adjtime, that is, for each calibration (except the - * first) we also do an adjustment. + * At calibration time we set the Hardware Clock and refresh + * both timestamps in <*adjtime_p>. * - * We are now at calibration time. + * Here, with the --update-drift option, we also update the + * drift factor in <*adjtime_p>. * * Let us do computation in doubles. (Floats almost suffice, * but 195 days + 1 second equals 195 days in floats.) @@ -1259,7 +1266,7 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, const bool set, const time_t set_time, const bool hctosys, const bool systohc, const bool systz, const struct timeval startup_time, - const bool utc, const bool local_opt, + const bool utc, const bool local_opt, const bool update, const bool testing, const bool predict, const bool get) { /* Contents of the adjtime file, or what they should be. */ @@ -1364,7 +1371,7 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, adjust_drift_factor(&adjtime, time_inc(t2tv(set_time), time_diff (read_time, startup_time)), - hclock_valid, hclocktime); + hclock_valid, hclocktime, update); } else if (adjust) { if (tdrift.tv_sec > 0 || tdrift.tv_sec < -1) do_adjustment(&adjtime, hclock_valid, @@ -1388,7 +1395,7 @@ manipulate_clock(const bool show, const bool adjust, const bool noadjfile, reftime, universal, testing); if (!noadjfile) adjust_drift_factor(&adjtime, nowtime, - hclock_valid, hclocktime); + hclock_valid, hclocktime, update); } else if (hctosys) { rc = set_system_clock(hclock_valid, hclocktime, testing, universal); @@ -1592,10 +1599,11 @@ static void usage(const char *fmt, ...) " --epoch specifies the year which is the beginning of the\n" " hardware clock's epoch value\n"), _PATH_RTC_DEV); fprintf(usageto, _( + " --update-drift update drift factor in %s\n" " --noadjfile do not access %s; this requires the use of\n" " either --utc or --localtime\n" " --adjfile specifies the path to the adjust file;\n" - " the default is %s\n"), _PATH_ADJTIME, _PATH_ADJTIME); + " the default is %s\n"), _PATH_ADJTIME, _PATH_ADJTIME, _PATH_ADJTIME); fputs(_(" --test do not update anything, just show what would happen\n" " -D, --debug debugging mode\n" "\n"), usageto); #ifdef __alpha__ @@ -1639,7 +1647,7 @@ int main(int argc, char **argv) /* The options debug, badyear and epoch_option are global */ bool show, set, systohc, hctosys, systz, adjust, getepoch, setepoch, predict, compare, get; - bool utc, testing, local_opt, noadjfile, directisa; + bool utc, testing, local_opt, update, noadjfile, directisa; char *date_opt; #ifdef __alpha__ bool ARCconsole, Jensen, SRM, funky_toy; @@ -1659,7 +1667,8 @@ int main(int argc, char **argv) OPT_SET, OPT_SETEPOCH, OPT_SYSTZ, - OPT_TEST + OPT_TEST, + OPT_UPDATE }; static const struct option longopts[] = { @@ -1700,6 +1709,7 @@ int main(int argc, char **argv) {"systz", 0, 0, OPT_SYSTZ}, {"predict-hc", 0, 0, OPT_PREDICT_HC}, {"get", 0, 0, OPT_GET}, + {"update-drift",0, 0, OPT_UPDATE}, {NULL, 0, NULL, 0} }; @@ -1709,6 +1719,7 @@ int main(int argc, char **argv) OPT_SET, OPT_SETEPOCH, OPT_SYSTZ }, { 'u', OPT_LOCALTIME}, { OPT_ADJFILE, OPT_NOADJFILE }, + { OPT_NOADJFILE, OPT_UPDATE }, { 0 } }; int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; @@ -1743,7 +1754,7 @@ int main(int argc, char **argv) /* Set option defaults */ show = set = systohc = hctosys = systz = adjust = noadjfile = predict = - compare = get = FALSE; + compare = get = update = FALSE; getepoch = setepoch = utc = local_opt = directisa = testing = debug = FALSE; #ifdef __alpha__ ARCconsole = Jensen = SRM = funky_toy = badyear = FALSE; @@ -1836,6 +1847,9 @@ int main(int argc, char **argv) case OPT_GET: get = TRUE; /* --get */ break; + case OPT_UPDATE: + update = TRUE; /* --update-drift */ + break; #ifdef __linux__ case 'f': rtc_dev_name = optarg; /* --rtc */ @@ -1950,7 +1964,7 @@ int main(int argc, char **argv) } else rc = manipulate_clock(show, adjust, noadjfile, set, set_time, hctosys, systohc, systz, startup_time, utc, - local_opt, testing, predict, get); + local_opt, update, testing, predict, get); hwclock_exit(rc); return rc; /* Not reached */ -- cgit v1.2.3-55-g7522 From cd950279f19f2c685047705d4cc1820cc1566769 Mon Sep 17 00:00:00 2001 From: J William Piggott Date: Wed, 15 Oct 2014 16:14:37 -0400 Subject: hwclock: Add --update-drift option MAN Update hwclock man page for the hwclock: Add --update-drift option patch. Signed-off-by: J William Piggott --- sys-utils/hwclock.8.in | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) (limited to 'sys-utils') diff --git a/sys-utils/hwclock.8.in b/sys-utils/hwclock.8.in index d04a429df..d67595521 100644 --- a/sys-utils/hwclock.8.in +++ b/sys-utils/hwclock.8.in @@ -342,7 +342,18 @@ with SRM console. Do everything except actually updating the Hardware Clock or anything else. This is useful, especially in conjunction with .BR \-\-debug , -in learning about +in learning about the internal operations of hwclock. + +.TP +.B \-\-update\-drift +Update the Hardware Clock's drift factor in +.IR @ADJTIME_PATH@ . +It is used with +.BR --set\ or \ --systohc , +otherwise it is ignored. +See the discussion below, under +.B The Adjust +.BR Function. .TP .BR \-u , \ \-\-utc @@ -532,8 +543,8 @@ command to set the Hardware Clock to the true current time. .B hwclock creates the adjtime file and records in it the current time as the last time the clock was calibrated. -5 days later, the clock has gained 10 seconds, so you issue another -.I hwclock \-\-set +5 days later, the clock has gained 10 seconds, so you issue the +.I hwclock \-\-set \-\-update\-drift command to set it back 10 seconds. .B hwclock updates the adjtime file to show the current time as the last time the @@ -552,15 +563,16 @@ Another 24 hours goes by and you issue another does the same thing: subtracts 2 seconds and updates the adjtime file with the current time as the last time the clock was adjusted. .PP -Every time you calibrate (set) the clock (using -.I \-\-set -or -.IR \-\-systohc ), -.B hwclock -recalculates the systematic drift rate based on how long it has been +When you use the +.I \-\-update\-drift +option with +.IR \-\-set\ or \ \-\-systohc , +the systematic drift rate is (re)calculated based on how long it has been since the last calibration, how long it has been since the last adjustment, what drift rate was assumed in any intervening -adjustments, and the amount by which the clock is presently off. +adjustments, and the amount by which the clock is presently off. This updated +drift factor is then saved in +.IR @ADJTIME_PATH@ . .PP A small amount of error creeps in when the Hardware Clock is set, so -- cgit v1.2.3-55-g7522