;
; x86 Processor identification for rc5 distributed.net effort
; Written in a dark and stormy night (Jan 16, 1998) by
; Cyrus Patel <cyp@fb14.uni-mainz.de>
;
; $Id: x86ident.asm,v 1.5.2.5 2000/02/12 23:51:09 cyp Exp $
;
; correctly identifies almost every 386+ processor with the
; following exceptions:
; a) The code to use Cyrix Device ID registers for differentiate between a 
;    Cyrix 486, 6x86, etc is in place but disabled. Even so, it would not
;    detect correctly if CPUID is enabled _and_ reassigned.
; b) It may not correctly identify a Rise CPU if cpuid is disabled/reassigned
; c) It identifies an Intel 486 as an AMD 486 (chips are identical) and not
;    vice-versa because the Intel:0400 CPUID is actually a real CPUID value.
; d) It does not id the Transmeta Crusoe :)
;
; x86ident:  return u32
;            hiword = manufacturer ID (also for 386/486)
;                     (BX from cpuid for simplicity)
;                     GenuineIntel = 'eG' = 0x6547
;                     AuthenticAMD = 'uA' = 0x7541
;                     CyrixInstead = 'yC' = 0x7943
;                     NexGenDriven = 'eN' = 0x654E
;                     CentaurHauls = 'eC' = 0x6543
;                     UMC UMC UMC  = 'MU' = 0x4D55
;                     RiseRiseRise = 'iR' = 0x6952
;            loword = bits 12..15 for identification extensions
;                     bits 11...0 for family/model/stepping per CPUID
;                                 or 0x0300 for 386, 0x0400 for 486

%ifdef __OMF__   ; Watcom+OS/2 or Borland+Win32
[SECTION DATA CLASS=DATA USE32 PUBLIC ALIGN=16]
[SECTION TEXT CLASS=CODE USE32 PUBLIC ALIGN=16]
%define __DATASECT__ [SECTION DATA]
%define __CODESECT__ [SECTION TEXT]
%elifdef OS2
[SECTION DATA CLASS=DATA USE32 PUBLIC ALIGN=16]
[SECTION TEXT CLASS=CODE USE32 PUBLIC ALIGN=16]
%define __DATASECT__ [SECTION DATA]
%define __CODESECT__ [SECTION TEXT]
%elifdef __ELF__
[SECTION .data align=16]
[SECTION .text align=32]
%define __DATASECT__ [SECTION .data]
%define __CODESECT__ [SECTION .text]
%else
%define __DATASECT__ [SECTION .data]
%define __CODESECT__ [SECTION .text]
%endif

GLOBAL          x86ident,_x86ident

__DATASECT__
__savident      dd 0              ; avoid serialization caused by CPUID

__CODESECT__
_x86ident:
x86ident:       mov     eax,[__savident]
                or      eax, eax
                jz      _ge386
                ret

_ge386:         ; an interrupt would change the AC bit, so loop 'n'
                ; times, incrementing a state counter for each state
                push    ebx             ; save ebx from damage
                pushfd                  ; save flags from damage
                xor     ebx, ebx        ; bl=is386 count, bh=not386 count
                mov     dl, 31          ; the number of times we check
                pushfd                  ; copy EFLAGS
                pop     ecx             ; ... into ecx
_386next:       mov     eax, ecx        ; copy original EFLAGS
                xor     eax, 40000h     ; toggle AC bit (1<<18) in eflags
                push    eax             ; copy modified eflags
                popfd                   ; ... to EFLAGS
                pushfd                  ; copy (possible modified) EFLAGS
                pop     eax             ; ... back to eax
                sub     eax, ecx        ; will be 386 if no change
                cmp     eax, 1          ; set carry if 386
                sbb     eax, eax        ; make 0xffffffff if carry set
                dec     dl              ; decrement loop count
                jz      _386eval        ; break if done all
                inc     bl              ; increment the is386 count
                or      al,al           ; clear zero if is386
                jnz     _386next        ; go loop if hit
                dec     bl              ; undo last change
                inc     bh              ; increment the not386 count
                jmp     short _386next  ; go loop for other hit
_386eval:       cmp     bl, bh          ; is386 count > not386 count
                popfd                   ; restore saved flags
                pop     ebx             ; restore ebx
                jbe     _ge486          ; try 486 if not

                ;we're either a 386 or a  nextgen 386 class CPU.

                mov     ax, 5555h       ; cx+nexgen check (only if <PII)
                cmp     ax, ax          ; set ZF=1
                mov     cx, 2           ; odd by even division
                div     cx              ; i386 clears ZF, Nx586 leaves it set
                jz      _nx586          ; continue if ZF is still set
                mov     eax, 65470300h  ; 'eG' as in "GenuineIntel + i386
                jmp     _end

