/ / 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