/* Intel and AMD x86 CPUID display program v 3.0 (March 2009)
 * $Id: cpuid.c,v 1.3 2009/04/06 05:38:58 karn Exp karn $
 * Copyright 2009 Phil Karn, KA9Q
 * May be used under the terms of the GNU Public License (GPL)

 * Reference documents:
 * AMD CPUID Specification, publication 25481, revision 2.28, April 2008
 * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/25481.pdf

 * Intel 64 and IA-32 Architectures Software Developer's Manual 
 * Volume 2A: Instruction Set Reference, A-M
 * http://download.intel.com/design/processor/manuals/253666.pdf

 * Intel Processor Identification and the CPUID Instruction
 * Application Note 485, November 2008
 * http://www.intel.com/Assets/PDF/appnote/241618.pdf
 */

#ifndef __i386__
//#error "Must compile for i386"
#endif

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cpuid.h"


char *Regnames[] = {"EAX", "EBX", "ECX", "EDX" };

int Verbose = 0;
int Instructions = 0;
int Cache = 0;

void show_instructions(void);

int main(int argc,char *argv[]){
  unsigned int maxi,maxei,regs[4];
  char *brand;
  int vendor,c;

  while((c = getopt(argc,argv,"vVic")) != EOF){
    switch(c){
    case 'c': /* Show cache information */
      Cache++;
      break;
    case 'v': /* -v shows raw register values; -v -v decodes everything */
      Verbose++;
      break;
    case 'i': /* List supported instructions */
      Instructions++;
      break;
    case 'V':
      printf("x86 CPUID decoder by Phil Karn, KA9Q; compiled %s\n",__DATE__);
      exit(0);
    default:
      printf("Usage: %s [-v [-v]] [-V]\n",argv[0]);
      exit(1);
    }
  }

  /* Rather than diddle with the CPU flags to see if CPUID is present, just set a signal
   * catcher and try to use it. Intel says we shouldn't do this, perhaps the opcode
   * doesn't trap on some CPUs?
   */
  signal(SIGILL,illtrap); /* Catch illegal instruction trap on CPUID if it happens */

  regs[0] = 0;
  cpuid(regs);
  maxi = regs[0] & 0xffff; /* The high-order word is non-zero on some Cyrix CPUs */  
  vendor = regs[1];

  printf("Vendor ID: ");
  putreg(regs[1]); putreg(regs[3]); putreg(regs[2]); /* Order is intentional */
  putchar('\n');

  /* Display the processor name string if available
   * It's an extended function on Intel and AMD
   */
  if((brand = brand_string()) != NULL){
    puts(brand);
  } else if(Verbose == 0){
    printf("No brand string; try -v or -v -v\n");
  }

  /* See if hypervisor is present */
  regs[0] = 0x40000000;
  cpuid(regs);
  if(regs[0] >= 0x40000000){
    /* Hypervisor present */
    /* http://lwn.net/Articles/301888 */
    printf("Hypervisor present: ");
    putreg(regs[1]); putreg(regs[2]); putreg(regs[3]); /* Order is intentional */
    putchar('\n');     putchar('\n'); 
  }

  if(Instructions)
    show_instructions();

  if(Cache)
    show_cache();

  if(Verbose < 1)
    exit(0);

  printf("CPUID raw data:\n");
  printf("EAX in      EAX      EBX      ECX      EDX\n");
  /* Dump all the CPUID results in raw hex */
  showregs(0);
  showregs(0x40000000); /* Reserved for hypervisors */
  showregs(0x80000000);

  if(Verbose < 2)
    exit(0);

  regs[0] = 0;
  cpuid(regs);
  maxi = regs[0] & 0xffff; /* The high-order word is non-zero on some Cyrix CPUs */  
  vendor = regs[1];

  printf("Vendor ID: ");
  putreg(regs[1]); putreg(regs[3]); putreg(regs[2]); /* Order is intentional */
  putchar('\n');

  /* How many extended pages? */
  regs[0] = 0x80000000;
  cpuid(regs);
  if(regs[0] & 0x80000000)
    maxei = regs[0] & 0xffff;
  else
    maxei = 0;


  printf("Basic CPUID leaves: %d; extended CPUID leaves: %d\n",maxi,maxei);
  putchar('\n');

  switch(vendor){
  case 0x756e6547: /* 'GenuineIntel' */
    dointel();
    break;
  case 0x68747541: /* 'AuthenticAMD' */
    doamd();
    break;
  case 0x69727943: /* Cyrix */
    docyrix();
    break;
  case 0x646f6547: /* 'Geode by NSC' */
    dogeode();
    break;

  case 0x746e6543: /* 'CentaurHauls' */
    dointel(); /* Change this when I implement do_via() */
    break;
  case 0x52697365: /* Rise */
  case 0x65736952: /* Rise */
  case 0x20536953: /* SiS */
  default:
    printf("Unknown vendor, decoding as Intel\n");
    dointel();
    break;
  }
  printf("\n");

  regs[0] = 0x40000000;
  cpuid(regs);

  if(regs[0] >= 0x40000000){
    /* Hypervisor present */
    /* http://lwn.net/Articles/301888 */
    printf("Hypervisor leaf 0x40000010\n");

    regs[0] = 0x40000010;
    cpuid(regs);
    printf("Virtual TSC frequency: %ld kHz\n",regs[0]);
    printf("Bus frequency: %ld kHz\n",regs[1]);
  }
  exit(0);
}

