; WHATCHIP.ASM  By Christy Gemmell
;
;   Adapted and embellished from a program by Anthony Naggs which was
;   published in PROGRAM NOW magazine, November 1989 issue.
;   See the CPUID code at the end of this file.

Ticks     equ     46Ch                    ; Address of BIOS tick counter

Code        segment para 'CODE'
            assume  cs:code,ds:code,ss:code,es:nothing
            org     100h

WhatChip    proc  far
            push  bp                    ; Establish
            mov   bp,sp                 ;    stack frame
            push  ds                    ; Preserve
            push  es                    ;    working
            pushf                       ;      registers

;   Before the 80286, Intel family microprocessors decrement the value
;   in SP before pushing it on the stack. Later processors do not.
;
            push  sp                    ; Push stack pointer
            pop   ax                    ;    and retrieve into AX
            cmp   ax,sp                 ; When was SP decremented?
            jnz   Chip_04               ; Branch if it was before the push

;   Advanced CPU offering PROTECTED mode operation ...
;
            cli                         ; Don't interrupt!
            mov   ax,3506h              ; DOS Service 53
            int   21h                   ;  - get current INT 6 vector
            mov   word ptr [Vector+2],es; Save vector in
            mov   word ptr [Vector],bx  ;    program code
            mov   ax,2506h              ; Replace with
            lea   dx,[Int6]             ;    address of our
            int   21h                   ;      own handler
            mov   bx,8                  ; Start by assuming an 80286

;   Test for 80386/80486 by embedding instructions for those processors in
;   the program code and seeing if INT 6 (Illegal Opcode) is triggered.
;
;   First, try loading one of the 80386/486 Extended (32-bit) registers.
;   The LSW of EBX (ie BX) is loaded with a value of 9.
;
Chip_01:
            db    66h,0BBh              ; MOV EBX,0A000009h
            dd    0A000009h             ; Load EBX register

;   Next try an 80486 Byte Swap instruction to reverse the sequence of bytes
;   in EBX. This would place a value of 1E6 hex or 486 decimal in BX.
;
Chip_02:
            db    0Fh,0CBh              ; BSWAP EBX (reverse byte order)

;   Interrupt handler directs 80286 and 80386 to return to here so that we
;   can now replace the original INT 6 interrupt vector.
;
Chip_03:
            mov   dx,word ptr [Vector]  ; Restore address of
            mov   ds,word ptr [Vector+2];    original INT 6 handler
            mov   ax,2506h              ; DOS Service 37
            int   21h                   ;  - set interrupt vector
            sti                         ; Allow hardware interrupts
            jmp   short Chip_11         ; BX now indicates CPU type

;   This section handles processors earlier than the 80286, but which one?
;   The 80186/8 modified the shift/rotate instruction so that shift counts
;   greater than 31 (which would zero the target) are masked by modulo 32.
;   If we try a 33-bit left shift on an 80186/8, therefore, the value will
;   only be shifted once or halved (33 modulo 32 = 1), instead of being
;   reduced to zero as it would be on earlier processors.
;
Chip_04:
            mov   ax,12                 ; Load 12 in AX
            mov   cl,33                 ; Number of bits to shift
            sar   ax,cl                 ; Shift left with zero fill
            mov   bx,ax                 ; BX = 6 if Intel 80186/8
            jnz   Chip_06               ; Test for 16-bit data bus

;   We've eliminated the 80186 and 80188 now to try and distinguish the
;   Intel 8086/8 from the NEC V20/V30 series. One difference is that the
;   Intel AAD (Arithmatic Adjust for Divide) instruction allows you to use
;   a different value multiplicand than 10 whereas the NEC encodes it in
;   the processor microcode, so that it is a constant. This test embeds an
;   AAD instruction with an operand of 16 in the program. If the operand
;   is actually used, it's an Intel, otherwise it's an NEC.
;
            mov   ax,0208h              ; Value to divide
            db    0D5h,16               ; Opcode for AAD 16
            cmp   al,28h                ; Result is hex?
            jz    Chip_05               ; Yes, it's an Intel
            mov   bx,4                  ; Otherwise it's a NEC
            jmp   short Chip_06         ; Test for 16-bit data bus

;   Before proceeding further we should test if this is a standard 8086/8
;   or one of the low-power consumption CMOS series. The standard version
;   has a bug such that if an instruction which has more than one prefix
;   is interrupted then, on return, only the last prefix will be retained.
;   This test sets up a string operation which continues for long enough
;   to be interrupted by the clock tick interrupt. If it does not complete
;   successfully, then we have an 8086/8 since Intel fixed this bug in the
;   CMOS-based 80C86/8 processors.
;
Chip_05:
            xor   bx,bx                 ; Start by assuming an 8088
            push  si                    ; Preserve SI register
            sti                         ; Enable timer interrupt
            mov   cx,0FFFFh             ; This loop takes approx
            rep   lods byte ptr es:[si] ;    100ms on an 8MHz 80C86
            pop   si                    ; Restore SI
            or    cx,cx                 ; Did loop complete?
            jne   Chip_06               ; Must be an original 8088/86
            mov   bx,2                  ; Otherwise its a CMOS version

