title  "Processor type and stepping detection"
;++
;
; Copyright (c) 1989  Microsoft Corporation
;
; Module Name:
;
;    cpu.asm
;
; Abstract:
;
;    This module implements the assembley code necessary to determine
;    cpu type and stepping information.
;
; Author:
;
;    Shie-Lin Tzong (shielint) 28-Oct-1991.
;        Some of the code is extracted from Cruiser (mainly,
;        the code to determine 386 stepping.)
;
; Environment:
;
;    80x86 Real Mode.
;
; Revision History:
;
;
;--

        .xlist
include cpu.inc
        .list

;
; constant for i386 32-bit multiplication test
;

MULTIPLIER            equ     00000081h
MULTIPLICAND          equ     0417a000h
RESULT_HIGH           equ     00000002h
RESULT_LOW            equ     0fe7a000h

;
; Constants for Floating Point test
;

REALLONG_LOW          equ     00000000
REALLONG_HIGH         equ     3FE00000h
PSEUDO_DENORMAL_LOW   equ     00000000h
PSEUDO_DENORMAL_MID   equ     80000000h
PSEUDO_DENORMAL_HIGH  equ     0000h

.386p

_TEXT   SEGMENT PARA USE16 PUBLIC 'CODE'
        ASSUME  CS: _TEXT, DS:NOTHING, SS:NOTHING


;++
;
; USHORT
; HwGetProcessorType (
;    VOID
;    )
;
; Routine Description:
;
;    This function determines type of processor (80486, 80386, 80286,
;    and even 8086/8088).  it relies on Intel-approved code that takes
;    advantage of the documented behavior of the high nibble of the flag
;    word in the REAL MODE of the various processors.
;
;    For completeness, the code also checks for 8088/8086.  But, it won't
;    work.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    (ax) = x86h or 0 if unrecongnized processor.
;
;--

.8086

        public  _HwGetProcessorType
_HwGetProcessorType      proc    near

        pushf                           ; save entry flags

;
;    The MSB (bit 15) is always a one on the 8086 and 8088 and a zero on
;    the 286, 386 and 486.
;

        pushf
        pop     ax
        and     ax, NOT 08000h          ; clear bit 15 of flags
        push    ax
        popf                            ; try to put that in the flags
        pushf
        pop     ax                      ; look at what really went into flags

        test    ax,08000h               ; Was high bit set ?
        jnz     short x_86              ; if nz, still set, goto x_86

;
;    Bit 14 (NT flag) and bits 13/12 (IOPL bit field) are always zero on
;    the 286, but can be set on the 386 and 486.
;

        or      ax,07000h               ; Try to set the NT/IOPL bits
        push    ax
        popf                            ; Put in to the flags
        sti                             ; (for VDMM/IOPL0)
        pushf
        pop     ax                      ; look at actual flags
        test    ax,07000h               ; Any high bits set ?
        jz      short x_286             ; if z, no, goto x_286

.386p

;
;    The Alignment Check bit in flag can be set on 486 and is always zero
;    on 386.
;

        mov     eax,cr0                 ; test for 486 processor
        push    eax                     ; save CR0 value
        and     eax,not CR0_AM          ; disable alignment check
        mov     cr0,eax
        db      ADDRESS_OVERRIDE
        pushfd                          ; save original EFLAGS
        db      ADDRESS_OVERRIDE
        pushfd                          ; try to set alignment check
        or      dword ptr [esp],EFLAGS_AC ;           bit in EFLAGS
        db      ADDRESS_OVERRIDE
        popfd
        db      ADDRESS_OVERRIDE
        pushfd                          ; copy new flags into ECX
        pop     ecx                     ; [ecx] = new flags word
        db      ADDRESS_OVERRIDE
        popfd                           ; restore original EFLAGS
        pop     eax                     ; restore original CR0 value
        mov     cr0,eax
        and     ecx, EFLAGS_AC          ; did AC bit get set?
        jz      short x_386             ; if z, no, goto x_386

        mov     eax, 4h                 ; if nz, we have a 486 processor

.286p

        jmp     short hpt99

x_286:
        mov     ax, 2h                  ; Return 286 processor type.
        jmp     short hpt99

x_86:
        mov     ax, 0h                  ; Return 86h for 8088/8086 CPU type.
        jmp     short hpt99

x_386:
        mov     ax, 3h                  ; Return 386 processor type.
hpt99:
        popf                            ; restore flags
        ret

_HwGetProcessorType      endp

IFDEF ALLOW_386
.386p

