howto.htm   [plain text]


<html><head><title>
How to Write a Reference Clock Driver
</title></head><body><h3>
How to Write a Reference Clock Driver
</h3>

<img align=left src=pic/pogo4.gif><a href=http://www.eecis.udel.edu/~mills/pictures.htm>from <i>Pogo</i>, Walt Kelly</a>

<p>You need a little magic.
<br clear=left><hr>

<h4>Description</h4>

<p>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 operate just like 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,
the transmit, receive and packet procedures are replaced with separate
code to simulate them.

<p>Radio and modem reference clocks by convention have addresses in the
form <tt>127.127.<i>t</i>.<i>u</i></tt>, where <i>t</i> is the clock
type and <i>u</i> in the range 0-3 is used to distinguish multiple
instances of clocks of the same type. Most clocks require a serial port
or special bus peripheral. The particular device is normally specified
by adding a soft link <tt>/dev/device<i>d</i>d</tt> to the particular
hardware device involved, where <tt><i>d</i></tt> corresponds to the
unit number.

<p>The best way to understand how the clock drivers work is to study the
<tt>ntp_refclock.c</tt> module and one of the drivers already
implemented, such as <tt>refclock_wwvb.c</tt>. Routines
<tt>refclock_transmit()</tt> and <tt>refclock_receive()</tt> maintain
the peer variables in a state analogous to a network peer and pass
received data on through the clock filters. Routines
<tt>refclock_peer()</tt> and <tt>refclock_unpeer()</tt> 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.

<p>The main interface used by these routines is the
<tt>refclockproc</tt> 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. The support routines are passed a pointer to the
<tt>peer</tt> structure, which is used for all peer-specific processing
and contains a pointer to the <tt>refclockproc</tt> structure, which in
turn contains a pointer to the unit structure, if used. For legacy
purposes, a table <tt>typeunit[type][unit]</tt> contains the peer
structure pointer for each configured clock type and unit.

<p>The reference clock interface supports auxiliary functions to support
in-stream timestamping, pulse-per-second (PPS) interfacing and precision
time kernel support. In most cases the drivers do not need to be aware
of them, since they are detected at autoconfigure time and loaded
automatically when the device is opened. These include the
<tt>tty_clk</tt> and <tt>ppsclock</tt> STREAMS modules and
<tt>ppsapi</tt> PPS interface described in the <a href="ldisc.htm">Line
Disciplines and Streams Modules</a> page. The <tt>tty_clk</tt> module
reduces latency errors due to the operating system and serial port code
in slower systems. The <tt>ppsclock</tt> module is an interface for the
PPS signal provided by some radios. The <tt>ppsapi</tt> PPS interface
replaces the <tt>ppsclock</tt> STREAMS module and is expected to become
the IETF standard cross-platform interface for PPS signals. In either
case, the PPS signal can be connected via a level converter/pulse
generator described in the <a href = "gadget.htm"> Gadget Box PPS Level
Converter and CHU Modem</a> page.

<p>By convention, reference clock drivers are named in the form
<tt>refclock_<i>xxxx</i>.c</tt>, where <i>xxxx</i> 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 <a href="refclock.htm"> Reference Clock Drivers</a> page
and its dependencies. All drivers supported by the particular hardware
and operating system are automatically detected in the autoconfigure
phase and conditionally compiled. They are configured when the daemon is
started according to the configuration file, as described in the <a
href="config.htm"> Configuration Options </a> page.

<p>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 <tt>ntpq</tt> and <tt>ntpdc</tt>, as well as
the <tt>filegen</tt> facility, which can be used to record device
status on a continuous basis.

<p>The general organization of a typical clock driver includes a
receive-interrupt routine to read a timecode from the I/O buffer and
convert to internal format, generally in days, hours, minutes, seconds
and fraction. Some timecode formats include provisions for leap-second
warning and determine the clock hardware and software health. The
interrupt routine then calls <tt>refclock_process()</tt> with these data
and the timestamp captured at the on-time character of the timecode.
This routine saves each sample as received in a circular buffer, which
can store from a few up to 60 samples, in cases where the timecodes
arrive one per second.

<p>The <tt>refclock_transmit()</tt> routine in the interface is called
by the system at intervals defined by the poll interval in the peer
structure, generally 64 s. This routine in turn calls the transmit poll
routine in the driver. In the intended design, the driver calls the
<tt>refclock_receive()</tt> to process the offset samples that have
accumulated since the last poll and produce the final offset and
variance. The samples are processed by recursively discarding median
outlyers until about 60 percent of samples remain, then averaging the
surviving samples. When a reference clock must be explicitly polled to
produce a timecode, the driver can reset the poll interval so that the
poll routine is called a specified number of times at 1-s intervals.

<p>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. Send the
driver file itself and patches for the other files to Dave Mills
(mills@udel.edu).

<h4>Conventions, Fudge Factors and Flags</h4>

