/* 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 #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 };