'===========================================================================
' Subject: GET CPU & FPU INFORMATION          Date: 08-05-97 (15:10)       
'  Author: Hans Lunsing                       Code: QB, QBasic, PDS        
'  Origin: FidoNet QUIK_BAS Echo            Packet: DOS.ABC
'===========================================================================
'Well, I decided to try to translate my assembler program for CPU and FPU
'recognition for QBASIC. You will find the result in this and the
'following 4 messages. For QuickBasic the machine code part is available
'as an object file as well. You will find it in PostIt! format in the
'last message of this series.

'Information about the CPU and the FPU can be obtained by a call to the
'SUB GetProcInfo, declared as follows:

'DECLARE SUB GetProcInfo (ProcInfo AS ProcInfoType)

'ProcInfoType has the following structure:

'TYPE ProcInfoType
'  CpuFamily   AS INTEGER
'  CpuVendor   AS INTEGER
'  CpuModel    AS INTEGER
'  CpuStepping AS INTEGER
'  CpuType     AS INTEGER
'  CpuFeatures AS LONG
'  CPUIDFlag   AS INTEGER
'  CxDirFlag   AS INTEGER
'  Fpu         AS INTEGER
'END TYPE

'There is one extra element: CxDirFlag. It's one for Cyrix processors
'supporting the DIR registers (containing information about that CPU),
'and zero otherwise. If CPUIDFlag is zero and CxDirFlag = 1 the element
'CpuFeatures is zero, being undefined, but the CpuFamily, CpuModel,
'CpuStepping and CpuType are derived from the DIRs.

'The most important elements of this structure are CpuFamily and Fpu.
'They get one of the following values:

'CpuFamily:                                Fpu identification:

'0 - 8086/8088, NEC V20/V30                0 - no coprocessor
'1 - 80186                                 1 - 8087
'2 - 80286                                 2 - 80287
'3 - 80386                                 3 - 80387
'4 - 80486, AMD 5x86, Cyrix 486            4 - built in or 80387
'5 - Pentium, AMD K5 and K6, Cyrix 5x86    5 - built in
'6 - Pentium Pro, Cyrix 6x86

'NEC V20/V30 can be distinguished from 8086/8088 by the CpuVendor
'element. It's 6 for NEC, and 0 (unspecified) for 8086/8088.

'Distinction between the 80386DX and the 80386SX can be made with the
'CpuModel element. It's 0 for the DX, and 1 for the SX. Furthermore, the
'80486DX has a built-in FPU, the 80486SX has not, but can have a 80387
'alongside.

'All other information stems from the CPUID (CPU identification)
'instruction available on all modern processors. If the instruction is
'fully supported the CPUIDFlag is 2, if it gives the vendor only the flag
'is 1, and otherwise it's 0. Except in the case of 80386DX or SX the
'elements CpuModel, CpuStepping, CpuType and CpuFeatures will have no
'meaning if CPUIDFlag <> 2. The first three will be 255 then, and the
'features 0.

'I hope the code is sufficiently self explanatory. Here is is:

'----------------------------<BEGIN OF CODE>----------------------------

' ProcId
' QBASIC procedure to get info about the CPU and the FPU
' By Hans Lunsing, 1996,

' using Intel's, AMD's and Cyrix's documentation about the processors
' supporting the CPUID instruction, Cyrix's documentation about its
' device identification registers (DIR), source code given by Intel, AMD
' and Cyrix, and code snippets from several other sources.

' Donated to the public domain
' ----------------------------------------------------------------------
DEFINT A-Z

CONST FALSE = 0, TRUE = NOT FALSE

TYPE ProcInfoType
  CpuFamily   AS INTEGER
  CpuVendor   AS INTEGER
  CpuModel    AS INTEGER
  CpuStepping AS INTEGER
  CpuType     AS INTEGER
  CpuFeatures AS LONG
  CPUIDFlag   AS INTEGER
  CxDirFlag   AS INTEGER
  Fpu         AS INTEGER
END TYPE

DECLARE SUB GetProcInfo (ProcInfo AS ProcInfoType)
DECLARE FUNCTION GetCPUName$ (ProcInfo AS ANY)
DECLARE FUNCTION GetFPUName$ (ProcInfo AS ANY)
DECLARE FUNCTION GetVendorName$ (Vendor AS INTEGER)

