/*      x86-cpuid.cpp
//      
//      Copyright 2005-2011 Lucas Tsatiris <systester.project@gmail.com>
//      
//      This program is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; either version 2 of the License, or
//      (at your option) any later version.
//      
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//      
//      You should have received a copy of the GNU General Public License
//      along with this program; if not, write to the Free Software
//      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.
//      
*/      



/* Determine the x86 or amd64 CPU */

/* Intel and AMD x86 CPUID display program v 3.3 (1 Jan 2002)
 * Copyright 2002 Phil Karn, KA9Q
 * Updated 24 Apr 2001 to latest Intel CPUID spec
 * Updated 22 Dec 2001 to decode Intel flag 28, hyper threading
 * Updated 1 Jan 2002 to cover AMD Duron, Athlon
 * May be used under the terms of the GNU Public License (GPL)

 * Reference documents:
 * ftp://download.intel.com/design/pro/applnots/24161809.pdf  (AP-485)
 * http://developer.intel.com/design/Pentium4/manuals/24547103.pdf
 * http://developer.intel.com/design/pentiumiii/applnots/24512501.pdf (AP-909)
 * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/20734.pdf
 * 
 */

/* cpuid.c
 * These are the parts of the source needed for the System Stability Tester Project
 * Distributed under the term of the GPL.
 * Modified by Lucas Tsatiris 2005, 2007, 2008.
 */

#if defined (_MSC_VER)		/* Microsoft Visual C++ */
#include <stdio.h>
#include <string.h>
#include <intrin.h>

int
CPUID (char *ChipID, char *VendorID)
{
  unsigned nIds;
  int CPUInfo[4], retval = 0;
  char CPUString[64];
  __cpuid (CPUInfo, 0);
  nIds = CPUInfo[0];
  memset (CPUString, 0, 64);
  *((int *) CPUString) = CPUInfo[1];
  *((int *) (CPUString + 4)) = CPUInfo[3];
  *((int *) (CPUString + 8)) = CPUInfo[2];

  if (strcmp (CPUString, "GenuineIntel") == 0)
    retval = 1;

  if (strcmp (CPUString, "AuthenticAMD") == 0)
    retval = 2;

  if (retval != 0)
    {
      memset (ChipID, 0, 64);
      __cpuid (CPUInfo, 0x80000002);
      memcpy (ChipID, CPUInfo, sizeof (CPUInfo));

      __cpuid (CPUInfo, 0x80000003);
      memcpy (ChipID + 16, CPUInfo, sizeof (CPUInfo));

      __cpuid (CPUInfo, 0x80000004);
      memcpy (ChipID + 32, CPUInfo, sizeof (CPUInfo));
    }

  return retval;
}
#else /* Unices or Windows with GCC */
unsigned long eax, ebx, ecx, edx;
int k;

#if (defined(__SUNPRO_CC) || defined(__SUNPRO_C)) && defined(__sun)	/* Solaris with SunCC */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


void
cpuid (unsigned long in, unsigned long a, unsigned long b, unsigned long c,
       unsigned long d)
{
  struct
  {
    uint32_t r_eax, r_ebx, r_ecx, r_edx;
  } _r, *rp = &_r;
  static const char devname[] = "/dev/cpu/self/cpuid";
  d = open (devname, O_RDONLY);
  pread (d, rp, sizeof (_r), in);
  eax = _r.r_eax;
  ebx = _r.r_ebx;
  ecx = _r.r_ecx;
  edx = _r.r_edx;
}


#elif defined (__GNUC__) && defined (__APPLE__)	/* Mac OS X (Intel x86) with GCC */
#define cpuid(in,a,b, c, d)  asm("pushl %%ebx \n\t"\
                                 "cpuid \n\t"\
                                 "movl %%ebx, %1 \n\t"\
                                 "popl %%ebx \n\t"\
                                 : "=a" (a), "=r" (b), "=c" (c), "=d" (d) : "a" (in));

#elif defined (__GNUC__)	/* Linux or Windows with GCC */

#define cpuid(in,a,b,c,d)\
  asm("cpuid": "=a" (a), "=b" (b), "=c" (c), "=d" (d) : "a" (in));

#endif /* __SUNPRO_CC && __sun */

void
printregs (char *ChipID, int eax, int ebx, int ecx, int edx)
{
  int j, l;
  char string[64], xstring[64];

  string[16] = '\0';
  for (j = 0; j < 4; j++)
    {
      string[j] = eax >> (8 * j);
      string[j + 4] = ebx >> (8 * j);
      string[j + 8] = ecx >> (8 * j);
      string[j + 12] = edx >> (8 * j);
    }
  j = 0;
  while ((string[j] < 33) && (j < 16))
    j++;

  if (j == 16)
    return;
  strcpy (xstring, &string[j]);
  l = strlen (xstring);
  ChipID[k - 1] = xstring[0];
  for (j = 1; j < l; j++)
    {
      if (xstring[j] >= ' ')
	{
	  if (ChipID[k - 1] == ' ')
	    {
	      if (xstring[j] != ' ')
		ChipID[k++] = xstring[j];
	    }
	  else
	    ChipID[k++] = xstring[j];
	}
    }
  ChipID[k++] = 0;
}


/* Intel-specific information */
void
dointel (char *ChipID)
{
  cpuid (0x80000000, eax, ebx, ecx, edx);
  if (eax & 0x80000000)
    {
      /* Extended feature/signature bits supported */
      int maxe = eax;
      if (maxe >= 0x80000004)
	{
	  int i;

	  for (i = 0x80000002; i <= 0x80000004; i++)
	    {
	      unsigned long eax, ebx, ecx, edx;

	      cpuid (i, eax, ebx, ecx, edx);
	      printregs (ChipID, eax, ebx, ecx, edx);
	    }
	}
    }
}

/* AMD-specific information */
void
doamd (char *ChipID)
{
  /* Check for presence of extended info */
  // cpuid (0x80000000, maxei, unused, unused, unused);
  cpuid (0x80000000, eax, ebx, ecx, edx);
  if (eax >= 0x80000002)
    {
      /* Processor identification string */
      int j;
      for (j = 0x80000002; j <= 0x80000004; j++)
	{
	  // unsigned long eax, ebx, ecx, edx;

	  cpuid (j, eax, ebx, ecx, edx);
	  printregs (ChipID, eax, ebx, ecx, edx);
	}
    }
}

int
CPUID (char *ChipID, char *VendorID)
{
  unsigned long retval;
  int i, n = 0;
  k = 1;

  /* Vendor ID and max CPUID level supported */
  cpuid (0, eax, ebx, ecx, edx);

  for (i = 0; i < 4; i++)
    VendorID[n++] = (ebx >> (8 * i));

  for (i = 0; i < 4; i++)
    VendorID[n++] = (edx >> (8 * i));

  for (i = 0; i < 4; i++)
    VendorID[n++] = (ecx >> (8 * i));

  VendorID[n] = 0;

  switch (ebx)
    {
    case 0x756e6547:		/* Intel */
      retval = 1;
      dointel (ChipID);
      break;
    case 0x68747541:		/* AMD */
      retval = 2;
      doamd (ChipID);
      break;
    default:
      retval = 0;
      strcpy (VendorID, "Unknown CPU vendor");
      break;
    }
  return retval;
}
#endif /* _MSC_VER */
