; ============================================================================= ; ; Litos8 CPU service ; ; ============================================================================= ; ----------------------------------------------------------------------------- ; Exported user functions (2): ; GetCPUInfo - get CPU info structure ; GetCPUInfoText - get CPU info text ; ----------------------------------------------------------------------------- ; ------------- Macro - short delay (roughly 1.5 microsecond) ; Alternatives of dummy ports: ; 80h: AT diagnostic port used by Linux for short delay ; 0ebh: dummy port used by some BIOSes for short delays ; 0edh: dummy port used by some BIOSes for short delays %define SHORT_DELAY out 80h,al ; ------------- macro - check if FPU numeric coprocessor is present ; returns NZ = FPU is present %macro IS_FPU 0 test byte [CPUFlags],CPU_FPU ; check FPU flag %endmacro ; ------------- macro - check if CPUID instruction is supported ; returns NZ = CPUID instruction is supported %macro IS_CPUID 0 test byte [CPUFlags],CPU_CPUID ; check CPUID flag %endmacro ; ------------- macro - check if RDTSC instruction is supported ; returns NZ = RDTSC instruction is supported %macro IS_RDTSC 0 test byte [CPUFlags],CPU_RDTSC ; check RDTSC flag %endmacro ; ------------- macro - check if MMX instructions are supported ; returns NZ = MMX instructions are supported %macro IS_MMX 0 test byte [CPUFlags],CPU_MMX ; check MMX flag %endmacro ; ------------- macro - check if SSE instructions are supported ; returns NZ = SSE instructions are supported %macro IS_SSE 0 test byte [CPUFlags],CPU_SSE ; check SSE flag %endmacro ; ------------- macro - check if SSE2 instructions are supported ; returns NZ = SSE2 instructions are supported %macro IS_SSE2 0 test byte [CPUFlags],CPU_SSE2 ; check SSE2 flag %endmacro CODEINIT_SECTION ; ----------------------------------------------------------------------------- ; INIT: Initialize CPU ; ----------------------------------------------------------------------------- ; ------------- initialize CPU info structure InitCPU: mov word [CPUVendorID],CPUVendorIDText ; vendor mov word [CPUEquipment],CPUEquipText ; equipment list ; ------------- detect numeric coprocessor test byte [Equipment],B1 ; numeric coprocessor present? jz short InitCPU1 ; no numeric coprocessor %ifndef NOFPU or byte [CPUFlags],CPU_FPU ; set FPU flag finit ; initialize FPU %endif ; ------------- detect 80186 and more (FLAG bits 12..15 cannot be cleared to 0) InitCPU1: pushf ; push flags xor ax,ax ; AX <- 0 push ax ; 0 -> stack popf ; flags <- 0 pushf ; flags -> stack pop ax ; AX <- flags popf ; pop flags and ah,0f0h ; check bits 12..15 cmp ah,0f0h ; can be changed? %if MAXCPUTYPE < CPU8086 mov al,CPUUnknown jmp short InitCPU2 %endif %if MAXCPUTYPE > CPUV30 jne short InitCPU3 ; can be changed - 80186 or more %endif ; ------------- detect V30 (MUL AL instruction does not change ZF) xor al,al ; set ZF mov al,40h ; AL <- 40h, some number mul al ; AX <- multiply 40h * 40h mov al,CPU8086 ; 8086 %if MAXCPUTYPE <= CPU8086 jmp short InitCPU2 %endif jnz short InitCPU2 ; ZF changed - 8086 mov al,CPUV30 ; ZF not changed - V30 InitCPU2: jmp InitCPU8 ; set CPU type ; ------------- detect 80186 (PUSH SP saves SP after operation) InitCPU3: push sp ; push SP pop bx ; BX <- SP cmp bx,sp ; pushed state before operation? mov al,CPU186 ; CPU 80186 %if MAXCPUTYPE <= CPU186 jmp short InitCPU2 %endif jne short InitCPU2 ; SP not as before push - 80186 ; ------------- detect 80286 (FLAG bits 12..15 cannot be set to 1) pushf ; push flags mov ax,0f000h ; AX <- F000h, flags 12..15 set to 1 push ax ; F000h -> stack popf ; flags <- F000h pushf ; flags -> stack pop ax ; AX <- flags popf ; pop flags and ah,0f0h ; check bits 12..15 mov al,CPU286 ; AL <- CPU 80286 %if MAXCPUTYPE <= CPU286 jmp short InitCPU2 %endif jz short InitCPU2 ; bits 12..15 cannot be set - 80286 ; ------------- detect 80386 (cannot change AC flag bit, align error interrupt) CPU 386 mov edx,esp ; EDX <- push ESP and esp,~3 ; align ESP to DWORD pushfd ; push eflags cli ; disable interrupts pushfd ; eflags -> stack pop eax ; EAX <- eflags mov ecx,eax ; ECX <- store eflags xor eax,B18+B21 ; change bits AC and ID push eax ; push EAX popfd ; set eflags pushfd ; push eflags pop eax ; EAX <- eflags popfd ; pop eflags mov esp,edx ; ESP <- pop ESP xor eax,ecx ; EAX <- changed bits shr eax,8 ; AH <- B18 as B2, B21 as B5 CPU 8086 mov al,CPU386 ; AL <- CPU 80386 test ah,B2 ; bit AC (B18) changed? %if MAXCPUTYPE <= CPU386 jmp short InitCPU2 %endif jz short InitCPU2 ; bit AC cannot be changed - 80386 ; ------------- detect 80486 (cannot change ID flag bit, CPUID not supported) test ah,B5 ; bit ID (B21) changed? %if MAXCPUTYPE > CPU486 jnz short InitCPU4 ; bit ID can be changed - Pentium %endif jmp InitCPU7 ; bit ID cannot be changed - 80486 ; ------------- load Pentium info InitCPU4: %ifdef NOCPUID jmp short InitCPU69 %endif CPU 586 xor eax,eax ; EAX <- 0 xor ebx,ebx ; EBX <- 0 cpuid ; CPUID or ebx,ebx ; vendor 1 valid? jz short InitCPU7 ; vendor 1 invalid, use 80486 mov [CPUVendorIDText+2],ebx ; vendor 1 mov [CPUVendorIDText+6],edx ; vendor 2 mov [CPUVendorIDText+10],ecx ; vendor 3 mov byte [CPUVendorIDText],12 ; length of vendor text CPU 8086 or byte [CPUFlags],CPU_CPUID ; set CPUID flag ; ------------- find vendor mov bx,CPUVendor ; BX <- vendor code mov byte [bx],CPU_VEND_NUM-1 ; preset to last code mov di,CPUVendString2 ; DI <- last string InitCPU5: mov cx,6 ; CX <- text length mov si,CPUVendorIDText+2 ; SI <- compared vendor string push di ; push DI repe cmpsw ; compare one string pop di ; pop DI je short InitCPU6 ; vendor has been found sub di,byte 12 ; DI <- previous string dec byte [bx] ; decrease vendor code jnz short InitCPU5 ; test next vendor string ; ------------- get CPU information (here EAX = max. CPUID function) -> DX:AX InitCPU6: or ax,ax ; function 1 supported? jz short InitCPU69 ; function 1 not supported CPU 586 mov eax,1 ; EAX <- required function code cpuid ; CPUID with function 1 mov eax,edx ; EAX <- equipment shr edx,16 ; DX <- equipment HIGH CPU 8086 ; ------------- set equipment flags test al,B4 ; check TSC time stamp counter jz short InitCPU62 ; no TSC time stamp counter %ifndef NORDTSC or byte [CPUFlags],CPU_RDTSC ; set RDTSC flag %endif InitCPU62: test dl,B7 ; B23: check MMX instruction set jz short InitCPU63 ; no MMX instruction set %ifndef NOMMX or byte [CPUFlags],CPU_MMX ; set MMX flag %endif InitCPU63: test dh,B1 ; B25: check SSE instruction set jz short InitCPU64 ; no SSE instruction set %ifndef NOSSE or byte [CPUFlags],CPU_SSE ; set SSE flag %endif InitCPU64: test dh,B2 ; B26: check SSE2 instruction set jz short InitCPU69 ; no SSE2 instruction set %ifndef NOSSE2 or byte [CPUFlags],CPU_SSE2 ; set SSE2 flag %endif InitCPU69: mov al,CPUPent ; AL <- CPU Pentium test byte [CPUFlags],CPU_RDTSC ; support RDTSC ? jnz short InitCPU8 ; yes, otherwise it must be some 486 ; ------------- set CPU type InitCPU7: mov al,CPU486 ; AL <- CPU 80486 InitCPU8: mov [CPUType],al ; set CPU type ; ------------- prepare CPU name mov bh,0 ; BH <- 0 mov bl,[CPUType] ; BX <- CPU type shl bx,1 ; BX <- offset in address table mov ax,[CPUNameList+bx] ; AX <- CPU name mov [CPUName],ax ; set CPU name ; ------------- prepare vendor name mov bh,0 ; BH <- 0 mov bl,[CPUVendor] ; BX <- CPU vendor mov ax,CPUVendorIDText ; AX <- unknown vendor shl bx,1 ; BX <- offset in address table jz short InitCPU9 ; unknown vendor mov ax,[CPUVendorList+bx] ; AX <- CPU vendor name InitCPU9: mov [CPUVendorName],ax ; set CPU vendor name ; ------------- generate equipment list mov bx,CPUEquipList ; BX <- equipment list mov di,CPUEquipText+2 ; DI <- destination buffer mov ax,B0*256 + " " ; AH <- bit mask, AL <- space char InitCPU92: test [CPUFlags],ah ; check flag jz short InitCPU94 ; flag not set mov si,[bx] ; SI <- equipment name mov cx,[si] ; CX <- text length inc si inc si ; SI <- start of text rep movsb ; copy equipment text stosb ; store space character InitCPU94: inc bx ; BX += 1 inc bx ; BX <- shift equipment pointer shl ah,1 ; AH <- next bit flag jnc short InitCPU92 ; next flag sub di,CPUEquipText+2 ; DI <- text length jz short InitCPU95 ; no text dec di ; DI <- without end space InitCPU95: mov [CPUEquipText],di ; set text length ret ; ----------------------------------------------------------------------------- ; INIT: Detect CPU frequency ; ----------------------------------------------------------------------------- ; NOTES: It uses TIMER 2 counter. ; ----------------------------------------------------------------------------- ; ------------- disable interrupts InitCPUFreq: pushf ; push flags cli ; disable interrupts ; ------------- disable output to PC speaker and enable Timer 2 output mov al,0 ; AL <- disable flag call SetSpeakerGate ; disable speaker output inc ax ; AL <- 1, enable flag call SetTimer2Gate ; enable Timer 2 output ; ------------- set Timer 2 counter to 65536 (period 54.92542 ms) mov al,TIMER_MODE_CNT ; counter mode mov dl,2 ; DL <- counter 2 xor cx,cx ; CX <- initial value call TimerSetMode ; start Timer 2 in mode 0 ; ------------- check if time-stamp counter is supported IS_RDTSC ; is time-stamp counter supported? jz short InitCPUFreq5; time-stamp counter is not supported ; ------------- read time-stamp counter, start time -> EDI:ESI CPU 586 rdtsc ; read time-stamp counter (-> EDX:EAX) xchg eax,esi ; ESI <- start time LOW mov edi,edx ; EDI <- start time HIGH ; ------------- wait for Timer 2 output InitCPUFreq2: call GetTimer2Out ; get Timer 2 output jc short InitCPUFreq2 ; wait for end of Timer 2 counting ; ------------- read time-stamp counter, get interval -> EDX:EAX rdtsc ; read time-stamp counter (-> EDX:EAX) sub eax,esi ; EAX <- interval LOW sbb edx,edi ; EDX <- interval HIGH ; ------------- limit counter value -> EDX:EAX cmp edx,byte 35h ; maximal value jb short InitCPUFreq4 ; frequency is OK mov edx,35h ; EDX <- limit maximal value HIGH or eax,-1 ; EAX <- limit maximal value LOW ; ------------- calculate frequency -> DX:AX InitCPUFreq4: shld edx,eax,25 ; shift EDX:EAX << 25 (e.g. * 2^25) shl eax,25 ; shift EAX << 25 mov ecx,1842991270 ; timer divisor (=54.92542*(2^25)) div ecx ; calculate frequency mov edx,eax ; EDX <- frequency shr edx,16 ; DX <- frequency HIGH CPU 8086 jmp InitCPUFreq8 ; ------------- prepare registers InitCPUFreq5: mov di,8000h ; DI <- multiplier xor ax,ax ; AX <- 0 mov cx,1000 ; CX <- number of loops ; ------------- waste CPU time InitCPUFreq6: %rep 10 mul di ; one CPU delay %endrep loop InitCPUFreq6 ; next loop ; ------------- read counter (-> CX) mov dl,2 ; DL <- counter 2 call TimerGetVal ; get timer 2 value -> AX xchg ax,cx ; CX <- timer 2 value neg cx ; CX <- counter %ifdef DEB_CPUSPEEDCOEF mov [CPUSpeedCoef],cx ; Calculate coeficient: coef = freq(kHZ) * cx ; Pentium 4 @ 2.594 GHz: 73 -> 189362000 (B496F50) ; Pentium 3 @ 501.1 MHz: 120 -> 60132000 (3958AA0) ; Pentium Pro-S @ 199.8 MHz: 303 -> 60600000 (39CAEC0) ; Celeron 666 (Pentium Intel 668.202 MHz): 90 -> 60138180 (395A2C4) ; AMD K6/300 (Pentium AMD 300.688 MHz): 119 -> 35781872 (221FCF0) ; Compaq XE 486DX @ 66 MHz: 4781 -> 315546000 (12CED990) ; Amd386DX/25 MHz: 12512 -> 312800000 (12A4F300) ; Amd386DX/40 MHz: 7812 -> 312480000 (12A01100) ; aprox: 486 : 286 = 310374 : 25254 ; 286 -> 25000000 (17D7840) ; aprox: 486 : 86 = 310374 : 10000 ; 86 -> 10080000 (99CF00) %endif ; ------------- calculate frequency jcxz InitCPUFreq82 ; invalid counter xor bx,bx ; BX <- coefficient LOW ; Pentium (but Pentium uses RDTSC! ... so only for debug) ;mov ax,398h ; AX <- coefficient HIGH ;cmp byte [CPUType],CPUPent ;jae short InitCPUFreq72 ; 486 mov ax,12D0h ; AX <- coefficient HIGH cmp byte [CPUType],CPU486 jae short InitCPUFreq72 ; 386 mov ax,12A0h ; AX <- coefficient HIGH cmp byte [CPUType],CPU386 jae short InitCPUFreq72 ; 286 mov ax,180h ; AX <- coefficient HIGH cmp byte [CPUType],CPU286 jae short InitCPUFreq72 ; 186 or less mov ax,9ah ; AX <- coefficient HIGH InitCPUFreq72: xor dx,dx ; DX <- 0 div cx ; divide coefficient HIGH xchg ax,bx ; AX <- coefficient LOW, BX <- result HIGH div cx ; divide coefficient LOW mov dx,bx ; DX <- result HIGH ; ------------- limit minimal value InitCPUFreq8: or dx,dx ; check minimal value HIGH jnz short InitCPUFreq9 ; frequency is OK cmp ax,2000 ; check minimal value LOW ja short InitCPUFreq9 ; frequency is OK InitCPUFreq82: xor dx,dx ; DX <- limit minimal value HIGH mov ax,2000 ; AX <- limit minimal value LOW InitCPUFreq9: mov [CPUFreq],ax ; set CPU frequency LOW mov [CPUFreq+2],dx ; set CPU frequency HIGH ; ------------- disable Timer 2 output mov al,0 ; AL <- disable flag call SetTimer2Gate ; disable Timer 2 output ; ------------- set Timer 2 default speed (tone generator, aprox. 900 Hz) mov ch,5 ; CX <- aprox. 900 Hz mov dl,2 ; DL <- counter 2 call TimerSetVal ; set Timer 2 speed (speaker) ; ------------- re-enable interrupts popf ; pop flags ret CODE_SECTION ; ----------------------------------------------------------------------------- ; Get CPU info structure ; ----------------------------------------------------------------------------- ; OUTPUT: BX = pointer to CPU info structure CPUINFO ; ----------------------------------------------------------------------------- GetCPUInfo: mov bx,CPUInfo ret ; ----------------------------------------------------------------------------- ; Get CPU info TEXT ; ----------------------------------------------------------------------------- ; OUTPUT: BX = pointer to TEXT structure (NULL on memory error) ; CY = memory error (BX = NULL) ; NOTES: Returned text should be destroyed using TextFree. ; ----------------------------------------------------------------------------- ; ------------- push registers GetCPUInfoText: push ax ; push AX push dx ; push DX push si ; push SI ; ------------- CPU name mov si,CPUInfo ; SI <- CPU info structure CPUINFO mov bx,[si+CPUINFO_Name] ; BX <- CPU name call TextDup ; text duplicate xchg ax,bx ; BX <- output text or NULL jc short GetCPUInfoText9 ; memory error call TextAddSpc ; add space character ;-------------- vendor name mov ax,[si+CPUINFO_VendNam] ; AX <- pointer to CTEXT vendor call TextAddText ; add text call TextAddSpc ; add space character ; ------------- CPU frequency mov ax,[si+CPUINFO_Freq] mov dx,[si+CPUINFO_Freq+2] ; DX:AX <- frequency in kHz call TextAddDWord ; add frequency mov ax,CPUTextKHz ; AX <- text " kHz, " call TextAddText ; add text " kHz, " ; ------------- CPU equipment mov ax,[si+CPUINFO_Equip] call TextAddText ; add equipment text ; ------------- pop registers clc ; clear error flag GetCPUInfoText9:pop si ; pop SI pop dx ; pop DX pop ax ; pop AX ret ; ----------------------------------------------------------------------------- ; Constant data ; ----------------------------------------------------------------------------- CONST_SECTION ; ------------- CPU type name CPUNameUnknown: CTEXT "x86" CPUName8086: CTEXT "8086" CPUNameV30: CTEXT "NEC V30" CPUName186: CTEXT "80186" CPUName286: CTEXT "80286" CPUName386: CTEXT "80386" CPUName486: CTEXT "80486" CPUNamePent: CTEXT "Pentium" CPUNameList: dw CPUNameUnknown dw CPUName8086 dw CPUNameV30 dw CPUName186 dw CPUName286 dw CPUName386 dw CPUName486 dw CPUNamePent ; ------------- vendor strings CPUVendString: db 'GenuineIntel' ; Intel db 'AuthenticAMD' ; AMD db 'CyrixInstead' ; Cyrix db 'CentaurHauls' ; Centaur db 'NexGenDriven' ; NexGen db 'GenuineTMx86' ; Transmeta db 'RiseRiseRise' ; Rise db 'UMC UMC UMC ' ; UMC db 'SiS SiS SiS ' ; SiS db 'VIA VIA VIA ' ; VIA db 'Vortex86 SoC' ; Vortex CPUVendString2: db 'Geode by NSC' ; National Semiconductor ; ------------- vendor name CPUVendNo: CTEXT "" CPUVendIntel: CTEXT "Intel" CPUVendAMD: CTEXT "AMD" CPUVendCyrix: CTEXT "Cyrix" CPUVendCent: CTEXT "Centaur" CPUVendNexGen: CTEXT "NexGen" CPUVendTrans: CTEXT "Transmeta" CPUVendRise: CTEXT "Rise" CPUVendUMC: CTEXT "UMC" CPUVendSiS: CTEXT "SiS" CPUVendVIA: CTEXT "VIA" CPUVendVortex: CTEXT "Vortex" CPUVendNSC: CTEXT "National Semiconductor" CPUVendorList: dw CPUVendNo dw CPUVendIntel dw CPUVendAMD dw CPUVendCyrix dw CPUVendCent dw CPUVendNexGen dw CPUVendTrans dw CPUVendRise dw CPUVendUMC dw CPUVendSiS dw CPUVendVIA dw CPUVendVortex dw CPUVendNSC ; ------------- equipment names CPUEquipFPU: CTEXT "FPU" ; 3+1 chars CPUEquipCPUID: CTEXT "CPUID" ; 5+1 chars CPUEquipRDTSC: CTEXT "RDTSC" ; 5+1 chars CPUEquipMMX: CTEXT "MMX" ; 3+1 chars CPUEquipSSE: CTEXT "SSE" ; 3+1 chars CPUEquipSSE2: CTEXT "SSE2" ; 4+1 chars CPUEquipList: dw CPUEquipFPU dw CPUEquipCPUID dw CPUEquipRDTSC dw CPUEquipMMX dw CPUEquipSSE dw CPUEquipSSE2 CPUEQMAXLEN EQU 4+6+6+4+4+5 ; equipment list max length CPUTextKHz: CTEXT " kHz, " ; ----------------------------------------------------------------------------- ; Uninitialised data ; ----------------------------------------------------------------------------- DATA_SECTION ; ------------- CPU info structure CPUINFO align 4, resb 1 CPUInfo: CPUType: resb 1 ; 0: CPU type CPUVendor: resb 1 ; 1: CPU vendor CPUFlags: resw 1 ; 2: CPU flags CPUFreq: resd 1 ; 4: CPU frequency in kHz CPUName: resw 1 ; 8: pointer to CTEXT CPU name CPUVendorID: resw 1 ; 10: pointer to CTEXT vendor ident. CPUVendorName: resw 1 ; 12: pointer to CTEXT vendor name CPUEquipment: resw 1 ; 14: point. to CTEXT, equipment list align 2, resb 1 CPUVendorIDText:resb 2+12 ; CTEXT vendor ident. align 2, resb 1 CPUEquipText: resb 2+CPUEQMAXLEN ; CTEXT equipment list %ifdef DEB_CPUSPEEDCOEF CPUSpeedCoef: resw 1 %endif