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

/*******************************************************************************
*
*  Module  : refclock_tsyncpci.c
*  Date    : 09/08/08
*  Purpose : Implements a reference clock driver for the NTP daemon.  This
*            reference clock driver provides a means to communicate with
*            the Spectracom TSYNC PCI timing devices and use them as a time
*            source.
*
*  (C) Copyright 2008 Spectracom Corporation
*
*  This software is provided by Spectracom Corporation 'as is' and
*  any express or implied warranties, including, but not limited to, the
*  implied warranties of merchantability and fitness for a particular purpose
*  are disclaimed.  In no event shall Spectracom Corporation be liable
*  for any direct, indirect, incidental, special, exemplary, or consequential
*  damages (including, but not limited to, procurement of substitute goods
*  or services; loss of use, data, or profits; or business interruption)
*  however caused and on any theory of liability, whether in contract, strict
*  liability, or tort (including negligence or otherwise) arising in any way
*  out of the use of this software, even if advised of the possibility of
*  such damage.
*
*  This software is released for distribution according to the NTP copyright
*  and license contained in html/copyright.html of NTP source.
*
*******************************************************************************/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined(REFCLOCK) && defined(CLOCK_TSYNCPCI)

#include <asm/ioctl.h>
#ifdef HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif

#include <stdio.h>
#include <ctype.h>
#include <netinet/in.h>


#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_refclock.h"
#include "ntp_unixtime.h"
#include "ntp_stdlib.h"
#include "ntp_calendar.h"


/*******************************************************************************
**
** This driver supports the Spectracom TSYNC PCI GPS receiver.  It requires
** that the tsyncpci.o device driver be installed and loaded.
**
*******************************************************************************/

#define TSYNC_PCI_REVISION "1.11"

/*
** TPRO interface definitions
*/
#define DEVICE      "/dev/tsyncpci"             /* device name */
#define PRECISION   (-20)                       /* precision assumed (1 us) */
#define DESCRIPTION "Spectracom TSYNC-PCI"      /* WRU */

#define SECONDS_1900_TO_1970 (2208988800U)

#define TSYNC_REF_IID               (0x2500)    // SS CAI, REF IID
#define TSYNC_REF_DEST_ID           (0x0001)    // KTS Firmware
#define TSYNC_REF_IN_PYLD_OFF       (0)
#define TSYNC_REF_IN_LEN            (0)
#define TSYNC_REF_OUT_PYLD_OFF      (0)
#define TSYNC_REF_OUT_LEN           (8)
#define TSYNC_REF_MAX_OUT_LEN       (16)
#define TSYNC_REF_PYLD_LEN          (TSYNC_REF_IN_LEN +                     \
                                    TSYNC_REF_MAX_OUT_LEN)
#define TSYNC_REF_LEN               (4)
#define TSYNC_REF_LOCAL             ("LOCL")

#define TSYNC_TMSCL_IID              (0x2301)    // CS CAI, TIMESCALE IID
#define TSYNC_TMSCL_DEST_ID          (0x0001)    // KTS Firmware
#define TSYNC_TMSCL_IN_PYLD_OFF      (0)
#define TSYNC_TMSCL_IN_LEN           (0)
#define TSYNC_TMSCL_OUT_PYLD_OFF     (0)
#define TSYNC_TMSCL_OUT_LEN          (4)
#define TSYNC_TMSCL_MAX_OUT_LEN      (12)
#define TSYNC_TMSCL_PYLD_LEN         (TSYNC_TMSCL_IN_LEN +                    \
                                    TSYNC_TMSCL_MAX_OUT_LEN)

#define TSYNC_LEAP_IID              (0x2307)    // CS CAI, LEAP SEC IID
#define TSYNC_LEAP_DEST_ID          (0x0001)    // KTS Firmware
#define TSYNC_LEAP_IN_PYLD_OFF      (0)
#define TSYNC_LEAP_IN_LEN           (0)
#define TSYNC_LEAP_OUT_PYLD_OFF     (0)
#define TSYNC_LEAP_OUT_LEN          (28)
#define TSYNC_LEAP_MAX_OUT_LEN      (36)
#define TSYNC_LEAP_PYLD_LEN         (TSYNC_LEAP_IN_LEN +                    \
                                    TSYNC_LEAP_MAX_OUT_LEN)

