;-----------------------------------------------------------------------------
; CPU_Type determines the CPU type in the system.
;-----------------------------------------------------------------------------
; Written by:
;   Robert Collins
;   3361 Keys Lane
;   Anaheim, CA  92804
;-----------------------------------------------------------------------------
; Input:   None
; Output:  AX = CPU type
;           0 = 8086/8088
;           1 = 80186/80188
;           2 = 80286
;           3 = 80386
;           4 = 80486
;        FFFF = Unknown CPU type
;-----------------------------------------------------------------------------

.radix 16                       ; anybody that programs in base 10 is a wimp!
.386P                           ; MASM 5.1 directive

;-----------------------------------------------------------------------------
; PUBLIC statements here
;-----------------------------------------------------------------------------
        Public  CPU_Type

;-----------------------------------------------------------------------------
; Local variable definitions
;-----------------------------------------------------------------------------
        INT6    equ     [bp-4]

;-----------------------------------------------------------------------------
; Interrupt vector segment
;-----------------------------------------------------------------------------
ABS0    segment use16 at 0

        org 6*4
Orig_INT6       dd      ?

ABS0    ends


;-----------------------------------------------------------------------------
; 80486 instruction macro -- because MASM 5.1 doesn't support the 80486!
;-----------------------------------------------------------------------------
XADD    macro
        db      0fh,0C0h,0D2h           ; 80486 instruction macro
ENDM


cseg    segment para    use16 public 'code'
        assume  cs:cseg

CPU_Type        proc    near
;-----------------------------------------------------------------------------
; Determine the CPU type by testing for differences in the CPU in the system.
;-----------------------------------------------------------------------------
; To test for the 8086/8088, test the value of SP after it is placed on the
; stack.  The 8086/8088 increments this value before pushing it on the stack,
; all other CPU's increment SP after pushing it on the stack.
;-----------------------------------------------------------------------------
        xor     ax,ax                   ; clear CPU type return register
        push    sp                      ; save SP on stack to look at
        pop     bx                      ; get SP saved on stack
        cmp     bx,sp                   ; if 8086/8088, these values will differ
        jnz     CPU_8086_exit           ; yep
;-----------------------------------------------------------------------------
; When we get here, we know that we aren't a 8086/8088.  And since all
; subsequent processors will trap invalid opcodes via INT6, we will determine
; which CPU we are by trapping an invalid opcode.
;   We are an 80486 if:  XADD   DX,DX   executes correctly
;             80386 if:  MOV    EDX,CR0 executes correctly
;             80286 if:  SMSW   DX      executes correctly
;             80186 if:  SHL    DX,5    executes correctly
;-----------------------------------------------------------------------------
; Setup INT6 handler
;-----------------------------------------------------------------------------
        enter   4,0                     ; create stack frame
        mov     word ptr INT6,offset INT6_handler
        mov     INT6+2,cs
        call    set_INT6_vector         ; set pointer to our INT6 handler
        mov     ax,4                    ; initialize CPU flag=4 (80486)
        xor     cx,cx                   ; initialize semaphore

;-----------------------------------------------------------------------------
; Now, try and determine which CPU we are by executing invalid opcodes.
; The instructions I chose to invoke invalid opcodes, are themselves rather
; benign.  In each case, the chosen instruction modifies the DX register,
; and nothing else.  No system parameters are changed, e.g. protected mode,
; or other CPU dependant features.
;-----------------------------------------------------------------------------
; The 80486 instruction 'XADD' xchanges the registers, then adds them.
; The exact syntax for a '486 compiler would be:  XADD  DX,DX.
;-----------------------------------------------------------------------------
        XADD   ;DX,DX                   ; 80486
        jcxz    CPU_exit
        dec     ax                      ; set 80386 semaphore
        inc     cx                      ; CX=0

;-----------------------------------------------------------------------------
; For a description on the effects of the following instructions, look in
; the Intel Programmers Reference Manual's for the 80186, 80286, or 80386.
;-----------------------------------------------------------------------------
        mov     edx,cr0                 ; 80386
        jcxz    CPU_exit
        dec     ax                      ; set 80286 semaphore
        inc     cx                      ; CX=0

        smsw    dx                      ; 80286
        jcxz    CPU_exit
        dec     ax                      ; set 80186 semaphore
        inc     cx                      ; CX=0

        shl     dx,5                    ; 80186/80188
        jcxz    CPU_exit
        dec     ax                      ; set UNKNOWN_CPU semaphore

CPU_exit:
        call    set_INT6_vector
        leave

CPU_8086_exit:
        ret


;-----------------------------------------------------------------------------
; Set the INT6 vector by exchanging it with the one currently on the stack.
;-----------------------------------------------------------------------------
set_INT6_vector:
        push    ds
        push    ABS0                    ; save interrupt vector segment
        pop     ds                      ; make DS=INT vector segment
        mov     dx,word ptr ds:Orig_INT6;       ; get offset if INT6 handler
        xchg    INT6,dx                 ; set new INT6 offset
        mov     word ptr ds:Orig_INT6,dx
        mov     dx,word ptr ds:Orig_INT6+2      ; get segment of INT6 handler
        xchg    INT6+2,dx               ; set new INT6 segment
        mov     word ptr ds:Orig_INT6+2,dx
        pop     ds                      ; restore segment register
        ret                             ; split


;-----------------------------------------------------------------------------
; INT6 handler sets a semaphore (CX=FFFF) and adjusts the return address to
; point past the invalid opcode.
;-----------------------------------------------------------------------------
INT6_handler:
        enter   0,0                     ; create new stack frame
        dec     cx                      ; make CX=FFFF
        add     word ptr ss:[bp][2],3   ; point past invalid opcode
        leave
        iret

CPU_Type        endp

cseg    ends
        end

-- 
"Worship the Lord your God, and serve him only."  Mat. 4:10
Robert Collins                 UUCP:  ucbvax!ucivax!icnvax!conexch!rob
HOMENET:  (805) 523-3205       UUCP:  uunet!ccicpg!turnkey!conexch!rob
WORKNET:  (805) 378-7901


