home *** CD-ROM | disk | FTP | other *** search
/ Megazine / Megazine-1.iso / PROGRAMA / C / ZTIMER23 / SRC / ZTIMER / LZTIMER.ASM next >
Encoding:
Assembly Source File  |  1996-02-05  |  11.9 KB  |  424 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. ;*                  Copyright (C) 1996 SciTech Software
  13. ;*
  14. ;* Filename:    $Workfile:   lztimer.asm  $
  15. ;* Version:        $Revision:   1.0  $
  16. ;*
  17. ;* Language:    80386 Assembler
  18. ;* Environment:    IBM PC (MS DOS)
  19. ;*
  20. ;* Description:    Uses the 8253 timer and the BIOS time-of-day count to time
  21. ;*                the performance of code that takes less than an hour to
  22. ;*                execute.
  23. ;*
  24. ;*                The routines in this package only works with interrupts
  25. ;*                enabled, and in fact will explicitly turn interrupts on
  26. ;*                in order to ensure we get accurate results from the timer.
  27. ;*
  28. ;*    Externally 'C' callable routines:
  29. ;*
  30. ;*    LZTimerOn:        Saves the BIOS time of day count and starts the
  31. ;*                    long period Zen Timer.
  32. ;*
  33. ;*    LZTimerLap:        Latches the current count, and keeps the timer running
  34. ;*
  35. ;*    LZTimerOff:        Stops the long-period Zen Timer and saves the timer
  36. ;*                    count and the BIOS time of day count.
  37. ;*
  38. ;*    LZTimerCount:    Returns an unsigned long representing the timed count
  39. ;*                    in microseconds. If more than an hour passed during
  40. ;*                    the timing interval, LZTimerCount will return the
  41. ;*                    value 0xFFFFFFFF (an invalid count).
  42. ;*
  43. ;*    Note:    If either more than an hour passes between calls to LZTimerOn
  44. ;*            and LZTimerOff, an error is reported. For timing code that takes
  45. ;*            more than a few minutes to execute, use the low resolution
  46. ;*            Ultra Long Period Zen Timer code, which should be accurate
  47. ;*            enough for most purposes.
  48. ;*
  49. ;*    Note:    Each block of code being timed should ideally be run several
  50. ;*            times, with at least two similar readings required to
  51. ;*            establish a true measurement, in order to eliminate any
  52. ;*            variability caused by interrupts.
  53. ;*
  54. ;*    Note:    Interrupts must not be disabled for more than 54 ms at a
  55. ;*            stretch during the timing interval. Because interrupts are
  56. ;*            enabled, key, mice, and other devices that generate interrupts
  57. ;*            should not be used during the timing interval.
  58. ;*
  59. ;*    Note:    Any extra code running off the timer interrupt (such as
  60. ;*            some memory resident utilities) will increase the time
  61. ;*            measured by the Zen Timer.
  62. ;*
  63. ;*    Note:    These routines can introduce inaccuracies of up to a few
  64. ;*            tenths of a second into the system clock count for each
  65. ;*            code section being timed. Consequently, it's a good idea to
  66. ;*            reboot at the conclusion of timing sessions. (The
  67. ;*            battery-backed clock, if any, is not affected by the Zen
  68. ;*            timer.)
  69. ;*
  70. ;*  All registers and all flags are preserved by all routines, except
  71. ;*    interrupts which are always turned on
  72. ;*
  73. ;* $Date:   05 Feb 1996 14:35:54  $ $Author:   KendallB  $
  74. ;*
  75. ;****************************************************************************
  76.  
  77.         IDEAL
  78.  
  79. INCLUDE "model.mac"                ; Memory model macros
  80.  
  81. header    lztimer                    ; Set up memory model
  82.  
  83. ifndef  __WINDOWS16__
  84. ifndef  __WINDOWS32__
  85.  
  86. ;****************************************************************************
  87. ;
  88. ; Equates used by long period Zen Timer
  89. ;
  90. ;****************************************************************************
  91.  
  92. ; Base address of 8253 timer chip
  93.  
  94. BASE_8253        =        40h
  95.  
  96. ; The address of the timer 0 count registers in the 8253
  97.  
  98. TIMER_0_8253    =        BASE_8253 + 0
  99.  
  100. ; The address of the mode register in the 8253
  101.  
  102. MODE_8253        =        BASE_8253 + 3
  103.  
  104. ; The address of the BIOS timer count variable in the BIOS data area.
  105.  
  106. TIMER_COUNT        =        6Ch
  107.  
  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.
  111.  
  112. macro    DELAY
  113.         jmp        $+2
  114.         jmp        $+2
  115.         jmp        $+2
  116. endm
  117.  
  118. begdataseg    lztimer
  119.  
  120.         $EXTRN  __ZTimerBIOS,USHORT
  121.  
  122. StartBIOSCount      dd  ?       ; Starting BIOS count dword
  123. EndBIOSCount        dd    ?        ; Ending BIOS count dword
  124. EndTimedCount        dw    ?        ; Timer 0 count at the end of timing period
  125.  
  126. enddataseg    lztimer
  127.  
  128. begcodeseg    lztimer                ; Start of code segment
  129.  
  130. ;----------------------------------------------------------------------------
  131. ; void LZTimerOn(void);
  132. ;----------------------------------------------------------------------------
  133. ; Starts the Long period Zen timer counting.
  134. ;----------------------------------------------------------------------------
  135. procstart        _LZTimerOn
  136.  
  137. ; Set the timer 0 of the 8253 to mode 2 (divide-by-N), to cause
  138. ; linear counting rather than count-by-two counting. Also stops
  139. ; timer 0 until the timer count is loaded, except on PS/2 computers.
  140.  
  141.         mov        al,00110100b        ; mode 2
  142.         out        MODE_8253,al
  143.  
  144. ; Set the timer count to 0, so we know we won't get another timer
  145. ; interrupt right away. Note: this introduces an inaccuracy of up to 54 ms
  146. ; in the system clock count each time it is executed.
  147.  
  148.         DELAY
  149.         sub        al,al
  150.         out        TIMER_0_8253,al        ; lsb
  151.         DELAY
  152.         out        TIMER_0_8253,al        ; msb
  153.  
  154. ; Store the timing start BIOS count
  155.  
  156.         push    es
  157.         mov        ax,[__ZTimerBIOS]
  158.         mov        es,ax
  159.         cli                            ; No interrupts while we grab the count
  160.         mov        eax,[es:TIMER_COUNT]
  161.         sti
  162.         mov        [StartBIOSCount],eax
  163.         pop        es
  164.  
  165. ; Set the timer count to 0 again to start the timing interval.
  166.  
  167.         mov        al,00110100b        ; set up to load initial
  168.         out        MODE_8253,al        ; timer count
  169.         DELAY
  170.         sub        al,al
  171.         out        TIMER_0_8253,al        ; load count lsb
  172.         DELAY
  173.         out        TIMER_0_8253,al        ; load count msb
  174.  
  175.         ret
  176.  
  177. procend        _LZTimerOn
  178.  
  179. ;----------------------------------------------------------------------------
  180. ; void LZTimerOff(void);
  181. ;----------------------------------------------------------------------------
  182. ; Stops the long period Zen timer and saves count.
  183. ;----------------------------------------------------------------------------
  184. procstart        _LZTimerOff
  185.  
  186. ; Latch the timer count.
  187.  
  188.         mov        al,00000000b        ; latch timer 0
  189.         out        MODE_8253,al
  190.         cli                            ; Stop the BIOS count
  191.  
  192. ; Read the BIOS count. (Since interrupts are disabled, the BIOS
  193. ; count won't change).
  194.  
  195.         push    es
  196.         mov     ax,[__ZTimerBIOS]
  197.         mov        es,ax
  198.         mov        eax,[es:TIMER_COUNT]
  199.         mov        [EndBIOSCount],eax
  200.         pop        es
  201.  
  202. ; Read out the count we latched earlier.
  203.  
  204.         in        al,TIMER_0_8253        ; least significant byte
  205.         DELAY
  206.         mov        ah,al
  207.         in        al,TIMER_0_8253        ; most significant byte
  208.         xchg    ah,al
  209.         neg        ax                    ; Convert from countdown remaining
  210.                                     ;  to elapsed count
  211.         mov        [EndTimedCount],ax
  212.         sti                            ; Let the BIOS count continue
  213.  
  214.         ret
  215.  
  216. procend        _LZTimerOff
  217.  
  218. ;----------------------------------------------------------------------------
  219. ; unsigned long LZTimerLap(void)
  220. ;----------------------------------------------------------------------------
  221. ; Latches the current count and converts it to a microsecond timing value,
  222. ; but leaves the timer still running. We dont check for and overflow,
  223. ; where the time has gone over an hour in this routine, since we want it
  224. ; to execute as fast as possible.
  225. ;----------------------------------------------------------------------------
  226. procstart        _LZTimerLap
  227.  
  228.         use_ebx                        ; Save EBX for 32 bit code
  229.  
  230. ; Latch the timer count.
  231.  
  232.         mov     al,00000000b        ; latch timer 0
  233.         out        MODE_8253,al
  234.         cli                         ; Stop the BIOS count
  235.  
  236. ; Read the BIOS count. (Since interrupts are disabled, the BIOS
  237. ; count won't change).
  238.  
  239.         push    es
  240.         mov        ax,[__ZTimerBIOS]
  241.         mov        es,ax
  242.         mov        eax,[es:TIMER_COUNT]
  243.         mov        [EndBIOSCount],eax
  244.         pop        es
  245.  
  246. ; Read out the count we latched earlier.
  247.  
  248.         in        al,TIMER_0_8253        ; least significant byte
  249.         DELAY
  250.         mov        ah,al
  251.         in        al,TIMER_0_8253        ; most significant byte
  252.         xchg    ah,al
  253.         neg        ax                    ; Convert from countdown remaining
  254.                                     ;  to elapsed count
  255.         mov        [EndTimedCount],ax
  256.         sti                            ; Let the BIOS count continue
  257.  
  258. ; See if a midnight boundary has passed and adjust the finishing BIOS
  259. ; count by the number of ticks in 24 hours. We wont be able to detect
  260. ; more than 24 hours, but at least we can time across a midnight
  261. ; boundary
  262.  
  263.         mov        eax,[EndBIOSCount]        ; Is end < start?
  264.         cmp        eax,[StartBIOSCount]
  265.         jae        @@CalcBIOSTime            ; No, calculate the time taken
  266.  
  267. ; Adjust the finishing time by adding the number of ticks in 24 hours
  268. ; (1573040).
  269.  
  270.         add        [EndBIOSCount],1800B0h
  271.  
  272. ; Convert the BIOS time to microseconds
  273.  
  274. @@CalcBIOSTime:
  275.         mov        ax,[WORD EndBIOSCount]
  276.         sub        ax,[WORD StartBIOSCount]
  277.         mov        dx,54925            ; Number of microseconds each
  278.                                     ;  BIOS count represents.
  279.         mul        dx
  280.         mov        bx,ax                ; set aside BIOS count in
  281.         mov        cx,dx                ;  microseconds
  282.  
  283. ; Convert timer count to microseconds
  284.  
  285.         push    _si
  286.         mov        ax,[EndTimedCount]
  287.         mov        si,8381
  288.         mul        si
  289.         mov        si,10000
  290.         div        si                    ; * 0.8381 = * 8381 / 10000
  291.         pop        _si
  292.  
  293. ; Add the timer and BIOS counts together to get an overall time in
  294. ; microseconds.
  295.  
  296.         add        ax,bx
  297.         adc        cx,0
  298. if flatmodel
  299.         shl        ecx,16
  300.         mov        cx,ax
  301.         mov        eax,ecx                ; EAX := timer count
  302. else
  303.         mov        dx,cx
  304. endif
  305.         unuse_ebx                    ; Restore EBX for 32 bit code
  306.         ret
  307.  
  308. procend        _LZTimerLap
  309.  
  310. ;----------------------------------------------------------------------------
  311. ; unsigned long LZTimerCount(void);
  312. ;----------------------------------------------------------------------------
  313. ; Returns an unsigned long representing the net time in microseconds.
  314. ;
  315. ; If an hour has passed while timing, we return 0xFFFFFFFF as the count
  316. ; (which is not a possible count in itself).
  317. ;----------------------------------------------------------------------------
  318. procstart        _LZTimerCount
  319.  
  320.         use_ebx                        ; Save EBX for 32 bit code
  321.  
  322. ; See if a midnight boundary has passed and adjust the finishing BIOS
  323. ; count by the number of ticks in 24 hours. We wont be able to detect
  324. ; more than 24 hours, but at least we can time across a midnight
  325. ; boundary
  326.  
  327.         mov        eax,[EndBIOSCount]        ; Is end < start?
  328.         cmp        eax,[StartBIOSCount]
  329.         jae        @@CheckForHour            ; No, check for hour passing
  330.  
  331. ; Adjust the finishing time by adding the number of ticks in 24 hours
  332. ; (1573040).
  333.  
  334.         add        [EndBIOSCount],1800B0h
  335.  
  336. ; See if more than an hour passed during timing. If so, notify the user.
  337.  
  338. @@CheckForHour:
  339.         mov        ax,[WORD StartBIOSCount+2]
  340.         cmp        ax,[WORD EndBIOSCount+2]
  341.         jz        @@CalcBIOSTime        ; Hour count didn't change, so
  342.                                     ;  everything is fine
  343.  
  344.         inc        ax
  345.         cmp        ax,[WORD EndBIOSCount+2]
  346.         jnz        @@TestTooLong        ; Two hour boundaries passed, so the
  347.                                     ;  results are no good
  348.         mov     ax,[WORD EndBIOSCount]
  349.         cmp        ax,[WORD StartBIOSCount]
  350.         jb        @@CalcBIOSTime        ; a single hour boundary passed. That's
  351.                                     ; OK, so long as the total time wasn't
  352.                                     ; more than an hour.
  353.  
  354. ; Over an hour elapsed passed during timing, which renders
  355. ; the results invalid. Notify the user. This misses the case where a
  356. ; multiple of 24 hours has passed, but we'll rely on the perspicacity of
  357. ; the user to detect that case :-).
  358.  
  359. @@TestTooLong:
  360. if    flatmodel
  361.         mov        eax,0FFFFFFFFh
  362. else
  363.         mov        ax,0FFFFh
  364.         mov        dx,0FFFFh
  365. endif
  366.         jmp        short @@Done
  367.  
  368. ; Convert the BIOS time to microseconds
  369.  
  370. @@CalcBIOSTime:
  371.         mov        ax,[WORD EndBIOSCount]
  372.         sub        ax,[WORD StartBIOSCount]
  373.         mov        dx,54925            ; Number of microseconds each
  374.                                     ;  BIOS count represents.
  375.         mul        dx
  376.         mov        bx,ax                ; set aside BIOS count in
  377.         mov        cx,dx                ;  microseconds
  378.  
  379. ; Convert timer count to microseconds
  380.  
  381.         push    _si
  382.         mov        ax,[EndTimedCount]
  383.         mov        si,8381
  384.         mul        si
  385.         mov        si,10000
  386.         div        si                    ; * 0.8381 = * 8381 / 10000
  387.         pop        _si
  388.  
  389. ; Add the timer and BIOS counts together to get an overall time in
  390. ; microseconds.
  391.  
  392.         add        ax,bx
  393.         adc        cx,0
  394. if flatmodel
  395.         shl        ecx,16
  396.         mov        cx,ax
  397.         mov        eax,ecx                ; EAX := timer count
  398. else
  399.         mov        dx,cx
  400. endif
  401.  
  402. @@Done:
  403.         unuse_ebx                    ; Restore EBX for 32 bit code
  404.         ret
  405.  
  406. procend        _LZTimerCount
  407.  
  408. procstart   _LZ_disable
  409.         cli
  410.         ret
  411. procend     _LZ_disable
  412.  
  413. procstart   _LZ_enable
  414.         sti
  415.         ret
  416. procend     _LZ_enable
  417.  
  418. endcodeseg    lztimer
  419.  
  420. endif
  421. endif
  422.  
  423.         END                            ; End of module
  424.