diff --git a/Makefile.in b/Makefile.in index 23a83fe..fd12f79 100644 --- a/Makefile.in +++ b/Makefile.in @@ -46,7 +46,7 @@ OBJS = util.o sched.o regress.o local.o \ nameserv.o acquire.o manual.o addrfilt.o \ cmdparse.o mkdirpp.o rtc.o pktlength.o clientlog.o \ broadcast.o refclock.o refclock_shm.o refclock_sock.o \ - refclock_pps.o + refclock_pps.o tempcomp.o EXTRA_OBJS=@EXTRA_OBJECTS@ diff --git a/chrony.texi b/chrony.texi index 01be635..e0ab4f1 100644 --- a/chrony.texi +++ b/chrony.texi @@ -1200,6 +1200,7 @@ directives can occur in any order in the file. * server directive:: Specify an NTP server * sched_priority directive:: Require real-time scheduling and specify a priority for it. * lock_all directive:: Require that chronyd be locked into RAM. +* tempcomp directive:: Specify temperature sensor and compensation coefficients @end menu @c }}} @@ -1790,6 +1791,9 @@ This option logs information about the system's real-time clock. @item refclocks This option logs the raw and filtered reference clock measurements to a file called refclocks.log. +@item tempcomp +This option logs the temperature measurements and system rate +compensations to a file called tempcomp.log. @end table The files are written to the directory specified by the logdir @@ -1807,6 +1811,7 @@ log measurements statistics tracking * tracking log:: The format of the tracking log * RTC log:: The format of the RTC log * refclocks log:: The format of the refclocks log +* tempcomp log:: The format of the tempcomp log @end menu @c }}} @c {{{ measurements.log @@ -2065,6 +2070,36 @@ that the local clock is slow. [-6.741777e-07] Assumed dispersion of the sample. [1.000e-06] @end enumerate +A banner is periodically written to the log file to indicate the +meanings of the columns. +@c }}} +@c {{{ tempcomp.log +@node tempcomp log +@subsubsection Tempcomp log file format + +An example line (which actually appears as a single line in the file) +from the tempcomp log file is shown below. + +@example +2010-04-19 10:39:48 2.8000e+04 3.6600e-01 +@end example + +The columns are as follows (the quantities in square brackets are the +values from the example line above) : + +@enumerate 1 +@item +Date [2010-04-19] +@item +Hour:Minute:Second [10:39:48]. Note that the +date/time pair is expressed in UTC, not the local time zone. +@item +Temperature read from tempcomp file. [2.8000e+04] +@item +Applied compensation in ppm, positive means the system clock is +running faster than it would be without the compensation. [3.6600e-01] +@end enumerate + A banner is periodically written to the log file to indicate the meanings of the columns. @c }}} @@ -2563,6 +2598,49 @@ chronyc's @code{online} (@pxref{online command}) command when the link has been established, to enable measurements to start.) @end table +@c }}} +@c {{{ tempcomp +@node tempcomp directive +@subsection tempcomp +Normally, changes in rate of drift of the system clock are caused mainly by +changes in temperature of the crystal oscillator on the mainboard. + +If there are available temperature measurements from a sensor close to the +oscillator, @code{tempcomp} directive can be used to compensate for the changes +in rate and possibly improve clock accuracy. + +Whether it will really help depends on many factors, including resolution of +the sensor, noise in measurements, time source polling interval, compensation +update interval, how good are the temperature coefficients, and how close is +the sensor to the oscillator. The frequency reported in tracking.log should +be more stable and the offsets should be smaller. + +The directive has six parameters: path to the file which contains current +temperature in text format, update interval (in seconds), and temperature +coefficients T0, k0, k1, k2. + +The frequency compensation is calculated (in ppm) as + +@code{k0 + (T - T0) * k1 + (T - T0)^2 * k2} + +The result has to be between -10 ppm and 10 ppm, otherwise the measurement is +considered to be faulty and will be ignored. The k0 coefficient can be used to +get the results in that range. + +Valid measurements and calculated corrections are logged to tempcomp.log file if +enabled with @code{log tempcomp} directive. + +An example of use is + +@example +tempcomp /sys/class/hwmon/hwmon1/device/temp2_input 30 26000 0.0 0.000183 0.0 +@end example + +The measured temperature will be read from the file in Linux sysfs filesystem +every 30 seconds. When the temperature is 26 degress (26000), the system clock +frequency will not be adjusted. When it is 27 degrees (27000), the clock will +be set to run 0.183ppm faster than it would be without the compensation, etc. + @c }}} @c }}} @c {{{ S:Running chronyc diff --git a/conf.c b/conf.c index d1d0758..dd8940b 100644 --- a/conf.c +++ b/conf.c @@ -105,6 +105,7 @@ static void parse_linux_hz(const char *); static void parse_linux_freq_scale(const char *); static void parse_sched_priority(const char *); static void parse_lockall(const char *); +static void parse_tempcomp(const char *); /* ================================================== */ /* Configuration variables */ @@ -125,6 +126,7 @@ static int do_log_statistics = 0; static int do_log_tracking = 0; static int do_log_rtc = 0; static int do_log_refclocks = 0; +static int do_log_tempcomp = 0; static int do_dump_on_exit = 0; static char *logdir = "."; static char *dumpdir = "."; @@ -183,6 +185,11 @@ static IPAddr bind_cmd_address4, bind_cmd_address6; * chronyds being started. */ static char *pidfile = "/var/run/chronyd.pid"; +/* Temperature sensor, update interval and compensation coefficients */ +static char *tempcomp_file = NULL; +static double tempcomp_interval; +static double tempcomp_T0, tempcomp_k0, tempcomp_k1, tempcomp_k2; + /* Boolean for whether the Linux HZ value has been overridden, and the * new value. */ static int set_linux_hz = 0; @@ -239,6 +246,7 @@ static const Command commands[] = { {"rtcdevice", 9, parse_rtcdevice}, {"pidfile", 7, parse_pidfile}, {"broadcast", 9, parse_broadcast}, + {"tempcomp", 8, parse_tempcomp}, {"linux_hz", 8, parse_linux_hz}, {"linux_freq_scale", 16, parse_linux_freq_scale}, {"sched_priority", 14, parse_sched_priority}, @@ -679,6 +687,9 @@ parse_log(const char *line) } else if (!strncmp(line, "refclocks", 9)) { do_log_refclocks = 1; line += 9; + } else if (!strncmp(line, "tempcomp", 8)) { + do_log_tempcomp = 1; + line += 8; } else { break; } @@ -1114,6 +1125,34 @@ parse_broadcast(const char *line) /* ================================================== */ +static void +parse_tempcomp(const char *line) +{ + const char *tmp; + + while (isspace(line[0])) + line++; + tmp = line; + while (line[0] != '\0' && !isspace(line[0])) + line++; + + if (line == tmp) { + LOG(LOGS_WARN, LOGF_Configure, "Could not read tempcomp filename at line %d", line_number); + return; + } + + if (sscanf(line, "%lf %lf %lf %lf %lf", &tempcomp_interval, &tempcomp_T0, &tempcomp_k0, &tempcomp_k1, &tempcomp_k2) != 5) { + LOG(LOGS_WARN, LOGF_Configure, "Could not read tempcomp interval or coefficients at line %d", line_number); + return; + } + + tempcomp_file = MallocArray(char, 1 + line - tmp); + strncpy(tempcomp_file, tmp, line - tmp); + tempcomp_file[line - tmp] = '\0'; +} + +/* ================================================== */ + static void parse_linux_hz(const char *line) { @@ -1273,6 +1312,7 @@ CNF_GetLogRtc(void) } /* ================================================== */ + int CNF_GetLogRefclocks(void) { @@ -1281,6 +1321,14 @@ CNF_GetLogRefclocks(void) /* ================================================== */ +int +CNF_GetLogTempComp(void) +{ + return do_log_tempcomp; +} + +/* ================================================== */ + char * CNF_GetKeysFile(void) { @@ -1514,3 +1562,16 @@ CNF_GetLockMemory(void) { return lock_memory; } + +/* ================================================== */ + +void +CNF_GetTempComp(char **file, double *interval, double *T0, double *k0, double *k1, double *k2) +{ + *file = tempcomp_file; + *interval = tempcomp_interval; + *T0 = tempcomp_T0; + *k0 = tempcomp_k0; + *k1 = tempcomp_k1; + *k2 = tempcomp_k2; +} diff --git a/conf.h b/conf.h index 1ec99f2..31f4cf0 100644 --- a/conf.h +++ b/conf.h @@ -53,6 +53,7 @@ extern int CNF_GetLogStatistics(void); extern int CNF_GetLogTracking(void); extern int CNF_GetLogRtc(void); extern int CNF_GetLogRefclocks(void); +extern int CNF_GetLogTempComp(void); extern char *CNF_GetKeysFile(void); extern char *CNF_GetRtcFile(void); extern unsigned long CNF_GetCommandKey(void); @@ -81,4 +82,6 @@ extern void CNF_SetupAccessRestrictions(void); extern int CNF_GetSchedPriority(void); extern int CNF_GetLockMemory(void); +extern void CNF_GetTempComp(char **file, double *interval, double *T0, double *k0, double *k1, double *k2); + #endif /* GOT_CONF_H */ diff --git a/local.c b/local.c index 3c5efb3..dbda744 100644 --- a/local.c +++ b/local.c @@ -45,6 +45,9 @@ /* Variable to store the current frequency, in ppm */ static double current_freq_ppm; +/* Temperature compensation, in ppm */ +static double temp_comp_ppm; + /* ================================================== */ /* Store the system dependent drivers */ @@ -152,6 +155,7 @@ LCL_Initialise(void) /* This ought to be set from the system driver layer */ current_freq_ppm = 0.0; + temp_comp_ppm = 0.0; calculate_sys_precision(); } @@ -360,7 +364,16 @@ LCL_GetOffsetCorrection(struct timeval *raw, double *correction, double *err) double LCL_ReadAbsoluteFrequency(void) { - return (*drv_read_freq)(); + double freq; + + freq = (*drv_read_freq)(); + + /* Undo temperature compensation */ + if (temp_comp_ppm != 0.0) { + freq = (freq + temp_comp_ppm) / (1.0 - 1.0e-6 * temp_comp_ppm); + } + + return freq; } /* ================================================== */ @@ -374,6 +387,11 @@ LCL_SetAbsoluteFrequency(double afreq_ppm) struct timeval raw, cooked; double dfreq; + /* Apply temperature compensation */ + if (temp_comp_ppm != 0.0) { + afreq_ppm = afreq_ppm * (1.0 - 1.0e-6 * temp_comp_ppm) - temp_comp_ppm; + } + /* Call the system-specific driver for setting the frequency */ (*drv_set_freq)(afreq_ppm); @@ -580,3 +598,26 @@ LCL_SetLeap(int leap) } /* ================================================== */ + +void +LCL_SetTempComp(double comp) +{ + if (temp_comp_ppm == comp) + return; + + /* Undo previous compensation */ + current_freq_ppm = (current_freq_ppm + temp_comp_ppm) / + (1.0 - 1.0e-6 * temp_comp_ppm); + + /* Apply new compensation */ + current_freq_ppm = current_freq_ppm * (1.0 - 1.0e-6 * comp) - comp; + + temp_comp_ppm = comp; + + /* Call the system-specific driver for setting the frequency */ + (*drv_set_freq)(current_freq_ppm); + + return; +} + +/* ================================================== */ diff --git a/local.h b/local.h index bef1a3e..2b9ea82 100644 --- a/local.h +++ b/local.h @@ -188,4 +188,9 @@ extern int LCL_MakeStep(double threshold); and zero cancels scheduled leap second. */ extern void LCL_SetLeap(int leap); +/* Routine to set a frequency correction (in ppm) that should be applied + to local clock to compensate for temperature changes. A positive + argument means that the clock frequency should be increased. */ +extern void LCL_SetTempComp(double comp); + #endif /* GOT_LOCAL_H */ diff --git a/logging.c b/logging.c index 8271c00..a2f471e 100644 --- a/logging.c +++ b/logging.c @@ -58,7 +58,7 @@ struct LogFile { static int n_filelogs = 0; /* Increase this when adding a new logfile */ -#define MAX_FILELOGS 5 +#define MAX_FILELOGS 6 static struct LogFile logfiles[MAX_FILELOGS]; diff --git a/main.c b/main.c index 31f4a5c..7da6250 100644 --- a/main.c +++ b/main.c @@ -53,6 +53,7 @@ #include "clientlog.h" #include "broadcast.h" #include "nameserv.h" +#include "tempcomp.h" /* ================================================== */ @@ -86,6 +87,7 @@ MAI_CleanupAndExit(void) SRC_DumpSources(); } + TMC_Finalise(); MNL_Finalise(); ACQ_Finalise(); KEY_Finalise(); @@ -326,6 +328,7 @@ int main KEY_Initialise(); ACQ_Initialise(); MNL_Initialise(); + TMC_Initialise(); LOG_CreateLogFileDir(); diff --git a/tempcomp.c b/tempcomp.c new file mode 100644 index 0000000..d7f02ad --- /dev/null +++ b/tempcomp.c @@ -0,0 +1,101 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2010 + * + * 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. + * + ********************************************************************** + + ======================================================================= + + Routines implementing temperature compensation. + + */ + +#include "conf.h" +#include "local.h" +#include "memory.h" +#include "util.h" +#include "logging.h" +#include "sched.h" +#include "tempcomp.h" + +static SCH_TimeoutID timeout_id; + +static LOG_FileID logfileid; + +static char *filename; +static double update_interval; +static double T0, k0, k1, k2; + +static void +read_timeout(void *arg) +{ + FILE *f; + double temp, comp; + + f = fopen(filename, "r"); + + if (f && fscanf(f, "%lf", &temp) == 1) { + comp = k0 + (temp - T0) * k1 + (temp - T0) * (temp - T0) * k2; + + /* Don't allow corrections above 10 ppm */ + if (fabs(comp) < 10.0) { + LCL_SetTempComp(comp); + + if (logfileid != -1) { + struct timeval now; + + LCL_ReadCookedTime(&now, NULL); + LOG_FileWrite(logfileid, "%s %11.4e %11.4e", + UTI_TimeToLogForm(now.tv_sec), temp, comp); + } + } + } + + if (f) + fclose(f); + + timeout_id = SCH_AddTimeoutByDelay(update_interval, read_timeout, NULL); +} + +void +TMC_Initialise(void) +{ + CNF_GetTempComp(&filename, &update_interval, &T0, &k0, &k1, &k2); + + if (filename == NULL) + return; + + if (update_interval <= 0.0) + update_interval = 1.0; + + logfileid = CNF_GetLogTempComp() ? LOG_FileOpen("tempcomp", + " Date (UTC) Time Temp. Comp.") + : -1; + + read_timeout(NULL); +} + +void +TMC_Finalise(void) +{ + if (filename == NULL) + return; + + SCH_RemoveTimeout(timeout_id); + Free(filename); +} diff --git a/tempcomp.h b/tempcomp.h new file mode 100644 index 0000000..de65469 --- /dev/null +++ b/tempcomp.h @@ -0,0 +1,29 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Miroslav Lichvar 2010 + * + * 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. + * + ********************************************************************** + + ======================================================================= + + Header file for temperature compensation. + + */ + +extern void TMC_Initialise(void); +extern void TMC_Finalise(void);