1290001Sglebius/*******************************************************************************
2290001Sglebius*
3290001Sglebius*  Module  : refclock_tsyncpci.c
4290001Sglebius*  Date    : 09/08/08
5290001Sglebius*  Purpose : Implements a reference clock driver for the NTP daemon.  This
6290001Sglebius*            reference clock driver provides a means to communicate with
7290001Sglebius*            the Spectracom TSYNC PCI timing devices and use them as a time
8290001Sglebius*            source.
9290001Sglebius*
10290001Sglebius*  (C) Copyright 2008 Spectracom Corporation
11290001Sglebius*
12290001Sglebius*  This software is provided by Spectracom Corporation 'as is' and
13290001Sglebius*  any express or implied warranties, including, but not limited to, the
14290001Sglebius*  implied warranties of merchantability and fitness for a particular purpose
15290001Sglebius*  are disclaimed.  In no event shall Spectracom Corporation be liable
16290001Sglebius*  for any direct, indirect, incidental, special, exemplary, or consequential
17290001Sglebius*  damages (including, but not limited to, procurement of substitute goods
18290001Sglebius*  or services; loss of use, data, or profits; or business interruption)
19290001Sglebius*  however caused and on any theory of liability, whether in contract, strict
20290001Sglebius*  liability, or tort (including negligence or otherwise) arising in any way
21290001Sglebius*  out of the use of this software, even if advised of the possibility of
22290001Sglebius*  such damage.
23290001Sglebius*
24290001Sglebius*  This software is released for distribution according to the NTP copyright
25290001Sglebius*  and license contained in html/copyright.html of NTP source.
26290001Sglebius*
27290001Sglebius*******************************************************************************/
28290001Sglebius#ifdef HAVE_CONFIG_H
29290001Sglebius#include <config.h>
30290001Sglebius#endif
31290001Sglebius
32290001Sglebius#if defined(REFCLOCK) && defined(CLOCK_TSYNCPCI)
33290001Sglebius
34290001Sglebius#include <asm/ioctl.h>
35290001Sglebius#ifdef HAVE_SYS_IOCTL_H
36290001Sglebius# include <sys/ioctl.h>
37290001Sglebius#endif
38290001Sglebius
39290001Sglebius#include <stdio.h>
40290001Sglebius#include <ctype.h>
41290001Sglebius#include <netinet/in.h>
42290001Sglebius
43290001Sglebius
44290001Sglebius#include "ntpd.h"
45290001Sglebius#include "ntp_io.h"
46290001Sglebius#include "ntp_refclock.h"
47290001Sglebius#include "ntp_unixtime.h"
48290001Sglebius#include "ntp_stdlib.h"
49290001Sglebius#include "ntp_calendar.h"
50290001Sglebius
51290001Sglebius
52290001Sglebius/*******************************************************************************
53290001Sglebius**
54290001Sglebius** This driver supports the Spectracom TSYNC PCI GPS receiver.  It requires
55290001Sglebius** that the tsyncpci.o device driver be installed and loaded.
56290001Sglebius**
57290001Sglebius*******************************************************************************/
58290001Sglebius
59290001Sglebius#define TSYNC_PCI_REVISION "1.11"
60290001Sglebius
61290001Sglebius/*
62290001Sglebius** TPRO interface definitions
63290001Sglebius*/
64290001Sglebius#define DEVICE      "/dev/tsyncpci"             /* device name */
65290001Sglebius#define PRECISION   (-20)                       /* precision assumed (1 us) */
66290001Sglebius#define DESCRIPTION "Spectracom TSYNC-PCI"      /* WRU */
67290001Sglebius
68290001Sglebius#define SECONDS_1900_TO_1970 (2208988800U)
69290001Sglebius
70290001Sglebius#define TSYNC_REF_IID               (0x2500)    // SS CAI, REF IID
71290001Sglebius#define TSYNC_REF_DEST_ID           (0x0001)    // KTS Firmware
72290001Sglebius#define TSYNC_REF_IN_PYLD_OFF       (0)
73290001Sglebius#define TSYNC_REF_IN_LEN            (0)
74290001Sglebius#define TSYNC_REF_OUT_PYLD_OFF      (0)
75290001Sglebius#define TSYNC_REF_OUT_LEN           (8)
76290001Sglebius#define TSYNC_REF_MAX_OUT_LEN       (16)
77290001Sglebius#define TSYNC_REF_PYLD_LEN          (TSYNC_REF_IN_LEN +                     \
78290001Sglebius                                     TSYNC_REF_MAX_OUT_LEN)
79290001Sglebius#define TSYNC_REF_LEN               (4)
80290001Sglebius#define TSYNC_REF_LOCAL             ("LOCL")
81290001Sglebius
82290001Sglebius#define TSYNC_TMSCL_IID              (0x2301)    // CS CAI, TIMESCALE IID
83290001Sglebius#define TSYNC_TMSCL_DEST_ID          (0x0001)    // KTS Firmware
84290001Sglebius#define TSYNC_TMSCL_IN_PYLD_OFF      (0)
85290001Sglebius#define TSYNC_TMSCL_IN_LEN           (0)
86290001Sglebius#define TSYNC_TMSCL_OUT_PYLD_OFF     (0)
87290001Sglebius#define TSYNC_TMSCL_OUT_LEN          (4)
88290001Sglebius#define TSYNC_TMSCL_MAX_OUT_LEN      (12)
89290001Sglebius#define TSYNC_TMSCL_PYLD_LEN         (TSYNC_TMSCL_IN_LEN +                    \
90290001Sglebius                                     TSYNC_TMSCL_MAX_OUT_LEN)
91290001Sglebius
92290001Sglebius#define TSYNC_LEAP_IID              (0x2307)    // CS CAI, LEAP SEC IID
93290001Sglebius#define TSYNC_LEAP_DEST_ID          (0x0001)    // KTS Firmware
94290001Sglebius#define TSYNC_LEAP_IN_PYLD_OFF      (0)
95290001Sglebius#define TSYNC_LEAP_IN_LEN           (0)
96290001Sglebius#define TSYNC_LEAP_OUT_PYLD_OFF     (0)
97290001Sglebius#define TSYNC_LEAP_OUT_LEN          (28)
98290001Sglebius#define TSYNC_LEAP_MAX_OUT_LEN      (36)
99290001Sglebius#define TSYNC_LEAP_PYLD_LEN         (TSYNC_LEAP_IN_LEN +                    \
100290001Sglebius                                     TSYNC_LEAP_MAX_OUT_LEN)
101290001Sglebius
102290001Sglebius// These define the base date/time of the system clock.  The system time will
103290001Sglebius// be tracked as the number of seconds from this date/time.
104290001Sglebius#define TSYNC_TIME_BASE_YEAR        (1970) // earliest acceptable year
105290001Sglebius
106290001Sglebius#define TSYNC_LCL_STRATUM           (0)
107290001Sglebius
108290001Sglebius/*
109290001Sglebius** TSYNC Time Scales type
110290001Sglebius*/
111290001Sglebiustypedef enum
112290001Sglebius{
113290001Sglebius    TIME_SCALE_UTC    = 0,   // Universal Coordinated Time
114290001Sglebius    TIME_SCALE_TAI    = 1,   // International Atomic Time
115290001Sglebius    TIME_SCALE_GPS    = 2,   // Global Positioning System
116290001Sglebius    TIME_SCALE_LOCAL  = 3,   // UTC w/local rules for time zone and DST
117290001Sglebius    NUM_TIME_SCALES   = 4,   // Number of time scales
118290001Sglebius
119290001Sglebius    TIME_SCALE_MAX    = 15   // Maximum number of timescales
120290001Sglebius
121290001Sglebius} TIME_SCALE;
122290001Sglebius
123290001Sglebius/*
124290001Sglebius** TSYNC Board Object
125290001Sglebius*/
126290001Sglebiustypedef struct BoardObj {
127290001Sglebius
128290001Sglebius  int            file_descriptor;
129290001Sglebius  unsigned short devid;
130290001Sglebius  unsigned short options;
131290001Sglebius  unsigned char  firmware[5];
132290001Sglebius  unsigned char  FPGA[5];
133290001Sglebius  unsigned char  driver[7];
134290001Sglebius
135290001Sglebius} BoardObj;
136290001Sglebius
137290001Sglebius/*
138290001Sglebius** TSYNC Time Object
139290001Sglebius*/
140290001Sglebiustypedef struct TimeObj {
141290001Sglebius
142290001Sglebius  unsigned char  syncOption;  /* -M option */
143290001Sglebius  unsigned int   secsDouble;  /* seconds floating pt */
144290001Sglebius  unsigned char  seconds;     /* seconds whole num */
145290001Sglebius  unsigned char  minutes;
146290001Sglebius  unsigned char  hours;
147290001Sglebius  unsigned short days;
148290001Sglebius  unsigned short year;
149290001Sglebius  unsigned short flags;      /* bit 2 SYNC, bit 1 TCODE; all others 0 */
150290001Sglebius
151290001Sglebius} TimeObj;
152290001Sglebius
153290001Sglebius/*
154290001Sglebius** NTP Time Object
155290001Sglebius*/
156290001Sglebiustypedef struct NtpTimeObj {
157290001Sglebius
158290001Sglebius    TimeObj        timeObj;
159290001Sglebius    struct timeval tv;
160290001Sglebius    unsigned int   refId;
161290001Sglebius
162290001Sglebius} NtpTimeObj;
163290001Sglebius/*
164290001Sglebius** TSYNC Supervisor Reference Object
165290001Sglebius*/
166290001Sglebiustypedef struct ReferenceObj {
167290001Sglebius
168290001Sglebius    char time[TSYNC_REF_LEN];
169290001Sglebius    char pps[TSYNC_REF_LEN];
170290001Sglebius
171290001Sglebius} ReferenceObj;
172290001Sglebius
173290001Sglebius/*
174290001Sglebius** TSYNC Seconds Time Object
175290001Sglebius*/
176290001Sglebiustypedef struct SecTimeObj
177290001Sglebius{
178290001Sglebius    unsigned int seconds;
179290001Sglebius    unsigned int ns;
180290001Sglebius}
181290001SglebiusSecTimeObj;
182290001Sglebius
183290001Sglebius/*
184290001Sglebius** TSYNC DOY Time Object
185290001Sglebius*/
186290001Sglebiustypedef struct DoyTimeObj
187290001Sglebius{
188290001Sglebius    unsigned int year;
189290001Sglebius    unsigned int doy;
190290001Sglebius    unsigned int hour;
191290001Sglebius    unsigned int minute;
192290001Sglebius    unsigned int second;
193290001Sglebius    unsigned int ns;
194290001Sglebius}
195290001SglebiusDoyTimeObj;
196290001Sglebius
197290001Sglebius/*
198290001Sglebius** TSYNC Leap Second Object
199290001Sglebius*/
200290001Sglebiustypedef struct LeapSecondObj
201290001Sglebius{
202290001Sglebius    int        offset;
203290001Sglebius    DoyTimeObj utcDate;
204290001Sglebius}
205290001SglebiusLeapSecondObj;
206290001Sglebius
207290001Sglebius/*
208290001Sglebius * structures for ioctl interactions with driver
209290001Sglebius */
210290001Sglebius#define DI_PAYLOADS_STARTER_LENGTH 4
211290001Sglebiustypedef struct ioctl_trans_di {
212290001Sglebius
213290001Sglebius    // input parameters
214290001Sglebius    uint16_t        dest;
215290001Sglebius    uint16_t        iid;
216290001Sglebius
217290001Sglebius    uint32_t        inPayloadOffset;
218290001Sglebius    uint32_t        inLength;
219290001Sglebius    uint32_t        outPayloadOffset;
220290001Sglebius    uint32_t        maxOutLength;
221290001Sglebius
222290001Sglebius    // output parameters
223290001Sglebius    uint32_t        actualOutLength;
224290001Sglebius    int32_t         status;
225290001Sglebius
226290001Sglebius    // Input and output
227290001Sglebius
228290001Sglebius    // The payloads field MUST be last in ioctl_trans_di.
229290001Sglebius    uint8_t         payloads[DI_PAYLOADS_STARTER_LENGTH];
230290001Sglebius
231290001Sglebius}ioctl_trans_di;
232290001Sglebius
233290001Sglebius/*
234290001Sglebius * structure for looking up a reference ID from a reference name
235290001Sglebius */
236290001Sglebiustypedef struct
237290001Sglebius{
238290001Sglebius    const char* pRef;           // KTS Reference Name
239290001Sglebius    const char* pRefId;         // NTP Reference ID
240290001Sglebius
241290001Sglebius} RefIdLookup;
242290001Sglebius
243290001Sglebius/*
244290001Sglebius * unit control structure
245290001Sglebius */
246290001Sglebiustypedef struct  {
247290001Sglebius    uint32_t refPrefer;         // Reference prefer flag
248290001Sglebius    uint32_t refId;             // Host peer reference ID
249290001Sglebius    uint8_t  refStratum;        // Host peer reference stratum
250290001Sglebius
251290001Sglebius} TsyncUnit;
252290001Sglebius
253290001Sglebius/*
254290001Sglebius**  Function prototypes
255290001Sglebius*/
256290001Sglebiusstatic void tsync_poll     (int unit, struct peer *);
257290001Sglebiusstatic void tsync_shutdown (int, struct peer *);
258290001Sglebiusstatic int  tsync_start    (int, struct peer *);
259290001Sglebius
260290001Sglebius/*
261290001Sglebius**  Helper functions
262290001Sglebius*/
263290001Sglebiusstatic void ApplyTimeOffset    (DoyTimeObj* pDt, int off);
264290001Sglebiusstatic void SecTimeFromDoyTime (SecTimeObj* pSt, DoyTimeObj* pDt);
265290001Sglebiusstatic void DoyTimeFromSecTime (DoyTimeObj* pDt, SecTimeObj* pSt);
266290001Sglebius
267290001Sglebius/*
268290001Sglebius**  Transfer vector
269290001Sglebius*/
270290001Sglebiusstruct refclock refclock_tsyncpci = {
271290001Sglebius    tsync_start,    /* start up driver */
272290001Sglebius    tsync_shutdown, /* shut down driver */
273290001Sglebius    tsync_poll,     /* transmit poll message */
274290001Sglebius    noentry,        /* not used (old tsync_control) */
275290001Sglebius    noentry,        /* initialize driver (not used) */
276290001Sglebius    noentry,        /* not used (old tsync_buginfo) */
277290001Sglebius    NOFLAGS         /* not used */
278290001Sglebius};
279290001Sglebius
280290001Sglebius/*
281290001Sglebius * Reference ID lookup table
282290001Sglebius */
283290001Sglebiusstatic RefIdLookup RefIdLookupTbl[] =
284290001Sglebius{
285290001Sglebius    {"gps",  "GPS"},
286290001Sglebius    {"ir",   "IRIG"},
287290001Sglebius    {"hvq",  "HVQ"},
288290001Sglebius    {"frq",  "FREQ"},
289290001Sglebius    {"mdm",  "ACTS"},
290290001Sglebius    {"epp",  "PPS"},
291290001Sglebius    {"ptp",  "PTP"},
292290001Sglebius    {"asc",  "ATC"},
293290001Sglebius    {"hst0", "USER"},
294290001Sglebius    {"hst",  TSYNC_REF_LOCAL},
295290001Sglebius    {"self", TSYNC_REF_LOCAL},
296290001Sglebius    {NULL,   NULL}
297290001Sglebius};
298290001Sglebius
299290001Sglebius/*******************************************************************************
300290001Sglebius**          IOCTL DEFINITIONS
301290001Sglebius*******************************************************************************/
302290001Sglebius#define IOCTL_TPRO_ID            't'
303290001Sglebius#define IOCTL_TPRO_OPEN          _IOWR(IOCTL_TPRO_ID, 0,  BoardObj)
304290001Sglebius#define IOCTL_TPRO_GET_NTP_TIME  _IOWR(IOCTL_TPRO_ID, 25, NtpTimeObj)
305290001Sglebius#define IOCTL_TSYNC_GET          _IOWR(IOCTL_TPRO_ID, 26, ioctl_trans_di)
306290001Sglebius
307290001Sglebius/******************************************************************************
308290001Sglebius *
309290001Sglebius * Function:    tsync_start()
310290001Sglebius * Description: Used to intialize the Spectracom TSYNC reference driver.
311290001Sglebius *
312290001Sglebius * Parameters:
313290001Sglebius *     IN:  unit - not used.
314290001Sglebius *         *peer - pointer to this reference clock's peer structure
315290001Sglebius *     Returns: 0 - unsuccessful
316290001Sglebius *              1 - successful
317290001Sglebius *
318290001Sglebius*******************************************************************************/
319290001Sglebiusstatic int tsync_start(int unit, struct peer *peer)
320290001Sglebius{
321290001Sglebius    struct refclockproc *pp;
322290001Sglebius    TsyncUnit           *up;
323290001Sglebius
324290001Sglebius
325290001Sglebius    /*
326290001Sglebius    **  initialize reference clock and peer parameters
327290001Sglebius    */
328290001Sglebius    pp                = peer->procptr;
329290001Sglebius    pp->clockdesc     = DESCRIPTION;
330290001Sglebius    pp->io.clock_recv = noentry;
331290001Sglebius    pp->io.srcclock   = peer;
332290001Sglebius    pp->io.datalen    = 0;
333290001Sglebius    peer->precision   = PRECISION;
334290001Sglebius
335290001Sglebius    // Allocate and initialize unit structure
336290001Sglebius    if (!(up = (TsyncUnit*)emalloc(sizeof(TsyncUnit))))
337290001Sglebius    {
338290001Sglebius        return (0);
339290001Sglebius    }
340290001Sglebius
341290001Sglebius    // Store reference preference
342290001Sglebius    up->refPrefer = peer->flags & FLAG_PREFER;
343290001Sglebius
344290001Sglebius    // Initialize reference stratum level and ID
345290001Sglebius    up->refStratum = STRATUM_UNSPEC;
346290001Sglebius    strncpy((char *)&up->refId, TSYNC_REF_LOCAL, TSYNC_REF_LEN);
347290001Sglebius
348290001Sglebius    // Attach unit structure
349290001Sglebius    pp->unitptr = (caddr_t)up;
350290001Sglebius
351290001Sglebius    /* Declare our refId as local in the beginning because we do not know
352290001Sglebius     * what our actual refid is yet.
353290001Sglebius     */
354290001Sglebius    strncpy((char *)&pp->refid, TSYNC_REF_LOCAL, TSYNC_REF_LEN);
355290001Sglebius
356290001Sglebius    return (1);
357290001Sglebius
358290001Sglebius} /* End - tsync_start() */
359290001Sglebius
360290001Sglebius/*******************************************************************************
361290001Sglebius**
362290001Sglebius** Function:    tsync_shutdown()
363290001Sglebius** Description: Handles anything related to shutting down the reference clock
364290001Sglebius**              driver. Nothing at this point in time.
365290001Sglebius**
366290001Sglebius** Parameters:
367290001Sglebius**     IN:  unit - not used.
368290001Sglebius**         *peer - pointer to this reference clock's peer structure
369290001Sglebius**     Returns: none.
370290001Sglebius**
371290001Sglebius*******************************************************************************/
372290001Sglebiusstatic void tsync_shutdown(int unit, struct peer *peer)
373290001Sglebius{
374290001Sglebius
375290001Sglebius} /* End - tsync_shutdown() */
376290001Sglebius
377290001Sglebius/******************************************************************************
378290001Sglebius *
379290001Sglebius * Function:    tsync_poll()
380290001Sglebius * Description: Retrieve time from the TSYNC device.
381290001Sglebius *
382290001Sglebius * Parameters:
383290001Sglebius *     IN:  unit - not used.
384290001Sglebius *         *peer - pointer to this reference clock's peer structure
385290001Sglebius *     Returns: none.
386290001Sglebius *
387290001Sglebius*******************************************************************************/
388290001Sglebiusstatic void tsync_poll(int unit, struct peer *peer)
389290001Sglebius{
390290001Sglebius    char                 device[32];
391290001Sglebius    struct refclockproc *pp;
392290001Sglebius    struct calendar      jt;
393290001Sglebius    TsyncUnit           *up;
394290001Sglebius    unsigned char        synch;
395290001Sglebius    double               seconds;
396290001Sglebius    int                  err;
397290001Sglebius    int                  err1;
398290001Sglebius    int                  err2;
399290001Sglebius    int                  err3;
400290001Sglebius    int                  i;
401290001Sglebius    int                  j;
402290001Sglebius    unsigned int         itAllocationLength;
403290001Sglebius    unsigned int         itAllocationLength1;
404290001Sglebius    unsigned int         itAllocationLength2;
405290001Sglebius    NtpTimeObj           TimeContext;
406290001Sglebius    BoardObj             hBoard;
407290001Sglebius    char                 timeRef[TSYNC_REF_LEN + 1];
408290001Sglebius    char                 ppsRef [TSYNC_REF_LEN + 1];
409290001Sglebius    TIME_SCALE           tmscl = TIME_SCALE_UTC;
410290001Sglebius    LeapSecondObj        leapSec;
411290001Sglebius    ioctl_trans_di      *it;
412290001Sglebius    ioctl_trans_di      *it1;
413290001Sglebius    ioctl_trans_di      *it2;
414290001Sglebius    l_fp                 offset;
415290001Sglebius    l_fp                 ltemp;
416290001Sglebius    ReferenceObj *	 pRefObj;
417290001Sglebius
418290001Sglebius
419290001Sglebius    /* Construct the device name */
420290001Sglebius    sprintf(device, "%s%d", DEVICE, (int)peer->refclkunit);
421290001Sglebius
422290001Sglebius    printf("Polling device number %d...\n", (int)peer->refclkunit);
423290001Sglebius
424290001Sglebius    /* Open the TSYNC device */
425290001Sglebius    hBoard.file_descriptor = open(device, O_RDONLY | O_NDELAY, 0777);
426290001Sglebius
427290001Sglebius    /* If error opening TSYNC device... */
428290001Sglebius    if (hBoard.file_descriptor < 0)
429290001Sglebius    {
430290001Sglebius        msyslog(LOG_ERR, "Couldn't open device");
431290001Sglebius        return;
432290001Sglebius    }
433290001Sglebius
434290001Sglebius    /* If error while initializing the board... */
435290001Sglebius    if (ioctl(hBoard.file_descriptor, IOCTL_TPRO_OPEN, &hBoard) < 0)
436290001Sglebius    {
437290001Sglebius        msyslog(LOG_ERR, "Couldn't initialize device");
438290001Sglebius        close(hBoard.file_descriptor);
439290001Sglebius        return;
440290001Sglebius    }
441290001Sglebius
442290001Sglebius    /* Allocate memory for ioctl message */
443290001Sglebius    itAllocationLength =
444290001Sglebius        (sizeof(ioctl_trans_di) - DI_PAYLOADS_STARTER_LENGTH) +
445290001Sglebius        TSYNC_REF_IN_LEN + TSYNC_REF_MAX_OUT_LEN;
446290001Sglebius
447290001Sglebius    it = (ioctl_trans_di*)alloca(itAllocationLength);
448290001Sglebius    if (it == NULL) {
449290001Sglebius        msyslog(LOG_ERR, "Couldn't allocate transaction memory - Reference");
450290001Sglebius        return;
451290001Sglebius    }
452290001Sglebius
453290001Sglebius    /* Build SS_GetRef ioctl message */
454290001Sglebius    it->dest             = TSYNC_REF_DEST_ID;
455290001Sglebius    it->iid              = TSYNC_REF_IID;
456290001Sglebius    it->inPayloadOffset  = TSYNC_REF_IN_PYLD_OFF;
457290001Sglebius    it->inLength         = TSYNC_REF_IN_LEN;
458290001Sglebius    it->outPayloadOffset = TSYNC_REF_OUT_PYLD_OFF;
459290001Sglebius    it->maxOutLength     = TSYNC_REF_MAX_OUT_LEN;
460290001Sglebius    it->actualOutLength  = 0;
461290001Sglebius    it->status           = 0;
462290001Sglebius    memset(it->payloads, 0, TSYNC_REF_MAX_OUT_LEN);
463290001Sglebius
464290001Sglebius    /* Read the reference from the TSYNC-PCI device */
465290001Sglebius    err = ioctl(hBoard.file_descriptor,
466290001Sglebius                 IOCTL_TSYNC_GET,
467290001Sglebius                (char *)it);
468290001Sglebius
469290001Sglebius    /* Allocate memory for ioctl message */
470290001Sglebius    itAllocationLength1 =
471290001Sglebius        (sizeof(ioctl_trans_di) - DI_PAYLOADS_STARTER_LENGTH) +
472290001Sglebius        TSYNC_TMSCL_IN_LEN + TSYNC_TMSCL_MAX_OUT_LEN;
473290001Sglebius
474290001Sglebius    it1 = (ioctl_trans_di*)alloca(itAllocationLength1);
475290001Sglebius    if (it1 == NULL) {
476290001Sglebius        msyslog(LOG_ERR, "Couldn't allocate transaction memory - Time Scale");
477290001Sglebius        return;
478290001Sglebius    }
479290001Sglebius
480290001Sglebius    /* Build CS_GetTimeScale ioctl message */
481290001Sglebius    it1->dest             = TSYNC_TMSCL_DEST_ID;
482290001Sglebius    it1->iid              = TSYNC_TMSCL_IID;
483290001Sglebius    it1->inPayloadOffset  = TSYNC_TMSCL_IN_PYLD_OFF;
484290001Sglebius    it1->inLength         = TSYNC_TMSCL_IN_LEN;
485290001Sglebius    it1->outPayloadOffset = TSYNC_TMSCL_OUT_PYLD_OFF;
486290001Sglebius    it1->maxOutLength     = TSYNC_TMSCL_MAX_OUT_LEN;
487290001Sglebius    it1->actualOutLength  = 0;
488290001Sglebius    it1->status           = 0;
489290001Sglebius    memset(it1->payloads, 0, TSYNC_TMSCL_MAX_OUT_LEN);
490290001Sglebius
491290001Sglebius    /* Read the Time Scale info from the TSYNC-PCI device */
492290001Sglebius    err1 = ioctl(hBoard.file_descriptor,
493290001Sglebius                 IOCTL_TSYNC_GET,
494290001Sglebius                 (char *)it1);
495290001Sglebius
496290001Sglebius    /* Allocate memory for ioctl message */
497290001Sglebius    itAllocationLength2 =
498290001Sglebius        (sizeof(ioctl_trans_di) - DI_PAYLOADS_STARTER_LENGTH) +
499290001Sglebius        TSYNC_LEAP_IN_LEN + TSYNC_LEAP_MAX_OUT_LEN;
500290001Sglebius
501290001Sglebius    it2 = (ioctl_trans_di*)alloca(itAllocationLength2);
502290001Sglebius    if (it2 == NULL) {
503290001Sglebius        msyslog(LOG_ERR, "Couldn't allocate transaction memory - Leap Second");
504290001Sglebius        return;
505290001Sglebius    }
506290001Sglebius
507290001Sglebius    /* Build CS_GetLeapSec ioctl message */
508290001Sglebius    it2->dest             = TSYNC_LEAP_DEST_ID;
509290001Sglebius    it2->iid              = TSYNC_LEAP_IID;
510290001Sglebius    it2->inPayloadOffset  = TSYNC_LEAP_IN_PYLD_OFF;
511290001Sglebius    it2->inLength         = TSYNC_LEAP_IN_LEN;
512290001Sglebius    it2->outPayloadOffset = TSYNC_LEAP_OUT_PYLD_OFF;
513290001Sglebius    it2->maxOutLength     = TSYNC_LEAP_MAX_OUT_LEN;
514290001Sglebius    it2->actualOutLength  = 0;
515290001Sglebius    it2->status           = 0;
516290001Sglebius    memset(it2->payloads, 0, TSYNC_LEAP_MAX_OUT_LEN);
517290001Sglebius
518290001Sglebius    /* Read the leap seconds info from the TSYNC-PCI device */
519290001Sglebius    err2 = ioctl(hBoard.file_descriptor,
520290001Sglebius                 IOCTL_TSYNC_GET,
521290001Sglebius                 (char *)it2);
522290001Sglebius
523290001Sglebius    pp = peer->procptr;
524290001Sglebius    up = (TsyncUnit*)pp->unitptr;
525290001Sglebius
526290001Sglebius    /* Read the time from the TSYNC-PCI device */
527290001Sglebius    err3 = ioctl(hBoard.file_descriptor,
528290001Sglebius                 IOCTL_TPRO_GET_NTP_TIME,
529290001Sglebius                 (char *)&TimeContext);
530290001Sglebius
531290001Sglebius    /* Close the TSYNC device */
532290001Sglebius    close(hBoard.file_descriptor);
533290001Sglebius
534290001Sglebius    // Check for errors
535290001Sglebius    if ((err < 0) ||(err1 < 0) || (err2 < 0) || (err3 < 0) ||
536290001Sglebius        (it->status != 0) || (it1->status != 0) || (it2->status != 0) ||
537290001Sglebius        (it->actualOutLength  != TSYNC_REF_OUT_LEN) ||
538290001Sglebius        (it1->actualOutLength != TSYNC_TMSCL_OUT_LEN) ||
539290001Sglebius        (it2->actualOutLength != TSYNC_LEAP_OUT_LEN)) {
540290001Sglebius        refclock_report(peer, CEVNT_FAULT);
541290001Sglebius        return;
542290001Sglebius    }
543290001Sglebius
544290001Sglebius    // Extract reference identifiers from ioctl payload
545290001Sglebius    memset(timeRef, '\0', sizeof(timeRef));
546290001Sglebius    memset(ppsRef, '\0', sizeof(ppsRef));
547290001Sglebius    pRefObj = (void *)it->payloads;
548290001Sglebius    memcpy(timeRef, pRefObj->time, TSYNC_REF_LEN);
549290001Sglebius    memcpy(ppsRef, pRefObj->pps, TSYNC_REF_LEN);
550290001Sglebius
551290001Sglebius    // Extract the Clock Service Time Scale and convert to correct byte order
552293896Sglebius    memcpy(&tmscl, it1->payloads, sizeof(tmscl));
553290001Sglebius    tmscl = ntohl(tmscl);
554290001Sglebius
555290001Sglebius    // Extract leap second info from ioctl payload and perform byte swapping
556290001Sglebius    for (i = 0; i < (sizeof(leapSec) / 4); i++)
557290001Sglebius    {
558290001Sglebius        for (j = 0; j < 4; j++)
559290001Sglebius        {
560290001Sglebius            ((unsigned char*)&leapSec)[(i * 4) + j] =
561290001Sglebius                    ((unsigned char*)(it2->payloads))[(i * 4) + (3 - j)];
562290001Sglebius        }
563290001Sglebius    }
564290001Sglebius
565290001Sglebius    // Determine time reference ID from reference name
566290001Sglebius    for (i = 0; RefIdLookupTbl[i].pRef != NULL; i++)
567290001Sglebius    {
568290001Sglebius       // Search RefID table
569290001Sglebius       if (strstr(timeRef, RefIdLookupTbl[i].pRef) != NULL)
570290001Sglebius       {
571290001Sglebius          // Found the matching string
572290001Sglebius          break;
573290001Sglebius       }
574290001Sglebius    }
575290001Sglebius
576290001Sglebius    // Determine pps reference ID from reference name
577290001Sglebius    for (j = 0; RefIdLookupTbl[j].pRef != NULL; j++)
578290001Sglebius    {
579290001Sglebius       // Search RefID table
580290001Sglebius       if (strstr(ppsRef, RefIdLookupTbl[j].pRef) != NULL)
581290001Sglebius       {
582290001Sglebius          // Found the matching string
583290001Sglebius          break;
584290001Sglebius       }
585290001Sglebius    }
586290001Sglebius
587290001Sglebius    // Determine synchronization state from flags
588290001Sglebius    synch = (TimeContext.timeObj.flags == 0x4) ? 1 : 0;
589290001Sglebius
590290001Sglebius    // Pull seconds information from time object
591290001Sglebius    seconds = (double) (TimeContext.timeObj.secsDouble);
592290001Sglebius    seconds /= (double) 1000000.0;
593290001Sglebius
594290001Sglebius    /*
595290001Sglebius    ** Convert the number of microseconds to double and then place in the
596290001Sglebius    ** peer's last received long floating point format.
597290001Sglebius    */
598290001Sglebius    DTOLFP(((double)TimeContext.tv.tv_usec / 1000000.0), &pp->lastrec);
599290001Sglebius
600290001Sglebius    /*
601290001Sglebius    ** The specTimeStamp is the number of seconds since 1/1/1970, while the
602290001Sglebius    ** peer's lastrec time should be compatible with NTP which is seconds since
603290001Sglebius    ** 1/1/1900.  So Add the number of seconds between 1900 and 1970 to the
604290001Sglebius    ** specTimeStamp and place in the peer's lastrec long floating point struct.
605290001Sglebius    */
606290001Sglebius    pp->lastrec.Ul_i.Xl_ui += (unsigned int)TimeContext.tv.tv_sec +
607290001Sglebius                                            SECONDS_1900_TO_1970;
608290001Sglebius
609290001Sglebius    pp->polls++;
610290001Sglebius
611290001Sglebius    /*
612290001Sglebius    **  set the reference clock object
613290001Sglebius    */
614290001Sglebius    sprintf(pp->a_lastcode, "%03d %02d:%02d:%02.6f",
615290001Sglebius            TimeContext.timeObj.days, TimeContext.timeObj.hours,
616290001Sglebius            TimeContext.timeObj.minutes, seconds);
617290001Sglebius
618290001Sglebius    pp->lencode = strlen (pp->a_lastcode);
619290001Sglebius    pp->day     = TimeContext.timeObj.days;
620290001Sglebius    pp->hour    = TimeContext.timeObj.hours;
621290001Sglebius    pp->minute  = TimeContext.timeObj.minutes;
622290001Sglebius    pp->second  = (int) seconds;
623290001Sglebius    seconds     = (seconds - (double) (pp->second / 1.0)) * 1000000000;
624290001Sglebius    pp->nsec    = (long) seconds;
625290001Sglebius
626290001Sglebius    /*
627290001Sglebius    **  calculate year start
628290001Sglebius    */
629290001Sglebius    jt.year       = TimeContext.timeObj.year;
630290001Sglebius    jt.yearday    = 1;
631290001Sglebius    jt.monthday   = 1;
632290001Sglebius    jt.month      = 1;
633290001Sglebius    jt.hour       = 0;
634290001Sglebius    jt.minute     = 0;
635290001Sglebius    jt.second     = 0;
636290001Sglebius    pp->yearstart = caltontp(&jt);
637290001Sglebius
638290001Sglebius    // Calculate and report reference clock offset
639290001Sglebius    offset.l_ui = (long)(((pp->day - 1) * 24) + pp->hour + GMT);
640290001Sglebius    offset.l_ui = (offset.l_ui * 60) + (long)pp->minute;
641290001Sglebius    offset.l_ui = (offset.l_ui * 60) + (long)pp->second;
642290001Sglebius    offset.l_ui = offset.l_ui + (long)pp->yearstart;
643290001Sglebius    offset.l_uf = 0;
644290001Sglebius    DTOLFP(pp->nsec / 1e9, &ltemp);
645290001Sglebius    L_ADD(&offset, &ltemp);
646290001Sglebius    refclock_process_offset(pp, offset, pp->lastrec,
647290001Sglebius                            pp->fudgetime1);
648290001Sglebius
649290001Sglebius    // KTS in sync
650290001Sglebius    if (synch) {
651290001Sglebius        // Subtract leap second info by one second to determine effective day
652290001Sglebius        ApplyTimeOffset(&(leapSec.utcDate), -1);
653290001Sglebius
654290001Sglebius        // If there is a leap second today and the KTS is using a time scale
655290001Sglebius        // which handles leap seconds then
656290001Sglebius        if ((tmscl != TIME_SCALE_GPS) && (tmscl != TIME_SCALE_TAI) &&
657290001Sglebius            (leapSec.utcDate.year == (unsigned int)TimeContext.timeObj.year) &&
658290001Sglebius            (leapSec.utcDate.doy  == (unsigned int)TimeContext.timeObj.days))
659290001Sglebius        {
660290001Sglebius            // If adding a second
661290001Sglebius            if (leapSec.offset == 1)
662290001Sglebius            {
663290001Sglebius                pp->leap = LEAP_ADDSECOND;
664290001Sglebius            }
665290001Sglebius            // Else if removing a second
666290001Sglebius            else if (leapSec.offset == -1)
667290001Sglebius            {
668290001Sglebius                pp->leap = LEAP_DELSECOND;
669290001Sglebius            }
670290001Sglebius            // Else report no leap second pending (no handling of offsets
671290001Sglebius            // other than +1 or -1)
672290001Sglebius            else
673290001Sglebius            {
674290001Sglebius                pp->leap = LEAP_NOWARNING;
675290001Sglebius            }
676290001Sglebius        }
677290001Sglebius        // Else report no leap second pending
678290001Sglebius        else
679290001Sglebius        {
680290001Sglebius            pp->leap = LEAP_NOWARNING;
681290001Sglebius        }
682290001Sglebius
683290001Sglebius        peer->leap = pp->leap;
684290001Sglebius        refclock_report(peer, CEVNT_NOMINAL);
685290001Sglebius
686290001Sglebius        // If reference name reported, then not in holdover
687290001Sglebius        if ((RefIdLookupTbl[i].pRef != NULL) &&
688290001Sglebius            (RefIdLookupTbl[j].pRef != NULL))
689290001Sglebius        {
690290001Sglebius            // Determine if KTS being synchronized by host (identified as
691290001Sglebius            // "LOCL")
692290001Sglebius            if ((strcmp(RefIdLookupTbl[i].pRefId, TSYNC_REF_LOCAL) == 0) ||
693290001Sglebius                (strcmp(RefIdLookupTbl[j].pRefId, TSYNC_REF_LOCAL) == 0))
694290001Sglebius            {
695290001Sglebius                // Clear prefer flag
696290001Sglebius                peer->flags &= ~FLAG_PREFER;
697290001Sglebius
698290001Sglebius                // Set reference clock stratum level as unusable
699290001Sglebius                pp->stratum   = STRATUM_UNSPEC;
700290001Sglebius                peer->stratum = pp->stratum;
701290001Sglebius
702290001Sglebius                // If a valid peer is available
703290001Sglebius                if ((sys_peer != NULL) && (sys_peer != peer))
704290001Sglebius                {
705290001Sglebius                    // Store reference peer stratum level and ID
706290001Sglebius                    up->refStratum = sys_peer->stratum;
707290001Sglebius                    up->refId      = addr2refid(&sys_peer->srcadr);
708290001Sglebius                }
709290001Sglebius            }
710290001Sglebius            else
711290001Sglebius            {
712290001Sglebius                // Restore prefer flag
713290001Sglebius                peer->flags |= up->refPrefer;
714290001Sglebius
715290001Sglebius                // Store reference stratum as local clock
716290001Sglebius                up->refStratum = TSYNC_LCL_STRATUM;
717290001Sglebius                strncpy((char *)&up->refId, RefIdLookupTbl[j].pRefId,
718290001Sglebius                    TSYNC_REF_LEN);
719290001Sglebius
720290001Sglebius                // Set reference clock stratum level as local clock
721290001Sglebius                pp->stratum   = TSYNC_LCL_STRATUM;
722290001Sglebius                peer->stratum = pp->stratum;
723290001Sglebius            }
724290001Sglebius
725290001Sglebius            // Update reference name
726290001Sglebius            strncpy((char *)&pp->refid, RefIdLookupTbl[j].pRefId,
727290001Sglebius                TSYNC_REF_LEN);
728290001Sglebius            peer->refid = pp->refid;
729290001Sglebius        }
730290001Sglebius        // Else in holdover
731290001Sglebius        else
732290001Sglebius        {
733290001Sglebius            // Restore prefer flag
734290001Sglebius            peer->flags |= up->refPrefer;
735290001Sglebius
736290001Sglebius            // Update reference ID to saved ID
737290001Sglebius            pp->refid   = up->refId;
738290001Sglebius            peer->refid = pp->refid;
739290001Sglebius
740290001Sglebius            // Update stratum level to saved stratum level
741290001Sglebius            pp->stratum   = up->refStratum;
742290001Sglebius            peer->stratum = pp->stratum;
743290001Sglebius        }
744290001Sglebius    }
745290001Sglebius    // Else KTS not in sync
746290001Sglebius    else {
747290001Sglebius        // Place local identifier in peer RefID
748290001Sglebius        strncpy((char *)&pp->refid, TSYNC_REF_LOCAL, TSYNC_REF_LEN);
749290001Sglebius        peer->refid = pp->refid;
750290001Sglebius
751290001Sglebius        // Report not in sync
752290001Sglebius        pp->leap   = LEAP_NOTINSYNC;
753290001Sglebius        peer->leap = pp->leap;
754290001Sglebius    }
755290001Sglebius
756290001Sglebius    if (pp->coderecv == pp->codeproc) {
757290001Sglebius        refclock_report(peer, CEVNT_TIMEOUT);
758290001Sglebius        return;
759290001Sglebius    }
760290001Sglebius
761290001Sglebius    record_clock_stats(&peer->srcadr, pp->a_lastcode);
762290001Sglebius    refclock_receive(peer);
763290001Sglebius
764290001Sglebius    /* Increment the number of times the reference has been polled */
765290001Sglebius    pp->polls++;
766290001Sglebius
767290001Sglebius} /* End - tsync_poll() */
768290001Sglebius
769290001Sglebius
770290001Sglebius////////////////////////////////////////////////////////////////////////////////
771290001Sglebius// Function:    ApplyTimeOffset
772290001Sglebius// Description: The ApplyTimeOffset function adds an offset (in seconds) to a
773290001Sglebius//              specified date and time.  The specified date and time is passed
774290001Sglebius//              back after being modified.
775290001Sglebius//
776290001Sglebius// Assumptions: 1. Every fourth year is a leap year.  Therefore, this function
777290001Sglebius//                 is only accurate through Feb 28, 2100.
778290001Sglebius////////////////////////////////////////////////////////////////////////////////
779290001Sglebiusvoid ApplyTimeOffset(DoyTimeObj* pDt, int off)
780290001Sglebius{
781290001Sglebius    SecTimeObj st;                  // Time, in seconds
782290001Sglebius
783290001Sglebius
784290001Sglebius    // Convert date and time to seconds
785290001Sglebius    SecTimeFromDoyTime(&st, pDt);
786290001Sglebius
787290001Sglebius    // Apply offset
788290001Sglebius    st.seconds = (int)((signed long long)st.seconds + (signed long long)off);
789290001Sglebius
790290001Sglebius    // Convert seconds to date and time
791290001Sglebius    DoyTimeFromSecTime(pDt, &st);
792290001Sglebius
793290001Sglebius} // End ApplyTimeOffset
794290001Sglebius
795290001Sglebius
796290001Sglebius////////////////////////////////////////////////////////////////////////////////
797290001Sglebius// Function:    SecTimeFromDoyTime
798290001Sglebius// Description: The SecTimeFromDoyTime function converts a specified date
799290001Sglebius//              and time into a count of seconds since the base time.  This
800290001Sglebius//              function operates across the range Base Time to Max Time for
801290001Sglebius//              the system.
802290001Sglebius//
803290001Sglebius// Assumptions: 1. A leap year is any year evenly divisible by 4.  Therefore,
804290001Sglebius//                 this function is only accurate through Feb 28, 2100.
805290001Sglebius//              2. Conversion does not account for leap seconds.
806290001Sglebius////////////////////////////////////////////////////////////////////////////////
807290001Sglebiusvoid SecTimeFromDoyTime(SecTimeObj* pSt, DoyTimeObj* pDt)
808290001Sglebius{
809290001Sglebius    unsigned int yrs;               // Years
810290001Sglebius    unsigned int lyrs;              // Leap years
811290001Sglebius
812290001Sglebius
813290001Sglebius    // Start with accumulated time of 0
814290001Sglebius    pSt->seconds  = 0;
815290001Sglebius
816290001Sglebius    // Calculate the number of years and leap years
817290001Sglebius    yrs           = pDt->year - TSYNC_TIME_BASE_YEAR;
818290001Sglebius    lyrs          = (yrs + 1) / 4;
819290001Sglebius
820290001Sglebius    // Convert leap years and years
821290001Sglebius    pSt->seconds += lyrs           * SECSPERLEAPYEAR;
822290001Sglebius    pSt->seconds += (yrs - lyrs)   * SECSPERYEAR;
823290001Sglebius
824290001Sglebius    // Convert days, hours, minutes and seconds
825290001Sglebius    pSt->seconds += (pDt->doy - 1) * SECSPERDAY;
826290001Sglebius    pSt->seconds += pDt->hour      * SECSPERHR;
827290001Sglebius    pSt->seconds += pDt->minute    * SECSPERMIN;
828290001Sglebius    pSt->seconds += pDt->second;
829290001Sglebius
830290001Sglebius    // Copy the subseconds count
831290001Sglebius    pSt->ns       = pDt->ns;
832290001Sglebius
833290001Sglebius} // End SecTimeFromDoyTime
834290001Sglebius
835290001Sglebius
836290001Sglebius////////////////////////////////////////////////////////////////////////////////
837290001Sglebius// Function:    DoyTimeFromSecTime
838290001Sglebius// Description: The DoyTimeFromSecTime function converts a specified count
839290001Sglebius//              of seconds since the start of our base time into a SecTimeObj
840290001Sglebius//              structure.
841290001Sglebius//
842290001Sglebius// Assumptions: 1. A leap year is any year evenly divisible by 4.  Therefore,
843290001Sglebius//                 this function is only accurate through Feb 28, 2100.
844290001Sglebius//              2. Conversion does not account for leap seconds.
845290001Sglebius////////////////////////////////////////////////////////////////////////////////
846290001Sglebiusvoid DoyTimeFromSecTime(DoyTimeObj* pDt, SecTimeObj* pSt)
847290001Sglebius{
848290001Sglebius    signed long long secs;          // Seconds accumulator variable
849290001Sglebius    unsigned int     yrs;           // Years accumulator variable
850290001Sglebius    unsigned int     doys;          // Days accumulator variable
851290001Sglebius    unsigned int     hrs;           // Hours accumulator variable
852290001Sglebius    unsigned int     mins;          // Minutes accumulator variable
853290001Sglebius
854290001Sglebius
855290001Sglebius    // Convert the seconds count into a signed 64-bit number for calculations
856290001Sglebius    secs  = (signed long long)(pSt->seconds);
857290001Sglebius
858290001Sglebius    // Calculate the number of 4 year chunks
859290001Sglebius    yrs   = (unsigned int)((secs /
860290001Sglebius                           ((SECSPERYEAR * 3) + SECSPERLEAPYEAR)) * 4);
861290001Sglebius    secs %= ((SECSPERYEAR * 3) + SECSPERLEAPYEAR);
862290001Sglebius
863290001Sglebius    // If there is at least a normal year worth of time left
864290001Sglebius    if (secs >= SECSPERYEAR)
865290001Sglebius    {
866290001Sglebius        // Increment the number of years and subtract a normal year of time
867290001Sglebius        yrs++;
868290001Sglebius        secs -= SECSPERYEAR;
869290001Sglebius    }
870290001Sglebius
871290001Sglebius    // If there is still at least a normal year worth of time left
872290001Sglebius    if (secs >= SECSPERYEAR)
873290001Sglebius    {
874290001Sglebius        // Increment the number of years and subtract a normal year of time
875290001Sglebius        yrs++;
876290001Sglebius        secs -= SECSPERYEAR;
877290001Sglebius    }
878290001Sglebius
879290001Sglebius    // If there is still at least a leap year worth of time left
880290001Sglebius    if (secs >= SECSPERLEAPYEAR)
881290001Sglebius    {
882290001Sglebius        // Increment the number of years and subtract a leap year of time
883290001Sglebius        yrs++;
884290001Sglebius        secs -= SECSPERLEAPYEAR;
885290001Sglebius    }
886290001Sglebius
887290001Sglebius    // Calculate the day of year as the number of days left, then add 1
888290001Sglebius    // because months start on the 1st.
889290001Sglebius    doys  = (unsigned int)((secs / SECSPERDAY) + 1);
890290001Sglebius    secs %= SECSPERDAY;
891290001Sglebius
892290001Sglebius    // Calculate the hour
893290001Sglebius    hrs   = (unsigned int)(secs / SECSPERHR);
894290001Sglebius    secs %= SECSPERHR;
895290001Sglebius
896290001Sglebius    // Calculate the minute
897290001Sglebius    mins  = (unsigned int)(secs / SECSPERMIN);
898290001Sglebius    secs %= SECSPERMIN;
899290001Sglebius
900290001Sglebius    // Fill in the doytime structure
901290001Sglebius    pDt->year   = yrs + TSYNC_TIME_BASE_YEAR;
902290001Sglebius    pDt->doy    = doys;
903290001Sglebius    pDt->hour   = hrs;
904290001Sglebius    pDt->minute = mins;
905290001Sglebius    pDt->second = (unsigned int)secs;
906290001Sglebius    pDt->ns     = pSt->ns;
907290001Sglebius
908290001Sglebius} // End DoyTimeFromSecTime
909290001Sglebius
910290001Sglebius#else
911290001Sglebiusint refclock_tsyncpci_bs;
912290001Sglebius#endif /* REFCLOCK */
913