Path: utzoo!utgpu!news-server.csri.toronto.edu!cs.utexas.edu!tut.cis.ohio-state.edu!rutgers!mcdchg!ddsw1!andyross From: andyross@ddsw1.MCS.COM (Andrew Rossmann) Newsgroups: comp.os.msdos.programmer Subject: Re: Detecting an 80486 Message-ID: <1990Jul23.223141.1776@ddsw1.MCS.COM> Date: 23 Jul 90 22:31:41 GMT References: <1990Jul20.230335.22816@ddsw1.MCS.COM> <26a858b9@ralf> Reply-To: andyross@ddsw1.MCS.COM (Andrew Rossmann) Organization: ddsw1.MCS.COM Contributor, Wheeling, IL Lines: 637 In article <26a858b9@ralf> Ralf.Brown@B.GP.CS.CMU.EDU writes: >In article <1990Jul20.230335.22816@ddsw1.MCS.COM>, andyross@ddsw1.MCS.COM (Andrew Rossmann) wrote: >}In article <315@bally.Bally.COM> pete@bally.UUCP (exilied in my own office) writes: >}>In article <1990Jul19.025150.6150@looking.on.ca> brad@looking.on.ca (Brad Templeton) writes: >} In my Infoplus program, the 286/386/486 are detected by trapping the >Unfortunately, your program will fail miserably on my system (and about a >million others), because I am running QEMM-386. QEMM runs in protected mode >with DOS in virtual-86 mode. Any invalid opcode exception that your >program generated invokes the QEMM exception handler, NOT YOURS. And the >QEMM exception handler gives the choice of terminating or rebooting. I >expect similar behavior from 386^Max, CEMM, AllCharge386, MICEMM, .... >-- Since my program is grabbing the interrupt, there is no way QEMM (or whatever) can get it. I've run Infoplus under QEMM 5 and MICEMM and EMM386.SYS, and it works fine. What QEMM and other DON'T like is doing a 386 protected instruction!! (Which I found out the hard way.) The only time it doesn't seem to work right is running under Windows 3 enhanced mode. Several people have asked to see the code I use. Here it is. It is written for use under Turbo Pascal, but should be adaptable. I'll even throw in the other two routines used by Infoplus. Someone might find them useful, too. page 75, 110 ;-------------------------------------------------------------------- ; ; INFOPLUS.ASM ; ; Version 1.10 ; ; Three subprograms used by INFOPLUS.PAS: ; ; CPUID - identifies host CPU and NDP (if ; any) ; DISKREAD - reads absolute sectors from disk ; LONGCALL - calls a routine using a CALL FAR ; ; Originally by: ; Steve Grant ; Long Beach, CA ; January 13, 1989 ; ; mods by Andrew Rossmann (7/20/90) ; 286/386/486 detection adapted from code by Robert Collins. ;-------------------------------------------------------------------- .286P .8087 public CPUID, DISKREAD, LONGCALL ;----------------------------------------------------------------------------- ; 80486 instruction macro -- because MASM 5.1 doesn't support the 80486! ;----------------------------------------------------------------------------- ;uncomment next 3 lines if not using TASM 2.0 ;XADD macro ; db 0fh,0C0h,0D2h ; 80486 instruction macro ;ENDM CODE segment byte ; Conditional jumps are all coded with the SHORT qualifier in ; order to minimize the size of the .OBJ file output of Turbo ; Assembler. ;-------------------------------------------------------------------- CPUID proc near assume cs:CODE, ds:DATA, es:nothing, ss:nothing ; On entry: ; ; BP ; SP => near return address ; offset of a cpu_info_t record ; segment " " " " ; ; On exit, the cpu_info_t record has been filled in as follows: ; ; byte = CPU type ; word = Machine Status Word ; 6 bytes = Global Descriptor Table ; 6 bytes = Interrupt Descriptor Table ; boolean = segment register change/interrupt flag ; byte = NDP type ; word = NDP control word mCPU equ byte ptr [bx] mMSW equ word ptr [bx + 1] mGDT equ [bx + 3] mIDT equ [bx + 9] mchkint equ byte ptr [bx + 15] mNDP equ byte ptr [bx + 16] mNDPCW equ word ptr [bx + 17] f8088 equ 0 f8086 equ 1 fV20 equ 2 fV30 equ 3 f80188 equ 4 f80186 equ 5 f80286 equ 6 f80386 equ 7 f80486 equ 8 funk = 0FFH false equ 0 true equ 1 push bp mov bp,sp push ds lds bx,[bp + 4] call cpu call chkint call ndp pop ds pop bp ret 4 ;-------------------------------------------------------------------- cpu: ; interrupt of multi-prefix string instruction mov mCPU,funk ;set CPU type to unknown sti mov cx,0FFFFH rep lods byte ptr es:[si] jcxz short cpu_02 call piq cmp dx,4 jg short cpu_01 mov mCPU,f8088 ret cpu_01: cmp dx,6 jne short cpu_01a mov mCPU,f8086 cpu_01a: ret cpu_02: ; number of bits in displacement register used by shift mov al,0FFH mov cl,20H shl al,cl or al,al jnz short cpu_04 call piq cmp dx,4 jg short cpu_03 mov mCPU,fV20 ret cpu_03: cmp dx,6 jne short CPUID_done mov mCPU,fV30 ret cpu_04: ; order of write/decrement by PUSH SP push sp pop ax cmp ax,sp je short cpu_06 call piq cmp dx,4 jg short cpu_05 mov mCPU,f80188 ret cpu_05: cmp dx,6 jne short CPUID_done mov mCPU,f80186 ret ; The following code is adapted from a program by Robert Collins ;since we probably have a 286, 386 or 486, we'll trap the invalid opcode ;handler, and try to determine the cpu type by using various opcodes. ;First, though, grab some tables cpu_06: smsw mMSW sgdt mGDT sidt mIDT ;----------------------------------------------------------------------------- ; Setup INT6 handler ;----------------------------------------------------------------------------- push bx ;save bx mov ax,3506h ;get INT 6 pointer int 21h mov old_int06_seg,es ;save mov old_int06_ofs,bx push ds ;save DS mov dx,seg INT6_handler ;point to new handler mov ds,dx mov dx,offset INT6_handler mov ax,2506h ;put into effect int 21h pop ds ;restore DS pop bx mov mCPU,f80486 ; initialize CPU flag xor cx,cx ; initialize semaphore ;----------------------------------------------------------------------------- ; Now, try and determine which CPU we are by executing invalid opcodes. ; The instructions I chose to invoke invalid opcodes, are themselves rather ; benign. In each case, the chosen instruction modifies the DX register, ; and nothing else. No system parameters are changed, e.g. protected mode, ; or other CPU dependant features. ;----------------------------------------------------------------------------- ; The 80486 instruction 'XADD' xchanges the registers, then adds them. ;----------------------------------------------------------------------------- ;if not using TASM 2.0, comment out next 2 lines, and uncoment 3rd. .486 XADD DX,DX ; 80486 ; XADD ;DX,DX .286 jcxz CPU_exit mov mCPU,f80386 ; set 80386 semaphore xor cx,cx ; CX=0 ;----------------------------------------------------------------------------- ; For a description on the effects of the following instructions, look in ; the Intel Programmers Reference Manual's for the 80186, 80286, or 80386. ;----------------------------------------------------------------------------- .386 mov edx,edx ; 80386 .286P jcxz CPU_exit mov mCPU,f80286 ; set 80286 semaphore xor cx,cx ; CX=0 smsw dx ; 80286 jcxz CPU_exit mov mCPU,funk ; set unknown CPU CPU_exit: push ds ;save DS lds dx,old_int06 ;get old pointer mov ax,2506h ;restore it int 21h pop ds ;!!!!!!! ;!!! Original 286/386 detection code. Commented out but left in in ;!!! case of problems. ;!!!!!!! ; try to alter flag register bits 15-12 ; ; pushf ; pop ax ; mov cx,ax ; xor cx,0F000H ; push cx ; popf ; pushf ; pop cx ; cmp ax,cx ; jne short cpu_07 ; mov mCPU,f80286 ; ret ;cpu_07: ; mov mCPU,f80386 ; ret ;cpu_08: ; mov mCPU,funk CPUID_done: ret ;-------------------------------------------------------------------- piq: ; On exit: ; ; DX = length of prefetch instruction queue ; ; This subroutine uses self-modifying code, but can ; nevertheless be run repeatedly in the course of the calling ; program. count = 7 opincdx equ 42H ; inc dx opcode opnop equ 90H ; nop opcode mov al,opincdx mov cx,count push cx push cs pop es mov di,offset piq_01 - 1 push di std rep stosb mov al,opnop pop di pop cx xor dx,dx cli rep stosb rept count inc dx endm piq_01: sti ret ;-------------------------------------------------------------------- chkint: ; save old INT 01H vector push bx mov ax,3501H int 21H mov old_int01_ofs,bx mov old_int01_seg,es pop bx ; redirect INT 01H vector push ds mov ax,2501H mov dx,seg new_int01 mov ds,dx mov dx,offset new_int01 int 21H pop ds ; set TF and change SS -- did we trap on following instruction? pushf pop ax or ah,01H ; set TF push ax popf push ss ; CPU may wait one ; instruction before ; recognizing single step ; interrupt pop ss chkint_01: ; shouldn't ever trap here ; restore old INT 01H vector push ds mov ax,2501H lds dx,old_int01 int 21H pop ds ret ;-------------------------------------------------------------------- new_int01: ; INT 01H handler (single step) ; ; On entry: ; ; SP => IP ; CS ; flags sti pop ax ; IP cmp ax,offset chkint_01 jb short new_int01_03 je short new_int01_01 mov mchkint,false jmp short new_int01_02 new_int01_01: mov mchkint,true new_int01_02: pop cx ; CS pop dx ; flags and dh,0FEH ; turn off TF push dx ; flags push cx ; CS new_int01_03: push ax ; IP iret ;----------------------------------------------------------------------------- ; INT6 handler sets a semaphore (CX=FFFF) and adjusts the return address to ; point past the invalid opcode. ;----------------------------------------------------------------------------- INT6_handler: enter 0,0 ; create new stack frame dec cx ; make CX=FFFF add word ptr ss:[bp][2],3 ; point past invalid opcode leave iret ;-------------------------------------------------------------------- ndp: fnone equ 0 f8087 equ 1 f80287 equ 2 f80387 equ 3 funk = 0FFH mov word ptr ndp_cw,0000H cli ; The next three 80x87 instructions cannot carry the WAIT prefix, ; because there may not be an 80x87 for which to wait. The WAIT is ; therefore emulated with a MOV CX,! LOOP $ combination. ; CPU NDP fnsave ndp_save ; 14 221 mov cx,(221-6-1)/17+1 ; 4 loop $ ; 17*CX-12 ; 17*CX+6 fninit ; 8 8 mov cx,(8-0-1)/17+1 ; 4 loop $ ; 17*CX-12 ; 17*CX fnstcw ndp_cw ; 14 24 mov cx,(24-2-1)/17+1 ; 4 loop $ ; 17*CX-12 ; 17*CX+2 sti mov ax,ndp_cw cmp ax,0000H jne short ndp_01 mov mNDP,fnone ret ndp_01: cmp ax,03FFH jne short ndp_02 mov mNDP,f8087 jmp short ndp_04 ndp_02: .287 cmp ax,037FH jne short ndp_05 fld1 fldz fdiv fld1 fchs fldz fdiv fcom fstsw ndp_sw mov ax,ndp_sw and ah,41H ; C3, C0 cmp ah,40H ; ST(0) = ST(1) jne short ndp_03 mov mNDP,f80287 jmp short ndp_04 ndp_03: cmp ah,01H ; ST(0) < ST(1) jne short ndp_05 mov mNDP,f80387 ndp_04: .8087 frstor ndp_save fstcw mNDPCW ret ndp_05: mov mNDP,funk ret CPUID endp ;-------------------------------------------------------------------- DISKREAD proc near assume cs:CODE, ds:DATA, es:nothing ; On entry: ; ; BP ; SP => near return address ; offset of disk buffer ; segment " " " ; number of sectors to read ; starting logical sector number ; drive number (0=A, 1=B, etc.) ; ; On exit: ; ; AX = function result ; 00 - function successful ; 01..FF - DOS INT 25H error result drive equ [bp + 12] starting_sector equ [bp + 10] number_of_sectors equ [bp + 8] buffer equ [bp + 4] push bp mov bp,sp mov ax,3000h ;get DOS version int 21h cmp al,4 ;DOS 4? jb read3 ;Read assuming DOS 3.x mov al,drive mov bx,starting_sector ;copy info into parameter block mov extd_starting_sector_lo,bx mov extd_starting_sector_hi,0 ;We're only using lower part mov bx,number_of_sectors mov extd_number_of_sectors,bx les bx,buffer ;get seg:ofs of buffer in ES:BX mov extd_bufofs,bx ;put into block mov extd_bufseg,es mov bx,offset dos4_block ;DS:BX points to block mov cx,-1 ;-1 means extended read push ds ;save DS (not really needed, but lets ;me share code with DOS 3 read.) jmp short readit read3: mov al,drive mov dx,starting_sector mov cx,number_of_sectors push ds lds bx,buffer ;get seg:ofs of buffer in DS:BX readit: int 25H inc sp ; fix broken stack inc sp pop ds jc short diskread_01 xor ax,ax diskread_01: pop bp ret 10 DISKREAD endp ; ;LONGCALL will call a routine using a CALL FAR. This is used by XMS ; ;Pascal format: procedure longcall(AXi: word; var addr: longint; ; var AXo, BXo, DXo: word); external; ; longcall proc near assume cs:CODE, ds:DATA, es:nothing AXi equ [bp+20] addr equ [bp+16] AXo equ [bp+12] BXo equ [bp+8] DXo equ [bp+4] push bp mov bp,sp les di,addr mov ax,es:[di] mov word ptr address,ax mov ax,es:[di+2] mov word ptr address+2,ax mov ax,AXi call dword ptr address les di,DXo mov [es:di],dx les di,BXo mov [es:di],bx les di,AXo mov [es:di],ax pop bp ret 18 longcall endp code ends ;-------------------------------------------------------------------- DATA segment byte ; storage for CPUID ; redirected INT 01H vector old_int01 label dword old_int01_ofs dw ? old_int01_seg dw ? ; redirected INT 6 vector old_int06 label dword old_int06_ofs dw ? old_int06_seg dw ? ; storage for NDPID ; 80x87 control word after initialization, status word after divide by zero ndp_cw dw ? ndp_save db 94 dup (?) ndp_sw dw ? ; storage for DISKREAD ; DOS 4.0 extended read parameter block dos4_block label byte extd_starting_sector_lo dw ? extd_starting_sector_hi dw ? extd_number_of_sectors dw ? extd_bufofs dw ? extd_bufseg dw ? ; storage for LONGCALL address dd ? DATA ends end Andrew Rossmann andyross@ddsw1.MCS.COM