            PAGE    ,120

;-------------------------------------------------------------------------------
;
; This is a test program to demonstrate the capabilities of subroutine
; CPU_NDP_TST. To produce an executable COM file, proceed as follows:
;
; MASM CPUTEST;
; LINK CPUTEST;
; EXE2BIN CPUTEST.EXE CPUTEST.COM
;
; use MASM 5.1 or TASM 2.01 to assemble this file
;
; CPU_NDP_TST is (c) 1988-1991 Norbert Juffa, this version dated 09-14-1991
;
; Code to check for IIT NDPs courtesy of IIT Corporation Europe 02-19-1991
;
;-------------------------------------------------------------------------------

            .386p                        ; 386 and 387 code
            .387                         ;  will be used

JMPS        EQU     <JMP SHORT>          ; jumps have to be
JES         EQU     <JE  SHORT>          ;  declared as short,
JBS         EQU     <JB  SHORT>          ;   since the default
JNZS        EQU     <JNZ SHORT>          ;    distance with the .386
JCS         EQU     <JC  SHORT>          ;     directive is near
JZS         EQU     <JZ  SHORT>          ;      jumps, which can
JNES        EQU     <JNE SHORT>          ;       only be executed on
JAES        EQU     <JAE SHORT>          ;        386 and 486 machines


_TEXT       SEGMENT BYTE PUBLIC USE16 'CODE'
            ASSUME  CS:_TEXT, DS:_TEXT   ; DS=CS=ES=SS for COM program

            ORG     100h                 ; to make COM program

MainStart:  MOV     DX, OFFSET IntroMsg  ; pointer to introductory message
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            CALL    CPU_NDP_TST          ; determine installed CPU and NDP
            MOV     BX, AX               ; save processor types
            MOV     DX, OFFSET CPUMsg    ; pointer to CPU message
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            MOV     AX, BX               ; get processor types (AL = CPU)
            MOV     AH, 22               ; compute pointer
            MUL     AH                   ;  to message for
            ADD     AX, OFFSET Processor ;   this CPU type
            XCHG    AX, DX               ; pointer to correct CPU type message
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            MOV     DX, OFFSET EOL       ; get pointer to CR-LF
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            MOV     DX, OFFSET NDPMsg    ; get pointer to NDP message
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            XCHG    AX, BX               ; get processor types (AH = NDP)
            MOV     AL, 23               ; compute pointer
            MUL     AH                   ;  to message for
            ADD     AX,OFFSET Coprocessor;   this NDP type
            XCHG    AX, DX               ; pointer to correct CPU type message
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            MOV     DX, OFFSET EOL       ; get pointer to CR-LF
            MOV     AH, 9                ; DOS #9, print string
            INT     21h                  ; call DOS
            MOV     AX, 4C00h            ; DOS #4C, terminate program, errlvl 0
MainEnd:    INT     21h                  ; call DOS


;-------------------------------------------------------------------------------
; CPU_NDP_TST is a subroutine which returns the currently installed CPU and
; numerical coprocessor. To distinguish between the different types of CPUs
; and NDPs, it relies only on the incompatiblities of the processors themselves
; and does not require other system ressources such as timers. This routine
; must be executed in the real mode of i286, i386(sx), and i486 processors!
;
; INPUT:       NONE                   CPU          NDP
; OUTPUT:      AH   - NDP type        ----------   ------------
;              AL   - CPU type        n.a.   - 0       none - 0
;                                     8088   - 1   Emulator - 1
; DESTROYED:   AX,BX,CX,DX,Flags      8086   - 2       8087 - 2
;              NDP control word       V20    - 3      80187 - 3
;              (user configuration)   V30    - 4      80287 - 4
;              preserved              80188  - 5    80287XL - 5
;                                     80186  - 6      80387 - 6
;                                     80286  - 7    IIT2C87 - 7
;                                     80386  - 8    IIT2C87 - 8
;                                     80486  - 9    IIT3C87 - 9
;                                     80486sx-10      80486 -10
;
;-------------------------------------------------------------------------------

            PUBLIC  CPU_NDP_TST
            ASSUME  CS: _TEXT, DS: _TEXT

OriginlCtrl EQU     [BP-2]               ; caller's NDP control word
Ctrl        EQU     [BP-4]               ; saved NDP control word for tests
Stat        EQU     [BP-6]               ; saved NDP status word for tests

