From 5575d6e3fa8555ba7b6d7efdd29dbd311dbce27d Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Thu, 2 Apr 2015 23:18:13 -0700 Subject: [PATCH] Cleanup --- tests/pc/cpu/cpuid.asm | 1269 ++++++++++++++++++++-------------------- 1 file changed, 643 insertions(+), 626 deletions(-) diff --git a/tests/pc/cpu/cpuid.asm b/tests/pc/cpu/cpuid.asm index f28532fc9..99c8b8f30 100644 --- a/tests/pc/cpu/cpuid.asm +++ b/tests/pc/cpu/cpuid.asm @@ -1,628 +1,645 @@ +; Posted in comp.sys.ibm.pc by Michael A. Shiels 8/16/89 ; From https://groups.google.com/forum/#!searchin/comp.sys.ibm.pc/single-step$20interrupt/comp.sys.ibm.pc/irWPIdzmCHQ/SyqEtq9mqCEJ - title CPUID - Determine CPU & NDP Type - page 58,122 - name CPUID - -; -; 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, ; 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, ; 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 \ No newline at end of file + title CPUID - Determine CPU & NDP Type + page 58,122 + name CPUID + +; +; 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 struct +ARG_BP 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 CP & 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 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 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 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 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 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 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 CPU_ID ; 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 CPU_ID Procedure + page + +CPU_ID proc near ; start CPU_ID 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, ; 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 affect 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_STR.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, ; restore registers + pop XX + endm + + assume ds:nothing,es:nothing + ret ; return to caller +CPU_ID endp ; end CPU_ID 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 + + end INITIAL ; end CPU_ID module