home *** CD-ROM | disk | FTP | other *** search
- title CPUID -- Determine CPU & NDP Type
- page 58,122
- name CPUID
-
- COMMENT|
-
- CPUID purports to uniquely identify each Intel CPU & NDP used in IBM
- PCs and compatibles. For more details, see the accompanying .TXT
- file.
-
- Notes on Program Structure
- --------------------------
-
- This program uses four segments, two classes, and one group. It
- demonstrates a useful technique for programmers who generate .COM
- programs. In particular, it shows how to use segment classes to
- re-order segments, and how to eliminate the linker's warning message
- about the absence of a stack segment.
-
- The correspondence between segments and classes is as follows:
-
- Segment Class
- ------- -----
- STACK prog
- DATA data
- MDATA data
- CODE prog
-
- The segments appear in the above order in the program source to
- avoid forward references in the CODE segment to labels in the
- DATA/MDATA segments. However, because the STACK segment appears first
- in the file, it and all segments in the same class are made contiguous
- by the linker. Thus they precede the DATA/MDATA segments in the
- resulting .COM file because the latter are in a different class. In
- this manner, although DATA and MDATA precede CODE in the source file,
- their order is swapped in the .COM file. That way there is no need
- for an initial skip over the data areas to get to the CODE segment.
- As a side benefit, declaring a STACK segment (as the first segment in
- the source) also eliminates the linker's warning about that segment
- being missing. Finally, all segments are declared to be in the same
- group so the linker can properly resolve offsets.
-
- Note that if you re-assemble the code for any reason, it is
- important to use an assembler later than the IBM version 1.0. That
- version has a number of bugs including an annoying habit of
- alphabetizing segment names in the .OBJ file. Such gratuitous
- behavior defeats the above technique as well as exhibits generally bad
- manners. If you use IBM MASM 2.0, be sure to specify /S to order the
- segments properly.
-
- If the program reports results at variance with your knowledge of
- the system, please contact the author.
-
- Environments tested in:
-
- CPU Speed
- System in MHz CPU NDP
- --------------------------------------------------
- IBM PC AT 6 Intel 80286 Intel 80287
- IBM PC AT 9 Intel 80286 Intel 80287
- IBM PC AT 6 Intel 80286 none
- IBM PC AT 8.5 Intel 80286 none
- IBM PC 4.77 Intel 8088 Intel 8087-3
- IBM PC 4.77 Intel 8088* Intel 8087-3
- IBM PC XT 4.77 Intel 8088 none
- IBM PC XT 4.77 Intel 8088 Intel 8087-3
- COMPAQ 4.77 Intel 8088 none
- COMPAQ 4.77 NEC V20 none
- AT&T PC 6300 8 Intel 8086 Intel 8087-2
- AT&T PC 6300 8 NEC V30 Intel 8087-2
- TANDY 2000 8 Intel 80186 none
-
- * = faulty CPU
-
- Program structure:
- Group PGROUP:
- Stack segment STACK, byte-aligned, stack, class 'prog'
- Program segment CODE, byte-aligned, public, class 'prog'
- Data segment DATA, byte-aligned, public, class 'data'
- Data segment MDATA, byte-aligned, public, class 'data'
-
- Assembly requirements:
-
- Use MASM 1.25 or later.
- With IBM's MASM 2.0 only, use /S to avoid
- alphabetizing the segment names.
- Use /r option to generate real NDP code.
-
- MASM CPUID/r; to convert .ASM to .OBJ
- LINK CPUID; to convert .OBJ to .EXE
- EXE2BIN CPUID CPUID.COM to convert .EXE to .COM
- ERASE CPUID.EXE to avoid executing .EXE
-
- Note that the linker doesn't warn about a missing stack segment.
-
- Copyright free.
-
- Original code by:
-
- Bob Smith May 1985.
- Qualitas, Inc.
- 8314 Thoreau Dr.
- Bethesda, MD 20817
- 301-469-8848
-
- Arthur Zachai suggested the technique to distinguish within the 808x
- and 8018x families by exploiting the difference in the length of
- their pre-fetch instruction queues.
-
- Modifications by:
-
- Who When Why
- ---------------------------------------------------------------------
- Bob Smith 21 Jan 86 Distinguish NEC V20/V30s
- from Intel 8088/8086s
-
- |
-
- subttl Structures, Records, Equates, & Macros
- page
- ARG_STR struc
-
- dw ? ; Caller's BP
- ARG_OFF dw ? ; Caller's offset
- ARG_SEG dw ? ; segment
- ARG_FLG dw ? ; flags
-
- ARG_STR ends
-
- ; Record to define bits in the CPU's & NDP's flags' registers
-
- CPUFLAGS record R0:1,NT:1,IOPL:2,OF:1,DF:1,IF:1,TF:1,SF:1,ZF:1,R1:1,AF:1,R2:1,PF:1,R3:1,CF:1
- NDPFLAGS record R4:3,IC:1,RC:2,PC:2,IEM:1,R5:1,PM:1,UM:1,OM:1,ZM:1,DM:1,IM:1
-
- COMMENT|
-
- FLG_PIQL Pre-fetch instruction queue length, 0 => 4-byte,
- 1 => 6-byte
- FLG_08 Intel 808x
- FLG_NEC NEC V20 or V30
- FLG_18 Intel 8018x
- FLG_28 Intel 8028x
-
- FLG_87 Intel 8087
- FLG_287 Intel 80287
-
- FLG_CERR Faulty CPU
- FLG_NERR Faulty NDP switch setting
-
- |
-
- FLG record RSVD:9,FLG_NERR:1,FLG_CERR:1,FLG_NDP:2,FLG_CPU:3
-
- ; CPU-related flags
-
- FLG_PIQL equ 001b shl FLG_CPU
- FLG_08 equ 000b shl FLG_CPU
- FLG_NEC equ 010b shl FLG_CPU
- FLG_18 equ 100b shl FLG_CPU
- FLG_28 equ 110b shl FLG_CPU
-
- FLG_8088 equ FLG_08
- FLG_8086 equ FLG_08 or FLG_PIQL
- FLG_V20 equ FLG_NEC
- FLG_V30 equ FLG_NEC or FLG_PIQL
- FLG_80188 equ FLG_18
- FLG_80186 equ FLG_18 or FLG_PIQL
- FLG_80286 equ FLG_28 or FLG_PIQL
-
- ; NDP-related flags
-
- ; 00b shl FLG_NDP Not present
- FLG_87 equ 01b shl FLG_NDP
- FLG_287 equ 10b shl FLG_NDP
-
- BEL equ 07h
- LF equ 0Ah
- CR equ 0Dh
- EOS equ '$'
-
- POPFF macro
- local L1,L2
-
- jmp short L2 ; Skip over IRET
- L1:
- iret ; Pop the CS & IP pushed below along
- ; w. the flags, our original purpose
- L2:
- push cs ; Prepare for IRET by pushing CS
- call L1 ; Push IP, jump to IRET
-
- endm ; POPFF macro
-
- TAB macro TYP
-
- push bx ; Save for a moment
- and bx,mask FLG_&TYP ; Isolate flags
- mov cl,FLG_&TYP ; Shift amount
- shr bx,cl ; Shift to low-order
- shl bx,1 ; Times two to index table of words
- mov dx,TYP&MSG_TAB[bx] ; DS:DX ==> descriptive message
- pop bx ; Restore
-
- mov ah,09h ; Function code to display string
- int 21h ; Request DOS service
-
- endm ; TAB macro
- page
- INT_VEC segment at 0 ; Start INT_VEC segment
-
- dd ? ; Pointer to INT 00h
- INT01_OFF dw ? ; Pointer to INT 01h
- INT01_SEG dw ?
-
- INT_VEC ends ; End INT_VEC segment
-
- PGROUP group STACK,CODE,DATA,MDATA
-
-
- ; The following segment both positions class 'prog' segments lower in
- ; memory than others so the first byte of the resulting .COM file is
- ; in the CODE segment, as well as satisfies the LINKer's need to have
- ; a stack segment.
-
- STACK segment byte stack 'prog' ; Start STACK segment
- STACK ends ; End STACK segment
-
- I11_REC record I11_PRN:2,I11_RSV1:2,I11_COM:3,I11_RSV2:1,I11_DISK:2,I11_VID:2,I11_RSV3:2,I11_NDP:1,I11_IPL:1
-
- DATA segment byte public 'data' ; Start DATA segment
- assume ds:PGROUP
-
- OLDINT01_VEC label dword ; Save area for original INT 01h handler
- OLDINT01_OFF dw ?
- OLDINT01_SEG dw ?
-
- NDP_CW label word ; Save area for NDP control word
- db ?
- NDP_CW_HI db 0 ; High byte of control word
-
- NDP_ENV dw 7 dup (?) ; Save area for NDP environment
-
- DATA ends ; End DATA segment
- subttl Message Data Area
- page
- MDATA segment byte public 'data' ; Start MDATA segment
- assume ds:PGROUP
-
- MSG_START db 'CPUID -- Version 1.0'
- db CR,LF,EOS
-
- MSG_8088 db 'CPU is an Intel 8088.',CR,LF,EOS
- MSG_8086 db 'CPU is an Intel 8086.',CR,LF,EOS
- MSG_V20 db 'CPU is an NEC V20.',CR,LF,EOS
- MSG_V30 db 'CPU is an NEC V30.',CR,LF,EOS
- MSG_80188 db 'CPU is an Intel 80188.',CR,LF,EOS
- MSG_80186 db 'CPU is an Intel 80186.',CR,LF,EOS
- MSG_UNK db 'CPU is a maverick -- 80288??',CR,LF,EOS
- MSG_80286 db 'CPU is an Intel 80286.',CR,LF,EOS
-
- CPUMSG_TAB label word
- dw PGROUP:MSG_8088 ; 000 = Intel 8088
- dw PGROUP:MSG_8086 ; 001 = Intel 8086
- dw PGROUP:MSG_V20 ; 010 = NEC V20
- dw PGROUP:MSG_V30 ; 011 = NEC V30
- dw PGROUP:MSG_80188 ; 100 = Intel 80188
- dw PGROUP:MSG_80186 ; 101 = Intel 80186
- dw PGROUP:MSG_UNK ; 110 = ?
- dw PGROUP:MSG_80286 ; 111 = Intel 80286
-
- NDPMSG_TAB label word
- dw PGROUP:MSG_NDPX ; 00 = No NDP
- dw PGROUP:MSG_8087 ; 01 = Intel 8087
- dw PGROUP:MSG_80287 ; 10 = Intel 80287
-
- MSG_NDPX db 'NDP is not present.',CR,LF,EOS
- MSG_8087 db 'NDP is an Intel 8087.',CR,LF,EOS
- MSG_80287 db 'NDP is an Intel 80287.',CR,LF,EOS
-
- CERRMSG_TAB label word
- dw PGROUP:MSG_CPUOK ; 0 = CPU healthy
- dw PGROUP:MSG_CPUBAD ; 1 = CPU faulty
-
- MSG_CPUOK db 'CPU appears to be healthy.',CR,LF,EOS
- MSG_CPUBAD label byte
- db BEL,'*** CPU incorrectly allows interrupts'
- db 'after a change to SS ***',CR,LF
- db 'It should be replaced with a more recent'
- db 'version as it could crash the',CR,LF
- db 'system at seemingly random times.',CR,LF,EOS
-
- NERRMSG_TAB label word
- dw PGROUP:MSG_NDPSWOK ; 0 = NDP switch set correctly
- dw PGROUP:MSG_NDPSWERR ; 1 = NDP switch set incorrectly
-
- MSG_NDPSWOK db EOS ; No message
- MSG_NDPSWERR label byte
- db '*** Although there is an NDP installed'
- db 'on this system, the corresponding',CR,LF
- db 'system board switch is not properly set. '
- db 'To correct this, flip switch 2 of',CR,LF
- db 'switch block 1 on the system board.',CR,LF,EOS
-
- MDATA ends ; End MDATA segment
- subttl Main Routine
- page
- CODE segment byte public 'prog' ; Start CODE segment
- assume cs:PGROUP,ds:PGROUP,es:PGROUP
-
- org 100h ; Skip over PSP
- INITIAL proc near
-
- mov dx,offset ds:MSG_START ; Starting message
- mov ah,09h ; Function code to display string
- int 21h ; Request DOS service
-
- call CPUID ; Check the CPU's identity
-
- TAB CPU ; Display CPU results
- TAB NDP ; Display NDP results
- TAB CERR ; Display CPU ERR results
- TAB NERR ; Display NDP ERR results
-
- ret ; Return to DOS
-
- INITIAL endp ; End INITIAL procedure
- subttl CPUID Procedure
- page
- CPUID proc near ; Start CPUID procedure
- assume cs:PGROUP,ds:PGROUP,es:PGROUP
-
- COMMENT|
-
- This procedure determines the type of CPU and NDP (if any) in use.
-
- The possibilities include:
-
- Intel 8086
- Intel 8088
- NEC V20
- NEC V30
- Intel 80186
- Intel 80188
- Intel 80286
- Intel 8087
- Intel 80287
-
- Also checked is whether or not the CPU allows interrupts after
- changing the SS segment register. If the CPU does, it is faulty and
- should be replaced.
-
- Further, if an NDP is installed, non-AT machines should have a
- system board switch set correspondingly. Such a discrepancy is
- reported upon.
-
- On exit, BX contains flag settings (as defined in FLG record) which
- the caller can check. For example, to test for an Intel 80286, use
-
- and bx,mask FLAG_CPU
-
- cmp bx,FLG_80286
- je ITSA286
-
- |
-
- irp XX,<ax,cx,di,ds,es> ; Save registers
- push XX
- endm
-
- ; Test for 80286 -- this CPU executes PUSH SP by first storing SP on
- ; stack, then decrementing it. Earlier CPUs decrement, THEN store.
-
- mov bx,FLG_28 ; Assume it's a 286
-
- push sp ; Only 286 pushes pre-push SP
- pop ax ; Get it back
-
- cmp ax,sp ; Check for same
- je CHECK_PIQL ; They are, so it's a 286
-
- ; Test for 80186/80188 -- 18x and 286 CPUs mask shift/rotate
- ; operations mod 32; earlier CPUs use all 8 bits of CL.
-
- mov bx,FLG_18 ; Assume it's an 8018x
- mov cl,32+1 ; 18x masks shift counts mod 32
- ; Note we can't use just 32 in CL
- mov al,0FFh ; Start with all bits set
-
- shl al,cl ; Shift one position if 18x
- jnz CHECK_PIQL ; Some bits still on,
- ; so it's a 18x, check PIQL
-
- mov bx,FLG_NEC ; Assume it's an NEC V-series CPU
- call CHECK_NEC ; See if it's an NEC chip
- jcxz CHECK_PIQL ; Good guess, check PIQL
-
- mov bx,FLG_08 ; It's an 808x
- subttl Check Length Of Pre-fetch Instruction Queue
- page
- COMMENT|
-
- Check the length of the pre-fetch instruction queue (PIQ).
-
- xxxx6 CPUs have a PIQ length of 6 bytes,
- xxxx8 CPUs " " " 4 "
-
- Self-modifying code is used to distinguish the two PIQ lengths.
-
- |
-
- CHECK_PIQL:
- call PIQL_SUB ; Handled via subroutine
- jcxz CHECK_ERR ; If CX is 0, INC was not executed,
- ; hence PIQ length is 4
- or bx,FLG_PIQL ; PIQ length is 6
- subttl Check For Allowing Interrupts After POP SS
- page
-
- ; Test for faulty chip (allows interrupts after change to SS register)
-
- CHECK_ERR:
- xor ax,ax ; Prepare to address
- ; interrupt vector segment
- mov ds,ax ; DS points to segment 0
- assume ds:INT_VEC ; Tell the assembler
-
- cli ; Nobody move while we swap
-
- mov ax,offset cs:INT01 ; Point to our own handler
- xchg ax,INT01_OFF ; Get and swap offset
- mov OLDINT01_OFF,ax ; Save to restore later
-
- mov ax,cs ; Our handler's segment
- xchg ax,INT01_SEG ; Get and swap segment
- mov OLDINT01_SEG,ax ; Save to restore later
-
- ; Note we continue with interrupts disabled to avoid
- ; an external interrupt occurring during this test.
-
- mov cx,1 ; Initialize a register
- push ss ; Save SS to store back into itself
-
- pushf ; Move flags
- pop ax ; ...into AX
- or ax,mask TF ; Set trap flag
- push ax ; Place onto stack
- POPFF ; ...and then into effect
- ; Some CPUs effect the trap flag
- ; immediately, some
- ; wait one instruction
-
- nop ; Allow interrupt to take effect
- POST_NOP:
- pop ss ; Change the stack segment register
- ; (to itself)
- dec cx ; Normal CPUs execute this instruction
- ; before recognizing the single-step
- ; interrupt
- hlt ; We never get here
- INT01:
-
- ; Note IF=TF=0
-
- ; If we're stopped at or before POST_NOP, continue on
-
- push bp ; Prepare to address the stack
- mov bp,sp ; Hello, Mr. Stack
-
- cmp [bp].ARG_OFF,offset cs:POST_NOP ; Check offset
- pop bp ; Restore
- ja INT01_DONE ; We're done
-
- iret ; Return to caller
- INT01_DONE:
-
- ; Restore old INT 01h handler
-
- les ax,OLDINT01_VEC ; ES:AX ==> old INT 01h handler
- assume es:nothing ; Tell the assembler
- mov INT01_OFF,ax ; Restore offset
- mov INT01_SEG,es ; ...and segment
-
- sti ; Allow interrupts again (IF=1)
-
- add sp,3*2 ; Strip IP, CS, and Flags from stack
-
- push cs ; Setup DS for code below
- pop ds
- assume ds:PGROUP ; Tell the assembler
-
- jcxz CHECK_NDP ; If CX is 0, the DEC CX was executed,
- ; and the CPU is OK
- or bx,mask FLG_CERR ; It's a faulty chip
- subttl Check For Numeric Data Processor
- page
- COMMENT|
-
- Test for a Numeric Data Processor -- Intel 8087 or 80287. The
- technique used is passive -- it leaves the NDP in the same state in
- which it is found.
-
- |
-
- CHECK_NDP:
- cli ; Protect FNSTENV
- fnstenv NDP_ENV ; If NDP present, save
- ; current environment,
- ; otherwise, this instruction
- ; is ignored
- mov cx,50/7 ; Cycle this many times
- loop $ ; Wait for result to be stored
- sti ; Allow interrupts
-
- fninit ; Initialize processor to known state
- jmp short $+2 ; Wait for initialization
-
- fnstcw NDP_CW ; Save control word
- jmp short $+2 ; Wait for result to be stored
- jmp short $+2
-
- cmp NDP_CW_HI,03h ; Check for NDP initial control word
- jne CPUID_EXIT ; No NDP installed
-
- int 11h ; Get equipment flags into AX
-
- test ax,mask I11_NDP ; Check NDP-installed bit
- jnz CHECK_NDP1 ; It's correctly set
-
- or bx,mask FLG_NERR ; Mark as in error
- CHECK_NDP1:
- and NDP_CW,not mask IEM ; Enable interrupts
- ; (IEM=0, 8087 only)
- fldcw NDP_CW ; Reload control word
- fdisi ; Disable interrupts (IEM=1) on 8087,
- ; ignored by 80287
- fstcw NDP_CW ; Save control word
- fldenv NDP_ENV ; Restore original NDP environment
- ; No need to wait
- ; for environment to be loaded
-
- test NDP_CW,mask IEM ; Check Interrupt Enable Mask
- ; (8087 only)
- jnz CPUID_8087 ; It changed, hence NDP is an 8087
-
- or bx,FLG_287 ; NDP is an 80287
- jmp short CPUID_EXIT ; Exit with flags in BX
- CPUID_8087:
- or bx,FLG_87 ; NDP is an 8087
- CPUID_EXIT:
- irp XX,<es,ds,di,cx,ax> ; Restore registers
- pop XX
- endm
- assume ds:nothing,es:nothing
-
- ret ; Return to caller
-
- CPUID endp ; End CPUID procedure
- subttl Check For NEC V20/V30
- page
- CHECK_NEC proc near
-
- COMMENT|
-
- The NEC V20/V30 CPUs are very compatible with the Intel 8088/8086.
- The only point of "incompatiblity" is that they do not contain a bug
- found in the Intel CPUs. Specifically, the NEC CPUs correctly restart
- an interrupted multi-prefix string instruction at the start of the
- instruction. The Intel CPUs incorrectly restart it in the middle of
- the instruction. This routine tests for that situation by executing
- such an instruction for a sufficiently long period of time for a timer
- interrupt to occur. If at the end of the instruction, CX is zero,
- it must be an NEC CPU; if not, it's an Intel CPU.
-
- Note that we're counting on the timer interrupt to do its thing
- every 18.2 times per second.
-
- Here's a worst case analysis: An Intel 8088/8086 executes 65535
- iterations of LODSB ES:[SI] in 2+9+13*65535 = 851,966 clock ticks. If
- the Intel 8088/8086 is running at 10 MHz, each clock tick is 100
- nanoseconds, hence the entire operation takes 85 milliseconds. If the
- timer is running at normal speed, it interrupts the CPU every 55
- millseconds and so should interrupt the repeated string instruction at
- least once.
-
- |
-
- mov cx,0FFFFh ; Move a lot of data
- sti ; Ensure timer enabled
-
- ; Execute multi-prefix instruction. Note that the value of ES as
- ; well as the direction flag setting is irrelevant.
-
- push ax ; Save registers
- push si
- rep lods byte ptr es:[si]
- pop si ; Restore
- pop ax
-
- ; On exit, if CX is zero, it's an NEC CPU, otherwise it's an Intel CPU
-
- ret ; Return to caller
-
- CHECK_NEC endp
- subttl Pre-fetch Instruction Queue Subroutine
- page
- PIQL_SUB proc near
-
- COMMENT|
-
- This subroutine attempts to discern the length of the CPU's
- pre-fetch instruction queue (PIQ).
-
- The technique used is to first ensure that the PIQ is full, then
- change an instruction which should be in a six-byte PIQ but not in a
- four-byte PIQ. Subsequently, if the original instruction is executed,
- the PIQ is six bytes long; if the new instruction is executed, the PIQ
- length is four.
-
- We ensure the PIQ is full by executing an instruction which takes
- long enough so that the Bus Interface Unit (BIU) can fill the PIQ
- while the instruction is executing.
-
- Specifically, for all but the last STOSB, we're simply marking time
- waiting for the BIU to fill the PIQ. The last STOSB actually changes
- the instruction. By that time, the original instruction should be in
- a six-byte PIQ but not a four-byte PIQ.
-
- |
-
- assume cs:PGROUP,es:PGROUP
-
- @REP equ 3 ; Repeat the store this many times
-
- std ; Store backwards
- mov di,offset es:LAB_INC+@REP-1 ; Change the instructions
- ; at ES:DI
- ; and preceding
- mov al,ds:LAB_STI ; Change to a STI
- mov cx,@REP ; Give the BIU time
- ; to pre-fetch instructions
- cli ; Ensure interrupts are disabled,
- ; otherwise a timer tick
- ; could change the PIQ filling
- rep stosb ; Change the instruction
- ; During execution of this instruction the BIU
- ; is refilling the PIQ. The current
- ; instruction is no longer in the PIQ.
- ; Note at end, CX is 0
-
- ; The PIQ begins filling here
-
- cld ; Restore direction flag
- nop ; PIQ fillers
- nop
- nop
-
- ; The following instruction is beyond a four-byte-PIQ CPU's reach,
- ; but within that of a six-byte-PIQ CPU.
-
- LAB_INC label byte
- inc cx ; Executed only if PIQ length is 6
-
- LAB_STI label byte
- rept @REP-1
- sti ;; Restore interrupts
- endm
-
- ret ; Return to caller
-
- assume ds:nothing,es:nothing
-
- PIQL_SUB endp ; End PIQL_SUB procedure
-
- CODE ends ; End CODE segment
-
- if1
- %OUT Pass 1 complete
- else
- %OUT Pass 2 complete
- endif
-
- end INITIAL ; End CPUID module