// These define the base date/time of the system clock.  The system time will
// be tracked as the number of seconds from this date/time.
#define TSYNC_TIME_BASE_YEAR        (1970) // earliest acceptable year

#define TSYNC_LCL_STRATUM           (0)

/*
** TSYNC Time Scales type
*/
typedef enum
{
   TIME_SCALE_UTC    = 0,   // Universal Coordinated Time
   TIME_SCALE_TAI    = 1,   // International Atomic Time
   TIME_SCALE_GPS    = 2,   // Global Positioning System
   TIME_SCALE_LOCAL  = 3,   // UTC w/local rules for time zone and DST
   NUM_TIME_SCALES   = 4,   // Number of time scales

   TIME_SCALE_MAX    = 15   // Maximum number of timescales

} TIME_SCALE;

/*
** TSYNC Board Object
*/
typedef struct BoardObj {

 int            file_descriptor;
 unsigned short devid;
 unsigned short options;
 unsigned char  firmware[5];
 unsigned char  FPGA[5];
 unsigned char  driver[7];

} BoardObj;

/*
** TSYNC Time Object
*/
typedef struct TimeObj {

 unsigned char  syncOption;  /* -M option */
 unsigned int   secsDouble;  /* seconds floating pt */
 unsigned char  seconds;     /* seconds whole num */
 unsigned char  minutes;
 unsigned char  hours;
 unsigned short days;
 unsigned short year;
 unsigned short flags;      /* bit 2 SYNC, bit 1 TCODE; all others 0 */

} TimeObj;

/*
** NTP Time Object
*/
typedef struct NtpTimeObj {

   TimeObj        timeObj;
   struct timeval tv;
   unsigned int   refId;

} NtpTimeObj;
/*
** TSYNC Supervisor Reference Object
*/
typedef struct ReferenceObj {

   char time[TSYNC_REF_LEN];
   char pps[TSYNC_REF_LEN];

} ReferenceObj;

/*
** TSYNC Seconds Time Object
*/
typedef struct SecTimeObj
{
   unsigned int seconds;
   unsigned int ns;
}
SecTimeObj;

/*
** TSYNC DOY Time Object
*/
typedef struct DoyTimeObj
{
   unsigned int year;
   unsigned int doy;
   unsigned int hour;
   unsigned int minute;
   unsigned int second;
   unsigned int ns;
}
DoyTimeObj;

/*
** TSYNC Leap Second Object
*/
typedef struct LeapSecondObj
{
   int        offset;
   DoyTimeObj utcDate;
}
LeapSecondObj;

/*
* structures for ioctl interactions with driver
*/
#define DI_PAYLOADS_STARTER_LENGTH 4
typedef struct ioctl_trans_di {

   // input parameters
   uint16_t        dest;
   uint16_t        iid;

   uint32_t        inPayloadOffset;
   uint32_t        inLength;
   uint32_t        outPayloadOffset;
   uint32_t        maxOutLength;

   // output parameters
   uint32_t        actualOutLength;
   int32_t         status;

   // Input and output

   // The payloads field MUST be last in ioctl_trans_di.
   uint8_t         payloads[DI_PAYLOADS_STARTER_LENGTH];

}ioctl_trans_di;

/*
* structure for looking up a reference ID from a reference name
*/
typedef struct
{
   const char* pRef;           // KTS Reference Name
   const char* pRefId;         // NTP Reference ID

} RefIdLookup;

/*
* unit control structure
*/
typedef struct  {
   uint32_t refPrefer;         // Reference prefer flag
   uint32_t refId;             // Host peer reference ID
   uint8_t  refStratum;        // Host peer reference stratum

} TsyncUnit;