;++
;
; USHORT
; HwGetCpuStepping (
;    UHSORT CpuType
;    )
;
; Routine Description:
;
;    This function determines cpu stepping for the specified CPU type.
;
;    Currently, this routine only determine stepping for 386 and 486.
;
; Arguments:
;
;    CpuType - The Cpu type which its stepping information will be returned.
;              The input value MUST be either 386 or 486.
;
; Return Value:
;
;    [ax] - Cpu stepping.  For example, [ax] = D0h for D0 stepping.
;
;--

if 0
HgcsCpuType     equ     [esp + 2]

        public  _HwGetCpuStepping
_HwGetCpuStepping        proc

        mov     ax, HgcsCpuType         ; [ax] = CpuType
        cmp     ax, 3h                  ; Is cpu = 386?
        jz      short Hgcs00            ; if z, yes, go Hgcs00

        call    Get486Stepping          ; else, check for 486 stepping
        jmp     short Hgcs90            ; [ax] = Stepping information

Hgcs00:
        call    _Get386Stepping         ; [ax] = Stepping information

Hgcs90:
        ret

_HwGetCpuStepping        endp
endif

;++
;
; USHORT
; _Get386Stepping (
;    VOID
;    )
;
; Routine Description:
;
;    This function determines cpu stepping for i386 CPU stepping.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    [ax] - Cpu stepping.  For example, [ax] = D0h for D0 stepping.
;    [ax] = 0 means bad CPU and stepping is not important.
;
;--

        public  _Get386Stepping
_Get386Stepping  proc

        call    MultiplyTest            ; Perform mutiplication test
        jnc     short G3s00             ; if nc, muttest is ok
        mov     ax, 0
        ret
G3s00:
        call    Check386B0              ; Check for B0 stepping
        jnc     short G3s05             ; if nc, it's B1/later
        mov     ax, 0B0h                ; It is B0/earlier stepping
        ret

G3s05:
        call    Check386D1              ; Check for D1 stepping
        jc      short G3s10             ; if c, it is NOT D1
        mov     ax, 0D1h                ; It is D1/later stepping
        ret

G3s10:
        mov     ax, 0B1h                ; assume it is B1 stepping
        ret

_Get386Stepping  endp

if 0
;++
;
; USHORT
; Get486Stepping (
;    VOID
;    )
;
; Routine Description:
;
;    This function determines cpu stepping for i486 CPU type.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    [ax] - Cpu stepping.  For example, [ax] = D0h for D0 stepping.
;
;--

        public  Get486Stepping
Get486Stepping          proc

        call    Check486AStepping       ; Check for A stepping
        jnc     short G4s00             ; if nc, it is NOT A stepping

        mov     ax, 0A0h                ; set to A stepping
        ret

G4s00:  call    Check486BStepping       ; Check for B stepping
        jnc     short G4s10             ; if nc, it is NOT a B stepping

        mov     ax, 0B0h                ; set to B stepping
        ret

;
; Before we test for 486 C/D step, we need to make sure NPX is present.
; Because the test uses FP instruction to do the detection.
;

G4s10:  call    _IsNpxPresent           ; Check if cpu has coprocessor support?
        cmp     ax, 0
        jz      short G4s15             ; it is actually 486sx

        call    Check486CStepping       ; Check for C stepping
        jnc     short G4s20             ; if nc, it is NOT a C stepping
G4s15:
        mov     ax, 0C0h                ; set to C stepping
        ret

G4s20:  mov     ax, 0D0h                ; Set to D stepping
        ret

Get486Stepping          endp

;++
;
; BOOLEAN
; Check486AStepping (
;    VOID
;    )
;
; Routine Description:
;
;    This routine checks for 486 A Stepping.
;
;    It takes advantage of the fact that on the A-step of the i486
;    processor, the ET bit in CR0 could be set or cleared by software,
;    but was not used by the hardware.  On B or C -step, ET bit in CR0
;    is now hardwired to a "1" to force usage of the 386 math coprocessor
;    protocol.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    Carry Flag clear if B or later stepping.
;    Carry Flag set if A or earlier stepping.
;
;--
        public  Check486AStepping
Check486AStepping       proc    near
.386p
        mov     eax, cr0                ; reset ET bit in cr0
        and     eax, NOT CR0_ET
        mov     cr0, eax

        mov     eax, cr0                ; get cr0 back
        test    eax, CR0_ET             ; if ET bit still set?
        jnz     short cas10             ; if nz, yes, still set, it's NOT A step
        stc
        ret

cas10:  clc
        ret

        ret
Check486AStepping       endp

