1 ;****************************************************************************
3 ;* SciTech OS Portability Manager Library
5 ;* ========================================================================
7 ;* The contents of this file are subject to the SciTech MGL Public
8 ;* License Version 1.0 (the "License"); you may not use this file
9 ;* except in compliance with the License. You may obtain a copy of
10 ;* the License at http://www.scitechsoft.com/mgl-license.txt
12 ;* Software distributed under the License is distributed on an
13 ;* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
14 ;* implied. See the License for the specific language governing
15 ;* rights and limitations under the License.
17 ;* The Original Code is Copyright (C) 1991-1998 SciTech Software, Inc.
19 ;* The Initial Developer of the Original Code is SciTech Software, Inc.
20 ;* All Rights Reserved.
22 ;* ========================================================================
24 ;* Language: NASM or TASM Assembler
25 ;* Environment: IBM PC (MS DOS)
27 ;* Description: Uses the 8253 timer and the BIOS time-of-day count to time
28 ;* the performance of code that takes less than an hour to
31 ;* The routines in this package only works with interrupts
32 ;* enabled, and in fact will explicitly turn interrupts on
33 ;* in order to ensure we get accurate results from the timer.
35 ;* Externally 'C' callable routines:
37 ;* LZ_timerOn: Saves the BIOS time of day count and starts the
38 ;* long period Zen Timer.
40 ;* LZ_timerLap: Latches the current count, and keeps the timer running
42 ;* LZ_timerOff: Stops the long-period Zen Timer and saves the timer
43 ;* count and the BIOS time of day count.
45 ;* LZ_timerCount: Returns an unsigned long representing the timed count
46 ;* in microseconds. If more than an hour passed during
47 ;* the timing interval, LZ_timerCount will return the
48 ;* value 0xFFFFFFFF (an invalid count).
50 ;* Note: If either more than an hour passes between calls to LZ_timerOn
51 ;* and LZ_timerOff, an error is reported. For timing code that takes
52 ;* more than a few minutes to execute, use the low resolution
53 ;* Ultra Long Period Zen Timer code, which should be accurate
54 ;* enough for most purposes.
56 ;* Note: Each block of code being timed should ideally be run several
57 ;* times, with at least two similar readings required to
58 ;* establish a true measurement, in order to eliminate any
59 ;* variability caused by interrupts.
61 ;* Note: Interrupts must not be disabled for more than 54 ms at a
62 ;* stretch during the timing interval. Because interrupts are
63 ;* enabled, key, mice, and other devices that generate interrupts
64 ;* should not be used during the timing interval.
66 ;* Note: Any extra code running off the timer interrupt (such as
67 ;* some memory resident utilities) will increase the time
68 ;* measured by the Zen Timer.
70 ;* Note: These routines can introduce inaccuracies of up to a few
71 ;* tenths of a second into the system clock count for each
72 ;* code section being timed. Consequently, it's a good idea to
73 ;* reboot at the conclusion of timing sessions. (The
74 ;* battery-backed clock, if any, is not affected by the Zen
77 ;* All registers and all flags are preserved by all routines, except
78 ;* interrupts which are always turned on
80 ;****************************************************************************
86 ;****************************************************************************
88 ; Equates used by long period Zen Timer
90 ;****************************************************************************
92 ; Base address of 8253 timer chip
96 ; The address of the timer 0 count registers in the 8253
98 TIMER_0_8253 equ BASE_8253 + 0
100 ; The address of the mode register in the 8253
102 MODE_8253 equ BASE_8253 + 3
104 ; The address of the BIOS timer count variable in the BIOS data area.
108 ; Macro to delay briefly to ensure that enough time has elapsed between
109 ; successive I/O accesses so that the device being accessed can respond
110 ; to both accesses even on a very fast PC.
130 cextern _ZTimerBIOSPtr,DPTR
132 StartBIOSCount dd 0 ; Starting BIOS count dword
133 EndBIOSCount dd 0 ; Ending BIOS count dword
134 EndTimedCount dw 0 ; Timer 0 count at the end of timing period
138 begcodeseg _lztimer ; Start of code segment
140 ;----------------------------------------------------------------------------
141 ; void LZ_timerOn(void);
142 ;----------------------------------------------------------------------------
143 ; Starts the Long period Zen timer counting.
144 ;----------------------------------------------------------------------------
145 cprocstart LZ_timerOn
147 ; Set the timer 0 of the 8253 to mode 2 (divide-by-N), to cause
148 ; linear counting rather than count-by-two counting. Also stops
149 ; timer 0 until the timer count is loaded, except on PS/2 computers.
151 mov al,00110100b ; mode 2
154 ; Set the timer count to 0, so we know we won't get another timer
155 ; interrupt right away. Note: this introduces an inaccuracy of up to 54 ms
156 ; in the system clock count each time it is executed.
160 out TIMER_0_8253,al ; lsb
162 out TIMER_0_8253,al ; msb
164 ; Store the timing start BIOS count
168 mov ebx,[_ZTimerBIOSPtr]
170 les bx,[_ZTimerBIOSPtr]
172 cli ; No interrupts while we grab the count
173 mov eax,[_ES _bx+TIMER_COUNT]
175 mov [StartBIOSCount],eax
178 ; Set the timer count to 0 again to start the timing interval.
180 mov al,00110100b ; set up to load initial
181 out MODE_8253,al ; timer count
184 out TIMER_0_8253,al ; load count lsb
186 out TIMER_0_8253,al ; load count msb
192 ;----------------------------------------------------------------------------
193 ; void LZ_timerOff(void);
194 ;----------------------------------------------------------------------------
195 ; Stops the long period Zen timer and saves count.
196 ;----------------------------------------------------------------------------
197 cprocstart LZ_timerOff
199 ; Latch the timer count.
201 mov al,00000000b ; latch timer 0
203 cli ; Stop the BIOS count
205 ; Read the BIOS count. (Since interrupts are disabled, the BIOS
206 ; count won't change).
210 mov ebx,[_ZTimerBIOSPtr]
212 les bx,[_ZTimerBIOSPtr]
214 mov eax,[_ES _bx+TIMER_COUNT]
215 mov [EndBIOSCount],eax
218 ; Read out the count we latched earlier.
220 in al,TIMER_0_8253 ; least significant byte
223 in al,TIMER_0_8253 ; most significant byte
225 neg ax ; Convert from countdown remaining
227 mov [EndTimedCount],ax
228 sti ; Let the BIOS count continue
234 ;----------------------------------------------------------------------------
235 ; unsigned long LZ_timerLap(void)
236 ;----------------------------------------------------------------------------
237 ; Latches the current count and converts it to a microsecond timing value,
238 ; but leaves the timer still running. We dont check for and overflow,
239 ; where the time has gone over an hour in this routine, since we want it
240 ; to execute as fast as possible.
241 ;----------------------------------------------------------------------------
242 cprocstart LZ_timerLap
244 push ebx ; Save EBX for 32 bit code
246 ; Latch the timer count.
248 mov al,00000000b ; latch timer 0
250 cli ; Stop the BIOS count
252 ; Read the BIOS count. (Since interrupts are disabled, the BIOS
253 ; count wont change).
257 mov ebx,[_ZTimerBIOSPtr]
259 les bx,[_ZTimerBIOSPtr]
261 mov eax,[_ES _bx+TIMER_COUNT]
262 mov [EndBIOSCount],eax
265 ; Read out the count we latched earlier.
267 in al,TIMER_0_8253 ; least significant byte
270 in al,TIMER_0_8253 ; most significant byte
272 neg ax ; Convert from countdown remaining
274 mov [EndTimedCount],ax
275 sti ; Let the BIOS count continue
277 ; See if a midnight boundary has passed and adjust the finishing BIOS
278 ; count by the number of ticks in 24 hours. We wont be able to detect
279 ; more than 24 hours, but at least we can time across a midnight
282 mov eax,[EndBIOSCount] ; Is end < start?
283 cmp eax,[StartBIOSCount]
284 jae @@CalcBIOSTime ; No, calculate the time taken
286 ; Adjust the finishing time by adding the number of ticks in 24 hours
289 add [DWORD EndBIOSCount],1800B0h
291 ; Convert the BIOS time to microseconds
294 mov ax,[WORD EndBIOSCount]
295 sub ax,[WORD StartBIOSCount]
296 mov dx,54925 ; Number of microseconds each
297 ; BIOS count represents.
299 mov bx,ax ; set aside BIOS count in
300 mov cx,dx ; microseconds
302 ; Convert timer count to microseconds
305 mov ax,[EndTimedCount]
309 div si ; * 0.8381 = * 8381 / 10000
312 ; Add the timer and BIOS counts together to get an overall time in
320 mov eax,ecx ; EAX := timer count
324 pop ebx ; Restore EBX for 32 bit code
329 ;----------------------------------------------------------------------------
330 ; unsigned long LZ_timerCount(void);
331 ;----------------------------------------------------------------------------
332 ; Returns an unsigned long representing the net time in microseconds.
334 ; If an hour has passed while timing, we return 0xFFFFFFFF as the count
335 ; (which is not a possible count in itself).
336 ;----------------------------------------------------------------------------
337 cprocstart LZ_timerCount
339 push ebx ; Save EBX for 32 bit code
341 ; See if a midnight boundary has passed and adjust the finishing BIOS
342 ; count by the number of ticks in 24 hours. We wont be able to detect
343 ; more than 24 hours, but at least we can time across a midnight
346 mov eax,[EndBIOSCount] ; Is end < start?
347 cmp eax,[StartBIOSCount]
348 jae @@CheckForHour ; No, check for hour passing
350 ; Adjust the finishing time by adding the number of ticks in 24 hours
353 add [DWORD EndBIOSCount],1800B0h
355 ; See if more than an hour passed during timing. If so, notify the user.
358 mov ax,[WORD StartBIOSCount+2]
359 cmp ax,[WORD EndBIOSCount+2]
360 jz @@CalcBIOSTime ; Hour count didn't change, so
364 cmp ax,[WORD EndBIOSCount+2]
365 jnz @@TestTooLong ; Two hour boundaries passed, so the
366 ; results are no good
367 mov ax,[WORD EndBIOSCount]
368 cmp ax,[WORD StartBIOSCount]
369 jb @@CalcBIOSTime ; a single hour boundary passed. That's
370 ; OK, so long as the total time wasn't
373 ; Over an hour elapsed passed during timing, which renders
374 ; the results invalid. Notify the user. This misses the case where a
375 ; multiple of 24 hours has passed, but we'll rely on the perspicacity of
376 ; the user to detect that case :-).
387 ; Convert the BIOS time to microseconds
390 mov ax,[WORD EndBIOSCount]
391 sub ax,[WORD StartBIOSCount]
392 mov dx,54925 ; Number of microseconds each
393 ; BIOS count represents.
395 mov bx,ax ; set aside BIOS count in
396 mov cx,dx ; microseconds
398 ; Convert timer count to microseconds
401 mov ax,[EndTimedCount]
405 div si ; * 0.8381 = * 8381 / 10000
408 ; Add the timer and BIOS counts together to get an overall time in
416 mov eax,ecx ; EAX := timer count
421 @@Done: pop ebx ; Restore EBX for 32 bit code
426 cprocstart LZ_disable