How to Write a Reference Clock Driver
Last update: April 3, 2024 16:42 UTC (f170361b7)
Table of Contents
Description
Reference clock support maintains the fiction that the clock is actually an ordinary peer in the NTP tradition, but operating at a synthetic stratum of zero. The entire suite of algorithms used to filter the received data, select the best clocks or peers and combine them to produce a local clock correction are operative as with ordinary NTP peers. In this way, defective clocks can be detected and removed from the peer population. As no packets are exchanged with a reference clock, however, we replace the transmit, receive and packet procedures with separate code to simulate them.
Radio and modem reference clocks by convention have addresses in the form 127.127.t.u
, where t
is the clock type and u
in the range 0-3 is used to distinguish multiple instances of clocks of the same type. Most clocks require a serial or parallel port or special bus peripheral. The particular device is normally specified by adding a soft link /dev/device__d
to the particular hardware device involved, where d
corresponds to the unit number.
The best way to understand how the clock drivers work is to study the ntp_refclock.c
module and one of the drivers already implemented, such as refclock_wwvb.c
. Routines refclock_transmit()
and refclock_receive()
maintain the peer variables in a state analogous to a network peer and pass received data on through the clock filters. Routines refclock_peer()
and refclock_unpeer()
are called to initialize and terminate reference clock associations, should this ever be necessary. A set of utility routines is included to open serial devices, process sample data, edit input lines to extract embedded timestamps and to perform various debugging functions.
The main interface used by these routines is the refclockproc
structure, which contains for most drivers the decimal equivalents of the year, day, month, hour, second and millisecond/microsecond decoded from the ASCII timecode. Additional information includes the receive timestamp, exception report, statistics tallies, etc. In addition, there may be a driver-specific unit structure used for local control of the device. The support routines are passed a pointer to the peer
structure, which is used for all peer-specific processing and contains a pointer to the refclockproc
structure, which in turn contains a pointer to the unit structure, if used. A table typeunit[type][unit]
contains the peer structure pointer for each configured clock type and unit.
Many drivers support the tty_clk
and/or ppsclock
line disciplines or streams modules described in the Line Disciplines and Streams Modules page. The tty_clk
module reduces latency errors due to the operating system and serial port code in slower systems. The ppsclock
module is an interface for the PPS signal provided by some radios. It can be connected via a level converter/pulse generator described in the Gadget Box PPS Level Converter and CHU Modem page.
By convention, reference clock drivers are named in the form refclock_xxxx.c
, where xxxx
is a unique string. Each driver is assigned a unique type number, long-form driver name, short-form driver name, and device name. The existing assignments are in the Reference Clock Drivers page and its dependencies. Drivers are conditionally compiled using a unique flag string in the CLOCKDEFS
line described in the Configuration Options page.
The standard clock driver interface includes a set of common support routines some of which do such things as start and stop the device, open the serial port, and establish special functions such as PPS signal support. Other routines read and write data to the device and process time values. Most drivers need only a little customizing code to, for instance, transform idiosyncratic timecode formats to standard form, poll the device as necessary, and handle exception conditions. A standard interface is available for remote debugging and monitoring programs, such as ntpq
and xntpdc
, as well as the filegen
facility, which can be used to record device status on a continuous basis.
The interface code and this documentation have been developed over some time and required not a little hard work converting old drivers, etc. Should you find success writing a driver for a new radio or modem service, please consider contributing it to the common good.
Files Which Need to be Changed
A new reference clock implementation needs to supply, in addition to the driver itself, several changes to existing files.
./include/ntp.h
-
The reference clock type defines are used in many places. Each driver is assigned a unique type number. Unused numbers are clearly marked in the list. A unique REFCLK_xxxx
identification code should be recorded in the list opposite its assigned type number.
./libntp/clocktypes.c
-
The ./libntp/clktype
array is used by certain display functions. A unique short-form name of the driver should be entered together with its assigned identification code.
./xntpd/ntp_control.c
-
The clocktypes
array is used for certain control message displays functions. It should be initialized with the reference clock class assigned to the driver, as per the NTP specification RFC-1305. See the ./include/ntp_control.h
header file for the assigned classes.
./xntpd/refclock_conf.c
-
This file contains a list of external structure definitions which are conditionally defined. A new set of entries should be installed similar to those already in the table. The refclock_conf
array is a set of pointers to transfer vectors in the individual drivers. The external name of the transfer vector should be initialized in correspondence with the type number.
./acconfig.h
-
This is a configuration file used by the autoconfigure scheme. Add two lines in the form:
/* Define if we have a FOO clock */
#undef FOO
where FOO
is the define used to cause the driver to be included in the distribution.
./configure.in
-
This is a configuration file used by the autoconfigure scheme. Add lines similar to the following:
AC_MSG_CHECKING(FOO clock_description)
AC_ARG_ENABLE(FOO, [ --enable-FOO clock_description],
[ntp_ok=$enableval], [ntp_ok=$ntp_eac])
if test "$ntp_ok" = "yes"; then
ntp_refclock=yes
AC_DEFINE(FOO)
fi
AC_MSG_RESULT($ntp_ok)
(Note that $ntp_eac
is the value from --{dis,en}able-all-clocks
for non-PARSE clocks and $ntp_eacp
is the value from --{dis,en}able-parse- clocks
for PARSE clocks. See the documentation on the autoconf and automake tools from the GNU distributions.)
./xntpd/Makefile.am
-
This is the makefile prototype used by the autoconfigure scheme. Add the driver file name to the entries already in the xntpd_SOURCES
list.
Patches to automake-1.0
are required for the autoconfigure scripts to work properly. The file automake-1.0.patches
can be used for this purpose.
./xntpd/Makefile.am
-
Do the following sequence of commands:
automake
autoconf
autoheader
configure
or simply run make
, which will do this command sequence automatically.
Interface Routine Overview
refclock_newpeer
- initialize and start a reference clock
-
This routine allocates and initializes the interface structure which supports a reference clock in the form of an ordinary NTP peer. A driver-specific support routine completes the initialization, if used. Default peer variables which identify the clock and establish its reference ID and stratum are set here. It returns one if success and zero if the clock address is invalid or already running, insufficient resources are available or the driver declares a bum rap.
refclock_unpeer
- shut down a clock
-
This routine is used to shut down a clock and return its resources to the system.
refclock_transmit
- simulate the transmit procedure
-
This routine implements the NTP transmit procedure for a reference clock. This provides a mechanism to call the driver at the NTP poll interval, as well as provides a reachability mechanism to detect a broken radio or other madness.
refclock_sample
- process a pile of samples from the clock
-
This routine converts the timecode in the form days, hours, minutes, seconds, milliseconds/microseconds to internal timestamp format. It then calculates the difference from the receive timestamp and assembles the samples in a shift register. It implements a recursive median filter to suppress spikes in the data, as well as determine a rough dispersion estimate. A configuration constant time adjustment fudgetime1
can be added to the final offset to compensate for various systematic errors. The routine returns one if success and zero if failure due to invalid timecode data or very noisy offsets.
Note that no provision is included for the year, as provided by some (but not all) radio clocks. Ordinarily, the year is implicit in the Unix file system and hardware/software clock support, so this is ordinarily not a problem. Nevertheless, the absence of the year should be considered more a bug than a feature and may be supported in future.
refclock_receive
- simulate the receive and packet procedures
-
This routine simulates the NTP receive and packet procedures for a reference clock. This provides a mechanism in which the ordinary NTP filter, selection and combining algorithms can be used to suppress misbehaving radios and to mitigate between them when more than one is available for backup.
refclock_gtlin
- groom next input line and extract timestamp
-
This routine processes the timecode received from the clock and removes the parity bit and control characters. If a timestamp is present in the timecode, as produced by the tty_clk
line discipline/streams module, it returns that as the timestamp; otherwise, it returns the buffer timestamp. The routine return code is the number of characters in the line.
refclock_open
- open serial port for reference clock
-
This routine opens a serial port for I/O and sets default options. It returns the file descriptor if success and zero if failure.
refclock_ioctl
- set serial port control functions
-
This routine attempts to hide the internal, system-specific details of serial ports. It can handle POSIX (termios
), SYSV (termio
) and BSD (sgtty
) interfaces with varying degrees of success. The routine sets up the tty_clk, chu_clk
and ppsclock
streams module/line discipline, if compiled in the daemon and requested in the call. The routine returns one if success and zero if failure.
refclock_control
- set and/or return clock values
-
This routine is used mainly for debugging. It returns designated values from the interface structure that can be displayed using xntpdc and the clockstat command. It can also be used to initialize configuration variables, such as fudgetimes, fudgevalues, reference ID
and stratum
.
refclock_buginfo
- return debugging info
-
This routine is used mainly for debugging. It returns designated values from the interface structure that can be displayed using xntpdc
and the clkbug
command.