/*      $NetBSD: refclock_atom.c,v 1.6 2024/08/18 20:47:18 christos Exp $       */

/*
* refclock_atom - clock driver for 1-pps signals
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#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"

/*
* This driver requires the PPSAPI interface (RFC 2783)
*/
#if defined(REFCLOCK) && defined(CLOCK_ATOM) && defined(HAVE_PPSAPI)
#include "ppsapi_timepps.h"
#include "refclock_atom.h"

/*
* 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 over a congested link and
* retime a server before redistributing the time to clients. It can
*also be used as a holdover should all other synchronization sources
* beconme unreachable.
*
* Before this driver becomes active, the local clock must be set to
* within +-0.4 s by another means, such as a radio clock or NTP
* itself. There are two ways to connect the PPS signal, normally at TTL
* levels, to the computer. One is to shift to EIA levels and connect to
* pin 8 (DCD) of a serial port. This requires a level converter and
* may require a one-shot flipflop to lengthen the pulse. The other is
* to connect the PPS signal directly to pin 10 (ACK) of a PC paralell
* port. These methods are architecture dependent.
*
* This driver requires the Pulse-per-Second API for Unix-like Operating
* Systems, Version 1.0, RFC-2783 (PPSAPI). Implementations are
* available for FreeBSD, Linux, SunOS, Solaris and Tru64. However, at
* present only the Tru64 implementation provides the full generality of
* the API with multiple PPS drivers and multiple handles per driver. If
* the PPSAPI is normally implemented in the /usr/include/sys/timepps.h
* header file and kernel support specific to each operating system.
*
* This driver normally uses the PLL/FLL clock discipline implemented in
* the ntpd code. Ordinarily, this is the most accurate means, as the
* median filter in the driver interface is much larger than in the
* kernel. However, if the systemic clock frequency error is large (tens
* to hundreds of PPM), it's better to used the kernel support, if
* available.
*
* This deriver is subject to the mitigation rules described in the
* "mitigation rulse and the prefer peer" page. However, there is an
* important difference. If this driver becomes the PPS driver according
* to these rules, it is acrive only if (a) a prefer peer other than
* this driver is among the survivors or (b) there are no survivors and
* the minsane option of the tos command is zero. This is intended to
* support space missions where updates from other spacecraft are
* infrequent, but a reliable PPS signal, such as from an Ultra Stable
* Oscillator (USO) is available.
*
* Fudge Factors
*
* The PPS timestamp is captured on the rising (assert) edge if flag2 is
* dim (default) and on the falling (clear) edge if lit. If flag3 is dim
* (default), the kernel PPS support is disabled; if lit it is enabled.
* If flag4 is lit, each timesampt is copied to the clockstats file for
* later analysis. This can be useful when constructing Allan deviation
* plots. The time1 parameter can be used to compensate for
* miscellaneous device driver and OS delays.
*/
/*
* Interface definitions
*/
#define DEVICE          "/dev/pps%d" /* device name and unit */
#define PRECISION       (-20)   /* precision assumed (about 1 us) */
#define REFID           "PPS\0" /* reference ID */
#define DESCRIPTION     "PPS Clock Discipline" /* WRU */

/*
* PPS unit control structure
*/
struct ppsunit {
       struct refclock_atom atom; /* atom structure pointer */
       int     fddev;          /* file descriptor */
};

/*
* Function prototypes
*/
static  int     atom_start      (int, struct peer *);
static  void    atom_shutdown   (int, struct peer *);
static  void    atom_poll       (int, struct peer *);
static  void    atom_timer      (int, struct peer *);

/*
* Transfer vector
*/
struct  refclock refclock_atom = {
       atom_start,             /* start up driver */
       atom_shutdown,          /* shut down driver */
       atom_poll,              /* transmit poll message */
       noentry,                /* control (not used) */
       noentry,                /* initialize driver (not used) */
       noentry,                /* buginfo (not used) */
       atom_timer,             /* called once per second */
};


/*
* atom_start - initialize data for processing
*/
static int
atom_start(
       int unit,               /* unit number (not used) */
       struct peer *peer       /* peer structure pointer */
       )
{
       struct refclockproc *pp;
       struct ppsunit *up;
       char    device[80];

       /*
        * Allocate and initialize unit structure
        */
       pp = peer->procptr;
       peer->precision = PRECISION;
       pp->clockdesc = DESCRIPTION;
       pp->stratum = STRATUM_UNSPEC;
       memcpy((char *)&pp->refid, REFID, 4);
       up = emalloc(sizeof(struct ppsunit));
       memset(up, 0, sizeof(struct ppsunit));
       pp->unitptr = up;

       /*
        * Open PPS device. This can be any serial or parallel port and
        * not necessarily the port used for the associated radio.
        */
       snprintf(device, sizeof(device), DEVICE, unit);
       up->fddev = tty_open(device, O_RDWR, 0777);
       if (up->fddev <= 0) {
               msyslog(LOG_ERR,
                       "refclock_atom: %s: %m", device);
               return (0);
       }

       /*
        * Light up the PPSAPI interface.
        */
       return (refclock_ppsapi(up->fddev, &up->atom));
}


/*
* atom_shutdown - shut down the clock
*/
static void
atom_shutdown(
       int unit,               /* unit number (not used) */
       struct peer *peer       /* peer structure pointer */
       )
{
       struct refclockproc *pp;
       struct ppsunit *up;

       pp = peer->procptr;
       up = pp->unitptr;
       if (up->fddev > 0)
               close(up->fddev);
       free(up);
}

/*
* atom_timer - called once per second
*/
void
atom_timer(
       int     unit,           /* unit pointer (not used) */
       struct peer *peer       /* peer structure pointer */
       )
{
       struct ppsunit *up;
       struct refclockproc *pp;
       char    tbuf[80];

       pp = peer->procptr;
       up = pp->unitptr;
       if (refclock_pps(peer, &up->atom, pp->sloppyclockflag) <= 0)
               return;

       peer->flags |= FLAG_PPS;

       /*
        * If flag4 is lit, record each second offset to clockstats.
        * That's so we can make awesome Allan deviation plots.
        */
       if (pp->sloppyclockflag & CLK_FLAG4) {
               snprintf(tbuf, sizeof(tbuf), "%.9f",
                        pp->filter[pp->coderecv]);
               record_clock_stats(&peer->srcadr, tbuf);
       }
}


/*
* atom_poll - called by the transmit procedure
*/
static void
atom_poll(
       int unit,               /* unit number (not used) */
       struct peer *peer       /* peer structure pointer */
       )
{
       struct refclockproc *pp;

       /*
        * Don't wiggle the clock until some other driver has numbered
        * the seconds.
        */
       if (sys_leap == LEAP_NOTINSYNC)
               return;

       pp = peer->procptr;
       pp->polls++;
       if (pp->codeproc == pp->coderecv) {
               peer->flags &= ~FLAG_PPS;
               refclock_report(peer, CEVNT_TIMEOUT);
               return;
       }
       pp->lastref = pp->lastrec;
       refclock_receive(peer);
}
#else
NONEMPTY_TRANSLATION_UNIT
#endif /* REFCLOCK */