///////////////////////////////////////////////////////////////////////////////
/// @file deMachineWindowsCPU.cpp
///
/// @brief CPU Detector
///
/// @author Lightning
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Nov 2001
/// @author Lightning
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////




//=================================================================================
// Includes
//=================================================================================

//#include <cstdio>

#pragma warning (push)
#pragma warning (disable : 4201)
#include <windows.h> // for mmsystem and other stuff
#include <mmsystem.h>
#pragma warning (pop)

#include "deMachineWindowsCPU.hpp"


//=================================================================================
// deMachineWindowsCPU::deMachineWindowsCPU()
//
// Constructor 
//=================================================================================
deMachineWindowsCPU::deMachineWindowsCPU()
{
	CreationResult = deFALSE;

	CPUInfo.CPUType = deCPUInfo::DECPUTYPE_UNKNOWN;
	CPUInfo.Name[0] = NULL;
	CPUInfo.Vendor[0] = NULL;
	CPUInfo.Mhz = 0.0f;
	CPUInfo.Freq = 1.0f;
	CPUInfo.ProcessorType = ' ';
	CPUInfo.Family = ' ';
	CPUInfo.Model = ' ';
	CPUInfo.Stepping = ' ';
	CPUInfo.ExtendedFamily = ' ';
	CPUInfo.Cache[0] = 0;
	CPUInfo.Cache[1] = 0;
	CPUInfo.Cache[2] = 0;
	CPUInfo.Cache[3] = 0;
	CPUInfo.Cache[4] = 0;
	CPUInfo.Cache[5] = 0;
	CPUInfo.Cache[6] = 0;
	CPUInfo.Cache[7] = 0;
	CPUInfo.FPU = deFALSE;
	CPUInfo.VME = deFALSE;
	CPUInfo.DE = deFALSE;
	CPUInfo.PSE = deFALSE;
	CPUInfo.TSC = deFALSE;
	CPUInfo.MSR = deFALSE;
	CPUInfo.MCE = deFALSE;
	CPUInfo.CX8 = deFALSE;
	CPUInfo.APIC = deFALSE;
	CPUInfo.SEP = deFALSE;
	CPUInfo.MTRR = deFALSE;
	CPUInfo.PGE = deFALSE;
	CPUInfo.MCA = deFALSE;
	CPUInfo.CMOV = deFALSE;
	CPUInfo.PAT = deFALSE;
	CPUInfo.PSE36 = deFALSE;
	CPUInfo.MMX = deFALSE;
	CPUInfo.FXSR = deFALSE;
	CPUInfo.SSE = deFALSE;
	CPUInfo.PAE = deFALSE;
	CPUInfo.INTEL_PN = deFALSE;
	CPUInfo.INTEL_CLFSH = deFALSE;
	CPUInfo.INTEL_DS = deFALSE;
	CPUInfo.INTEL_ACPI = deFALSE;
	CPUInfo.INTEL_SSE2 = deFALSE;
	CPUInfo.INTEL_SS = deFALSE;
	CPUInfo.INTEL_TM = deFALSE;
	CPUInfo.AMD_AMIE = deFALSE;
	CPUInfo.AMD_3DNOWEXT = deFALSE;
	CPUInfo.AMD_3DNOW = deFALSE;
	CPUInfo.AMD_MPCAPABLE = deFALSE;

	__int64 Clock = 0;
	long Time = 0;

	//start the timer for figuring out the mhz
	StartTimer(&Time, &Clock);

	//now do some processing while we run a timer function for mhz
	GetVendorAndCPUInfo();

	//get the number of processors
	SYSTEM_INFO SysInfo;
	GetSystemInfo(&SysInfo);

	CPUInfo.CPUCount = SysInfo.dwNumberOfProcessors;

	//now figure out the mhz
	StopTimer(&Time, &Clock);

	CreationResult = deTRUE;
	return;
}