'-----------------------------------------------------------------------
' The machine code of the assembler procedure for GetProcInfo
'-----------------------------------------------------------------------
GetProcInfoASM:
' Number of bytes
DATA 1237
' Hexadecimal representation of machine code
DATA 55,8B,EC,9C,1E,06,56,57,8B,5E,06,8B,3F,81,EF,0A
DATA 00,8C,C8,8E,D8,8E,C0,80,BD,C7,00,FF,75,72,C6,85
DATA C9,00,00,E8,FA,00,72,11,C6,85,C7,00,00,E8,FD,00
DATA 72,5B,C6,85,C9,00,06,EB,54,E8,F8,00,72,07,C6,85
DATA C7,00,01,EB,48,E8,02,01,72,07,C6,85,C7,00,02,EB
DATA 3C,E8,06,01,72,0E,C6,85,C7,00,03,E8,24,01,88,85
DATA CB,00,EB,29,C6,85,C7,00,04,E8,9A,02,72,12,C6,85
DATA C9,00,03,E8,A3,02,80,BD,D7,00,01,75,10,E8,42,03
DATA E8,90,01,80,BD,C9,00,03,75,03,E8,5C,03,E8,91,03
DATA 8B,46,FC,8E,C0,BE,C7,00,03,F7,8B,7E,08,B9,14,00
DATA F3,A4,5F,5E,07,1F,9D,5D,CA,04,00,48,61,6E,73,20
DATA 4C,75,6E,73,69,6E,67,2C,20,31,39,39,37,FF,00,FF
DATA 00,FF,00,FF,00,FF,00,00,00,00,00,00,00,00,00,FF
DATA 00,00,00,00,00,00,00,00,00,00,00,00,00,24,00,00
DATA 00,00,00,00,47,65,6E,75,69,6E,65,49,6E,74,65,6C
DATA 41,75,74,68,65,6E,74,69,63,41,4D,44,41,4D,44,20
DATA 49,53,42,45,54,54,45,52,43,79,72,69,78,49,6E,73
DATA 74,65,61,64,55,4D,43,20,55,4D,43,20,55,4D,43,20
DATA B0,FF,B1,20,D2,E0,0A,C0,F9,75,01,F8,C3,F9,60,F8
DATA 72,01,61,C3,9C,58,8B,C8,25,FF,0F,50,9D,9C,58,25
DATA 00,F0,3D,00,F0,F9,75,01,F8,C3,81,C9,00,F0,51,9D
DATA 9C,58,25,00,F0,F9,75,01,F8,C3,8B,DC,83,E4,FC,66
DATA 9C,66,58,66,8B,C8,66,35,00,00,04,00,66,50,66,9D
DATA 66,9C,66,58,66,51,66,9D,66,33,C1,8B,E3,F9,75,01
DATA F8,C3,57,FC,B9,00,04,33,F6,F3,66,AD,B9,00,04,33
DATA F6,8B,FE,FA,B0,B0,E6,43,EB,00,32,C0,E6,42,EB,00
DATA E6,42,EB,00,E4,61,0C,01,E6,61,F3,A5,B0,80,E6,43
DATA EB,00,FB,E4,42,EB,00,8A,D0,E4,42,EB,00,8A,F0,F7
DATA DA,E4,61,24,FE,E6,61,52,B9,00,04,33,F6,8B,FE,FA
DATA B0,B0,E6,43,EB,00,32,C0,E6,42,EB,00,E6,42,EB,00
DATA E4,61,0C,01,E6,61,F3,66,A5,B0,80,E6,43,EB,00,FB
DATA E4,42,EB,00,8A,D0,E4,42,EB,00,8A,F0,F7,DA,E4,61
DATA 24,FE,E6,61,58,8B,D8,D1,EB,03,C3,3B,C2,B0,00,14
DATA 00,5F,C3,C6,85,D5,00,00,66,8B,C1,66,35,00,00,20
DATA 00,66,50,66,9D,66,9C,66,58,66,33,C1,0F,84,D5,00
DATA FE,85,D5,00,66,33,C0,0F,A2,66,89,9D,DB,00,66,89
DATA 95,DF,00,66,89,8D,E3,00,BE,DB,00,03,F7,56,57,81
DATA C7,EE,00,B9,0C,00,F3,A6,5F,5E,0B,C9,75,07,C6,85
DATA C9,00,01,EB,65,56,57,81,C7,FA,00,B9,0C,00,F3,A6
DATA 5F,5E,0B,C9,75,07,C6,85,C9,00,02,EB,4D,56,57,81
DATA C7,06,01,B9,0C,00,F3,A6,5F,5E,0B,C9,75,07,C6,85
DATA C9,00,02,EB,35,56,57,81,C7,12,01,B9,0C,00,F3,A6
DATA 5F,5E,0B,C9,75,07,C6,85,C9,00,03,EB,1D,56,57,81
DATA C7,1E,01,B9,0C,00,F3,A6,5F,5E,0B,C9,75,07,C6,85
DATA C9,00,04,EB,05,C6,85,C9,00,09,66,83,F8,01,7C,35
DATA FE,85,D5,00,66,33,C0,66,40,0F,A2,66,89,95,D1,00
DATA 8A,D0,80,E2,0F,88,95,CD,00,24,F0,C0,E8,04,88,85
DATA CB,00,8A,F4,80,E6,0F,88,B5,C7,00,80,E4,30,C0,EC
DATA 04,88,A5,CF,00,C3,33,C0,9E,B8,05,00,B3,02,F6,F3
DATA 9F,80,FC,02,F9,75,01,F8,C3,C6,85,E8,00,FF,C6,85
DATA E9,00,FF,B4,C3,E8,E3,00,8A,D0,34,80,E8,E6,00,B4
DATA C0,E8,D7,00,B4,C3,E8,D2,00,32,ED,3A,C2,74,02,FE
DATA C5,8A,C2,E8,CF,00,C6,85,D7,00,00,80,FE,01,75,18
DATA FE,85,D7,00,B4,FE,E8,B2,00,88,85,E8,00,B4,FF,E8
DATA A9,00,88,85,E9,00,EB,34,B4,C2,E8,9E,00,8A,D0,34
DATA 04,E8,A1,00,B4,C0,E8,92,00,B4,C2,E8,8D,00,32,C9
DATA 3A,C2,74,02,FE,C1,8A,C2,E8,8A,00,80,FA,01,75,07
DATA C6,85,E8,00,FE,EB,05,C6,85,E8,00,FD,8A,85,E8,00
DATA 8A,E0,24,0F,C0,EC,04,C6,85,C7,00,04,88,A5,CB,00
DATA 88,85,CD,00,3C,03,72,09,3C,0F,74,05,C6,85,C7,00
DATA 05,C3,B4,C3,E8,44,00,88,85,EA,00,24,0F,0C,10,E8
DATA 43,00,B4,E8,E8,34,00,88,85,EB,00,0C,80,E8,35,00
DATA B4,C3,8A,85,EA,00,E8,2C,00,B4,C3,E8,1D,00,88,85
DATA EA,00,24,0F,0C,10,E8,1C,00,B4,E8,8A,85,EB,00,E8
DATA 13,00,B4,C3,8A,85,EA,00,E8,0A,00,9C,FA,8A,C4,E6
DATA 22,E4,23,9D,C3,9C,FA,86,C4,E6,22,86,C4,E6,23,9D
DATA C3,9C,B2,00,80,BD,D5,00,02,75,0C,8B,85,D1,00,A8
DATA 01,74,04,B2,05,EB,7F,DB,E3,C7,85,EC,00,5A,5A,DD
DATA BD,EC,00,8B,85,EC,00,3C,00,75,6B,D9,BD,EC,00,8B
DATA 85,EC,00,25,3F,10,83,F8,3F,75,5B,B2,01,80,BD,C7
DATA 00,02,72,52,B2,02,74,4E,80,BD,C7,00,03,75,24,9B
DATA D9,E8,9B,D9,EE,9B,DE,F9,9B,D9,C0,9B,D9,E0,9B,DE
DATA D9,9B,DD,BD,EC,00,8B,85,EC,00,9E,B2,02,74,27,B2
DATA 03,EB,23,B2,05,80,BD,C7,00,04,75,1A,80,BD,D5,00
DATA 01,75,0C,F7,85,D1,00,01,00,75,0B,B2,03,EB,07,E8
DATA 0A,00,73,02,B2,03,88,95,D9,00,9D,C3,0F,20,C0,8A
DATA D8,34,10,0F,22,C0,0F,20,C0,86,C3,0F,22,C0,3A,C3
DATA F9,75,01,F8,C3

