;****************************************************************************
;*
;*                            The MegaToolbox
;*
;*                  Copyright (C) 1993 Kendall Bennett.
;*                          All rights reserved.
;*
;* Filename:    $RCSfile: cpu.asm $
;* Version:     $Revision: 1.1 $
;*
;* Language:    8086 Assembler
;* Environment: IBM PC (MS DOS)
;*
;* Description: Autodetection routine to determine the type of CPU installed
;*              in the system.
;*
;* $Id: cpu.asm 1.1 1993/09/02 11:04:55 kjb Exp $
;*
;* Revision History:
;* -----------------
;*
;* $Log: cpu.asm $
;* Revision 1.1  1993/09/02  11:04:55  kjb
;* Initial revision
;*
;****************************************************************************

        IDEAL

INCLUDE "model.mac"             ; Memory model macros

header  cpu                     ; Set up memory model

;****************************************************************************
;
; Equates used by queryCPU routine.
;
;****************************************************************************

; Central Processing Unit type codes

CPU86       =   0               ; 8086/88 type processor
CPU186      =   1               ; 80186 type processor
CPU286      =   2               ; 80286 type processor
CPU286p     =   3               ; 80286 type processor in protected mode
CPU386      =   4               ; 80386 type processor
CPU386p     =   5               ; 80386 type processor in protected mode
CPU486      =   6               ; 80486 type processor
CPU486p     =   7               ; 80486 type processor in protected mode

; Floating Point Unit type codes

FPUNONE     =   0               ; No coprocessor present
FPU87       =   1               ; 8087 coprocessor
FPU287      =   2               ; 80287 coprocessor
FPU387      =   3               ; 80387 coprocessor
FPU487      =   4               ; 80487 coprocessor

begcodeseg  cpu                 ; Start of code segment

P386                            ; Enable all '386 instructions
P387                            ; Enable all '387 instructions

; Save the type of CPU detected so we can determine the co-processor
; type correctly. This means that we MUST call queryCpu() BEFORE calling
; queryFpu() to obtain correct results.

cpu         dw  CPU86

;----------------------------------------------------------------------------
; cpu_type queryCpu(void)
;----------------------------------------------------------------------------
; Determine type of processor present.
;----------------------------------------------------------------------------
procstart   _queryCpu

        push    bp              ; We MUST save bp for initialization code...

        mov     ax,CPU86        ; Default to 8086/8088 processor
        push    sp
        pop     bx              ; BX holds the value of SP or SP-2
        cmp     bx,sp           ; 88/86/186 pushes the value of SP-2
        je      @@Check286      ; Must be a 286/386/486 type processor
        mov     cl,32           ; 186 uses count mod 32 = 0
        shl     bx,cl           ; 86 shifts 32 bits left so ax = 0
        jz      @@Done          ; zero: shifted out all bits so 86/88
        mov     ax,CPU186       ; nonzero: no shift, so 186
        jz      @@Done

@@Check286:                     ; First check for 386/486 in 32 bit mode
        pushf                   ; Test for 16 or 32 operand size:
        mov     bx,sp           ;  pushed 2 or 4 bytes of flags
        popf
        inc     bx
        inc     bx
        cmp     bx,sp           ; did pushf change sp by 2?
        jnz     @@Check486      ; 32 bit push, so it is a 386/486

        sub     sp,6            ; Is it a 286/386/486 in 16 bit mode?
        mov     bp,sp
        sgdt    [QWORD ptr bp]  ; 80286/386/486 specific instrucion
        add     sp,4            ; Get global descriptor table
        pop     bx
        inc     bh              ; Third word of GDT = -1 for 286
        jnz     @@Check486      ; We have a 386/486

        mov     ax,CPU286       ; We have a 286
        jmp     @@TestPROT

@@Check486:

; Distinguish an 80386 from an 80486. Bit 18 (40000H) of EFLAGS register
; is used only in the 486. This code flips it and tests if anything happened.

        mov     edx,esp         ; Save stack pointer
        and     esp,not 3       ; Align stack pointer to prevent a fault
                                ;  when we set the AC flag on a 486
        pushfd                  ; Copy the EFLAGS register
        pop     eax             ;   into register eax
        mov     ecx,eax         ; Save the original EFLAGS value
        xor     eax,40000H      ; Flip the AC flag bit
        push    eax             ; Try to put the modified value back
        popfd                   ;   into the EFLAGS register
        pushfd                  ; Copy the EFLAGS register again
        pop     eax             ;   into eax
        xor     eax,ecx         ; Compare the old and new AC bits
        shr     eax,18          ; Shift and mask to get the AC comparison bit
        and     eax,1           ;   in the low order position of eax
        push    ecx
        popfd                   ; Restore EFLAGS that were saved on entry
        mov     esp,edx         ; And restore stack pointer to saved value
        mov     bx,ax           ; and move into bx

