News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Determine CPUSpeed in PMode

Started by thomasantony, April 21, 2005, 11:07:34 AM

Previous topic - Next topic

thomasantony

Hi,
   I am using the following code for calculating the operating frequency of my processor in my OS. I found it at :

http://www.mega-tokyo.com/osfaq2/index.php/How%20can%20I%20tell%20CPU%20speed%20%3F?

They say it expects the timer (PIT) to be programed to 100Hz. The variable [Ticks] is incremented every time the Timer Interrupt is called. Can someone tell me how I can program the timer. I tried googling but none of the results were very clear. Some said it ran at The timer is running at the default frequency as I have not tampered with it yet. I read somewhere the default frequency is 18.2 Hz or something. Now it gives me an outrageous value like 5927667295 MHz :dazzled:  . Well I wish I had one like that. ;) But unfortunately I only have 850 MHz.

CPUSpeed:
push ebx

mov eax,1
cpuid
test edx,10h ; check whether RDTSC is supported
jnz @cpus_bad
mov ebx,[Ticks]
@cpus_waittimer: ; Wait for TIMER IRQ to fire
cmp ebx,[Ticks]
jz @cpus_waittimer

rdtsc ; returns ticks in edx:eax QWORD
mov [ticksLow],eax ; low DWORD in [ebp-4]
mov [ticksHigh],edx ; high DWORD in [ebp-8]
add ebx,2 ; Now ebx is [ticks]-1. So next tick is ebx+2

@cpus_waittimer2:
cmp ebx,[Ticks]
jnz @cpus_waittimer2
rdtsc
sub eax,[ticksLow]
sbb edx,[ticksHigh]

mov ebx,10000 ; divide to get speed in MHz
div ebx
and eax,0FFh
jmp @cpus_exit
@cpus_bad:
mov eax,-1
@cpus_exit:
pop ebx
ret

CPUSpeed:
push ebx

mov al,36h
out 43h,al
mov ax,100
out 40h,al
mov al,ah
out 40h,al

; mov dword[Ticks],0
mov eax,1
cpuid
test edx,10h ; check whether RDTSC is supported
jnz @cpus_bad
mov ebx,[Ticks]
@cpus_waittimer: ; Wait for TIMER IRQ to fire
cmp ebx,[Ticks]
jz @cpus_waittimer

rdtsc ; returns ticks in edx:eax QWORD
mov [ticksLow],eax ; low DWORD in [ebp-4]
mov [ticksHigh],edx ; high DWORD in [ebp-8]
add ebx,2 ; Now ebx is [ticks]-1. So next tick is ebx+2

@cpus_waittimer2:
cmp ebx,[Ticks]
jnz @cpus_waittimer2
rdtsc
sub eax,[ticksLow]
sbb edx,[ticksHigh]

mov ebx,10000 ; divide to get speed in MHz
div ebx
and eax,0FFh
jmp @cpus_exit
@cpus_bad:
mov eax,-1
@cpus_exit:
pop ebx
ret

ticksLow dd 0
ticksHigh dd 0

Plz help

Thomas
There are 10 types of people in the world. Those who understand binary and those who don't.


Programmer's Directory. Submit for free

MichaelW

The 100Hz frequency would make the math a little easier, but I doubt that it would be enough easier to justify reprogramming the timer. I normally use the BIOS Event Wait function for this purpose. The BIOS Event Wait function will always fail for a DOS program running under Windows 2000/XP, it depends on the RTC so it is not supported on most pre-AT systems (~1983), and it will fail if some other process is using it.

.data
    ts  dq 0
.code
    rdtsc
    mov   dword ptr ts,eax
    mov   dword ptr ts+4,edx

    ; Set up and call the BIOS RTC Delay function
    ; for a 1 second delay.
    mov   ah,86h
    mov   cx,0fh
    mov   dx,4240h
    int   15h
    jc    RTC_Delay_function_failed

    rdtsc
    sub   eax,dword ptr ts
    sbb   edx,dword ptr ts+4
    mov   ecx,1000000
    div   ecx