' Pretty impressive, isn't it? For me it was, when I saw the result :)

'-----------------------------------------------------------------------
' Example: get CPU and FPU info
'-----------------------------------------------------------------------

DIM ProcInfo AS ProcInfoType
GetProcInfo ProcInfo

PRINT "CPU: "; GetCPUName$(ProcInfo)
PRINT "FPU: "; GetFPUName$(ProcInfo)
PRINT
PRINT "CPU Vendor:   "; GetVendorName$(ProcInfo.CpuVendor)
IF ProcInfo.CPUIDFlag = 2 THEN
  PRINT "CPU version:  ";
  PRINT USING "#_.##_.##(#)"; ProcInfo.CpuFamily; ProcInfo.CpuModel; ProcInfo.CpuStepping; ProcInfo.CpuType
  PRINT "CPU features: "; RIGHT$("0000000" + HEX$(ProcInfo.CpuFeatures), 8)
ELSE
  PRINT "CPU family:   "; ProcInfo.CpuFamily
END IF

END

' Vendor codes

CONST UNKNOWN = 0, INTEL = 1, AMD = 2, CYRIX = 3
CONST UMC = 4, NEC = 6, OTHER = 9

' CPU family codes

CONST I8086 = 0, I80186 = 1, I80286 = 2, I80386 = 3, I80486 = 4
CONST FAMILY5 = 5, FAMILY6 = 6