/*
**  Function prototypes
*/
static void tsync_poll     (int unit, struct peer *);
static void tsync_shutdown (int, struct peer *);
static int  tsync_start    (int, struct peer *);

/*
**  Helper functions
*/
static void ApplyTimeOffset    (DoyTimeObj* pDt, int off);
static void SecTimeFromDoyTime (SecTimeObj* pSt, DoyTimeObj* pDt);
static void DoyTimeFromSecTime (DoyTimeObj* pDt, SecTimeObj* pSt);

/*
**  Transfer vector
*/
struct refclock refclock_tsyncpci = {
   tsync_start,    /* start up driver */
   tsync_shutdown, /* shut down driver */
   tsync_poll,     /* transmit poll message */
   noentry,        /* not used (old tsync_control) */
   noentry,        /* initialize driver (not used) */
   noentry,        /* not used (old tsync_buginfo) */
   NOFLAGS         /* not used */
};

/*
* Reference ID lookup table
*/
static RefIdLookup RefIdLookupTbl[] =
{
   {"gps",  "GPS"},
   {"ir",   "IRIG"},
   {"hvq",  "HVQ"},
   {"frq",  "FREQ"},
   {"mdm",  "ACTS"},
   {"epp",  "PPS"},
   {"ptp",  "PTP"},
   {"asc",  "ATC"},
   {"hst0", "USER"},
   {"hst",  TSYNC_REF_LOCAL},
   {"self", TSYNC_REF_LOCAL},
   {NULL,   NULL}
};

/*******************************************************************************
**          IOCTL DEFINITIONS
*******************************************************************************/
#define IOCTL_TPRO_ID            't'
#define IOCTL_TPRO_OPEN          _IOWR(IOCTL_TPRO_ID, 0,  BoardObj)
#define IOCTL_TPRO_GET_NTP_TIME  _IOWR(IOCTL_TPRO_ID, 25, NtpTimeObj)
#define IOCTL_TSYNC_GET          _IOWR(IOCTL_TPRO_ID, 26, ioctl_trans_di)

/******************************************************************************
*
* Function:    tsync_start()
* Description: Used to intialize the Spectracom TSYNC reference driver.
*
* Parameters:
*     IN:  unit - not used.
*         *peer - pointer to this reference clock's peer structure
*     Returns: 0 - unsuccessful
*              1 - successful
*
*******************************************************************************/
static int tsync_start(int unit, struct peer *peer)
{
   struct refclockproc *pp;
   TsyncUnit           *up;


   /*
   **  initialize reference clock and peer parameters
   */
   pp                = peer->procptr;
   pp->clockdesc     = DESCRIPTION;
   pp->io.clock_recv = noentry;
   pp->io.srcclock   = peer;
   pp->io.datalen    = 0;
   peer->precision   = PRECISION;

   // Allocate and initialize unit structure
   if (!(up = (TsyncUnit*)emalloc(sizeof(TsyncUnit))))
   {
       return (0);
   }

   // Store reference preference
   up->refPrefer = peer->flags & FLAG_PREFER;

   // Initialize reference stratum level and ID
   up->refStratum = STRATUM_UNSPEC;
   strncpy((char *)&up->refId, TSYNC_REF_LOCAL, TSYNC_REF_LEN);

   // Attach unit structure
   pp->unitptr = (caddr_t)up;

   /* Declare our refId as local in the beginning because we do not know
    * what our actual refid is yet.
    */
   strncpy((char *)&pp->refid, TSYNC_REF_LOCAL, TSYNC_REF_LEN);

   return (1);

} /* End - tsync_start() */

/*******************************************************************************
**
** Function:    tsync_shutdown()
** Description: Handles anything related to shutting down the reference clock
**              driver. Nothing at this point in time.
**
** Parameters:
**     IN:  unit - not used.
**         *peer - pointer to this reference clock's peer structure
**     Returns: none.
**
*******************************************************************************/
static void tsync_shutdown(int unit, struct peer *peer)
{

} /* End - tsync_shutdown() */

