NetBSD/usr.sbin/xntp/xntpd/refclock_atom.c

521 lines
14 KiB
C

/*
* refclock_atom - clock driver for 1-pps signals
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#if defined(REFCLOCK) && defined(ATOM)
#include <stdio.h>
#include <ctype.h>
#include <sys/time.h>
#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_unixtime.h"
#include "ntp_refclock.h"
#include "ntp_stdlib.h"
#ifdef PPS
# include <sys/ppsclock.h>
#endif /* PPS */
/*
* This driver furnishes an interface for pulse-per-second (PPS) signals
* produced by a cesium clock, timing receiver or related equipment. It
* can be used to remove accumulated jitter and retime a secondary
* server when synchronized to a primary server over a congested, wide-
* area network and before redistributing the time to local clients.
*
* In order for this driver to work, the local clock must be set to
* within +-500 ms by another means, such as a radio clock or NTP
* itself. The 1-pps signal is connected via a serial port and gadget
* box consisting of a one-shot and RS232 level converter. When operated
* at 38.4 kbps with a SPARCstation IPC, this arrangement has a worst-
* case jitter less than 26 us.
*
* There are three ways in which this driver can be used. The first way
* uses the LDISC_PPS line discipline and works only for the baseboard
* serial ports of the Sun SPARCstation. The PPS signal is connected via
* a gadget box to the carrier detect (CD) line of a serial port and
* flag3 of the driver configured for that port is set. This causes the
* ppsclock streams module to be configured for that port and capture a
* timestamp at the on-time transition of the PPS signal. This driver
* then reads the timestamp directly by a designated ioctl() system
* call. This provides the most accurate time and least jitter of any
* other scheme. There is no need to configure a dedicated device for
* this purpose, which ordinarily is the device used for the associated
* radio clock.
*
* The second way uses the LDISC_CLKPPS line discipline and works for
* any architecture supporting a serial port. If after a few seconds
* this driver finds no ppsclock module configured, it attempts to open
* a serial port device /dev/pps%d, where %d is the unit number, and
* assign the LDISC_CLKPPS line discipline to it. If the line discipline
* fails, no harm is done except the accuracy is reduced somewhat. The
* pulse generator in the gadget box is adjusted to produce a start bit
* of length 26 usec at 38400 bps. Used with the LDISC_CLKPPS line
* discipline, this produces an ASCII DEL character ('\377') followed by
* a timestamp at each seconds epoch.
*
* The third way involves an auxiliary radio clock driver which calls
* the PPS driver with a timestamp captured by that driver. This use is
* documented in the source code for the driver(s) involved. Note that
* some drivers collect the sample information themselves before calling
* our pps_sample(), and others call us knowing only that they are running
* shortly after an on-time tick and they expect us to retrieve the PPS
* offset, fudge their result, and insert it into the timestream.
*
* Fudge Factors
*
* There are no special fudge factors other than the generic and those
* explicitly defined above. The fudge time1 parameter can be used to
* compensate for miscellaneous UART and OS delays. Allow about 247 us
* for uart delays at 38400 bps and about 1 ms for SunOS streams
* nonsense.
*/
/*
* Interface definitions
*/
#ifndef PPSX
#define DEVICE "/dev/pps%d" /* device name and unit */
#ifdef B38400
#define SPEED232 B38400 /* uart speed (38400 baud) */
#else
#define SPEED232 EXTB /* as above */
#endif
#endif /* PPSX */
#define PRECISION (-20) /* precision assumed (about 1 usec) */
#define REFID "PPS\0" /* reference ID */
#define DESCRIPTION "PPS Clock Discipline" /* WRU */
#define PPSMAXDISPERSE (FP_SECOND / 100) /* max sample dispersion */
#define NSAMPLES 32 /* final stages of median filter */
#ifdef PPS
#define PPS_POLL 2 /* ppsclock poll interval (s) */
#endif /* PPS */
/*
* Imported from ntp_timer module
*/
extern u_long current_time; /* current time (s) */
extern struct event timerqueue[]; /* inner space */
/*
* Imported from ntpd module
*/
extern int debug; /* global debug flag */
/*
* Imported from ntp_loopfilter module
*/
#ifndef PPSX
extern int fdpps; /* pps file descriptor */
#endif /* PPSX */
extern int pps_update; /* prefer peer valid update */
/*
* Imported from ntp_proto module
*/
extern struct peer *sys_peer; /* somebody in charge */
/*
* Unit control structure
*/
struct atomunit {
#ifdef PPS
struct event timer; /* pps poll interval timer */
struct ppsclockev ev; /* ppsclock control */
#endif /* PPS */
int pollcnt; /* poll message counter */
};
/*
* Global variables
*/
static struct peer *last_atom_peer; /* peer structure pointer */
/*
* Function prototypes
*/
static int atom_start P((int, struct peer *));
static void atom_shutdown P((int, struct peer *));
static void atom_poll P((int, struct peer *));
#ifdef PPS
static void atom_pps P((struct peer *));
#endif /* PPS */
/*
* Transfer vector
*/
struct refclock refclock_atom = {
atom_start, /* start up driver */
atom_shutdown, /* shut down driver */
atom_poll, /* transmit poll message */
noentry, /* not used (old atom_control) */
noentry, /* initialize driver */
noentry, /* not used (old atom_buginfo) */
NOFLAGS /* not used */
};
/*
* atom_start - initialize data for processing
*/
static int
atom_start(unit, peer)
int unit;
struct peer *peer;
{
register struct atomunit *up;
struct refclockproc *pp;
/*
* Allocate and initialize unit structure
*/
if (!(up = (struct atomunit *)
emalloc(sizeof(struct atomunit))))
return (0);
memset((char *)up, 0, sizeof(struct atomunit));
pp = peer->procptr;
pp->unitptr = (caddr_t)up;
/*
* Initialize miscellaneous variables
*/
peer->precision = PRECISION;
pp->clockdesc = DESCRIPTION;
memcpy((char *)&pp->refid, REFID, 4);
up->pollcnt = 2;
pp->nstages = MAXSTAGE;
#ifdef PPS
/*
* Arm the timer for the first interrupt. Give it ten seconds to
* allow the ppsclock line to be configured, since it could be
* assigned to another driver.
*/
up->timer.peer = (struct peer *)peer;
up->timer.event_handler = atom_pps;
up->timer.event_time = current_time + 10;
TIMER_INSERT(timerqueue, &up->timer);
#endif /* PPS */
last_atom_peer = peer;
return (1);
}
/*
* atom_shutdown - shut down the clock
*/
static void
atom_shutdown(unit, peer)
int unit;
struct peer *peer;
{
register struct atomunit *up;
struct refclockproc *pp;
pp = peer->procptr;
up = (struct atomunit *)pp->unitptr;
if (last_atom_peer == peer)
last_atom_peer = 0;
#ifdef PPS
TIMER_DEQUEUE(&up->timer);
#endif /* PPS */
if (pp->io.fd)
io_closeclock(&pp->io);
free(up);
}
/*
* pps_sample - process pps sample offset -- backwards compatible interface
*/
int
pps_sample(tsr)
l_fp *tsr;
{
struct peer *peer;
struct refclockproc *pp;
register struct atomunit *up;
int i;
l_fp lftemp; /* l_fp temps */
/*
* This routine is called once per second by an auxilliary
* routine in another driver. It saves the sign-extended
* fraction supplied in the argument in a circular buffer for
* processing at the next poll event.
*/
peer = last_atom_peer;
if (!peer)
return (-1); /* no ATOM configured ? Forget it ! */
pp = peer->procptr;
up = (struct atomunit *)pp->unitptr;
L_CLR(&lftemp);
L_ADDF(&lftemp, tsr->l_f);
i = ((int)(pp->coderecv)) % pp->nstages;
pp->filter[i] = lftemp;
if (pp->coderecv == 0)
for (i = 1; i < pp->nstages; i++)
pp->filter[i] = pp->filter[0];
pp->coderecv++;
up->pollcnt = 2;
/* HACK -- use the local UN*X clock to get the time -- this is wrong */
pp->lastrec.l_ui = time(0) - 2 + JAN_1970;
pp->lastrec.l_uf = 0;
return (0);
}
#ifdef PPS
/*
* atom_pps - receive data from the LDISC_PPS discipline
*/
static void
atom_pps(peer)
struct peer *peer;
{
register struct atomunit *up;
struct refclockproc *pp;
l_fp lftmp;
int i;
/*
* This routine is called once per second when the LDISC_PPS
* discipline is present. It snatches the pps timestamp from the
* kernel and saves the sign-extended fraction in a circular
* buffer for processing at the next poll event.
*/
pp = peer->procptr;
up = (struct atomunit *)pp->unitptr;
/*
* Arm the timer for the next interrupt
*/
up->timer.event_time = current_time + PPS_POLL;
TIMER_INSERT(timerqueue, &up->timer);
/*
* Convert the timeval to l_fp and save for billboards. Sign-
* extend the fraction and stash in the buffer. No harm is done
* if previous data are overwritten. If the discipline comes bum
* or the data grow stale, just forget it.
*/
i = up->ev.serial;
if (ioctl(fdpps, CIOGETEV, (caddr_t)&up->ev) < 0)
return;
if (i == up->ev.serial)
return;
pp->lastrec.l_ui = up->ev.tv.tv_sec + JAN_1970;
TVUTOTSF(up->ev.tv.tv_usec, pp->lastrec.l_uf);
L_CLR(&lftmp);
L_ADDF(&lftmp, pp->lastrec.l_f);
L_NEG(&lftmp);
i = ((int)(pp->coderecv)) % pp->nstages;
pp->filter[i] = lftmp;
if (pp->coderecv == 0)
for (i = 1; i < pp->nstages; i++)
pp->filter[i] = pp->filter[0];
pp->coderecv++;
up->pollcnt = 2;
}
#endif /* PPS */
#ifndef PPSX
static void atom_receive P((struct recvbuf *));
/*
* atom_receive - receive data from the serial line interface
*/
static void
atom_receive(rbufp)
struct recvbuf *rbufp;
{
register struct atomunit *up;
struct refclockproc *pp;
struct peer *peer;
l_fp lftmp;
int i;
/*
* This routine is called once per second when the serial
* interface is in use. It snatches the timestamp from the
* buffer and saves the sign-extended fraction in a circular
* buffer for processing at the next poll event.
*/
peer = (struct peer *)rbufp->recv_srcclock;
pp = peer->procptr;
up = (struct atomunit *)pp->unitptr;
pp->lencode = refclock_gtlin(rbufp, pp->lastcode, BMAX,
&pp->lastrec);
/*
* Save the timestamp for billboards. Sign-extend the fraction
* and stash in the buffer. No harm is done if previous data are
* overwritten.
*/
L_CLR(&lftmp);
L_ADDF(&lftmp, pp->lastrec.l_f);
L_NEG(&lftmp);
i = ((int)(pp->coderecv)) % pp->nstages;
pp->filter[i] = lftmp;
if (pp->coderecv == 0)
for (i = 1; i < pp->nstages; i++)
pp->filter[i] = pp->filter[0];
pp->coderecv++;
up->pollcnt = 2;
}
#endif /* PPSX */
/*
* Compare two l_fp's - used with qsort()
*/
static int
atom_cmpl_fp(p1, p2)
register const void *p1, *p2; /* l_fp to compare */
{
if (!L_ISGEQ((l_fp *)p1, (l_fp *)p2))
return (-1);
if (L_ISEQU((l_fp *)p1, (l_fp *)p2))
return (0);
return (1);
}
/*
* atom_poll - called by the transmit procedure
*/
static void
atom_poll(unit, peer)
int unit;
struct peer *peer;
{
register struct atomunit *up;
struct refclockproc *pp;
int i, n;
l_fp median, lftmp;
l_fp off[MAXSTAGE];
u_fp disp;
/*
* At each poll we check for timeout. At the first timeout we
* test to see if the LDISC_PPS discipline is present and, if
* so, use that. If not, we attempt to open a serial line with
* LDISC_CLKPPS discipline. If that fails, we bitch to the log
* and clam up.
*/
pp = peer->procptr;
up = (struct atomunit *)pp->unitptr;
pp->polls++;
if (up->pollcnt == 0) {
refclock_report(peer, CEVNT_FAULT);
return;
}
up->pollcnt--;
#ifndef PPSX
if (up->pollcnt == 0) {
if (!pp->io.fd && fdpps == -1) {
int fd;
char device[20];
/*
* Open serial port. Use CLKPPS line discipline,
* if available. If unavailable, the code works
* anyway, but at reduced accuracy.
*/
(void)sprintf(device, DEVICE, unit);
if (!(fd = refclock_open(device, SPEED232,
LDISC_CLKPPS))) {
refclock_report(peer, CEVNT_FAULT);
return;
}
pp->io.clock_recv = atom_receive;
pp->io.srcclock = (caddr_t)peer;
pp->io.datalen = 0;
pp->io.fd = fd;
if (!io_addclock(&pp->io)) {
(void) close(fd);
refclock_report(peer, CEVNT_FAULT);
return;
}
}
}
#endif /* PPSX */
/*
* Valid time (leap bits zero) is returned only if the prefer
* peer has survived the intersection algorithm and within
* CLOCK_MAX of local time and not too long ago. This ensures
* the pps time is within +-0.5 s of the local time and the
* seconds numbering is unambiguous.
*/
if (pps_update) {
pp->leap = 0;
pp->lasttime = current_time;
} else
pp->leap = LEAP_NOTINSYNC;
/*
* Copy the raw offsets and sort into ascending order
*/
for (i = 0; i < MAXSTAGE; i++)
off[i] = pp->filter[i];
qsort((char *)off, pp->nstages, sizeof(l_fp), atom_cmpl_fp);
/*
* Reject the furthest from the median of nstages samples until
* nskeep samples remain.
*/
i = 0;
n = pp->nstages;
while ((n - i) > NSAMPLES) {
lftmp = off[n - 1];
median = off[(n + i) / 2];
L_SUB(&lftmp, &median);
L_SUB(&median, &off[i]);
if (L_ISHIS(&median, &lftmp)) {
/* reject low end */
i++;
} else {
/* reject high end */
n--;
}
}
/*
* Compute the dispersion based on the difference between the
* extremes of the remaining offsets. Add to this the time since
* the last clock update, which represents the dispersion
* increase with time. We know that NTP_MAXSKEW is 16. If the
* sum is greater than the allowed sample dispersion, bail out.
* Otherwise, return the median offset plus the configured
* fudgetime1 value.
*/
lftmp = off[n - 1];
L_SUB(&lftmp, &off[i]);
disp = LFPTOFP(&lftmp) + current_time - pp->lasttime;
if (disp > PPSMAXDISPERSE) {
refclock_report(peer, CEVNT_BADTIME);
pp->leap = LEAP_NOTINSYNC;
}
pp->offset = off[(n + 1) / 2];
L_ADD(&pp->offset, &pp->fudgetime1);
pp->dispersion = disp;
if (pp->leap == 0)
refclock_report(peer, CEVNT_NOMINAL);
refclock_receive(peer, &pp->offset, 0, pp->dispersion,
&pp->lastrec, &pp->lastrec, pp->leap);
}
#endif /* ATOM && REFCLOCK */