home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / dos / zen / ztimer / lztimer.asm < prev    next >
Encoding:
Assembly Source File  |  1992-01-27  |  20.7 KB  |  733 lines

  1. ;****************************************************************************
  2. ;*
  3. ;*                           Long Period Zen Timer
  4. ;*
  5. ;*                               From the book
  6. ;*                         "Zen of Assembly Language"
  7. ;*                            Volume 1, Knowledge
  8. ;*
  9. ;*                             by Michael Abrash
  10. ;*
  11. ;*                      Modifications by Kendall Bennett
  12. ;*
  13. ;* Filename:    $RCSfile: lztimer.asm $
  14. ;* Version:        $Revision: 1.3 $
  15. ;*
  16. ;* Language:    8086 Assembler
  17. ;* Environment:    IBM PC (MS DOS)
  18. ;*
  19. ;* Description:    Uses the 8253 timer and the BIOS time-of-day count to time
  20. ;*                the performance of code that takes less than an hour to
  21. ;*                execute.
  22. ;*
  23. ;*                Because interrupts are left on (in order to allow the timer
  24. ;*                interrupt to be recognised), this is less accurate than the
  25. ;*                precision Zen Timer, so it is best used only to time code
  26. ;*                that takes more than about 54 milliseconds to execute (code
  27. ;*                that causes the precision Zen Timer to overflow). Resolution
  28. ;*                is limited by the occurrence of timer interrupts.
  29. ;*
  30. ;*    Externally 'C' callable routines:
  31. ;*
  32. ;*    LZTimerOn:        Saves the BIOS time of day count and starts the
  33. ;*                    long period Zen Timer.
  34. ;*
  35. ;*    LZTimerOff:        Stops the long-period Zen Timer and saves the timer
  36. ;*                    count and the BIOS time of day count.
  37. ;*
  38. ;*    LZTimerReport:    Prints the net time that passed between starting and
  39. ;*                    stopping the timer.
  40. ;*
  41. ;*    LZTimerCount:    Returns an unsigned long representing the timed count
  42. ;*                    in microseconds. If more than an hour passed or
  43. ;*                    midnight passed during the timing interval, LZTimerCount
  44. ;*                    will return the value 0xFFFFFFFF (an invalid count).
  45. ;*
  46. ;*    Note:    If either more than an hour passes or midnight falls between
  47. ;*            calls to LZTimerOn and LZTimerOff, an error is reported. For
  48. ;*            timing code that takes more than a few minutes to execute,
  49. ;*            either the DOS TIME command in a batch file before and after
  50. ;*            execution of the code to time or the use of the DOS time-of-day
  51. ;*            function in place of the long-period Zen Timer is more than
  52. ;*            adequate.
  53. ;*
  54. ;*    Note:    The PS/2 version is assembled by setting the symbol PS2 to 1.
  55. ;*            PS2 must be set to 1 on PS/2 computers because the PS/2's timers
  56. ;*            are not compatible with an undocumented timer-stopping feature
  57. ;*            of the 8253; the alternative timing approach that must be used
  58. ;*            on PS/2 computers leaves a short window during which the timer 0
  59. ;*            count and the BIOS timer count may not be synchronised. You
  60. ;*            should also set the PS2 symbol to 1 if you're getting erratic
  61. ;*            or obviously incorrect results.
  62. ;*
  63. ;*    Note:    When PS2 is 0, the code relies on a undocumented feature of the
  64. ;*            8253 to get more reliable readings. It is possible that the
  65. ;*            8253 (or whatever chip is emulating the 8253) may be put into
  66. ;*            an undefined or incorrect state when this feature if used.
  67. ;*
  68. ;*        *****************************************************************
  69. ;*        * If your computer displays any hint of erratic behaviour        *
  70. ;*        * after the long-period Zen Timer is used, such as the floppy    *
  71. ;*        * drive failing to operate properly, reboot the system, set        *
  72. ;*        * PS2 to 1 and leave it that way!                                *
  73. ;*        *****************************************************************
  74. ;*
  75. ;*    Note:    Each block of code being timed should ideally be run several
  76. ;*            times, with at least two similar readings required to
  77. ;*            establish a true measurement, in order to eliminate any
  78. ;*            variability caused by interrupts.
  79. ;*
  80. ;*    Note:    Interrupts must not be disabled for more than 54 ms at a
  81. ;*            stretch during the timing interval. Because interrupts are
  82. ;*            enabled, key, mice, and other devices that generate interrupts
  83. ;*            should not be used during the timing interval.
  84. ;*
  85. ;*    Note:    Any extra code running off the timer interrupt (such as
  86. ;*            some memory resident utilities) will increase the time
  87. ;*            measured by the Zen Timer.
  88. ;*
  89. ;*    Note:    These routines can introduce inaccuracies of up to a few
  90. ;*            tenths of a second into the system clock count for each
  91. ;*            code section being timed. Consequently, it's a good idea to
  92. ;*            reboot at the conclusion of timing sessions. (The
  93. ;*            battery-backed clock, if any, is not affected by the Zen
  94. ;*            timer.)
  95. ;*
  96. ;*  All registers and all flags are preserved by all routines.
  97. ;*
  98. ;* $Id: lztimer.asm 1.3 92/01/27 21:39:57 kjb release $
  99. ;*
  100. ;* Revision History:
  101. ;* -----------------
  102. ;*
  103. ;* $Log:    lztimer.asm $
  104. ;* Revision 1.3  92/01/27  21:39:57  kjb
  105. ;* Converted to a memory model independant library, and released to the
  106. ;* public.,
  107. ;* 
  108. ;* Revision 1.2  91/11/16  17:11:33  kjb
  109. ;* Modified to return a long integer count rather than a string.
  110. ;*
  111. ;* Revision 1.1  91/11/14  17:16:28  kjb
  112. ;* Initial revision
  113. ;*
  114. ;****************************************************************************
  115.  
  116.         IDEAL
  117.  
  118. INCLUDE "model.mac"                ; Memory model macros
  119.  
  120. header    lztimer                    ; Set up memory model
  121.  
  122. ;****************************************************************************
  123. ;
  124. ; Equates used by long period Zen Timer
  125. ;
  126. ;****************************************************************************
  127.  
  128. ; Set PS2 to 0 to assemble for use on a fully 8253 compatible system. Set
  129. ; it to 1 for PS/2 computers and partially compatible 8253 systems.
  130.  
  131. PS2                equ        1
  132.  
  133. ; Base address of 8253 timer chip
  134.  
  135. BASE_8253        =        40h
  136.  
  137. ; The address of the timer 0 count registers in the 8253
  138.  
  139. TIMER_0_8253    =        BASE_8253 + 0
  140.  
  141. ; The address of the mode register in the 8253
  142.  
  143. MODE_8253        =        BASE_8253 + 3
  144.  
  145. ; The address of the BIOS timer count variable in the BIOS data area.
  146.  
  147. TIMER_COUNT        =        46Ch
  148.  
  149. ; Macro to emulate a POPF instruction in order to fix the bug in some
  150. ; 80286 chips which allows interrupts to occur during a POPF even when
  151. ; interrupts are disabled.
  152.  
  153. macro    MPOPF
  154.         local    p1,p2
  155.         jmp        short p2
  156. p1:        iret                    ; Jump to pushed address & pop flags
  157. p2:        push    cs                ; construct far return address to
  158.         call    p1                ;  the next instructionz
  159. endm
  160.  
  161. ; Macro to delay briefly to ensure that enough time has elapsed between
  162. ; successive I/O accesses so that the device being accessed can respond
  163. ; to both accesses even on a very fast PC.
  164.  
  165. macro    DELAY
  166.         jmp        $+2
  167.         jmp        $+2
  168.         jmp        $+2
  169. endm
  170.  
  171. begcodeseg    lztimer                ; Start of code segment
  172.  
  173. StartBIOSCountLow    dw    ?        ; BIOS count low word at the start of
  174.                                 ;  the timing period
  175. StartBIOSCountHigh    dw    ?        ; BIOS count high word at the start of
  176.                                 ;  the timing period
  177. EndBIOSCountLow        dw    ?        ; BIOS count low word at the end of
  178.                                 ;  the timing period
  179. EndBIOSCountHigh    dw    ?        ; BIOS count high word at the end of
  180.                                 ;  the timing period
  181. EndTimedCount        dw    ?        ; Timer 0 count at the end of
  182.                                 ;  the timing period
  183. ReferenceCount        dw    ?        ; Number of counts required to execute timer
  184.                                 ;  overhead code
  185.  
  186. ; String printed to report results.
  187.  
  188. label            OutputStr byte
  189.                 db        0dh, 0ah, 'Timed count: '
  190. TimedCountStr    db        10 dup (?)
  191.                 db        ' microseconds', 0ah, 0dh
  192.                 db        '$'
  193.  
  194. ; Temporary storage for timed count as it's divided down by powers
  195. ; of ten when converting from doubleword binary to ASCII
  196.  
  197. CurrentCountLow        dw    ?
  198. CurrentCountHigh    dw    ?
  199.  
  200. ; Powers of ten table use to perform division by 10 when doing doubleword
  201. ; conversion from binary to ASCII.
  202.  
  203. label    PowersOfTen word
  204.         dd        1
  205.         dd        10
  206.         dd        100
  207.         dd        1000
  208.         dd        10000
  209.         dd        100000
  210.         dd        1000000
  211.         dd        10000000
  212.         dd        100000000
  213.         dd        1000000000
  214. label    PowersOfTenEnd word
  215.  
  216. ; String printed to report that the high word of the BISO count changed
  217. ; while timing (an hour elapsed or midnight was crossed),
  218. ; and so the count is invalid and the test needs to be rerun.
  219.  
  220. label    TurnOverStr byte
  221.         db    0dh,0ah
  222.         db    '****************************************************',0dh,0ah
  223.         db    '* Either midnight passed or an hour or more passed *',0dh,0ah
  224.         db    '* while timing was in progress. If the former was  *',0dh,0ah
  225.         db    '* the case, please re-run the test; if the latter  *',0dh,0ah
  226.         db    '* was the case, the test code takes too long to    *',0dh,0ah
  227.         db    '* run to be timed by the long-period Zen Timer.    *',0dh,0ah
  228.         db    '* Suggestions: Use the DOS TIME command, the DOS   *',0dh,0ah
  229.         db    '* time function, or a watch!                       *',0dh,0ah
  230.         db    '****************************************************',0dh,0ah
  231.         db    '$'
  232.  
  233. ;----------------------------------------------------------------------------
  234. ; void LZTimerOn(void);
  235. ;----------------------------------------------------------------------------
  236. ; Starts the Long period Zen timer counting.
  237. ;----------------------------------------------------------------------------
  238. procfar        _LZTimerOn
  239.  
  240. ; Save the context of the program being timed
  241.  
  242.         push    ax
  243.         pushf
  244.  
  245. ; Set the timer 0 of the 8253 to mode 2 (divide-by-N), to cause
  246. ; linear counting rather than count-by-two counting. Also stops
  247. ; timer 0 until the timer count is loaded, except on PS/2 computers.
  248.  
  249.         mov        al,00110100b        ; mode 2
  250.         out        MODE_8253,al
  251.  
  252. ; Set the timer count to 0, so we know we won't get another timer
  253. ; interrupt right away. Note: this introduces and inaccuracy of up to 54 ms
  254. ; in the system clock count each time it is executed.
  255.  
  256.         DELAY
  257.         sub        al,al
  258.         out        TIMER_0_8253,al        ; lsb
  259.         DELAY
  260.         out        TIMER_0_8253,al        ; msb
  261.  
  262. ; In case interrupts are disabled, enable interrupts briefly to allow the
  263. ; interrupt generated when switching from mode 3 to mode 2 to be recognised.
  264. ; Interrupts must be enabled for at least 210 ns to allow time for that
  265. ; interrupt to occur. Here, 10 jumps are used for the delay to ensure that
  266. ; the delay time will be more than enough even on a very fast PC.
  267.  
  268.         pushf
  269.         sti
  270.         rept    10
  271.         jmp        $+2
  272.         endm
  273.         MPOPF
  274.  
  275. ; Store the timing start BIOS count.
  276. ; (Since the timer count was just set to 0, the BIOS count will stay the
  277. ; same for the next 54 ms, so we don't need to disable interrupts in order
  278. ; to avoid getting a half-changed count.)
  279.  
  280.         push    ds
  281.         sub        ax,ax
  282.         mov        ds,ax
  283.         mov        ax,[ds:TIMER_COUNT+2]
  284.         mov        [StartBIOSCountHigh],ax
  285.         mov        ax,[ds:TIMER_COUNT]
  286.         mov        [StartBIOSCountLow],ax
  287.         pop        ds
  288.  
  289. ; Set the timer count to 0 again to start the timing interval.
  290.  
  291.         mov        al,00110100b        ; set up to load initial
  292.         out        MODE_8253,al        ; timer count
  293.         DELAY
  294.         sub        al,al
  295.         out        TIMER_0_8253,al        ; load count lsb
  296.         DELAY
  297.         out        TIMER_0_8253,al        ; load count msb
  298.  
  299. ; Restore the context and return.
  300.  
  301.         MPOPF                        ; keeps interrupts off
  302.         pop        ax
  303.         ret
  304.  
  305. procend        _LZTimerOn
  306.  
  307. ;----------------------------------------------------------------------------
  308. ; void LZTimerOff(void);
  309. ;----------------------------------------------------------------------------
  310. ; Stops the long period Zen timer and saves count.
  311. ;----------------------------------------------------------------------------
  312. procfar        _LZTimerOff
  313.  
  314. ; Save the context of the program being timed
  315.  
  316.         pushf
  317.         push    ax
  318.         push    cx
  319.  
  320. ; In case interrupts are disabled, enable interrupts briefly to allow
  321. ; any pending interrupts to be handled. Interrupts must be enabled for at
  322. ; leas 210 ns to allow time for that interrupt to occur. Here, 10 jumps
  323. ; are used for the delay to ensure that the delay will be more than long
  324. ; enough even on a very fast PC.
  325.  
  326.         sti
  327.         rept    10
  328.         jmp        $+2
  329.         endm
  330.  
  331. ; Latch the timer count.
  332.  
  333. if PS2
  334.  
  335.         mov        al,00000000b        ; latch timer 0
  336.         out        MODE_8253,al
  337.  
  338. ; This is where a one instruction long window exists on the PS/2. The timer
  339. ; count and the BIOS count can lose synchronization; since the timer keeps
  340. ; counting after it's latched, it can turn over right after it's latched
  341. ; and cause the BIOS count to turn over before interrupts are disabled,
  342. ; leaving us with a timer count from before the timer turned over coupled
  343. ; with a BIOS count from after the timer turned over. The result is a count
  344. ; that's 54 ms too long.
  345.  
  346. else
  347.  
  348. ; Set timer 0 to mode 2 (divide-by-N), waiting for a 2-byte count load,
  349. ; which stops timer 0 until the count is loaded. (Only works on fully
  350. ; 8253 compatible chips).
  351.  
  352.         mov        al,00110100b        ; Mode 2
  353.         out        MODE_8253,al
  354.         DELAY
  355.         mov        al,00000000b        ; Latch timer 0 count
  356.         out        MODE_8253,al
  357.  
  358. endif
  359.  
  360.         cli                            ; Stop the BIOS count
  361.  
  362. ; Read the BIOS count. (Since interrupts are disabled, the BIOS
  363. ; count won't change).
  364.  
  365.         push    ds
  366.         sub        ax,ax
  367.         mov        ds,ax
  368.         mov        ax,[ds:TIMER_COUNT+2]
  369.         mov        [EndBIOSCountHigh],ax
  370.         mov        ax,[ds:TIMER_COUNT]
  371.         mov        [EndBIOSCountLow],ax
  372.         pop        ds
  373.  
  374. ; Read out the count we latched earlier.
  375.  
  376.         in        al,TIMER_0_8253        ; least significant byte
  377.         DELAY
  378.         mov        ah,al
  379.         in        al,TIMER_0_8253        ; most significant byte
  380.         xchg    ah,al
  381.         neg        ax                    ; Convert from countdown remaining
  382.                                     ;  to elapsed count
  383.         mov        [EndTimedCount],ax
  384.  
  385. ; Restart timer 0, which is still waiting for an initial count
  386. ; to be loaded.
  387.  
  388. ife    PS2
  389.  
  390.         DELAY
  391.         mov        al,00110100b        ; mode 2, waiting to load a 2 byte count
  392.         out        MODE_8253,al
  393.         DELAY
  394.         sub        al,al
  395.         out        TIMER_0_8253,al        ; lsb
  396.         DELAY
  397.         mov        al,ah
  398.         out        TIMER_0_8253,al        ; msb
  399.  
  400. endif
  401.  
  402.         sti                            ; Let the BIOS count continue
  403.  
  404. ; Time a zero-length code fragment, to get a reference count for how
  405. ; much overhead this routine has. Time it 16 times and average it, for
  406. ; accuracy, rounding the result.
  407.  
  408.         mov        [ReferenceCount],0
  409.         mov        cx,16
  410.         cli                            ; interrupts off to allow a precise
  411.                                     ;  reference count
  412. @@RefLoop:
  413.         call    ReferenceTimerOn
  414.         call    ReferenceTimerOff
  415.         loop    @@RefLoop
  416.         sti
  417.         add        [ReferenceCount],8    ; total + (0.5 * 16)
  418.         mov        cl,4
  419.         shr        [ReferenceCount],cl    ; (total) / 16 + 0.5
  420.  
  421. ; Restore the context of the program being timed and return to it.
  422.  
  423.         pop        cx
  424.         pop        ax
  425.         MPOPF
  426.         ret
  427.  
  428. procend        _LZTimerOff
  429.  
  430. ;----------------------------------------------------------------------------
  431. ; ReferenceTimerOn
  432. ;----------------------------------------------------------------------------
  433. ; Called by PZTimerOff to start timer for overhead measurements.
  434. ;----------------------------------------------------------------------------
  435. proc        ReferenceTimerOn far
  436.  
  437. ; Save the context of the program being timed
  438.  
  439.         push    ax
  440.         pushf                        ; Interrupts are already off
  441.  
  442. ; Set the timer 0 of the 8253 to mode 2 (divide-by-N), to cause
  443. ; linear counting rather than count-by-two counting.
  444.  
  445.         mov        al,00110100b        ; set up to load
  446.         out        MODE_8253,al        ; timer count
  447.         DELAY
  448.  
  449. ; Set the timer count to 0
  450.  
  451.         sub        al,al
  452.         out        TIMER_0_8253,al        ; load count lsb
  453.         DELAY
  454.         out        TIMER_0_8253,al        ; load count msb
  455.  
  456. ; Restore the context and return.
  457.  
  458.         MPOPF
  459.         pop        ax
  460.         ret
  461.  
  462. procend        ReferenceTimerOn
  463.  
  464. ;----------------------------------------------------------------------------
  465. ; ReferenceTimerOff
  466. ;----------------------------------------------------------------------------
  467. ; Called by PZTimerOff to stop timer and add result to ReferenceCount
  468. ; for overhead measurements.
  469. ;----------------------------------------------------------------------------
  470. proc        ReferenceTimerOff far
  471.  
  472. ; Save the context of the program being timed
  473.  
  474.         pushf
  475.         push    ax
  476.         push    cx
  477.  
  478. ; Match the interrupt window delay in LZTimerOff
  479.  
  480.         sti
  481.         rept    10
  482.         jmp        $+2
  483.         endm
  484.  
  485. ; Latch the count and read it.
  486.  
  487.         mov        al,00000000b        ; latch timer 0
  488.         out        MODE_8253,al
  489.         DELAY
  490.         in        al,TIMER_0_8253        ; least significant byte
  491.         DELAY
  492.         mov        ah,al
  493.         in        al,TIMER_0_8253        ; most significant byte
  494.         xchg    ah,al
  495.         neg        ax                    ; Convert from countdown remaining
  496.                                     ;  to elapsed count
  497.         add        [ReferenceCount],ax
  498.  
  499. ; Restore the context of the program being timed and return to it.
  500.  
  501.         pop        cx
  502.         pop        ax
  503.         MPOPF
  504.         ret
  505.  
  506. procend        ReferenceTimerOff
  507.  
  508. ;----------------------------------------------------------------------------
  509. ; void LZTimerReport(void);
  510. ;----------------------------------------------------------------------------
  511. ; Report timing results found.
  512. ;----------------------------------------------------------------------------
  513. procfar        _LZTimerReport
  514.  
  515.         pushf
  516.         push    ax
  517.         push    bx
  518.         push    cx
  519.         push    dx
  520.         push    si
  521.         push    di
  522.         push    ds
  523.  
  524.         push    cs                    ; DOS functions require that DS point
  525.         pop        ds                    ;  to text to be displayed on the screen
  526.  
  527. if codesize
  528.         ASSUME    ds:lztimer_TEXT
  529. else
  530.         ASSUME    ds:_TEXT
  531. endif
  532.  
  533. ; See if midnight or more than an hour passed during timing. If so, notify
  534. ; the user.
  535.  
  536.         mov        ax,[StartBIOSCountHigh]
  537.         cmp        ax,[EndBIOSCountHigh]
  538.         jz        @@CalcBIOSTime        ; Hour count didn't change, so
  539.                                     ;  everything is fine
  540.  
  541.         inc        ax
  542.         cmp        ax,[EndBIOSCountHigh]
  543.         jnz        @@TestTooLong        ; Midnight or two hour boundaries
  544.                                     ; passed, so the results are no good
  545.         mov     ax,[EndBIOSCountLow]
  546.         cmp        ax,[StartBIOSCountLow]
  547.         jb        @@CalcBIOSTime        ; a single hour boundary passed. That's
  548.                                     ; OK, so long as the total time wasn't
  549.                                     ; more than an hour.
  550.  
  551. ; Over an hour elapsed or midnight passed during timing, which renders
  552. ; the results invalid. Notify the user. This misses the case where a
  553. ; multiple of 24 hours has passed, but we'll rely on the perspicacity of
  554. ; the user to detect that case :-).
  555.  
  556. @@TestTooLong:
  557.         mov        ah,9
  558.         mov        dx,offset TurnOverStr
  559.         int        21h
  560.         jmp        short @@Done
  561.  
  562. ; Convert the BIOS time to microseconds
  563.  
  564. @@CalcBIOSTime:
  565.         mov        ax,[EndBIOSCountLow]
  566.         sub        ax,[StartBIOSCountLow]
  567.         mov        dx,54925            ; Number of microseconds each
  568.                                     ;  BIOS count represents.
  569.         mul        dx
  570.         mov        bx,ax                ; set aside BIOS count in
  571.         mov        cx,dx                ;  microseconds
  572.  
  573. ; Convert timer count to microseconds
  574.  
  575.         mov        ax,[EndTimedCount]
  576.         mov        si,8381
  577.         mul        si
  578.         mov        si,10000
  579.         div        si                    ; * 0.8381 = * 8381 / 10000
  580.  
  581. ; Add the timer and BIOS counts together to get an overall time in
  582. ; microseconds.
  583.  
  584.         add        bx,ax
  585.         adc        cx,0
  586.  
  587. ; Subtract the timer overhead and save the result
  588.  
  589.         mov        ax,[ReferenceCount]
  590.         mov        si,8381
  591.         mul        si
  592.         mov        si,10000
  593.         div        si                    ; * 0.8381 = * 8381 / 10000
  594.         sub        bx,ax
  595.         sbb        cx,0
  596.         mov        [CurrentCountLow],bx
  597.         mov        [CurrentCountHigh],cx
  598.  
  599. ; Convert the result to an ASCII string by trial subtractions of
  600. ; powers of 10.
  601.  
  602.         mov        di,(offset PowersOfTenEnd - offset PowersOfTen) - 4
  603.         mov        si,offset TimedCountStr
  604.  
  605. @@CTSNextDigit:
  606.         mov        bl,'0'
  607.  
  608. @@CTSLoop:
  609.         mov        ax,[CurrentCountLow]
  610.         mov        dx,[CurrentCountHigh]
  611.         sub        ax,[PowersOfTen+di]
  612.         sbb        dx,[PowersOfTen+di+2]
  613.         jc        @@CTSNextPowerDown
  614.         inc        bl
  615.         mov        [CurrentCountLow],ax
  616.         mov        [CurrentCountHigh],dx
  617.         jmp        @@CTSLoop
  618.  
  619. @@CTSNextPowerDown:
  620.         mov        [si],bl
  621.         inc        si
  622.         sub        di,4
  623.         jns        @@CTSNextDigit
  624.  
  625. ; Print the results.
  626.  
  627.         mov        ah,9
  628.         mov        dx,offset OutputStr
  629.         int        21h
  630.  
  631. @@Done:
  632.         pop        ds
  633.         pop        di
  634.         pop        si
  635.         pop        dx
  636.         pop        cx
  637.         pop        bx
  638.         pop        ax
  639.         MPOPF
  640.         ret
  641.  
  642.         ASSUME    ds:DGROUP
  643.  
  644. procend        _LZTimerReport
  645.  
  646. ;----------------------------------------------------------------------------
  647. ; unsigned long LZTimerCount(void);
  648. ;----------------------------------------------------------------------------
  649. ; Returns an unsigned long representing the net time in microseconds.
  650. ;
  651. ; If either and hour has passed, or midnight passed while timing, we
  652. ; return 0xFFFFFFFF as the count (which is not a possible count in itself).
  653. ;----------------------------------------------------------------------------
  654. procfar        _LZTimerCount
  655.  
  656.         setupDS
  657.  
  658. ; See if midnight or more than an hour passed during timing. If so, notify
  659. ; the user.
  660.  
  661.         mov        ax,[StartBIOSCountHigh]
  662.         cmp        ax,[EndBIOSCountHigh]
  663.         jz        @@CalcBIOSTime        ; Hour count didn't change, so
  664.                                     ;  everything is fine
  665.  
  666.         inc        ax
  667.         cmp        ax,[EndBIOSCountHigh]
  668.         jnz        @@TestTooLong        ; Midnight or two hour boundaries
  669.                                     ; passed, so the results are no good
  670.         mov     ax,[EndBIOSCountLow]
  671.         cmp        ax,[StartBIOSCountLow]
  672.         jb        @@CalcBIOSTime        ; a single hour boundary passed. That's
  673.                                     ; OK, so long as the total time wasn't
  674.                                     ; more than an hour.
  675.  
  676. ; Over an hour elapsed or midnight passed during timing, which renders
  677. ; the results invalid. Notify the user. This misses the case where a
  678. ; multiple of 24 hours has passed, but we'll rely on the perspicacity of
  679. ; the user to detect that case :-).
  680.  
  681. @@TestTooLong:
  682.         mov        ax,0FFFFh
  683.         mov        dx,0FFFFh
  684.         jmp        short @@Done
  685.  
  686. ; Convert the BIOS time to microseconds
  687.  
  688. @@CalcBIOSTime:
  689.         mov        ax,[EndBIOSCountLow]
  690.         sub        ax,[StartBIOSCountLow]
  691.         mov        dx,54925            ; Number of microseconds each
  692.                                     ;  BIOS count represents.
  693.         mul        dx
  694.         mov        bx,ax                ; set aside BIOS count in
  695.         mov        cx,dx                ;  microseconds
  696.  
  697. ; Convert timer count to microseconds
  698.  
  699.         mov        ax,[EndTimedCount]
  700.         mov        si,8381
  701.         mul        si
  702.         mov        si,10000
  703.         div        si                    ; * 0.8381 = * 8381 / 10000
  704.  
  705. ; Add the timer and BIOS counts together to get an overall time in
  706. ; microseconds.
  707.  
  708.         add        bx,ax
  709.         adc        cx,0
  710.  
  711. ; Subtract the timer overhead and save the result
  712.  
  713.         mov        ax,[ReferenceCount]
  714.         mov        si,8381
  715.         mul        si
  716.         mov        si,10000
  717.         div        si                    ; * 0.8381 = * 8381 / 10000
  718.         sub        bx,ax
  719.         sbb        cx,0
  720.         mov        ax,bx
  721.         mov        dx,cx
  722.  
  723. @@Done:
  724.         restoreDS
  725.  
  726.         ret
  727.  
  728. procend        _LZTimerCount
  729.  
  730. endcodeseg    lztimer
  731.  
  732.         END                        ; End of module
  733.