;**************************************************************************** ;* ;* SciTech OS Portability Manager Library ;* ;* ======================================================================== ;* ;* The contents of this file are subject to the SciTech MGL Public ;* License Version 1.0 (the "License"); you may not use this file ;* except in compliance with the License. You may obtain a copy of ;* the License at http://www.scitechsoft.com/mgl-license.txt ;* ;* Software distributed under the License is distributed on an ;* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or ;* implied. See the License for the specific language governing ;* rights and limitations under the License. ;* ;* The Original Code is Copyright (C) 1991-1998 SciTech Software, Inc. ;* ;* The Initial Developer of the Original Code is SciTech Software, Inc. ;* All Rights Reserved. ;* ;* ======================================================================== ;* ;* Language: NASM or TASM Assembler ;* Environment: IBM PC (MS DOS) ;* ;* Description: Uses the 8253 timer and the BIOS time-of-day count to time ;* the performance of code that takes less than an hour to ;* execute. ;* ;* The routines in this package only works with interrupts ;* enabled, and in fact will explicitly turn interrupts on ;* in order to ensure we get accurate results from the timer. ;* ;* Externally 'C' callable routines: ;* ;* LZ_timerOn: Saves the BIOS time of day count and starts the ;* long period Zen Timer. ;* ;* LZ_timerLap: Latches the current count, and keeps the timer running ;* ;* LZ_timerOff: Stops the long-period Zen Timer and saves the timer ;* count and the BIOS time of day count. ;* ;* LZ_timerCount: Returns an unsigned long representing the timed count ;* in microseconds. If more than an hour passed during ;* the timing interval, LZ_timerCount will return the ;* value 0xFFFFFFFF (an invalid count). ;* ;* Note: If either more than an hour passes between calls to LZ_timerOn ;* and LZ_timerOff, an error is reported. For timing code that takes ;* more than a few minutes to execute, use the low resolution ;* Ultra Long Period Zen Timer code, which should be accurate ;* enough for most purposes. ;* ;* Note: Each block of code being timed should ideally be run several ;* times, with at least two similar readings required to ;* establish a true measurement, in order to eliminate any ;* variability caused by interrupts. ;* ;* Note: Interrupts must not be disabled for more than 54 ms at a ;* stretch during the timing interval. Because interrupts are ;* enabled, key, mice, and other devices that generate interrupts ;* should not be used during the timing interval. ;* ;* Note: Any extra code running off the timer interrupt (such as ;* some memory resident utilities) will increase the time ;* measured by the Zen Timer. ;* ;* Note: These routines can introduce inaccuracies of up to a few ;* tenths of a second into the system clock count for each ;* code section being timed. Consequently, it's a good idea to ;* reboot at the conclusion of timing sessions. (The ;* battery-backed clock, if any, is not affected by the Zen ;* timer.) ;* ;* All registers and all flags are preserved by all routines, except ;* interrupts which are always turned on ;* ;**************************************************************************** IDEAL include "scitech.mac" ;**************************************************************************** ; ; Equates used by long period Zen Timer ; ;**************************************************************************** ; Base address of 8253 timer chip BASE_8253 equ 40h ; The address of the timer 0 count registers in the 8253 TIMER_0_8253 equ BASE_8253 + 0 ; The address of the mode register in the 8253 MODE_8253 equ BASE_8253 + 3 ; The address of the BIOS timer count variable in the BIOS data area. TIMER_COUNT equ 6Ch ; Macro to delay briefly to ensure that enough time has elapsed between ; successive I/O accesses so that the device being accessed can respond ; to both accesses even on a very fast PC. ifdef USE_NASM %macro DELAY 0 jmp short $+2 jmp short $+2 jmp short $+2 %endmacro else macro DELAY jmp short $+2 jmp short $+2 jmp short $+2 endm endif header _lztimer begdataseg _lztimer cextern _ZTimerBIOSPtr,DPTR StartBIOSCount dd 0 ; Starting BIOS count dword EndBIOSCount dd 0 ; Ending BIOS count dword EndTimedCount dw 0 ; Timer 0 count at the end of timing period enddataseg _lztimer begcodeseg _lztimer ; Start of code segment ;---------------------------------------------------------------------------- ; void LZ_timerOn(void); ;---------------------------------------------------------------------------- ; Starts the Long period Zen timer counting. ;---------------------------------------------------------------------------- cprocstart LZ_timerOn ; Set the timer 0 of the 8253 to mode 2 (divide-by-N), to cause ; linear counting rather than count-by-two counting. Also stops ; timer 0 until the timer count is loaded, except on PS/2 computers. mov al,00110100b ; mode 2 out MODE_8253,al ; Set the timer count to 0, so we know we won't get another timer ; interrupt right away. Note: this introduces an inaccuracy of up to 54 ms ; in the system clock count each time it is executed. DELAY sub al,al out TIMER_0_8253,al ; lsb DELAY out TIMER_0_8253,al ; msb ; Store the timing start BIOS count use_es ifdef flatmodel mov ebx,[_ZTimerBIOSPtr] else les bx,[_ZTimerBIOSPtr] endif cli ; No interrupts while we grab the count mov eax,[_ES _bx+TIMER_COUNT] sti mov [StartBIOSCount],eax unuse_es ; Set the timer count to 0 again to start the timing interval. mov al,00110100b ; set up to load initial out MODE_8253,al ; timer count DELAY sub al,al out TIMER_0_8253,al ; load count lsb DELAY out TIMER_0_8253,al ; load count msb ret cprocend ;---------------------------------------------------------------------------- ; void LZ_timerOff(void); ;---------------------------------------------------------------------------- ; Stops the long period Zen timer and saves count. ;---------------------------------------------------------------------------- cprocstart LZ_timerOff ; Latch the timer count. mov al,00000000b ; latch timer 0 out MODE_8253,al cli ; Stop the BIOS count ; Read the BIOS count. (Since interrupts are disabled, the BIOS ; count won't change). use_es ifdef flatmodel mov ebx,[_ZTimerBIOSPtr] else les bx,[_ZTimerBIOSPtr] endif mov eax,[_ES _bx+TIMER_COUNT] mov [EndBIOSCount],eax unuse_es ; Read out the count we latched earlier. in al,TIMER_0_8253 ; least significant byte DELAY mov ah,al in al,TIMER_0_8253 ; most significant byte xchg ah,al neg ax ; Convert from countdown remaining ; to elapsed count mov [EndTimedCount],ax sti ; Let the BIOS count continue ret cprocend ;---------------------------------------------------------------------------- ; unsigned long LZ_timerLap(void) ;---------------------------------------------------------------------------- ; Latches the current count and converts it to a microsecond timing value, ; but leaves the timer still running. We dont check for and overflow, ; where the time has gone over an hour in this routine, since we want it ; to execute as fast as possible. ;---------------------------------------------------------------------------- cprocstart LZ_timerLap push ebx ; Save EBX for 32 bit code ; Latch the timer count. mov al,00000000b ; latch timer 0 out MODE_8253,al cli ; Stop the BIOS count ; Read the BIOS count. (Since interrupts are disabled, the BIOS ; count wont change). use_es ifdef flatmodel mov ebx,[_ZTimerBIOSPtr] else les bx,[_ZTimerBIOSPtr] endif mov eax,[_ES _bx+TIMER_COUNT] mov [EndBIOSCount],eax unuse_es ; Read out the count we latched earlier. in al,TIMER_0_8253 ; least significant byte DELAY mov ah,al in al,TIMER_0_8253 ; most significant byte xchg ah,al neg ax ; Convert from countdown remaining ; to elapsed count mov [EndTimedCount],ax sti ; Let the BIOS count continue ; See if a midnight boundary has passed and adjust the finishing BIOS ; count by the number of ticks in 24 hours. We wont be able to detect ; more than 24 hours, but at least we can time across a midnight ; boundary mov eax,[EndBIOSCount] ; Is end < start? cmp eax,[StartBIOSCount] jae @@CalcBIOSTime ; No, calculate the time taken ; Adjust the finishing time by adding the number of ticks in 24 hours ; (1573040). add [DWORD EndBIOSCount],1800B0h ; Convert the BIOS time to microseconds @@CalcBIOSTime: mov ax,[WORD EndBIOSCount] sub ax,[WORD StartBIOSCount] mov dx,54925 ; Number of microseconds each ; BIOS count represents. mul dx mov bx,ax ; set aside BIOS count in mov cx,dx ; microseconds ; Convert timer count to microseconds push _si mov ax,[EndTimedCount] mov si,8381 mul si mov si,10000 div si ; * 0.8381 = * 8381 / 10000 pop _si ; Add the timer and BIOS counts together to get an overall time in ; microseconds. add ax,bx adc cx,0 ifdef flatmodel shl ecx,16 mov cx,ax mov eax,ecx ; EAX := timer count else mov dx,cx endif pop ebx ; Restore EBX for 32 bit code ret cprocend ;---------------------------------------------------------------------------- ; unsigned long LZ_timerCount(void); ;---------------------------------------------------------------------------- ; Returns an unsigned long representing the net time in microseconds. ; ; If an hour has passed while timing, we return 0xFFFFFFFF as the count ; (which is not a possible count in itself). ;---------------------------------------------------------------------------- cprocstart LZ_timerCount push ebx ; Save EBX for 32 bit code ; See if a midnight boundary has passed and adjust the finishing BIOS ; count by the number of ticks in 24 hours. We wont be able to detect ; more than 24 hours, but at least we can time across a midnight ; boundary mov eax,[EndBIOSCount] ; Is end < start? cmp eax,[StartBIOSCount] jae @@CheckForHour ; No, check for hour passing ; Adjust the finishing time by adding the number of ticks in 24 hours ; (1573040). add [DWORD EndBIOSCount],1800B0h ; See if more than an hour passed during timing. If so, notify the user. @@CheckForHour: mov ax,[WORD StartBIOSCount+2] cmp ax,[WORD EndBIOSCount+2] jz @@CalcBIOSTime ; Hour count didn't change, so ; everything is fine inc ax cmp ax,[WORD EndBIOSCount+2] jnz @@TestTooLong ; Two hour boundaries passed, so the ; results are no good mov ax,[WORD EndBIOSCount] cmp ax,[WORD StartBIOSCount] jb @@CalcBIOSTime ; a single hour boundary passed. That's ; OK, so long as the total time wasn't ; more than an hour. ; Over an hour elapsed passed during timing, which renders ; the results invalid. Notify the user. This misses the case where a ; multiple of 24 hours has passed, but we'll rely on the perspicacity of ; the user to detect that case :-). @@TestTooLong: ifdef flatmodel mov eax,0FFFFFFFFh else mov ax,0FFFFh mov dx,0FFFFh endif jmp short @@Done ; Convert the BIOS time to microseconds @@CalcBIOSTime: mov ax,[WORD EndBIOSCount] sub ax,[WORD StartBIOSCount] mov dx,54925 ; Number of microseconds each ; BIOS count represents. mul dx mov bx,ax ; set aside BIOS count in mov cx,dx ; microseconds ; Convert timer count to microseconds push _si mov ax,[EndTimedCount] mov si,8381 mul si mov si,10000 div si ; * 0.8381 = * 8381 / 10000 pop _si ; Add the timer and BIOS counts together to get an overall time in ; microseconds. add ax,bx adc cx,0 ifdef flatmodel shl ecx,16 mov cx,ax mov eax,ecx ; EAX := timer count else mov dx,cx endif @@Done: pop ebx ; Restore EBX for 32 bit code ret cprocend cprocstart LZ_disable cli ret cprocend cprocstart LZ_enable sti ret cprocend endcodeseg _lztimer END