//=================================================================================
// deMachineWindowsCPU::StartTimer()
//
// Grab the current time to compare with later to figure out the Mhz.
//
// Check to see if CPUID works.  If it does, check to see if the TSC flag is
// set which indicates that RDTSC will work on the chip. Because Clock will then
// be set, Clock is used to determine if StopTimer can call RDTSC also
//=================================================================================
void deMachineWindowsCPU::StartTimer(long *Time, __int64 *Clock)
{
	_asm
	{
		//lets see if we can execute CPUID by seeing if we can flip the 21st bit
		//of the flags register

		pushfd
		pop eax

		//save the original
		mov ecx, eax

		//swap the 21st bit
		xor eax, 200000h

		//push it back on the stack, set the flags
		push eax
		popfd

		//now get the flags
		pushfd
		pop eax

		//see if the values are the same
		xor eax, ecx
		jz NoCPUIDSupport

		//grab the features
		mov eax, 1
		CPUID

		//if the RDTSC check fails, eax and edx needs to be blank so it has no
		//time in it

		mov ebx, edx
		xor eax, eax
		xor edx, edx

		//see if the TSC flag is set so we can call RDTSC, if not then exit out
		and ebx, CPU_INTEL_TSC
		jz NoCPUIDSupport

		//obtain the RDTSC time
		RDTSC

	NoCPUIDSupport:
		mov ecx, Clock
		mov	[ecx],eax
		mov	[ecx+4],edx
	}

	//if we got a clock time, get the time from the OS
	if(*Clock)
	{
		*Time = timeGetTime();
	}

	return;
}



