summaryrefslogtreecommitdiffstats
path: root/refclock_phc.c
diff options
context:
space:
mode:
Diffstat (limited to 'refclock_phc.c')
-rw-r--r--refclock_phc.c232
1 files changed, 232 insertions, 0 deletions
diff --git a/refclock_phc.c b/refclock_phc.c
new file mode 100644
index 0000000..abda2e8
--- /dev/null
+++ b/refclock_phc.c
@@ -0,0 +1,232 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Miroslav Lichvar 2013, 2017, 2023
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ PTP hardware clock (PHC) refclock driver.
+
+ */
+
+#include "config.h"
+
+#include "refclock.h"
+
+#ifdef FEAT_PHC
+
+#include "sysincl.h"
+
+#include <sys/sysmacros.h>
+
+#include "array.h"
+#include "refclock.h"
+#include "hwclock.h"
+#include "local.h"
+#include "logging.h"
+#include "memory.h"
+#include "util.h"
+#include "sched.h"
+#include "sys_linux.h"
+
+struct phc_instance {
+ int fd;
+ int dev_index;
+ int mode;
+ int nocrossts;
+ int extpps;
+ int pin;
+ int channel;
+ struct timespec last_extts;
+ HCL_Instance clock;
+};
+
+/* Array of RCL_Instance with enabled extpps */
+static ARR_Instance extts_phcs = NULL;
+
+static void read_ext_pulse(int sockfd, int event, void *anything);
+
+static int phc_initialise(RCL_Instance instance)
+{
+ const char *options[] = {"nocrossts", "extpps", "pin", "channel", "clear", NULL};
+ struct phc_instance *phc;
+ int phc_fd, rising_edge;
+ struct stat st;
+ char *path, *s;
+
+ RCL_CheckDriverOptions(instance, options);
+
+ path = RCL_GetDriverParameter(instance);
+
+ phc_fd = SYS_Linux_OpenPHC(path, 0);
+ if (phc_fd < 0)
+ LOG_FATAL("Could not open PHC");
+
+ phc = MallocNew(struct phc_instance);
+ phc->fd = phc_fd;
+ if (fstat(phc_fd, &st) < 0 || !S_ISCHR(st.st_mode))
+ LOG_FATAL("Could not get PHC index");
+ phc->dev_index = minor(st.st_rdev);
+ phc->mode = 0;
+ phc->nocrossts = RCL_GetDriverOption(instance, "nocrossts") ? 1 : 0;
+ phc->extpps = RCL_GetDriverOption(instance, "extpps") ? 1 : 0;
+ UTI_ZeroTimespec(&phc->last_extts);
+ phc->clock = HCL_CreateInstance(0, 16, UTI_Log2ToDouble(RCL_GetDriverPoll(instance)),
+ RCL_GetPrecision(instance));
+
+ if (phc->extpps) {
+ s = RCL_GetDriverOption(instance, "pin");
+ phc->pin = s ? atoi(s) : 0;
+ s = RCL_GetDriverOption(instance, "channel");
+ phc->channel = s ? atoi(s) : 0;
+ rising_edge = RCL_GetDriverOption(instance, "clear") ? 0 : 1;
+
+ if (!SYS_Linux_SetPHCExtTimestamping(phc->fd, phc->pin, phc->channel,
+ rising_edge, !rising_edge, 1))
+ LOG_FATAL("Could not enable external PHC timestamping");
+
+ SCH_AddFileHandler(phc->fd, SCH_FILE_INPUT, read_ext_pulse, instance);
+
+ if (!extts_phcs)
+ extts_phcs = ARR_CreateInstance(sizeof (RCL_Instance));
+ ARR_AppendElement(extts_phcs, &instance);
+ } else {
+ phc->pin = phc->channel = 0;
+ }
+
+ RCL_SetDriverData(instance, phc);
+ return 1;
+}
+
+static void phc_finalise(RCL_Instance instance)
+{
+ struct phc_instance *phc;
+ unsigned int i;
+
+ phc = (struct phc_instance *)RCL_GetDriverData(instance);
+
+ if (phc->extpps) {
+ SCH_RemoveFileHandler(phc->fd);
+ SYS_Linux_SetPHCExtTimestamping(phc->fd, phc->pin, phc->channel, 0, 0, 0);
+
+ for (i = 0; i < ARR_GetSize(extts_phcs); i++) {
+ if ((*(RCL_Instance *)ARR_GetElement(extts_phcs, i)) == instance)
+ ARR_RemoveElement(extts_phcs, i--);
+ }
+ if (ARR_GetSize(extts_phcs) == 0) {
+ ARR_DestroyInstance(extts_phcs);
+ extts_phcs = NULL;
+ }
+ }
+
+ HCL_DestroyInstance(phc->clock);
+ close(phc->fd);
+ Free(phc);
+}
+
+static void process_ext_pulse(RCL_Instance instance, struct timespec *phc_ts)
+{
+ struct phc_instance *phc;
+ struct timespec local_ts;
+ double local_err;
+
+ phc = RCL_GetDriverData(instance);
+
+ if (UTI_CompareTimespecs(&phc->last_extts, phc_ts) == 0) {
+ DEBUG_LOG("Ignoring duplicated PHC timestamp");
+ return;
+ }
+ phc->last_extts = *phc_ts;
+
+ if (!HCL_CookTime(phc->clock, phc_ts, &local_ts, &local_err))
+ return;
+
+ RCL_AddCookedPulse(instance, &local_ts, 1.0e-9 * local_ts.tv_nsec, local_err,
+ UTI_DiffTimespecsToDouble(phc_ts, &local_ts));
+}
+
+static void read_ext_pulse(int fd, int event, void *anything)
+{
+ RCL_Instance instance;
+ struct phc_instance *phc1, *phc2;
+ struct timespec phc_ts;
+ unsigned int i;
+ int channel;
+
+ if (!SYS_Linux_ReadPHCExtTimestamp(fd, &phc_ts, &channel))
+ return;
+
+ instance = anything;
+ phc1 = RCL_GetDriverData(instance);
+
+ /* The Linux kernel (as of 6.2) has one shared queue of timestamps for all
+ descriptors of the same PHC. Search for all refclocks that expect
+ the timestamp. */
+
+ for (i = 0; i < ARR_GetSize(extts_phcs); i++) {
+ instance = *(RCL_Instance *)ARR_GetElement(extts_phcs, i);
+ phc2 = RCL_GetDriverData(instance);
+ if (!phc2->extpps || phc2->dev_index != phc1->dev_index || phc2->channel != channel)
+ continue;
+ process_ext_pulse(instance, &phc_ts);
+ }
+}
+
+#define PHC_READINGS 25
+
+static int phc_poll(RCL_Instance instance)
+{
+ struct timespec phc_ts, sys_ts, local_ts, readings[PHC_READINGS][3];
+ struct phc_instance *phc;
+ double phc_err, local_err;
+ int n_readings;
+
+ phc = (struct phc_instance *)RCL_GetDriverData(instance);
+
+ n_readings = SYS_Linux_GetPHCReadings(phc->fd, phc->nocrossts, &phc->mode,
+ PHC_READINGS, readings);
+ if (n_readings < 1)
+ return 0;
+
+ if (!HCL_ProcessReadings(phc->clock, n_readings, readings, &phc_ts, &sys_ts, &phc_err))
+ return 0;
+
+ LCL_CookTime(&sys_ts, &local_ts, &local_err);
+ HCL_AccumulateSample(phc->clock, &phc_ts, &local_ts, phc_err + local_err);
+
+ if (phc->extpps)
+ return 0;
+
+ DEBUG_LOG("PHC offset: %+.9f err: %.9f",
+ UTI_DiffTimespecsToDouble(&phc_ts, &sys_ts), phc_err);
+
+ return RCL_AddSample(instance, &sys_ts, &phc_ts, LEAP_Normal);
+}
+
+RefclockDriver RCL_PHC_driver = {
+ phc_initialise,
+ phc_finalise,
+ phc_poll
+};
+
+#else
+
+RefclockDriver RCL_PHC_driver = { NULL, NULL, NULL };
+
+#endif