home *** CD-ROM | disk | FTP | other *** search
- ;****************************************************************************
- ;*
- ;* Long Period Zen Timer
- ;*
- ;* From the book
- ;* "Zen of Assembly Language"
- ;* Volume 1, Knowledge
- ;*
- ;* by Michael Abrash
- ;*
- ;* Modifications by Kendall Bennett
- ;* Copyright (C) 1996 SciTech Software
- ;*
- ;* Filename: $Workfile: lztimer.asm $
- ;* Version: $Revision: 1.0 $
- ;*
- ;* Language: 80386 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:
- ;*
- ;* LZTimerOn: Saves the BIOS time of day count and starts the
- ;* long period Zen Timer.
- ;*
- ;* LZTimerLap: Latches the current count, and keeps the timer running
- ;*
- ;* LZTimerOff: Stops the long-period Zen Timer and saves the timer
- ;* count and the BIOS time of day count.
- ;*
- ;* LZTimerCount: Returns an unsigned long representing the timed count
- ;* in microseconds. If more than an hour passed during
- ;* the timing interval, LZTimerCount will return the
- ;* value 0xFFFFFFFF (an invalid count).
- ;*
- ;* Note: If either more than an hour passes between calls to LZTimerOn
- ;* and LZTimerOff, 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
- ;*
- ;* $Date: 05 Feb 1996 14:35:54 $ $Author: KendallB $
- ;*
- ;****************************************************************************
-
- IDEAL
-
- INCLUDE "model.mac" ; Memory model macros
-
- header lztimer ; Set up memory model
-
- ifndef __WINDOWS16__
- ifndef __WINDOWS32__
-
- ;****************************************************************************
- ;
- ; Equates used by long period Zen Timer
- ;
- ;****************************************************************************
-
- ; Base address of 8253 timer chip
-
- BASE_8253 = 40h
-
- ; The address of the timer 0 count registers in the 8253
-
- TIMER_0_8253 = BASE_8253 + 0
-
- ; The address of the mode register in the 8253
-
- MODE_8253 = BASE_8253 + 3
-
- ; The address of the BIOS timer count variable in the BIOS data area.
-
- TIMER_COUNT = 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.
-
- macro DELAY
- jmp $+2
- jmp $+2
- jmp $+2
- endm
-
- begdataseg lztimer
-
- $EXTRN __ZTimerBIOS,USHORT
-
- StartBIOSCount dd ? ; Starting BIOS count dword
- EndBIOSCount dd ? ; Ending BIOS count dword
- EndTimedCount dw ? ; Timer 0 count at the end of timing period
-
- enddataseg lztimer
-
- begcodeseg lztimer ; Start of code segment
-
- ;----------------------------------------------------------------------------
- ; void LZTimerOn(void);
- ;----------------------------------------------------------------------------
- ; Starts the Long period Zen timer counting.
- ;----------------------------------------------------------------------------
- procstart _LZTimerOn
-
- ; 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
-
- push es
- mov ax,[__ZTimerBIOS]
- mov es,ax
- cli ; No interrupts while we grab the count
- mov eax,[es:TIMER_COUNT]
- sti
- mov [StartBIOSCount],eax
- pop 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
-
- procend _LZTimerOn
-
- ;----------------------------------------------------------------------------
- ; void LZTimerOff(void);
- ;----------------------------------------------------------------------------
- ; Stops the long period Zen timer and saves count.
- ;----------------------------------------------------------------------------
- procstart _LZTimerOff
-
- ; 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).
-
- push es
- mov ax,[__ZTimerBIOS]
- mov es,ax
- mov eax,[es:TIMER_COUNT]
- mov [EndBIOSCount],eax
- pop 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
-
- procend _LZTimerOff
-
- ;----------------------------------------------------------------------------
- ; unsigned long LZTimerLap(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.
- ;----------------------------------------------------------------------------
- procstart _LZTimerLap
-
- use_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 won't change).
-
- push es
- mov ax,[__ZTimerBIOS]
- mov es,ax
- mov eax,[es:TIMER_COUNT]
- mov [EndBIOSCount],eax
- pop 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 [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
- if flatmodel
- shl ecx,16
- mov cx,ax
- mov eax,ecx ; EAX := timer count
- else
- mov dx,cx
- endif
- unuse_ebx ; Restore EBX for 32 bit code
- ret
-
- procend _LZTimerLap
-
- ;----------------------------------------------------------------------------
- ; unsigned long LZTimerCount(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).
- ;----------------------------------------------------------------------------
- procstart _LZTimerCount
-
- use_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 [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:
- if 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
- if flatmodel
- shl ecx,16
- mov cx,ax
- mov eax,ecx ; EAX := timer count
- else
- mov dx,cx
- endif
-
- @@Done:
- unuse_ebx ; Restore EBX for 32 bit code
- ret
-
- procend _LZTimerCount
-
- procstart _LZ_disable
- cli
- ret
- procend _LZ_disable
-
- procstart _LZ_enable
- sti
- ret
- procend _LZ_enable
-
- endcodeseg lztimer
-
- endif
- endif
-
- END ; End of module
-