' FPU identifications:

CONST NONE = 0, I8087 = 1, I80287 = 2, I80387 = 3
CONST BUILT.IN.OR.387 = 4, BUILT.IN = 5

FUNCTION GetCPUName$ (ProcInfo AS ProcInfoType)
DIM Cpu AS STRING
IF ProcInfo.CPUIDFlag = 2 OR ProcInfo.CxDirFlag = 1 THEN
  'CPU supports CPUID instruction fully, or is a Cyrix processor
  'and supports the DIR registers
  SELECT CASE ProcInfo.CpuVendor
  CASE INTEL
    SELECT CASE ProcInfo.CpuFamily
    CASE I80486
      SELECT CASE ProcInfo.CpuModel
      CASE 3: Cpu = "Intel486 DX2 or DX2 Overdrive"
      CASE 5: Cpu = "Intel486 SX2"
      CASE 7: Cpu = "Intel486 DX2 (write back enhanced)"
      CASE 8:
        IF ProcInfo.CpuType = 0 THEN
          Cpu = "Intel486 DX4 or DX4 Overdrive"
        ELSE
          Cpu = "Intel486 DX4 Overdrive"
        END IF
      CASE ELSE
        Cpu = "Intel486"
      END SELECT
    CASE FAMILY5
      SELECT CASE ProcInfo.CpuModel
      CASE 1: Cpu = "Pentium (510\60, 567\66)"
      CASE 2:
        IF ProcInfo.CpuType = 0 THEN
          Cpu = "Pentium (680\75, 735\90, 815\100, 1000\120, 1110\133)"
        ELSE
          Cpu = "Pentium Overdrive for Pentium CPU"
        END IF
      CASE 3: Cpu = "Pentium Overdrive for Intel486 CPU"
      CASE 4: Cpu = "Overdrive for Pentium (680\75, 735\90, 815\100, 1000\120, 1110\133)"
      CASE 5: Cpu = "Pentium Overdrive for Intel486 DX4 CPU"
      CASE ELSE
        Cpu = "Intel Pentium"
      END SELECT
    CASE FAMILY6
      SELECT CASE ProcInfo.CpuModel
      CASE 1: Cpu = "Pentium Pro"
      CASE 3: Cpu = "Overdrive for Pentium Pro"
      CASE ELSE
        Cpu = "Intel Pentium Pro"
      END SELECT
    CASE ELSE
      Cpu = "Intel Pentium Pro Plus"
    END SELECT
  CASE AMD
    SELECT CASE ProcInfo.CpuFamily
    CASE I80486
      SELECT CASE ProcInfo.CpuModel
      CASE 3:  Cpu = "Am486DX2 (Write Trough)"
      CASE 7:  Cpu = "Am486DX2 (Write Back)"
      CASE 8:  Cpu = "Am486DX4 or Am5x86-150Mhz (Write Trough)"
      CASE 9:  Cpu = "Am486DX4 or Am5x86-150Mhz (Write Back)"
      CASE 14: Cpu = "Am5x86 133 or 160Mhz (Write Trough)"
      CASE 15: Cpu = "Am5x86 133 or 160Mhz (Write Back)"
      CASE ELSE
        Cpu = "AMD Am486 or Am5x86"
      END SELECT
    CASE FAMILY5
      SELECT CASE ProcInfo.CpuModel
      CASE 0:  Cpu = "AMD-K5 (model 0)"
      CASE 1:  Cpu = "AMD-K5 (model 1)"
      CASE 6:  Cpu = "AMD-K6"
      CASE ELSE
        Cpu = "AMD-K5 or AMD-K6"
      END SELECT
    CASE ELSE
      Cpu = "AMD-K6 Plus"
    CASE ELSE
    END SELECT

  CASE CYRIX
    SELECT CASE ProcInfo.CpuModel
    CASE 0
      SELECT CASE ProcInfo.CpuStepping
      CASE 0: Cpu = "Cx486 SLC"
      CASE 1: Cpu = "Cx486 DLC"
      CASE 2: Cpu = "Cx486 SLC2"
      CASE 3: Cpu = "Cx486 DLC2"
      CASE 4: Cpu = "Cx486 SRx, Retail Upgrade Cx486SLC"
      CASE 5: Cpu = "Cx486 DRx, Retail Upgrade Cx486DLC"
      CASE 6: Cpu = "Cx486 SRx2, Retail Upgrade 2x Cx486SLC"
      CASE 7: Cpu = "Cx486 DRx2, Retail Upgrade 2x Cx486DLC"
      CASE ELSE: Cpu = "Cx486 model 0 stepping" + STR$(ProcInfo.CpuStepping)
      END SELECT
    CASE 1
      SELECT CASE ProcInfo.CpuStepping
      CASE 0: Cpu = "Cx486S B step"
      CASE 1: Cpu = "Cx486S2 B step"
      CASE 2: Cpu = "Cx486Se B step"
      CASE 3: Cpu = "Cx486S2e B step"
      CASE 10: Cpu = "Cx486DX"
      CASE 11: Cpu = "Cx486DX2"
      CASE 15: Cpu = "Cx486DX4"
      CASE ELSE: Cpu = "Cx486 model 1 stepping" + STR$(ProcInfo.CpuStepping)
      END SELECT
    CASE 2
      SELECT CASE ProcInfo.CpuStepping
      CASE 8, 10: Cpu = "Cyrix 5x86 1x Core/Bus Clock"
      CASE 9, 11: Cpu = "Cyrix 5x86 2x Core/Bus Clock"
      CASE 13, 15: Cpu = "Cyrix 5x86 3x Core/Bus Clock"
      CASE 12, 14: Cpu = "Cyrix 5x86 4x Core/Bus Clock"
      CASE ELSE:   Cpu = "Cyrix 5x86 stepping" + STR$(ProcInfo.CpuStepping)
      END SELECT
    CASE 3
      SELECT CASE ProcInfo.CpuStepping
      CASE 0, 2: Cpu = "Cyrix 6x86 1x Core/Bus Clock"
      CASE 1, 3: Cpu = "Cyrix 6x86 2x Core/Bus Clock"
      CASE 5, 7: Cpu = "Cyrix 6x86 3x Core/Bus Clock"
      CASE 4, 6: Cpu = "Cyrix 6x86 4x Core/Bus Clock"
      CASE ELSE:   Cpu = "Cyrix 6x86 stepping" + STR$(ProcInfo.CpuStepping)
      END SELECT
    CASE 15
      SELECT CASE ProcInfo.CpuStepping
      CASE 13:   Cpu = "Cx486SLC or DLC, old model"
      CASE 14:   Cpu = "Cx486Sa Step=1"
      CASE ELSE: Cpu = "Cyrix unknown model"
      END SELECT
    CASE ELSE
      Cpu = "Cyrix unknown model"
    END SELECT
  CASE UMC
    SELECT CASE ProcInfo.CpuFamily
    CASE I80486:  Cpu = "UMC 80486-like"
    CASE FAMILY5: Cpu = "UMC Pentium-like"
    CASE FAMILY6: Cpu = "UMC Pentium-Pro-like"
    CASE ELSE: Cpu = "UMC Pentium-Pro-like Plus"
    END SELECT
  CASE ELSE
    SELECT CASE ProcInfo.CpuFamily
    CASE I80486:  Cpu = "80486-like"
    CASE FAMILY5: Cpu = "Pentium-like"
    CASE FAMILY6: Cpu = "Pentium-Pro-like"
    CASE ELSE: Cpu = "Pentium-Pro-like Plus"
    END SELECT
  END SELECT
