The OS Journal

The OS Journal
http://www.osjournal.hopto.org/
A project by the people at alt.os.development
CPUID & Processor Identification
by Jens Olsson, Homepage
 Comments(0)
 Standard Version
 Direct Link


Introduction
If you are programming an os and want to use all new features added to newer processors you will need way to check which features the current processor is compatible with. To do this on the Pentium processors and later is easy since you are provided an instruction called CPUID for this purpose. On the 486 and older it's harder. although some 486 processors support the CPUID instruction, most of them doesn't. It is possible to check whether the processor is a 8086, 286, 386 or 486 without the CPUID instruction. This can be done in various ways where one of them is to execute an instruction which only works on the processor checked and later. If the processor encounters an instruction it can't handle, an invalid opcode exception wil be generated.
In this article we are going to go threw most of the things you need to do to identify your processor.



Checking for 8086, 286, 386, 486 processors



The CPUID instruction
The CPUID instruction is streightforward. The first you should do is to load EAX with 0. CPUID returns different values in EAX, EBX, ECX and EDX depending on the value in EAX before the call. When CPUID is executed with EAX=0, it will return the following:
EAX The largest value EAX can be set to before executing the CPUID instruction. If it is, lets say 5, you can call CPUID 5 times more with EAX=1 for the first, 2 for the second and so on.
EBX, EDX, ECX These registers together forms the often called "vendor string".

Each processor manufacturer have their own vendor string. For example, Intels vendor string is "GenuineIntel" and AMD's vendor string is "AuthenticAMD" while old Cyrix uses "CyrixInstead". This is very important because the information retrieved if you call CPUID with EAX>0 is different between different manufacturers.
We have to store all this information in some variables for future use. The following C++ example will execute the CPUID instruction with EAX=0 and store the information retrieved in 2 variables.
#include <stdio.h>
#include <string.h>

void main() {
  char VendorSign[13];   //We need somewhere to store our vendorstring
  unsigned long MaxEAX;  //This will be used to store the maximum EAX
                         //possible to call CPUID with.

  asm {
    XOR       EAX,                        EAX
    //An efficient alternatvie to MOV EAX, 0x0

    CPUID
    //This instruction will load our registers with the data we need.

    MOV       dword ptr [VendorSign],     EBX
    //Copy the first 4 bytes in the VendorString from EBX.

    MOV       dword ptr [VendorSign+4],   EDX
    //Copy the next 4 bytes.

    MOV       dword ptr [VendorSign+8],   ECX
    //Copy the next 4 bytes.

    MOV       dword ptr MaxEAX,           EAX
    //EAX contains the maximum value to call CPUID with. Copy it to the
    //MaxEAX variable.
  }
  VendorSign[12]=0;  //The last character in the VendorSign can be anything.
                     //To make sure that it stops at the last character we add
                     //a zero character at the end


  printf("Vendor string: %s\n", VendorSign);
  printf("Maximum EAX value: %i\n", MaxEAX);

}



