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>
162 lines
3.8 KiB
C
162 lines
3.8 KiB
C
/*
|
|
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
|
|
};
|