ELSE
  'CPU doesn't support CPUID instruction or supports it for vendor id only
  SELECT CASE ProcInfo.CpuFamily
  CASE I8086
    IF ProcInfo.CpuVendor = NEC THEN
      Cpu = "NEC V20/V30"
    ELSE
      Cpu = "8086/8088"
    END IF
  CASE I80186
    Cpu = "80186/80188"
  CASE I80286
    Cpu = "80286"
  CASE I80386
    SELECT CASE ProcInfo.CpuModel
    CASE 0: Cpu = "80386 DX"
    CASE 1: Cpu = "80386 SX"
    CASE ELSE: Cpu = "80386"
    END SELECT
  CASE I80486
    IF ProcInfo.CpuVendor = CYRIX THEN
      Cpu = "Cx486 SLC/DLC"
    ELSEIF ProcInfo.Fpu = BUILT.IN THEN
      Cpu = "80486 DX (or newer)"
    ELSE
      Cpu = "80486 SX"
    END IF
  END SELECT
END IF
GetCPUName$ = Cpu
END FUNCTION

FUNCTION GetFPUName$ (ProcInfo AS ProcInfoType)
DIM Fpu AS STRING
SELECT CASE ProcInfo.Fpu
CASE BUILT.IN: Fpu = "built-in"
CASE I80387:   Fpu = "80387"
CASE I80287:   Fpu = "80287"
CASE I8087:    Fpu = "8087"
CASE ELSE:     Fpu = "not present"
END SELECT
GetFPUName$ = Fpu
END FUNCTION

