// LM89.cpp : Defines the initialization routines for the DLL.
//
// created by Unwinder
/////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"

#include <float.h>
#include <shlwapi.h>
#include <afxdllx.h>

#include "Context.h"
#include "LM89.h"
#include "RivaTunerExports.h"
#include "MonitoringSourceDesc.h"
/////////////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
static AFX_EXTENSION_MODULE LM89DLL = { NULL, NULL };
/////////////////////////////////////////////////////////////////////////////
#define MAX_GPU															16
/////////////////////////////////////////////////////////////////////////////
GET_DEVICE_ID_EX_PROC		g_pGetDeviceIDEx				= NULL;
GET_I2C_BUS_NUM_EX_PROC		g_pGetI2CBusNumEx				= NULL;
READ_I2C_EX_PROC			g_pReadI2CEx					= NULL;

CContext					g_context[MAX_GPU];

HINSTANCE					g_hModule						= 0;
/////////////////////////////////////////////////////////////////////////////
extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	UNREFERENCED_PARAMETER(lpReserved);

	if (dwReason == DLL_PROCESS_ATTACH)
	{
		if (!AfxInitExtensionModule(LM89DLL, hInstance))
			return 0;

		new CDynLinkLibrary(LM89DLL);

		g_hModule = hInstance;
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		AfxTermExtensionModule(LM89DLL);
	}
	return 1;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function rips required string param from specified
