| ;**************************************************************************** |
| ;* |
| ;* 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 |