/******************************************************************************
*
* Function:    tsync_poll()
* Description: Retrieve time from the TSYNC device.
*
* Parameters:
*     IN:  unit - not used.
*         *peer - pointer to this reference clock's peer structure
*     Returns: none.
*
*******************************************************************************/
static void tsync_poll(int unit, struct peer *peer)
{
   char                 device[32];
   struct refclockproc *pp;
   struct calendar      jt;
   TsyncUnit           *up;
   unsigned char        synch;
   double               seconds;
   int                  err;
   int                  err1;
   int                  err2;
   int                  err3;
   int                  i;
   int                  j;
   unsigned int         itAllocationLength;
   unsigned int         itAllocationLength1;
   unsigned int         itAllocationLength2;
   NtpTimeObj           TimeContext;
   BoardObj             hBoard;
   char                 timeRef[TSYNC_REF_LEN + 1];
   char                 ppsRef [TSYNC_REF_LEN + 1];
   TIME_SCALE           tmscl = TIME_SCALE_UTC;
   LeapSecondObj        leapSec;
   ioctl_trans_di      *it;
   ioctl_trans_di      *it1;
   ioctl_trans_di      *it2;
   l_fp                 offset;
   l_fp                 ltemp;
   ReferenceObj *       pRefObj;


   /* Construct the device name */
   sprintf(device, "%s%d", DEVICE, (int)peer->refclkunit);

   printf("Polling device number %d...\n", (int)peer->refclkunit);

   /* Open the TSYNC device */
   hBoard.file_descriptor = open(device, O_RDONLY | O_NDELAY, 0777);

   /* If error opening TSYNC device... */
   if (hBoard.file_descriptor < 0)
   {
       msyslog(LOG_ERR, "Couldn't open device");
       return;
   }

   /* If error while initializing the board... */
   if (ioctl(hBoard.file_descriptor, IOCTL_TPRO_OPEN, &hBoard) < 0)
   {
       msyslog(LOG_ERR, "Couldn't initialize device");
       close(hBoard.file_descriptor);
       return;
   }

   /* Allocate memory for ioctl message */
   itAllocationLength =
       (sizeof(ioctl_trans_di) - DI_PAYLOADS_STARTER_LENGTH) +
       TSYNC_REF_IN_LEN + TSYNC_REF_MAX_OUT_LEN;

   it = (ioctl_trans_di*)alloca(itAllocationLength);
   if (it == NULL) {
       msyslog(LOG_ERR, "Couldn't allocate transaction memory - Reference");
       return;
   }

   /* Build SS_GetRef ioctl message */
   it->dest             = TSYNC_REF_DEST_ID;
   it->iid              = TSYNC_REF_IID;
   it->inPayloadOffset  = TSYNC_REF_IN_PYLD_OFF;
   it->inLength         = TSYNC_REF_IN_LEN;
   it->outPayloadOffset = TSYNC_REF_OUT_PYLD_OFF;
   it->maxOutLength     = TSYNC_REF_MAX_OUT_LEN;
   it->actualOutLength  = 0;
   it->status           = 0;
   memset(it->payloads, 0, TSYNC_REF_MAX_OUT_LEN);

   /* Read the reference from the TSYNC-PCI device */
   err = ioctl(hBoard.file_descriptor,
                IOCTL_TSYNC_GET,
               (char *)it);

   /* Allocate memory for ioctl message */
   itAllocationLength1 =
       (sizeof(ioctl_trans_di) - DI_PAYLOADS_STARTER_LENGTH) +
       TSYNC_TMSCL_IN_LEN + TSYNC_TMSCL_MAX_OUT_LEN;

   it1 = (ioctl_trans_di*)alloca(itAllocationLength1);
   if (it1 == NULL) {
       msyslog(LOG_ERR, "Couldn't allocate transaction memory - Time Scale");
       return;
   }

   /* Build CS_GetTimeScale ioctl message */
   it1->dest             = TSYNC_TMSCL_DEST_ID;
   it1->iid              = TSYNC_TMSCL_IID;
   it1->inPayloadOffset  = TSYNC_TMSCL_IN_PYLD_OFF;
   it1->inLength         = TSYNC_TMSCL_IN_LEN;
   it1->outPayloadOffset = TSYNC_TMSCL_OUT_PYLD_OFF;
   it1->maxOutLength     = TSYNC_TMSCL_MAX_OUT_LEN;
   it1->actualOutLength  = 0;
   it1->status           = 0;
   memset(it1->payloads, 0, TSYNC_TMSCL_MAX_OUT_LEN);

   /* Read the Time Scale info from the TSYNC-PCI device */
   err1 = ioctl(hBoard.file_descriptor,
                IOCTL_TSYNC_GET,
                (char *)it1);

   /* Allocate memory for ioctl message */
   itAllocationLength2 =
       (sizeof(ioctl_trans_di) - DI_PAYLOADS_STARTER_LENGTH) +
       TSYNC_LEAP_IN_LEN + TSYNC_LEAP_MAX_OUT_LEN;

   it2 = (ioctl_trans_di*)alloca(itAllocationLength2);
   if (it2 == NULL) {
       msyslog(LOG_ERR, "Couldn't allocate transaction memory - Leap Second");
       return;
   }

   /* Build CS_GetLeapSec ioctl message */
   it2->dest             = TSYNC_LEAP_DEST_ID;
   it2->iid              = TSYNC_LEAP_IID;
   it2->inPayloadOffset  = TSYNC_LEAP_IN_PYLD_OFF;
   it2->inLength         = TSYNC_LEAP_IN_LEN;
   it2->outPayloadOffset = TSYNC_LEAP_OUT_PYLD_OFF;
   it2->maxOutLength     = TSYNC_LEAP_MAX_OUT_LEN;
   it2->actualOutLength  = 0;
   it2->status           = 0;
   memset(it2->payloads, 0, TSYNC_LEAP_MAX_OUT_LEN);

   /* Read the leap seconds info from the TSYNC-PCI device */
   err2 = ioctl(hBoard.file_descriptor,
                IOCTL_TSYNC_GET,
                (char *)it2);

   pp = peer->procptr;
   up = (TsyncUnit*)pp->unitptr;

   /* Read the time from the TSYNC-PCI device */
   err3 = ioctl(hBoard.file_descriptor,
                IOCTL_TPRO_GET_NTP_TIME,
                (char *)&TimeContext);

   /* Close the TSYNC device */
   close(hBoard.file_descriptor);

   // Check for errors
   if ((err < 0) ||(err1 < 0) || (err2 < 0) || (err3 < 0) ||
       (it->status != 0) || (it1->status != 0) || (it2->status != 0) ||
       (it->actualOutLength  != TSYNC_REF_OUT_LEN) ||
       (it1->actualOutLength != TSYNC_TMSCL_OUT_LEN) ||
       (it2->actualOutLength != TSYNC_LEAP_OUT_LEN)) {
       refclock_report(peer, CEVNT_FAULT);
       return;
   }

   // Extract reference identifiers from ioctl payload
   memset(timeRef, '\0', sizeof(timeRef));
   memset(ppsRef, '\0', sizeof(ppsRef));
   pRefObj = (void *)it->payloads;
   memcpy(timeRef, pRefObj->time, TSYNC_REF_LEN);
   memcpy(ppsRef, pRefObj->pps, TSYNC_REF_LEN);

   // Extract the Clock Service Time Scale and convert to correct byte order
   memcpy(&tmscl, it1->payloads, sizeof(tmscl));
   tmscl = ntohl(tmscl);

   // Extract leap second info from ioctl payload and perform byte swapping
   for (i = 0; i < (sizeof(leapSec) / 4); i++)
   {
       for (j = 0; j < 4; j++)
       {
           ((unsigned char*)&leapSec)[(i * 4) + j] =
                   ((unsigned char*)(it2->payloads))[(i * 4) + (3 - j)];
       }
   }

   // Determine time reference ID from reference name
   for (i = 0; RefIdLookupTbl[i].pRef != NULL; i++)
   {
      // Search RefID table
      if (strstr(timeRef, RefIdLookupTbl[i].pRef) != NULL)
      {
         // Found the matching string
         break;
      }
   }

   // Determine pps reference ID from reference name
   for (j = 0; RefIdLookupTbl[j].pRef != NULL; j++)
   {
      // Search RefID table
      if (strstr(ppsRef, RefIdLookupTbl[j].pRef) != NULL)
      {
         // Found the matching string
         break;
      }
   }

   // Determine synchronization state from flags
   synch = (TimeContext.timeObj.flags == 0x4) ? 1 : 0;

   // Pull seconds information from time object
   seconds = (double) (TimeContext.timeObj.secsDouble);
   seconds /= (double) 1000000.0;

   /*
   ** Convert the number of microseconds to double and then place in the
   ** peer's last received long floating point format.
   */
   DTOLFP(((double)TimeContext.tv.tv_usec / 1000000.0), &pp->lastrec);

   /*
   ** The specTimeStamp is the number of seconds since 1/1/1970, while the
   ** peer's lastrec time should be compatible with NTP which is seconds since
   ** 1/1/1900.  So Add the number of seconds between 1900 and 1970 to the
   ** specTimeStamp and place in the peer's lastrec long floating point struct.
   */
   pp->lastrec.Ul_i.Xl_ui += (unsigned int)TimeContext.tv.tv_sec +
                                           SECONDS_1900_TO_1970;

   pp->polls++;

   /*
   **  set the reference clock object
   */
   sprintf(pp->a_lastcode, "%03d %02d:%02d:%02.6f",
           TimeContext.timeObj.days, TimeContext.timeObj.hours,
           TimeContext.timeObj.minutes, seconds);

   pp->lencode = strlen (pp->a_lastcode);
   pp->day     = TimeContext.timeObj.days;
   pp->hour    = TimeContext.timeObj.hours;
   pp->minute  = TimeContext.timeObj.minutes;
   pp->second  = (int) seconds;
   seconds     = (seconds - (double) (pp->second / 1.0)) * 1000000000;
   pp->nsec    = (long) seconds;

   /*
   **  calculate year start
   */
   jt.year       = TimeContext.timeObj.year;
   jt.yearday    = 1;
   jt.monthday   = 1;
   jt.month      = 1;
   jt.hour       = 0;
   jt.minute     = 0;
   jt.second     = 0;
   pp->yearstart = caltontp(&jt);

   // Calculate and report reference clock offset
   offset.l_ui = (long)(((pp->day - 1) * 24) + pp->hour + GMT);
   offset.l_ui = (offset.l_ui * 60) + (long)pp->minute;
   offset.l_ui = (offset.l_ui * 60) + (long)pp->second;
   offset.l_ui = offset.l_ui + (long)pp->yearstart;
   offset.l_uf = 0;
   DTOLFP(pp->nsec / 1e9, &ltemp);
   L_ADD(&offset, &ltemp);
   refclock_process_offset(pp, offset, pp->lastrec,
                           pp->fudgetime1);

   // KTS in sync
   if (synch) {
       // Subtract leap second info by one second to determine effective day
       ApplyTimeOffset(&(leapSec.utcDate), -1);

       // If there is a leap second today and the KTS is using a time scale
       // which handles leap seconds then
       if ((tmscl != TIME_SCALE_GPS) && (tmscl != TIME_SCALE_TAI) &&
           (leapSec.utcDate.year == (unsigned int)TimeContext.timeObj.year) &&
           (leapSec.utcDate.doy  == (unsigned int)TimeContext.timeObj.days))
       {
           // If adding a second
           if (leapSec.offset == 1)
           {
               pp->leap = LEAP_ADDSECOND;
           }
           // Else if removing a second
           else if (leapSec.offset == -1)
           {
               pp->leap = LEAP_DELSECOND;
           }
           // Else report no leap second pending (no handling of offsets
           // other than +1 or -1)
           else
           {
               pp->leap = LEAP_NOWARNING;
           }
       }
       // Else report no leap second pending
       else
       {
           pp->leap = LEAP_NOWARNING;
       }

       peer->leap = pp->leap;
       refclock_report(peer, CEVNT_NOMINAL);

       // If reference name reported, then not in holdover
       if ((RefIdLookupTbl[i].pRef != NULL) &&
           (RefIdLookupTbl[j].pRef != NULL))
       {
           // Determine if KTS being synchronized by host (identified as
           // "LOCL")
           if ((strcmp(RefIdLookupTbl[i].pRefId, TSYNC_REF_LOCAL) == 0) ||
               (strcmp(RefIdLookupTbl[j].pRefId, TSYNC_REF_LOCAL) == 0))
           {
               // Clear prefer flag
               peer->flags &= ~FLAG_PREFER;

               // Set reference clock stratum level as unusable
               pp->stratum   = STRATUM_UNSPEC;
               peer->stratum = pp->stratum;

               // If a valid peer is available
               if ((sys_peer != NULL) && (sys_peer != peer))
               {
                   // Store reference peer stratum level and ID
                   up->refStratum = sys_peer->stratum;
                   up->refId      = addr2refid(&sys_peer->srcadr);
               }
           }
           else
           {
               // Restore prefer flag
               peer->flags |= up->refPrefer;

               // Store reference stratum as local clock
               up->refStratum = TSYNC_LCL_STRATUM;
               strncpy((char *)&up->refId, RefIdLookupTbl[j].pRefId,
                   TSYNC_REF_LEN);

               // Set reference clock stratum level as local clock
               pp->stratum   = TSYNC_LCL_STRATUM;
               peer->stratum = pp->stratum;
           }

           // Update reference name
           strncpy((char *)&pp->refid, RefIdLookupTbl[j].pRefId,
               TSYNC_REF_LEN);
           peer->refid = pp->refid;
       }
       // Else in holdover
       else
       {
           // Restore prefer flag
           peer->flags |= up->refPrefer;

           // Update reference ID to saved ID
           pp->refid   = up->refId;
           peer->refid = pp->refid;

           // Update stratum level to saved stratum level
           pp->stratum   = up->refStratum;
           peer->stratum = pp->stratum;
       }
   }
   // Else KTS not in sync
   else {
       // Place local identifier in peer RefID
       strncpy((char *)&pp->refid, TSYNC_REF_LOCAL, TSYNC_REF_LEN);
       peer->refid = pp->refid;

       // Report not in sync
       pp->leap   = LEAP_NOTINSYNC;
       peer->leap = pp->leap;
   }

   if (pp->coderecv == pp->codeproc) {
       refclock_report(peer, CEVNT_TIMEOUT);
       return;
   }

   record_clock_stats(&peer->srcadr, pp->a_lastcode);
   refclock_receive(peer);

   /* Increment the number of times the reference has been polled */
   pp->polls++;

} /* End - tsync_poll() */