I did find some code that implements a 10-second delay by reprogramming system timer 2 (present on all systems, normally used for speaker sounds).

;--------------------------------------------------------------
; If the system timer clock is somewhere close to 1,193,182Hz,
; This program should delay 10 seconds between prompts.
;--------------------------------------------------------------
    .model small
    .386
.stack
.data
    tobegin   db "Press any key to begin",13,10,"$"
    finished  db "Finished, press any key to exit",13,10,"$"
.code
.startup

    ; Set the gate for timer 2 (bit 0 at I/O port 61h) to off.
    ; To avoid changing other bits in the register, read the
    ; current value, set bit 0, and write the altered value
    ; back.
    mov   dx,61h
    in    al,dx
    and   al,NOT 1
    out   dx,al

    ; Program timer 2 for LSB then MSB, mode 0, binary.
    ; The system timer control word register is at I/O
    ; port 43h. The system timer control word is set as
    ; follows:
    ;   bit 7-6:  10  = timer 2
    ;   bit 5-4:  11  = R/W LSB then MSB
    ;   bit 3-1:  000 = single timeout
    ;   bit 0:    0   = binary
    mov   dx,43h
    mov   al,0b0h
    out   dx,al

    ; The system timer normally has a 1,192,182Hz clock.
    ; For a full timer cycle you load an initial count
    ; of zero, which, because the count is decremented
    ; before it is checked for zero, causes the timer
    ; to count 65,536 clock cycles before it times out.
    ; Each cycle with take 65536/1192183 ~ 55ms, so
    ; 182 cycles should take ~10 seconds.

    mov   ah,9
    mov   dx,OFFSET tobegin
    int   21h 
    mov   ah,0
    int   16h

    mov   cx,182
  looper:
    ; Load the starting value, LSB then MSB.
    mov   dx,42h
    mov   al,0
    out   dx,al
    out   dx,al

    ; Set the gate for timer 2 (bit 0 at I/O port 61h) to on.
    mov   dx,61h
    in    al,dx
    or    al,1
    out   dx,al
   
    ; Wait until the output bit (bit 5 at I/O port 61h) is set.
  @@:
    in    al,dx
    and   al,20h
    jz    @B 

    loop looper

    mov   ah,9
    mov   dx,OFFSET finished
    int   21h
    mov   ah,0
    int   16h

    .exit
end


See Ralf Brown's Interrupt List for details.

eschew obfuscation

thomasantony

Hi Micheal,
   Thanx I will check it out. I can't use all that interrupts tho. But the working part doesn't use any interrupts right. Where is the final clock speed located in?

Thomas :U
There are 10 types of people in the world. Those who understand binary and those who don't.


Programmer's Directory. Submit for free

MichaelW

Hi Thomas,

The first block of code leaves the clock speed in EAX (or at least that is what it is supposed to do :bg). For a programmable delay without interrupts, the best I can do in the limited amount of time I have right now is provide some QuickBASIC code. You could create an ASM version of this, programmed for a 1-second delay, and substitute it for the BIOS function call in the first block of code. For better accuracy you could increase the delay period and the divider for the cycle count (both in the same ratio) and/or move the timer setup to before the first RDTSC.