;   This final section tests if the processor has an 8 or 16-bit data bus,
;   distinguishing the 8088/80C88/80188/V20 from the 8086/80C86/80186/V30.
;   The two series differ in that the 8-bit processors will replenish the
;   instruction queue after each byte is read, whereas 16-bit processors
;   wait until two bytes have been read before topping up the queue. Also
;   the 8086 queue is six bytes long while the 8088 series queue has only
;   four bytes. This routine tests the actual length of the instruction
;   queue by placing a dummy opcode at the fifth byte and testing if it is
;   actually executed. The test includes self-modifying code.
;
Chip_06:
            push  di                    ; Save index register
            push  bx                    ;    and processor type
            mov   dx,0                  ; DX is incremented if 16-bit
            mov   bx,4                  ; Iteration count
            std                         ; Set direction flag
            mov   al,90h                ; Opcode for NOP
Chip_07:
            lea   di,[Chip_09]
            cli                         ; Prevent queue being emptied
            mov   cx,3                  ; Allow enough time for
            rep   stosb                 ;    instruction queue to fill
            nop                         ; Pad four bytes (8-bit queue
            nop                         ;    length) before the INC
            nop
            nop
Chip_08:
            inc   dx                    ; Will this be overwritten
            nop                         ;    before it executes?
Chip_09:
            nop
            sti                         ; Restore interrupts
            mov   byte ptr [Chip_08],42h; Restore INC DX instruction
            dec   bx                    ; Repeat this test four times
            jnz   Chip_07               ;    to ensure accuracy
            pop   bx                    ; Restore processor type
            cmp   dx,0                  ; Was DX incremented?
            jz    Chip_10               ; No, must be 8-bit
            inc   bx                    ; Indicate 16-bit version
Chip_10:
            pop   di                    ; Restore destination index

Chip_11:
            push  cs                    ; Make sure code and data
            pop   ds                    ;    segments are aligned
            mov   dx,offset Report      ; DS:DX==> report heading
            mov   ah,9                  ; DOS Service 9
            int   21h                   ;  - output string to display
            mov   ax,bx                 ; Find offset
            mov   dx,15                 ;    of appropriate
            mul   dx                    ;      microprocessor
            mov   dx,offset Chips       ;        description string
            add   dx,ax                 ;          in the report table
            mov   ah,9                  ; DOS Service 9
            int   21h                   ;  - output string to display
            mov   dx,offset Tail        ; DS:DX==> message tail
            mov   ah,9                  ; DOS Service 9
            int   21h                   ;  - output string to display
            mov   dx,offset Speed       ; DS:DX==> message tail
            mov   ah,9                  ; DOS Service 9
            int   21h                   ;  - output string to display
Chip_12:
            push  bx                    ; Save CPU type
            sti                         ; Enable interrupts
            call  Classify              ; Determine CPU class
            xor   cx,cx                 ; Point ES to ROM BIOS data
            mov   es,cx                 ;    area in low memory
            dec   ax                    ; Test CPU class
            jnz   Chip_13               ; Weed out the geriatrics
            call  Timer_1               ; Evaluate 8088s and V20s
            jmp   short Chip_17         ;    and report
Chip_13:
            dec   ax                    ; Who's next?
            jnz   Chip_14               ; Seperate the men from the boys
            call  Timer_2               ; Evaluate 8086s, V30s and 80186s
            jmp   short Chip_17         ; Report when finished
Chip_14:
            dec   ax                    ; Now for the serious contenders
            jnz   Chip_15               ; Power users step forward!
            call  Timer_3               ; But is it a FAST 80286?
            jmp   short Chip_17         ; Publish the results
Chip_15:
            dec   ax                    ; The final eliminator
            jnz   Chip_16               ; What on Earth can this be?
            call  Timer_4               ; Lucky sod!
            jmp   short Chip_17         ; Rub it in, why don't you?
Chip_16:
            xor   ax,ax                 ; Speed could not
            mov   dx,offset _Unknown    ;    be measured!
            jmp   short Chip_19         ; So give up
Chip_17:
            sub   cx,dx                 ; Subtract finish from start time
            cmp   ax,cx                 ; Less than fudge factor?
            jbe   Chip_16               ; Not a meaningful
            jcxz  Chip_16               ;    result
            xor   dx,dx                 ; Convert
            div   cx                    ;    tick
            add   ax,32h                ;      count
            mov   cl,64h                ;        into
            div   cl                    ;          MegaHertz
            xor   ah,ah                 ; Clock speed is in AL
