home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Phoenix CD 2.0
/
Phoenix_CD.cdr
/
02a
/
pctj486.zip
/
CPUID.ASM
< prev
next >
Wrap
Assembly Source File
|
1985-12-23
|
17KB
|
620 lines
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:
Speed
System in MHz CPU NDP
--------------------------------------------
IBM PC AT 6 80286 80287
IBM PC AT 6 80286 none
IBM PC 4.77 8088 8087-3
IBM PC 4.77 faulty 8088 8087-3
IBM PC XT 4.77 8088 none
IBM PC XT 4.77 8088 8087-3
COMPAQ 4.77 8088 none
AT&T PC 6300 8 8086 8087-2
TANDY 2000 8 80186 none
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 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
|
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 808x
FLG_18 8018x
FLG_28 8028x
FLG_87 8087
FLG_287 80287
FLG_CERR Faulty CPU
FLG_NERR Faulty NDP switch setting
|
FLG record RSVD:9,FLG_NERR:1,FLG_CERR:1,FLG_287:1,FLG_87:1,FLG_NDP:0,FLG_28:1,FLG_18:1,FLG_08:0,FLG_PIQL:1,FLG_CPU:0
; CPU-related flags
FLG_CPU$ equ (mask FLG_28) or (mask FLG_18) or (mask FLG_08) or (mask FLG_PIQL)
; NDP-related flags
FLG_NDP$ equ (mask FLG_287) or (mask FLG_87)
; Error-related flags
FLG_CERR$ equ mask FLG_CERR
FLG_NERR$ equ mask FLG_NERR
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
; current CS
call L1 ; Push IP, jump to IRET
endm ; POPFF macro
TAB macro TYP
push bx ; Save for a moment
and bx,FLG_&TYP&$ ; Isolate flags
mov cl,FLG_&TYP ; Shift amount
shr bx,cl ; Shift to low-order
shl bx,1 ; X 2 to index table of words
mov dx,TYP&MSG_TAB[bx] ; DS:DX ==> desc. message
pop bx ; Restore
mov ah,09h ; Function code to display strng
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 3 equates give the current version number of
; this software, which is 1.42.
VERS_H equ '1'
VERS_T equ '4'
VERS_U equ '2'
; 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 '
db VERS_H,'.',VERS_T,VERS_U
db CR,LF,EOS
MSG_8088 db 'CPU is an 8088.',CR,LF,EOS
MSG_8086 db 'CPU is an 8086.',CR,LF,EOS
MSG_80188 db 'CPU is an 80188.',CR,LF,EOS
MSG_80186 db 'CPU is an 80186.',CR,LF,EOS
MSG_UNK db 'CPU is a maverick -- 80288??',CR,LF,EOS
MSG_80286 db 'CPU is an 80286.',CR,LF,EOS
CPUMSG_TAB label word
dw PGROUP:MSG_8088 ; 000 = 8088
dw PGROUP:MSG_8086 ; 001 = 8086
dw PGROUP:MSG_80188 ; 010 = 80188
dw PGROUP:MSG_80186 ; 011 = 80186
dw PGROUP:MSG_UNK ; 100 = ?
dw PGROUP:MSG_80286 ; 101 = 80286
NDPMSG_TAB label word
dw PGROUP:MSG_NDPX ; 00 = No NDP
dw PGROUP:MSG_8087 ; 01 = 8087
dw PGROUP:MSG_80287 ; 10 = 80287
MSG_NDPX db 'NDP is not present.',CR,LF,EOS
MSG_8087 db 'NDP is an 8087.',CR,LF,EOS
MSG_80287 db 'NDP is an 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 after a change to SS ***',CR,LF
db 'It should be replaced with a more recent 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 on this system, the corresponding',CR,LF
db 'system board switch is not properly set. 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:
8086
8088
80186
80188
80286
8087
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.
|
irp XX,<ax,cx,di,ds,es> ; Save registers
push XX
endm
; Test for 80286 -- this CPU first stores SP on stack, then
; decrements it. Earlier CPUs decrement then store.
mov bx,mask 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,mask FLG_18 ; Assume it's an 8018x
mov cl,32+1 ; 18x masks shift counts mod 32
; Note we cant 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,mask 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, the INC was not
; executed, hence PIQ length = 4
or bx,mask 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 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 -- 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 NDP 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 env.
; 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,mask FLG_287 ; NDP is an 80287
jmp short CPUID_EXIT ; Exit with flags in BX
CPUID_8087:
or bx,mask 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 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 an 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 = 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