; 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 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,<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 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,<es,ds,di,cx,ax>	; 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
