/* * * NTVDM specific version of Quick event dispatcher. * * See quick_ev.c for current insignia compatibility level, and full * documentation. Functionally compatible with: * * "quick_ev.c 1.43 07/04/95 Copyright Insignia Solutions Ltd" * * Quick Events are fully supported on Risc platforms. * Quick Events are stubbed to dispatch immediatley on x86 platforms. * Tick events are not supported on any platform, (no longer used) * All Global quick event interfaces use the host_ica_lock for * synchronization. * * 11-Dec-1995 Jonle */ #include #include #include #include #include "insignia.h" #include "host_def.h" #include #include #include #include TypesH #include MemoryH #include "xt.h" #include CpuH #include "error.h" #include "config.h" #include "debug.h" #include "timer.h" #include "host_hfx.h" #include "quick_ev.h" #include "timestmp.h" #include "ica.h" #include "nt_eoi.h" /* * The Quick event structure */ typedef struct _QuickEventEntry { LIST_ENTRY qevListEntry; LARGE_INTEGER DueTimeStamp; ULONG DelayTime; Q_CALLBACK_FN qevCallBack; long Param; ULONG QuickEventId; } QEV_ENTRY, *PQEV_ENTRY; /* Quick event handle structure. externally its defined * as a LONGLONG, internally we manipulate as a QEVHANDLE union * giving us a simple and 99% effective algorithm for verifying * qevent handles. * * CAVEAT: the QEVHANDLE union MUST not be larger than a LONGLONG. */ typedef union _QuickEventHandle { struct { PVOID pvQuickEvent; ULONG QuickEventId; }; LONGLONG Handle; } QEVHANDLE, PQEVHANDLE; LIST_ENTRY QuickEventListHead = {&QuickEventListHead,&QuickEventListHead}; ULONG qevNextHandleId=0; LARGE_INTEGER qevNextDueTime = {0,0}; extern void host_TimeStamp(PLARGE_INTEGER pliTime); // nt_timer.c /* * Calibration variables */ #define DEFAULT_IJCTIME 10 #define CALIBCYCLE16 16 // CALIBCYCLE16 must be 16, because hard coded // shift operations are used to avoid division. void quick_tick_recalibrate(void); GLOBAL IBOOL DisableQuickTickRecal = FALSE; ULONG qevJumpRestart = 100; ULONG qevUsecPerIJC = 0; ULONG qevCalibUsecPerIJC; int qevCalibCycle=0; BOOL QevInitialized = FALSE; LARGE_INTEGER qevCalibCount={0,0}; LARGE_INTEGER qevCalibTime={0,0}; LARGE_INTEGER qevPeriodTime={0,0}; VOID q_event_init( void ) { #ifndef MONITOR PLIST_ENTRY Next; PQEV_ENTRY pqevEntry; #if DBG if (sizeof(QEVHANDLE) > sizeof(ULONGLONG)) { DbgPrint("sizeof(QEVHANDLE) > sizeof(ULONGLONG)\n"); DbgBreakPoint(); } #endif host_ica_lock(); // // do first time initialization, this must be done before ANY // devices access the quick event interface. // if (!QevInitialized ) { qevJumpRestart = host_get_jump_restart(); qevUsecPerIJC = DEFAULT_IJCTIME * qevJumpRestart; qevCalibUsecPerIJC = DEFAULT_IJCTIME * qevJumpRestart; qevPeriodTime.QuadPart = 100000 * 16; // tick every 100 ms, cycle =16 QevInitialized = TRUE; } if (IsListEmpty(&QuickEventListHead)) { host_q_ev_set_count(0); qevNextDueTime.QuadPart = 0; } qevCalibCycle=0; qevCalibCount.QuadPart = 0; host_TimeStamp(&qevCalibTime); host_ica_unlock(); #endif } #ifndef MONITOR /* * Caller must hold ica lock */ void ResetCpuQevCount( PLARGE_INTEGER CurrTime ) { LARGE_INTEGER DiffTime; PQEV_ENTRY pqevEntry; ULONG DelayTime; if (IsListEmpty(&QuickEventListHead)) { host_q_ev_set_count(0); qevNextDueTime.QuadPart = 0; return; } pqevEntry = CONTAINING_RECORD(QuickEventListHead.Flink, QEV_ENTRY, qevListEntry ); DiffTime.QuadPart = pqevEntry->DueTimeStamp.QuadPart - CurrTime->QuadPart; /* * If behind schedule use a reduced delay time to speed up * dispatching of events. Can't go too fast or quick events will * batch up. */ if (DiffTime.QuadPart < 0) { DelayTime = (pqevEntry->DelayTime >> 1) + 1; } else { DelayTime = DiffTime.LowPart; /* ignore overflow! */ } qevNextDueTime.QuadPart = CurrTime->QuadPart + DelayTime; host_q_ev_set_count(host_calc_q_ev_inst_for_time(DelayTime)); } #endif /* * add_q_event_t - add event to do in n usecs * * */ q_ev_handle add_q_event_t( Q_CALLBACK_FN func, unsigned long Time, long param ) { #ifdef MONITOR /* * On X86 dispatch immediately, as x86 has no efficient way * to acheive usec granularity. */ (*func)(param); return (q_ev_handle)1; #else /* MONITOR */ QEVHANDLE qevHandle; PLIST_ENTRY Next; PQEV_ENTRY NewEntry; PQEV_ENTRY EarlierEntry; PQEV_ENTRY pqevEntry; LARGE_INTEGER CurrTime; host_ica_lock(); NewEntry = qevHandle.pvQuickEvent = malloc(sizeof(QEV_ENTRY)); if (!NewEntry) { host_ica_unlock(); return (q_ev_handle)1; } host_TimeStamp(&CurrTime); NewEntry->DueTimeStamp.QuadPart = CurrTime.QuadPart + Time; NewEntry->qevCallBack = func; NewEntry->Param = param; NewEntry->QuickEventId = qevNextHandleId++; qevHandle.QuickEventId = NewEntry->QuickEventId; /* * The Quick event list is sorted in ascending order * by DueTimeStamp, insert in sorted order. */ EarlierEntry = NULL; Next = QuickEventListHead.Blink; while (Next != &QuickEventListHead) { pqevEntry = CONTAINING_RECORD(Next, QEV_ENTRY, qevListEntry); if (NewEntry->DueTimeStamp.QuadPart > pqevEntry->DueTimeStamp.QuadPart) { EarlierEntry = pqevEntry; break; } Next= Next->Blink; } /* * If Earlier Entry found, chain the new entry in after * the earlier entry, and set the DelayTimes. */ if (EarlierEntry) { Next = EarlierEntry->qevListEntry.Flink; NewEntry->qevListEntry.Flink = Next; NewEntry->qevListEntry.Blink = &EarlierEntry->qevListEntry; EarlierEntry->qevListEntry.Flink = &NewEntry->qevListEntry; NewEntry->DelayTime = (ULONG)(NewEntry->DueTimeStamp.QuadPart - EarlierEntry->DueTimeStamp.QuadPart); if (Next == &QuickEventListHead) { QuickEventListHead.Blink = &NewEntry->qevListEntry; } else { pqevEntry = CONTAINING_RECORD(Next, QEV_ENTRY, qevListEntry); pqevEntry->qevListEntry.Blink = &NewEntry->qevListEntry; pqevEntry->DelayTime = (ULONG)(pqevEntry->DueTimeStamp.QuadPart - NewEntry->DueTimeStamp.QuadPart); } } /* * Earlier Entry not found insert at head of list, * reset the cpu count and real expected due time. */ else { InsertHeadList(&QuickEventListHead, &NewEntry->qevListEntry); NewEntry->DelayTime = Time; ResetCpuQevCount(&CurrTime); } host_ica_unlock(); return qevHandle.Handle; #endif } /* * add_q_event_i - add event to do in n number of instructions. * * HOWEVER, instructions is interpreted as time with (1 instr\1 usec). * It is not Instruction Jump Counts (IJC). * */ q_ev_handle add_q_event_i( Q_CALLBACK_FN func, unsigned long instrs, long param ) { return add_q_event_t(func, instrs, param); } /* * Called from the cpu when a count of zero is reached */ VOID dispatch_q_event( void ) { #ifndef MONITOR PQEV_ENTRY pqevEntry; LARGE_INTEGER CurrTime; Q_CALLBACK_FN qevCallBack = NULL; long Param; host_ica_lock(); if (!IsListEmpty(&QuickEventListHead)) { pqevEntry = CONTAINING_RECORD(QuickEventListHead.Flink, QEV_ENTRY, qevListEntry ); qevCallBack = pqevEntry->qevCallBack; Param = pqevEntry->Param; RemoveEntryList(&pqevEntry->qevListEntry); free(pqevEntry); } if (IsListEmpty(&QuickEventListHead)) { host_q_ev_set_count(0); qevNextDueTime.QuadPart = 0; } else { host_TimeStamp(&CurrTime); ResetCpuQevCount(&CurrTime); } host_ica_unlock(); if (qevCallBack) { (*qevCallBack)(Param); } #endif } VOID delete_q_event( q_ev_handle Handle ) { #ifndef MONITOR QEVHANDLE qevHandle; PLIST_ENTRY Next; LARGE_INTEGER CurrTime; PQEV_ENTRY pqevEntry; PQEV_ENTRY EntryFound; qevHandle.Handle = Handle; host_ica_lock(); // // Search the qev list for the entry to ensure // that the qevHandle exists. // EntryFound = NULL; Next = QuickEventListHead.Flink; while (Next != &QuickEventListHead) { pqevEntry = CONTAINING_RECORD(Next, QEV_ENTRY, qevListEntry); Next = Next->Flink; if (pqevEntry == qevHandle.pvQuickEvent && pqevEntry->QuickEventId == qevHandle.QuickEventId) { EntryFound = pqevEntry; break; } } if (!EntryFound) { host_ica_unlock(); return; } // // Adjust the Next entry's DelayTime. // if (Next != &QuickEventListHead) { pqevEntry = CONTAINING_RECORD(Next, QEV_ENTRY, qevListEntry); pqevEntry->DelayTime += EntryFound->DelayTime; } // // If the entry being removed was at the head of the list // Get curr time and remember that head has changed. // if (EntryFound->qevListEntry.Blink == &QuickEventListHead) { host_TimeStamp(&CurrTime); } else { CurrTime.QuadPart = 0; } // // Remove the entry found, and reset Cpu qev count // if head has changed // RemoveEntryList(&EntryFound->qevListEntry); free(EntryFound); // // if head of list changed, reset the Cpu quick event count // if (CurrTime.QuadPart) { ResetCpuQevCount(&CurrTime); } host_ica_unlock(); #endif } #ifndef MONITOR /* * The QuickEvent list stores time in usecs. The CPU quick event counter * uses Instruction Jump Counts (IJC) which tracks progress in emulated * code as opposed to time. The following calibration code attempts to * relate the two. */ /* * Convert time in usecs to Instruction Jump Counts (IJC) */ IU32 calc_q_inst_for_time( IU32 Usecs ) { ULONG InstrJumpCounts; InstrJumpCounts = (Usecs * qevJumpRestart)/qevUsecPerIJC; if (!InstrJumpCounts) { InstrJumpCounts = 1; } return InstrJumpCounts; } /* * Convert Instruction Jump Counts (IJC) to time in usecs */ IU32 calc_q_time_for_inst( IU32 InstrJumpCounts ) { ULONG Usecs; Usecs = InstrJumpCounts * qevUsecPerIJC / qevJumpRestart; if (!Usecs) { Usecs = 1; } return Usecs; } /* * Calibration of quick events. * * quick_tick_recalibrate is invoked on each timer event. Its purpose is * to align progress in emulated code with real time. Progress in emulated * code is tracked by the cpu with Instruction Jump Counts (IJC). * Real Time is tracked by the NT performance counter, with resolution * in usecs (via host_TimeStamp). * * On each call to quick_tick_rcalibrate we retrieve the cpu's IJC, and * the current time, giving us a Usec to Instruction Jump Count ratio. * A running average of the UsecPerIJC ratio is used to convert between * real time and IJC's to set the cpu's quick event counter. An averageing * method was chosen because: * * - avoidance of unusual code fragments which may give artificial ratios. * * - The cpu emulator only increments the Instruction Jump Counter when it * is emulating code, extended durations out of the emulator produces * unrealistically high UsecPerIJC ratios. * * - performance overhead of updating the ratio. * */ void quick_tick_recalibrate(void) { LARGE_INTEGER CurrTime, PeriodTime; ULONG usecPerIJC; ULONG CalibCount; #ifndef PROD if (DisableQuickTickRecal) { qevUsecPerIJC = DEFAULT_IJCTIME * qevJumpRestart; return; } #endif host_ica_lock(); CalibCount = host_get_q_calib_val(); if (!CalibCount) { host_ica_unlock(); return; } qevCalibCount.QuadPart += CalibCount; if (++qevCalibCycle == CALIBCYCLE16) { host_TimeStamp(&CurrTime); PeriodTime.QuadPart = CurrTime.QuadPart - qevCalibTime.QuadPart; qevCalibTime = CurrTime; qevPeriodTime.QuadPart = (qevPeriodTime.QuadPart + PeriodTime.QuadPart) >> 1; qevCalibCycle = 0; } else { // // Use an estimate of elapsed time, to avoid calling system on // every timer event. // PeriodTime.QuadPart = (qevPeriodTime.QuadPart >> 4) * qevCalibCycle; CurrTime.QuadPart = qevCalibTime.QuadPart + qevPeriodTime.QuadPart; } // // Calculate usecPerIJC for this period, ensuring that its not too // large, which is caused by app spending most of its time outside // of the emulator (Idle, network etc.). // usecPerIJC = (ULONG)((PeriodTime.QuadPart * qevJumpRestart)/qevCalibCount.QuadPart); if (usecPerIJC > 10000) { // max at 100 usec PerIJC usecPerIJC = 10000; } else if (usecPerIJC < 100 ) { // min at 1 usec Per IJC usecPerIJC = 100; } // // Add it into the averaged usecPerIJC, with 25% weight // qevUsecPerIJC = (usecPerIJC + qevUsecPerIJC + (qevCalibUsecPerIJC << 1)) >> 2; if (!qevCalibCycle) { qevCalibUsecPerIJC = qevUsecPerIJC; qevCalibCount.QuadPart = 0; } // // Check the quick event list for late events. If more than a msec // behind, reduce the delay, and inform the emulator so it // will dispatch soon. // if (qevNextDueTime.QuadPart && qevNextDueTime.QuadPart < CurrTime.QuadPart - 1000) { ULONG InstrJumpCounts; InstrJumpCounts = (host_q_ev_get_count() >> 1) + 1; host_q_ev_set_count(InstrJumpCounts); } host_ica_unlock(); } #endif