//=================================================================================
// deMachineWindowsCPU::GetVendorAndCPUInfo()
//
// Get the vendor and CPU info
//=================================================================================
void deMachineWindowsCPU::GetVendorAndCPUInfo(void)
{
	long RetValue = 0;
	long LargestExtendedFunction = 0;
	long Features = 0;
	long ExtFeatures = 0;
	char Vendor[13];
	char ProcessorType = ' ';
	char Family = ' ';
	char Model = ' ';
	char Stepping = ' ';

	Vendor[0] = NULL;

	//get the Vendor string
	_asm
	{
		//lets see if we can execute CPUID by seeing if we can flip the 21st bit
		//of the flags register

		pushfd
		pop eax

		//save the original
		mov ecx, eax

		//swap the 21st bit
		xor eax, 200000h

		//push it back on the stack, set the flags
		push eax
		popfd

		//now get the flags
		pushfd
		pop eax

		//see if the values are the same
		xor eax, ecx
		jz NoCPUIDSupport
		
		//CPUID supported, go ahead and grab some info
		xor eax, eax
		CPUID

		//grab the vendor and see if we reconize it
		lea eax, Vendor
		mov [eax], ebx
		mov [eax+4], edx
		mov [eax+8], ecx

		//see if it is an Intel CPU and flag accordingly
		cmp [eax], 756E6547h		//Genu
		jne AMDCheck

		cmp [eax+4], 49656E69h		//ineI
		jne EndCPUCheck

		cmp [eax+8], 6C65746Eh		//ntel
		jne EndCPUCheck

		//Intel CPU
		mov RetValue, 1
		jmp short EndCPUCheck

	AMDCheck:
		cmp [eax], 68747541h		//Auth
		jne EndCPUCheck

		cmp [eax+4], 69746E65h		//enti
		jne EndCPUCheck

		cmp [eax+8], 444D4163h		//cAMD
		jne EndCPUCheck

		mov RetValue, 2

	EndCPUCheck:

	NoCPUIDSupport:
	}

	strcpy(CPUInfo.Vendor, Vendor);

	if(CPUInfo.Vendor[0])
	{
		//save off the CPU type
		if (RetValue == 0)
         CPUInfo.CPUType = deCPUInfo::DECPUTYPE_UNKNOWN;
         
		if (RetValue == 1)
         CPUInfo.CPUType = deCPUInfo::DECPUTYPE_INTEL;
         
		if (RetValue == 2)
         CPUInfo.CPUType = deCPUInfo::DECPUTYPE_AMD;

		//grab the default features and the processor info then see if extended features
		//are allowed
		_asm
		{
			mov eax, 01h
			CPUID
			mov Features, edx

			mov dx, ax
			and dx, 0F0Fh

			//grab the Processor info
			shr al, 4
			mov Stepping, dl
			mov Model, al

			shr ah, 4
			and ah, 3
			mov Family, dh
			mov ProcessorType, ah

			//save the largest extended feature function allowed
			mov eax, 080000000h
			CPUID
			mov LargestExtendedFunction, eax

			cmp eax, 80000001h
			jb NoExtendedFunctions

			//grab the extended features if the exist
			mov eax, 080000001h
			CPUID
			mov ExtFeatures, edx

		NoExtendedFunctions:
		}

		CPUInfo.ProcessorType = ProcessorType;
		CPUInfo.Family = Family;
		CPUInfo.Model = Model;
		CPUInfo.Stepping = Stepping;

		//grab info based on the type of CPU
		switch(RetValue)
		{
			case 1:
				GetIntelCPUInfo(LargestExtendedFunction);
				break;

			case 2:
				GetAMDCPUInfo(LargestExtendedFunction);
				break;

			default:
				//sprintf(CPUInfo.Name, "%s", PROCESSOR_UNKNOWN);
				strcpy(CPUInfo.Name, PROCESSOR_UNKNOWN);
				break;
		}

	}
	else
	{
		//don't know the CPU information. Not even the type At least tell them it is an
		//unknown
		//sprintf(CPUInfo.Name, "%s", PROCESSOR_UNKNOWN);
		strcpy(CPUInfo.Name, PROCESSOR_UNKNOWN);
	}

	if (Features & CPU_INTEL_FPU)
		CPUInfo.FPU = deTRUE;

	if (Features & CPU_INTEL_VME)
		CPUInfo.VME = deTRUE;

	if (Features & CPU_INTEL_DE)
		CPUInfo.DE = deTRUE;

	if (Features & CPU_INTEL_PSE)
		CPUInfo.PSE = deTRUE;

	if (Features & CPU_INTEL_TSC)
		CPUInfo.TSC = deTRUE;

	if (Features & CPU_INTEL_MSR)
		CPUInfo.MSR = deTRUE;

	if (Features & CPU_INTEL_MCE)
		CPUInfo.MCE = deTRUE;

	if (Features & CPU_INTEL_CX8)
		CPUInfo.CX8 = deTRUE;

	if (Features & CPU_INTEL_APIC)
		CPUInfo.APIC = deTRUE;

	if (Features & CPU_INTEL_SEP)
		CPUInfo.SEP = deTRUE;

	if (Features & CPU_INTEL_MTRR)
		CPUInfo.MTRR = deTRUE;

	if (Features & CPU_INTEL_PGE)
		CPUInfo.PGE = deTRUE;

	if (Features & CPU_INTEL_MCA)
		CPUInfo.MCA = deTRUE;

	if (Features & CPU_INTEL_CMOV)
		CPUInfo.CMOV = deTRUE;

	if (Features & CPU_INTEL_PAT)
		CPUInfo.PAT = deTRUE;

	if (Features & CPU_INTEL_PSE36)
		CPUInfo.PSE36 = deTRUE;

	if (Features & CPU_INTEL_MMX)
		CPUInfo.MMX = deTRUE;

	if (Features & CPU_INTEL_FXSR)
		CPUInfo.FXSR = deTRUE;

	if (Features & CPU_INTEL_SSE)
		CPUInfo.SSE = deTRUE;

	if (Features & CPU_AMD_PAE)
		CPUInfo.PAE = deTRUE;

	if (CPUInfo.CPUType == deCPUInfo::DECPUTYPE_INTEL)
	{
		if (Features & CPU_INTEL_PN)
			CPUInfo.INTEL_PN = deTRUE;

		if (Features & CPU_INTEL_CLFSH)
			CPUInfo.INTEL_CLFSH = deTRUE;

		if (Features & CPU_INTEL_DS)
			CPUInfo.INTEL_DS = deTRUE;

		if (Features & CPU_INTEL_ACPI)
			CPUInfo.INTEL_ACPI = deTRUE;

		if (Features & CPU_INTEL_SSE2)
			CPUInfo.INTEL_SSE2 = deTRUE;

		if (Features & CPU_INTEL_SS)
			CPUInfo.INTEL_SS = deTRUE;

		if (Features & CPU_INTEL_TM)
			CPUInfo.INTEL_TM = deTRUE;
	}      

	if (CPUInfo.CPUType == deCPUInfo::DECPUTYPE_AMD)
	{
		if (ExtFeatures & CPU_AMD_EXT_AMIE)
			CPUInfo.AMD_AMIE = deTRUE;

		if (ExtFeatures & CPU_AMD_EXT_3DNOWEXT)
			CPUInfo.AMD_3DNOWEXT = deTRUE;

		if (ExtFeatures & CPU_AMD_EXT_3DNOW)
			CPUInfo.AMD_3DNOW = deTRUE;

		if (ExtFeatures & CPU_AMD_EXT_MPCAPABLE)
			CPUInfo.AMD_MPCAPABLE = deTRUE;
	}      

	return;
}



