Coherent4.2.10/tboot/cpuid.m
/
/ 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
CPUID proc near / Start CPUID procedure
/ 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
/
push ax / Save registers
push cx
push di
push ds
push es
/ 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
/ 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
pop es / Restore registers
pop ds
pop di
pop cx
pop ax
ret / Return to caller
CPUID endp / End CPUID procedure
/ subttl Distinguish A 286 From 386
/ page
DIST_286or386 proc near
/ 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.
push 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:
pop ax / Restore
ret / Return to caller
DIST_286or386 endp / End DIST_286or386 procedure
/ subttl Check For NEC V20/V30
/ page
CHECK_NEC proc near
/ 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
CHECK_NEC endp
/ subttl Pre-fetch Instruction Queue Subroutine
/ page
PIQL_SUB proc near
/ 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.
push ax / Save registers
push bx
push dx
push si
push di
@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
pop di / Restore
pop si
pop dx
pop bx
pop ax
/ At the end, CX has the length of the PIQ
ret / Return to caller
LAB_INC label byte
inc bx / Increment counter
PIQL_SUB endp / End PIQL_SUB procedure
/ subttl Check For Faulty Interrupts
/ page
ERR_SUB proc near
/ 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
push ax / Save registers
push ds
xor ax,ax / Prepare to address interrupt vector segment
mov ds,ax / DS points to segment 0
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
mov INT01_OFF,ax / Restore offset
mov INT01_SEG,es / ...and segment
pop es / Restore
sti / Allow interrupts again (IF=1)
add sp,3*2 / Strip IP, CS, and Flags from stack
pop ds / Restore
pop ax
ret / Return to caller
ERR_SUB endp / End ERR_SUB procedure
/ subttl Check For Numeric Data Processor
/ page
NDP_SUB proc near
/ 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.
push ax / Save registers
push cx
push di
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
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
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
sub di,(size NDP_CW)-1 / Back off to start of control word
fnstcw es:[di] / Save control word
pop es / Restore
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:
pop di / Restore
pop cx
pop ax
ret / Return to caller
NDP_SUB endp / End NDP_SUB procedure
/ subttl Check For Weitek 1167
/ page
CHECK_1167 proc near
/ 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
CHECK_1167 endp / End CHECK_1167 procedure
/ subttl Distinguish A 287 From 387
/ page
DIST_287or387 proc near
/ 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
DIST_287or387 endp / End DIST_287or387 procedure
/ subttl Calculate Maximum Offset
/ page
MAX_OFFSET proc near
/ On entry:
/
/ ES:DI ==> memory offset
/
/ On exit:
/
/ ES:DI ==> same location but with DI as large as possible
MAXOFF equ 0FFF0h
push ax / Save registers
push cx
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
pop cx / Restore
pop ax
ret / Return to caller
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