/* * 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 <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 */ #ifdef PPS /* avoid tty_clk if ppsclock available */ #undef TTYCLK #endif /* PPS */ #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 */ #ifdef PPS_SAMPLE static struct peer *pps_peer; /* atom driver for auxiliary PPS sources */ #endif #ifdef TTYCLK static void atom_receive P((struct recvbuf *)); #endif /* TTYCLK */ /* * Unit control structure */ struct atomunit { #ifdef PPS struct ppsclockev ev; /* ppsclock control */ #endif /* PPS */ int flags; /* flags that wave */ int pollcnt; /* poll message counter */ }; /* * 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 int 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( int unit, struct peer *peer ) { register struct atomunit *up; struct refclockproc *pp; int flags; #ifdef TTYCLK int fd; char device[20]; #endif /* TTYCLK */ flags = 0; #ifdef TTYCLK /* * Open serial port. Use LDISC_CLKPPS line discipline only * if the LDISC_PPS line discipline is not availble, */ (void)sprintf(device, DEVICE, unit); if ((fd = refclock_open(device, SPEED232, LDISC_CLKPPS)) == 0) return (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 */ #ifdef PPS_SAMPLE if (pps_peer == 0) pps_peer = peer; #endif /* PPS_SAMPLE */ /* * Initialize miscellaneous variables */ peer->precision = PRECISION; pp->clockdesc = DESCRIPTION; memcpy((char *)&pp->refid, REFID, 4); up->pollcnt = 2; up->flags = flags; pp->nstages = MAXSTAGE; pp->nskeep = MAXSTAGE * 3 / 5; 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 */ #ifdef PPS_SAMPLE if (pps_peer == peer) pps_peer = 0; #endif /* PPS_SAMPLE */ free(up); } #ifdef PPS /* * atom_pps - receive data from the LDISC_PPS discipline */ static int atom_pps( struct peer *peer ) { register struct atomunit *up; struct refclockproc *pp; l_fp lftmp; double doffset; 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; /* * 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 (fdpps <= 0) return (1); if (ioctl(fdpps, CIOGETEV, (caddr_t)&up->ev) < 0) return (1); if (i == up->ev.serial) return (2); up->flags |= FLAG_PPS; 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); LFPTOD(&lftmp, doffset); pp->filter[pp->coderecv++ % pp->nstages] = -doffset + pp->fudgetime1; up->pollcnt = 2 * 60; return (0); } #endif /* PPS */ #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); pp->filter[pp->coderecv++ % pp->nstages] = -doffset + pp->fudgetime1; up->pollcnt = 2; } #endif /* TTYCLK */ #ifdef PPS_SAMPLE /* * 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); pp->filter[pp->coderecv++ % pp->nstages] = -doffset + pp->fudgetime1; up->pollcnt = 2 * 60; return (0); } #endif /* PPS_SAMPLE */ /* * atom_poll - called by the transmit procedure */ static void atom_poll( int unit, struct peer *peer ) { register struct atomunit *up; 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; up = (struct atomunit *)pp->unitptr; #ifdef PPS if (!(up->flags & FLAG_AUX)) { if (atom_pps(peer)) return; } if (peer->burst > 0) return; #endif /* PPS */ pp->polls++; if (up->pollcnt == 0) { refclock_report(peer, CEVNT_FAULT); return; } up->pollcnt--; /* * 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; refclock_receive(peer); #ifdef PPS peer->burst = pp->nstages; #endif /* PPS */ } #else int refclock_atom_bs; #endif /* REFCLOCK */