SUB GetProcInfo (ProcInfo AS ProcInfoType) STATIC
'-----------------------------------------------------------------------
' Procedure to get info about the CPU and the FPU.
' Uses the ProcInfoType structure to return the info in.
'-----------------------------------------------------------------------

IF NOT MachineCode% THEN
  ' First time dimension string array to hold machine code
  RESTORE GetProcInfoASM
  READ nASMBYTES%
  REDIM ASMBuffer(0 TO nASMBYTES% - 1) AS STRING * 1
END IF

' Get address of machine code
DEF SEG = VARSEG(ASMBuffer(0))
Offset% = VARPTR(ASMBuffer(0))

IF NOT MachineCode% THEN
  ' First time load string array with machine code
  FOR i% = 0 TO nASMBYTES% - 1
    READ Code$
    POKE Offset% + i%, VAL("&H" + Code$)
  NEXT i%
  ' Indicate availability of machine code
  MachineCode% = TRUE
END IF

' Get info.
' The second offset% is necessary!!!
CALL ABSOLUTE(ProcInfo, Offset%, Offset%)

DEF SEG
END SUB

FUNCTION GetVendorName$ (Vendor AS INTEGER)
DIM Ven AS STRING
SELECT CASE Vendor
CASE INTEL: Ven = "Intel"
CASE AMD: Ven = "AMD"
CASE CYRIX: Ven = "Cyrix"
CASE UMC: Ven = "UMC"
CASE NEC: Ven = "NEC"
CASE ELSE: Ven = "not specified"
END SELECT
GetVendorName$ = Ven
END FUNCTION