CPUID, EAX=1
Now it gets even more interesting. If you execute CPUID while EAX=1 instead you retrieve the processor family, model and stepping stored in EAX. You will also get a bitmap containing information on which functions (MMX, 3DNow etc.) that exists on the processor. Do not call CPUID with EAX=1 if our variable, MaxEAX is less than 1. It is a bit more frustrating when using EAX=1 because the processor manufacturers don't have the same meaning for the bits in the bitmap returned. Some are the same but many differ, we therefore have to check our VendorSign variable to determine how we should treat our bitmap. In this example we will make an array of all features in the bitmap that is correct for the Intel processor. We then check if AMD is used and change the array to the correct features. We will concentrate on Intel and AMD in this document. Adding support for Cyrix and other processors is left as an exercise for the reader;)
  char* Comp1[32];  //This is the array that will hold the short names
                    //for our features bitmap. The names added below is 
                    //valid for the Intel processors and many of them
                    //are valid for other processors as well.

  if(strcmp(VendorSign, "GenuineIntel")==0) {
    Comp1[0]="FPU";   //Floating Point Unit
    Comp1[1]="VME";   //Virtual Mode Extension
    Comp1[2]="DE";    //Debugging Extension
    Comp1[3]="PSE";   //Page Size Extension
    Comp1[4]="TSC";   //Time Stamp Counter
    Comp1[5]="MSR";   //Model Specific Registers
    Comp1[6]="PAE";   //Physical Address Extesnion
    Comp1[7]="MCE";   //Machine Check Extension
    Comp1[8]="CX8";   //CMPXCHG8 Instruction
    Comp1[9]="APIC";  //On-chip APIC Hardware
    Comp1[10]="";     //Reserved
    Comp1[11]="SEP";  //SYSENTER SYSEXIT
    Comp1[12]="MTRR"; //Machine Type Range Registers
    Comp1[13]="PGE";  //Global Paging Extension
    Comp1[14]="MCA";  //Machine Check Architecture
    Comp1[15]="CMOV"; //Conditional Move Instrction
    Comp1[16]="PAT";  //Page Attribute Table
    Comp1[17]="PSE-36"; //36-bit Page Size Extension
    Comp1[18]="PSN";  //96-bit Processor Serial Number
    Comp1[19]="CLFSH"; //CLFLUSH Instruction
    Comp1[20]="";     //Reserved
    Comp1[21]="DS";   //Debug Trace Store
    Comp1[22]="ACPI"; //ACPI Support
    Comp1[23]="MMX";  //MMX Technology
    Comp1[24]="FXSR"; //FXSAVE FXRSTOR (Fast save and restore)
    Comp1[25]="SSE";  //Streaming SIMD Extensions
    Comp1[26]="SSE2"; //Streaming SIMD Extensions 2
    Comp1[27]="SS";   //Self-Snoop
    Comp1[28]="HTT";  //Hyper-Threading Technology
    Comp1[29]="TM";   //Thermal Monitor Supported
    Comp1[30]="IA-64"; //IA-64 capable
    Comp1[31]="";     //Reserved
  }
  else if(strcmp(VendorSign, "AuthenticAMD")==0) {
    Comp1[0]="FPU";   //Floating Point Unit
    Comp1[1]="VME";   //Virtual Mode Extension
    Comp1[2]="DE";    //Debugging Extension
    Comp1[3]="PSE";   //Page Size Extension
    Comp1[4]="TSC";   //Time Stamp Counter
    Comp1[5]="MSR";   //Model Specific Registers
    Comp1[6]="PAE";   //Physical Address Extesnion
    Comp1[7]="MCE";   //Machine Check Extension
    Comp1[8]="CX8";   //CMPXCHG8 Instruction
    Comp1[9]="APIC";  //On-chip APIC Hardware
    Comp1[10]="";     //Reserved
    Comp1[11]="SEP";  //SYSENTER SYSEXIT
    Comp1[12]="MTRR"; //Machine Type Range Registers
    Comp1[13]="PGE";  //Global Paging Extension
    Comp1[14]="MCA";  //Machine Check Architecture
    Comp1[15]="CMOV"; //Conditional Move Instrction
    Comp1[16]="PAT";  //Page Attribute Table
    Comp1[17]="PSE-36"; //36-bit Page Size Extension
    Comp1[18]="";     //?
    Comp1[19]="MPC";  //MultiProcessing Capable (I made this short up, correct?)
    Comp1[20]="";     //Reserved
    Comp1[21]="";     //?
    Comp1[22]="MIE";  //AMD Multimedia Instruction Extensions (I made this short up, correct?)
    Comp1[23]="MMX";  //MMX Technology
    Comp1[24]="FXSR"; //FXSAVE FXRSTOR (Fast save and restore)
    Comp1[25]="SSE";  //Streaming SIMD Extensions
    Comp1[26]="";     //?
    Comp1[27]="";     //?
    Comp1[28]="";     //?
    Comp1[29]="";     //?
    Comp1[30]="3DNowExt"; //3DNow Instruction Extensions (I made this short up, correct?)
    Comp1[31]="3DNow"; //3DNow Instructions (I made this short up, correct?)
  }

  unsigned long REGEAX, REGEBX, REGECX, REGEDX;
  int dFamily, dModel, dStepping, dFamilyEx, dModelEx;
  char dType[10];
  int dComp1Supported[32];
  int dBrand, dCacheLineSize, dLogicalProcessorCount, dLocalAPICID;

  if(MaxEAX>=1) {
    asm {
      MOV     EAX,                    1
      CPUID
      MOV     [REGEAX],               EAX
      MOV     [REGEBX],               EBX
      MOV     [REGECX],               ECX
      MOV     [REGEDX],               EDX
    }
    dFamily=((REGEAX>>8)&0xF);
    dModel=((REGEAX>>4)&0xF);
    dStepping=(REGEAX&0xF);

    dFamilyEx=((REGEAX>>20)&0xFF);
    dModelEx=((REGEAX>>16)&0xF);
    switch(((REGEAX>>12)&0x7)) {
      case 0:
        strcpy(dType, "Original");
        break;
      case 1:
        strcpy(dType, "OverDrive");
        break;
      case 2:
        strcpy(dType, "Dual");
        break;
    }

    for(unsigned long C=1, Q=0;Q<32;C*=2, Q++) {
      dComp1Supported[Q]=(REGEDX&C)!=0?1:0;
    }


    dBrand=REGEBX&0xFF;
    dCacheLineSize=((strcmp(Comp1[19], "CLFSH")==0)&&(dComp1Supported[19]==1))?((REGEBX>>8)&0xFF)*8:-1;
    dLogicalProcessorCount=((strcmp(Comp1[28], "HTT")==0)&&(dComp1Supported[28]==1))?((REGEBX>>16)&0xFF):-1;
    dLocalAPICID=((REGEBX>>24)&0xFF); //This only works on P4 or later, must check for that in the future

  }
  
  printf("%s\n", dType);
  printf("Family %i, Model %i, Stepping %i\n", dFamily, dModel, dStepping);
  printf("Extended Family %i, Extended Model %i\n", dFamilyEx, dModelEx);
  printf("Supported flags: ");
  for(unsigned long Q=0;Q<27;Q++) {
    if(dComp1Supported[Q]) {
      printf("%s ", Comp1[Q]);
    }
  }
  printf("\n");
  printf("CacheLineSize: %i\n", dCacheLineSize);
  printf("Logical processor count: %i\n", dLogicalProcessorCount);
  printf("Local APIC ID: %i\n", dLocalAPICID);




TODO
* Add information about L1 and L2 cache retrieved when EAX=2.

* Add information about detecting CPUID instruction and determine pre CPUID processor types.




Comments(0) | Standard Version
http://www.osjournal.n3.net/!11