;       SCCSID = @(#)inicpu.asm 6.1 90/11/17
        SCCSID equ "@(#)inicpu.asm      1.10 90/09/17"
        page    ,132

; ****************************************************************************
; *                                                                          *
; *                       IBM/Microsoft Confidential                         *
; *                                                                          *
; *                 Copyright (c) IBM Corporation  1987, 1990                *
; *                 Copyright (c) Microsoft Corp.  1987, 1990                *
; *                           All Rights Reserved                            *
; *                                                                          *
; ****************************************************************************

.xlist
include dosseg.inc
include basemaca.inc
include fnoinit.inc
include osmaca.inc
include mi.inc
include protmode.inc
include basemid.inc
include doshlp.inc
.list
        ModName inicpu, FNO_INICPU_ASM
        CpuMode 386

        ReferGlobal     _CpuType,dword
        ReferGlobal     _CpuStepping,dword
        ReferGlobal     _Pentium_Vendor_ID,dword
        ReferGlobal     _Pentium_Stepping,dword
        ReferGlobal     _Pentium_Feature_Flags,dword
        ReferGlobal     DHF,DWORD
        ReferGlobal     _CpuVendor,dword

        EXTRNFAR        WrtToScrn

        FARCODE INIT
INITCODE segment

;       Patterns for 32-bit multiply test

multiplier      dd      00000081h       ; multiplier
multiplicand    dd      0417a000h       ; multiplicand
goodhi          dd      00000002h       ; correct product (high order)
goodlo          dd      0fe7a000h       ; correct product (low order)


;***LP  CpuAbort - abort the boot cycle with a message
;
;       ENTRY   (AX) = error message number
;       EXIT    DOES NOT RETURN
;
;       WrtToScrn(message)
;       WrtToScrn("insert disk and re-boot")
;       enable interrupts
;       loop forever, allowing CTL-ALT-DEL

Procedure CpuAbort,,local
    assume ds:nothing, es:nothing, fs:nothing, gs:nothing, ss:nothing
        DosContext <ds>
        push    ds
        call    DHF.DosHlpRealGetMessage; (ds:si) = message length + text
        lodsw
        mov     cx,ax                   ; (cx) = length
        movzx   edx,si
        CALLFAR WrtToScrn
        pop     ds
        mov     ax,MSG_SYSINIT_INSER_DK
        call    DHF.DosHlpRealGetMessage; (ds:si) = message length+text
        lodsw
        mov     cx,ax                   ; (cx) = message length
        movzx   edx,si
        CALLFAR WrtToScrn
abt90:  sti
        jmp     short abt90
EndProc CpuAbort


;***LP  CpuStepping - test cpu type and stepping
;
;       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.
;
;       ENTRY   NONE
;       EXIT    NONE
;       USES    EAX, EBX, ECX, EDX, DS, Flags
;
;       if (CheckB1() == error)
;           CpuAbort("bad B0 stepping")
;       CpuStepping = CheckD1()? CPU_386D1 : CPU_386B1
;       if (MulTest() == error)
;           CpuAbort("bad 32 bit multiply stepping")
;       if CpuType = CPU_486
;           CpuStepping = CPU_486B4
;       if CpuType = CPU_PENTIUM
;           CpuStepping = CPU_PENTIUMXXX
;           Issue CPUID instruction to get stepping level

Procedure CpuStepping
    assume ds:nothing, es:nothing, fs:nothing, gs:nothing, ss:nothing
;       RMONLY
        ; 157841 Test to see if this is an IBM/Cyrix Processor
        ; If it is IBM/Cyrix, don't call CheckB1, CheckD1, Multest. 
        ; Also if it is IBM/Cyrix, don't issue the CPUID command.
        DosContext ds
        cmp   _CpuVendor, CPU_CYRIX     ; 157841 test to see if this is cyrix
        jz    csc80                     ; 157841 yes IBM/Cyrix

        call    CheckB1                 ; check for B1 stepping
        mov     ax, MSG_80_STEPPING
        jc      short CpuAbort          ; jmp if B0 stepping
        call    CheckD1                 ; check for D1 stepping
        mov     eax,CPU_386B1           ; assume B1
        jc      short csc40             ; jmp if B1 stepping
        mov     eax,CPU_386D1           ; else D1
csc40:  DosContext ds
        mov     [_CpuStepping],eax
        call    MulTest                 ; test 32 bit multiply
        DosContext ds
        mov     ax, MSG_32BIT_MULTIPLY
        jc      short CpuAbort          ; jmp if bad chip
        cmp     [_CpuType],CPU_486      ; check for 486
        jne     short csc50             ; jmp if not a 486
        mov     [_CpuStepping],CPU_486B4; first 486 stepping
csc50:
        cmp     [_CpuType],CPU_PENTIUM  ; check for Pentium or Pro
        jb      cscx                    ; 157841 jmp if not a Pentium or Pro

       ;CPUID                           ; Get Vendor ID from Pentium
        mov     eax,0
        db 00Fh   ;Assembler doesn't know Pentium instruction, must db
        db 0A2h   ;
        mov     _Pentium_Vendor_ID[0],ebx
        mov     _Pentium_Vendor_ID[4],edx
        mov     _Pentium_Vendor_ID[8],ecx

        mov     eax,1
       ;CPUID                           ; Get Vendor ID from Pentium
        db 00Fh   ;Assembler doesn't know Pentium instruction, must db
        db 0A2h   ;
        mov     _Pentium_Stepping,eax
        mov     _Pentium_Feature_Flags,edx
        cmp     [_CpuType],CPU_PENTIUM     ; 157841 check for 586 
        jne     short csc60                ; 157841 must be 686
        mov     edx,CPU_PENTIUMXXX
        jmp     short csc70                ; 157841
csc60:                                     ; 157841
        mov     edx,CPU_PENTIUMPROXXX      ; 157841
csc70:                                     ; 157841
        mov     dx,ax                      ; 157841
        mov     [_CpuStepping],edx         ; Save Pentium Stepping level
        jmp     short cscx                 ; 157841

        ; 157841 - This is an IBM/CYRIX Processor
csc80:                                     ; 157841
        mov     _Pentium_Vendor_ID[0],0    ; 157841
        mov     _Pentium_Vendor_ID[4],0    ; 157841
        mov     _Pentium_Vendor_ID[8],0    ; 157841
        mov     _Pentium_Stepping,0        ; 157841
        mov     _Pentium_Feature_Flags,0   ; 157841
        xor     eax, eax                   ; 157841 zero eax
        mov     al, 0ffh                   ; 157841 index for stepping register
        out     22h, al                    ; 157841 write to port 22h   
        in      al,23h                     ; 157841 read stepping level 
        cmp     [_CpuType],CPU_PENTIUM     ; 157841 check for 5x86 
        jne     short csc90                ; 157841 must be 6x86
        mov     edx,CPU_PENTIUMXXX
        jmp     short csc100               ; 157841
csc90:                                     ; 157841
        mov     edx,CPU_PENTIUMPROXXX      ; 157841
csc100:                                    ; 157841
        mov     dx,ax                      ; 157841
        mov     [_CpuStepping],edx         ; Save Pentium Stepping level

cscx:   ret
EndProc CpuStepping


;***LP  CheckB1 - check for 386 B1 stepping
;
;       This routine 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.
;
;       ENTRY   NONE
;       EXIT    Carry Flag clear if B1 or later stepping
;               Carry Flag set if B0 or prior
;       USES    EAX, EBX, ECX, EDX, DS, Flags
;
;       Save old invalid op-code fault handler
;       Set up invalid op-code fault handler
;       attempt "xbts cx,dx,ax,cl" instruction
;       Restore old invalid op-code fault handler
;       if (CX != 0 || fault handler got control)
;           return(success)             ; is B1 or newer
;       return(failure)                 ; else is B0

Procedure CheckB1,,local
    assume ds:nothing, es:nothing, fs:nothing, gs:nothing, ss:nothing
;       RMONLY
        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     [bx].Offst,offset Int6
        mov     [bx].Segmt,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
        ret
EndProc CheckB1


;***LP  Int6 - Temporary int 6 handler
;
;       Temporary int 6 handler - assumes the cause of the exception was the
;       attempted execution of an XTBS instruction.
;
;       ENTRY   NONE
;       EXIT    NONE
;       USES    Resets caller's IP
;
;       iret to CheckB1's b1c60 label

Procedure Int6,,local
        mov     [esp].IretIP,offset b1c60       ; set IP to clc instruction
        iret
EndProc Int6


;***LP  CheckD1 - check for 386 D1 stepping
;
;       This routine takes advantage of the fact that the B1 and earlier
;       steppings execute two iterations of a rep movsb instruction before
;       accepting the trace trap.  D1 and later steppings do not exibit this
;       behavior.  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.
;
;       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.
;
;       ENTRY   NONE
;       EXIT    Carry Flag clear if D1 or later stepping
;               Carry Flag set if B1 or prior
;       USES    EAX, EBX, ECX, EDX, DS, Flags
;
;       Save old trace trap handler
;       Set up trace trap handler
;       attempt to trace a rep movsb instruction with CX set to 2
;       Restore old trace trap handler
;       if (CX == 0)                    ; if trace stopped after 2 repetitions
;           return(failure)             ; is B1
;       return(success)                 ; else is D1 or newer

Procedure CheckD1,,local
    assume ds:nothing, es:nothing, fs:nothing, gs:nothing, ss:nothing
;       RMONLY
        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     [bx].Offst,offset Int1
        mov     [bx].Segmt,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],F_TRACE
        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:   ret
