;       Filename:       cpuid32.msm
;
;       This program has been developed by Intel Corporation.  You have
;       Intel's permission to incorporate this source code into your
;       product royalty free.
;
;       Intel specifically disclaims all warranties, express or implied,
;       and all liability, including consequential and other indirect
;       damages, for the use of this code, including liability for
;       infringement of any proprietary rights.  Intel does not assume
;       any responsibility for any errors which may appear in this code
;       nor any responsibility to update it.
;
;       This program contains three parts:
;       Part 1: Identifies CPU type in the variable cpu_type:
;               0=8086 processor
;               2=Intel 286 processor
;               3=Intel386(TM) processor
;               4=Intel486(TM) processor
;               5=Pentium(TM) processor
;
;       Part 2: Identifies FPU type in the variable fpu_type:
;               0=FPU not present
;               1=FPU present
;               2=287 present (only if cpu_type=3)
;               3=387 present (only if cpu_type=3)
;
;       Part 3: Prints out the appropriate message.  This part can
;               be removed if this program is not used in a DOS-based
;               system.  Portions affected are at the end of the
;               data segment and the print procedure in the code
;               segment.
;
;       REVISION HISTORY:
;       Date:  4/93
;       1. Replaced the .486 with .186 to avoid generation of 0FH type long
;          conditional branches (such as the branch to end_get_cpuid at
;          the end of the 8086/8088 test, near code address 003D).  These
;          are not executable on the 8086/8088 and 80286, so the code falls
;          into some strange place and hangs the system.  The .186 also allows
;          multi-bit shifts to unpack CPUID info.  Using .186 requires
;          that all 32-bit operand prefix (66H) be inserted by hand.
;          This is done with a macro (OPND32).
;       2. Avoid all usage of 32-bit operands until it is clear that the
;          CPU is at least an 80386.  The use of the Exx registers caused
;          the generation of the 66H prefix, which are not executed
;          correctly on the 8086/8088 and 80286.
;       3. Eliminated all the register save/restore and added comment
;          that all registers are used by the functions.
;       4. Do the stack alignment just before messing with the AC bit in
;          EFLAGS, otherwise the stack may not be properly aligned.  Also
;          restore the AC bit immediately, so it does not stay set.
;       5. Changed the FPU detection to set one flag fpu_type (instead of
;          the previous two flags: fpu_present and infinity).  An fpu_type
;          of zero indicates no floating point unit is present, an fpu_type
;          of 2 indicates an 80287 is present, an fpu_type of 3 indicates
;          an 80387 is present.
;
;       If this code is assembled with MASM with no options specified, it
;       runs correctly on an 8086/8088, 80286, 80386, 80486, and
;       Pentium(tm) processor.
;


        TITLE   CPUID
        DOSSEG
        .model  small
        .stack  100h
        .186

OPND32 MACRO op_code, op_erand
        db      66h     ; Force 32-bit operand size
  IFNB <op_code>
        db      op_code
    IFNB <op_erand>
        dd      op_erand; 32-bit immediate value
    ENDIF
  ENDIF
ENDM

CPUID5 MACRO
        db      0fh     ; Hardcoded opcode for CPUID instruction
        db      0a2h
ENDM

TRUE            equ     1
FAMILY_MASK     equ     0f00h
FAMILY_SHIFT    equ     8
MODEL_MASK      equ     0f0h
MODEL_SHIFT     equ     4
STEPPING_MASK   equ     0fh
FPU_FLAG        equ     1h
MCE_FLAG        equ     80h
CMPXCHG8B_FLAG  equ     100h

        .data
fp_status       dw      ?
vendor_id       db      12 dup (?)
cpu_type        db      ?
modell           db      ?
stepping        db      ?
id_flag         db      0
fpu_type        db      0
intel_proc      db      0
feature_flags   dw      2 dup (0)
;
; remove the remaining data declarations if not using the DOS-based
; print procedure
;
id_msg          db      "This system has a$"
fp_8087         db      " and an 8087 math coprocessor$"
fp_80287        db      " and an 80287 math coprocessor$"
fp_80387        db      " and an 80387 math coprocessor$"
c8086           db      "n 8086/8088 processor$"
c286            db      "n 80286 processor$"
c386            db      "n 80386 processor$"
c486            db      "n 80486 DX processor or 80487 SX math coprocessor$"
c486nfp         db      "n 80486 SX processor$"
Intel486_msg    db      13,10,"This system contains a Genuine Intel486(TM) processor",13,10,"$"
Pentium_msg     db      13,10,"This system contains a Genuine Intel Pentium(TM)  processor",13,10,"$"
modelmsg        db      "Model:            $"
steppingmsg     db      "Stepping:         $"
familymsg       db      13,10,"Processor Family: $"
period          db      ".",13,10,"$"
dataCR          db      ?,13,10,"$"
intel_id        db      "GenuineIntel"
fpu_msg         db      13,10,"This processor contains a FPU",13,10,"$"
mce_msg         db      "This processor supports the Machine Check Exception",13,10,"$"
cmp_msg         db      "This processor supports the CMPXCHG8B instruction",13,10,"$"
not_intel       db      "t least an 80486 processor.",13,10
                db      "It does not contain a Genuine Intel part and as a result,",13,10
                db      "the CPUID detection information cannot be determined at this time.",13,10,"$"

