diff --git a/configure b/configure index d3e775f..dbdd72d 100755 --- a/configure +++ b/configure @@ -524,7 +524,8 @@ fi if [ $feat_refclock = "1" ]; then add_def FEAT_REFCLOCK - EXTRA_OBJECTS="$EXTRA_OBJECTS refclock.o refclock_phc.o refclock_pps.o refclock_shm.o refclock_sock.o" + EXTRA_OBJECTS="$EXTRA_OBJECTS refclock.o refclock_phc.o refclock_pps.o \ + refclock_rtc.o refclock_shm.o refclock_sock.o" fi MYCC="$CC" diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc index 42c65cb..16967d5 100644 --- a/doc/chrony.conf.adoc +++ b/doc/chrony.conf.adoc @@ -478,7 +478,7 @@ the driver-specific parameter using the *:* character. + 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*::: Driver for the kernel PPS (pulse per second) API. The parameter is the path to @@ -596,6 +596,23 @@ refclock PHC /dev/ptp1:nocrossts poll 3 pps refclock PHC /dev/ptp2:extpps:pin=1 width 0.2 poll 2 ---- + +*RTC*::: +Driver for using the Real Time Clock (RTC) as a reference clock. The parameter +is the path to the RTC character device which should be used as a time source. +This driver cannot be used together with the <> or +<> directive. The driver supports the following option: ++ +*utc*:::: +Assume that RTC keeps Universal Coordinated Time (UTC) instead of local +time. +{blank}::: ++ +Examples: ++ +---- +refclock RTC /dev/rtc0:utc +---- ++ {blank}:: The *refclock* directive supports the following options: + @@ -2206,7 +2223,8 @@ The directive takes no arguments. It is equivalent to specifying the *-u* switch to the Linux *hwclock* program. + Note that this setting is overridden by the <> file -and is not relevant for the <> directive. +and is not relevant for the <> directive or when the RTC +is used as reference clock. [[rtcsync]]*rtcsync*:: The *rtcsync* directive enables a mode where the system time is periodically diff --git a/refclock.c b/refclock.c index 4a466bd..a0e716c 100644 --- a/refclock.c +++ b/refclock.c @@ -48,6 +48,7 @@ extern RefclockDriver RCL_SHM_driver; extern RefclockDriver RCL_SOCK_driver; extern RefclockDriver RCL_PPS_driver; extern RefclockDriver RCL_PHC_driver; +extern RefclockDriver RCL_RTC_driver; struct FilterSample { double offset; @@ -160,6 +161,8 @@ RCL_AddRefclock(RefclockParameters *params) inst->driver = &RCL_PPS_driver; } else if (strcmp(params->driver_name, "PHC") == 0) { inst->driver = &RCL_PHC_driver; + } else if (strcmp(params->driver_name, "RTC") == 0) { + inst->driver = &RCL_RTC_driver; } else { LOG_FATAL("unknown refclock driver %s", params->driver_name); } diff --git a/refclock_rtc.c b/refclock_rtc.c new file mode 100644 index 0000000..2e0c091 --- /dev/null +++ b/refclock_rtc.c @@ -0,0 +1,178 @@ +/* + chronyd/chronyc - Programs for keeping computer clocks accurate. + + ********************************************************************** + * Copyright (C) Uwe Kleine-König, Pengutronix 2021 + * Copyright (C) Ahmad Fatoum, Pengutronix 2024 + * + * 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. + * + ********************************************************************** + + ======================================================================= + + RTC refclock driver. + + */ + +#include + +#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" + +#ifdef FEAT_RTC + +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; + + RCL_UpdateReachability(instance); + + status = RCL_AddSample(instance, now, &rtc_ts, LEAP_Normal); + + 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); + struct timespec now; + time_t rtc_sec; + int status; + + status = RTC_Linux_CheckInterrupt(rtcfd); + if (status < 0) { + 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_ReadTimeAfterInterrupt(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}; + struct refrtc_instance *rtc; + int rtcfd, status; + const char *path; + + RCL_CheckDriverOptions(instance, options); + + if (CNF_GetRtcSync() || CNF_GetRtcFile()) + LOG_FATAL("RTC refclock 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 : %s", path, strerror(errno)); + + /* 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) { + 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_ReadTimeNow(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 +}; + +#else + +RefclockDriver RCL_RTC_driver = { NULL, NULL, NULL }; + +#endif