EndProc CheckD1


;***LP  Int1 - Temporary int 1 handler
;
;       Temporary int 1 handler - assumes the cause of the exception was
;       trace trap at the above rep movs instruction.
;
;       ENTRY   NONE
;       EXIT    NONE
;       USES    Resets caller's IP, Clears caller's Trace Flag
;
;       clear caller's trace flag
;       iret to CheckD1's d1c60 label

Procedure Int1,,local
        and     [esp].IretFLAGS,not F_TRACE     ; clear Trace Flag
        mov     [esp].IretIP,offset d1c60       ; set IP to next instruction
        iret
EndProc Int1


;***LP  MulTest - Check the 386 32-bit MULtiply instruction.
;
;       This procedure will display a warning message if the CPU fails.
;
;       ENTRY   (DS) = DOSINITDATA
;       EXIT    Carry Flag clear on success
;               Carry Flag set on failure
;       USES    EAX, EBX, ECX, EDX, DS, Flags
;
;       call MulProc 64k times
;       if (error)
;           return(failure)
;       return(success)

Procedure MulTest,,local
    assume ds:nothing, es:nothing, fs:nothing, gs:nothing, ss:nothing
;       RMONLY
        xor     cx,cx                   ; 64K times is a nice round number
        push    cs
        pop     ds
    assume ds:DOSCODE
mlt00:  call    MulProc                 ; Q: does this chip's multiply work?
        jc      short mltx              ;   N: exit
        loop    mlt00                   ;   Y: PASSED, loop to try again
        clc
mltx:   ret
EndProc MulTest


;***LP  MulProc - 32-bit multiply pattern test.
;
;       The supplied pattern values must be used for consistent results.
;
;       ENTRY   (DS) = DOSINITDATA
;       EXIT    Carry Flag clear on success
;               Carry Flag set on failure
;       USES    EAX, EBX, EDX, Flags
;
;       Attempt a 32 bit multiplication known to fail on bad 386s
;       if (the result is not as expected)
;           return(failure)             ; the chip is bad
;       return(success)

Procedure MulProc,,local
    assume ds:DOSCODE, es:nothing, fs:nothing, gs:nothing, ss:nothing
;       RMONLY
        mov     ebx,[multiplier]
        mov     eax,[multiplicand]
        mul     ebx

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

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

        clc                             ; indicate success
mlpx:   ret
EndProc  MulProc

INITCODE ends
        end
