| [PATCH] refclock: add SENSORS driver for OpenBSD timedelta sensors |
[ Thread Index |
Date Index
| More chrony.tuxfamily.org/chrony-dev Archives
]
- Subject: [PATCH] refclock: add SENSORS driver for OpenBSD timedelta sensors
- From: Atanas Vladimirov <vladimirov.atanas@xxxxxxxxx>
- Date: Mon, 13 Jul 2026 22:52:19 +0300
OpenBSD does not provide the RFC 2783 PPS API. Instead, serial line
time sources attached with ldattach(8) (e.g. nmea(4)) and dedicated
radio clock drivers export a timedelta sensor through the hw.sensors
sysctl framework, containing the offset of the system clock relative
to the reference. When the line discipline is configured to timestamp
a PPS signal on a modem control line (e.g. ldattach -t dcd nmea), the
kernel timestamps the pulse at interrupt time, making the sensor
accurate to a few microseconds.
---
configure | 14 ++-
doc/chrony.conf.adoc | 20 ++++-
refclock.c | 3 +
refclock_sensors.c | 198 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 233 insertions(+), 2 deletions(-)
create mode 100644 refclock_sensors.c
diff --git a/configure b/configure
index 4c385af..d1f6b15 100755
--- a/configure
+++ b/configure
@@ -514,7 +514,7 @@ fi
if [ $feat_refclock = "1" ]; then
add_def FEAT_REFCLOCK
EXTRA_OBJECTS="$EXTRA_OBJECTS refclock.o refclock_phc.o refclock_pps.o \
- refclock_rtc.o refclock_shm.o refclock_sock.o"
+ refclock_rtc.o refclock_sensors.o refclock_shm.o refclock_sock.o"
fi
MYCC="$CC"
@@ -787,6 +787,18 @@ then
add_def FEAT_PPS
fi
+if [ $feat_refclock = "1" ] && \
+ test_code 'OpenBSD sensors' 'sys/types.h sys/time.h sys/sysctl.h sys/sensors.h' '' '' '
+ struct sensordev sd;
+ struct sensor s;
+ int mib[5] = { CTL_HW, HW_SENSORS, 0, SENSOR_TIMEDELTA, 0 };
+ size_t len = sizeof (s);
+ return sysctl(mib, 5, &s, &len, NULL, 0) + sizeof (sd.xname) +
+ SENSOR_S_OK + SENSOR_FINVALID;'
+then
+ add_def FEAT_SENSORS
+fi
+
if [ $feat_droproot = "1" ] && [ $try_libcap = "1" ] && \
test_code \
libcap \
diff --git a/doc/chrony.conf.adoc b/doc/chrony.conf.adoc
index 414c9c5..7b02436 100644
--- a/doc/chrony.conf.adoc
+++ b/doc/chrony.conf.adoc
@@ -500,7 +500,7 @@ the driver-specific parameter using the *:* character.
+
This directive can be used multiple times to specify multiple reference clocks.
+
-There are five drivers included in *chronyd*:
+There are six drivers included in *chronyd*:
+
*PPS*:::
Driver for the kernel PPS (pulse per second) API. The parameter is the path to
@@ -636,6 +636,24 @@ Examples:
refclock RTC /dev/rtc0:utc
----
+
+*SENSORS*:::
+Driver for timedelta sensors provided by the OpenBSD *hw.sensors* framework.
+The parameter is the name of the sensor device (e.g. _nmea0_) whose first
+timedelta sensor should be used as a time source. Timedelta sensors are
+provided by serial line time sources attached with *ldattach(8)*, such as
+*nmea(4)*, and by dedicated radio clock devices. When the line discipline is
+configured to timestamp a PPS signal (e.g. *ldattach -t dcd nmea*), the kernel
+timestamps the pulse at interrupt time and the sensor provides a PPS-corrected
+offset of the system clock. Samples are ignored while the kernel marks the
+sensor as invalid or its status is not OK (e.g. the receiver lost its fix, or
+the PPS signal is missing). This driver is available only on OpenBSD.
++
+Examples:
++
+----
+refclock SENSORS nmea0 refid GPS precision 1e-7 prefer
+----
++
{blank}::
The *refclock* directive supports the following options:
+
diff --git a/refclock.c b/refclock.c
index d73e3c0..cab49cf 100644
--- a/refclock.c
+++ b/refclock.c
@@ -49,6 +49,7 @@ extern RefclockDriver RCL_SOCK_driver;
extern RefclockDriver RCL_PPS_driver;
extern RefclockDriver RCL_PHC_driver;
extern RefclockDriver RCL_RTC_driver;
+extern RefclockDriver RCL_SENSORS_driver;
struct FilterSample {
double offset;
@@ -163,6 +164,8 @@ RCL_AddRefclock(RefclockParameters *params)
inst->driver = &RCL_PHC_driver;
} else if (strcmp(params->driver_name, "RTC") == 0) {
inst->driver = &RCL_RTC_driver;
+ } else if (strcmp(params->driver_name, "SENSORS") == 0) {
+ inst->driver = &RCL_SENSORS_driver;
} else {
LOG_FATAL("unknown refclock driver %s", params->driver_name);
}
diff --git a/refclock_sensors.c b/refclock_sensors.c
new file mode 100644
index 0000000..51c8c91
--- /dev/null
+++ b/refclock_sensors.c
@@ -0,0 +1,198 @@
+/*
+ chronyd/chronyc - Programs for keeping computer clocks accurate.
+
+ **********************************************************************
+ * Copyright (C) Atanas Vladimirov 2026
+ *
+ * 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.
+ *
+ **********************************************************************
+
+ =======================================================================
+
+ OpenBSD hw.sensors timedelta refclock driver.
+
+ Timedelta sensors are provided by serial line time sources attached
+ with ldattach(8), such as nmea(4), msts(4) and endrun(4), and by
+ dedicated radio clock devices like udcf(4) and mbg(4). When the
+ nmea(4) line discipline is timestamped by a PPS signal on the DCD
+ line (ldattach -t dcd), the kernel timestamps the pulse at interrupt
+ time and the sensor is accurate to a few microseconds.
+
+ The sensor value is the difference between the system time and the
+ reference time in nanoseconds (positive if the system clock is ahead)
+ and the sensor timestamp is the system time of the measurement.
+
+ */
+
+#include "config.h"
+
+#include "refclock.h"
+
+#ifdef FEAT_SENSORS
+
+#include "sysincl.h"
+
+#include <sys/sysctl.h>
+#include <sys/sensors.h>
+
+#include "logging.h"
+#include "memory.h"
+#include "util.h"
+
+struct sensors_instance {
+ int dev;
+ char *name;
+ struct timeval last;
+};
+
+static int get_sensordev(int dev, struct sensordev *sd)
+{
+ int mib[3] = { CTL_HW, HW_SENSORS, dev };
+ size_t len = sizeof (*sd);
+
+ return sysctl(mib, 3, sd, &len, NULL, 0);
+}
+
+static int find_sensordev(const char *name)
+{
+ struct sensordev sd;
+ int dev;
+
+ for (dev = 0; ; dev++) {
+ if (get_sensordev(dev, &sd) < 0) {
+ if (errno == ENOENT) /* past the last device */
+ break;
+ if (errno == ENXIO) /* empty slot, keep scanning */
+ continue;
+ return -1;
+ }
+ if (strcmp(sd.xname, name) == 0)
+ return dev;
+ }
+
+ return -1;
+}
+
+/* Read the timedelta sensor after verifying the device name and presence of
+ the sensor, as the kernel reuses indices of detached devices and a
+ different device could take the place of the configured device */
+static int read_timedelta(int dev, const char *name, struct sensor *s)
+{
+ struct sensordev sd;
+ int mib[5] = { CTL_HW, HW_SENSORS, dev, SENSOR_TIMEDELTA, 0 };
+ size_t len = sizeof (*s);
+
+ if (dev < 0 || get_sensordev(dev, &sd) < 0 ||
+ strcmp(sd.xname, name) != 0 || sd.maxnumt[SENSOR_TIMEDELTA] < 1)
+ return -1;
+
+ return sysctl(mib, 5, s, &len, NULL, 0);
+}
+
+static int sensors_initialise(RCL_Instance instance)
+{
+ struct sensors_instance *sensors;
+ struct sensor s;
+ char *name;
+ int dev;
+
+ RCL_CheckDriverOptions(instance, NULL);
+
+ name = RCL_GetDriverParameter(instance);
+ if (!name || name[0] == '\0')
+ LOG_FATAL("Missing sensor device");
+
+ dev = find_sensordev(name);
+ if (dev < 0)
+ LOG_FATAL("Could not find sensor device %s", name);
+
+ if (read_timedelta(dev, name, &s) < 0)
+ LOG_FATAL("Could not read timedelta sensor of device %s", name);
+
+ sensors = MallocNew(struct sensors_instance);
+ sensors->dev = dev;
+ sensors->name = name;
+ sensors->last.tv_sec = 0;
+ sensors->last.tv_usec = 0;
+
+ RCL_SetDriverData(instance, sensors);
+ return 1;
+}
+
+static void sensors_finalise(RCL_Instance instance)
+{
+ Free(RCL_GetDriverData(instance));
+}
+
+static int sensors_poll(RCL_Instance instance)
+{
+ struct sensors_instance *sensors;
+ struct timespec sys_ts, ref_ts;
+ struct sensor s;
+ double offset;
+
+ sensors = (struct sensors_instance *)RCL_GetDriverData(instance);
+
+ if (read_timedelta(sensors->dev, sensors->name, &s) < 0) {
+ /* the device may have been reattached with a different index */
+ sensors->dev = find_sensordev(sensors->name);
+ DEBUG_LOG("Could not read sensor %s", sensors->name);
+ return 0;
+ }
+
+ /* Ignore the sensor while it is invalid or degraded */
+ if (s.flags & SENSOR_FINVALID || s.status != SENSOR_S_OK) {
+ DEBUG_LOG("Ignoring sensor %s flags=%x status=%d",
+ sensors->name, (unsigned int)s.flags, (int)s.status);
+ return 0;
+ }
+
+ /* Wait for a new measurement */
+ if (s.tv.tv_sec == sensors->last.tv_sec &&
+ s.tv.tv_usec == sensors->last.tv_usec)
+ return 0;
+ sensors->last = s.tv;
+
+ if (!UTI_IsTimevalNormal(&s.tv)) {
+ DEBUG_LOG("Invalid timestamp from sensor %s", sensors->name);
+ return 0;
+ }
+
+ UTI_TimevalToTimespec(&s.tv, &sys_ts);
+
+ /* The sensor value is system time minus reference time in nanoseconds */
+ offset = -(double)s.value / 1.0e9;
+
+ if (!UTI_IsTimeOffsetSane(&sys_ts, offset))
+ return 0;
+
+ UTI_AddDoubleToTimespec(&sys_ts, offset, &ref_ts);
+
+ DEBUG_LOG("SENSORS %s sample offset=%.9f", sensors->name, offset);
+
+ return RCL_AddSample(instance, &sys_ts, &ref_ts, LEAP_Normal, 1);
+}
+
+RefclockDriver RCL_SENSORS_driver = {
+ sensors_initialise,
+ sensors_finalise,
+ sensors_poll
+};
+
+#else
+
+RefclockDriver RCL_SENSORS_driver = { NULL, NULL, NULL };
+
+#endif
--
2.43.0
--
To unsubscribe email chrony-dev-request@xxxxxxxxxxxxxxxxxxxx with "unsubscribe" in the subject.
For help email chrony-dev-request@xxxxxxxxxxxxxxxxxxxx with "help" in the subject.
Trouble? Email listmaster@xxxxxxxxxxxxxxxxxxxx.