// cpu.h

// cpu identification, and safe cycle counting support

// NB: no cpu.cpp needed

// IKS Mar 97

#ifndef CPU_H
#define CPU_H

// ************************ Cycle Counting Class ***********************

// see class CycleCounter below
	
// **************************** ProcessorID ***********************


typedef signed long SLONG;


class CPUID
{
public:
// SIMPLE CPU IDENTIFICATION
	CPUID()									{ detect(); /* instantaneous */ }
	// just declare a variable of this class, then call the functions below for results
	// assumes at least 486, and only registers FPU through CPUID_features (compatible processors from late 486s onward)

	ULONG processor() const					{ return family; }
	// returns 4=486, 5=Intel Pentium/Cyrix 6x86/AMD K5, 6=Intel PentiumPro/Cyrix M2/AMD K6

	ULONG isMMX() const						{ return MMX; }
	// returns 1=MMX available, 0=not

	void  get_vendor(char vendor[13]) const	{ for (int i=13; --i>=0; ) vendor[i]=CPUID::vendor[i]; }
	// returns 12char+null terminator string: "GenuineIntel","AuthenticAMD" or "CyrixInstead"

	ULONG RDTSC_available() const			{ return rdtscON; }
	// returns true if RDTSC instruction is supported on the processor 
	// AND available at the current privilege level


// SCAREY STUFF BELOW FOR ADVANCED USE ONLY  
	ULONG CPUID_features() const			{ return features;}
	/* Advanced use only, see Intel processor handbooks
	 CPUID feature bits (all 0 if no CPUID instruction on processor)
			0 FPU
			1 VME
			2 DE
			3 PSE
			4 TSC
			5 MSR
			6 PAE
			7 MCE
			8 CX8
			9 APIC
			10,11 reserved
			12 MTRR
			13 PGE
			14 MCA
			15 CMOV
			16-22 reserved
			23 MMX
			24-31 reserved
	*/

	ULONG CPUID_version() const				{ return version; }
	/* Advanced use only, see Intel processor handbooks
	 CPUID version bits (all 0 if no CPUID instruction on processor)
			0..3	stepping
			4..7	model
			8..11	family, 4=486, 5=Intel Pentium/Cyrix 6x86/AMD K5, 6=Intel PentiumPro/Cyrix M2/AMD K6
			12..13	type	0=OEM chip,1=overdrive,2=dual processor,3=reserved
	 */

private:
	SLONG	cpuidmax;	// -1 -> no CPUID, n>=0 -> can use CPUID with eax<=n
	ULONG	version;	// type13:12,family11:8,model7:4,stepping3:0
	ULONG	features;	// FPU0,TSC4,CMOV15
	ULONG	family;		// 4=486, 5=Pentium, 6=PentiumPro
	ULONG	rdtscON;	// 1=can use, 0=can't due to privilege level or processor doesn't support it
	ULONG	rdpmcON;	// 1=can use RDPMC (but beware CPL=0 needed to program the counters)
	ULONG	MMX;		// 1=MMX supported, 0=isn't (same as features bit 23)
	char	vendor[13];	// actually always 12chars+null