//=================================================================================
// deMachineWindowsCPU::GetIntelCPUInfo()
//
// Get Intel-specific CPU info
//=================================================================================
void deMachineWindowsCPU::GetIntelCPUInfo(long LargestExtendedFunction)
{
	char  ExtendedFamily = ' ';

	//check and see if the CPU name is supported and if so grab it
	if(LargestExtendedFunction >= 0x80000004)
	{
		GetEmbeddedCPUName();

		if(CPUInfo.Family == 0x0F)
		{
			//due to this being a P4 and higher, there is also an extended family value,
			//grab it
			_asm
			{
				mov eax, 1
				CPUID

				shr eax, 18
				mov ExtendedFamily, al
			}

         CPUInfo.ExtendedFamily = ExtendedFamily;
		}
	}
	else
	{
		//sprintf(CPUInfo.Name, "%s", GetIntelCPUName());
		strcpy(CPUInfo.Name, GetIntelCPUName());
	}

	GetIntelCPUCacheValues();

	return;
}



//=================================================================================
// deMachineWindowsCPU::GetEmbeddedCPUName()
//
// Get embedded CPU Name
//=================================================================================
void deMachineWindowsCPU::GetEmbeddedCPUName(void)
{
   char  Name[48];


   Name[0] = NULL;

	_asm
	{
		lea edi, Name

		mov eax, 080000002h
		CPUID

		mov [edi], eax
		mov [edi+4], ebx
		mov [edi+8], ecx
		mov [edi+12], edx
		add edi, 16

		mov eax, 080000003h
		CPUID
		mov [edi], eax
		mov [edi+4], ebx
		mov [edi+8], ecx
		mov [edi+12], edx
		add edi, 16

		mov eax, 080000004h
		CPUID
		mov [edi], eax
		mov [edi+4], ebx
		mov [edi+8], ecx
		mov [edi+12], edx
	}

   strcpy(CPUInfo.Name, Name);

	return;
}



