schuler@psuvax1.UUCP (David W. Schuler) (04/13/86)
This is the source code for CPUID, a program which will determine the cpu type and numeric coprocessor type in an IBM-PC (or compatible). This is the source code directly from the April 1986 issue of PC Tech Journal. --- David W. Schuler {akgua,allegra,ihnp4}!psuvax1!gondor!schuler Penn State University schuler%gondor@penn-state.csnet 602 Sproul Bldg. schuler%psuvaxg.bitnet@wiscvm.arpa University Park, PA {akgua,allegra,ihnp4}!psuvax1!tvw@psuvma.bitnet schuler@gondor.uucp : schuler@psuvaxg.bitnet --- Are you a turtle? ==========================cut here======================================= title CPUID - Determine CPU & NDP Type page 58,122 name CPUID ; :ts=8 /* for MY editor only */ ; ; CPUID uniquely identifies each NEC & Intel CPU & NDP. ; ; 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 apprear 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 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. 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 ; IBM PC Portable 4.77 NEC V20 none ; 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. ; ; Author: ; ; Original code by: Bob Smith May 1985 ; Qualitas, Inc. ; 8314 Thoreau Dr. ; Bethesda, MD 20817 ; ; 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. ; ; Published in PC Tech Journal - April 1986 - Vol 4 No 4 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 RO: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 ; 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 ; with 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 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,CR,LF,EOS MSG_8088 db 'CPU is an Intel 8088.' db CR,LF,EOS MSG_8086 db 'CPU is an Intel 8086.' db CR,LF,EOS MSG_V20 db 'CPU is an NEC V20.' db CR,LF,EOS MSG_V30 db 'CPU is an NEC V30.' db CR,LF,EOS MSG_80188 db 'CPU is an Intel 80188.' db CR,LF,EOS MSG_80186 db 'CPU is an Intel 80186.' db CR,LF,EOS MSG_UNK db 'CPU is a maverick -- 80288??.' db CR,LF,EOS MSG_80286 db 'CPU is an Intel 80286.' db 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.' db CR,LF,EOS MSG_8087 db 'NDP is an Intel 8087.' db CR,LF,EOS MSG_80287 db 'NDP is an Intel 80287.' db 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.' db 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 sytem, 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 ; 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 register segment. 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. Such a discrepancy is reported. ; ; 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 CPU's 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 -- 18xx and 286 CPU's 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 its a 18x, check PIQL ; test for V20 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 ; Check the length of the pre-fetch instruction queue (PIQ). ; ; xxxx6 CPUs have a PIQ length of 6 bytes, ; xxxx8 CPUs have a PIQ length of 4 bytes ; ; Self-modifying code is used to distinguish the two PIQ lengths. CHECK_PIQL: call PIQL_SUB ; handle 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 occuring 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 cpu's 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 INTO1_DONE ; we're done iret ; return to caller INTO1_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 ; 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 falgs 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 ; The NEC V20/V30 are very compatible with the Intel 8086/8088. ; The only point of "incompatibility" is that they do not contain ; a bug found in the Intel CPU's. Specifically, the NEC CPU's ; correctly restart an interrupted multi-prefix string instruction ; at the start of the instruction. The Intel CPU's incorrectly ; restart 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 ; 55ms 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 ; This subroutine discerns 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 6-byte PIQ but not in a ; 4-byte PIQ. Then, if the original instruction is executed, the PIQ ; is 6-bytes long; if the new instruction is executed, PIQ length is 4. ; ; We ensure the PIQ is full be 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 byt the last STOSB, we're simple marking time ; waiting for the BIU to fill the PIQ. The last STOSB actually changes ; the instruction. By that time, the orignial instruction should be in ; a six-byte PIQ byt 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