_nx586:         ;Nx chips don't have EFLAGS.id, but support the cpuid opcode.
                ;(.ID may POSSIBLY be enabled with mov si,B00Eh;mov ax,4c03h;
                ;db 0Fh,55h). We could just blindly do a cpuid but we don't
                ;need feature flags, so we just return ('eN'<<16)+0500h
                ;the nexGen Nx586 (P5 class [60/66]) doesn't have an FPU,
                ;the nexGen Nx587 (P54C class [75+]) does.
                mov     eax, 654E0500h  ; family=5,model=0,stepping=0
                jmp     _end

_ge486:         pushfd                  ; copy EFLAGS
                pop     ecx             ; ... into ecx
                mov     eax, ecx        ; copy original flags
                xor     eax, 200000h    ; try to toggle ID bit
                push    eax             ; copy modified eflags
                popfd                   ; ... to EFLAGS
                pushfd                  ; push back flags
                pop     eax             ; ... into eax
                xor     eax, ecx        ; must be 486 if no change
                jz      _eq486          ;
                jmp     _ge586

                ; -----------------------------------------------------

_eq486:         fninit                  ; try to init coprocessor
                mov     ax, 5a5ah       ; rigged status word
                fnstsw  ax              ; try to store
                or      ax, ax          ; have an fpu?
                jnz     _in486sx        ; if not, must be intel

                push    eax             ; make buffer space
                finit                   ; initialize fpu
                fldpi                   ; loads pi
                f2xm1                   ; 2^pi - 1 => "undefined" number
                fstp    dword [esp]     ; store in slot
                pop     ecx             ; get the result
                rol     ecx, 16         ; switch hi/lo words
                cmp     cx,  3FC9h      ; cyrix=>3FC9xxxxh
                jnz     _in486          ; its an intel/AMD CPU if not

                ; we know we have a Cyrix co-processor, but that 
                ; could be a cyrix NPU for a non-cyrix CPU.
                ; find out whether the main processor is a cyrix too.
                ;
                ; Actually, the following 5/2 test is not valid for 
                ; 'late' Cyrix chips (iirc M2), where the flags
                ; behave just as their Intel/AMD pendants do.
                ; However, I've left it in for posterity.

                xor     ah, ah          ; for clearing flags
                sahf                    ; scrub lower 8 bits (exc. bit 1)
                lahf                    ; reload flags
                mov     ch, ah          ; save copy
                mov     ax, 5           ; 5/2 constant
                mov     cl, 2           ; divisor
                div     cl              ; won't change flags on cyrix
                lahf                    ; load flags
                cmp     ah, ch          ; no changes?
                jz      _cx486          ; cyrix chip if no change
                
                ; amd/intel appear to have the result as 3E46FEACh
                ; (pi as dword) but I'd rather not trust it since Intel
                ; docs specifically state that 2^pi-1 is undefined.

_in486:         ; we return AMD here since Intel:0400 is a valid CPUID for
                ; the "Intel 486DX25 or 33" while AMD:0400 is not.
                mov     eax, 75410400h  ; return *AMD* 486 CPU
                jmp     _end            ;

_in486sx:       mov     eax, 65470420h  ; Intel 486SX (no fpu)
                jmp     _end

                ; -----------------------------------------------------