; At this point ax = 0 for a 386, or ax = 1 for a 486

        mov     ax,CPU386       ; Assume a 386
        test    bx,bx
        jz      @@TestPROT      ; We have a 386
        mov     ax,CPU486       ; We have a 486

@@TestPROT:
        smsw    cx              ; protected? machine status -> cx
        ror     cx,1            ; protection bit -> carry flag
        jnc     @@Done          ; Real mode if no carry
        inc     ax              ; Protected: return value + 1

@@Done:
        mov     [cpu],ax        ; Save CPU type in code segment variable
        pop     bp              ; Restore bp
        ret                     ; We are done

procend     _queryCpu

ndp_cw  dw  ?
ndp_sw  dw  ?

;----------------------------------------------------------------------------
; fpu_type queryFpu(void)
;----------------------------------------------------------------------------
; Determine type of floating point coprocessor present in the system.
; The idea is to determine whether or not the floating-point control word
; can be successfully read. If it cannot, then no coprocessor exists.
; If it can the correct coprocessor is then determined depending on the
; main CPU id.
;----------------------------------------------------------------------------
procstart   _queryFpu

        push    bp

        mov     bx,FPUNONE      ; Default to no FPU present

; The next two 80x87 instructions cannot carry the WAIT prefix,
; because there may not be an 80x87 for which to wait.  The WAIT is
; therefore emulated with a MOV CX,<value> LOOP $ combination.

        mov     [ndp_cw],0      ; Clear the control word in memory
        cli                     ; Interrupts must be off during test

        fninit                  ; reset NDP status word
        mov     cx,2            ; Wait for co-pro to complete operation
        loop    $

        fnstcw  [ndp_cw]        ; Obtain the processor control word
        mov     cx,14h          ; Wait for co-pro to complete operation
        loop    $

        sti                     ; Re-enable interrupts

; We check to see that the precison control bits of the control word
; indicate 64 bit internal precision (bits 8 & 9 set) which is the default
; set up by the fninit instruction above. We also test that the exception
; masks are properly set.

        mov     ax,[ndp_cw]     ; AX := NDP control word
        and     ax,033fh        ; Mask out the precision control bits etc.
        cmp     ax,033fh        ; is the NDP present?
        jne     @@Done          ; No, we are all done... (must be a 3)

; Determine the type of NDP from the main CPU type

        mov     bx,FPU87        ; Start with the 8087 NDP
        mov     ax,[cpu]        ; Get current cpu type
        cmp     ax,CPU286       ; >= 80286 type processor?
        jge     @@80286         ; Yes, check for 287/387/487
        jmp     @@Done          ; No, we are done

; Now that we know we have a possible co-processor and the processor is
; at least an 80286, we can check to see if coprocessor emulation software
; is installed in the system. Some emulators such as FRANKIE.387 emulate
; the co-processor so well that the above checks believe a co-pro is
; actually out there.

@@80286:
        smsw    ax              ; AX := machine status word
        test    al,4            ; Check the EM bit status
        jnz     @@Done          ; Software emulation installed on INT 7!

        mov     ax,[cpu]        ; AX := current CPU flag
        cmp     ax,CPU386       ; Do we have a 386 or above?
        jge     @@80386         ; Yes, check for it
        mov     bx,FPU287       ; We have a 80287 co-pro
        jmp     @@Done

@@80386:
        cmp     ax,CPU486       ; Do we have a 486 or above?
        jge     @@80486         ; Yes, check for it

; The i386 processor can work with either an 80287 or 80387 co processor
; so we must check for that here. The 387 says that +inf <> -inf while
; the 287 says that they are the same.

        fld1                    ; Load +1.0 onto NDP stack
        fldz                    ; Load +0.0 onto NDP stack
        fdiv                    ; do +1/0 (create +inf)
        fld1                    ; Load +1.0 onto NDP stack
        fchs                    ; Change to -1.0
        fldz                    ; Load +0.0 onto NDP stack
        fdiv                    ; do -1/0 (create -inf)
        fcompp                  ; compare and pop values from stack
        fstsw   [ndp_sw]        ; Get the status word from the co pro
        mov     ax,[ndp_sw]     ; AX := Status word
        and     ah,41h          ; Mask out C3 and C0 condition codes
        cmp     ah,40h          ; C3 = 1, C0 = 0 means ST(0) == ST(1)
        mov     bx,FPU287       ; Set up for a 287 co pro
        je      @@Done          ; Yes, we were correct

        mov     bx,FPU387       ; No, it was an 80387
        jmp     @@Done

@@80486:
        mov     bx,FPU487       ; We must have a 487 co pro.

@@Done:
        mov     ax,bx           ; Return FPU type in AX
        pop     bp              ; Restore bp
        ret                     ; We are done

procend     _queryFpu

endcodeseg  cpu

        END                     ; End of module