;
;       The purpose of this code is to identify the processor and
;       coprocessor that is currently in the system.  The program first
;       determines the processor id.  When that is accomplished,
;       the program then determines whether a coprocessor
;       exists in the system.  If a coprocessor or integrated
;       coprocessor exists, the program identifies
;       the coprocessor id.  The program then prints out
;       the CPU and floating point presence and type.
;
        .code
start:  mov     ax, @data
        mov     ds, ax          ; set segment register
        mov     es, ax          ; set segment register
        pushf                   ; save for restoration at end
        call    get_cpuid
        call    get_fpuid
        call    print
        popf
        mov     ax, 4c00h       ; terminate program
        int     21h


get_cpuid proc
;
;       This procedure determines the type of CPU in a system
;       and sets the cpu_type variable with the appropriate
;       value.
;       All registers are used by this procedure, none are preserved.

;       Intel 8086 CPU check
;       Bits 12-15 of the FLAGS register are always set on the
;       8086 processor.
;
check_8086:
        pushf                   ; push original FLAGS
        pop     ax              ; get original FLAGS
        mov     cx, ax          ; save original FLAGS
        and     ax, 0fffh       ; clear bits 12-15 in FLAGS
        push    ax              ; save new FLAGS value on stack
        popf                    ; replace current FLAGS value
        pushf                   ; get new FLAGS
        pop     ax              ; store new FLAGS in AX
        and     ax, 0f000h      ; if bits 12-15 are set, then CPU
        cmp     ax, 0f000h      ;   is an 8086/8088
        mov     cpu_type, 0     ; turn on 8086/8088 flag
        jne     check_80286     ; jump if CPU is not 8086/8088
        jmp     end_get_cpuid
;       Intel 286 CPU check
;       Bits 12-15 of the FLAGS register are always clear on the
;       Intel 286 processor in real-address mode.
;
check_80286:
        or      cx, 0f000h      ; try to set bits 12-15
        push    cx              ; save new FLAGS value on stack
        popf                    ; replace current FLAGS value
        pushf                   ; get new FLAGS
        pop     ax              ; store new FLAGS in AX
        and     ax, 0f000h      ; if bits 12-15 clear, CPU=80286
        mov     cpu_type, 2     ; turn on 80286 flag
        jnz     check_80386     ; if no bits set, CPU is 80286
        jmp     end_get_cpuid
;       Intel386 CPU check
;       The AC bit, bit #18, is a new bit introduced in the EFLAGS
;       register on the Intel486 DX CPU to generate alignment faults.
;       This bit cannot be set on the Intel386 CPU.
;
check_80386:
;       It is now safe to use 32-bit opcode/operands
        mov     bx, sp          ; save current stack pointer to align
        and     sp, not 3       ; align stack to avoid AC fault
        OPND32
        pushf                   ; push original EFLAGS
        OPND32
        pop     ax              ; get original EFLAGS
        OPND32
        mov     cx, ax          ; save original EFLAGS
        OPND32  35h, 40000h     ; flip AC bit in EFLAGS
        OPND32
        push    ax              ; save new EFLAGS value on stack
        OPND32
        popf                    ; replace current EFLAGS value
        OPND32
        pushf                   ; get new EFLAGS
        OPND32
        pop     ax              ; store new EFLAGS in EAX
        OPND32
        xor     ax, cx          ; can't toggle AC bit, CPU=80386
        mov     cpu_type, 3     ; turn on 80386 CPU flag
        mov     sp, bx          ; restore original stack pointer
        jz      end_get_cpuid   ; jump if 80386 CPU
        and     sp, not 3       ; align stack to avoid AC fault
        OPND32
        push    cx
        OPND32
        popf                    ; restore AC bit in EFLAGS first
        mov     sp, bx          ; restore original stack pointer