//=================================================================================
// deMachineWindowsCPU::GetIntelCPUName()
//
// Get Intel CPU Name
//=================================================================================
char *deMachineWindowsCPU::GetIntelCPUName(void)
{
   char *CPUName = NULL;
   char BrandID;

	switch (CPUInfo.Family)
	{
		case 4:
			if(CPUInfo.Model == 4)
				CPUName = INTEL_486SL;

			if(CPUInfo.Model == 7)
				CPUName = INTEL_DX2WB;

			if(CPUInfo.Model == 8)
			{
				if(CPUInfo.ProcessorType == 1)
					CPUName = INTEL_DX4OVERDRIVE;
				else
					CPUName = INTEL_DX4;
			}

			break;
		
		case 5:
			if (CPUInfo.ProcessorType == 1)
			{
				//1, 2, and 3
				if(CPUInfo.Model >= 1 && CPUInfo.Model <= 3)
					CPUName = INTEL_PENTIUMOVERDRIVE;

				if(CPUInfo.Model == 4)
					CPUName = INTEL_PENTIUMOVERDRIVEMMX;
			}
			else
			{
				if(CPUInfo.Model == 1 || CPUInfo.Model == 2)
					CPUName = INTEL_PENTIUM;

				if(CPUInfo.Model == 4)
					CPUName = INTEL_PENTIUMMMX;
			}
			
			break;
				
		case 6:
			if(CPUInfo.Model == 1)
				CPUName = INTEL_PPRO;

			if(CPUInfo.Model == 3)
			{
				if(CPUInfo.ProcessorType == 1)
					CPUName = INTEL_PIIOVERDRIVE;
				else
					CPUName = INTEL_PII;
			}

			if(CPUInfo.Model == 5 || CPUInfo.Model == 6)
			{
				switch(GetIntelCPUCache())
				{
					case 0:
						CPUName = INTEL_PII;
						break;

					case 1:
						CPUName = INTEL_CELERON;
						break;

					case 2:
						CPUName = INTEL_PIIXEON;
						break;
				}
			}

			if(CPUInfo.Model == 7)
			{
				switch(GetIntelCPUCache())
				{
					case 0:
						CPUName = INTEL_PIII;
						break;
					case 1:
						CPUName = INTEL_CELERON;
						break;

					case 2:
						CPUName = INTEL_PIIIXEON;
						break;
				}
			}

			if(CPUInfo.Model >= 8)
			{
				//grab the BrandID
				_asm
				{
					mov eax, 01h
					CPUID
					mov BrandID, bl
				}

				switch(BrandID)
				{
					case 1:
						CPUName = INTEL_CELERON;
						break;
					case 2:
						CPUName = INTEL_PIII;
						break;
					case 3:
						CPUName = INTEL_PIIIXEON;
						break;
				}
			}

			break;

		default:
			CPUName = PROCESSOR_UNKNOWN;
			break;
				
	}

	if(CPUName == NULL)
		CPUName = PROCESSOR_UNKNOWN;

	return CPUName;
}



//=================================================================================
// deMachineWindowsCPU::GetIntelCPUCache()
//
// Get Intel CPU cache
//=================================================================================
long deMachineWindowsCPU::GetIntelCPUCache(void)
{
	long CPUType = 0;

	//No L2 Cache, CPUType = 1
	//1M or more, CPUType = 2
	//All other, CPUType = 0

	_asm
	{
		mov eax, 2
		CPUID

		//check EAX
		GetIntelCPUCache_EAX:
			cmp al, 040h
			jne GetIntelCPUCache_EAX_1M

			mov CPUType, 1h
			jmp GetIntelCPUCache_Done

		GetIntelCPUCache_EAX_1M:
			cmp al, 044h
			jb GetIntelCPUCache_EAX_Loop

			mov CPUType, 2h
			jmp GetIntelCPUCache_Done

		GetIntelCPUCache_EAX_Loop:
			shr eax, 8
			jnz GetIntelCPUCache_EAX

		//if ebx is blank then goto ecx, if not then swap eax and ebx and run on ebx
		or ebx, ebx
		jz GetIntelCPUCache_ECX

		xchg eax, ebx
		jmp GetIntelCPUCache_EAX

		//if ecx is blank then goto edx, if not then swap eax and ecx and run on ecx
		GetIntelCPUCache_ECX:
			or ecx, ecx
			jz GetIntelCPUCache_EDX

			xchg eax, ecx
			jmp GetIntelCPUCache_EAX

		//if edx is blank then exit, if not then swap eax and edx and run on edx
		GetIntelCPUCache_EDX:
			or edx, edx
			jz GetIntelCPUCache_Done

			xchg eax, edx
			jmp GetIntelCPUCache_EAX

		GetIntelCPUCache_Done:
	}

	return CPUType;
}



//=================================================================================
// deMachineWindowsCPU::GetIntelCPUCacheValues()
//
// Get Intel CPU cache values
//=================================================================================
void deMachineWindowsCPU::GetIntelCPUCacheValues(void)
{
	long CacheEAX, CacheEBX, CacheECX, CacheEDX;

	_asm
	{
		mov eax, 2
		CPUID

		mov CacheEAX, eax
		mov CacheEBX, ebx
		mov CacheECX, ecx
		mov CacheEDX, edx
	}

	CPUInfo.Cache[0] = CacheEAX;
	CPUInfo.Cache[1] = CacheEBX;
	CPUInfo.Cache[2] = CacheECX;
	CPUInfo.Cache[3] = CacheEDX;

	return;
}