_cx486:         mov     eax, 79430400h  ; Cyrix 486
                jmp     _end            ; hop over Cyrix stuff

                ; use Cyrix 'Device ID registers' to identifiy
                ; Cyrix documentation states that the in/outs here don't
                ; generate external signals on the bus (ie, the wouldn't 
                ; generate an exception), but that is apparently incorrect. 
                ; It does work for Win9x though.
                
                ; #define getCx86(reg)  ({ outb((reg), 0x22); inb(0x23); })
                ; #define setCx86(reg, data) do { \ outb((reg), 0x22); \ outb((data), 0x23); \ } while (0)

                ;Cyrix CPU configuration register indexes
                CX86_CCR0  equ 0c0h
                CX86_CCR1  equ 0c1h
                CX86_CCR2  equ 0c2h
                CX86_CCR3  equ 0c3h
                CX86_CCR4  equ 0e8h
                CX86_CCR5  equ 0e9h
                CX86_CCR6  equ 0eah
                CX86_DIR0  equ 0feh
                CX86_DIR1  equ 0ffh
                CX86_ARR_BASE  equ 0c4h
                CX86_RCR_BASE  equ 0dch

                mov     dx, 22h        ; command port at 0x22
                mov     al, CX86_CCR3  ; the value we want
                out     dx, al         ; access command
                inc     dx             ; read/write from/to 0x23
                in      al, dx         ; read CCR3 value
                mov     ah, al         ; save ccr3

                mov     dx, 22h        ; command port at 0x22
                mov     al, CX86_CCR3  ; the value we want to set
                out     dx, al         ; access command
                mov     al, ah         ; our saved ccr3
                xor     al, 80h        ; modify value (CCR3_MAPEN3)
                inc     dx             ; read/write from/to 0x23
                out     dx, al         ; new value

                jmp     _cxbussettle1
                _cxbussettle1:

                mov     dx, 22h        ; command port at 0x22
                mov     al, CX86_CCR3  ; the value we want to get
                out     dx, al         ; the value we want
                inc     dx             ; read/write from/to 0x23
                in      al, dx         ; read CCR3 value

                cmp     al,ah          ; has the ccr3 changed?
                mov     al,ah          ; copy back old ccr3 value
                jnz     _cx586         ; newer Cyrix

                mov     dx, 22h        ; command port at 0x22
                mov     al, CX86_CCR2  ; the value we want
                out     dx, al         ; access command
                inc     dx             ; read/write from/to 0x23
                in      al, dx         ; read CCR2 value
                mov     ah, al         ; save ccr2

                mov     dx, 22h        ; command port at 0x22
                mov     al, CX86_CCR2  ; the value we want to set
                out     dx, al         ; access command
                mov     al, ah         ; our saved ccr2 value
                xor     al, 40h        ; modify for change test (CCR2_LOCK_NW)
                inc     dx             ; read/write from/to 0x23
                out     dx, al         ; new value

                jmp     _cxbussettle2
                _cxbussettle2:

                mov     dx, 22h        ; command port at 0x22
                mov     al, CX86_CCR2  ; the value we want
                out     dx, al         ; access command
                inc     dx             ; read/write from/to 0x23
                in      al, dx         ; read CCR2 value

                mov     cx,0000h       ; DIR0 = 0x00 = Cx486SLC/DLC
                cmp     al,ah          ; ccr2 unchanged?
                jz      _cxeval        ; not changed, ie Cx486SLC/DLC

                mov     dx, 22h        ; command port at 0x22
                mov     al, CX86_CCR2  ; the value we want to set
                out     dx, al         ; access command
                inc     dx             ; read/write from/to 0x23
                mov     al, ah         ; our saved ccr2
                out     dx, al         ; restore value

                mov     cx,0010h       ; DIR0 = 0x10 = Cx486S A step
                jmp     short _cxeval

_cx586:         mov     dx, 22h        ; command port at 0x22
                mov     al, CX86_CCR3  ; the value we want to set
                out     dx, al         ; access command
                inc     dx             ; read/write from/to 0x23
                mov     al,ah          ; our saved ccr3
                out     dx, al         ; restore value

                mov     dx, 22h        ; command port at 0x22
                mov     al, CX86_DIR0  ; the value we want
                out     dx, al         ; access command
                inc     dx             ; read/write from/to 0x23
                in      al, dx         ; read DIR0 value
                mov     cl, al         ; save DIR0

                mov     dx, 22h        ; command port at 0x22
                mov     al, CX86_DIR1  ; the value we want
                out     dx, al         ; access command
                inc     dx             ; read/write from/to 0x23
                in      al, dx         ; read DIR1 value
                mov     ch, al         ; save DIR1

_cxeval:        ;DIR0 = cl, DIR1 = ch
                mov     eax, 79430000h ; cyrix something.
                mov     al, cl         ; set emulated model/stepping = DIR0

                mov     dl, cl         ; copy DIR0
                shr     dl, 4          ; dir0_msn get "family"

                cmp     dl, 2          ; < 5x86?
                jb      _cx4x86        ; proceed if so
                jz      _cx5x86        ; Cx5x86
                cmp     dl, 3          ; Cx6x86(M1)?
                jz      _cx6x86        ; proceed if so
                cmp     dl, 4          ; Media GX/GXm?
                jz      _cx4xgx        ; proceed if so
                cmp     dl, 0fh        ; TI, Overdrive and other.
                jz      _ti4x86        ; proceed if so
                cmp     dl, 5          ; Cx6x86MX(M2)?
                jnz     _cx4x86        ; no? something wrong. assume 486

                or      ah, 6          ; 686 class CPU
                and     al, 0fh        ; 05x -> 00x
                jmp     _end           ; model 0=6x86MX
_cx6x86:        or      ah, 5          ; 586 class CPU
                and     al, 2fh        ; 03x -> 02x
                jmp     _end           ; model 2=6x86
