Merge branch 'a3f/rtc-refclk' into 'master'

refclock: add new refclock for RTCs

See merge request chrony/chrony!9
This commit is contained in:
Ahmad Fatoum 2024-10-09 07:23:06 +00:00
commit 8b70af5dc2
6 changed files with 365 additions and 143 deletions

3
configure vendored
View file

@ -839,6 +839,9 @@ if [ $feat_rtc = "1" ] && [ $try_rtc = "1" ] && \
then then
EXTRA_OBJECTS="$EXTRA_OBJECTS rtc_linux.o" EXTRA_OBJECTS="$EXTRA_OBJECTS rtc_linux.o"
add_def FEAT_RTC add_def FEAT_RTC
if [ $feat_refclock = "1" ]; then
EXTRA_OBJECTS="$EXTRA_OBJECTS refclock_rtc.o"
fi
fi fi
if [ $feat_refclock = "1" ] && [ $feat_phc = "1" ] && [ $try_phc = "1" ] && \ if [ $feat_refclock = "1" ] && [ $feat_phc = "1" ] && [ $try_phc = "1" ] && \

View file

@ -478,7 +478,7 @@ the driver-specific parameter using the *:* character.
+ +
This directive can be used multiple times to specify multiple reference clocks. This directive can be used multiple times to specify multiple reference clocks.
+ +
There are four drivers included in *chronyd*: There are five drivers included in *chronyd*:
+ +
*PPS*::: *PPS*:::
Driver for the kernel PPS (pulse per second) API. The parameter is the path to Driver for the kernel PPS (pulse per second) API. The parameter is the path to
@ -596,6 +596,25 @@ refclock PHC /dev/ptp1:nocrossts poll 3 pps
refclock PHC /dev/ptp2:extpps:pin=1 width 0.2 poll 2 refclock PHC /dev/ptp2:extpps:pin=1 width 0.2 poll 2
---- ----
+ +
*RTC*:::
Driver for using the Real Time Clock (RTC) as reference clock.
The parameter is the path to the RTC character device which should be used
as a time source.
This cannot be used cannot be used with the <<rtcfile,*rtcfile*>> or
<<rtcsync,*rtcsync*>> directive.
The driver supports the following options:
+
*utc*::::
Assume that RTC keeps Universal Coordinated Time (UTC) instead of local
time.
{blank}:::
+
Examples:
+
----
refclock RTC /dev/rtc0:utc
----
+
{blank}:: {blank}::
The *refclock* directive supports the following options: The *refclock* directive supports the following options:
+ +
@ -2203,7 +2222,8 @@ The directive takes no arguments. It is equivalent to specifying the *-u*
switch to the Linux *hwclock* program. switch to the Linux *hwclock* program.
+ +
Note that this setting is overridden by the <<hwclockfile,*hwclockfile*>> file Note that this setting is overridden by the <<hwclockfile,*hwclockfile*>> file
and is not relevant for the <<rtcsync,*rtcsync*>> directive. and is not relevant for the <<rtcsync,*rtcsync*>> directive or when the RTC
is used as clock reference.
[[rtcsync]]*rtcsync*:: [[rtcsync]]*rtcsync*::
The *rtcsync* directive enables a mode where the system time is periodically The *rtcsync* directive enables a mode where the system time is periodically

View file