void putreg(unsigned int x){
  int j;

  for(j=0;j<4;j++){
    if(x & 0xff)
      putchar(x);
    x >>= 8;
  }
}
void showregs(unsigned int start){
  unsigned int j;
  unsigned int regs[4];
  unsigned int finish;

  finish = start;
  do {
    regs[0] = start;
    cpuid(regs);
    if((start & 0x0fffffff) == 0)
      finish = regs[0];
    printf("%08x:",start);
    for(j=0;j<4;j++)
      printf(" %08x",regs[j]);
    putchar('\n');
  } while (++start <= finish);
}


int scompare(const void *a,const void *b){
  char *ap,*bp;

  if(a == b)
    return 0; /* Including both null */
  if(a == NULL)
    return -1;
  if(b == NULL)
    return 1;

  ap = *(char **)a;
  bp = *(char **)b;

  if(ap == bp)
    return 0;
  if(ap == NULL)
    return -1;
  if(bp == NULL)
    return 1;

  return strcmp(ap,bp);
}

int ffcompare(const void *a,const void *b){
  struct featureflag *ap,*bp;

  if(a == b)
    return 0; /* Including both null */
  if(a == NULL)
    return -1;
  if(b == NULL)
    return 1;

  ap = *(struct featureflag **)a;
  bp = *(struct featureflag **)b;

  if(ap == bp)
    return 0;
  if(ap == NULL)
    return -1;
  if(bp == NULL)
    return 1;

  return strcmp(ap->full,bp->full);
}

void illtrap(int x){
  if(x == SIGILL)
    printf("Illegal instruction trap; CPUID instruction probaby not supported\n");
  else
    printf("Caught trap %d\n",x);
  exit(1);
}

/* Extract the human-readable CPU brand string, if available */
char *brand_string(void){
  unsigned int regs[4],i;
  char *cp;
  static char result[50]; /* Maximum 48 characters */

  /* If the extended function is available, EAX will be at least 0x80000004 */
  regs[0] = 0x80000000;
  cpuid(regs);
  if(regs[0] < 0x80000004)
    return NULL; /* Extended CPUID function with brand string not available */

  cp = result;
  for(i=0x80000002;i<=0x80000004;i++){
    unsigned int j,k,c;

    regs[0] = i;
    cpuid(regs);
    /* Extract characters from registers E[ABCD]X, low byte first */
    for(j=0;j<4;j++){
      c = regs[j];
      for(k=0;k<4;k++){
	/* Skip leading spaces */
	if(cp != result || (c & 0xff) != ' ')
	  *cp++ = c;
	c >>= 8;
      }
    }
  }
  *cp = '\0';
  /* Remove trailing spaces */
  while(cp > result && cp[-1] == ' ')
    *--cp = '\0';

  return result;
}

/* Use Intel for these */
int std_ecx_bits[33] = {0,9,13,19,20,22,23,27,-1};
int std_edx_bits[33] = {4,8,15,19,23,24,25,26,-1};
/* And AMD for these */
int ext_ecx_bits[33] = {5,6,7,8,11,-1};
int ext_edx_bits[33] = {11,22,30,31,-1};


/* List supported instructions, using both Intel and AMD tables */
void show_instructions(void){
  unsigned int regs[4];
  int i,j;
  struct featureflag *list[160];
  int count = 0;

  regs[0] = 1;
  cpuid(regs);
  for(i=0;i<33;i++){
    j = std_ecx_bits[i];
    if(j == -1)
      break;
    if(regs[2] & (1 << j)){
      list[count++] = &Intel_standard_ecx[j];
    }
  }
  for(i=0;i<33;i++){
    j = std_edx_bits[i];
    if(j == -1)
      break;
    if(regs[3] & (1 << j)){
      list[count++] = &Intel_standard_edx[j];
    }
  }
  regs[0] = 0x80000000;
  cpuid(regs);
  if(regs[0] > 0x80000000){

    regs[0] = 0x80000001;
    cpuid(regs);
    
    for(i=0;i<33;i++){
      j = ext_ecx_bits[i];
      if(j == -1)
	break;
      if(regs[2] & (1 << j)){
	list[count++] = &AMD_extended_ecx[j];
      }
    }
    for(i=0;i<33;i++){
      j = ext_edx_bits[i];
      if(j == -1)
	break;
      if(regs[3] & (1 << j)){
	list[count++] = &AMD_extended_edx[j];
      }
    }
  }
  qsort(list,count,sizeof(*list),ffcompare);

  for(i=0;i<count;i++)
    printf("%-12s %s\n",list[i]->abbrev,list[i]->full);

}

void decode_feature_flags(struct featureflag table[],int reg){
  int i;

  for(i=0;i<32;i++){
    if(reg & (1<<i)){
      printf("%2d %-12s %s\n",i,
	     table[i].abbrev ? table[i].abbrev : "",
	     table[i].full ? table[i].full : "Reserved");
    }
  }
}

/* Display cache information */
void show_cache(){
  unsigned int regs[4];
  int maxi;
  int vendor;

  /* What kind of CPU is this? */
  regs[0] = 0;
  cpuid(regs);
  vendor = regs[1];

  switch(vendor){
  case 0x756e6547: /* 'GenuineIntel' */
    do_intel_cache();
    break;
  case 0x68747541: /* 'AuthenticAMD' */
    doamd();
    break;
  case 0x69727943: /* Cyrix */
    docyrix();
    break;
  case 0x646f6547: /* 'Geode by NSC' */
    dogeode();
    break;

  case 0x746e6543: /* IDT */
  case 0x52697365: /* Rise */
  case 0x65736952: /* Rise */
  case 0x20536953: /* SiS */
  default:
    printf("Unknown vendor, decoding as Intel\n");
    dointel();
    break;
  }
}