;++
;
; BOOLEAN
; Check486BStepping (
;    VOID
;    )
;
; Routine Description:
;
;    This routine checks for 486 B Stepping.
;
;    On the i486 processor, the "mov to/from DR4/5" instructions were
;    aliased to "mov to/from DR6/7" instructions.  However, the i486
;    B or earlier steps generate an Invalid opcode exception when DR4/5
;    are used with "mov to/from special register" instruction.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    Carry Flag clear if C or later stepping.
;    Carry Flag set if B stepping.
;
;--
        public  Check486BStepping
Check486BStepping       proc

        push    ds
        push    bx

        xor     ax,ax
        mov     ds,ax                   ; (DS) = 0 (real mode IDT)
        mov     bx,6*4
        push    dword ptr [bx]          ; save old int 6 vector

        mov     word ptr [bx].VectorOffset,offset Temporary486Int6
        mov     [bx].VectorSegment,cs         ; set vector to new int 6 handler

c4bs50: db      0fh, 21h, 0e0h            ; mov eax, DR4
        nop
        nop
        nop
        nop
        nop
        clc                             ; it is C step
        jmp     short c4bs70
c4bs60: stc                             ; it's B step
c4bs70: pop     dword ptr [bx]          ; restore old int 6 vector

        pop     bx
        pop     ds
        ret

        ret

Check486BStepping       endp

;++
;
; BOOLEAN
; Temporary486Int6 (
;    VOID
;    )
;
; Routine Description:
;
;    Temporary int 6 handler - assumes the cause of the exception was the
;    attempted execution of an mov to/from DR4/5 instruction.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    none.
;
;--

Temporary486Int6        proc

        mov     word ptr [esp].IretIp,offset c4bs60 ; set IP to stc instruction
        iret

Temporary486Int6        endp

;++
;
; BOOLEAN
; Check486CStepping (
;    VOID
;    )
;
; Routine Description:
;
;    This routine checks for 486 C Stepping.
;
;    This routine takes advantage of the fact that FSCALE produces
;    wrong result with Denormal or Pseudo-denormal operand on 486
;    C and earlier steps.
;
;    If the value contained in ST(1), second location in the floating
;    point stack, is between 1 and 11, and the value in ST, top of the
;    floating point stack, is either a pseudo-denormal number or a
;    denormal number with the underflow exception unmasked, the FSCALE
;    instruction produces an incorrect result.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    Carry Flag clear if D or later stepping.
;    Carry Flag set if C stepping.
;
;--

FpControl       equ     [ebp - 2]
RealLongSt1     equ     [ebp - 10]
PseudoDenormal  equ     [ebp - 20]
FscaleResult    equ     [ebp - 30]

        public  Check486CStepping
Check486CStepping       proc

        push    ebp
        mov     ebp, esp
        sub     esp, 30                 ; Allocate space for temp real variables

;
; Initialize the local FP variables to predefined values.
; RealLongSt1 = 1.0 * (2 ** -1) = 0.5 in normalized double precision FP form
; PseudoDenormal =  a unsupported format by IEEE.
;                   Sign bit = 0
;                   Exponent = 000000000000000B
;                   Significand = 100000...0B
; FscaleResult = The result of FSCALE instruction.  Depending on 486 step,
;                the value will be different:
;                Under C and earlier steps, 486 returns the original value
;                in ST as the result.  The correct returned value should be
;                original significand and an exponent of 0...01.
;

        mov     dword ptr RealLongSt1, REALLONG_LOW
        mov     dword ptr RealLongSt1 + 4, REALLONG_HIGH
        mov     dword ptr PseudoDenormal, PSEUDO_DENORMAL_LOW
        mov     dword ptr PseudoDenormal + 4, PSEUDO_DENORMAL_MID
        mov     word ptr PseudoDenormal + 8, PSEUDO_DENORMAL_HIGH

.387
        fnstcw  FpControl               ; Get FP control word
        or      word ptr FpControl, 0FFh ; Mask all the FP exceptions
        fldcw   FpControl               ; Set FP control

        fld     qword ptr RealLongSt1   ; 0 < ST(1) = RealLongSt1 < 1
        fld     tbyte ptr PseudoDenormal; Denormalized operand. Note, i486
                                        ; won't report denormal exception
                                        ; on 'FLD' instruction.
                                        ; ST(0) = Extended Denormalized operand
        fscale                          ; try to trigger 486Cx errata
        fstp    tbyte ptr FscaleResult  ; Store ST(0) in FscaleResult
        cmp     word ptr FscaleResult + 8, PSEUDO_DENORMAL_HIGH
                                        ; Is Exponent changed?
        jz      short c4ds00            ; if z, no, it is C step
        clc
        jmp     short c4ds10
