//************************************************************************** // // TIMER.C -- Xena Gaming Project // // Version 3.XX // // Copyright (c) 1997 Microsoft Corporation. All rights reserved. // // @doc // @module TIMER.C | Timing routines to support device input/output //************************************************************************** #include "msgame.h" //--------------------------------------------------------------------------- // Definitions //--------------------------------------------------------------------------- #define MILLI_SECONDS 1000L #define MICRO_SECONDS 1000000L #define TIMER_RESOLUTION 25L #define TIMER_CALIBRATE_TRIES 4L #define TIMER_CALIBRATE_TIMER 25000L #define TIMER_CALIBRATE_PORT 2500L //--------------------------------------------------------------------------- // Private Data //--------------------------------------------------------------------------- static ULONG PerformanceFrequency = 0L; static ULONG CalibratedResolution = 0L; //--------------------------------------------------------------------------- // @func Converts system ticks into microseconds // @parm ULONG | Ticks | System ticks in system time // @rdesc Returns time in microseconds // @comm Private function //--------------------------------------------------------------------------- static ULONG TIMER_TimeInMicroseconds (ULONG Ticks) { ULONG Remainder; LARGE_INTEGER Microseconds; Microseconds = RtlEnlargedUnsignedMultiply (Ticks, MICRO_SECONDS); Microseconds = RtlExtendedLargeIntegerDivide (Microseconds, PerformanceFrequency, &Remainder); return (Microseconds.LowPart); } //--------------------------------------------------------------------------- // @func Times a fixed delay loop of instructions // @rdesc Returns delay in microseconds // @comm Private function //--------------------------------------------------------------------------- static ULONG TIMER_CalibrateOnTimer (VOID) { ULONG Calibration; LARGE_INTEGER StopTicks; LARGE_INTEGER StartTicks; PORTIO_MaskInterrupts (); StartTicks = KeQueryPerformanceCounter (NULL); __asm { mov ecx, TIMER_CALIBRATE_TIMER CalibrationLoop: xchg al, ah xchg al, ah dec ecx jne CalibrationLoop } StopTicks = KeQueryPerformanceCounter (NULL); PORTIO_UnMaskInterrupts (); Calibration = TIMER_TimeInMicroseconds (StopTicks.LowPart-StartTicks.LowPart); MsGamePrint ((DBG_VERBOSE, "TIMER: TIMER_CalibrateOnTimer Returning %ld uSecs\n", Calibration)); return (Calibration); } //--------------------------------------------------------------------------- // @func Times a fixed delay loop of port I/O calls // @parm PGAMEPORT | PortInfo | Gameport parameters // @rdesc Returns delay in microseconds // @comm Private function //--------------------------------------------------------------------------- static ULONG TIMER_CalibrateOnPort (PGAMEPORT PortInfo) { ULONG Calibration; LARGE_INTEGER StopTicks; LARGE_INTEGER StartTicks; if (!PORTIO_AcquirePort (PortInfo)) { MsGamePrint ((DBG_SEVERE, "TIMER: TIMER_CalibrateOnPort Could Not Acquire Port\n")); return (0); } PORTIO_MaskInterrupts (); StartTicks = KeQueryPerformanceCounter (NULL); __asm { mov ecx, TIMER_CALIBRATE_PORT mov edx, PortInfo CalibrationLoop: push edx call PORTIO_Read test al, al dec ecx jne CalibrationLoop } StopTicks = KeQueryPerformanceCounter (NULL); PORTIO_UnMaskInterrupts (); PORTIO_ReleasePort (PortInfo); Calibration = TIMER_TimeInMicroseconds (StopTicks.LowPart-StartTicks.LowPart); MsGamePrint ((DBG_VERBOSE, "TIMER: TIMER_CalibrateOnPort Returning %ld uSecs\n", Calibration)); return (Calibration); } //--------------------------------------------------------------------------- // @func Retrieves current system time in milliseconds // @rdesc Returns current system time in milliseconds // @comm Public function //--------------------------------------------------------------------------- ULONG TIMER_GetTickCount (VOID) { ULONG Remainder; LARGE_INTEGER TickCount; TickCount = KeQueryPerformanceCounter (NULL); TickCount = RtlExtendedIntegerMultiply (TickCount, MILLI_SECONDS); TickCount = RtlExtendedLargeIntegerDivide (TickCount, PerformanceFrequency, &Remainder); return (TickCount.LowPart); } //--------------------------------------------------------------------------- // @func Calibrates the system processor speed for timing delays // @rdesc Returns NT status code (Success always) // @comm Public function //--------------------------------------------------------------------------- NTSTATUS TIMER_Calibrate (VOID) { ULONG Tries; ULONG Rounding; ULONG Accumulator; LARGE_INTEGER Frequency; KeQueryPerformanceCounter (&Frequency); PerformanceFrequency = Frequency.LowPart; MsGamePrint ((DBG_VERBOSE, "TIMER: PerformanceFrequency is %ld hz\n", PerformanceFrequency)); for (Accumulator = 0, Tries = 0; Tries < TIMER_CALIBRATE_TRIES; Tries++) Accumulator += TIMER_CalibrateOnTimer (); Rounding = (Accumulator % TIMER_CALIBRATE_TRIES) >= (TIMER_CALIBRATE_TRIES/2) ? 1 : 0; Accumulator = (Accumulator / TIMER_CALIBRATE_TRIES) + Rounding; MsGamePrint ((DBG_VERBOSE, "TIMER: Average Timer Calibration is %ld usecs\n", Accumulator)); Rounding = ((TIMER_RESOLUTION*TIMER_CALIBRATE_TIMER)/Accumulator) >= (Accumulator/2) ? 1 : 0; CalibratedResolution = ((TIMER_RESOLUTION*TIMER_CALIBRATE_TIMER)/Accumulator) + Rounding; MsGamePrint ((DBG_VERBOSE, "TIMER: Calibrated Timer Resolution on %lu msecs is %ld loops\n", TIMER_RESOLUTION, CalibratedResolution)); return (STATUS_SUCCESS); } //--------------------------------------------------------------------------- // @func Calibrates delays for the system processor speed during port access // @parm PGAMEPORT | PortInfo | Gameport parameters // @parm ULONG | Microseconds | Delay in microseconds to calibrate // @rdesc Returns delay in counts for microseconds during port access // @comm Public function //--------------------------------------------------------------------------- ULONG TIMER_CalibratePort (PGAMEPORT PortInfo, ULONG Microseconds) { ULONG Tries; ULONG Errors; ULONG Calibration; ULONG Rounding; ULONG Accumulator; LARGE_INTEGER Frequency; KeQueryPerformanceCounter (&Frequency); PerformanceFrequency = Frequency.LowPart; MsGamePrint ((DBG_VERBOSE, "TIMER: PerformanceFrequency is %ld hz\n", PerformanceFrequency)); for (Accumulator = 0, Tries = 0, Errors = 0; Tries < TIMER_CALIBRATE_TRIES; Tries++) { Calibration = TIMER_CalibrateOnPort (PortInfo); if (!Calibration) Errors++; else Accumulator += Calibration; } Tries -= Errors; if (Tries) { Rounding = (Accumulator % Tries) >= (Tries/2) ? 1 : 0; Accumulator = (Accumulator / Tries) + Rounding; MsGamePrint ((DBG_VERBOSE, "TIMER: Average Port Calibration is %ld usecs\n", Accumulator)); Rounding = ((Microseconds*TIMER_CALIBRATE_PORT)/Accumulator) >= (CalibratedResolution/2) ? 1 : 0; Accumulator = ((Microseconds*TIMER_CALIBRATE_PORT)/Accumulator) + Rounding; MsGamePrint ((DBG_VERBOSE, "TIMER: Calibrated Port Resolution on %lu msecs is %ld loops\n", Microseconds, Accumulator)); } else Accumulator++; return (Accumulator); } //--------------------------------------------------------------------------- // @func Convert delays in microseconds to loop counts based on the system processor speed // @parm ULONG | Microseconds | Delay in microseconds to calibrate // @rdesc Returns delay in loop counts // @comm Public function //--------------------------------------------------------------------------- ULONG TIMER_GetDelay (ULONG Microseconds) { ULONG Delay; ULONG Rounding; Rounding = ((Microseconds*CalibratedResolution)%TIMER_RESOLUTION)>(TIMER_RESOLUTION/2) ? 1 : 0; Delay = ((Microseconds*CalibratedResolution)/TIMER_RESOLUTION) + Rounding; return (Delay?Delay:1); } //--------------------------------------------------------------------------- // @func Delays in loop counts based on the system processor speed // @parm ULONG | Delay | Calibrated delay in loop counts // @comm Public function //--------------------------------------------------------------------------- VOID TIMER_DelayMicroSecs (ULONG Delay) { __asm { mov ecx, Delay DelayLoop: xchg al, ah xchg al, ah dec ecx jne DelayLoop } }