Files
epics-base/src/libCom/osi/os/WIN32/osdTime.cpp
T
Andrew Johnson c16c4590ec Replace epicsTimeERROR with specific error status values
Introduced error status values for epicsTime routines.
The epicsTimeERROR identifier no longer exists, so code that uses it will
no longer compile (deliberately). Replace all uses of it with a specific
error status value.
2014-12-02 15:25:09 -06:00

663 lines
22 KiB
C++

/*************************************************************************\
* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne
* National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
* Operator of Los Alamos National Laboratory.
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
//
// $Revision-Id$
//
// Author: Jeff Hill
//
//
//
// ANSI C
//
#include <cmath>
#include <ctime>
#include <climits>
#include <cstdio>
//
// WIN32
//
#define VC_EXTRALEAN
#define STRICT
#include <windows.h>
#include <process.h>
//
// EPICS
//
#define epicsExportSharedSymbols
#include "epicsTime.h"
#include "generalTimeSup.h"
#include "epicsTimer.h"
#include "errlog.h"
#include "epicsAssert.h"
#include "epicsThread.h"
#if defined ( DEBUG )
# define debugPrintf(argsInParen) ::printf argsInParen
#else
# define debugPrintf(argsInParen)
#endif
extern "C" void setThreadName ( DWORD dwThreadID, LPCSTR szThreadName );
static int osdTimeGetCurrent ( epicsTimeStamp *pDest );
// for mingw
#if !defined ( MAXLONGLONG )
# define MAXLONGLONG 0x7fffffffffffffffLL
#endif
#if !defined ( MINLONGLONG )
# define MINLONGLONG ~0x7fffffffffffffffLL
#endif
#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
# define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000
#endif
static const LONGLONG epicsEpochInFileTime = 0x01b41e2a18d64000LL;
static unsigned __stdcall _pllThreadEntry ( void * pCurrentTimeIn );
class currentTime {
public:
currentTime ();
~currentTime ();
void getCurrentTime ( epicsTimeStamp & dest );
void startPLL ();
private:
CRITICAL_SECTION mutex;
LONGLONG lastPerfCounter;
LONGLONG perfCounterFreq;
LONGLONG epicsTimeLast; // nano-sec since the EPICS epoch
LONGLONG perfCounterFreqPLL;
LONGLONG lastPerfCounterPLL;
LONGLONG lastFileTimePLL;
HANDLE threadHandle;
unsigned threadId;
bool perfCtrPresent;
bool threadShutdownCmd;
bool threadHasExited;
void updatePLL ();
static const int pllDelay; /* integer seconds */
// cant be static because of diff btw __stdcall and __cdecl
friend unsigned __stdcall _pllThreadEntry ( void * pCurrentTimeIn );
};
const int currentTime :: pllDelay = 5;
static currentTime * pCurrentTime = 0;
static const LONGLONG FILE_TIME_TICKS_PER_SEC = 10000000;
static const LONGLONG EPICS_TIME_TICKS_PER_SEC = 1000000000;
static const LONGLONG ET_TICKS_PER_FT_TICK =
EPICS_TIME_TICKS_PER_SEC / FILE_TIME_TICKS_PER_SEC;
//
// Start and register time provider
//
static int timeRegister(void)
{
pCurrentTime = new currentTime ();
generalTimeCurrentTpRegister("PerfCounter", 150, osdTimeGetCurrent);
pCurrentTime->startPLL ();
return 1;
}
static int done = timeRegister();
//
// osdTimeGetCurrent ()
//
static int osdTimeGetCurrent ( epicsTimeStamp *pDest )
{
assert ( pCurrentTime );
pCurrentTime->getCurrentTime ( *pDest );
return epicsTimeOK;
}
inline void UnixTimeToFileTime ( const time_t * pAnsiTime, LPFILETIME pft )
{
LONGLONG ll = Int32x32To64 ( *pAnsiTime, 10000000 ) + 116444736000000000LL;
pft->dwLowDateTime = static_cast < DWORD > ( ll );
pft->dwHighDateTime = static_cast < DWORD > ( ll >>32 );
}
static int daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31,
31, 30, 31, 30, 31 };
static bool isLeapYear ( DWORD year )
{
if ( (year % 4) == 0 ) {
return ( ( year % 100 ) != 0 || ( year % 400 ) == 0 );
} else {
return false;
}
}
static int dayOfYear ( DWORD day, DWORD month, DWORD year )
{
DWORD nDays = 0;
for ( unsigned m = 1; m < month; m++ ) {
nDays += daysInMonth[m-1];
if ( m == 2 && isLeapYear(year) ) {
nDays++;
}
}
return nDays + day;
}
// synthesize a reentrant gmtime on WIN32
int epicsShareAPI epicsTime_gmtime ( const time_t *pAnsiTime, struct tm *pTM )
{
FILETIME ft;
UnixTimeToFileTime ( pAnsiTime, &ft );
SYSTEMTIME st;
BOOL status = FileTimeToSystemTime ( &ft, &st );
if ( ! status ) {
return S_time_conversion;
}
pTM->tm_sec = st.wSecond; // seconds after the minute - [0,59]
pTM->tm_min = st.wMinute; // minutes after the hour - [0,59]
pTM->tm_hour = st.wHour; // hours since midnight - [0,23]
assert ( st.wDay >= 1 && st.wDay <= 31 );
pTM->tm_mday = st.wDay; // day of the month - [1,31]
assert ( st.wMonth >= 1 && st.wMonth <= 12 );
pTM->tm_mon = st.wMonth - 1; // months since January - [0,11]
assert ( st.wYear >= 1900 );
pTM->tm_year = st.wYear - 1900; // years since 1900
pTM->tm_wday = st.wDayOfWeek; // days since Sunday - [0,6]
pTM->tm_yday = dayOfYear ( st.wDay, st.wMonth, st.wYear ) - 1;
pTM->tm_isdst = 0;
return epicsTimeOK;
}
// synthesize a reentrant localtime on WIN32
int epicsShareAPI epicsTime_localtime (
const time_t * pAnsiTime, struct tm * pTM )
{
FILETIME ft;
UnixTimeToFileTime ( pAnsiTime, & ft );
TIME_ZONE_INFORMATION tzInfo;
DWORD tzStatus = GetTimeZoneInformation ( & tzInfo );
if ( tzStatus == TIME_ZONE_ID_INVALID ) {
return S_time_timezone;
}
//
// There are remarkable weaknesses in the FileTimeToLocalFileTime
// interface so we don't use it here. Unfortunately, there is no
// corresponding function that works on file time.
//
SYSTEMTIME st;
BOOL success = FileTimeToSystemTime ( & ft, & st );
if ( ! success ) {
return S_time_conversion;
}
SYSTEMTIME lst;
success = SystemTimeToTzSpecificLocalTime (
& tzInfo, & st, & lst );
if ( ! success ) {
return S_time_conversion;
}
//
// We must convert back to file time so that we can determine if DST
// is active...
//
FILETIME lft;
success = SystemTimeToFileTime ( & lst, & lft );
if ( ! success ) {
return S_time_conversion;
}
int is_dst = -1; // unknown state of dst
if ( tzStatus != TIME_ZONE_ID_UNKNOWN &&
tzInfo.StandardDate.wMonth != 0 &&
tzInfo.DaylightDate.wMonth != 0) {
// determine if the specified date is
// in daylight savings time
tzInfo.StandardDate.wYear = st.wYear;
FILETIME StandardDateFT;
success = SystemTimeToFileTime (
& tzInfo.StandardDate, & StandardDateFT );
if ( ! success ) {
return S_time_conversion;
}
tzInfo.DaylightDate.wYear = st.wYear;
FILETIME DaylightDateFT;
success = SystemTimeToFileTime (
& tzInfo.DaylightDate, & DaylightDateFT );
if ( ! success ) {
return S_time_conversion;
}
if ( CompareFileTime ( & lft, & DaylightDateFT ) >= 0
&& CompareFileTime ( & lft, & StandardDateFT ) < 0 ) {
is_dst = 1;
}
else {
is_dst = 0;
}
}
pTM->tm_sec = lst.wSecond; // seconds after the minute - [0,59]
pTM->tm_min = lst.wMinute; // minutes after the hour - [0,59]
pTM->tm_hour = lst.wHour; // hours since midnight - [0,23]
assert ( lst.wDay >= 1 && lst.wDay <= 31 );
pTM->tm_mday = lst.wDay; // day of the month - [1,31]
assert ( lst.wMonth >= 1 && lst.wMonth <= 12 );
pTM->tm_mon = lst.wMonth - 1; // months since January - [0,11]
assert ( lst.wYear >= 1900 );
pTM->tm_year = lst.wYear - 1900; // years since 1900
pTM->tm_wday = lst.wDayOfWeek; // days since Sunday - [0,6]
pTM->tm_yday = dayOfYear ( lst.wDay, lst.wMonth, lst.wYear ) - 1;
pTM->tm_isdst = is_dst;
return epicsTimeOK;
}
currentTime::currentTime () :
lastPerfCounter ( 0 ),
perfCounterFreq ( 0 ),
epicsTimeLast ( 0 ),
perfCounterFreqPLL ( 0 ),
lastPerfCounterPLL ( 0 ),
lastFileTimePLL ( 0 ),
threadHandle ( 0 ),
threadId ( 0 ),
perfCtrPresent ( false ),
threadShutdownCmd ( false ),
threadHasExited ( false )
{
InitializeCriticalSection ( & this->mutex );
// avoid interruptions by briefly becoming a time critical thread
int originalPriority = GetThreadPriority ( GetCurrentThread () );
SetThreadPriority ( GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL );
FILETIME ft;
GetSystemTimeAsFileTime ( & ft );
LARGE_INTEGER tmp;
QueryPerformanceCounter ( & tmp );
this->lastPerfCounter = tmp.QuadPart;
// if no high resolution counters then default to low res file time
if ( QueryPerformanceFrequency ( & tmp ) ) {
this->perfCounterFreq = tmp.QuadPart;
this->perfCtrPresent = true;
}
SetThreadPriority ( GetCurrentThread (), originalPriority );
LARGE_INTEGER liFileTime;
liFileTime.LowPart = ft.dwLowDateTime;
liFileTime.HighPart = ft.dwHighDateTime;
if ( liFileTime.QuadPart >= epicsEpochInFileTime ) {
// the windows file time has a maximum resolution of 100 nS
// and a nominal resolution of 10 mS - 16 mS
this->epicsTimeLast =
( liFileTime.QuadPart - epicsEpochInFileTime ) *
ET_TICKS_PER_FT_TICK;
}
else {
errlogPrintf (
"win32 osdTime.cpp init detected questionable "
"system date prior to EPICS epoch, epics time will not advance\n" );
this->epicsTimeLast = 0;
}
this->perfCounterFreqPLL = this->perfCounterFreq;
this->lastPerfCounterPLL = this->lastPerfCounter;
this->lastFileTimePLL = liFileTime.QuadPart;
}
void currentTime :: startPLL ()
{
// create frequency estimation thread when needed
if ( this->perfCtrPresent && ! this->threadHandle ) {
this->threadHandle = (HANDLE)
_beginthreadex ( 0, 4096, _pllThreadEntry, this,
CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION,
& this->threadId );
assert ( this->threadHandle );
BOOL bstat = SetThreadPriority (
this->threadHandle, THREAD_PRIORITY_HIGHEST );
assert ( bstat );
DWORD wstat = ResumeThread ( this->threadHandle );
assert ( wstat != 0xFFFFFFFF );
}
}
currentTime::~currentTime ()
{
EnterCriticalSection ( & this->mutex );
this->threadShutdownCmd = true;
while ( ! this->threadHasExited ) {
LeaveCriticalSection ( & this->mutex );
Sleep ( 250 /* mS */ );
EnterCriticalSection ( & this->mutex );
}
LeaveCriticalSection ( & this->mutex );
DeleteCriticalSection ( & this->mutex );
}
void currentTime::getCurrentTime ( epicsTimeStamp & dest )
{
if ( this->perfCtrPresent ) {
EnterCriticalSection ( & this->mutex );
LARGE_INTEGER curPerfCounter;
QueryPerformanceCounter ( & curPerfCounter );
LONGLONG offset;
if ( curPerfCounter.QuadPart >= this->lastPerfCounter ) {
offset = curPerfCounter.QuadPart - this->lastPerfCounter;
}
else {
//
// must have been a timer roll-over event
//
// It takes 9.223372036855e+18/perf_freq sec to roll over this
// counter. This is currently about 245118 years using the perf
// counter freq value on my system (1193182). Nevertheless, I
// have code for this situation because the performance
// counter resolution will more than likely improve over time.
//
offset = ( MAXLONGLONG - this->lastPerfCounter )
+ ( curPerfCounter.QuadPart - MINLONGLONG ) + 1;
}
if ( offset < MAXLONGLONG / EPICS_TIME_TICKS_PER_SEC ) {
offset *= EPICS_TIME_TICKS_PER_SEC;
offset /= this->perfCounterFreq;
}
else {
double fpOffset = static_cast < double > ( offset );
fpOffset *= EPICS_TIME_TICKS_PER_SEC;
fpOffset /= static_cast < double > ( this->perfCounterFreq );
offset = static_cast < LONGLONG > ( fpOffset );
}
LONGLONG epicsTimeCurrent = this->epicsTimeLast + offset;
if ( this->epicsTimeLast > epicsTimeCurrent ) {
double diff = static_cast < double >
( this->epicsTimeLast - epicsTimeCurrent );
errlogPrintf (
"currentTime::getCurrentTime(): %f sec "
"time discontinuity detected\n",
diff );
}
this->epicsTimeLast = epicsTimeCurrent;
this->lastPerfCounter = curPerfCounter.QuadPart;
LeaveCriticalSection ( & this->mutex );
dest.secPastEpoch = static_cast < epicsUInt32 >
( epicsTimeCurrent / EPICS_TIME_TICKS_PER_SEC );
dest.nsec = static_cast < epicsUInt32 >
( epicsTimeCurrent % EPICS_TIME_TICKS_PER_SEC );
}
else {
// if high resolution performance counters are not supported then
// fall back to low res file time
FILETIME ft;
GetSystemTimeAsFileTime ( & ft );
dest = epicsTime ( ft );
}
}
//
// Maintain corrected version of the performance counter's frequency using
// a phase locked loop. This approach is similar to NTP's.
//
void currentTime :: updatePLL ()
{
EnterCriticalSection ( & this->mutex );
// avoid interruptions by briefly becoming a time critical thread
LARGE_INTEGER curFileTime;
LARGE_INTEGER curPerfCounter;
{
int originalPriority = GetThreadPriority ( GetCurrentThread () );
SetThreadPriority ( GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL );
FILETIME ft;
GetSystemTimeAsFileTime ( & ft );
QueryPerformanceCounter ( & curPerfCounter );
SetThreadPriority ( GetCurrentThread (), originalPriority );
curFileTime.LowPart = ft.dwLowDateTime;
curFileTime.HighPart = ft.dwHighDateTime;
}
LONGLONG perfCounterDiff;
if ( curPerfCounter.QuadPart >= this->lastPerfCounterPLL ) {
perfCounterDiff = curPerfCounter.QuadPart - this->lastPerfCounterPLL;
}
else {
//
// must have been a timer roll-over event
//
// It takes 9.223372036855e+18/perf_freq sec to roll over this
// counter. This is currently about 245118 years using the perf
// counter freq value on my system (1193182). Nevertheless, I
// have code for this situation because the performance
// counter resolution will more than likely improve over time.
//
perfCounterDiff = ( MAXLONGLONG - this->lastPerfCounterPLL )
+ ( curPerfCounter.QuadPart - MINLONGLONG ) + 1;
}
this->lastPerfCounterPLL = curPerfCounter.QuadPart;
LONGLONG fileTimeDiff = curFileTime.QuadPart - this->lastFileTimePLL;
this->lastFileTimePLL = curFileTime.QuadPart;
// discard glitches
if ( fileTimeDiff <= 0 ) {
LeaveCriticalSection( & this->mutex );
debugPrintf ( ( "currentTime: file time difference in PLL was less than zero\n" ) );
return;
}
LONGLONG freq = ( FILE_TIME_TICKS_PER_SEC * perfCounterDiff ) / fileTimeDiff;
LONGLONG delta = freq - this->perfCounterFreqPLL;
// discard glitches
LONGLONG bound = this->perfCounterFreqPLL >> 10;
if ( delta < -bound || delta > bound ) {
LeaveCriticalSection( & this->mutex );
debugPrintf ( ( "freq est out of bounds l=%d e=%d h=%d\n",
static_cast < int > ( -bound ),
static_cast < int > ( delta ),
static_cast < int > ( bound ) ) );
return;
}
// update feedback loop estimating the performance counter's frequency
LONGLONG feedback = delta >> 8;
this->perfCounterFreqPLL += feedback;
LONGLONG perfCounterDiffSinceLastFetch;
if ( curPerfCounter.QuadPart >= this->lastPerfCounter ) {
perfCounterDiffSinceLastFetch =
curPerfCounter.QuadPart - this->lastPerfCounter;
}
else {
//
// must have been a timer roll-over event
//
// It takes 9.223372036855e+18/perf_freq sec to roll over this
// counter. This is currently about 245118 years using the perf
// counter freq value on my system (1193182). Nevertheless, I
// have code for this situation because the performance
// counter resolution will more than likely improve over time.
//
perfCounterDiffSinceLastFetch =
( MAXLONGLONG - this->lastPerfCounter )
+ ( curPerfCounter.QuadPart - MINLONGLONG ) + 1;
}
// discard performance counter delay measurement glitches
{
const LONGLONG expectedDly = this->perfCounterFreq * pllDelay;
const LONGLONG bnd = expectedDly / 4;
if ( perfCounterDiffSinceLastFetch <= 0 ||
perfCounterDiffSinceLastFetch >= expectedDly + bnd ) {
LeaveCriticalSection( & this->mutex );
debugPrintf ( ( "perf ctr measured delay out of bounds m=%d max=%d\n",
static_cast < int > ( perfCounterDiffSinceLastFetch ),
static_cast < int > ( expectedDly + bnd ) ) );
return;
}
}
// Update the current estimated time.
this->epicsTimeLast +=
( perfCounterDiffSinceLastFetch * EPICS_TIME_TICKS_PER_SEC )
/ this->perfCounterFreq;
this->lastPerfCounter = curPerfCounter.QuadPart;
LONGLONG epicsTimeFromCurrentFileTime;
{
static bool firstMessageWasSent = false;
if ( curFileTime.QuadPart >= epicsEpochInFileTime ) {
epicsTimeFromCurrentFileTime =
( curFileTime.QuadPart - epicsEpochInFileTime ) *
ET_TICKS_PER_FT_TICK;
firstMessageWasSent = false;
}
else {
/*
* if the system time jumps to before the EPICS epoch
* then latch to the EPICS epoch printing only one
* warning message the first time that the issue is
* detected
*/
if ( ! firstMessageWasSent ) {
errlogPrintf (
"win32 osdTime.cpp time PLL update detected questionable "
"system date prior to EPICS epoch, epics time will not advance\n" );
firstMessageWasSent = true;
}
epicsTimeFromCurrentFileTime = 0;
}
}
delta = epicsTimeFromCurrentFileTime - this->epicsTimeLast;
if ( delta > EPICS_TIME_TICKS_PER_SEC || delta < -EPICS_TIME_TICKS_PER_SEC ) {
// When there is an abrupt shift in the current computed time vs
// the time derived from the current file time then someone has
// probably adjusted the real time clock and the best reaction
// is to just assume the new time base
this->epicsTimeLast = epicsTimeFromCurrentFileTime;
this->perfCounterFreq = this->perfCounterFreqPLL;
debugPrintf ( ( "currentTime: did someone adjust the date?\n" ) );
}
else {
// update the effective performance counter frequency that will bring
// our calculated time base in syncy with file time one second from now.
this->perfCounterFreq =
( EPICS_TIME_TICKS_PER_SEC * this->perfCounterFreqPLL )
/ ( delta + EPICS_TIME_TICKS_PER_SEC );
// limit effective performance counter frequency rate of change
LONGLONG lowLimit = this->perfCounterFreqPLL - bound;
if ( this->perfCounterFreq < lowLimit ) {
debugPrintf ( ( "currentTime: out of bounds low freq excursion %d\n",
static_cast <int> ( lowLimit - this->perfCounterFreq ) ) );
this->perfCounterFreq = lowLimit;
}
else {
LONGLONG highLimit = this->perfCounterFreqPLL + bound;
if ( this->perfCounterFreq > highLimit ) {
debugPrintf ( ( "currentTime: out of bounds high freq excursion %d\n",
static_cast <int> ( this->perfCounterFreq - highLimit ) ) );
this->perfCounterFreq = highLimit;
}
}
# if defined ( DEBUG )
LARGE_INTEGER sysFreq;
QueryPerformanceFrequency ( & sysFreq );
double freqDiff = static_cast <int>
( this->perfCounterFreq - sysFreq.QuadPart );
freqDiff /= sysFreq.QuadPart;
freqDiff *= 100.0;
double freqEstDiff = static_cast <int>
( this->perfCounterFreqPLL - sysFreq.QuadPart );
freqEstDiff /= sysFreq.QuadPart;
freqEstDiff *= 100.0;
debugPrintf ( ( "currentTime: freq delta %f %% freq est "
"delta %f %% time delta %f sec\n",
freqDiff,
freqEstDiff,
static_cast < double > ( delta ) /
EPICS_TIME_TICKS_PER_SEC ) );
# endif
}
LeaveCriticalSection ( & this->mutex );
}
static unsigned __stdcall _pllThreadEntry ( void * pCurrentTimeIn )
{
currentTime * pCT =
reinterpret_cast < currentTime * > ( pCurrentTimeIn );
setThreadName ( pCT->threadId, "EPICS Time PLL" );
while ( ! pCT->threadShutdownCmd ) {
Sleep ( currentTime :: pllDelay * 1000 /* mS */ );
pCT->updatePLL ();
}
EnterCriticalSection ( & pCT->mutex );
pCT->threadHasExited = true;
LeaveCriticalSection ( & pCT->mutex );
return 1;
}
epicsTime::operator FILETIME () const
{
LARGE_INTEGER ftTicks;
ftTicks.QuadPart = ( this->secPastEpoch * FILE_TIME_TICKS_PER_SEC ) +
( this->nSec / ET_TICKS_PER_FT_TICK );
ftTicks.QuadPart += epicsEpochInFileTime;
FILETIME ts;
ts.dwLowDateTime = ftTicks.LowPart;
ts.dwHighDateTime = ftTicks.HighPart;
return ts;
}
epicsTime::epicsTime ( const FILETIME & ts )
{
LARGE_INTEGER lift;
lift.LowPart = ts.dwLowDateTime;
lift.HighPart = ts.dwHighDateTime;
if ( lift.QuadPart > epicsEpochInFileTime ) {
LONGLONG fileTimeTicksSinceEpochEPICS =
lift.QuadPart - epicsEpochInFileTime;
this->secPastEpoch = static_cast < epicsUInt32 >
( fileTimeTicksSinceEpochEPICS / FILE_TIME_TICKS_PER_SEC );
this->nSec = static_cast < epicsUInt32 >
( ( fileTimeTicksSinceEpochEPICS % FILE_TIME_TICKS_PER_SEC ) *
ET_TICKS_PER_FT_TICK );
}
else {
this->secPastEpoch = 0;
this->nSec = 0;
}
}
epicsTime & epicsTime::operator = ( const FILETIME & rhs )
{
*this = epicsTime ( rhs );
return *this;
}