@ -48,6 +48,7 @@ extern RefclockDriver RCL_SHM_driver;
extern RefclockDriver RCL_SOCK_driver; extern RefclockDriver RCL_SOCK_driver;
extern RefclockDriver RCL_PPS_driver; extern RefclockDriver RCL_PPS_driver;
extern RefclockDriver RCL_PHC_driver; extern RefclockDriver RCL_PHC_driver;
extern RefclockDriver RCL_RTC_driver;
struct FilterSample { struct FilterSample {
double offset; double offset;
@ -63,6 +64,7 @@ struct RCL_Instance_Record {
int driver_poll; int driver_poll;
int driver_polled; int driver_polled;
int poll; int poll;
int inpoll;
int leap_status; int leap_status;
int local; int local;
int pps_forced; int pps_forced;
@ -159,6 +161,8 @@ RCL_AddRefclock(RefclockParameters *params)
inst->driver = &RCL_PPS_driver; inst->driver = &RCL_PPS_driver;
} else if (strcmp(params->driver_name, "PHC") == 0) { } else if (strcmp(params->driver_name, "PHC") == 0) {
inst->driver = &RCL_PHC_driver; inst->driver = &RCL_PHC_driver;
} else if (strcmp(params->driver_name, "RTC") == 0) {
inst->driver = &RCL_RTC_driver;
} else { } else {
LOG_FATAL("unknown refclock driver %s", params->driver_name); LOG_FATAL("unknown refclock driver %s", params->driver_name);
} }
@ -174,6 +178,7 @@ RCL_AddRefclock(RefclockParameters *params)
inst->driver_parameter_length = 0; inst->driver_parameter_length = 0;
inst->driver_poll = params->driver_poll; inst->driver_poll = params->driver_poll;
inst->poll = params->poll; inst->poll = params->poll;
inst->inpoll = 0;
inst->driver_polled = 0; inst->driver_polled = 0;
inst->leap_status = LEAP_Normal; inst->leap_status = LEAP_Normal;
inst->local = params->local; inst->local = params->local;
@ -502,7 +507,7 @@ RCL_AddSample(RCL_Instance instance, struct timespec *sample_time,
log_sample(instance, &cooked_time, 0, 0, raw_offset, offset, dispersion); log_sample(instance, &cooked_time, 0, 0, raw_offset, offset, dispersion);
/* for logging purposes */ /* for logging purposes */
if (!instance->driver->poll) if (!instance->inpoll)
instance->driver_polled++; instance->driver_polled++;
return 1; return 1;
@ -659,7 +664,7 @@ RCL_AddCookedPulse(RCL_Instance instance, struct timespec *cooked_time,
offset, dispersion); offset, dispersion);
/* for logging purposes */ /* for logging purposes */
if (!instance->driver->poll) if (!instance->inpoll)
instance->driver_polled++; instance->driver_polled++;
return 1; return 1;
@ -785,7 +790,9 @@ poll_timeout(void *arg)
if (inst->driver->poll) { if (inst->driver->poll) {
poll = inst->driver_poll; poll = inst->driver_poll;
inst->inpoll = 1;
inst->driver->poll(inst); inst->driver->poll(inst);
inst->inpoll = 0;
inst->driver_polled++; inst->driver_polled++;
} }

162
refclock_rtc.c Normal file
View file

@ -0,0 +1,162 @@
/*
chronyd/chronyc - Programs for keeping computer clocks accurate.
**********************************************************************
* Copyright (C) 2021 Uwe Kleine-König, Pengutronix
*
* 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.
*
**********************************************************************
=======================================================================
RTC refclock driver.
*/
#include <linux/rtc.h>
#include "config.h"
#include "conf.h"
#include "refclock.h"
#include "local.h"
#include "logging.h"
#include "memory.h"
#include "sched.h"
#include "util.h"
#include "rtc_linux.h"
struct refrtc_instance {
int fd;
int polling;
int utc;
};
static int refrtc_add_sample(RCL_Instance instance, struct timespec *now,
time_t rtc_sec, long rtc_nsec)
{
struct timespec rtc_ts;
int status;
rtc_ts.tv_sec = rtc_sec;
rtc_ts.tv_nsec = rtc_nsec;
status = RCL_AddSample(instance, now, &rtc_ts, LEAP_Normal);
if (status == 0)
DEBUG_LOG("rtc_sec = %s, now = %s",
UTI_TimespecToString(&rtc_ts), UTI_TimespecToString(now));
return status;
}
static void refrtc_read_after_uie(int rtcfd, int event, void *data)
{
RCL_Instance instance = (RCL_Instance)data;
struct refrtc_instance *rtc = RCL_GetDriverData(instance);
time_t rtc_sec;
struct timespec now;
int status;
status = RTC_Linux_CheckInterrupt(rtcfd);
if (status <= -1) {
SCH_RemoveFileHandler(rtcfd);
RTC_Linux_SwitchInterrupt(rtcfd, 0); /* Likely to raise error too, but just to be sure... */
close(rtcfd);
rtc->fd = -1;
return;
} else if (status == 0) {
/* Wait for the next interrupt, this one may be bogus */
return;
}
rtc_sec = RTC_Linux_ReadTime_AfterInterrupt(rtcfd, rtc->utc, NULL, &now);
if (rtc_sec == (time_t)-1)
return;
refrtc_add_sample(instance, &now, rtc_sec, 0);
}
static int refrtc_initialise(RCL_Instance instance)
{
const char *options[] = {"utc", NULL};
int rtcfd;
const char *path;
struct refrtc_instance *rtc;
int status;
RCL_CheckDriverOptions(instance, options);
if (CNF_GetRtcSync() || CNF_GetRtcFile())
LOG_FATAL("refclock RTC cannot be used together with rtcsync or rtcfile");
path = RCL_GetDriverParameter(instance);
rtcfd = open(path, O_RDONLY);
if (rtcfd < 0)
LOG_FATAL("Could not open RTC device %s : %m", path);
/* Close on exec */
UTI_FdSetCloexec(rtcfd);
rtc = MallocNew(struct refrtc_instance);
rtc->fd = rtcfd;
rtc->utc = RCL_GetDriverOption(instance, "utc") ? 1 : 0;
RCL_SetDriverData(instance, rtc);
/* Try to enable update interrupts */
status = RTC_Linux_SwitchInterrupt(rtcfd, 1);
if (status == 1) {
SCH_AddFileHandler(rtcfd, SCH_FILE_INPUT, refrtc_read_after_uie, instance);
rtc->polling = 0;
} else {
LOG(LOGS_INFO, "Falling back to polling for %s", path);
rtc->polling = 1;
}
return 1;
}
static void refrtc_finalise(RCL_Instance instance)
{
struct refrtc_instance *rtc;
rtc = RCL_GetDriverData(instance);
if (!rtc->polling) {
SCH_RemoveFileHandler(rtc->fd);
RTC_Linux_SwitchInterrupt(rtc->fd, 0);
}
close(rtc->fd);
Free(rtc);
}
static int refrtc_poll(RCL_Instance instance)
{
struct refrtc_instance *rtc;
struct timespec now;
time_t rtc_sec;
rtc = RCL_GetDriverData(instance);
if (!rtc->polling)
return 0;
rtc_sec = RTC_Linux_ReadTime_Now(rtc->fd, rtc->utc, NULL, &now);
if (rtc_sec == (time_t)-1)
return 0;
/* As the rtc has a resolution of 1s, only add half a second */
return refrtc_add_sample(instance, &now, rtc_sec, 500000000);
}
RefclockDriver RCL_RTC_driver = {
refrtc_initialise,
refrtc_finalise,
refrtc_poll
};

View file

@ -296,14 +296,25 @@ slew_samples
corresponding real time clock 'DMY HMS' form, taking account of corresponding real time clock 'DMY HMS' form, taking account of
whether the user runs his RTC on the local time zone or UTC */ whether the user runs his RTC on the local time zone or UTC */
static struct tm * static void
rtc_from_t(const time_t *t) rtc_from_t(const time_t *t, struct rtc_time *rtc_raw, int utc)
{ {
if (rtc_on_utc) { struct tm *rtc_tm;
return gmtime(t); if (utc) {
rtc_tm = gmtime(t);
} else { } else {
return localtime(t); rtc_tm = localtime(t);
} }
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;
} }
/* ================================================== */ /* ================================================== */
@ -341,17 +352,27 @@ rtc_from_t(const time_t *t)
*/ */
static time_t static time_t
t_from_rtc(struct tm *stm) { t_from_rtc(struct rtc_time *rtc_raw, int utc)
struct tm temp1, temp2, *tm; {
struct tm rtc_tm, temp1, temp2, *tm;
long diff; long diff;
time_t t1, t2; time_t t1, t2;
temp1 = *stm; /* 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_tm.tm_wday = 0;
temp1 = rtc_tm;
temp1.tm_isdst = 0; temp1.tm_isdst = 0;
t1 = mktime(&temp1); t1 = mktime(&temp1);
tm = rtc_on_utc ? gmtime(&t1) : localtime(&t1); tm = utc ? gmtime(&t1) : localtime(&t1);
if (!tm) { if (!tm) {
DEBUG_LOG("gmtime()/localtime() failed"); DEBUG_LOG("gmtime()/localtime() failed");
return -1; return -1;
@ -476,8 +497,8 @@ write_coefs_to_file(int valid,time_t ref_time,double offset,double rate)
/* ================================================== */ /* ================================================== */
static int int
switch_interrupts(int on_off) RTC_Linux_SwitchInterrupt(int fd, int on_off)
{ {
if (ioctl(fd, on_off ? RTC_UIE_ON : RTC_UIE_OFF, 0) < 0) { if (ioctl(fd, on_off ? RTC_UIE_ON : RTC_UIE_OFF, 0) < 0) {
LOG(LOGS_ERR, "Could not %s RTC interrupt : %s", LOG(LOGS_ERR, "Could not %s RTC interrupt : %s",
@ -508,7 +529,7 @@ RTC_Linux_Initialise(void)
} }
/* Make sure the RTC supports interrupts */ /* Make sure the RTC supports interrupts */
if (!switch_interrupts(1) || !switch_interrupts(0)) { if (!RTC_Linux_SwitchInterrupt(fd, 1) || !RTC_Linux_SwitchInterrupt(fd, 0)) {
close(fd); close(fd);
return 0; return 0;
} }
@ -557,7 +578,7 @@ RTC_Linux_Finalise(void)
/* Remove input file handler */ /* Remove input file handler */
if (fd >= 0) { if (fd >= 0) {
SCH_RemoveFileHandler(fd); SCH_RemoveFileHandler(fd);
switch_interrupts(0); RTC_Linux_SwitchInterrupt(fd, 0);
close(fd); close(fd);
/* Save the RTC data */ /* Save the RTC data */
@ -578,7 +599,7 @@ static void
measurement_timeout(void *any) measurement_timeout(void *any)
{ {
timeout_id = 0; timeout_id = 0;
switch_interrupts(1); RTC_Linux_SwitchInterrupt(fd, 1);
} }
/* ================================================== */ /* ================================================== */
@ -586,21 +607,10 @@ measurement_timeout(void *any)
static void static void
set_rtc(time_t new_rtc_time) set_rtc(time_t new_rtc_time)
{ {
struct tm rtc_tm;
struct rtc_time rtc_raw; struct rtc_time rtc_raw;
int status; int status;
rtc_tm = *rtc_from_t(&new_rtc_time); rtc_from_t(&new_rtc_time, &rtc_raw, rtc_on_utc);
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); status = ioctl(fd, RTC_SET_TIME, &rtc_raw);
if (status < 0) { if (status < 0) {
@ -750,16 +760,11 @@ process_reading(time_t rtc_time, struct timespec *system_time)
/* ================================================== */ /* ================================================== */
static void int
read_from_device(int fd_, int event, void *any) RTC_Linux_CheckInterrupt(int fd)
{ {
int status; int status;
unsigned long data; 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)); status = read(fd, &data, sizeof(data));
@ -767,64 +772,80 @@ read_from_device(int fd_, int event, void *any)
/* This looks like a bad error : the file descriptor was indicating it was /* This looks like a bad error : the file descriptor was indicating it was
* ready to read but we couldn't read anything. Give up. */ * ready to read but we couldn't read anything. Give up. */
LOG(LOGS_ERR, "Could not read flags %s : %s", CNF_GetRtcDevice(), strerror(errno)); LOG(LOGS_ERR, "Could not read flags %s : %s", CNF_GetRtcDevice(), strerror(errno));
SCH_RemoveFileHandler(fd); return -1;
switch_interrupts(0); /* Likely to raise error too, but just to be sure... */ }
close(fd);
fd = -1;
return;
}
if (skip_interrupts > 0) { if (skip_interrupts > 0) {
/* Wait for the next interrupt, this one may be bogus */ /* Wait for the next interrupt, this one may be bogus */
skip_interrupts--; skip_interrupts--;
return; return 0;
} }
if ((data & RTC_UF) == RTC_UF) { /* Update interrupt detected? */
/* Update interrupt detected */ return (data & RTC_UF) == RTC_UF;
}
/* Read RTC time, sandwiched between two polls of the system clock
so we can bound any error. */
SCH_GetLastEventTime(&sys_time, NULL, NULL); time_t
RTC_Linux_ReadTime_AfterInterrupt(int fd, int utc,
struct timespec *sys_time_cooked,
struct timespec *sys_time_raw)
{
int status;
struct rtc_time rtc_raw;
status = ioctl(fd, RTC_RD_TIME, &rtc_raw); /* Read RTC time, sandwiched between two polls of the system clock
if (status < 0) { so we can bound any error. */
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 */ SCH_GetLastEventTime(sys_time_cooked, NULL, sys_time_raw);
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_tm.tm_wday = 0;
rtc_t = t_from_rtc(&rtc_tm); 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));
return -1;
}
if (rtc_t == (time_t)(-1)) { /* Convert RTC time into a struct timespec */
error = 1; return t_from_rtc(&rtc_raw, utc);
goto turn_off_interrupt; }
}
process_reading(rtc_t, &sys_time); static void
read_from_device(int fd_, int event, void *any)
{
int status;
struct timespec sys_time;
time_t rtc_t;
int error = 0;
if (n_samples < 4) { status = RTC_Linux_CheckInterrupt(fd);
measurement_period = LOWEST_MEASUREMENT_PERIOD; if (status == -1) {
} else if (n_samples < 6) { SCH_RemoveFileHandler(fd);
measurement_period = LOWEST_MEASUREMENT_PERIOD << 1; RTC_Linux_SwitchInterrupt(fd, 0); /* Likely to raise error too, but just to be sure... */
} else if (n_samples < 10) { close(fd);
measurement_period = LOWEST_MEASUREMENT_PERIOD << 2; fd = -1;
} else if (n_samples < 14) { return;
measurement_period = LOWEST_MEASUREMENT_PERIOD << 3; } else if (status == 0) {
} else { /* Wait for the next interrupt, this one may be bogus */
measurement_period = LOWEST_MEASUREMENT_PERIOD << 4; return;
} }
rtc_t = RTC_Linux_ReadTime_AfterInterrupt(fd, rtc_on_utc, &sys_time, NULL);
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: turn_off_interrupt:
@ -836,7 +857,7 @@ turn_off_interrupt:
operating_mode = OM_NORMAL; operating_mode = OM_NORMAL;
(after_init_hook)(after_init_hook_arg); (after_init_hook)(after_init_hook_arg);
switch_interrupts(0); RTC_Linux_SwitchInterrupt(fd, 0);
timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL); timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
} }
@ -848,7 +869,7 @@ turn_off_interrupt:
DEBUG_LOG("Could not complete after trim relock due to errors"); DEBUG_LOG("Could not complete after trim relock due to errors");
operating_mode = OM_NORMAL; operating_mode = OM_NORMAL;
switch_interrupts(0); RTC_Linux_SwitchInterrupt(fd, 0);
timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL); timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
} }
@ -856,7 +877,7 @@ turn_off_interrupt:
break; break;
case OM_NORMAL: case OM_NORMAL:
switch_interrupts(0); RTC_Linux_SwitchInterrupt(fd, 0);
timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL); timeout_id = SCH_AddTimeoutByDelay((double) measurement_period, measurement_timeout, NULL);
@ -878,7 +899,7 @@ RTC_Linux_TimeInit(void (*after_hook)(void *), void *anything)
operating_mode = OM_INITIAL; operating_mode = OM_INITIAL;
timeout_id = 0; timeout_id = 0;
switch_interrupts(1); RTC_Linux_SwitchInterrupt(fd, 1);
} }
/* ================================================== */ /* ================================================== */
@ -911,6 +932,33 @@ RTC_Linux_WriteParameters(void)
return(retval); return(retval);
} }
time_t
RTC_Linux_ReadTime_Now(int fd, int utc,
struct timespec *old_sys_cooked,
struct timespec *old_sys_raw)
{
struct rtc_time rtc_raw, rtc_raw_retry;
int status;
/* 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 */
if (old_sys_raw)
LCL_ReadRawTime(old_sys_raw);
if (old_sys_cooked)
LCL_ReadCookedTime(old_sys_cooked, NULL);
return status >= 0 ? t_from_rtc(&rtc_raw, utc) : -1;
}
/* ================================================== */ /* ================================================== */
/* Try to set the system clock from the RTC, in the same manner as /* 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 /sbin/hwclock -s would do. We're not as picky about OS version
@ -920,9 +968,7 @@ RTC_Linux_WriteParameters(void)
int int
RTC_Linux_TimePreInit(time_t driftfile_time) RTC_Linux_TimePreInit(time_t driftfile_time)
{ {
int fd, status; int fd;
struct rtc_time rtc_raw, rtc_raw_retry;
struct tm rtc_tm;
time_t rtc_t; time_t rtc_t;
double accumulated_error, sys_offset; double accumulated_error, sys_offset;
struct timespec new_sys_time, old_sys_time; struct timespec new_sys_time, old_sys_time;
@ -938,66 +984,41 @@ RTC_Linux_TimePreInit(time_t driftfile_time)
return 0; /* Can't open it, and won't be able to later */ 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. rtc_t = RTC_Linux_ReadTime_Now(fd, rtc_on_utc, &old_sys_time, NULL);
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); close(fd);
if (status >= 0) { if (rtc_t != (time_t)(-1)) {
/* 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) */
/* Work out approximatation to correct time (to about the if (valid_coefs_from_file) {
nearest second) */ accumulated_error = file_ref_offset +
if (valid_coefs_from_file) { (rtc_t - file_ref_time) * 1.0e-6 * file_rate_ppm;
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 { } 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; 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 { } else {
return 0; return 0;
} }
@ -1065,7 +1086,7 @@ RTC_Linux_Trim(void)
/* And start rapid sampling, interrupts on now */ /* And start rapid sampling, interrupts on now */
SCH_RemoveTimeout(timeout_id); SCH_RemoveTimeout(timeout_id);
timeout_id = 0; timeout_id = 0;
switch_interrupts(1); RTC_Linux_SwitchInterrupt(fd, 1);
} }
return 1; return 1;

View file

@ -42,4 +42,13 @@ extern int RTC_Linux_Trim(void);
extern void RTC_Linux_CycleLogFile(void); extern void RTC_Linux_CycleLogFile(void);
extern int RTC_Linux_SwitchInterrupt(int fd, int on_off);
extern int RTC_Linux_CheckInterrupt(int fd);
extern time_t RTC_Linux_ReadTime_AfterInterrupt(int fd, int utc,
struct timespec *sys_time_cooked,
struct timespec *sys_time_raw);
extern time_t RTC_Linux_ReadTime_Now(int fd, int utc,
struct timespec *sys_time_cooked,
struct timespec *sys_time_raw);
#endif /* _GOT_RTC_LINUX_H */ #endif /* _GOT_RTC_LINUX_H */