From 104f986b0650b8f93540785d2bcf486905e49b62 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 5 May 2024 20:43:21 +0200 Subject: Adding upstream version 3.4. Signed-off-by: Daniel Baumann --- rtc_linux.c | 1129 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1129 insertions(+) create mode 100644 rtc_linux.c (limited to 'rtc_linux.c') 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 + +#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 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= 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; + +} -- cgit v1.2.3