Chip_18:
            call  XLate                 ; Display CPU speed
            mov   dx,offset Meg         ; DS:DX==> message tail
Chip_19:
            mov   ah,9                  ; DOS Service 9
            int   21h                   ;  - output string to display
            pop   ax                    ; Retrieve CPU type
            mov   ah,4Ch                ; DOS Service 76
            int   21h                   ;  - terminate program
WhatChip    endp

;      Identify processor installed by class.
;
Classify    proc  near
            push  sp                    ; Save stack pointer
            pushf                       ;    and flags
            mov   dx,3                  ; Assume 80286
            push  sp                    ; Push stack pointer
            pop   ax                    ;    and pop into AX
            cmp   ax,sp                 ; Still the same?
            je    Class_2               ; Yes, must be 80286 or later
            dec   dx                    ; Assume 16-bit bus
            mov   ax,0FFFFh             ; Set every bit of AX
            mov   cl,21h                ; Test if a complete
            shl   ax,cl                 ;    shift is masked
            jc    Class_1               ; Yes, must be 16-bit bus
            dec   dx                    ; Otherwise it's an 8-bit bus
Class_1:
            mov   ax,dx                 ; CPU class to AX
            popf                        ; Clean up
            pop   sp                    ;    the stack
            ret                         ;      and return
Class_2:
            mov   ax,7000h              ; Set test value in AX
            push  ax                    ; Bounce it
            popf                        ;    through
            pushf                       ;      flags and
            pop   ax                    ;        back
            and   ax,7000h              ; Still the same?
            jz    Class_1               ; If so it's an 80286
            inc   dx                    ; Else it's an 80386 or 80486
            jmp   short Class_1         ;    so go and boast about it!!!
Classify    endp

;   Assorted timing loops.
;
;   These tests use the BIOS Countdown Timer at location 0000:046Ch in RAM.
;
;   Speed test for elderly Intel 8088s and NEC V20s
;
Timer_1     proc  near
            mov   dx,es:Ticks           ; DX = start time
Time_11:
            aam
            aam
            loop  Time_11
            mov   cx,es:Ticks           ; CX = finish time
            mov   ax,5454h              ; AX = conversion factor
            ret
Timer_1     endp


;   Speed test for Intel 8086s, 80186s and NEC V30s
;
Timer_2     proc  near
            mov   dx,es:Ticks           ; Start the countdown
Time_21:
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            loop  Time_21
            mov   cx,es:Ticks           ; Stop the clock
            mov   ax,4433h              ; Fudge factor
            ret
Timer_2     endp


;   Time trial for the Intel 80286
;
Timer_3     proc  near
            mov   dx,es:Ticks           ; Starting time
Time_31:
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            loop  Time_31
            cbw
            mov   cx,es:Ticks           ; Finishing time
            mov   ax,4DE0h              ; Fudge factor
            ret
Timer_3     endp

;   This one's for all you Speed Freaks out there!
;
Timer_4     proc  near
            mov   dx,es:Ticks           ; Whats the time?
Time_41:
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            aam
            loop  Time_41
            cbw
            mov   cx,es:Ticks           ; How long, O Lord?
            mov   ax,0A234h             ; Fudge factor
            ret
Timer_4     endp

;   Translate a Hexadecimal number into Decimal ASCII and display it.
;
;   On entry AX should contain the hexadecimal number to be translated and
;   the cursor correctly located on the screen.
;
XLate       proc  near
            push  ax                    ; Save working registers
            push  bx
            push  cx
            push  dx
            push  bp
            push  si                    ; Save source index pointer
            mov   bp,4                  ; Power = 4
            mov   si,offset Powers      ; DS:SI==> powers table
            mov   bh,0                  ; Digit printed flag
            mov   cx,ax                 ; Number to CX
XLate_1:
            mov   bl,0                  ; Repeat count
XLate_2:
            mov   ax,cx                 ; Test if number is negative
            rcl   ax,1                  ; Rotate left through carry
            jb    XLate_3               ; Negative, go
            mov   ax,[si]               ; Get current power
            clc                         ; Clear carry flag
            sub   cx,ax                 ; Subtract power
            inc   bl                    ; Bump repeat count
            jmp   XLate_2               ;   until negative
XLate_3:
            mov   ax,[si]               ; Get current power
            add   cx,ax                 ; Put last subtraction back
            dec   bl                    ; Subtract 1 from repeat count
            cmp   bl,0                  ; Zero?
            jz    XLate_4               ; Go and handle it
            mov   bh,1                  ; Set digit printed flag
            add   bl,48                 ; Make value ASCII digit
            push  ax                    ; Save pointer
            mov   dl,bl                 ; Character to DL
            mov   ah,2                  ; DOS Service 2
            int   21h                   ;  - output character
            pop   ax                    ; Recover pointer