//=================================================================================
// deMachineWindowsCPU::GetAMDCPUInfo()
//
// Get AMD-specific CPU info
//=================================================================================
void deMachineWindowsCPU::GetAMDCPUInfo(long LargestExtendedFunction)
{
	//go grab the CPU name from the chip
	if(LargestExtendedFunction >= 0x80000004)
		GetEmbeddedCPUName();

	//grab any cache info that can be obtained
	if(LargestExtendedFunction >= 0x80000005)
		GetAMDCPUCacheValues(LargestExtendedFunction);

	return;
}



//=================================================================================
// deMachineWindowsCPU::GetAMDCPUCacheValues()
//
// Get AMD CPU cache values
//=================================================================================
void deMachineWindowsCPU::GetAMDCPUCacheValues(long LargestExtendedFunction)
{
   long  Cache[8];


   Cache[0] = 0;
   Cache[1] = 0;
   Cache[2] = 0;
   Cache[3] = 0;
   Cache[4] = 0;
   Cache[5] = 0;
   Cache[6] = 0;
   Cache[7] = 0;

	_asm
	{
		mov eax, 80000005h
		CPUID

		//grab the L1 cache info
		//MSVC won't read the array as a long but as chars so
		//the positions are multiplied by 4 to offset the places
		//correctly
		mov Cache[0*4], eax
		mov Cache[1*4], ebx
		mov Cache[2*4], ecx
		mov Cache[3*4], edx
	}

	if(LargestExtendedFunction >= 0x80000006)
	{
		_asm
		{
			mov eax, 80000006h
			CPUID

			//grab the L1 cache info
			mov Cache[4*4], eax
			mov Cache[5*4], ebx
			mov Cache[6*4], ecx
			mov Cache[7*4], edx
		}
	}

   CPUInfo.Cache[0] = Cache[0];
   CPUInfo.Cache[1] = Cache[1];
   CPUInfo.Cache[2] = Cache[2];
   CPUInfo.Cache[3] = Cache[3];
   CPUInfo.Cache[4] = Cache[4];
   CPUInfo.Cache[5] = Cache[5];
   CPUInfo.Cache[6] = Cache[6];
   CPUInfo.Cache[7] = Cache[7];

	return;
}



//=================================================================================
// deMachineWindowsCPU::StopTimer()
//
// Stop Timer
//=================================================================================
void deMachineWindowsCPU::StopTimer(long *Time, __int64 *Clock)
{
	__int64 ElapsedClock;
	long ElapsedTime;
	LARGE_INTEGER Frequency;

	//Get the frequency of the CPU
	QueryPerformanceFrequency(&Frequency);
	CPUInfo.Freq = (float)Frequency.LowPart;

	//if we grabbed the RDTSC time then calc the speed of the chip
	if(*Clock)
	{
		//figure out the Mhz of the processor
		do
		{
			ElapsedTime = timeGetTime() - *Time;
		} while (ElapsedTime < 1000);	//go thru until a second has passed. I could do
										//500 ms but that would make it slightly more inaccurate
		//grab the RDTSC time now and return it
		_asm
		{
			RDTSC
			mov		dword ptr ElapsedClock,eax
			mov		dword ptr ElapsedClock+4,edx
		}

		//do some slight math on how much time has passed to determine the Mhz. You may note
		//that i don't do a check to round it to a valid number. This is due to overclocking
		//allowing odd values. It should be fairly accurate though
		ElapsedClock -= *Clock;
		ElapsedTime -= 4; // take off a few milliseconds to counter-act timer resolution error
		CPUInfo.Mhz = (ElapsedClock / (float)(1000 * ElapsedTime));
	}

	return;
}



//=================================================================================
// deMachineWindowsCPU::GetCPUInfo()
//
// Return the CPUInfo collected
//=================================================================================
deBoolean deMachineWindowsCPU::GetCPUInfo(deCPUInfo *Info)
{
   *Info = CPUInfo;

   return deTRUE;
}