CPU_NDP_TST PROC    NEAR
            PUSH    BP                   ; save caller's frame pointer
            MOV     BP, SP               ; make new frame pointer
            SUB     SP, 6                ; create local variables
            PUSH    SI                   ; save register
            PUSH    DI                   ;  variables (C, Modula)
            PUSH    DS                   ; save caller's data segment
            PUSH    CS                   ; make code segment
            POP     DS                   ;  also data segment
            PUSH    SP                   ; test updating
            POP     AX                   ;  of stackpointer
            CMP     AX, SP               ; stackpointer updated before push ?
            JES     $286_386             ; no, must be 286, 386 or 486
            MOV     AX, 1                ; try to shift
            MOV     CL, 33               ;  accu 33 times
            SHL     AX, CL               ; shift count masked off ?
            JNZS    $186_188             ; yes, must be 186 or 188
            PUSHA                        ; PUSHA executed on 88/86 as JMP --+
            STC                          ; carry set if V20 or V30          |
            JCS     $V20_V30             ; yes, must be V20 or V30  <-------+
            PUSHF                        ; save flags
            POP     AX                   ; pop flags into AX
            AND     AX, 0FFFH            ; clear bits 12-15 of flag register
            PUSH    AX                   ; put new flags in stack
            POPF                         ; pop into flag register
            PUSHF                        ; put flags on stack
            POP     AX                   ; get flags
            AND     AH, 0F0H             ; test if all bits
            CMP     AH, 0F0H             ;  in highest nibble set
            JES     $88_86               ; all bits in highest nibble set
            XOR     DL, DL               ; failed all tests, unknown CPU
            JMPS    $ndp_test            ; go and test ndp
$88_86:     MOV     DL, 1                ; else its an 88 or 86
            JMPS    $queue_test          ; decide wether 88 or 86
$V20_V30:   POPA                         ; remove pushed bytes
            MOV     DL, 3                ; its an V20 or V30
            JMPS    $queue_test          ; decide wether V20 or V30
$186_188:   MOV     DL, 5                ; 188/186
            JMPS    $queue_test          ; decide wether 188 or 186
$286_386:   MOV     DL, 7                ; 286, 386 or 486
            PUSH    7000H                ;  try to set
            POPF                         ;   IOPL and NT fields
            PUSHF                        ;    in bit 12-14
            POP     AX                   ;     of flag register
            TEST    AX, 7000H            ; bits cannot be set in 286 real mode
            JZS     $ndp_test            ; bits not set --> 286
            INC     DX                   ; CPU is an 386 (DL = 8) or 486
            MOV     EBX, ESP             ; save current stackpointer to align it
            AND     ESP, 0FFFFFFFCh      ; align stack to avoid AC fault
            PUSHFD                       ; save EFLAGS
            POP     EAX                  ; get EFLAGS from stack
            MOV     ECX, EAX             ; original value of EFLAGS
            XOR     EAX, 40000H          ; toggle AC bit in EFLAGS
            PUSH    EAX                  ; copy new value
            POPFD                        ;  to EFLAGS
            PUSHFD                       ; get new EFLAGS value
            POP     EAX                  ; put into EAX
            XOR     EAX, ECX             ; test if AC bit could be changed
            SHR     EAX, 18              ; EAX = 0 if 386, 1 if 486
            PUSH    ECX                  ; restore original
            POPFD                        ;  value of EFLAGS
            MOV     ESP, EBX             ; restore original stack pointer
            AND     EAX, 1               ; check CPU code in EAX
            JZS     $ndp_test            ; if 0, must be 386
            INC     DX                   ; it's a 486 (CPU = 9)
            JMPS    $ndp_test            ; check for NDP (486 <-> 486sx)
$queue_test:CLI                          ; int service disturbs prefetch queue
            MOV     BX, OFFSET $patch    ; load patch adress into BX
            MOV     BYTE PTR [BX], 42h   ; preset with opcode for INC DX
            MOV     AL, 90H              ; patch in a NOP (opcode 90h)
            MOV     CL, 31               ; rotate register 31 times to use up
            ROL     AH, CL               ;  time so prefetch queue can be filled
            MOV     BYTE PTR [BX], AL    ; insert NOP at label $patch
            NOP                          ; fill
            NOP                          ;  prefetch
            NOP                          ;   queue
            NOP                          ;    with NOPs
$patch:     INC     DX                   ; patched to nop on i88, i188, and V20
            STI                          ; test complete, interrupts OK now
$ndp_test:  FNSTCW  [OriginlCtrl]        ; save caller's NDP configuration
            XOR     DH, DH               ; assume no coprocessor
            XOR     AX, AX               ; clear register
            OUT     0F0h, AL             ; clear error signal
            FNINIT                       ; initialize coprocessor
            MOV     WORD PTR [Ctrl], AX  ; clear status variable
            NOT     AX                   ; load all 1's
            MOV     WORD PTR [Stat], AX  ; initialize status variable to all 1's
            FNSTCW  [Ctrl]               ; store NDP control word
            MOV     AX, [Ctrl]           ; get control word
            AND     AX, 0F3Fh            ; extract RC, PC and exception masks
            CMP     AX, 033Fh            ; RC=0, PC=3, masks=3F ?
            JES     $cont_test           ; NDP seems to be present, more tests
            CMP     DL, 9                ; no NDP. CPU = 486 ?
            SBB     DL, -1               ; yes: 486 w/o NDP = 486sx
            JMPS    $end_test            ; no further NDP testing required