SUB Delay (milliseconds%) STATIC

    ' Delays for <milliseconds%> milliseconds before returning.
    '
    ' This procedure uses system timer 2 to produce a short programmable
    ' delay. This method was chosen because it produces better accuracy
    ' than would be possible with the TIMER function and because it does
    ' not depend on a hardware interrupt (so it will continue to function
    ' with the system timer tick interrupt disabled).
    '
    ' IMPORTANT:
    '   Because system timer 2 is normally used to produce speaker
    '   sounds, this procedure will conflict with the QB SOUND and
    '   PLAY statements.

    ' Exit now if <milliseconds%> is 0. The timer decrements the count
    ' value before testing it for 0 so a starting value of 0 would cause
    ' the timer to cycle through the full range.
    IF milliseconds = 0 THEN EXIT SUB
 
    ' Set the gate for timer 2 (bit 0 at I/O port 61h) to off. To avoid
    ' changing other bits in the register, this statement reads the
    ' current value, sets bit 0, and writes the altered value back.
    OUT &H61, INP(&H61) AND NOT 1

    ' Program timer 2 for LSB then MSB, mode 0, binary. The system timer
    ' control word register is at I/O port 43h. The system timer control
    ' word is set as follows:
    '   bit 7-6: 10     = timer 2
    '   bit 5-4: 11     = R/W LSB then MSB
    '   bit 3-1: 000    = single timeout
    '   bit 0:   0      = binary
    OUT &H43, &HB0

    ' Calculate the number of full range cycles and the count for the
    ' last cycle. Each full range cycle has a duration of ~54.9 ms.
    totalCount& = milliseconds * 1193&
    cycleCount = totalCount& \ 65536
    lastCount& = totalCount& MOD 65536

    ' Do the full range cycles.
    FOR i = 1 TO cycleCount

        ' Load the starting value, LSB then MSB.
        OUT &H42, 0
        OUT &H42, 0

        ' Set the gate for timer 2 (bit 0 at I/O port 61h) to on.
        OUT &H61, INP(&H61) OR 1

        ' Wait until the output bit (bit 5 at I/O port 61h) is set.
        WAIT &H61, &H20
    NEXT

    ' Do the last cycle.
    IF lastCount& THEN
   
        ' Set the gate for timer 2 (bit 0 at I/O port 61h) to off.
        OUT &H61, INP(&H61) AND NOT 1
   
        ' Load the starting value, LSB then MSB.
        OUT &H42, LowByte(LowWord(lastCount&))
        OUT &H42, HighByte(LowWord(lastCount&))

        ' Set the gate for timer 2 to on.
        OUT &H61, INP(&H61) OR 1

        ' Wait until the output bit (bit 5 at I/O port 61h) is set.
        WAIT &H61, &H20
    END IF

    ' Set the gate for timer 2 (bit 0 at I/O port 61h) to off.
    OUT &H61, INP(&H61) AND NOT 1

END SUB

eschew obfuscation

MichaelW

I had more time than I thought. This version appears to work OK from Windows 98 MS-DOS mode, but it returns zero under Windows 2000 (expected) and GPF's under Windows 98 SE (?). And it returns the clock speed as a truncated integer.


[attachment deleted by admin]
eschew obfuscation

thomasantony

Hi,
   I tried the one after converting it to NASM. In Bochs, it displays the speed as 0 MHz :dazzled: while on my real machine it is displayed as 151 MHz  but is actually 851 MHz. Your prog gives the correct value in windows. I gues it is some problem with the constants. I can't understand how you found the LASTCOUNT value. If it is according to the formula

totalCount& = milliseconds * 1193&
cycleCount = totalCount& \ 65536
lastCount& = totalCount& MOD 65536

then it is not the value you have given. I have used these formulas with a smaller delay (125 ms ) and also with 2 seconds. No result. I will try your value and see the results

Thomas :U
There are 10 types of people in the world. Those who understand binary and those who don't.


Programmer's Directory. Submit for free

MichaelW

I calculated the constants as:

INT( 1193182 * 2 / 65536 ) = 36
1193182 * 2 - 65536 * 36 = 27068

I'll try the code on another system.

I have now tested on both of the systems I have here, and for both the program returns the same value that the DirectX Diagnostic tool returns. I tested on my Windows 98 SE system by switching to MS-DOS mode, and on my Windows 2000 system by booting from a Windows 98 SE startup disk.
eschew obfuscation

pbrennick

On Windows XP, it returns 0
On Windows ME, it crashes.

Paul