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.
-
- 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 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 XT/286 6 Intel 80286 none
- IBM PC AT 6 Intel 80286 Intel 80287
- IBM PC AT 9 Intel 80286 Intel 80287-8
- IBM PC AT 6 Intel 80286 none
- IBM PC AT 8.5 Intel 80286 none
- IBM 3270 PC AT 6 Intel 80286 none
- COMPAQ Portable 4.77 Intel 8088 none
- COMPAQ Portable 4.77 NEC V20 none
- COMPAQ 286 8 Intel 80286 none
- COMPAQ 386 16 Intel 80386 Intel 80827-8
- 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
- HP 150 4.77 Intel 8088 none
- Zenith Z-160 4.77 NEC V20 none
- HP Vectra RS/20 20 Intel 80386 Intel 80387, Weitek 1167
-
- * = with faulty CPU
-
- Program structure:
- Group PGROUP:
- Stack segment STACK, byte-aligned, stack, class 'prog'
- Program segment CODE, word-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-3164
- 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 8086/8088s
-
- David E. Michener 28 Jun 86 Use /z to avoid INT 11h if
- on a machine which doesn't
- support interrupts below 20h
-
- Bob Smith 15 Sep 86 Distinguish 286 from 386
-
- Bob Smith 6 Oct 86 Return length of PIQ
-
- Dan Lewis 15 Dec 86 Use /r to allow NDP instructions
- on 286/386 machines w/o NDP
-
- Bob Smith 17 Jun 87 Distinguish 287 from 387
-
- Bob Smith 24 Nov 87 Detect Weitek 1167
-
- |
-
- 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_38 Intel 8038x
-
- FLG_87 Intel 8087
- FLG_287 Intel 80287
- FLG_387 Intel 80387
-
- FLG_1167 Weitek 1167
-
- $FLG_CERR Faulty CPU
- $FLG_NERR Faulty NDP switch setting
-
- |
-
- FLG record $RSVD:6,$FLG_NERR:1,$FLG_CERR:1,$FLG_WTK:1,$FLG_NDP:3,$FLG_CPU:4
-
- ; CPU-related flags
-
- FLG_PIQL equ 0001b shl $FLG_CPU
- FLG_08 equ 0000b shl $FLG_CPU
- FLG_NEC equ 0010b shl $FLG_CPU
- FLG_18 equ 0100b shl $FLG_CPU
- FLG_28 equ 0110b shl $FLG_CPU
- FLG_38 equ 1000b 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
- FLG_80386 equ FLG_38 or FLG_PIQL
-
- ; NDP-related flags
-
- FLG_NDPX equ 000b shl $FLG_NDP ; Not present
- FLG_NDPU equ 001b shl $FLG_NDP ; Untested
- FLG_87 equ 010b shl $FLG_NDP
- FLG_287 equ 011b shl $FLG_NDP
- FLG_387 equ 100b shl $FLG_NDP
-
- FLG_1167 equ mask $FLG_WTK
-
- 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,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 at DS:DX
- int 21h ; Request DOS service
-
- endm ; TAB macro
-
- REGSAVE macro LIST ; Register save macro
-
- irp XX,<LIST>
- push XX
- endm
-
- endm ; REGSAVE
-
- REGREST macro LIST ; Register restore macro
-
- irp XX,<LIST>
- pop XX
- endm
-
- endm ; REGREST
- page
- INT_VEC segment at 0 ; Start INT_VEC segment
-
- org 4*01h
- INT01_OFF dw ? ; Pointer to INT 01h
- INT01_SEG dw ?
-
- INT_VEC ends ; End INT_VEC segment
-
- PGROUP group STACK,CODE,DATA,MDATA
-
- include VERSION.INC
-
- ; 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_ENV dw 7 dup (?) ; Save area for NDP environment
-
- NDP_CW label word ; Save area for NDP control word
- db ?
- NDP_CW_HI db 0 ; High byte of control word
- db 15 dup (?) ; Room for the CPU to step on
-
- 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
-
- LCL_FLAGS dw 0 ; Local flags
- @LCL_REAL equ 8000h ; Use real code to detect 287 or 387 coprocessors
- @LCL_I11H equ 4000h ; Avoid INT 11h on machines which don't support it
-
- LCL_OPTS db 'rb' ; Table of valid option letters
- NLCL_OPTS equ $-LCL_OPTS ; # valid ...
-
- LCL_ACTS dw PGROUP:CHECK_ARGS_REAL ; Table of corresponding actions
- dw PGROUP:CHECK_ARGS_I11H
-
- PIQL dw 0 ; Length of prefetch instruction queue
- PIQL_CNT dw 10 ; Retry count to ensure valid PIQL
-
- MSG_PIQL db 'The length of the pre-fetch instruction queue is '
- MSG_PIQL1 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_UNK2 db 'CPU is an Intel 80286',CR,LF,EOS
- MSG_80286 db 'CPU is an Intel 80286.',CR,LF,EOS
- MSG_UNK3 db 'CPU is an Intel 80386',CR,LF,EOS
- MSG_80386 db 'CPU is an Intel 80386.',CR,LF,EOS
-
- CPUMSG_TAB label word
- dw PGROUP:MSG_8088 ; 0000 = Intel 8088
- dw PGROUP:MSG_8086 ; 0001 = Intel 8086
- dw PGROUP:MSG_V20 ; 0010 = NEC V20
- dw PGROUP:MSG_V30 ; 0011 = NEC V30
- dw PGROUP:MSG_80188 ; 0100 = Intel 80188
- dw PGROUP:MSG_80186 ; 0101 = Intel 80186
- dw PGROUP:MSG_UNK2 ; 0110 = ?
- dw PGROUP:MSG_80286 ; 0111 = Intel 80286
- dw PGROUP:MSG_UNK3 ; 1000 = ?
- dw PGROUP:MSG_80386 ; 1001 = Intel 80386
-
- NDPMSG_TAB label word
- dw PGROUP:MSG_NDPX ; 000 = No NDP
- dw PGROUP:MSG_NDPU ; 001 = NDP untested
- dw PGROUP:MSG_8087 ; 010 = Intel 8087
- dw PGROUP:MSG_80287 ; 011 = Intel 80287
- dw PGROUP:MSG_80387 ; 100 = Intel 80387
-
-
- WTKMSG_TAB label word
- dw PGROUP:MSG_EMPTY ; 0 = No Weitek 1167
- dw PGROUP:MSG_1167 ; 1 = Weitek 1167
-
- MSG_NDPX db 'NDP is not present.',CR,LF,EOS
- MSG_NDPU db 'NDP is untested.',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
- MSG_80387 db 'NDP is an Intel 80387.',CR,LF,EOS
- MSG_1167 db 'NDP is a Weitek 1167.',CR,LF,EOS
- MSG_EMPTY db EOS
-
- CERRMSG_TAB label word
- dw PGROUP:MSG_CPUOK ; 0 = CPU healthy
- dw PGROUP:MSG_CPUBAD ; 1 = CPU faulty
-
- MSG_CPUOK db EOS ; No message
- 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 '*** The system board switch which indicates whether or not there is',CR,LF
- db 'an NDP installed in the system is not properly set. To correct this,',CR,LF
- db 'flip switch 2 of switch block 1 on the system board.',CR,LF,EOS
-
- MSG_REAL db '══> Using real code to detect math coprocessor...',CR,LF,EOS
- MSG_I11H db '══> Bypassing INT 11h test of system board switch...',CR,LF,EOS
-
- MSG_HELP db "CPUID.COM determines the CPU and NDP models in use. In particular, it can",CR,LF
- db "distinguish from each other any of the following CPUs and NDPs:",CR,LF
- db CR,LF
- db " Intel 8086, 8087, 8088, 80186, 80188, 80286, 80287, 80386, 80387",CR,LF
- db " NEC V20, V30",CR,LF
- db CR,LF
- db "It also reports on faulty 8086/8088s. Such a CPU is faulty if it allows an",CR,LF
- db "interrupt immediately after a change to the SS register. If the program",CR,LF
- db "indicates you have a faulty CPU, you should replace it. The program also",CR,LF
- db "checks on the system board's NDP switch in case there is an NDP installed.",CR,LF
- db CR,LF
- db "The program's syntax is as follows:",CR,LF
- db " CPUID to run the program",CR,LF
- db " CPUID /r to run the program using real code to detect a 287 or 387",CR,LF
- db " CPUID /b to run the program but bypass the INT 11h test for correctly",CR,LF
- db " set system board NDP switch",CR,LF
- db CR,LF
- db "If the program reports results at variance with your knowledge of the system,",CR,LF
- db "please contact the author:",CR,LF
- db " Bob Smith 301-469-8848",CR,LF
- db " Qualitas, Inc.",CR,LF
- db " 8314 Thoreau Dr.",CR,LF
- db " Bethesda, MD 20817-3164",CR,LF
- db EOS
-
- MDATA ends ; End MDATA segment
- subttl Main Routine
- page
- CODE segment word public 'prog' ; Start CODE segment
- assume cs:PGROUP
-
- org 100h ; Skip over PSP
- INITIAL proc near
- assume ds:PGROUP,es:PGROUP
-
- lea dx,MSG_START ; Starting message
- mov ah,09h ; Function code to display string at DS:DX
- int 21h ; Request DOS service
-
- call CHECK_ARGS ; Check command line for parameters
-
- call CPUID ; Check the CPU's identity
-
- TAB CPU ; Display CPU results
- TAB NDP ; Display NDP results
- TAB WTK ; Display Weitek results
- TAB CERR ; Display CPU ERR results
- TAB NERR ; Display NDP ERR results
-
- mov ax,PIQL ; Get length of PIQ
- aam ; Convert to decimal digits
- add ax,'00' ; Convert to ASCII
- mov MSG_PIQL1,ah ; Save high-order byte
- mov MSG_PIQL1+1,al ; low-order
- lea dx,MSG_PIQL ; DS:DX ==> string to display
- mov ah,09h ; Function code to display string at DS:DX
- int 21h ; Request DOS service
-
- mov ax,PIQL ; Get length of PIQ
- mov ah,4Ch ; Function call to exit with return code
- int 21h ; Return to DOS
-
- assume ds:nothing,es:nothing
-
- INITIAL endp ; End INITIAL procedure
- subttl CHECK_ARGS Procedure
- page
- CHECK_ARGS proc near ; Start CHECK_ARGS procedure
- assume ds:PGROUP,es:PGROUP
- COMMENT|
-
- Check the command line for parameters.
-
- The following parameters are allowed:
-
- /r Use real code to detect 287 or 387 math coprocessor
-
- |
-
- REGSAVE <ax,dx,si> ; Save registers
-
- mov si,81h ; DS:SI ==> command line parameters
- CHECK_ARGS_NEXT:
- call SKIP_WHITE ; Skip over white space
-
- lodsb ; Get next character
-
- cmp al,CR ; Check for end-of-line
- je CHECK_ARGS_EXIT ; That's all folks
-
- cmp al,'/' ; Option separator?
- jne CHECK_ARGS_ERR ; No, give 'em help
-
- lodsb ; Get option letter
- or al,20h ; Convert to lowercase
-
- lea di,LCL_OPTS ; ES:DI ==> string of valid option letters
- mov cx,NLCL_OPTS ; Length of above string
- repne scasb ; Search for it
- jne CHECK_ARGS_ERR ; Not present, give 'em help
-
- sub di,1+offset es:LCL_OPTS ; Convert to origin-0
- shl di,1 ; Times two to index table of words
- jmp LCL_ACTS[di] ; Take appropriate action
-
- CHECK_ARGS_REAL:
- lea dx,MSG_REAL ; DS:DX ==> message for this flag
- mov ah,09h ; Function code to display string at DS:DX
- int 21h ; Request DOS service
-
- or LCL_FLAGS,@LCL_REAL ; Mark as present
- jmp CHECK_ARGS_NEXT ; Go around again
-
- CHECK_ARGS_I11H:
- lea dx,MSG_I11H ; DS:DX ==> message for this flag
- mov ah,09h ; Function code to display string at DS:DX
- int 21h ; Request DOS service
-
- or LCL_FLAGS,@LCL_I11H ; Mark as present
- jmp CHECK_ARGS_NEXT ; Go around again
-
- CHECK_ARGS_ERR:
- lea dx,MSG_HELP ; DS:DX ==> help message
- mov ah,09h ; Function code to display string at DS:DX
- int 21h ; Request DOS service
-
- mov ax,4CFFh ; Function call to exit with return code
- int 21h ; Return to DOS
-
- CHECK_ARGS_EXIT:
- REGREST <si,dx,ax> ; Restore
-
- ret ; Return to caller
-
- assume ds:nothing,es:nothing
-
- CHECK_ARGS endp ; End CHECK_ARGS procedure
- subttl SKIP_WHITE Procedure
- page
- SKIP_WHITE proc near ; Start SKIP_WHITE procedure
- assume ds:PGROUP,es:PGROUP
- COMMENT|
-
- Skip over white space.
-
- On entry:
-
- DS:SI ==> next character (possibly white space)
-
- On exit:
-
- DS:SI ==> next character not white space
-
- |
-
- lodsb ; Get next character
-
- cmp al,' ' ; Check for blanks
- je SKIP_WHITE ; Go around again
-
- cmp al,TAB ; Check for TABs
- je SKIP_WHITE ; Go around again
-
- dec si ; Back off to non-white character
-
- ret ; Return to caller
-
- assume ds:nothing,es:nothing
-
- SKIP_WHITE endp ; End SKIP_WHITE procedure
- subttl CPUID Procedure
- page
- CPUID proc near ; Start CPUID procedure
- assume 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 80386
- Intel 8087
- Intel 80287
- Intel 80387
-
- 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
-
- |
-
- REGSAVE <ax,cx,di,ds,es> ; Save registers
-
- ; Test for 80286/386 -- these CPUs execute PUSH SP by first storing SP on stack,
- ; then decrementing it. Earlier CPUs first decrement then store.
-
- push sp ; Only 286 pushes pre-push SP
- pop ax ; Get it back
-
- cmp ax,sp ; Check for same
- jne CHECK_18x ; They aren't, try next class
-
- call DIST_286or386 ; Distinguish a 286 from 386
- jmp short CHECK_PIQL ; Join common code
-
- ; Test for 80186/80188 -- 18x and later CPUs mask shift/rotate operations
- ; mod 32; earlier CPUs use all 8 bits of CL.
-
- CHECK_18x:
- 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 or later;
- ; 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.
-
- To overcome write pipelining in 286/386 chips, the largest value
- over several executions of the subroutine is used.
-
- |
-
- CHECK_PIQL:
- call PIQL_SUB ; Handled via subroutine
-
- cmp cx,PIQL ; Use the larger of the two
- jbe CHECK_PIQL1 ; CX is smaller
-
- mov PIQL,cx ; Save to report on later
- CHECK_PIQL1:
- dec PIQL_CNT ; One fewer times through the loop
- jnz CHECK_PIQL ; Jump if
-
- cmp PIQL,4 ; Check PIQL
- jbe CHECK_ERR ; Jump if xxxx8
-
- or bx,FLG_PIQL ; PIQ length is 5 or longer
- subttl Check For Allowing Interrupts After POP SS
- page
-
- ; Test for faulty chip (allows interrupts after change to SS register)
-
- CHECK_ERR:
- call ERR_SUB ; Handled via subroutine
- 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
- CHECK_NDP:
- call NDP_SUB ; Handled via subroutine
-
- REGREST <es,ds,di,cx,ax> ; Restore registers
- assume ds:nothing,es:nothing
-
- ret ; Return to caller
-
- assume ds:nothing,es:nothing
-
- CPUID endp ; End CPUID procedure
- subttl Distinguish A 286 From 386
- page
- DIST_286or386 proc near
- assume ds:PGROUP,es:PGROUP
- COMMENT|
-
- The test for 286 vs. 386 is done by attempting to set flag bits in
- the high-order nibble of the flag word. If that's successful, it's a
- 386; otherwise it's a 286.
-
- |
-
- REGSAVE <ax> ; Save register
-
- pushf ; Save flags for a moment
-
- mov ax,0F000h ; Try to set high bits in flag register
-
- push ax ; Move into flag register
- popf
-
- pushf ; Get flags back into AX
- pop ax
-
- popf ; Restore original flags
-
- test ax,0F000h ; Any bits set?
- jz ITSA286 ; No, so it's a 286
-
- or bx,FLG_38 ; It's a 38x
- jmp short DIST_286or386_EXIT ; Join common exit code
- ITSA286:
- or bx,FLG_28 ; It's a 28x
- DIST_286or386_EXIT:
- REGREST <ax> ; Restore
-
- ret ; Return to caller
-
- assume ds:nothing,es:nothing
-
- DIST_286or386 endp ; End DIST_286or386 procedure
- subttl Check For NEC V20/V30
- page
- CHECK_NEC proc near
- assume ds:PGROUP,es:PGROUP
- COMMENT|
-
- The NEC V20/V30 CPUs are very compatible with the Intel 8086/8088.
- 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 8086/8088 executes 65535
- iterations of LODSB ES:[SI] in 2+9+13*65535 = 851,966 clock ticks. If
- the Intel 8086/8088 is running at 15 MHz, each clock tick is 66.67
- nanoseconds, hence the entire operation takes 56.8 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
-
- assume ds:nothing,es:nothing
-
- CHECK_NEC endp
- subttl Pre-fetch Instruction Queue Subroutine
- page
- PIQL_SUB proc near
- assume ds:PGROUP,es:PGROUP
- COMMENT|
-
- This subroutine attempts to discern the length of the CPU's
- pre-fetch instruction queue (PIQ).
-
- It stores a new instruction into the instruction stream
- following the STOSB. The loop proceeds backwards from the end
- of the stream to the beginning. At the point the inserted
- instruction is not executed, we have found the last byte in
- the PIQ. The value in CX at that time is then the PIQ length.
-
- |
-
- REGSAVE <ax,bx,dx,si,di> ; Save registers
-
- @REP equ 64 ; Maximum length of PIQ
- ; we can handle
-
- std ; Store backwards
-
- mov cx,@REP ; Loop counter
- lea di,LAB_NOP+@REP-1 ; ES:DI ==> PIQL last byte
- ; in fill area
-
- mov al,ds:LAB_INC ; Change to INC BX
- mov ah,ds:LAB_NOP ; Save a NOP here to restore
- mov si,1 ; Divisor
- xor dx,dx ; Zero high-order word for divide
- cli ; Ensure interrupts are disabled, otherwise
- ; a timer tick could disturb the PIQ filling
- even ; Ensure word alignment for LAB_FILL
- nop
- PIQL_SUB_NEXT:
- xor bx,bx ; Initialize flag
- div si ; Take up some time and
- ; refill the queue
- stosb ; Change the instruction
-
- ; The PIQ begins filling here
-
- LAB_NOP label byte
- rept @REP
- nop ;; Fill byte
- endm
-
- mov es:[di+1],ah ; Restore the NOP
-
- and bx,bx ; Did we execute it?
- loopnz PIQL_SUB_NEXT ; Go around again if we did
- ; and loop not finished
- inc cx ; Count in last byte
-
- sti ; Restore interrupts
- cld ; Restore direction flag
-
- REGREST <di,si,dx,bx,ax> ; Restore
-
- ; At the end, CX has the length of the PIQ
-
- ret ; Return to caller
-
- LAB_INC label byte
- inc bx ; Increment counter
-
- assume ds:nothing,es:nothing
-
- PIQL_SUB endp ; End PIQL_SUB procedure
- subttl Check For Faulty Interrupts
- page
- ERR_SUB proc near
- assume ds:PGROUP,es:PGROUP
- COMMENT|
-
- Test for faulty chip (allows interrupts after change to SS register).
- Setup a handler for INT 01h (single-step interrupt) and turn on that
- flag just before executing a POP SS. If the CPU allows a single-step
- interrupt after the POP SS, it's faulty.
-
- On exit:
-
- CX = 1 if CPU is faulty
- = 0 if OK
-
- |
-
- REGSAVE <ax,ds> ; Save registers
-
- 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
-
- lea ax,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
-
- push es ; Save for a moment
- 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
- pop es ; Restore
- assume es:PGROUP ; Tell the assembler
-
- sti ; Allow interrupts again (IF=1)
-
- add sp,3*2 ; Strip IP, CS, and Flags from stack
-
- REGREST <ds,ax> ; Restore
- assume ds:PGROUP ; Tell the assembler
-
- ret ; Return to caller
-
- assume ds:nothing,es:nothing
-
- ERR_SUB endp ; End ERR_SUB procedure
- subttl Check For Numeric Data Processor
- page
- NDP_SUB proc near
- assume ds:PGROUP,es:PGROUP
- COMMENT|
-
- Test for a Numeric Data Processor -- Intel 8087, 80287, or 80387.
- An 8087 allows FDISI, an 80287/80387 ignores it. The 80287 and 80387
- can be distinguished through their different treatment of the infinity
- closure setting.
-
- In general, the technique used is passive -- it leaves the NDP in
- the same state in which it is found.
-
- Unfortunately, some IBM PC/ATs and 3270/ATs without an NDP don't
- handle floating-point instructions correctly. In particular, when
- no-WAIT NDP instruction is executed on those systems, they wipe out
- the memory location and all bytes following in the same segment. To
- overcome this bug, Dan Lewis has suggested a technique which computes
- the segment and offset of the location into which the store is made
- such that the offset is in the last paragraph of the segment. This
- way, the wipe out is harmless.
-
- On exit:
-
- BX = $FLG_NDP & $FLG_NERR bits set as appropriate.
-
- |
-
- REGSAVE <ax,cx,di> ; Save registers
-
- call CHECK_1167 ; See if there's a Weitek 1167 in the system
-
- ; Because some IBM PC/ATs and 3270/ATs without an NDP don't handle
- ; floating-point instructions correctly, we check for a 286 explicitly
- ; and rely upon the equipment flags to tell us if there's an NDP installed.
- ; This behavior is also present on some 386s.
-
- test LCL_FLAGS,@LCL_REAL ; Use real code or not?
- jnz NDP_SUB1 ; It's real
-
- mov ax,bx ; Copy CPUID bits for destructive testing
- and ax,(mask $FLG_CPU) and not FLG_PIQL ; Isolate CPU bits
-
- cmp ax,FLG_28 ; Izit a 28x?
- je NDP_SUB0 ; Yes, skip NDP instructions
-
- cmp ax,FLG_38 ; Izit a 38x?
- jne NDP_SUB1 ; Not this time
- NDP_SUB0:
- test LCL_FLAGS,@LCL_I11H ; Skip INT 11h test?
- jnz NDP_SUB_UN ; Yes
-
- int 11h ; Get equipment flags into AX
-
- test ax,mask $I11_NDP ; Check NDP-installed bit
- jz NDP_SUB_EXIT0 ; Not installed
-
- call DIST_287or387 ; Distinguish a 287 from a 387
- NDP_SUB_EXIT0:
- jmp NDP_SUB_EXIT ; Join common exit code
-
- NDP_SUB_UN:
- or bx,FLG_NDPU ; Mark as untested
-
- jmp NDP_SUB_EXIT ; Join common exit code
-
- NDP_SUB1:
- push es ; Save for a moment
-
- lea di,NDP_ENV+(size NDP_ENV)-1 ; Offset of end of environment
- call MAX_OFFSET ; Return with ES:DI ==> NDP_ENV and DI largest
- assume es:nothing
- sub di,(size NDP_ENV)-1 ; Back off to start of NDP_ENV
-
- cli ; Protect FNSTENV
- fnstenv es:[di] ; If NDP present, save current environment,
- ; otherwise, this instruction is ignored
- sti ; Allow interrupts
-
- pop es ; Restore
- assume es:PGROUP
-
- mov cx,50/7 ; Cycle this many times
- loop $ ; Wait for result to be stored
-
- fninit ; Initialize processor to known state
- jmp short $+2 ; Wait for initialization
-
- push es ; Save for a moment
-
- lea di,NDP_CW+(size NDP_CW)-1 ; Offset of end of control word
- call MAX_OFFSET ; Return with ES:DI ==> NDP_CW and DI largest
- assume es:nothing
- sub di,(size NDP_CW)-1 ; Back off to start of control word
-
- fnstcw es:[di] ; Save control word
-
- pop es ; Restore
- assume es:PGROUP
-
- jmp short $+2 ; Wait for result to be stored
- jmp short $+2
-
- and NDP_CW,not mask $IC ; Turn off infinity control in case of 387
-
- cmp NDP_CW_HI,03h ; Check for NDP initial control word
- jne NDP_SUB_NONE ; No NDP installed
-
- test LCL_FLAGS,@LCL_I11H ; Skip INT 11h test?
- jnz NDP_SUB2 ; Yes
-
- int 11h ; Get equipment flags into AX
-
- test ax,mask $I11_NDP ; Check NDP-installed bit
- jnz NDP_SUB2 ; It's correctly set
-
- or bx,mask $FLG_NERR ; Mark as in error
- NDP_SUB2:
- 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/80387
- 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 NDP_SUB_8087 ; It changed, hence NDP is an 8087
-
- call DIST_287or387 ; Distinguish a 287 from a 387
-
- jmp short NDP_SUB_EXIT ; Exit with flags in BX
- NDP_SUB_8087:
- or bx,FLG_87 ; NDP is an 8087
-
- jmp short NDP_SUB_EXIT ; Join common exit code
- NDP_SUB_NONE:
- test LCL_FLAGS,@LCL_I11H ; Skip INT 11h test?
- jnz NDP_SUB_EXIT ; Yes
-
- int 11h ; Get equipment flags into AX
-
- test ax,mask $I11_NDP ; Check NDP-installed bit
- jz NDP_SUB_EXIT ; It's correctly set
-
- or bx,mask $FLG_NERR ; Mark as in error
- NDP_SUB_EXIT:
- REGREST <di,cx,ax> ; Restore
-
- ret ; Return to caller
-
- assume ds:nothing,es:nothing
-
- NDP_SUB endp ; End NDP_SUB procedure
- subttl Check For Weitek 1167
- page
- CHECK_1167 proc near
- assume ds:PGROUP,es:PGROUP
- COMMENT|
-
- See if there's a Weitek 1167 in the system.
-
- To determine that, clear EAX, call INT 11h, and test bit 24
- in EAX. If set, the coprocessor is present; if not, then
- it's not. Obviously, we must be running on a 386.
-
- |
-
- test LCL_FLAGS,@LCL_I11H ; Skip INT 11h test?
- jnz CHECK_1167_EXIT ; Yes, 1167 untested
-
- test bx,FLG_38 ; Are we on a 38x?
- jz CHECK_1167_EXIT ; No, thus no 1167
-
- db 66h ; Use EAX
- push ax ; Save for a moment
-
- db 66h ; Use EAX
- xor ax,ax ; Clear entire register
-
- int 11h ; Get equipment flags
-
- db 66h ; Use EAX
- test ax,0000h
- dw 0100h ; Test bit 24
- jz CHECK_1167_EXIT0 ; Not present
-
- or bx,FLG_1167 ; Mark as present
- CHECK_1167_EXIT0:
- db 66h ; Use EAX
- pop ax ; Restore
- CHECK_1167_EXIT:
- ret ; Return to caller
-
- assume ds:nothing,es:nothing
-
- CHECK_1167 endp ; End CHECK_1167 procedure
- subttl Distinguish A 287 From 387
- page
- DIST_287or387 proc near
- assume ds:PGROUP,es:PGROUP
- COMMENT|
-
- Distinguish a 287 from a 387.
-
- Both the 80287 and 80387 are initialized with the infinity closure
- bit set to one. However, only the 80287 is sensitive to the value of
- this bit. Ordinarily when this bit is set to one, the chip uses
- projective closure; when it is cleared to zero, the chip uses affine
- closure. Thus the 80287 is initialized to use projective closure, but
- that state can be changed through the infinity closure bit in the
- control word. On the other hand, the 80387 is initialized to use
- affine closure and remains in that state independent of the setting of
- the infinity closure bit. The two NDPs can be distinguished by
- executing code which is sensitive to the setting of the infinity
- closure bit.
-
- The algorithm used is based upon one published by Intel on how to
- detect the 80387.
-
- On exit:
-
- BX = $FLG_NDP bits set as appropriate.
-
- |
-
- .287
- fstenv NDP_ENV ; Save current environment
- finit ; Initialize processor to known state
- ; The 80287 is using projective
- ; closure for arithmetic, the 80387 is
- ; using affine closure
-
- fld1 ; Generate infinity
- fldz ; by dividing zero into one
- fdiv ; ST0 = +infinity
- fld st(0) ; Copy it
- fchs ; ST0 = -infinity, ST1 = +infinity
- fcompp ; Compare them and pop both from stack
- fstsw ax ; Get status word
- fldenv NDP_ENV ; Restore original NDP environment
- sahf ; Copy into flags
- jz DIST_287or387_PROJ ; Jump if the two are equal
- ; (projective closure)
-
- or bx,FLG_387 ; NDP is an 80387
-
- jmp short DIST_287or387_EXIT ; Join common exit code
-
- DIST_287or387_PROJ:
- or bx,FLG_287 ; NDP is an 80287
- DIST_287or387_EXIT:
- ret ; Return to caller
- .8087
- assume ds:nothing,es:nothing
-
- DIST_287or387 endp ; End DIST_287or387 procedure
- subttl Calculate Maximum Offset
- page
- MAX_OFFSET proc near
- assume ds:nothing,es:nothing
- COMMENT|
-
- On entry:
-
- ES:DI ==> memory offset
-
- On exit:
-
- ES:DI ==> same location but with DI as large as possible
-
- |
-
- MAXOFF equ 0FFF0h
-
- REGSAVE <ax,cx> ; Save registers
-
- push di
-
- mov cl,4 ; Shift amount
- shr di,cl ; Isolate para of control word
- mov ax,es ; Get current segment
- add ax,di ; Add in segment of control word
- sub ax,MAXOFF shr 4 ; Less a lot of paras
- mov es,ax ; Save as segment of control word
-
- pop di ; Restore
-
- or di,MAXOFF ; Include paras subtracted out above
-
- REGREST <cx,ax> ; Restore
-
- ret ; Return to caller
-
- assume ds:nothing,es:nothing
-
- MAX_OFFSET endp ; End MAX_OFFSET procedure
-
- CODE ends ; End CODE segment
-
- if1
- %OUT Pass 1 complete
- else
- %OUT Pass 2 complete
- endif
-
- end INITIAL ; End CPUID module