summaryrefslogtreecommitdiffstats
path: root/rtc_linux.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rtc_linux.c1129
1 files changed, 1129 insertions, 0 deletions
diff --git a/rtc_linux.c b/rtc_linux.c
new file mode 100644
index 0000000..d4d9bd0
--- /dev/null
+++ b/rtc_linux.c
@@ -0,0 +1,1129 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Richard P. Curnow 1997-2003
+ * Copyright (C) Miroslav Lichvar 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ Real-time clock driver for linux. This interfaces the program with
+ the clock that keeps time when the machine is turned off.
+
+ */
+
+#include "config.h"
+
+#include "sysincl.h"
+
+#include <linux/rtc.h>
+
+#include "logging.h"
+#include "sched.h"
+#include "local.h"
+#include "util.h"
+#include "sys_linux.h"
+#include "reference.h"
+#include "regress.h"
+#include "rtc.h"
+#include "rtc_linux.h"
+#include "conf.h"
+#include "memory.h"
+
+/* ================================================== */
+/* Forward prototypes */
+
+static void measurement_timeout(void *any);
+
+static void read_from_device(int fd_, int event, void *any);
+
+/* ================================================== */
+
+typedef enum {
+ OM_NORMAL,
+ OM_INITIAL,
+ OM_AFTERTRIM
+} OperatingMode;
+
+static OperatingMode operating_mode = OM_NORMAL;
+
+/* ================================================== */
+
+static int fd = -1;
+
+#define LOWEST_MEASUREMENT_PERIOD 15
+#define HIGHEST_MEASUREMENT_PERIOD 480
+#define N_SAMPLES_PER_REGRESSION 1
+
+static int measurement_period = LOWEST_MEASUREMENT_PERIOD;
+
+static SCH_TimeoutID timeout_id = 0;
+
+static int skip_interrupts;
+
+/* ================================================== */
+
+/* Maximum number of samples held */
+#define MAX_SAMPLES 64
+
+/* Real time clock samples. We store the seconds count as originally
+ measured, together with a 'trim' that compensates these values for
+ any steps made to the RTC to bring it back into line
+ occasionally. The trim is in seconds. */
+static time_t *rtc_sec = NULL;
+static double *rtc_trim = NULL;
+
+/* Reference time, against which delta times on the RTC scale are measured */
+static time_t rtc_ref;
+
+
+/* System clock samples associated with the above samples. */
+static struct timespec *system_times = NULL;
+
+/* Number of samples currently stored. */
+static int n_samples;
+
+/* Number of new samples since last regression */
+static int n_samples_since_regression;
+
+/* Number of runs of residuals in last regression (for logging) */
+static int n_runs;
+
+/* Coefficients */
+/* Whether they are valid */
+static int coefs_valid;
+
+/* Reference time */
+static time_t coef_ref_time;
+/* Number of seconds by which RTC was fast of the system time at coef_ref_time */
+static double coef_seconds_fast;
+
+/* Estimated number of seconds that RTC gains relative to system time
+ for each second of ITS OWN time */
+static double coef_gain_rate;
+
+/* Gain rate saved just before we step the RTC to correct it to the
+ nearest second, so that we can write a useful set of coefs to the
+ RTC data file once we have reacquired its offset after the step */
+static double saved_coef_gain_rate;
+
+/* Threshold for automatic RTC trimming in seconds, zero when disabled */
+static double autotrim_threshold;
+
+/* Filename supplied by config file where RTC coefficients are
+ stored. */
+static char *coefs_file_name;
+
+/* ================================================== */
+/* Coefficients read from file at start of run. */
+
+/* Whether we have tried to load the coefficients */
+static int tried_to_load_coefs = 0;
+
+/* Whether valid coefficients were read */
+static int valid_coefs_from_file = 0;
+
+/* Coefs read in */
+static time_t file_ref_time;
+static double file_ref_offset, file_rate_ppm;
+
+/* ================================================== */
+
+/* Flag to remember whether to assume the RTC is running on UTC */
+static int rtc_on_utc = 1;
+
+/* ================================================== */
+
+static LOG_FileID logfileid;
+
+/* ================================================== */
+
+static void (*after_init_hook)(void *) = NULL;
+static void *after_init_hook_arg = NULL;
+
+/* ================================================== */
+
+static void
+discard_samples(int new_first)
+{
+ int n_to_save;
+
+ assert(new_first >= 0 && new_first < n_samples);
+
+ n_to_save = n_samples - new_first;
+
+ memmove(rtc_sec, rtc_sec + new_first, n_to_save * sizeof(time_t));
+ memmove(rtc_trim, rtc_trim + new_first, n_to_save * sizeof(double));
+ memmove(system_times, system_times + new_first, n_to_save * sizeof(struct timespec));
+
+ n_samples = n_to_save;
+}
+
+/* ================================================== */
+
+#define NEW_FIRST_WHEN_FULL 4
+
+static void
+accumulate_sample(time_t rtc, struct timespec *sys)
+{
+
+ if (n_samples == MAX_SAMPLES) {
+ /* Discard oldest samples */
+ discard_samples(NEW_FIRST_WHEN_FULL);
+ }
+
+ /* Discard all samples if the RTC was stepped back (not our trim) */
+ if (n_samples > 0 && rtc_sec[n_samples - 1] - rtc >= rtc_trim[n_samples - 1]) {
+ DEBUG_LOG("RTC samples discarded");
+ n_samples = 0;
+ }
+
+ /* Always use most recent sample as reference */
+ /* use sample only if n_sample is not negative*/
+ if(n_samples >=0)
+ {
+ rtc_ref = rtc;
+ rtc_sec[n_samples] = rtc;
+ rtc_trim[n_samples] = 0.0;
+ system_times[n_samples] = *sys;
+ ++n_samples_since_regression;
+ }
+ ++n_samples;
+}
+
+/* ================================================== */
+/* The new_sample flag is to indicate whether to adjust the
+ measurement period depending on the behaviour of the standard
+ deviation. */
+
+static void
+run_regression(int new_sample,
+ int *valid,
+ time_t *ref,
+ double *fast,
+ double *slope)
+{
+ double rtc_rel[MAX_SAMPLES]; /* Relative times on RTC axis */
+ double offsets[MAX_SAMPLES]; /* How much the RTC is fast of the system clock */
+ int i;
+ double est_intercept, est_slope;
+ int best_new_start;
+
+ if (n_samples > 0) {
+
+ for (i=0; i<n_samples; i++) {
+ rtc_rel[i] = rtc_trim[i] + (double)(rtc_sec[i] - rtc_ref);
+ offsets[i] = ((double) (rtc_ref - system_times[i].tv_sec) -
+ (1.0e-9 * system_times[i].tv_nsec) +
+ rtc_rel[i]);
+
+ }
+
+ if (RGR_FindBestRobustRegression
+ (rtc_rel, offsets,
+ n_samples, 1.0e-9,
+ &est_intercept, &est_slope,
+ &n_runs,
+ &best_new_start)) {
+
+ /* Calculate and store coefficients. We don't do any error
+ bounds processing on any of these. */
+ *valid = 1;
+ *ref = rtc_ref;
+ *fast = est_intercept;
+ *slope = est_slope;
+
+ if (best_new_start > 0) {
+ discard_samples(best_new_start);
+ }
+
+
+ } else {
+ /* Keep existing coefficients. */
+ }
+ } else {
+ /* Keep existing coefficients. */
+ }
+
+}
+
+/* ================================================== */
+
+static void
+slew_samples
+(struct timespec *raw, struct timespec *cooked,
+ double dfreq,
+ double doffset,
+ LCL_ChangeType change_type,
+ void *anything)
+{
+ int i;
+ double delta_time;
+ double old_seconds_fast, old_gain_rate;
+
+ if (change_type == LCL_ChangeUnknownStep) {
+ /* Drop all samples. */
+ n_samples = 0;
+ }
+
+ for (i=0; i<n_samples; i++) {
+ UTI_AdjustTimespec(system_times + i, cooked, system_times + i, &delta_time,
+ dfreq, doffset);
+ }
+
+ old_seconds_fast = coef_seconds_fast;
+ old_gain_rate = coef_gain_rate;
+
+ if (coefs_valid) {
+ coef_seconds_fast += doffset;
+ coef_gain_rate += dfreq * (1.0 - coef_gain_rate);
+ }
+
+ DEBUG_LOG("dfreq=%.8f doffset=%.6f old_fast=%.6f old_rate=%.3f new_fast=%.6f new_rate=%.3f",
+ dfreq, doffset,
+ old_seconds_fast, 1.0e6 * old_gain_rate,
+ coef_seconds_fast, 1.0e6 * coef_gain_rate);
+}
+
+/* ================================================== */
+
+/* Function to convert from a time_t value represenging UTC to the
+ corresponding real time clock 'DMY HMS' form, taking account of
+ whether the user runs his RTC on the local time zone or UTC */
+
+static struct tm *
+rtc_from_t(const time_t *t)
+{
+ if (rtc_on_utc) {
+ return gmtime(t);
+ } else {
+ return localtime(t);
+ }
+}
+
+/* ================================================== */
+
+/* Inverse function to get back from RTC 'DMY HMS' form to time_t UTC
+ form. This essentially uses mktime(), but involves some awful
+ complexity to cope with timezones. The problem is that mktime's
+ behaviour with regard to the daylight saving flag in the 'struct
+ tm' does not seem to be reliable across all systems, unless that
+ flag is set to zero.
+
+ tm_isdst = -1 does not seem to work with all libc's - it is treated
+ as meaning there is DST, or fails completely. (It is supposed to
+ use the timezone info to work out whether summer time is active at
+ the specified epoch).
+
+ tm_isdst = 1 fails if the local timezone has no summer time defined.
+
+ The approach taken is as follows. Suppose the RTC is on localtime.
+ We perform all mktime calls with the tm_isdst field set to zero.
+
+ Let y be the RTC reading in 'DMY HMS' form. Let M be the mktime
+ function with tm_isdst=0 and L be the localtime function.
+
+ We seek x such that y = L(x). Now there will exist a value Z(t)
+ such that M(L(t)) = t + Z(t) for all t, where Z(t) depends on
+ whether daylight saving is active at time t.
+
+ We want L(x) = y. Therefore M(L(x)) = x + Z = M(y). But
+ M(L(M(y))) = M(y) + Z. Therefore x = M(y) - Z = M(y) - (M(L(M(y)))
+ - M(y)).
+
+ The case for the RTC running on UTC is identical but without the
+ potential complication that Z depends on t.
+*/
+
+static time_t
+t_from_rtc(struct tm *stm) {
+ struct tm temp1, temp2, *tm;
+ long diff;
+ time_t t1, t2;
+
+ temp1 = *stm;
+ temp1.tm_isdst = 0;
+
+ t1 = mktime(&temp1);
+
+ tm = rtc_on_utc ? gmtime(&t1) : localtime(&t1);
+ if (!tm) {
+ DEBUG_LOG("gmtime()/localtime() failed");
+ return -1;
+ }
+
+ temp2 = *tm;
+ temp2.tm_isdst = 0;
+ t2 = mktime(&temp2);
+ diff = t2 - t1;
+
+ if (t1 - diff == -1)
+ DEBUG_LOG("Could not convert RTC time");
+
+ return t1 - diff;
+}
+
+/* ================================================== */
+
+static void
+read_hwclock_file(const char *hwclock_file)
+{
+ FILE *in;
+ char line[256];
+ int i;
+
+ if (!hwclock_file || !hwclock_file[0])
+ return;
+
+ in = fopen(hwclock_file, "r");
+ if (!in) {
+ LOG(LOGS_WARN, "Could not open %s : %s",
+ hwclock_file, strerror(errno));
+ return;
+ }
+
+ /* Read third line from the file. */
+ for (i = 0; i < 3; i++) {
+ if (!fgets(line, sizeof(line), in))
+ break;
+ }
+
+ fclose(in);
+
+ if (i == 3 && !strncmp(line, "LOCAL", 5)) {
+ rtc_on_utc = 0;
+ } else if (i == 3 && !strncmp(line, "UTC", 3)) {
+ rtc_on_utc = 1;
+ } else {
+ LOG(LOGS_WARN, "Could not read RTC LOCAL/UTC setting from %s", hwclock_file);
+ }
+}
+
+/* ================================================== */
+
+static void
+setup_config(void)
+{
+ if (CNF_GetRtcOnUtc()) {
+ rtc_on_utc = 1;
+ } else {
+ rtc_on_utc = 0;
+ }
+
+ read_hwclock_file(CNF_GetHwclockFile());
+
+ autotrim_threshold = CNF_GetRtcAutotrim();
+}
+
+/* ================================================== */
+/* Read the coefficients from the file where they were saved
+ the last time the program was run. */
+
+static void
+read_coefs_from_file(void)
+{
+ FILE *in;
+
+ if (!tried_to_load_coefs) {
+
+ valid_coefs_from_file = 0; /* only gets set true if we succeed */
+
+ tried_to_load_coefs = 1;
+
+ if (coefs_file_name && (in = fopen(coefs_file_name, "r"))) {
+ if (fscanf(in, "%d%ld%lf%lf",
+ &valid_coefs_from_file,
+ &file_ref_time,
+ &file_ref_offset,
+ &file_rate_ppm) == 4) {
+ } else {
+ LOG(LOGS_WARN, "Could not read coefficients from %s", coefs_file_name);
+ }
+ fclose(in);
+ }
+ }
+}
+
+/* ================================================== */
+/* Write the coefficients to the file where they will be read
+ the next time the program is run. */
+
+static int
+write_coefs_to_file(int valid,time_t ref_time,double offset,double rate)
+{
+ struct stat buf;
+ char *temp_coefs_file_name;
+ FILE *out;
+ int r1, r2;
+
+ /* Create a temporary file with a '.tmp' extension. */
+
+ temp_coefs_file_name = (char*) Malloc(strlen(coefs_file_name)+8);
+
+ if(!temp_coefs_file_name) {
+ return RTC_ST_BADFILE;
+ }
+
+ strcpy(temp_coefs_file_name,coefs_file_name);
+ strcat(temp_coefs_file_name,".tmp");
+
+ out = fopen(temp_coefs_file_name, "w");
+ if (!out) {
+ Free(temp_coefs_file_name);
+ LOG(LOGS_WARN, "Could not open temporary RTC file %s.tmp for writing",
+ coefs_file_name);
+ return RTC_ST_BADFILE;
+ }
+
+ /* Gain rate is written out in ppm */
+ r1 = fprintf(out, "%1d %ld %.6f %.3f\n",
+ valid, ref_time, offset, 1.0e6 * rate);
+ r2 = fclose(out);
+ if (r1 < 0 || r2) {
+ Free(temp_coefs_file_name);
+ LOG(LOGS_WARN, "Could not write to temporary RTC file %s.tmp",
+ coefs_file_name);
+ return RTC_ST_BADFILE;
+ }
+
+ /* Clone the file attributes from the existing file if there is one. */
+
+ if (!stat(coefs_file_name,&buf)) {
+ if (chown(temp_coefs_file_name,buf.st_uid,buf.st_gid) ||
+ chmod(temp_coefs_file_name,buf.st_mode & 0777)) {
+ LOG(LOGS_WARN,
+ "Could not change ownership or permissions of temporary RTC file %s.tmp",
+ coefs_file_name);
+ }
+ }
+
+ /* Rename the temporary file to the correct location (see rename(2) for details). */
+
+ if (rename(temp_coefs_file_name,coefs_file_name)) {
+ unlink(temp_coefs_file_name);
+ Free(temp_coefs_file_name);
+ LOG(LOGS_WARN, "Could not replace old RTC file %s.tmp with new one %s",
+ coefs_file_name, coefs_file_name);
+ return RTC_ST_BADFILE;
+ }
+
+ Free(temp_coefs_file_name);
+
+ return RTC_ST_OK;
+}
+
+
+/* ================================================== */
+/* file_name is the name of the file where we save the RTC params
+ between executions. Return status is whether we could initialise
+ on this version of the system. */
+
+int
+RTC_Linux_Initialise(void)
+{
+ rtc_sec = MallocArray(time_t, MAX_SAMPLES);
+ rtc_trim = MallocArray(double, MAX_SAMPLES);
+ system_times = MallocArray(struct timespec, MAX_SAMPLES);
+
+ /* Setup details depending on configuration options */
+ setup_config();
+
+ /* In case it didn't get done by pre-init */
+ coefs_file_name = CNF_GetRtcFile();
+
+ /* Try to open device */
+
+ fd = open (CNF_GetRtcDevice(), O_RDWR);
+ if (fd < 0) {
+ LOG(LOGS_ERR, "Could not open RTC device %s : %s",
+ CNF_GetRtcDevice(), strerror(errno));
+ return 0;
+ }
+
+ /* Close on exec */
+ UTI_FdSetCloexec(fd);
+
+ n_samples = 0;
+ n_samples_since_regression = 0;
+ n_runs = 0;
+ coefs_valid = 0;
+
+ measurement_period = LOWEST_MEASUREMENT_PERIOD;
+
+ operating_mode = OM_NORMAL;
+
+ /* Register file handler */
+ SCH_AddFileHandler(fd, SCH_FILE_INPUT, read_from_device, NULL);
+
+ /* Register slew handler */
+ LCL_AddParameterChangeHandler(slew_samples, NULL);
+
+ logfileid = CNF_GetLogRtc() ? LOG_FileOpen("rtc",
+ " Date (UTC) Time RTC fast (s) Val Est fast (s) Slope (ppm) Ns Nr Meas")
+ : -1;
+ return 1;
+}
+
+/* ================================================== */
+
+void
+RTC_Linux_Finalise(void)
+{
+ SCH_RemoveTimeout(timeout_id);
+ timeout_id = 0;
+
+ /* Remove input file handler */
+ if (fd >= 0) {
+ SCH_RemoveFileHandler(fd);
+ close(fd);
+
+ /* Save the RTC data */
+ (void) RTC_Linux_WriteParameters();
+
+ }
+ Free(rtc_sec);
+ Free(rtc_trim);
+ Free(system_times);
+}
+
+/* ================================================== */
+
+static void
+switch_interrupts(int onoff)
+{
+ int status;
+
+ if (onoff) {
+ status = ioctl(fd, RTC_UIE_ON, 0);
+ if (status < 0) {
+ LOG(LOGS_ERR, "Could not %s RTC interrupt : %s", "enable", strerror(errno));
+ return;
+ }
+ skip_interrupts = 1;
+ } else {
+ status = ioctl(fd, RTC_UIE_OFF, 0);
+ if (status < 0) {
+ LOG(LOGS_ERR, "Could not %s RTC interrupt : %s", "disable", strerror(errno));
+ return;
+ }
+ }
+}
+
+/* ================================================== */
+
+static void
+measurement_timeout(void *any)
+{
+ timeout_id = 0;
+ switch_interrupts(1);
+}
+
+/* ================================================== */
+
+static void
+set_rtc(time_t new_rtc_time)
+{
+ struct tm rtc_tm;
+ struct rtc_time rtc_raw;
+ int status;
+
+ rtc_tm = *rtc_from_t(&new_rtc_time);
+
+ rtc_raw.tm_sec = rtc_tm.tm_sec;
+ rtc_raw.tm_min = rtc_tm.tm_min;
+ rtc_raw.tm_hour = rtc_tm.tm_hour;
+ rtc_raw.tm_mday = rtc_tm.tm_mday;
+ rtc_raw.tm_mon = rtc_tm.tm_mon;
+ rtc_raw.tm_year = rtc_tm.tm_year;
+ rtc_raw.tm_wday = rtc_tm.tm_wday;
+ rtc_raw.tm_yday = rtc_tm.tm_yday;
+ rtc_raw.tm_isdst = rtc_tm.tm_isdst;
+
+ status = ioctl(fd, RTC_SET_TIME, &rtc_raw);
+ if (status < 0) {
+ LOG(LOGS_ERR, "Could not set RTC time");
+ }
+
+}
+
+/* ================================================== */
+
+static void
+handle_initial_trim(void)
+{
+ double rate;
+ long delta_time;
+ double rtc_error_now, sys_error_now;
+
+ /* The idea is to accumulate some number of samples at 1 second
+ intervals, then do a robust regression fit to this. This
+ should give a good fix on the intercept (=system clock error
+ rel to RTC) at a particular time, removing risk of any
+ particular sample being an outlier. We can then look at the
+ elapsed interval since the epoch recorded in the RTC file,
+ and correct the system time accordingly. */
+
+ run_regression(1, &coefs_valid, &coef_ref_time, &coef_seconds_fast, &coef_gain_rate);
+
+ n_samples_since_regression = 0;
+
+ /* Set sample number to -1 so the next sample is not used, as it will not yet be corrected for System Trim*/
+
+ n_samples = -1;
+
+
+ read_coefs_from_file();
+
+ if (valid_coefs_from_file) {
+ /* Can process data */
+ delta_time = coef_ref_time - file_ref_time;
+ rate = 1.0e-6 * file_rate_ppm;
+ rtc_error_now = file_ref_offset + rate * (double) delta_time;
+
+ /* sys_error_now is positive if the system clock is fast */
+ sys_error_now = rtc_error_now - coef_seconds_fast;
+
+ LCL_AccumulateOffset(sys_error_now, 0.0);
+ LOG(LOGS_INFO, "System clock off from RTC by %f seconds (slew)",
+ sys_error_now);
+ } else {
+ LOG(LOGS_WARN, "No valid rtcfile coefficients");
+ }
+
+ coefs_valid = 0;
+
+ (after_init_hook)(after_init_hook_arg);
+
+ operating_mode = OM_NORMAL;
+}
+
+/* ================================================== */
+
+static void
+handle_relock_after_trim(void)
+{
+ int valid;
+ time_t ref;
+ double fast, slope;
+
+ valid = 0;
+ run_regression(1, &valid, &ref, &fast, &slope);
+
+ if (valid) {
+ write_coefs_to_file(1,ref,fast,saved_coef_gain_rate);
+ } else {
+ DEBUG_LOG("Could not do regression after trim");
+ }
+
+ coefs_valid = 0;
+ n_samples = 0;
+ n_samples_since_regression = 0;
+ operating_mode = OM_NORMAL;
+ measurement_period = LOWEST_MEASUREMENT_PERIOD;
+}
+
+/* ================================================== */
+
+static void
+maybe_autotrim(void)
+{
+ /* Trim only when in normal mode, the coefficients are fresh, the current
+ offset is above the threshold and the system clock is synchronized */
+
+ if (operating_mode != OM_NORMAL || !coefs_valid || n_samples_since_regression)
+ return;
+
+ if (autotrim_threshold <= 0.0 || fabs(coef_seconds_fast) < autotrim_threshold)
+ return;
+
+ if (REF_GetOurStratum() >= 16)
+ return;
+
+ RTC_Linux_Trim();
+}
+
+/* ================================================== */
+
+static void
+process_reading(time_t rtc_time, struct timespec *system_time)
+{
+ double rtc_fast;
+
+ accumulate_sample(rtc_time, system_time);
+
+ switch (operating_mode) {
+ case OM_NORMAL:
+
+ if (n_samples_since_regression >= N_SAMPLES_PER_REGRESSION) {
+ run_regression(1, &coefs_valid, &coef_ref_time, &coef_seconds_fast, &coef_gain_rate);
+ n_samples_since_regression = 0;
+ maybe_autotrim();
+ }
+
+ break;
+ case OM_INITIAL:
+ if (n_samples_since_regression >= 8) {
+ handle_initial_trim();
+ }
+ break;
+ case OM_AFTERTRIM:
+ if (n_samples_since_regression >= 8) {
+ handle_relock_after_trim();
+ }
+ break;
+ default:
+ assert(0);
+ break;
+ }
+
+
+ if (logfileid != -1) {
+ rtc_fast = (rtc_time - system_time->tv_sec) - 1.0e-9 * system_time->tv_nsec;
+
+ LOG_FileWrite(logfileid, "%s %14.6f %1d %14.6f %12.3f %2d %2d %4d",
+ UTI_TimeToLogForm(system_time->tv_sec),
+ rtc_fast,
+ coefs_valid,
+ coef_seconds_fast, coef_gain_rate * 1.0e6, n_samples, n_runs, measurement_period);
+ }
+
+}
+
+/* ================================================== */
+
+static void
+read_from_device(int fd_, int event, void *any)
+{
+ int status;
+ unsigned long data;
+ struct timespec sys_time;
+ struct rtc_time rtc_raw;
+ struct tm rtc_tm;
+ time_t rtc_t;
+ int error = 0;
+
+ status = read(fd, &data, sizeof(data));
+
+ if (status < 0) {
+ /* This looks like a bad error : the file descriptor was indicating it was
+ * ready to read but we couldn't read anything. Give up. */
+ LOG(LOGS_ERR, "Could not read flags %s : %s", CNF_GetRtcDevice(), strerror(errno));
+ SCH_RemoveFileHandler(fd);
+ switch_interrupts(0); /* Likely to raise error too, but just to be sure... */
+ close(fd);
+ fd = -1;
+ return;
+ }
+
+ if (skip_interrupts > 0) {
+ /* Wait for the next interrupt, this one may be bogus */
+ skip_interrupts--;
+ return;
+ }
+
+ if ((data & RTC_UF) == RTC_UF) {
+ /* Update interrupt detected */
+
+ /* Read RTC time, sandwiched between two polls of the system clock
+ so we can bound any error. */
+
+ SCH_GetLastEventTime(&sys_time, NULL, NULL);
+
+ status = ioctl(fd, RTC_RD_TIME, &rtc_raw);
+ if (status < 0) {
+ LOG(LOGS_ERR, "Could not read time from %s : %s", CNF_GetRtcDevice(), strerror(errno));
+ error = 1;
+ goto turn_off_interrupt;
+ }
+
+ /* Convert RTC time into a struct timespec */
+ rtc_tm.tm_sec = rtc_raw.tm_sec;
+ rtc_tm.tm_min = rtc_raw.tm_min;
+ rtc_tm.tm_hour = rtc_raw.tm_hour;
+ rtc_tm.tm_mday = rtc_raw.tm_mday;
+ rtc_tm.tm_mon = rtc_raw.tm_mon;
+ rtc_tm.tm_year = rtc_raw.tm_year;
+
+ rtc_t = t_from_rtc(&rtc_tm);
+
+ if (rtc_t == (time_t)(-1)) {
+ error = 1;
+ goto turn_off_interrupt;
+ }
+
+ process_reading(rtc_t, &sys_time);
+
+ if (n_samples < 4) {
+ measurement_period = LOWEST_MEASUREMENT_PERIOD;
+ } else if (n_samples < 6) {
+ measurement_period = LOWEST_MEASUREMENT_PERIOD << 1;
+ } else if (n_samples < 10) {
+ measurement_period = LOWEST_MEASUREMENT_PERIOD << 2;
+ } else if (n_samples < 14) {
+ measurement_period = LOWEST_MEASUREMENT_PERIOD << 3;
+ } else {
+ measurement_period = LOWEST_MEASUREMENT_PERIOD << 4;
+ }
+
+ }
+
+turn_off_interrupt:
+
+ switch (operating_mode) {
+ case OM_INITIAL:
+ if (error) {
+ DEBUG_LOG("Could not complete initial step due to errors");
+ operating_mode = OM_NORMAL;
+ (after_init_hook)(after_init_hook_arg);
+
+ switch_interrupts(0);
+
+ timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
+ }
+
+ break;
+
+ case OM_AFTERTRIM:
+ if (error) {
+ DEBUG_LOG("Could not complete after trim relock due to errors");
+ operating_mode = OM_NORMAL;
+
+ switch_interrupts(0);
+
+ timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
+ }
+
+ break;
+
+ case OM_NORMAL:
+ switch_interrupts(0);
+
+ timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
+
+ break;
+ default:
+ assert(0);
+ break;
+ }
+
+}
+
+/* ================================================== */
+
+void
+RTC_Linux_TimeInit(void (*after_hook)(void *), void *anything)
+{
+ after_init_hook = after_hook;
+ after_init_hook_arg = anything;
+
+ operating_mode = OM_INITIAL;
+ timeout_id = 0;
+ switch_interrupts(1);
+}
+
+/* ================================================== */
+
+void
+RTC_Linux_StartMeasurements(void)
+{
+ measurement_timeout(NULL);
+}
+
+/* ================================================== */
+
+int
+RTC_Linux_WriteParameters(void)
+{
+ int retval;
+
+ if (fd < 0) {
+ return RTC_ST_NODRV;
+ }
+
+ if (coefs_valid) {
+ retval = write_coefs_to_file(1,coef_ref_time, coef_seconds_fast, coef_gain_rate);
+ } else {
+ /* Don't change the existing file, it may not be 100% valid but is our
+ current best guess. */
+ retval = RTC_ST_OK; /*write_coefs_to_file(0,0,0.0,0.0); */
+ }
+
+ return(retval);
+}
+
+/* ================================================== */
+/* Try to set the system clock from the RTC, in the same manner as
+ /sbin/hwclock -s would do. We're not as picky about OS version
+ etc in this case, since we have fewer requirements regarding the
+ RTC behaviour than we do for the rest of the module. */
+
+int
+RTC_Linux_TimePreInit(time_t driftfile_time)
+{
+ int fd, status;
+ struct rtc_time rtc_raw, rtc_raw_retry;
+ struct tm rtc_tm;
+ time_t rtc_t;
+ double accumulated_error, sys_offset;
+ struct timespec new_sys_time, old_sys_time;
+
+ coefs_file_name = CNF_GetRtcFile();
+
+ setup_config();
+ read_coefs_from_file();
+
+ fd = open(CNF_GetRtcDevice(), O_RDONLY);
+
+ if (fd < 0) {
+ return 0; /* Can't open it, and won't be able to later */
+ }
+
+ /* Retry reading the rtc until both read attempts give the same sec value.
+ This way the race condition is prevented that the RTC has updated itself
+ during the first read operation. */
+ do {
+ status = ioctl(fd, RTC_RD_TIME, &rtc_raw);
+ if (status >= 0) {
+ status = ioctl(fd, RTC_RD_TIME, &rtc_raw_retry);
+ }
+ } while (status >= 0 && rtc_raw.tm_sec != rtc_raw_retry.tm_sec);
+
+ /* Read system clock */
+ LCL_ReadCookedTime(&old_sys_time, NULL);
+
+ close(fd);
+
+ if (status >= 0) {
+ /* Convert to seconds since 1970 */
+ rtc_tm.tm_sec = rtc_raw.tm_sec;
+ rtc_tm.tm_min = rtc_raw.tm_min;
+ rtc_tm.tm_hour = rtc_raw.tm_hour;
+ rtc_tm.tm_mday = rtc_raw.tm_mday;
+ rtc_tm.tm_mon = rtc_raw.tm_mon;
+ rtc_tm.tm_year = rtc_raw.tm_year;
+
+ rtc_t = t_from_rtc(&rtc_tm);
+
+ if (rtc_t != (time_t)(-1)) {
+
+ /* Work out approximatation to correct time (to about the
+ nearest second) */
+ if (valid_coefs_from_file) {
+ accumulated_error = file_ref_offset +
+ (rtc_t - file_ref_time) * 1.0e-6 * file_rate_ppm;
+ } else {
+ accumulated_error = 0.0;
+ }
+
+ /* Correct time */
+
+ new_sys_time.tv_sec = rtc_t;
+ /* Average error in the RTC reading */
+ new_sys_time.tv_nsec = 500000000;
+
+ UTI_AddDoubleToTimespec(&new_sys_time, -accumulated_error, &new_sys_time);
+
+ if (new_sys_time.tv_sec < driftfile_time) {
+ LOG(LOGS_WARN, "RTC time before last driftfile modification (ignored)");
+ return 0;
+ }
+
+ sys_offset = UTI_DiffTimespecsToDouble(&old_sys_time, &new_sys_time);
+
+ /* Set system time only if the step is larger than 1 second */
+ if (fabs(sys_offset) >= 1.0) {
+ if (LCL_ApplyStepOffset(sys_offset))
+ LOG(LOGS_INFO, "System time set from RTC");
+ }
+ } else {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+
+ return 1;
+}
+
+/* ================================================== */
+
+int
+RTC_Linux_GetReport(RPT_RTC_Report *report)
+{
+ report->ref_time.tv_sec = coef_ref_time;
+ report->ref_time.tv_nsec = 0;
+ report->n_samples = n_samples;
+ report->n_runs = n_runs;
+ if (n_samples > 1) {
+ report->span_seconds = ((rtc_sec[n_samples-1] - rtc_sec[0]) +
+ (long)(rtc_trim[n_samples-1] - rtc_trim[0]));
+ } else {
+ report->span_seconds = 0;
+ }
+ report->rtc_seconds_fast = coef_seconds_fast;
+ report->rtc_gain_rate_ppm = 1.0e6 * coef_gain_rate;
+ return 1;
+}
+
+/* ================================================== */
+
+int
+RTC_Linux_Trim(void)
+{
+ struct timespec now;
+
+ /* Remember the slope coefficient - we won't be able to determine a
+ good one in a few seconds when we determine the new offset! */
+ saved_coef_gain_rate = coef_gain_rate;
+
+ if (fabs(coef_seconds_fast) > 1.0) {
+
+ LOG(LOGS_INFO, "RTC wrong by %.3f seconds (step)",
+ coef_seconds_fast);
+
+ /* Do processing to set clock. Let R be the value we set the
+ RTC to, then in 500ms the RTC ticks (R+1) (see comments in
+ arch/i386/kernel/time.c about the behaviour of the real time
+ clock chip). If S is the system time now, the error at the
+ next RTC tick is given by E = (R+1) - (S+0.5). Ideally we
+ want |E| <= 0.5, which implies R <= S <= R+1, i.e. R is just
+ the rounded down part of S, i.e. the seconds part. */
+
+ LCL_ReadCookedTime(&now, NULL);
+
+ set_rtc(now.tv_sec);
+
+ /* All old samples will now look bogus under the new
+ regime. */
+ n_samples = 0;
+ operating_mode = OM_AFTERTRIM;
+
+ /* Estimate the offset in case writertc is called or chronyd
+ is terminated during rapid sampling */
+ coef_seconds_fast = -now.tv_nsec / 1.0e9 + 0.5;
+ coef_ref_time = now.tv_sec;
+
+ /* And start rapid sampling, interrupts on now */
+ SCH_RemoveTimeout(timeout_id);
+ timeout_id = 0;
+ switch_interrupts(1);
+ }
+
+ return 1;
+
+}