<p>Most drivers support manual or automatic calibration for systematic
offset bias using values encoded in the <tt>fudge</tt> configuration
command. By convention, the <tt>time1</tt> value defines the calibration
offset in seconds. For those drivers that support statistics collection
using the <tt>filegen</tt> utility and the <tt>clockstats</tt> file, the
<tt>flag4</tt> switch enables the utility. When a PPS signal is
available, a special automatic calibration facility is provided. If the
<tt>flag1</tt> switch is set and the PPS signal is actively disciplining
the system time, the calibration value is automatically adjusted to
maintain a residual offset of zero. Should the PPS signal or the prefer
peer fail, the adjustment is frozen and the remaining drivers continue
to discipline the system clock with a minimum of residual error.

<h4>Files Which Need to be Changed</h4>

<p>A new reference clock implementation needs to supply, in addition to
the driver itself, several changes to existing files.

<dl>

<dt><tt>./include/ntp.h</tt>
<dd>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 <tt>REFCLK_<i>xxxx</i></tt>
identification code should be recorded in the list opposite its assigned
type number.

<p><dt><tt>./libntp/clocktypes.c</tt>
<dd>The <tt>./libntp/clktype</tt> array is used by certain display
functions. A unique short-form name of the driver should be entered
together with its assigned identification code.

<p><dt><tt>./ntpd/ntp_control.c</tt>
<dd>The <tt>clocktypes</tt> 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 <tt>./include/ntp_control.h</tt> header file for
the assigned classes.

<p><dt><tt>./ntpd/refclock_conf.c</tt>
<dd>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 <tt>refclock_conf</tt>
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.

<p><dt><tt>./acconfig.h</tt>
<dd>This is a configuration file used by the autoconfigure scheme. Add
two lines in the form:

<p><pre>
  /* Define if we have a FOO clock */
  #undef FOO
</pre>

<p>where FOO is the define used to cause the driver to be included in
the distribution.

<p><dt><tt>./configure.in</tt>
<dd>This is a configuration file used by the autoconfigure scheme. Add
lines similar to the following:

<p><pre>
  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)
</pre>

<p>(Note that <tt>$ntp_eac</tt> is the value from <tt>--
{dis,en}able-all-clocks</tt> for non-PARSE clocks and
<tt>$ntp_eacp</tt> is the value from <tt>--{dis,en}able-parse-
clocks</tt> for PARSE clocks. See the documentation on the autoconf
and automake tools from the GNU distributions.)

<p><dt><tt>./ntpd/Makefile.am</tt>
<dd><p>This is the makefile prototype used by the autoconfigure scheme.
Add the driver file name to the entries already in the
<tt>ntpd_SOURCES</tt> list.

<p>Patches to <tt>automake-1.0</tt> are required for the
autoconfigure scripts to work properly. The file <tt>automake-
1.0.patches</tt> can be used for this purpose.

<p><dt><tt>./ntpd/Makefile.am</tt>
<dd>Do the following sequence of commands:

<p><pre>
  automake
  autoconf
  autoheader
  configure
</pre>

<p>or simply run <tt>make</tt>, which will do this command sequence
automatically.

</dl>

<p><h4>Interface Routine Overview</h4>

<dl>

<dt><tt>refclock_newpeer</tt> - initialize and start a reference
clock
<dd>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.
<p><dt><tt>refclock_unpeer</tt> - shut down a clock
<dd>This routine is used to shut down a clock and return its resources
to the system.

<p><dt><tt>refclock_transmit</tt> - simulate the transmit procedure
<dd>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.

<p><dt><tt>refclock_sample</tt> - process a pile of samples from the
clock
<dd>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
<tt>fudgetime1</tt> 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.

<p>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.

<p><dt><tt>refclock_receive</tt> - simulate the receive and packet
procedures
<dd>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.

<p><dt><tt>refclock_gtlin</tt> - groom next input line and extract
timestamp
<dd>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 <tt>tty_clk</tt> 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.

<p><dt><tt>refclock_open</tt> - open serial port for reference clock
<dd>This routine opens a serial port for I/O and sets default options.
It returns the file descriptor if success and zero if failure.

<p><dt><tt>refclock_ioctl</tt> - set serial port control functions
<dd>This routine attempts to hide the internal, system-specific details
of serial ports. It can handle POSIX (<tt>termios</tt>), SYSV
(<tt>termio</tt>) and BSD (<tt>sgtty</tt>) interfaces with
varying degrees of success. The routine sets up the <tt>tty_clk,
chu_clk</tt> and <tt>ppsclock</tt> streams module/line discipline,
if compiled in the daemon and requested in the call. The routine returns
one if success and zero if failure.

<p><dt><tt>refclock_control</tt> - set and/or return clock values
<dd>This routine is used mainly for debugging. It returns designated
values from the interface structure that can be displayed using ntpdc
and the clockstat command. It can also be used to initialize
configuration variables, such as <tt>fudgetimes, fudgevalues,</tt>
reference ID and stratum.

<p><dt><tt>refclock_buginfo</tt> - return debugging info
<dd>This routine is used mainly for debugging. It returns designated
values from the interface structure that can be displayed using
<tt>ntpdc</tt> and the <tt>clkbug</tt> command.

</dl>

<hr><a href=index.htm><img align=left src=pic/home.gif></a><address><a
href=mailto:mills@udel.edu> David L. Mills &lt;mills@udel.edu&gt;</a>
</address></a></body></html>