How to Write a Reference Clock Driver


Description

Reference clock support is provided here by maintaining the fiction that the clock is actually a peer. As no packets are exchanged with a reference clock, however, we replace the transmit, receive and packet procedures with separate code to simulate them. Routines refclock_transmit() and refclock_receive() maintain the peer variables in a state analogous to an actual peer and pass reference clock data on through the filters. Routines refclock_peer() and refclock_unpeer() are called to initialize and terminate reference clock associations. A set of utility routines is included to open serial devices, process sample data, edit input lines to extract embedded timestamps and to peform various debugging functions.

The main interface used by these routines is the refclockproc structure, which contains for most drivers the decimal equivalants 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 containes a pointer to the unit structure, if used. In addition, some routines expect an address in the dotted quad form 127.127.t.u, where t is the clock type and u the unit. A table typeunit[type][unit] contains the peer structure pointer for each configured clock type and unit.

Radio and modem clocks by convention have addresses in the form 127.127.t.u, where t is the clock type and u is a unit number in the range 0-3 used to distinguish multiple instances of clocks of the same type. Most of these clocks require support in the form of a serial 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 correspond to the unit number above. Following is a list showing the type and title of each driver currently implemented. Click on a selected type for specific description and configuration documentation. For those drivers without specific documentation, please contact the author listed in the copyright page.

Most drivers support the PPS signal provided by some radios and connected via a level converted described in the Line Disciplines and Streams Modules page. The signal is captured using a separate, dedicated serial port and the tty_clk line discipline/streams modules described in the kernel directory. For the highest precision, the signal is captured using the carrier-detect line of the same serial port using the ppsclock streams module described in the ppsclock directory.

Probably the best way to build a new driver is to modify an existing one already in the distribution. All reference clocks are named in the format refclock_xxxx.c, where xxxx is a unique string. Each driver is assigned a unique type number taken from the list in the refclocks page. Most drivers need a serial port to communicate with the radio or modem clock. By convention, the device name for this port is /dev/yyy%d, where yyy is a unique string and %d is the unit number. In addition, each driver is assigned names to be used in monitoring and debugging.

Drivers are conditionally compiled using a define and configuration file flag string unique to each driver. They include a standard interface to 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.

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 REFCLOCK_xxxx identification code should be recorded in the list opposite its assigned type number.

./lib/clocktypes.c
The clktypes array is used by certain display functions. A unique short-form name of the driver should be enterred opposite 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. See the ntp_control.h header file for the assigned classes.

./xntpd/refclock_conf.c
This file contains a list of defines and 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.

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, miinutes, 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.

This interface is needed to allow for clocks (e. g. parse) that can provide the correct offset including year information (though NTP usually gives up on offsets greater than 1000 seconds).

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.

David L. Mills (mills@udel.edu)