	void detect()
	{
		SLONG	cpuidmax=-1;					// -1 -> no CPUID, n>=0 -> can use CPUID with eax<=n
		ULONG	version=0;						// type13:12,family11:8,model7:4,stepping3:0
		ULONG	features=0;						// FPU0,TSC4,CMOV15
		ULONG	processor=0;					// 4=486, 5=Pentium
		ULONG	rdtscON=0;						// 1->can use RDTSC on current processor at current privilege level
		ULONG	rdpmcON=0;						// 1->can use RDPMC (but beware CPL=0 needed to program the counters)
		ULONG	MMX=0;							// 1->got MMX instructions (same as features bit23)
		char	vendor[13] = "AnonymousCPU";	// actually always 12chars+null

		#ifdef __WATCOM__
			Error	Please use a decent compiler (ie Microsoft)
		#else	// Microsoft version
		__asm
		{
			pushad		// NB: compiler doesn't understand CPUID corrupts edx

			// assume 486 or higher
			mov		processor,4		

			// check for set/clear bit 21 ID flag -> CPUID instruction valid
			pushfd				// push eflags
			pop		eax			// get eflags
			mov		ecx,eax		// save
			xor		eax,200000h	// attempt flip ID in eflags
			push	eax
			popfd
			pushfd
			pop eax
			xor		eax,ecx		// examine flip
			je		endcpuid	// got 486

			// know can execute CPUID
			mov		cpuidmax,0	// -1 -> no CPUID, n>=0 -> can use CPUID with eax<=n

			// get vendor (eax=0)
			mov		eax,0
			_emit	0x0f				//	CPUID
			_emit	0xa2
			mov		cpuidmax,eax		// eax is max value can call CPUID with
			mov		dword ptr vendor, ebx		//    "GenuineIntel"
			mov		dword ptr vendor[+4], edx	// or "AuthenticAMD"
			mov		dword ptr vendor[+8], ecx	// or "CyrixInstead"

			// get version+features (eax=1)
			mov		eax,1
			_emit	0x0f				//	CPUID
			_emit	0xa2
			mov		version,eax
			mov		features,edx
			// hold features in edx

			// NB: Pentium Pro can call CPUID (eax=2) for cache/TLB info
			// not implemented

			// extract processor type from version bits11:8
			shr		eax,8	
			and		eax,0fh
			mov		processor,eax

			// check for performance monitoring counters
			cmp		eax,5
			jl		endcpuid			// mov cr4 is valid for processor>=5 (Pentium)
			_emit	00fh				// 
			_emit	020h				//  mov	eax,CR4
			_emit	0e0h				// 
			and		eax,0100h			// CR4.PCE[bit8]=1:can use RDPMC,PCE=0:restricted to CPL=0
			setnz	byte ptr rdpmcON

			// test for availability of rdtsc
			// enabled in CPL=3 if TimeStampDisable bit in CR4.TSD is not disabled
			and		edx,010h	// features bit 4=TSC
			je		endcpuid	// TSC=0 -> no rdtsc, else guaranteed got CR4
			_emit	00fh			// 
			_emit	020h			//  mov	eax,CR4
			_emit	0e0h			// 
			and		eax,4		// CR4.TSD[bit2]=0:can use RDTSC,TSD=1:restricted to CPL=0
			setz	byte ptr rdtscON

		endcpuid:
			popad		// NB: compiler doesn't understand CPUID corrupts edx
		}
		#endif

		// copy results back into object state
		// since I couldn't get [ebx].version type addressing to work
		CPUID::cpuidmax	= cpuidmax;
		CPUID::version	= version;
		CPUID::features	= features;
		CPUID::family	= processor;
		CPUID::rdtscON	= rdtscON;
		CPUID::rdpmcON	= rdpmcON;
		CPUID::MMX		= (CPUID::features>>23)&1; // MMX=features bit 23
		//NB: MMX can be falsely detected if FPU emulation is on (CR0.EM[bit2]=1)
		// in which case FPU/MMX instructions generate Int 7 for emulation purposes
		for (int i=13; --i>=0; ) CPUID::vendor[i]=vendor[i];
	}
};


// **************************** RDTSC Instruction ***********************
// This instruction can only be used if supported on the CPU 
// and valid for the current privilege level

// ie. Pentium CPU or later.

// *** See CycleCounter class for easy and safe use ***

static inline ULONG RDTSC(void)
// returns value of processor cycle counter (lowest 32bits)
{
	ULONG lo32;
	__asm
	{
		xor		edx,edx		// compiler bug fixup:otherwise doesn't understand edx corrupted
		_emit	0x0f		// rdtsc instruction
		_emit	0x31		//	sets edx:eax to current 64bit cycle count
		mov		lo32,eax
//		mov		hi32,edx
	}
	return lo32;
}
 


// ************************ Cycle Counting Class ***********************

/* Use e.g.

		CycleCounter C;	// automatically started
			... code to time
		C.stop();
			... more code not timed
		printf("Time:%d\n",C.clocks());
*/

class CycleCounter
// safe use of RDTSC instruction for precise timing 
// always returns 0 if RDTSC instruction is invalid (on CPU or at current privilege level)
{
	ULONG t;
	ULONG allowed;
public:
	CycleCounter()		{ CPUID C; allowed = C.RDTSC_available(); t=0; start(); }
	// NB: constructor calls start() automatically
	
	void	start()		{ if (allowed) t=RDTSC(); }
	// starts the clock (NB: this was done automatically by constructor anyway)

	ULONG	stop()		{ if (allowed) t=RDTSC()-t; return t; }
	// stops clock, and returns time (in CPU clocks) between start() and stop()
	// NB: use of ULONGs means the difference value wraps correctly

	ULONG	clocks()	{ return t; }
	// returns time (in CPU clocks) between start() and stop()
};



#endif