Ancient Publication

Sometime in 1987, or thereabouts, I was working with various Intel processors. For some reason I needed to know which type of CPU some code was running on, so I wrote a routine that would determine whether the processor was an 8086, 80186, 80286, or 80386 and, if a 286 or 386, whether it was running in real or protected mode. I wrote an article about it which was first printed in PC Tech Journal, November 1987, on page 51. Somewhere I still have a copy of that issue. Somewhere. The article was then anthologized in Dr. Dobb's Toolbook of 80286/80306 programming on pages 75 to 78.

The text of the article and the source code is below the fold. I wish I had my original work to show how it was edited for publication.

Determining CPU Type
Bob Felts

With the advent of OS/2, a protected-mode operating system, it is useful to have a function to determine CPU type, regardless of CPU operating mode. Many previously published procedures of this kind work only in real mode because they write to the code segment. The function listed here, cputype, can be called by DOS and OS/2 programs in either real or protected mode. It returns a value of 86, 186, 286, or 386 in real mode, and -286 or -386 in protected mode. For brevity, the test to distinguish between the 8086 and 8088 classes of chip is not included.

The first test is a PUSH SP instruction to differentiate between 8086/80186 and 80286/80386 CPUs. If the value on the stack is the same as the SP value after the push, then the CPU is either an 8086 or an 80186. These chips are distinguished by their response to a shift instruction. For a 32-bit shift count, the 8086 will shift 32 bits, clearing the shifted register, whereas the 80186 will not shift at all, leaving the value in the register unchanged.

The next test determines whether the CPU is using 16- or 32-bit operands. This is done by pushing the flags, then testing the change in the SP register to determine whether two or four bytes were pushed. In the latter case, the CPU is an 80386 executing in a 32-bit segment. To load a two-byte immediate value into AX, the MOV instruction must be preceded by an operand-length override prefix. MASM version 4 has no mnemonic for this, so it is generated with a DB instruction at the label

The distinction between an 80386 that uses 16-bit operands and an 80286 is made by storing the global descriptor table register (GDTR) to a six-byte field in memory. The 80286 stores a -1 to the last byte of this field, whereas an 80386 stores either a 0 or a 1. The space for holding the GDTR value is allocated on the stack (by subtracting from SP), because in protected mode that is the only one of the four segments guaranteed to be writable.

The CPU operating mode (real or protected) is indicated by the low-order bit of the machine status word (MSW). At label
testprot, the MSW is loaded into a register, the mode bit is shifted into the carry flag, and, if that sets CF, the returned value is negated to indicate that the CPU is in protected mode.

The function follows Microsoft mixed-language naming conventions, because it has no parameters, it needs no declarations to specify the calling sequence. If called from a C program, it must be declared far.

; determine cpu type. works in both
; real and protected modes.
; in:
; none
; out:
; ax : processor type
; 86 => 8086
; 186 => 80186
; 286 => 80286 (real mode)
; -286 => 80286 (protected mode)
; 386 => 80386 (real mode)
; -386 => 80386 (protected mode)
; destroyed:
; upper 16 bits of eax if executed
; in a 32 bit 80386 code segment
.286p ; must be assembled in 16-bit code segment

public _cputype

_cputype proc near ; "far" for large model programs

pushf ; save original flags
push cx ; and registers
push bp

mov ax,sp ; 86/186 or 286/386?
push sp ; 86/186 will push sp-2
pop cx ; others will push sp
cmp ax,cx
jz short cpu_2386 ; if 80286/80386

mov ax,186 ; distinguish between 86 and 186
mov cl,32 ; 8086 will shift 32 bits
shl ax,cl ; 80186 will shift 0 bits
jnz short cpu_exit ; NZ implies 80186

mov ax,86 ; it's an 8086
jmp short cpu_exit

pushf ; 286/386. 32 or 16 bit operand size?
mov cx,sp ; if pushf pushed 2 bytes then
popf ; 16 bit operand size
inc cx ; assume 2 bytes
inc cx
cmp cx,ax
jnz short cpu_386_32 ; jmp/4 bytes, => 32-bit 80386

; either 286 or 386 with 16-bit operands
sub sp,6 ; allocate room for SGDT
mov bp,sp

ifndef PWORD
sgdt FWORD PTR ss:[bp] ; for 286 assemblers
sgdt PWORD PTR ss:[bp] ; for 386 assemblers

add sp,4 ; trash limit and base
pop ax
inc ah ; 286 stores -1, 386 stores 0/1
jnz short cpu_386_16

mov ax,286 ; it's a 286
jmp short cpu_prot ; go check for protected mode

db 066h ; 386 in 32 bit code segment
mov ax,386 ; 066h (override) to force 16 bit mov
jmp short cpu_prot ; into ax

mov ax,386 ; 386 in 16 bit code segment

smsw cx ; check for protected mode
ror cx,1
jnc short cpu_exit ; if PE == 0 then real mode

neg ax ; else flag protected mode

pop bp
pop cx

_cputype endp

blog comments powered by Disqus