c4ds00: stc
c4ds10: mov     esp, ebp
        pop     ebp
        ret

Check486CStepping       endp
endif

;++
;
; BOOLEAN
; Check386B0 (
;    VOID
;    )
;
; Routine Description:
;
;    This routine checks for 386 B0 or earlier stepping.
;
;    It takes advantage of the fact that the bit INSERT and
;    EXTRACT instructions that existed in B0 and earlier versions of the
;    386 were removed in the B1 stepping.  When executed on the B1, INSERT
;    and EXTRACT cause an int 6 (invalid opcode) exception.  This routine
;    can therefore discriminate between B1/later 386s and B0/earlier 386s.
;    It is intended to be used in sequence with other checks to determine
;    processor stepping by exercising specific bugs found in specific
;    steppings of the 386.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    Carry Flag clear if B1 or later stepping
;    Carry Flag set if B0 or prior
;
;--


    ASSUME ds:nothing, es:nothing, fs:nothing, gs:nothing, ss:nothing

Check386B0      proc

        push    ds
        push    bx

        xor     ax,ax
        mov     ds,ax                   ; (DS) = 0 (real mode IDT)
        mov     bx,6*4
        push    dword ptr [bx]          ; save old int 6 vector

        mov     word ptr [bx].VectorOffset,offset TemporaryInt6
        mov     [bx].VectorSegment,cs         ; set vector to new int 6 handler

;
; Attempt execution of Extract Bit String instruction.  Execution on
; B0 or earlier with length (CL) = 0 will return 0 into the destination
; (CX in this case).  Execution on B1 or later will fail either due to
; taking the invalid opcode trap, or if the opcode is valid, we don't
; expect CX will be zeroed by any new instruction supported by newer
; steppings.  The dummy int 6 handler will clears the Carry Flag and
; returns execution to the appropriate label.  If the instruction
; actually executes, CX will *probably* remain unchanged in any new
; stepping that uses the opcode for something else.  The nops are meant
; to handle newer steppings with an unknown instruction length.
;

        xor     ax,ax
        mov     dx,ax
        mov     cx,0ff00h               ; Extract length (CL) == 0, (CX) != 0

b1c50:  db      0fh, 0a6h, 0cah         ; xbts cx,dx,ax,cl
        nop
        nop
        nop
        nop
        nop
        stc                             ; assume B0
        jcxz    short b1c70             ; jmp if B0
b1c60:  clc
b1c70:  pop     dword ptr [bx]          ; restore old int 6 vector

        pop     bx
        pop     ds
        ret

Check386B0      endp

;++
;
; BOOLEAN
; TemporaryInt6 (
;    VOID
;    )
;
; Routine Description:
;
;    Temporary int 6 handler - assumes the cause of the exception was the
;    attempted execution of an XTBS instruction.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    none.
;
;--

TemporaryInt6    proc

        mov     word ptr [esp].IretIp,offset b1c60 ; set IP to clc instruction
        iret

TemporaryInt6   endp

;++
;
; BOOLEAN
; Check386D1 (
;    VOID
;    )
;
; Routine Description:
;
;    This routine checks for 386 D1 Stepping.
;
;    It takes advantage of the fact that on pre-D1 386, if a REPeated
;    MOVS instruction is executed when single-stepping is enabled,
;    a single step trap is taken every TWO moves steps, but should
;    occuu each move step.
;
;    NOTE: This routine cannot distinguish between a D0 stepping and a D1
;    stepping.  If a need arises to make this distinction, this routine
;    will need modification.  D0 steppings will be recognized as D1.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    Carry Flag clear if D1 or later stepping
;    Carry Flag set if B1 or prior
;
;--

    assume ds:nothing, es:nothing, fs:nothing, gs:nothing, ss:nothing

Check386D1      proc

        push    ds
        push    bx

        xor     ax,ax
        mov     ds,ax                   ; (DS) = 0 (real mode IDT)
        mov     bx,1*4
        push    dword ptr [bx]          ; save old int 1 vector

        mov     word ptr [bx].VectorOffset,offset TemporaryInt1
        mov     word ptr [bx].VectorSegment,cs ; set vector to new int 1 handler