////////////////////////////////////////////////////////////////////////////////
// Function:    ApplyTimeOffset
// Description: The ApplyTimeOffset function adds an offset (in seconds) to a
//              specified date and time.  The specified date and time is passed
//              back after being modified.
//
// Assumptions: 1. Every fourth year is a leap year.  Therefore, this function
//                 is only accurate through Feb 28, 2100.
////////////////////////////////////////////////////////////////////////////////
void ApplyTimeOffset(DoyTimeObj* pDt, int off)
{
   SecTimeObj st;                  // Time, in seconds


   // Convert date and time to seconds
   SecTimeFromDoyTime(&st, pDt);

   // Apply offset
   st.seconds = (int)((signed long long)st.seconds + (signed long long)off);

   // Convert seconds to date and time
   DoyTimeFromSecTime(pDt, &st);

} // End ApplyTimeOffset


////////////////////////////////////////////////////////////////////////////////
// Function:    SecTimeFromDoyTime
// Description: The SecTimeFromDoyTime function converts a specified date
//              and time into a count of seconds since the base time.  This
//              function operates across the range Base Time to Max Time for
//              the system.
//
// Assumptions: 1. A leap year is any year evenly divisible by 4.  Therefore,
//                 this function is only accurate through Feb 28, 2100.
//              2. Conversion does not account for leap seconds.
////////////////////////////////////////////////////////////////////////////////
void SecTimeFromDoyTime(SecTimeObj* pSt, DoyTimeObj* pDt)
{
   unsigned int yrs;               // Years
   unsigned int lyrs;              // Leap years


   // Start with accumulated time of 0
   pSt->seconds  = 0;

   // Calculate the number of years and leap years
   yrs           = pDt->year - TSYNC_TIME_BASE_YEAR;
   lyrs          = (yrs + 1) / 4;

   // Convert leap years and years
   pSt->seconds += lyrs           * SECSPERLEAPYEAR;
   pSt->seconds += (yrs - lyrs)   * SECSPERYEAR;

   // Convert days, hours, minutes and seconds
   pSt->seconds += (pDt->doy - 1) * SECSPERDAY;
   pSt->seconds += pDt->hour      * SECSPERHR;
   pSt->seconds += pDt->minute    * SECSPERMIN;
   pSt->seconds += pDt->second;

   // Copy the subseconds count
   pSt->ns       = pDt->ns;

} // End SecTimeFromDoyTime