;       Intel486 DX CPU, Intel487 SX NDP, and Intel486 SX CPU check
;       Checking for ability to set/clear ID flag (Bit 21) in EFLAGS
;       which indicates the presence of a processor
;       with the ability to use the CPUID instruction.
;
check_80486:
        mov     cpu_type, 4     ; turn on 80486 CPU flag
        OPND32
        mov     ax, cx          ; get original EFLAGS
        OPND32  35h, 200000h    ; flip ID bit in EFLAGS
        OPND32
        push    ax              ; save new EFLAGS value on stack
        OPND32
        popf                    ; replace current EFLAGS value
        OPND32
        pushf                   ; get new EFLAGS
        OPND32
        pop     ax              ; store new EFLAGS in EAX
        OPND32
        xor     ax, cx          ; can't toggle ID bit,
        je      end_get_cpuid   ;   CPU=80486

;       Execute CPUID instruction to determine vendor, family,
;       model and stepping.
;
check_vendor:
        mov     id_flag, 1              ; set flag indicating use of CPUID inst.
        OPND32
        xor     ax, ax                  ; set up input for CPUID instruction
        CPUID5                          ; macro for CPUID instruction
        OPND32
        mov     word ptr vendor_id, bx  ; setup to test for vendor id
        OPND32
        mov     word ptr vendor_id[+4], dx
        OPND32
        mov     word ptr vendor_id[+8], cx
        mov     si, offset vendor_id
        mov     di, offset intel_id
        mov     cx, length intel_id
compare:
        repe    cmpsb                   ; compare vendor id to "GenuineIntel"
        or      cx, cx
        jnz     end_get_cpuid           ; if not zero, not an Intel CPU,

intel_processor:
        mov     intel_proc, 1

cpuid_data:
        OPND32
        cmp     ax, 1                   ; make sure 1 is a valid input
                                        ; value for CPUID
        jl      end_get_cpuid           ; if not, jump to end
        OPND32
        xor     ax, ax                  ; otherwise, use as input to CPUID
        OPND32
        inc     ax                      ; and get stepping, model and family
        CPUID5
        mov     stepping, al
        and     stepping, STEPPING_MASK ; isolate stepping info

        and     al, MODEL_MASK          ; isolate model info
        shr     al, MODEL_SHIFT
        mov     modell, al

        and     ax, FAMILY_MASK         ; mask everything but family
        shr     ax, FAMILY_SHIFT
        mov     cpu_type, al            ; set cpu_type with family

        OPND32
        mov     feature_flags, dx       ; save feature flag data

end_get_cpuid:
        ret
get_cpuid endp


;******************************************************************

get_fpuid proc
;
;       This procedure determines the type of FPU in a system
;       and sets the fpu_type variable with the appropriate
;       value.
;       All registers are used by this procedure, none are preserved.

;       Coprocessor check
;       The algorithm is to determine whether the floating-point
;       status and control words can be written to.  If not, no
;       coprocessor exists.  If the status and control words can be
;       written to, the correct coprocessor is then determined
;       depending on the processor id.  The Intel386 CPU can
;       work with either an Intel287 NDP or an Intel387 NDP.
;       The infinity of the coprocessor must be
;       checked to determine the correct coprocessor id.

        fninit                  ; reset FP status word
        mov     fp_status, 5a5ah; initialize temp word to
                                ; non-zero value
        fnstsw  fp_status       ; save FP status word
        mov     ax, fp_status   ; check FP status word
        cmp     al, 0           ; see if correct status with
                                ; written
        mov     fpu_type, 0     ; no fpu present
        jne     end_get_fpuid

check_control_word:
        fnstcw  fp_status       ; save FP control word
        mov     ax, fp_status   ; check FP control word
        and     ax, 103fh       ; see if selected parts
                                ; looks OK
        cmp     ax, 3fh         ; check that 1's & 0's
                                ; correctly read
        mov     fpu_type, 0
        jne     end_get_fpuid
        mov     fpu_type, 1

;
;   80287/80387 check for the Intel386 CPU
;
check_infinity:
        cmp     cpu_type, 3
        jne     end_get_fpuid
        fld1                    ; must use default control from FNINIT
        fldz                    ; form infinity
        fdiv                    ; 8087 and Intel287 NDP say +inf = -inf
        fld     st              ; form negative infinity
        fchs                    ; Intel387 NDP says +inf <> -inf
        fcompp                  ; see if they are the same and remove them
        fstsw   fp_status       ; look at status from FCOMPP
        mov     ax, fp_status
        mov     fpu_type, 2     ; store Intel287 NDP for fpu type
        sahf                    ; see if infinities matched
        jz      end_get_fpuid   ; jump if 8087 or Intel287 is present
        mov     fpu_type, 3     ; store Intel387 NDP for fpu type