_cx5x86:       ;mov     dh, cl         ; freebsd says (but I don't believe)
               ;and     dh, 0fh        ; ... if the dir0_lsn ...
               ;cmp     dh, 8          ; ... is < 8 ...
               ;jb      _cx6x86        ; ... then this is an M1
                or      ah, 4          ; 486 class CPU
                add     al, 70h        ; 02x -> 09x
                jmp     _end           ; model 9=5x86
_cx4xgx:        mov     dh, ch         ; freebsd says ...
                and     dh, 0f0h       ; ... if the dir1_msn (stepping)
                cmp     dh, 030h       ; ... == 3, then this is a GXm (MMX!)
                jnz     _cx4x86        ; this is a MediaGX if not
                or      ah, 5          ; 586 class CPU if GXm
                jmp     _end           ; GXm = class 5, model 4
_ti4x86:        ;DIR0=0xFF=486SLC/DLC;0xFE=TI486SXL,0xFD=Overdrive
                and     al,0fh         ; convert to Cx486
_cx4x86:        or      ah, 4          ; 486 class CPU
                jmp     _end           ; model 0=Cx486SLC/DLC/SRx/DRx,
                                       ; model 1=Cx486S/DX/DX2/DX4, 4=MediaGX

                ; -----------------------------------------------------

_ge586:         push    ebx             ; cpuid trashes ebx
                xor     eax, eax        ; cpuid function zero
                cpuid                   ; => eax=maxlevels,ebx:ecx:edx=vendor
                mov     dx, bx          ; this is now our mfg id
                pop     ebx             ; restore ebx

                mov     ecx, eax        ; scratch pad
                xor     cl, cl          ; clear stepping and model
                cmp     ecx, 00000500h  ; 5xxh cpuid maxlevel? I think not!
                mov     ecx, 65470000h  ; P5 step A screwball (featuref=1BFh)
                mov     cx, ax          ; family/model/stepping/features
                xchg    ecx, eax        ; move to return reg
                jz      near _end       ; go exit if this is that wierdo
                mov     eax, ecx        ; restore the max supported level

                mov     cx, dx          ; move maker id
                xchg    ecx, eax        ; save eax, set mfg id
                shl     eax, 16         ; move it up
                mov     ax, 400h        ; assume 486
                or      ecx, ecx        ; only supports CPUID fxn 0?
                jz      near _end       ; exit if so

                push    dx              ; save maker code
                mov     eax, 1          ; family/model/stepping/features
                push    ebx             ; cpuid trashes ebx
                cpuid                   ; => ax=type/family/model/stepping
                pop     ebx             ; restore ebx
                and     ax,0fffh        ; drop the type flags
                mov     cx,ax           ; save family/model/stepping bits
                pop     ax              ; restore maker code
                shl     eax, 16         ; move maker code to high word
                mov     ax,cx           ; put back family/model/stepping bits

                ;On a PII and above we take extra steps to differentiate
                ;between a Celeron/Covington, PII, Celeron-A/Mendocino, Xeon.
                ;we need to do this since cache size is important
                ;for some cores.

                mov     ecx,eax         ; copy our combined id
                rol     ecx,16          ; get vendor id in cx
                cmp     cx, 6547h       ; 'Ge'nuineIntel?
                jnz     _end            ; exit it not
                mov     cx, ax          ; get family/model/stepping bits
                and     cx,0ff0h        ; mask only family/model
                shr     cx,4            ; move into cl
                cmp     cl,63h          ; less than PII?
                jb      _end            ; exit if so

                push    eax             ; save our old result
                push    ebx             ; save ebx from damage
                xor     eax,eax         ; do cpuid level 0 again
                cpuid                   ; => eax=max cpuid levels supported
                cmp     eax,2           ; do we have at least 2 cpuid levels?
                pop     ebx             ; restore ebx
                pop     eax             ; restore our old result
                jb      _end            ; go exit if not

                push    eax             ; save our old result
                push    ebx             ; save ebx from damage
                mov     eax,2           ; do cpuid level 2
                cpuid                   ; => dl=cache info
                pop     ebx             ; restore ebx
                pop     eax             ; restore our old result
                and     dx,0ffh         ; we only want the cache flags

                and     ah,0fh          ; assume no cache
                mov     ch,dl           ; copy for mask
                and     ch,0fh          ; mask off the size bit we want
                shl     ch,4            ; move to high nibble
                cmp     dl,040h         ; 0x40=no cache,1=128,2=256,3=512,...
                jb      _end            ; ... 4=1024,5=2048
                or      ah, ch          ; assume in range
                cmp     dl,045h         ; in range?
                jbe     _end            ; go quit if so
                and     ah,0fh          ; go back to assuming no cache
                cmp     dl,080h         ; same as 0x40 but for coppermine/PIII
                jb      _end            ; below range?
                or      ah, ch          ; assume in range
                cmp     dl,085h         ; in range?
                jbe     _end            ; go exit if so
                and     ah,0fh          ; go back to assuming no cache

_end:           mov     [__savident],eax; save it for next time
                ret