;
; Attempt execution of rep movsb instruction with the Trace Flag set.
; Execution on B1 or earlier with length (CX) > 1 will trace over two
; iterations before accepting the trace trap.  Execution on D1 or later
; will accept the trace trap after a single iteration.  The dummy int 1
; handler will return execution to the instruction following the movsb
; instruction.  Examination of (CX) will reveal the stepping.
;

        sub     sp,4                    ; make room for target of movsb
        xor     si,si                   ; (ds:si) = 0:0
        push    ss                      ; (es:di) = ss:sp-4
        pop     es
        mov     di,sp
        mov     cx,2                    ; 2 iterations
        pushf
        or      word ptr [esp], EFLAGS_TF
        popf                            ; cause a single step trap
        rep movsb

d1c60:  add     sp,4                    ; clean off stack
        pop     dword ptr [bx]          ; restore old int 1 vector
        stc                             ; assume B1
        jcxz    short d1cx              ; jmp if <= B1
        clc                             ; else clear carry to indicate >= D1
d1cx:
        pop     bx
        pop     ds
        ret

Check386D1      endp

;++
;
; BOOLEAN
; TemporaryInt1 (
;    VOID
;    )
;
; Routine Description:
;
;    Temporary int 1 handler - assumes the cause of the exception was
;    trace trap at the above rep movs instruction.
;
; Arguments:
;
;    (esp)->eip of trapped instruction
;           cs  of trapped instruction
;           eflags of trapped instruction
;
;--

TemporaryInt1   proc

        and     word ptr [esp].IretFlags,not EFLAGS_TF ; clear caller's Trace Flag
        mov     word ptr [esp].IretIp,offset d1c60     ; set IP to next instruction
        iret

TemporaryInt1   endp

;++
;
; BOOLEAN
; MultiplyTest (
;    VOID
;    )
;
; Routine Description:
;
;    This routine checks the 386 32-bit multiply instruction.
;    The reason for this check is because some of the i386 fail to
;    perform this instruction.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    Carry Flag clear on success
;    Carry Flag set on failure
;
;--
;

    assume ds:nothing, es:nothing, fs:nothing, gs:nothing, ss:nothing

MultiplyTest    proc

        xor     cx,cx                   ; 64K times is a nice round number
mlt00:  push    cx
        call    Multiply                ; does this chip's multiply work?
        pop     cx
        jc      short mltx              ; if c, No, exit
        loop    mlt00                   ; if nc, YEs, loop to try again
        clc
mltx:
        ret

MultiplyTest    endp

;++
;
; BOOLEAN
; Multiply (
;    VOID
;    )
;
; Routine Description:
;
;    This routine performs 32-bit multiplication test which is known to
;    fail on bad 386s.
;
;    Note, the supplied pattern values must be used for consistent results.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    Carry Flag clear on success.
;    Carry Flag set on failure.
;
;--

Multiply        proc

        mov     ecx, MULTIPLIER
        mov     eax, MULTIPLICAND
        mul     ecx

        cmp     edx, RESULT_HIGH        ; Q: high order answer OK ?
        stc                             ; assume failure
        jnz     short mlpx              ;   N: exit with error

        cmp     eax, RESULT_LOW         ; Q: low order answer OK ?
        stc                             ; assume failure
        jnz     short mlpx              ;   N: exit with error

        clc                             ; indicate success
mlpx:
        ret

Multiply        endp

;++
;
; BOOLEAN
; IsNpxPresent(
;     VOID
;     );
;
; Routine Description:
;
;     This routine determines if there is any Numeric coprocessor
;     present.  If yes, the ET bit in CR0 will be set; otherwise
;     it will be reset.
;
;     Note that we do NOT determine its type (287, 387).
;     This code is extracted from Intel book.
;
; Arguments:
;
;     None.
;
; Return:
;
;     TRUE - If NPX is present.  Else a value of FALSE is returned.
;
;--

if 0
        public  _IsNpxPresent
_IsNpxPresent   proc    near

        push    bp                      ; Save caller's bp
.386p
        mov     eax, cr0
        and     eax, NOT CR0_ET         ; Assume no NPX
        mov     edx, 0
.287
        fninit                          ; Initialize NPX
        mov     cx, 5A5Ah               ; Put non-zero value
        push    cx                      ;   into the memory we are going to use
        mov     bp, sp
        fnstsw  word ptr [bp]           ; Retrieve status - must use non-wait
        cmp     byte ptr [bp], 0        ; All bits cleared by fninit?
        jne     Inp10

        or      eax, CR0_ET
        mov     edx, 1
Inp10:
        mov     cr0, eax
        pop     ax                      ; clear scratch value
        pop     bp                      ; Restore caller's bp
        mov     eax, edx
        ret

_IsNpxPresent   endp
endif

ENDIF ; def ALLOW_386

_TEXT   ENDS
        END