/*************************************************************************\ * 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: johill@lanl.gov-20110115005649-10sil49p7idx8lpe // // Author: Jeff Hill // // // // ANSI C // #include #include #include #include // // WIN32 // #define VC_EXTRALEAN #define STRICT #include // // 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 static int osdTimeGetCurrent ( epicsTimeStamp *pDest ); // GNU seems to require that 64 bit constants have LL on // them. The borland compiler fails to compile constants // with the LL suffix. MS compiler doesnt care. #ifdef __GNUC__ #define LL_CONSTANT(VAL) VAL ## LL #else #define LL_CONSTANT(VAL) VAL #endif // for mingw #if !defined ( MAXLONGLONG ) #define MAXLONGLONG LL_CONSTANT(0x7fffffffffffffff) #endif static const LONGLONG epicsEpochInFileTime = LL_CONSTANT(0x01b41e2a18d64000); class currentTime : public epicsTimerNotify { 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; epicsTimerQueueActive * pTimerQueue; epicsTimer * pTimer; bool perfCtrPresent; epicsTimerNotify::expireStatus expire ( const epicsTime & ); }; 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 ) + LL_CONSTANT(116444736000000000); 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 epicsTimeERROR; } 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 epicsTimeERROR; } // // There are remarkable weaknessess in the FileTimeToLocalFileTime // interface so we dont use it here. Unfortunately, there is no // corresponding function that works on file time. // SYSTEMTIME st; BOOL success = FileTimeToSystemTime ( & ft, & st ); if ( ! success ) { return epicsTimeERROR; } SYSTEMTIME lst; success = SystemTimeToTzSpecificLocalTime ( & tzInfo, & st, & lst ); if ( ! success ) { return epicsTimeERROR; } // // 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 epicsTimeERROR; } 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 epicsTimeERROR; } tzInfo.DaylightDate.wYear = st.wYear; FILETIME DaylightDateFT; success = SystemTimeToFileTime ( & tzInfo.DaylightDate, & DaylightDateFT ); if ( ! success ) { return epicsTimeERROR; } 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 ), pTimerQueue ( 0 ), pTimer ( 0 ), perfCtrPresent ( 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 detected questionable " "system date prior to EPICS epoch\n" ); this->epicsTimeLast = 0; } this->perfCounterFreqPLL = this->perfCounterFreq; this->lastPerfCounterPLL = this->lastPerfCounter; this->lastFileTimePLL = liFileTime.QuadPart; } currentTime::~currentTime () { DeleteCriticalSection ( & this->mutex ); if ( this->pTimer ) { this->pTimer->destroy (); } if ( this->pTimerQueue ) { this->pTimerQueue->release (); } } 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 + MAXLONGLONG ); } 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. // epicsTimerNotify::expireStatus currentTime::expire ( const epicsTime & ) { // 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; } EnterCriticalSection ( & this->mutex ); 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 + MAXLONGLONG ); } 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 zero\n" ) ); return expireStatus ( restart, 1.0 /* sec */ ); } 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 expireStatus ( restart, 1.0 /* sec */ ); } // 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 + MAXLONGLONG ); } // Update the current estimated time. this->epicsTimeLast += ( perfCounterDiffSinceLastFetch * EPICS_TIME_TICKS_PER_SEC ) / this->perfCounterFreq; this->lastPerfCounter = curPerfCounter.QuadPart; LONGLONG epicsTimeFromCurrentFileTime = ( curFileTime.QuadPart - epicsEpochInFileTime ) * ET_TICKS_PER_FT_TICK; 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 // probabably 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 ( 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 ( this->perfCounterFreq - highLimit ) ) ); this->perfCounterFreq = highLimit; } } # if defined ( DEBUG ) LARGE_INTEGER sysFreq; QueryPerformanceFrequency ( & sysFreq ); double freqDiff = static_cast ( this->perfCounterFreq - sysFreq.QuadPart ); freqDiff /= sysFreq.QuadPart; freqDiff *= 100.0; double freqEstDiff = static_cast ( 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 ); return expireStatus ( restart, 1.0 /* sec */ ); } void currentTime::startPLL () { // create frequency estimation timer when needed if ( this->perfCtrPresent && ! this->pTimerQueue ) { this->pTimerQueue = & epicsTimerQueueActive::allocate ( true ); this->pTimer = & this->pTimerQueue->createTimer (); this->pTimer->start ( *this, 1.0 ); } } 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; }