end_get_fpuid:
        ret
get_fpuid endp


;*********************************************************************

print proc
;
;       This procedure prints the appropriate cpuid string and
;       numeric processor presence status.  If the CPUID instruction
;       was supported, this procedure prints out cpuid info.
;       All registers are used by this procedure, none are preserved.

        cmp     id_flag, 1              ; if set to 1, cpu supports
                                        ;   CPUID instruction
                                        ; print detailed CPUID information
        jne     cont
        jmp     print_cpuid_data

cont:   mov     dx, offset id_msg       ; print initial message
        mov     ah, 9h
        int     21h

print_86:
        cmp     cpu_type, 0
        jne     print_286
        mov     dx, offset c8086
        mov     ah, 9h
        int     21h
        cmp     fpu_type, 0
        jne     cont2
        jmp     end_print
cont2:  mov     dx, offset fp_8087
        mov     ah, 9h
        int     21h
        jmp     end_print

print_286:
        cmp     cpu_type, 2
        jne     print_386
        mov     dx, offset c286
        mov     ah, 9h
        int     21h
        cmp     fpu_type, 0
        jne     cont3
        jmp     end_print
cont3:  mov     dx, offset fp_80287
        mov     ah, 9h
        int     21h
        jmp     end_print

print_386:
        cmp     cpu_type, 3
        jne     print_486
        mov     dx, offset c386
        mov     ah, 9h
        int     21h
        cmp     fpu_type, 0
        jne     cont4
        jmp     end_print
cont4:  cmp     fpu_type, 2
        jne     print_387
        mov     dx, offset fp_80287
        mov     ah, 9h
        int     21h
        jmp     end_print

print_387:
        mov     dx, offset fp_80387
        mov     ah, 9h
        int     21h
        jmp     end_print

print_486:
        cmp     fpu_type, 0
        je      print_Intel486sx
        mov     dx, offset c486
        mov     ah, 9h
        int     21h
        jmp     end_print

print_Intel486sx:
        mov     dx, offset c486nfp
        mov     ah, 9h
        int     21h
        jmp     end_print

print_cpuid_data:

cmp_vendor:
        cmp     intel_proc, 1
        je      cont5
        jmp     not_GenuineIntel
cont5:  cmp     cpu_type, 4                     ; if cpu_type=4, print
                                                ; Intel486 CPU message
        jne     check_Pentium
        mov     dx, offset Intel486_msg
        mov     ah, 9h
        int     21h
        jmp     print_family

check_Pentium:
        cmp     cpu_type, 5                     ; if cpu_type=5, print
        jne     print_features                  ; Pentium processor message
        mov     dx, offset Pentium_msg
        mov     ah, 9h
        int     21h

print_family:
        mov     dx, offset familymsg            ; print family msg
        mov     ah, 9h
        int     21h
        mov     al, cpu_type
        mov     byte ptr dataCR, al
        add     byte ptr dataCR, 30h            ; convert to ASCII
        mov     dx, offset dataCR               ; print family info
        mov     ah, 9h
        int     21h

print_model:
        mov     dx, offset modelmsg             ; print model msg
        mov     ah, 9h
        int     21h
        mov     al, modell
        mov     byte ptr dataCR, al
        add     byte ptr dataCR, 30h            ; convert to ASCII
        mov     dx, offset dataCR               ; print model info
        mov     ah, 9h
        int     21h

print_stepping:
        mov     dx, offset steppingmsg          ; print stepping msg
        mov     ah, 9h
        int     21h
        mov     al, stepping
        mov     byte ptr dataCR, al
        add     byte ptr dataCR, 30h            ; convert to ASCII
        mov     dx, offset dataCR               ; print stepping info
        mov     ah, 9h
        int     21h

print_features:
        mov     ax, feature_flags
        and     ax, FPU_FLAG                    ; check for FPU
        jz      check_MCE
        mov     dx, offset fpu_msg
        mov     ah, 9h
        int     21h

check_MCE:
        mov     ax, feature_flags
        and     ax, MCE_FLAG                    ; check for MCE
        jz      check_CMPXCHG8B
        mov     dx, offset mce_msg
        mov     ah, 9h
        int     21h

check_CMPXCHG8B:
        mov     ax, feature_flags
        and     ax, CMPXCHG8B_FLAG              ; check for CMPXCHG8B
        jz      end_print
        mov     dx, offset cmp_msg
        mov     ah, 9h
        int     21h
        jmp     end_print

not_GenuineIntel:
        mov     dx, offset not_Intel
        mov     ah, 9h
        int     21h

end_print:
        ret
print endp

        end     start
