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. --- configure | 2 +- refclock.c | 3 + refclock_rtc.c | 151 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 refclock_rtc.c diff --git a/configure b/configure index 5b8948f0d368..769b534d862d 100755 --- a/configure +++ b/configure @@ -517,7 +517,7 @@ 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_shm.o refclock_sock.o refclock_rtc.o" fi MYCC="$CC" diff --git a/refclock.c b/refclock.c index 23a495a1986f..a95f7fd59902 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; @@ -173,6 +174,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 000000000000..401ffbac3363 --- /dev/null +++ b/refclock_rtc.c @@ -0,0 +1,151 @@ +/* + 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 "refclock.h" +#include "local.h" + +#include "logging.h" +#include "memory.h" +#include "sched.h" +#include "util.h" + +struct refrtc_instance { + int rtcfd; + int polling; +}; + +static int refrtc_read_data(RCL_Instance instance, struct refrtc_instance *rtc, double offset) +{ + struct rtc_time rtc_t = {0}; + struct tm rtc_tm = {0}; + time_t rtc_sec; + struct timespec rtc_ts = {0}; + struct timespec now; + int status; + + status = ioctl(rtc->rtcfd, RTC_RD_TIME, &rtc_t); + LCL_ReadRawTime(&now); + if (status < 0) + return 0; + + rtc_tm.tm_sec = rtc_t.tm_sec; + rtc_tm.tm_min = rtc_t.tm_min; + rtc_tm.tm_hour = rtc_t.tm_hour; + rtc_tm.tm_mday = rtc_t.tm_mday; + rtc_tm.tm_mon = rtc_t.tm_mon; + rtc_tm.tm_year = rtc_t.tm_year; + + rtc_sec = mktime(&rtc_tm); + if (rtc_sec == (time_t)-1) + return 0; + + status = RCL_AddSample(instance, &now, rtc_sec - now.tv_sec - 1e-9 * now.tv_nsec + offset, LEAP_Normal); + if (status == 0) { + LOG(LOGS_INFO, "%s:%d: status = %d, rtc_sec = %lu, now=%lu.%09lu", + __func__, __LINE__, status, rtc_sec, now.tv_sec, now.tv_nsec); + } + + 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); + unsigned long rtcdata; + ssize_t ret; + + ret = read(rtc->rtcfd, &rtcdata, sizeof(rtcdata)); + if (ret < sizeof(rtcdata)) { + LOG(LOGS_ERR, "%s:%d: reading from RTC returned %zd", __func__, __LINE__, ret); + return; + } + + if (rtcdata & (RTC_IRQF | RTC_UF) != (RTC_IRQF | RTC_UF)) + LOG(LOGS_ERR, "unexpected rtc data: 0x%lx", rtcdata); + else + LOG(LOGS_DEBUG, "read rtc data 0x%lx", rtcdata); + + refrtc_read_data(instance, rtc, 0.0); +} + +static int refrtc_initialise(RCL_Instance instance) { + int rtcfd; + const char *path; + struct refrtc_instance *rtc; + int status; + + path = RCL_GetDriverParameter(instance); + + rtcfd = open(path, O_RDONLY); + if (rtcfd < 0) + LOG_FATAL("Could not open RTC device %s : %m", path); + + rtc = MallocNew(struct refrtc_instance); + rtc->rtcfd = rtcfd; + + RCL_SetDriverData(instance, rtc); + + /* Try to enable update interrupts */ + status = ioctl(rtc->rtcfd, RTC_UIE_ON, 0); + if (status == 0) { + SCH_AddFileHandler(rtc->rtcfd, SCH_FILE_INPUT, refrtc_read_after_uie, instance); + rtc->polling = 0; + } else { + LOG(LOGS_INFO, "RTC %p doesn't support update IRQs", path); + rtc->polling = 1; + } + + return 1; +} + +static void refrtc_finalise(RCL_Instance instance) +{ + struct refrtc_instance *rtc; + + rtc = RCL_GetDriverData(instance); + + if (!rtc->polling) + ioctl(rtc->rtcfd, RTC_UIE_OFF, 0); + + close(rtc->rtcfd); + Free(rtc); +} + +static int refrtc_poll(RCL_Instance instance) +{ + struct refrtc_instance *rtc; + + rtc = RCL_GetDriverData(instance); + + if (!rtc->polling) + return 0; + + /* As the rtc has a resolution of 1s only add half a second */ + return refrtc_read_data(instance, rtc, 0.5); +} + +RefclockDriver RCL_RTC_driver = { + refrtc_initialise, + refrtc_finalise, + refrtc_poll +}; -- 2.30.2 -- To unsubscribe email chrony-dev-requ...@chrony.tuxfamily.org with "unsubscribe" in the subject. For help email chrony-dev-requ...@chrony.tuxfamily.org with "help" in the subject. Trouble? Email listmas...@chrony.tuxfamily.org.