blob: a4a9c7916ed4a59753603c80984d478765d216c7 [file] [log] [blame]
;****************************************************************************
;*
;* 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