/*******************************************************************************
*
* 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
/*******************************************************************************
**
** This driver supports the Spectracom TSYNC PCI GPS receiver. It requires
** that the tsyncpci.o device driver be installed and loaded.
**
*******************************************************************************/
// 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
/*
** 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 {
// 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 *);
// 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;
}
// 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);
// 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;
}
// 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;
}
/* 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;
////////////////////////////////////////////////////////////////////////////////
// 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;