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

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

#include "Context.h"
#include "NVBIOS.h"
#include "NVThermalDiode.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 NVThermalDiodeDLL = { NULL, NULL };
/////////////////////////////////////////////////////////////////////////////
#define MAX_CURVE_POINTS											16
#define MAX_GPU														16	
/////////////////////////////////////////////////////////////////////////////
GET_DEVICE_ID_EX_PROC			g_pGetDeviceIDEx					= NULL;
READ_REGISTER_ULONG_EX_PROC		g_pReadRegisterUlongEx				= NULL;
WRITE_REGISTER_ULONG_EX_PROC	g_pWriteRegisterUlongEx				= NULL;
ENABLE_READ_BIOS_EX_PROC		g_pEnableReadBIOSEx					= NULL;
READ_BIOS_EX_PROC				g_pReadBIOSEx						= 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(NVThermalDiodeDLL, hInstance))
			return 0;

		new CDynLinkLibrary(NVThermalDiodeDLL);

		g_hModule = hInstance;
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		AfxTermExtensionModule(NVThermalDiodeDLL);
	}
	return 1;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get a number of
// data sources in this plugin
//////////////////////////////////////////////////////////////////////
NVTHERMALDIODE_API DWORD GetSourcesNum()
{
	return 1;
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to get GPU fuse state by fuse number
//////////////////////////////////////////////////////////////////////
LONG GetFuseStateByNumber(DWORD dwGpu, DWORD dwFuse)
{
	if (g_context[dwGpu].m_dwCoreFamily >= 0x50)
		//G80+ fuses reading implementation
	{
		if (dwFuse >= 0xA0)
			return 0;

		if (dwFuse >= 0x80)
			return 1 & (g_pReadRegisterUlongEx(dwGpu, 0x2101C)>>(dwFuse - 0x80));

		if (dwFuse >= 0x60)
			return 1 & (g_pReadRegisterUlongEx(dwGpu, 0x21018)>>(dwFuse - 0x60));

		if (dwFuse >= 0x40)
			return 1 & (g_pReadRegisterUlongEx(dwGpu, 0x21014)>>(dwFuse - 0x40));

		if (dwFuse >= 0x20)
			return 1 & (g_pReadRegisterUlongEx(dwGpu, 0x21010)>>(dwFuse - 0x20));

		return 1 & (g_pReadRegisterUlongEx(dwGpu, 0x2100C)>>dwFuse);
	}
	else
		//NV4X fuses reading implementation
	{
		if (dwFuse >= 0xA0)
			return 0;

		if (dwFuse >= 0x80)
			return 1 & (g_pReadRegisterUlongEx(dwGpu, 0xC030)>>(dwFuse - 0x80));

		if (dwFuse >= 0x60)
			return 1 & (g_pReadRegisterUlongEx(dwGpu, 0xC01C)>>(dwFuse - 0x60));

		if (dwFuse >= 0x40)
			return 1 & (g_pReadRegisterUlongEx(dwGpu, 0xC018)>>(dwFuse - 0x40));

		if (dwFuse >= 0x20)
			return 1 & (g_pReadRegisterUlongEx(dwGpu, 0xC014)>>(dwFuse - 0x20));

		return 1 & (g_pReadRegisterUlongEx(dwGpu, 0xC010)>>dwFuse);
	}

	return 0;
}
//////////////////////////////////////////////////////////////////////
// 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
//////////////////////////////////////////////////////////////////////
NVTHERMALDIODE_API BOOL GetSourceDesc(DWORD dwIndex, LPMONITORING_SOURCE_DESC pDesc)
{
	//get host application module handle

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

	//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_pReadRegisterUlongEx	= (READ_REGISTER_ULONG_EX_PROC)GetProcAddress(hHost, "ReadRegisterUlongEx");
	if (!g_pReadRegisterUlongEx)
		return FALSE;

	g_pWriteRegisterUlongEx	= (WRITE_REGISTER_ULONG_EX_PROC)GetProcAddress(hHost, "WriteRegisterUlongEx");
	if (!g_pWriteRegisterUlongEx)
		return FALSE;

	g_pEnableReadBIOSEx		= (ENABLE_READ_BIOS_EX_PROC)GetProcAddress(hHost, "EnableReadBIOSEx");
	if (!g_pEnableReadBIOSEx)
		return FALSE;

	g_pReadBIOSEx			= (READ_BIOS_EX_PROC)GetProcAddress(hHost, "ReadBIOSEx");
	if (!g_pReadBIOSEx)
		return FALSE;

	//once we've got all function ptrs, we can proceed with hardware checking

	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;

	//first, we've to check if it is NVIDIA display adapter

	CString strDeviceID = g_pGetDeviceIDEx(dwGpu);	strDeviceID.MakeUpper();

	DWORD	vendorID, deviceID;
	DWORD	subsysID;
	DWORD	revision;

	sscanf(strDeviceID,"PCI\\VEN_%04x&DEV_%04x&SUBSYS_%08x&REV_%02x", &vendorID, &deviceID, &subsysID, &revision);

	if (vendorID != 0x10DE)
		//return error if display adapter is not manufactured by NVIDIA
		return FALSE;

	//second, we've to check if GPU has on-die thermal diode
	
	g_context[dwGpu].m_dwCoreFamily = (g_pReadRegisterUlongEx(dwGpu, 0)>>20) & 0xff;
		//get GPU core family ID

	BOOL	bUseThermalTable;
		//thermal diodes of modern GPUs don't require software calibration, so we can
		//optimize plugin startup by skipping VGA BIOS thermal table parsing on such
		//hardware
		//we'll use this flag further to determine if thermal table usage is required

	//init default thermal diode calibration parameters for thermal diode 
	//capable GPUs

	switch (g_context[dwGpu].m_dwCoreFamily)
	{
	case 0x43:
		//init default thermal diode calibration params for NV43
		g_context[dwGpu].m_dwDiodeGainMul	= 792;
		g_context[dwGpu].m_dwDiodeGainDiv	= 1000;
		g_context[dwGpu].m_dwDiodeOffsetMul	= 32060;
		g_context[dwGpu].m_dwDiodeOffsetDiv	= 1000;
		g_context[dwGpu].m_dwDiodeMask		= 0xff;
		bUseThermalTable					= TRUE;
		break;
	case 0x44:
	case 0x4A:
	case 0x47:
		//init default thermal diode calibration params for NV44 and G70
		g_context[dwGpu].m_dwDiodeGainMul	= 780;
		g_context[dwGpu].m_dwDiodeGainDiv	= 1000;
		g_context[dwGpu].m_dwDiodeOffsetMul	= 27839;
		g_context[dwGpu].m_dwDiodeOffsetDiv	= 1000;
		g_context[dwGpu].m_dwDiodeMask		= 0xff;
		bUseThermalTable					= TRUE;
		break;
	case 0x46:
	case 0x49:
	case 0x4B:
		//init default thermal diode calibration params for G71, G72 and G73
		g_context[dwGpu].m_dwDiodeGainMul	= 450;
		g_context[dwGpu].m_dwDiodeGainDiv	= 10000;
		g_context[dwGpu].m_dwDiodeOffsetMul	= -23517;
		g_context[dwGpu].m_dwDiodeOffsetDiv	= 100;
		g_context[dwGpu].m_dwDiodeMask		= 0x1fff;
		bUseThermalTable					= TRUE;
		break;
	case 0x50:
		//init default thermal diode calibration params for G80
		g_context[dwGpu].m_dwDiodeGainMul	= 430;
		g_context[dwGpu].m_dwDiodeGainDiv	= 10000;
		g_context[dwGpu].m_dwDiodeOffsetMul	= -22700;
		g_context[dwGpu].m_dwDiodeOffsetDiv	= 100;
		g_context[dwGpu].m_dwDiodeMask		= 0x1fff;
		bUseThermalTable					= TRUE;
		break;
	case 0x84:
	case 0x86:
	case 0x94:
		//init default thermal diode calibration params for G84, G86 and G94
		g_context[dwGpu].m_dwDiodeGainMul	= 1;
		g_context[dwGpu].m_dwDiodeGainDiv	= 1;
		g_context[dwGpu].m_dwDiodeOffsetMul	= 1;
		g_context[dwGpu].m_dwDiodeOffsetDiv	= 1;
		g_context[dwGpu].m_dwDiodeMask		= 0x3fff;
		bUseThermalTable					= FALSE;
		break;
	case 0x92:
		//init default thermal diode calibration params for G92 

		//Warning! These parameters are not used by NVIDIA anywhere and came from ASUS SmartDoctor
		//algorithm (which most likely uses roughly approximated calibration) so the result can be
		//really (!!!) inaccurate!

		//ASUS SmartDoctor uses (-13115 + x) / 18.7 + 1 calibration equation whilst we translate
		//it to native NVIDIA calibration equation format

		g_context[dwGpu].m_dwDiodeGainMul	= 10;
		g_context[dwGpu].m_dwDiodeGainDiv	= 187;
		g_context[dwGpu].m_dwDiodeOffsetMul	= -131150 + 187;
		g_context[dwGpu].m_dwDiodeOffsetDiv	= 187;
		g_context[dwGpu].m_dwDiodeMask		= 0x3fff;
		bUseThermalTable					= FALSE;
		break;
	default: 
		//return error if GPU has no on-die thermal diode
		return FALSE;
	}

	//starting from G71, each GPU diode is additionally calibrated during
	//manufacturing and each diode has personal binary temperature offset
	//defined with GPU fuses

	//now we'll read diode specific binary offset

	LONG dwDiodeOffsetBin	= 0;
	
	switch (g_context[dwGpu].m_dwCoreFamily)
	{
	case 0x46:
		dwDiodeOffsetBin	=	 GetFuseStateByNumber(dwGpu, 0x82)		|
								(GetFuseStateByNumber(dwGpu, 0x83)<<1)	|
								(GetFuseStateByNumber(dwGpu, 0x86)<<2);
		break;
	case 0x49:
	case 0x4B:
		dwDiodeOffsetBin	=	 GetFuseStateByNumber(dwGpu, 0x39)		|
								(GetFuseStateByNumber(dwGpu, 0x3A)<<1)	|
								(GetFuseStateByNumber(dwGpu, 0x3B)<<2)	|
								(GetFuseStateByNumber(dwGpu, 0x3C)<<3);
		break;
	case 0x50:
		dwDiodeOffsetBin	=	 GetFuseStateByNumber(dwGpu, 0x31)		|
								(GetFuseStateByNumber(dwGpu, 0x32)<<1)	|
								(GetFuseStateByNumber(dwGpu, 0x33)<<2)	|
								(GetFuseStateByNumber(dwGpu, 0x34)<<3) |
								(GetFuseStateByNumber(dwGpu, 0x35)<<4);
	}

	//convert diode specific offset from binary format to °C

	switch (g_context[dwGpu].m_dwCoreFamily)
	{
	case 0x46:
		{
			const LONG map[8] = { 0, 45, 25, -15, -45, -75, -105, 0 };

			g_context[dwGpu].m_dwDiodeOffsetBinMul = map[dwDiodeOffsetBin];
			g_context[dwGpu].m_dwDiodeOffsetBinDiv = 10;
		}
		break;
	case 0x49:
	case 0x4B:
		{
			const LONG map[16] = { 0, 7, 5, 3, 1, -1, -3, -5, -7, -9, -11, -13, -15, -17, -19, -21 };

			g_context[dwGpu].m_dwDiodeOffsetBinMul = map[dwDiodeOffsetBin];
			g_context[dwGpu].m_dwDiodeOffsetBinDiv = 1;
		}
		break;
	}

	//get fully qualitied path to .cfg

	char szCfgPath[MAX_PATH];
	GetModuleFileName(g_hModule, szCfgPath, MAX_PATH);
	
	PathRenameExtension(szCfgPath, ".cfg");

	//try to override thermal table usage flag using .cfg fi;e

	bUseThermalTable = GetPrivateProfileInt("Settings", "UseThermalTable", bUseThermalTable, szCfgPath);

	//third, we'll try to override default thermal diode calibration parameters via
	//BIOS thermal table 

	if (bUseThermalTable)
	{
		CNVBIOS nvBIOS;
			//we'll use this object to access NVIDIA BIOS specific info

		if (nvBIOS.Init(dwGpu))
			//init NVIDIA BIOS object
		{
			nvBIOS.GetThermalTableTemperatureCompensation(&g_context[dwGpu].m_dwTemperatureCompensationMul);
				//recalibrate temperature compensation using VGA BIOS thermal table
			nvBIOS.GetThermalTableTemperatureThreshold(0, 4, &g_context[dwGpu].m_dwTemperatureThreshold);
				//recalibrate temperature threshold

			nvBIOS.GetThermalTableDiodeGainMul(&g_context[dwGpu].m_dwDiodeGainMul);
			nvBIOS.GetThermalTableDiodeGainDiv(&g_context[dwGpu].m_dwDiodeGainDiv);
				//recalibrate diode gain using VGA BIOS thermal table

			nvBIOS.GetThermalTableDiodeOffsetMul(&g_context[dwGpu].m_dwDiodeOffsetMul);
			nvBIOS.GetThermalTableDiodeOffsetDiv(&g_context[dwGpu].m_dwDiodeOffsetDiv);
				//recalibrate diode offset using VGA BIOS thermal table

			//Note: hardware VGA BIOS variables virtualization technology provides ID-based
			//virtual variables, which can be mapped to hardware IO ports, registers and so
			//on via VGA BIOS defined scripts.
			//Virtual variables provide developers abstract unified access to GPU registers
			//layout specific things. 

			//diode binary offset can be redefined via variable virtualization too

			nvBIOS.GetVirtualVariable(6, &dwDiodeOffsetBin);
				//redefine binary diode offset using hardware VGA BIOS variables virtualization
				//technology

			nvBIOS.GetThermalTableDiodeOffsetBinMul(dwDiodeOffsetBin, &g_context[dwGpu].m_dwDiodeOffsetBinMul);
			nvBIOS.GetThermalTableDiodeOffsetBinDiv(dwDiodeOffsetBin, &g_context[dwGpu].m_dwDiodeOffsetBinDiv);
				//recalibrate diode binary offset to °C mapping using VGA BIOS thermal table
		}
	}

	//fourth, we'll try to override thermal diode calibration parameters using .cfg file

	g_context[dwGpu].m_dwTemperatureCompensationMul = GetPrivateProfileInt("Settings", "TemperatureCompensationMul", g_context[dwGpu].m_dwTemperatureCompensationMul, szCfgPath);
	g_context[dwGpu].m_dwTemperatureCompensationDiv = GetPrivateProfileInt("Settings", "TemperatureCompensationDiv", g_context[dwGpu].m_dwTemperatureCompensationDiv, szCfgPath);
		//recalibrate temeprature compensation using .cfg

	g_context[dwGpu].m_dwTemperatureThreshold = GetPrivateProfileInt("Settings", "TemperatureThreshold", g_context[dwGpu].m_dwTemperatureThreshold, szCfgPath);
		//recalibrate temperature threshold using .cfg

	g_context[dwGpu].m_dwDiodeGainMul = GetPrivateProfileInt("Settings", "DiodeGainMul", g_context[dwGpu].m_dwDiodeGainMul, szCfgPath);
	g_context[dwGpu].m_dwDiodeGainDiv = GetPrivateProfileInt("Settings", "DiodeGainDiv", g_context[dwGpu].m_dwDiodeGainDiv, szCfgPath);
		//recalibrate diode gain using .cfg

	g_context[dwGpu].m_dwDiodeOffsetMul = GetPrivateProfileInt("Settings", "DiodeOffsetMul", g_context[dwGpu].m_dwDiodeOffsetMul, szCfgPath);
	g_context[dwGpu].m_dwDiodeOffsetDiv = GetPrivateProfileInt("Settings", "DiodeOffsetDiv", g_context[dwGpu].m_dwDiodeOffsetDiv, szCfgPath);
		//recalibrate diode offset using .cfg

	g_context[dwGpu].m_dwDiodeOffsetBinMul = GetPrivateProfileInt("Settings", "DiodeOffsetBinMul", g_context[dwGpu].m_dwDiodeOffsetBinMul, szCfgPath);
	g_context[dwGpu].m_dwDiodeOffsetBinDiv = GetPrivateProfileInt("Settings", "DiodeOffsetBinDiv", g_context[dwGpu].m_dwDiodeOffsetBinDiv, szCfgPath);
		//recalibrate diode binary offset using .cfg
	
	if (!g_context[dwGpu].m_dwDiodeGainMul || !g_context[dwGpu].m_dwDiodeGainDiv || !g_context[dwGpu].m_dwDiodeOffsetDiv || !g_context[dwGpu].m_dwDiodeOffsetBinDiv || !g_context[dwGpu].m_dwTemperatureThreshold)
		//return error if calibration parameters are corrupted
		return FALSE;

	//find the maximum divider
	
	if (g_context[dwGpu].m_dwMaxDiv < g_context[dwGpu].m_dwTemperatureCompensationDiv)
		g_context[dwGpu].m_dwMaxDiv = g_context[dwGpu].m_dwTemperatureCompensationDiv;

	if (g_context[dwGpu].m_dwMaxDiv < g_context[dwGpu].m_dwDiodeGainDiv)
		g_context[dwGpu].m_dwMaxDiv = g_context[dwGpu].m_dwDiodeGainDiv;

	if (g_context[dwGpu].m_dwMaxDiv < g_context[dwGpu].m_dwDiodeOffsetDiv)
		g_context[dwGpu].m_dwMaxDiv = g_context[dwGpu].m_dwDiodeOffsetDiv;

	if (g_context[dwGpu].m_dwMaxDiv < g_context[dwGpu].m_dwDiodeOffsetBinDiv)
		g_context[dwGpu].m_dwMaxDiv = g_context[dwGpu].m_dwDiodeOffsetBinDiv;

	//convert all multipliers to maximum divider scale

	if (g_context[dwGpu].m_dwMaxDiv > g_context[dwGpu].m_dwTemperatureCompensationDiv)
		g_context[dwGpu].m_dwTemperatureCompensationMul = g_context[dwGpu].m_dwTemperatureCompensationMul * g_context[dwGpu].m_dwMaxDiv / g_context[dwGpu].m_dwTemperatureCompensationDiv;

	if (g_context[dwGpu].m_dwMaxDiv > g_context[dwGpu].m_dwDiodeGainDiv)
		g_context[dwGpu].m_dwDiodeGainMul = g_context[dwGpu].m_dwDiodeGainMul * g_context[dwGpu].m_dwMaxDiv / g_context[dwGpu].m_dwDiodeGainDiv;

	if (g_context[dwGpu].m_dwMaxDiv > g_context[dwGpu].m_dwDiodeOffsetDiv)
		g_context[dwGpu].m_dwDiodeOffsetMul = g_context[dwGpu].m_dwDiodeOffsetMul * g_context[dwGpu].m_dwMaxDiv / g_context[dwGpu].m_dwDiodeOffsetDiv;

	if (g_context[dwGpu].m_dwMaxDiv > g_context[dwGpu].m_dwDiodeOffsetBinDiv)
		g_context[dwGpu].m_dwDiodeOffsetBinMul = g_context[dwGpu].m_dwDiodeOffsetBinMul * g_context[dwGpu].m_dwMaxDiv / g_context[dwGpu].m_dwDiodeOffsetBinDiv;

	switch (dwSource)
		//now thermal diode capable GPU 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, " Temperature reported by GPU on-die thermal diode ");

		return TRUE;
	}

	return FALSE;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get capabilities
// for the specified data source
//////////////////////////////////////////////////////////////////////
NVTHERMALDIODE_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 helper function returns raw thermal diode data
/////////////////////////////////////////////////////////////////////////////
DWORD GetDiodeRawTemp(DWORD dwGpu)
{
	if (g_context[dwGpu].m_dwCoreFamily >= 0x84)	
		return g_pReadRegisterUlongEx(dwGpu, 0x20008) & g_context[dwGpu].m_dwDiodeMask;

	if (g_context[dwGpu].m_dwCoreFamily >= 0x50)	
		return g_pReadRegisterUlongEx(dwGpu, 0x20014) & g_context[dwGpu].m_dwDiodeMask;

	return g_pReadRegisterUlongEx(dwGpu, 0x15b4) & g_context[dwGpu].m_dwDiodeMask;
}
/////////////////////////////////////////////////////////////////////////////
// This helper function returns calibrated thermal diode data for G84 and 
// newer GPU or 0 if GPU doesn't support calibrated thermal diode reading 
/////////////////////////////////////////////////////////////////////////////
LONG GetDiodeTemp(DWORD dwGpu)
{
	if (g_context[dwGpu].m_dwCoreFamily >= 0x84)	
		return (char)g_pReadRegisterUlongEx(dwGpu, 0x20400);

	return 0;
}
/////////////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to poll data sources
/////////////////////////////////////////////////////////////////////////////
NVTHERMALDIODE_API FLOAT GetSourceData(DWORD dwIndex)
{
	FLOAT	result	= FLT_MAX;	
	LONG	temp;

	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;

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

		if (g_context[dwGpu].m_dwCoreFamily < 0x84)
			//temperature monitoring implementation for GPUs with no built-in hardware diode calibration logic (G80 and older)
		{
			temp = GetDiodeRawTemp(dwGpu);
				//get raw thermal diode readings

			if (g_context[dwGpu].m_dwCoreFamily < 0x50)
				//check if NV4x diode is actually enabled, otherwise enable it by programming
				//temperature threshold
			{
				if (!temp)
				{
					temp = (g_context[dwGpu].m_dwTemperatureThreshold * g_context[dwGpu].m_dwMaxDiv - g_context[dwGpu].m_dwTemperatureCompensationMul - g_context[dwGpu].m_dwDiodeOffsetMul) / g_context[dwGpu].m_dwDiodeGainMul;
						//calculate target raw thermal diode readings for temperature threshold

					g_pWriteRegisterUlongEx(dwGpu, 0x15b0, 0x10000000 | temp);
						//enable NV4x diode by programming temperature threshold

					temp = GetDiodeRawTemp(dwGpu);
						//try to read raw thermal diode readings once again after enabling it
				}
			}

			if (temp)
				result = (FLOAT)(temp * g_context[dwGpu].m_dwDiodeGainMul + g_context[dwGpu].m_dwDiodeOffsetMul + g_context[dwGpu].m_dwDiodeOffsetBinMul + g_context[dwGpu].m_dwTemperatureCompensationMul) / g_context[dwGpu].m_dwMaxDiv;
					//calibrate thermal diode readings
		}
		else
			//temperature monitoring implementation for GPUs with built-in hardware diode calibration logic (G84 and newer)
		{
			if (g_context[dwGpu].m_dwCoreFamily == 0x92)
				//special case for G92: we'll calibrate G92 raw thermal diode readings at software level because hardware
				//diode calibration logic seem to be broken in this GPU
			{
				temp = GetDiodeRawTemp(dwGpu);
					//get raw thermal diode readings

				result = (FLOAT)(temp * g_context[dwGpu].m_dwDiodeGainMul + g_context[dwGpu].m_dwDiodeOffsetMul) / g_context[dwGpu].m_dwMaxDiv;
					//calibrate thermal diode readings 
			}
			else
				result = (FLOAT)GetDiodeTemp(dwGpu);
					//directly read calibrated temperature from built-in GPU diode calibration logic
		}

		break;
	}

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