Important: |
---|
This is retired content. This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. |
You can minimize the impact of accessing the real-time clock (RTC) in any of the following ways:
- Replace the hardware RTC with a software RTC.
- Eliminate calls to the RTC.
- Access the clock at optimal times.
In standard x86 bus architecture, where the complementary metal oxide semiconductor (CMOS) system clock houses the RTC and the system clock resides on the bus, the RTC accesses the bus several times. In doing so, the bus can be locked for several microseconds, blocking it from other activities. This translates into higher latencies for interrup service routines (ISRs) and longer times to fully service an interrupt request (IRQ). To avoid this, consider replacing the hardware RTC with a software RTC. On a CEPC hardware platform, you must also eliminate the calls to the RTC when GetSystemTimeis called, and access the clock at optimal times to ensure that a large drift is not introduced.
The following code example shows how to replace the hardware RTC with a software RTC:
Copy Code | |
---|---|
#include <windows.h> #include <nkintr.h> #include "pc.h" #include "timer.h" // NOTE: A problem has been found with some chipsets such that setting the time to 23:59:59 on the 29th or 30th day of a month that has less than 31 days causes the clock to roll over incorrectly. Uncomment the following line to fix this problem. However, be aware that the fix consists of responding to calls that set the time to HH:MM:59 by instead setting the time to HH:MM:58. #define HARDWARE_TIME_SET_PROBLEM 1 __int64 RealTimeBias; unsigned long AlarmTime[2]; SYSTEMTIME RTC_AlarmTime; CRITICAL_SECTION RTC_critsect; BOOL fRTCInit; unsigned long volatile CurMSecHigh; // RTC without CMOS Global Variables BOOL rtInited = FALSE; FILETIME rtStartFileTime; FILETIME rtDeltaTime; FILETIME rtCurFileTime; FILETIME rtLastFileTime; DWORD rtLastCurMSec; extern DWORD CurMSec; // Prototypes for RTC without CMOS BOOL RT_Init_RealTime ( void ); BOOL RT_Bare_GetRealTime (LPSYSTEMTIME lpst); BOOL RT_Bare_SetRealTime (LPSYSTEMTIME lpst); void add64_64_64 (const FILETIME *lpnum1, LPFILETIME lpnum2, LPFILETIME lpres); void CMOS_Write( BYTE offset, BYTE value ) { BYTE cAddr; // Remember, only the low-order 5 bits in the address register are changed. cAddr = _inp( CMOS_ADDR ); _outp( CMOS_ADDR, (cAddr & RTC_ADDR_MASK) | offset ); _outp( CMOS_DATA, value ); // NOTE : If bytes 16-45 are ever updated, the checksum should be recalculated and stored. However, because none of those bytes are currently used, it will not be addressed here. DEBUGCHK(offset < 16); } BYTE CMOS_Read( BYTE offset ) { BYTE cAddr, cResult; // Remember, only the low-order 5 bits in the address register are changed. cAddr = _inp( CMOS_ADDR ); _outp( CMOS_ADDR, (cAddr & RTC_ADDR_MASK) | offset ); cResult = _inp( CMOS_DATA ); return (cResult); } BOOL IsTimeEqual(LPSYSTEMTIME lpst1, LPSYSTEMTIME lpst2) { if (lpst1->wYear != lpst2->wYear) return(FALSE); if (lpst1->wMonth != lpst2->wMonth) return(FALSE); if (lpst1->wDayOfWeek != lpst2->wDayOfWeek) return(FALSE); if (lpst1->wDay != lpst2->wDay) return(FALSE); if (lpst1->wHour != lpst2->wHour) return(FALSE); if (lpst1->wMinute != lpst2->wMinute) return(FALSE); if (lpst1->wSecond != lpst2->wSecond) return(FALSE); return (TRUE); } // NOTE : The RTC routines are not thread safe. But the ether debug functions do not want me to change interrupts, and so on. So the Bare_ functions is provided for callers who run with interrupts off, and so on. Then the real functions call into the bare functions after turning off interrupts. BOOL Bare_GetRealTime(LPSYSTEMTIME lpst) { SYSTEMTIME st; LPSYSTEMTIME lpst1 = &st, lpst2 = lpst, lptmp; lpst1->wSecond = 61; // initialize to an invalid value lpst2->wSecond = 62; // initialize to an invalid value do { // exchange lpst1 and lpst2 lptmp = lpst1; lpst1 = lpst2; lpst2 = lptmp; // Wait until not updating. while (CMOS_Read(RTC_STATUS_A) & RTC_SRA_UIP); // Read all values. lpst1->wYear = CMOS_Read(RTC_YEAR); lpst1->wMonth = CMOS_Read(RTC_MONTH); lpst1->wDayOfWeek = CMOS_Read(RTC_DO_WEEK); lpst1->wDay = CMOS_Read(RTC_DO_MONTH); lpst1->wHour = CMOS_Read(RTC_HOUR); lpst1->wMinute = CMOS_Read(RTC_MINUTE); lpst1->wSecond = CMOS_Read(RTC_SECOND); } while (!IsTimeEqual (lpst1, lpst2)); lpst->wMilliseconds = 0; // Not sure how you would get this if (!(CMOS_Read (RTC_STATUS_B) & RTC_SRB_DM)) { // Values returned in BCD. lpst->wSecond = DECODE_BCD(lpst->wSecond); lpst->wMinute = DECODE_BCD(lpst->wMinute); lpst->wHour = DECODE_BCD(lpst->wHour); lpst->wDay = DECODE_BCD(lpst->wDay); lpst->wDayOfWeek = DECODE_BCD(lpst->wDayOfWeek); lpst->wMonth = DECODE_BCD(lpst->wMonth); lpst->wYear = DECODE_BCD(lpst->wYear); } // OK - PC RTC returns 1998 as 98. lpst->wYear += (lpst->wYear > 70)? 1900 : 2000; #ifdef NOTDEF NKDbgPrintfW(TEXT("\r\nReal Time %d, %d, %d, %d, %d, %d, %d, %d\r\n"), lpst->wYear, lpst->wMonth, lpst->wDayOfWeek, lpst->wDay, lpst->wHour, lpst->wMinute, lpst->wSecond, lpst->wMilliseconds ); #endif return (TRUE); } BOOL Bare_SetRealTime(LPSYSTEMTIME lpst) { BYTE cStatusRegA, cStatusRegB, Year; #ifdef NOTDEF NKDbgPrintfW(TEXT("\r\nSet Real Time %d, %d, %d, %d, %d, %d, %d, %d\r\n"), pst->wYear, lpst->wMonth, lpst->wDayOfWeek, lpst->wDay, lpst->wHour, lpst->wMinute, lpst->wSecond, lpst->wMilliseconds ); #endif Year = lpst->wYear % 100; #ifdef HARDWARE_TIME_SET_PROBLEM if (lpst->wSecond == 59) { lpst->wSecond = 58; } #endif // Read the update in progress bit, wait for it to be clear. This bit will be set once per second for about 2 us. (Undoc. PC, page 897) do { cStatusRegA = CMOS_Read( RTC_STATUS_A); } while ( cStatusRegA & RTC_SRA_UIP ); // Disable updates while the values are changed cStatusRegB = CMOS_Read( RTC_STATUS_B ); cStatusRegB |= RTC_SRB_UPDT; CMOS_Write( RTC_STATUS_B, cStatusRegB ); if ( !(cStatusRegB & RTC_SRB_DM) ) { // BCD Mode CMOS_Write( RTC_YEAR, (BYTE)(CREATE_BCD(Year))); CMOS_Write( RTC_MONTH, (BYTE)(CREATE_BCD(lpst->wMonth))); CMOS_Write( RTC_DO_WEEK, (BYTE)(CREATE_BCD(lpst->wDayOfWeek))); CMOS_Write( RTC_DO_MONTH, (BYTE)(CREATE_BCD(lpst->wDay))); CMOS_Write( RTC_HOUR, (BYTE)(CREATE_BCD(lpst->wHour))); CMOS_Write( RTC_MINUTE, (BYTE)(CREATE_BCD(lpst->wMinute))); CMOS_Write( RTC_SECOND, (BYTE)(CREATE_BCD(lpst->wSecond))); // Not sure how you can do lpst->wMilliseconds; } else { // Binary mode CMOS_Write( RTC_YEAR, (UCHAR)Year); CMOS_Write( RTC_MONTH, (UCHAR)lpst->wMonth); CMOS_Write( RTC_DO_WEEK, (UCHAR)lpst->wDayOfWeek); CMOS_Write( RTC_DO_MONTH, (UCHAR)lpst->wDay); CMOS_Write( RTC_HOUR, (UCHAR)lpst->wHour); CMOS_Write( RTC_MINUTE, (UCHAR)lpst->wMinute); CMOS_Write( RTC_SECOND, (UCHAR)lpst->wSecond); // Not sure how you can do lpst->wMilliseconds; } // Reenable updates cStatusRegB &= ~RTC_SRB_UPDT; CMOS_Write( RTC_STATUS_B, cStatusRegB ); return (TRUE); } BOOL Bare_SetAlarmTime(LPSYSTEMTIME lpst) { BYTE cStatusRegA, cStatusRegB, Year; #ifdef NOTDEF NKDbgPrintfW(TEXT("\r\nSet Alarm Time %d, %d, %d, %d, %d, %d, %d, %d\r\n"), lpst->wYear, lpst->wMonth, lpst->wDayOfWeek, lpst->wDay, lpst->wHour, lpst->wMinute, lpst->wSecond, lpst->wMilliseconds ); #endif // NOTE : Because our alarm only has a 1-day rollover, the full alarm time needs to be stored and compared on alarm interrupts to see if it is at the correct day. RTC_AlarmTime = *lpst; Year = lpst->wYear % 100; // Read the update in progress bit, wait for it to be clear. This bit will be set once per second for about 2 us. (Undoc. PC, page 897) do { cStatusRegA = CMOS_Read( RTC_STATUS_A); } while ( cStatusRegA & RTC_SRA_UIP ); // Disable updates while the values are changed cStatusRegB = CMOS_Read( RTC_STATUS_B ); cStatusRegB |= RTC_SRB_UPDT; CMOS_Write( RTC_STATUS_B, cStatusRegB ); if ( !(cStatusRegB & RTC_SRB_DM) ) { // BCD Mode CMOS_Write( RTC_ALRM_HOUR, (BYTE)(CREATE_BCD(lpst->wHour))); CMOS_Write( RTC_ALRM_MINUTE, (BYTE)(CREATE_BCD(lpst->wMinute))); CMOS_Write( RTC_ALRM_SECOND, (BYTE)(CREATE_BCD(lpst->wSecond))); } else { // Binary mode CMOS_Write( RTC_ALRM_HOUR, (UCHAR)lpst->wHour); CMOS_Write( RTC_ALRM_MINUTE, (UCHAR)lpst->wMinute); CMOS_Write( RTC_ALRM_SECOND, (UCHAR)lpst->wSecond); } // Enable alarm interrupt and reenable updates. cStatusRegB = (cStatusRegB | RTC_SRB_AI) & ~RTC_SRB_UPDT; CMOS_Write( RTC_STATUS_B, cStatusRegB ); return( TRUE ); } BOOL OEMGetRealTime(LPSYSTEMTIME lpst) { BOOL RetVal; if (!fRTCInit) { InitializeCriticalSection(&RTC_critsect); // RTC without CMOS if( !rtInited )RT_Init_RealTime(); fRTCInit = TRUE; } EnterCriticalSection(&RTC_critsect); if( rtInited ) { //RTC without CMOS RetVal = RT_Bare_GetRealTime( lpst ); } else { RetVal = Bare_GetRealTime(lpst); } LeaveCriticalSection(&RTC_critsect); return RetVal; } BOOL OEMSetRealTime(LPSYSTEMTIME lpst) { BOOL RetVal; if (!fRTCInit) { InitializeCriticalSection(&RTC_critsect); if( !rtInited )RT_Init_RealTime(); fRTCInit = TRUE; } EnterCriticalSection(&RTC_critsect); if( rtInited ) { RetVal = RT_Bare_SetRealTime(lpst); } else { RetVal = Bare_SetRealTime(lpst); } LeaveCriticalSection(&RTC_critsect); return RetVal; } BOOL OEMSetAlarmTime(LPSYSTEMTIME lpst) { BOOL RetVal; if (!fRTCInit) { InitializeCriticalSection(&RTC_critsect); fRTCInit = TRUE; } EnterCriticalSection(&RTC_critsect); RetVal = Bare_SetAlarmTime(lpst); LeaveCriticalSection(&RTC_critsect); return RetVal; } // The following function attempts to eliminate the dependency on the CMOS for the clock times. A startup value is taken from the CMOS with all later times being returned as the Tick Count plus this initial value. Roll over is accounted for. If the RT system has not been initialized, the legacy Bare_GetRealTime is called. BOOL RT_Bare_GetRealTime(LPSYSTEMTIME lpst) { DWORD delta; // Calculate the current diference if( CurMSec >= rtLastCurMSec ) { delta = CurMSec - rtLastCurMSec; } else { // The counter had to be wrapped delta = CurMSec + (ULONG_MAX - rtLastCurMSec); } rtDeltaTime.dwHighDateTime = 0; rtDeltaTime.dwLowDateTime = delta * 10000; // Add the delta to the last. add64_64_64( &rtLastFileTime , & rtDeltaTime, & rtCurFileTime ); // Add the current time difference in & record the values. rtLastFileTime.dwLowDateTime = rtCurFileTime.dwLowDateTime; rtLastFileTime.dwHighDateTime = rtCurFileTime.dwHighDateTime; rtLastCurMSec = CurMSec; // Get the answer in a system-time format. if(KFileTimeToSystemTime( &rtCurFileTime, lpst ) ) { return TRUE; } else { // Failed and what time is it? return FALSE; } } // The following functions Converts the system time to the rtLastFileTime which stores the last time called. Zero the MS delta counter by setting the rtLastCurMSec to the CurMSec. BOOL RT_Bare_SetRealTime(LPSYSTEMTIME lpst) { if( KSystemTimeToFileTime(lpst, &rtLastFileTime ) ) { rtLastCurMSec = CurMSec; return TRUE; } return FALSE; } // The following function initializes the RT area in an attempt to eliminate the run time dependency of the OS on the CMOS. The X86 CMOS is very slow and can take up to 1ms to get the full CMOS clock. This function get the start time from the CMOS and initializes the local variables that the RT functions need. BOOL RT_Init_RealTime( void ) { SYSTEMTIME sysTime; RETAILMSG(1,(TEXT("\r\n\n******Called RT_Init_GetRealTime\r\n\n"))); // Get the curren time and store it. if( !Bare_GetRealTime( &sysTime )) { // Are not able to get the time? return FALSE; } // Convert the file time structure. if( !KSystemTimeToFileTime( &sysTime, &rtCurFileTime ) ) { return FALSE; } // Store Start and Current Versions of the File Time and Large Ints. rtLastCurMSec = (DWORD)CurMSec; rtStartFileTime.dwLowDateTime = rtCurFileTime.dwLowDateTime; rtStartFileTime.dwHighDateTime = rtCurFileTime.dwHighDateTime; rtLastFileTime.dwLowDateTime = rtCurFileTime.dwLowDateTime; rtLastFileTime.dwHighDateTime = rtCurFileTime.dwHighDateTime; rtInited = TRUE; return TRUE; } void add64_64_64(const FILETIME *lpnum1, LPFILETIME lpnum2, LPFILETIME, lpres) { __int64 num1, num2; num1 = (((__int64)lpnum1->dwHighDateTime)<<32)+ (__int64)lpnum1->dwLowDateTime; num2 = (((__int64)lpnum2->dwHighDateTime)<<32)+ (__int64)lpnum2->dwLowDateTime; num1 += num2; lpres->dwHighDateTime = (DWORD)(num1>>32); lpres->dwLowDateTime = (DWORD)(num1&0xffffffff); } } |