495 lines
13 KiB
C
495 lines
13 KiB
C
/*
|
|
* refclock_atom - clock driver for 1-pps signals
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#if defined(REFCLOCK) && defined(CLOCK_ATOM)
|
|
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
#include "ntpd.h"
|
|
#include "ntp_io.h"
|
|
#include "ntp_unixtime.h"
|
|
#include "ntp_refclock.h"
|
|
#include "ntp_stdlib.h"
|
|
|
|
#ifdef HAVE_SYS_TIME_H
|
|
# include <sys/time.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_TERMIOS_H
|
|
# include <sys/termios.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_PPSCLOCK_H
|
|
# include <sys/ppsclock.h>
|
|
#endif
|
|
#ifdef HAVE_PPSAPI
|
|
# ifdef HAVE_TIMEPPS_H
|
|
# include <timepps.h>
|
|
# else
|
|
# ifdef HAVE_SYS_TIMEPPS_H
|
|
# include <sys/timepps.h>
|
|
# endif
|
|
# endif
|
|
#endif /* HAVE_PPSAPI */
|
|
|
|
/*
|
|
* 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 flopflop and RS232 level converter.
|
|
* Conntection is either via the carrier detect (DCD) lead or via the
|
|
* receive data (RD) lead. The incidental jitter using the DCD lead is
|
|
* essentially the interrupt latency. The incidental jitter using the RD
|
|
* lead has an additional component due to the line sampling clock. When
|
|
* operated at 38.4 kbps, this arrangement has a worst-case jitter less
|
|
* than 26 us.
|
|
*
|
|
* There are four ways in which this driver can be used. They are
|
|
* described in decreasing order of merit below. The first way uses the
|
|
* ppsapi STREAMS module and the LDISC_PPS line discipline, while the
|
|
* second way uses the ppsclock STREAMS module and the LDISC_PPS line
|
|
* discipline. Either of these works only for the baseboard serial ports
|
|
* of the Sun SPARC IPC and clones. However, the ppsapi uses the
|
|
* proposed IETF interface expected to become standard for PPS signals.
|
|
* The serial port to be used is specified by the pps command in the
|
|
* configuration file. This driver reads the timestamp directly by a
|
|
* designated ioctl() system call.
|
|
*
|
|
* The third 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 fourth 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
|
|
* pps_sample(), and others call knowing only that they are running
|
|
* shortly after an on-time tick and they expect 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. 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 STREAMS nonsense with older workstations. Velocities may
|
|
* vary with modern workstations.
|
|
*/
|
|
/*
|
|
* Interface definitions
|
|
*/
|
|
#ifdef HAVE_PPSAPI
|
|
extern int pps_assert;
|
|
#endif /* HAVE_PPSAPI */
|
|
#ifdef TTYCLK
|
|
#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 /* TTYCLK */
|
|
|
|
#define PRECISION (-20) /* precision assumed (about 1 us) */
|
|
#define REFID "PPS\0" /* reference ID */
|
|
#define DESCRIPTION "PPS Clock Discipline" /* WRU */
|
|
|
|
#define FLAG_TTY 0x01 /* tty_clk heard from */
|
|
#define FLAG_PPS 0x02 /* ppsclock heard from */
|
|
#define FLAG_AUX 0x04 /* auxiliary PPS source */
|
|
|
|
static struct peer *pps_peer; /* atom driver for auxiliary PPS sources */
|
|
|
|
#ifdef TTYCLK
|
|
static void atom_receive P((struct recvbuf *));
|
|
#endif /* TTYCLK */
|
|
|
|
/*
|
|
* Unit control structure
|
|
*/
|
|
struct atomunit {
|
|
#ifdef HAVE_PPSAPI
|
|
pps_info_t pps_info; /* pps_info control */
|
|
#endif /* HAVE_PPSAPI */
|
|
#ifdef PPS
|
|
struct ppsclockev ev; /* ppsclock control */
|
|
#endif /* PPS */
|
|
int flags; /* flags that wave */
|
|
};
|
|
|
|
/*
|
|
* 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 *));
|
|
#if defined(PPS) || defined(HAVE_PPSAPI)
|
|
static int atom_pps P((struct peer *));
|
|
#endif /* PPS || HAVE_PPSAPI */
|
|
|
|
/*
|
|
* 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(
|
|
int unit,
|
|
struct peer *peer
|
|
)
|
|
{
|
|
register struct atomunit *up;
|
|
struct refclockproc *pp;
|
|
int flags;
|
|
#ifdef TTYCLK
|
|
int fd = 0;
|
|
char device[20];
|
|
int ldisc = LDISC_CLKPPS;
|
|
#endif /* TTYCLK */
|
|
|
|
pps_peer = peer;
|
|
flags = 0;
|
|
|
|
#ifdef TTYCLK
|
|
# if defined(SCO5_CLOCK)
|
|
ldisc = LDISC_RAW; /* DCD timestamps without any line discipline */
|
|
# endif
|
|
/*
|
|
* Open serial port. Use LDISC_CLKPPS line discipline only
|
|
* if the LDISC_PPS line discipline is not availble,
|
|
*/
|
|
# if defined(PPS) || defined(HAVE_PPSAPI)
|
|
if (fdpps <= 0)
|
|
# endif
|
|
{
|
|
(void)sprintf(device, DEVICE, unit);
|
|
if ((fd = refclock_open(device, SPEED232, ldisc)) != 0)
|
|
flags |= FLAG_TTY;
|
|
}
|
|
#endif /* TTYCLK */
|
|
|
|
/*
|
|
* Allocate and initialize unit structure
|
|
*/
|
|
if (!(up = (struct atomunit *)emalloc(sizeof(struct atomunit)))) {
|
|
#ifdef TTYCLK
|
|
if (flags & FLAG_TTY)
|
|
(void) close(fd);
|
|
#endif /* TTYCLK */
|
|
return (0);
|
|
}
|
|
memset((char *)up, 0, sizeof(struct atomunit));
|
|
pp = peer->procptr;
|
|
pp->unitptr = (caddr_t)up;
|
|
#ifdef TTYCLK
|
|
if (flags & FLAG_TTY) {
|
|
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);
|
|
free(up);
|
|
return (0);
|
|
}
|
|
}
|
|
#endif /* TTYCLK */
|
|
|
|
/*
|
|
* Initialize miscellaneous variables
|
|
*/
|
|
peer->precision = PRECISION;
|
|
pp->clockdesc = DESCRIPTION;
|
|
memcpy((char *)&pp->refid, REFID, 4);
|
|
up->flags = flags;
|
|
return (1);
|
|
}
|
|
|
|
|
|
/*
|
|
* atom_shutdown - shut down the clock
|
|
*/
|
|
static void
|
|
atom_shutdown(
|
|
int unit,
|
|
struct peer *peer
|
|
)
|
|
{
|
|
register struct atomunit *up;
|
|
struct refclockproc *pp;
|
|
|
|
pp = peer->procptr;
|
|
up = (struct atomunit *)pp->unitptr;
|
|
#ifdef TTYCLK
|
|
if (up->flags & FLAG_TTY)
|
|
io_closeclock(&pp->io);
|
|
#endif /* TTYCLK */
|
|
if (pps_peer == peer)
|
|
pps_peer = 0;
|
|
free(up);
|
|
}
|
|
|
|
|
|
#if defined(PPS) || defined(HAVE_PPSAPI)
|
|
/*
|
|
* atom_pps - receive data from the LDISC_PPS discipline
|
|
*/
|
|
static int
|
|
atom_pps(
|
|
struct peer *peer
|
|
)
|
|
{
|
|
register struct atomunit *up;
|
|
struct refclockproc *pp;
|
|
#ifdef HAVE_PPSAPI
|
|
struct timespec timeout;
|
|
# ifdef HAVE_TIMESPEC
|
|
struct timespec ts;
|
|
# else
|
|
struct timeval ts;
|
|
# endif /* HAVE_TIMESPEC */
|
|
#endif /* HAVE_PPSAPI */
|
|
l_fp lftmp;
|
|
double doffset;
|
|
int i;
|
|
#if !defined(HAVE_PPSAPI)
|
|
int request =
|
|
# ifdef HAVE_CIOGETEV
|
|
CIOGETEV
|
|
# endif
|
|
# ifdef HAVE_TIOCGPPSEV
|
|
TIOCGPPSEV
|
|
# endif
|
|
;
|
|
#endif /* HAVE_PPSAPI */
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* 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. Round the nanoseconds
|
|
* to microseconds with great care.
|
|
*/
|
|
if (fdpps <= 0)
|
|
return (1);
|
|
#ifdef HAVE_PPSAPI
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_nsec = 0;
|
|
i = up->pps_info.assert_sequence;
|
|
if (time_pps_fetch(fdpps, PPS_TSFMT_TSPEC, &up->pps_info, &timeout)
|
|
< 0)
|
|
return (2);
|
|
if (i == up->pps_info.assert_sequence)
|
|
return (3);
|
|
if (pps_assert)
|
|
ts = up->pps_info.assert_timestamp;
|
|
else
|
|
ts = up->pps_info.clear_timestamp;
|
|
pp->lastrec.l_ui = ts.tv_sec + JAN_1970;
|
|
ts.tv_nsec = (ts.tv_nsec + 500) / 1000;
|
|
if (ts.tv_nsec > 1000000) {
|
|
ts.tv_nsec -= 1000000;
|
|
ts.tv_sec++;
|
|
}
|
|
TVUTOTSF(ts.tv_nsec, pp->lastrec.l_uf);
|
|
#else
|
|
i = up->ev.serial;
|
|
if (ioctl(fdpps, request, (caddr_t)&up->ev) < 0)
|
|
return (2);
|
|
if (i == up->ev.serial)
|
|
return (3);
|
|
pp->lastrec.l_ui = up->ev.tv.tv_sec + JAN_1970;
|
|
TVUTOTSF(up->ev.tv.tv_usec, pp->lastrec.l_uf);
|
|
#endif /* HAVE_PPSAPI */
|
|
up->flags |= FLAG_PPS;
|
|
L_CLR(&lftmp);
|
|
L_ADDF(&lftmp, pp->lastrec.l_f);
|
|
LFPTOD(&lftmp, doffset);
|
|
SAMPLE(-doffset + pp->fudgetime1);
|
|
return (0);
|
|
}
|
|
#endif /* PPS || HAVE_PPSAPI */
|
|
|
|
#ifdef TTYCLK
|
|
/*
|
|
* atom_receive - receive data from the LDISC_CLK discipline
|
|
*/
|
|
static void
|
|
atom_receive(
|
|
struct recvbuf *rbufp
|
|
)
|
|
{
|
|
register struct atomunit *up;
|
|
struct refclockproc *pp;
|
|
struct peer *peer;
|
|
l_fp lftmp;
|
|
double doffset;
|
|
|
|
/*
|
|
* 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->a_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. Do this only if the ppsclock gizmo is not
|
|
* working.
|
|
*/
|
|
if (up->flags & FLAG_PPS)
|
|
return;
|
|
L_CLR(&lftmp);
|
|
L_ADDF(&lftmp, pp->lastrec.l_f);
|
|
LFPTOD(&lftmp, doffset);
|
|
SAMPLE(-doffset + pp->fudgetime1);
|
|
}
|
|
#endif /* TTYCLK */
|
|
|
|
/*
|
|
* pps_sample - receive PPS data from some other clock driver
|
|
*/
|
|
int
|
|
pps_sample(
|
|
l_fp *offset
|
|
)
|
|
{
|
|
register struct peer *peer;
|
|
register struct atomunit *up;
|
|
struct refclockproc *pp;
|
|
l_fp lftmp;
|
|
double doffset;
|
|
|
|
/*
|
|
* This routine is called once per second when the external
|
|
* clock driver processes PPS information. It processes the pps
|
|
* timestamp and saves the sign-extended fraction in a circular
|
|
* buffer for processing at the next poll event.
|
|
*/
|
|
peer = pps_peer;
|
|
if (peer == 0) /* nobody home */
|
|
return 1;
|
|
pp = peer->procptr;
|
|
up = (struct atomunit *)pp->unitptr;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
up->flags |= FLAG_AUX;
|
|
pp->lastrec = *offset;
|
|
L_CLR(&lftmp);
|
|
L_ADDF(&lftmp, pp->lastrec.l_f);
|
|
LFPTOD(&lftmp, doffset);
|
|
SAMPLE(-doffset + pp->fudgetime1);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* atom_poll - called by the transmit procedure
|
|
*/
|
|
static void
|
|
atom_poll(
|
|
int unit,
|
|
struct peer *peer
|
|
)
|
|
{
|
|
#if defined(PPS) || defined(HAVE_PPSAPI)
|
|
register struct atomunit *up;
|
|
#endif /* PPS || HAVE_PPSAPI */
|
|
struct refclockproc *pp;
|
|
|
|
/*
|
|
* Accumulate samples in the median filter. At the end of each
|
|
* poll interval, do a little bookeeping and process the
|
|
* samples.
|
|
*/
|
|
pp = peer->procptr;
|
|
#if defined(PPS) || defined(HAVE_PPSAPI)
|
|
up = (struct atomunit *)pp->unitptr;
|
|
if (!(up->flags & !(FLAG_AUX | FLAG_TTY))) {
|
|
int err;
|
|
|
|
err = atom_pps(peer);
|
|
if (err > 0) {
|
|
refclock_report(peer, CEVNT_FAULT);
|
|
return;
|
|
}
|
|
}
|
|
#endif /* PPS || HAVE_PPSAPI */
|
|
pp->polls++;
|
|
if (peer->burst > 0)
|
|
return;
|
|
if (pp->coderecv == pp->codeproc) {
|
|
refclock_report(peer, CEVNT_TIMEOUT);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* 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 = LEAP_NOWARNING;
|
|
} else {
|
|
pp->leap = LEAP_NOTINSYNC;
|
|
return;
|
|
}
|
|
pp->variance = 0;
|
|
record_clock_stats(&peer->srcadr, pp->a_lastcode);
|
|
refclock_receive(peer);
|
|
peer->burst = MAXSTAGE;
|
|
}
|
|
|
|
#else
|
|
int refclock_atom_bs;
|
|
#endif /* REFCLOCK */
|