]> git.ipfire.org Git - thirdparty/chrony.git/commitdiff
refclock: add new refclock for RTCs
authorUwe Kleine-König <u.kleine-koenig@pengutronix.de>
Wed, 30 Jun 2021 14:47:21 +0000 (16:47 +0200)
committerMiroslav Lichvar <mlichvar@redhat.com>
Wed, 4 Dec 2024 14:58:10 +0000 (15:58 +0100)
This refclock uses an RTC as reference source. If the RTC doesn't
support reporting an update event this source is quite coarse as it
usually needs a slow bus access to be read and has a precision of only
one second. If reporting an update event is available, the time is read
just after such an event which improves precision.

Depending on hardware capabilities you might want to combine it with a
PPS reference clock sourced from the same chip.

Note that you can enable UIE emulation in the Linux kernel to make a RTC
without interrupt support look like one with irqs in return for some
system and bus overhead.

Co-authored-by: Ahmad Fatoum <a.fatoum@pengutronix.de>
configure
doc/chrony.conf.adoc
refclock.c
refclock_rtc.c [new file with mode: 0644]

index d3e775f3feed2d78d8f7e46b72d0e0490307f019..dbdd72d6684f080654e4082b8a39f60aa5605d07 100755 (executable)
--- 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"
index 42c65cbdcf74a5c86c54079c4caeb132a04d87f5..16967d5a60b90f1bfb9b878d64320a05535bb158 100644 (file)
@@ -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 <<rtcfile,*rtcfile*>> or
+<<rtcsync,*rtcsync*>> 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 <<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 reference clock.
 
 [[rtcsync]]*rtcsync*::
 The *rtcsync* directive enables a mode where the system time is periodically
index 4a466bd524fd1c413c0b527932ea7836ad9be16e..a0e716c492ad40ffdce325dad6100b5752e38e8b 100644 (file)
@@ -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 (file)
index 0000000..2e0c091
--- /dev/null
@@ -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 <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"
+
+#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