XLate_4:
            cmp   bl,0                  ; Test for zero again
            jnz   XLate_6               ; No, we've done this power
            cmp   bp,0                  ; Last power?
            jz    XLate_5               ; Yes, better print it
            cmp   bh,1                  ; Any previous digits?
            jz    XLate_5               ; OK to print it then
            jmp   short XLate_6         ; Get next power
XLate_5:
            push  ax                    ; Save pointer
            mov   dl,48                 ; ASCII zero to AL
            mov   ah,2                  ; DOS Service 2
            int   21h                   ;  - output character
            pop   ax                    ; Recover pointer
            mov   bh,1                  ; Set digit printed flag
XLate_6:
            add   si,2                  ; Bump power table pointer
            sub   bp,1                  ; Decrement power
            jae   XLate_1               ; Loop through all powers
            pop   si                    ; Clean up the stack
            pop   bp
            pop   dx
            pop   cx
            pop   bx
            pop   ax
            ret
XLate       endp

;   Interrupt Handler for Illegal Opcode Errors.
;
;   Local Interrupt 16 exception handler to trap Illegal Opcode errors
;   which may be generated by tests for 80386 or 80486.
;
Int6        proc  far
            push  bp                    ; Establish
            mov   bp,sp                 ;    stack frame
            push  ax                    ; Save working register
            mov   ax,cs                 ; Compare local code segment
            cmp   [bp+4],ax             ;    with code segment of the
            pop   ax                    ;      interrupted process
            jnz   Chain                 ; Pass it on if it's not ours
            cmp   bx,8                  ; Was it the 286/386 test?
            je    FixUp                 ; Yes, fix up proper return
            add   word ptr [bp+2],(offset Chip_03-offset Chip_02)
            jmp   short Egress          ; Fix return from 486 test
FixUp:
            add   word ptr [bp+2],(offset Chip_03-offset Chip_01)
Egress:
            pop   bp                    ; Clean up the stack
            iret                        ;    and return to caller
Chain:
            pop   bp                    ; Chain to original
            jmp   [Vector]              ;    INT 6 handler
Vector:
            dd    ?                     ; Address of original handler
Int6        endp

;      Data Division.
;
Powers      dw    10000                 ; Powers of ten table
            dw    1000
            dw    100
            dw    10
            dw    1
Report      db    10,13,'This machine is fitted with ','$'
Chips       db    'an Intel 8088','$',0
            db    'an Intel 8086','$',0
            db    'an Intel 80C88','$'
            db    'an Intel 80C86','$'
            db    'a NEC V20','$',0,0,0,0,0
            db    'a NEC V30','$',0,0,0,0,0
            db    'an Intel 80188','$'
            db    'an Intel 80186','$'
            db    'an Intel 80286','$'
            db    'an Intel 80386','$'
            db    'an Intel 80486','$'
Tail        db    ' microprocessor',10,13,'$'
Speed       db    'running at ','$'
Meg         db    ' MHz clock speed.',10,13,'$'
_UnKnown    db    'an immeasurable clock speed.',13,10,'$'

Code        ends
            end   WhatChip

;Here's the additional code to identify Pentiums (and presumably any
;new CPUs that Intel bring out). Your program should only execute this
;code if your sure you've got a 486 or better.
;
;   Check for a 486 by attempting to toggle the EFLAGS register ID bit.
;   This bit cannot be changed on early model 486DXs and all 486SXs
;
                cli                             ; Disable interrupts
                pushfd                          ; Preserve extended flags
                pushfd                          ; Get EFLAGS
                pop     eax                     ;    into EAX
                mov     ebx,eax                 ; Copy 'em to EBX
                xor     eax,200000h             ; Toggle bit 21
                push    eax                     ; Get result back
                popfd                           ;    into flags
                pushfd                          ; Then reverse
                pop     eax                     ;    the process
                popfd                           ; Restore original flags
                sti                             ; Enable interrupts
                and     eax,200000h             ; Isolate bit 21
                and     ebx,200000h             ;    of EAX and EBX
                cmp     eax,ebx                 ; Are they the same?
                je      ??????                  ; Yes it's a standard 486

;   If we can toggle the ID bit then the CPU should be able to identify
;   itself - so let's ask it.
;
                mov     eax,1                   ; Get CPU ID information
CPUID:
                db      0Fh                     ; Opcodes for the
                db      0A2h                    ;    CPUID instruction
                and     eax,0F00h               ; Clear all but bits 8-11
                shr     eax,8                   ; EAX has family ID

If your program gets to here then AX will contain:

    4 = 486 (DX2 or better)     5 = Pentium     6 = P6?  etc etc...