$cont_test: FNSTSW  [Stat]               ; store NDP status
            TEST    WORD PTR [Stat],383Fh; stack top & exceptions must be clear
            JNZS    $end_test            ; ST & exceptions not clear -> no NDP
            MOV     DH, 1                ; coprocessor is at least emulator (=1)
            CMP     DL, 7                ; is CPU 80286 or higher ?
            JBS     $no_emulat           ; no, emulation impossible
            SMSW    AX                   ; get machine status word
            TEST    AL, 4                ; test if EM bit of MSW set
            JNZS    $ndp_exit            ; yes, NDP only emulated
$no_emulat: MOV     DH, 2                ; coprocessor is at least 8087 (=2)
            FLD1                         ; load 1.0
            WAIT                         ; needed for 8087
            FLDZ                         ; load 0.0
            WAIT                         ; needed for 8087
            FDIV                         ; 1.0 / 0.0 = +infinity
            WAIT                         ; needed for 8087
            FLD     ST(0)                ; duplicate +infinity
            WAIT                         ; needed for 8087
            FCHS                         ; generate -inifinity
            WAIT                         ; needed for 8087
            FCOMPP                       ; compare infinities and clear NDP stk
            WAIT                         ; needed for 8087
            FSTSW   WORD PTR [Stat]      ; save condition codes
            MOV     AX, [Stat]           ; load condition codes
            SAHF                         ; transfer CC into CPU flags
            JNES    $187_387             ; 187, C287, 287XL, 387, 486 if n. eq.
            CMP     DL, 7                ; is CPU >= 286 ?
            JBS     $ndp_exit            ; no, coprocessor is 8087
            MOV     DH, 4                ; coprocessor is 287
            JMPS    $chk_iit             ; check for IIT coprocessors
$187_387:   CMP     DL, 7                ; is CPU >= 286 ?
            JAES    $C287_387            ; yes, NDP is 287A,287XL,387,486,487sx
            MOV     DH, 3                ; coprocessor is 187
            JMPS    $ndp_exit            ; store CPU and NDP code
$C287_387:  CMP     DL, 8                ; is CPU >= 386 ?
            JAES    $387_486             ; yes, NDP is 387,486,487sx
            MOV     DH, 5                ; coprocessor is 287A or 287XL
            JMPS    $chk_iit             ; check for IIT coprocessors
$387_486:   CMP     DL, 9                ; is CPU >= 486 ?
            JBS     $387                 ; no, NDP is 387 / 387sx
            MOV     DH, 10               ; NDP = 486
            JMPS    $ndp_exit            ; test done
$387:       MOV     DH, 6                ; coprocessor is 387 or 387sx
$chk_iit:   FINIT                        ; initialize coprocessor
            FWAIT                        ; wait until operation complete
            FLD     TBYTE PTR [TstVal]   ; load denormal number
            FWAIT                        ; wait until operation complete
            FADD    ST(0), ST            ; result is a normal
            FWAIT                        ; wait until operation complete
            FSTSW   AX                   ; get status word of NDP
            AND     AX, 2                ; flag for denormal exception set ?
            JNZS    $ndp_exit            ; Intel types set denormal exception
            ADD     DH, 3                ; set IIT coprocessor types
$ndp_exit:  FLDCW   [OriginlCtrl]        ; restore caller's NDP configuration
$end_test:  XCHG    AX, DX               ; get result into AX
            POP     DS                   ; restore callers data segment
            POP     DI                   ; restore register
            POP     SI                   ;  variables (C, Modula)
            MOV     SP, BP               ; remove local variables
            POP     BP                   ; restore caller's frame pointer
            RET                          ; done

TstVal      DT      1                    ; denormal number for IIT test

CPU_NDP_TST ENDP


IntroMsg    DB      79 DUP ('-'), 10, 13
            DB      20 DUP (' '), 'C P U - T e s t     (c) 1988-1991 N.J.',10,13
            DB      79 DUP ('-'), 10, 13, 10, 13, '$'

CPUMsg      DB      'CPU ist $'
NDPMsg      DB      'NDP ist $'
EOL         DB      10, 13, '$'

Processor   DB      'nicht identifizierbar$'
            DB      'Intel 8088           $'
            DB      'Intel 8086           $'
            DB      'NEC V20              $'
            DB      'NEC V30              $'
            DB      'Intel 80188          $'
            DB      'Intel 80186          $'
            DB      'Intel 80286          $'
            DB      'Intel 80386 / 80386sx$'
            DB      'Intel 80486          $'
            DB      'Intel 80486SX        $'

Coprocessor DB      'nicht vorhanden       $'
            DB      'Emulator via INT 7    $'
            DB      'Intel 8087            $'
            DB      'Intel 80187           $'
            DB      'Intel 80287           $'
            DB      'Intel 80287A / 80287XL$'
            DB      'Intel 80387 / 80387sx $'
            DB      'IIT 2C87              $'
            DB      'IIT 2C87              $'
            DB      'IIT 3C87              $'
            DB      'eingebaut             $'

_TEXT       ENDS
            END     MainStart