////////////////////////////////////////////////////////////////////////////////
// Function:    DoyTimeFromSecTime
// Description: The DoyTimeFromSecTime function converts a specified count
//              of seconds since the start of our base time into a SecTimeObj
//              structure.
//
// Assumptions: 1. A leap year is any year evenly divisible by 4.  Therefore,
//                 this function is only accurate through Feb 28, 2100.
//              2. Conversion does not account for leap seconds.
////////////////////////////////////////////////////////////////////////////////
void DoyTimeFromSecTime(DoyTimeObj* pDt, SecTimeObj* pSt)
{
   signed long long secs;          // Seconds accumulator variable
   unsigned int     yrs;           // Years accumulator variable
   unsigned int     doys;          // Days accumulator variable
   unsigned int     hrs;           // Hours accumulator variable
   unsigned int     mins;          // Minutes accumulator variable


   // Convert the seconds count into a signed 64-bit number for calculations
   secs  = (signed long long)(pSt->seconds);

   // Calculate the number of 4 year chunks
   yrs   = (unsigned int)((secs /
                          ((SECSPERYEAR * 3) + SECSPERLEAPYEAR)) * 4);
   secs %= ((SECSPERYEAR * 3) + SECSPERLEAPYEAR);

   // If there is at least a normal year worth of time left
   if (secs >= SECSPERYEAR)
   {
       // Increment the number of years and subtract a normal year of time
       yrs++;
       secs -= SECSPERYEAR;
   }

   // If there is still at least a normal year worth of time left
   if (secs >= SECSPERYEAR)
   {
       // Increment the number of years and subtract a normal year of time
       yrs++;
       secs -= SECSPERYEAR;
   }

   // If there is still at least a leap year worth of time left
   if (secs >= SECSPERLEAPYEAR)
   {
       // Increment the number of years and subtract a leap year of time
       yrs++;
       secs -= SECSPERLEAPYEAR;
   }

   // Calculate the day of year as the number of days left, then add 1
   // because months start on the 1st.
   doys  = (unsigned int)((secs / SECSPERDAY) + 1);
   secs %= SECSPERDAY;

   // Calculate the hour
   hrs   = (unsigned int)(secs / SECSPERHR);
   secs %= SECSPERHR;

   // Calculate the minute
   mins  = (unsigned int)(secs / SECSPERMIN);
   secs %= SECSPERMIN;

   // Fill in the doytime structure
   pDt->year   = yrs + TSYNC_TIME_BASE_YEAR;
   pDt->doy    = doys;
   pDt->hour   = hrs;
   pDt->minute = mins;
   pDt->second = (unsigned int)secs;
   pDt->ns     = pSt->ns;

} // End DoyTimeFromSecTime

#else
NONEMPTY_TRANSLATION_UNIT
#endif /* REFCLOCK */