//<Param0><Separator><Param1><Separator>...<ParamN>-styled string
/////////////////////////////////////////////////////////////////////////////
BOOL GetParam(int iParam, LPCSTR lpSrc, CString &strParam, LPCSTR lpSeparator)
{
	CString strSrc = lpSrc;

	int iFirst = 0;

	while (iParam)
	{
		iFirst = strSrc.Find(lpSeparator, iFirst);

		if (iFirst == -1)
			return FALSE;

		iFirst++;
		iParam--;
	}

	int iLast = strSrc.Find(lpSeparator ,iFirst);

	if (iLast == -1)
		iLast = strlen(lpSrc);

	strParam = strSrc.Mid(iFirst, iLast - iFirst);

	strParam.TrimRight();
	strParam.TrimLeft();

	return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function compares two strings using whildcard character
/////////////////////////////////////////////////////////////////////////////
int CompareWithWildcard(LPCSTR str0, LPCSTR str1, char wildcard)
{
	int nLen0	= strlen(str0);
	int nLen1	= strlen(str1);

	if (nLen0 > nLen1)
		return 1;

	if (nLen0 < nLen1)
		return -1;

	for (int i=0; i<nLen0; i++)
	{
		char c0 = str0[i];
		char c1 = str1[i];

		if ((c0 != wildcard) &&
			(c1 != wildcard))
			if (c0 != c1)
				return (c0 > c1) ? 1 : -1;
	}

	return 0;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function reads the first matching entry for the currently
// selected display adapter from the specified section of .cfg
//
// Note: the entries in .cfg can be specified with wildcard character '?'
/////////////////////////////////////////////////////////////////////////////
CString GetMatchingHardwareEntry(DWORD dwGpu, LPCSTR lpSection)
{
	char szCfgPath[MAX_PATH];
	GetModuleFileName(g_hModule, szCfgPath, MAX_PATH);
	
	PathRenameExtension(szCfgPath, ".cfg");
		//get fully qualified path to .cfg

	LPSTR	lpBuf		= new char[32768];
		//allocate buffer for section
	if (GetPrivateProfileSection(lpSection, lpBuf, 32768, szCfgPath))
		//try to read section, continue if succeeded
	{
		CString strDeviceID	= g_pGetDeviceIDEx(dwGpu);	
			//get display adapter's PCI DeviceID string

		LPSTR	lpStr	= (LPSTR)lpBuf;
 		int		nLen	= strlen(lpStr);

		CString strLValue, strRValue;

		while (nLen)
			//loop through all section's lines
		{

			if (GetParam(0, lpStr, strLValue, "=") &&
				GetParam(1, lpStr, strRValue, "="))
				//try to rip <LValue>=<RValue>-formatted line and proceed
				//if format is correct
			{
				if (!CompareWithWildcard(strLValue, strDeviceID, '?'))
					//compare LValue with PCI DeviceID string
					//take a note that we'll use wildcards to allow multiple
					//display adapters to be specified with a single .cfg line
				{
					delete [] lpBuf;
						//deallocate buffer we've allocated before

					return strRValue;
				}
			}

			lpStr	+=	nLen + 1;
			nLen	=	strlen(lpStr);
				//proceed with next section's line
		}
	}

	delete [] lpBuf;
		//deallocate buffer we've allocated before

	return "";
		//return empty string if we've failed to find matching hardware entry
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get a number of
// data sources in this plugin
//////////////////////////////////////////////////////////////////////
LM89_API DWORD GetSourcesNum()
{
	return 2;
}
//////////////////////////////////////////////////////////////////////
// This helper function formats multiGPU friendly group name
////////////////////////////////////////////////////////////////////////
CString FormatMultiGpuGroupName(LPCSTR lpName, DWORD dwGpu)
{
	CString strName;

	if (dwGpu)
		strName.Format("%s%d", lpName, dwGpu - 1);
	else
		strName = lpName;

	return strName;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get descriptor
// for the specified data source
//////////////////////////////////////////////////////////////////////
LM89_API BOOL GetSourceDesc(DWORD dwIndex, LPMONITORING_SOURCE_DESC pDesc)
{
	//get host application module handle

	HMODULE hHost = GetModuleHandle(NULL);
	if (!hHost)
		return FALSE;

	DWORD dwGpu		= dwIndex>>16;
	DWORD dwSource	= dwIndex & 0xFFFF;
		//extract GPU and data source indices from macro index

	if (dwGpu >= MAX_GPU)
		//validate GPU index
		return FALSE;

	if (!g_context[dwGpu].m_dev)
		//small performance optimization: once the sensor is detected, we'll
		//precache its' location and will no longer perform I2C scanning
	{
		//get ptr to host application ID routine and return error if the host doesn't 
		//export it

		//we'll use this condition to verify if we're under RivaTuner v2.0 RC 15.7
		//(older versions are not supported) or any other compatible host

		GET_HOST_APP_ID_PROC pGetHostAppID = (GET_HOST_APP_ID_PROC)GetProcAddress(hHost, "GetHostAppID");
		if (!pGetHostAppID)
			return FALSE;

		//get ptrs to required plugin API functions

		g_pGetDeviceIDEx	= (GET_DEVICE_ID_EX_PROC)GetProcAddress(hHost, "GetDeviceIDEx");
		if (!g_pGetDeviceIDEx)
			return FALSE;

		g_pGetI2CBusNumEx	= (GET_I2C_BUS_NUM_EX_PROC)GetProcAddress(hHost, "GetI2CBusNumEx");
		if (!g_pGetI2CBusNumEx)
			return FALSE;

		g_pReadI2CEx		= (READ_I2C_EX_PROC)GetProcAddress(hHost, "ReadI2CEx");
		if (!g_pReadI2CEx)
			return FALSE;

		//once we've got all function ptrs, we can proceed with I2C bus scanning

		//first, we'll get number of I2C buses for the selected display device

		BYTE busNum		= g_pGetI2CBusNumEx(dwGpu);
		if (!busNum)
			return FALSE;

		//scan all I2C buses and check LM89/LM99 sensor presence	

		for (BYTE bus=0; bus<busNum; bus++)
		{
			BYTE data;

			if (g_pReadI2CEx(dwGpu, bus, 0x4C, 0xFE, &data) && (data == 0x01))
				//check LM89/LM99 manufacturer ID
			{
				if (g_pReadI2CEx(dwGpu, bus, 0x4C, 0xFF, &data) && (data == 0x31))
					//check LM89/LM99 revision ID
				{
					//we've found LM89 and can precache its' location

					g_context[dwGpu].m_bus = bus;
					g_context[dwGpu].m_dev = 0x4C;
					break;
				}
			}
		}

		if (!g_context[dwGpu].m_dev)
			//if we've not found LM89/LM99 then
			//scan all I2C buses and check LM89-I/LM99-I sensor presence	

			for (BYTE bus=0; bus<busNum; bus++)
			{
				BYTE data;

				if (g_pReadI2CEx(dwGpu, bus, 0x4D, 0xFE, &data) && (data == 0x01))
					//check LM89/LM99 manufacturer ID
				{
					if (g_pReadI2CEx(dwGpu, bus, 0x4D, 0xFF, &data) && (data == 0x34))
						//check LM89/LM99 revision ID
					{
						//we've found LM89 and can precache its' location

						g_context[dwGpu].m_bus = bus;
						g_context[dwGpu].m_dev = 0x4D;
						break;
					}
				}
			}
	}

	if (!g_context[dwGpu].m_dev)
		//return FALSE if we've not detected LM89/LM99/LM89-I/LM99-I
		return FALSE;


	//calibrate remote temperature offset

	//unfortunately LM89 and LM99 have fully identical registers set and chip IDs, however
	//LM99 requires +16 degrees temperature offset for remote temperature and LM89 doesn't

	//so we'll use display adapters database and forcibly specify offsets for display 
	//adapters with LM99 via [RemoteTemperatureOffset] section in LM89.cfg

	sscanf(GetMatchingHardwareEntry(dwGpu, "RemoteTemperatureOffset"), "%f", &g_context[dwGpu].m_fltTemperatureRemoteOffset);

	switch (dwSource)
		//now the sensor's location is detected,
		//so we can return requested information to RivaTuner
	{
	case 0:
		//fill core temperature source descriptor

		strcpy(pDesc->szName	, "Core temperature");
		strcpy(pDesc->szDim		, "°C");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, FormatMultiGpuGroupName("GPU", dwGpu));

		pDesc->fltMaxLimit	= 200.0f;
		pDesc->fltMinLimit	= 0.0f;
		pDesc->fltGridDim	= 10.0f;

		strcpy(pDesc->szDesc, " Remote temperature reported by LM89-compatible sensor ");

		return TRUE;

	case 1:
		//fill ambient temperature source descriptor

		strcpy(pDesc->szName	, "Ambient temperature");
		strcpy(pDesc->szDim		, "°C");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, FormatMultiGpuGroupName("PCB", dwGpu));

		pDesc->fltMaxLimit	= 200.0f;
		pDesc->fltMinLimit	= 0.0f;
		pDesc->fltGridDim	= 10.0f;

		strcpy(pDesc->szDesc, " Local temperature reported by LM89-compatible sensor ");

		return TRUE;
	}

	return FALSE;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get capabilities
// for the specified data source
//////////////////////////////////////////////////////////////////////
LM89_API BOOL GetSourceCaps(DWORD dwIndex, DWORD dwCaps, LPDWORD lpResult)
{
	//all sources support multiGPU monitoring so it is safe to ignore passed
	//index and check just the capability

	switch (dwCaps)
	{
	case  MONITORING_SOURCE_CAPS_MULTIGPU_SUPPORT:
		*lpResult = 1;
		return TRUE;
	}

	return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to poll data sources
/////////////////////////////////////////////////////////////////////////////
LM89_API FLOAT GetSourceData(DWORD dwIndex)
{
	DWORD dwGpu		= dwIndex>>16;
	DWORD dwSource	= dwIndex & 0xFFFF;
		//extract GPU and data source indices from macro index

	if (dwGpu >= MAX_GPU)
		//validate GPU index
		return FLT_MAX;

	FLOAT	result	= FLT_MAX;	
	SHORT	temp;
	BYTE	data;

	switch (dwSource)
	{
	case 0:
		//core temperature

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 1, &data))
			//read MSB
		{
			temp = data<<8;

			if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0x10, &data))
				//read LSB and combine it with MSB
				temp |= data;

			result = temp / 256.0f;
		}

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltTemperatureRemote = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltTemperatureRemote + g_context[dwGpu].m_fltTemperatureRemoteOffset;

	case 1:
		//ambient temperature

		if (g_pReadI2CEx(dwGpu, g_context[dwGpu].m_bus, g_context[dwGpu].m_dev, 0, &data))
			//read MSB
			result = (char)data;

		//perform misreading filtering

		if (result != FLT_MAX)
			g_context[dwGpu].m_fltTemperatureLocal = result;

		//return last correctly read value

		return g_context[dwGpu].m_fltTemperatureLocal;
	}

	return result;
}
/////////////////////////////////////////////////////////////////////////////
