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

#include <float.h>
#include <shlwapi.h>
#include <afxdllx.h>
 
#include "NVSU.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 NVSUDLL = { NULL, NULL };
/////////////////////////////////////////////////////////////////////////////
READ_BUS_DATA_ULONG_PROC	g_pReadBusDataUlong				= NULL;
READ_BUS_DATA_USHORT_PROC	g_pReadBusDataUshort			= NULL;
READ_BUS_DATA_UCHAR_PROC	g_pReadBusDataUchar				= NULL;

INP_PROC					g_pInp							= NULL;
INPW_PROC					g_pInpw							= NULL;
INPD_PROC					g_pInpd							= NULL;
OUTP_PROC					g_pOutp							= NULL;
OUTPW_PROC					g_pOutpw						= NULL;
OUTPD_PROC					g_pOutpd						= NULL;

BYTE						g_fnCode						= 0;
WORD						g_port							= 0;
DWORD						g_dwScratchRegisters[8]			= {0, 0, 0, 0, 0, 0, 0, 0};

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(NVSUDLL, hInstance))
			return 0;

		new CDynLinkLibrary(NVSUDLL);

		g_hModule = hInstance;
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		AfxTermExtensionModule(NVSUDLL);
	}
	return 1;
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to save MCP scratch registers 0 - 7
//////////////////////////////////////////////////////////////////////
void SaveScratchRegisters()
{
	for (DWORD dwIndex=0; dwIndex<8; dwIndex++)
		g_dwScratchRegisters[dwIndex] = g_pInpd((WORD)(g_port + 0x80 + dwIndex * 4));
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to restore MCP scratch registers 0 - 7
// saved via SaveScratchRegisters() 
//////////////////////////////////////////////////////////////////////
void RestoreScratchRegisters()
{
	for (DWORD dwIndex=0; dwIndex<8; dwIndex++)
		g_pOutpd((WORD)(g_port + 0x80 + dwIndex * 4), g_dwScratchRegisters[dwIndex]);
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to get CPU temperature via OEM BIOS
// NVSU SMI interface
//////////////////////////////////////////////////////////////////////
FLOAT GetCPUTemperature()
{
	FLOAT result = FLT_MAX;

	if (g_port && g_fnCode)
	{
		SaveScratchRegisters();

		g_pOutp(g_port + 0x2F, 0x12);
		g_pOutp(g_port + 0x2E, g_fnCode);
		if (!g_pInp(g_port + 0x2F))
			result = (char)g_pInp(g_port + 0x84);

		RestoreScratchRegisters();
	}

	if (result == -1.0f)
		return FLT_MAX;

	return result;
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to get system temperature via OEM BIOS
// NVSU SMI interface
//////////////////////////////////////////////////////////////////////
FLOAT GetSystemTemperature()
{
	FLOAT result = FLT_MAX;

	if (g_port && g_fnCode)
	{
		SaveScratchRegisters();

		g_pOutp(g_port + 0x2F, 0x13);
		g_pOutp(g_port + 0x2E, g_fnCode);
		if (!g_pInp(g_port + 0x2F))
			result = (char)g_pInp(g_port + 0x84);

		RestoreScratchRegisters();
	}

	if (result == -1.0f)
		return FLT_MAX;

	return result;
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to get CPU fan speed via OEM BIOS
// NVSU SMI interface
//////////////////////////////////////////////////////////////////////
FLOAT GetCPUFanSpeed()
{
	FLOAT result = FLT_MAX;

	if (g_port && g_fnCode)
	{
		SaveScratchRegisters();

		g_pOutp(g_port + 0x2F, 0x02);
		g_pOutp(g_port + 0x2E, g_fnCode);
		if (!g_pInp(g_port + 0x2F))
			result = (FLOAT)g_pInpd(g_port + 0x84);

		RestoreScratchRegisters();
	}

	if (result == -1.0f)
		return FLT_MAX;

	if (result > 10000.0f)
		//filter low RPM fan speed misreadings for ASUS boards
		return FLT_MAX;

	return result;
}
//////////////////////////////////////////////////////////////////////
// This helper function is used to get system fan speed via OEM BIOS
// NVSU SMI interface
//////////////////////////////////////////////////////////////////////
FLOAT GetSystemFanSpeed()
{
	FLOAT result = FLT_MAX;

	if (g_port && g_fnCode)
	{
		SaveScratchRegisters();

		g_pOutp(g_port + 0x2F, 0x06);
		g_pOutp(g_port + 0x2E, g_fnCode);
		if (!g_pInp(g_port + 0x2F))
			result = (FLOAT)g_pInpd(g_port + 0x84);

		RestoreScratchRegisters();
	}

	if (result == -1.0f)
		return FLT_MAX;

	if (result > 10000.0f)
		//filter low RPM fan speed misreadings for ASUS boards
		return FLT_MAX;

	return result;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get a number of
// data sources in this plugin
//////////////////////////////////////////////////////////////////////
NVSU_API DWORD GetSourcesNum()
{
	return 4;
}
//////////////////////////////////////////////////////////////////////
// This exported function is called by RivaTuner to get descriptor
// for the specified data source
//////////////////////////////////////////////////////////////////////
NVSU_API BOOL GetSourceDesc(DWORD dwIndex, LPMONITORING_SOURCE_DESC pDesc)
{
	//get host application module handle

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

	if (!g_port && !g_fnCode)
		//small performance optimization: once the MCP base port and SMI fn
		//code are detected, we'll precache them and will no longer perform
		//PCI bus 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_pReadBusDataUlong = (READ_BUS_DATA_ULONG_PROC)GetProcAddress(hHost, "ReadBusDataUlong");
		if (!g_pReadBusDataUlong)
			return FALSE;

		g_pReadBusDataUshort = (READ_BUS_DATA_USHORT_PROC)GetProcAddress(hHost, "ReadBusDataUshort");
		if (!g_pReadBusDataUshort)
			return FALSE;

		g_pReadBusDataUchar = (READ_BUS_DATA_UCHAR_PROC)GetProcAddress(hHost, "ReadBusDataUchar");
		if (!g_pReadBusDataUchar)
			return FALSE;

		g_pInp		= (INP_PROC)GetProcAddress(hHost, "inp");
		if (!g_pInp)
			return FALSE;

		g_pInpw		= (INPW_PROC)GetProcAddress(hHost, "inpw");
		if (!g_pInpw)
			return FALSE;

		g_pInpd		= (INPD_PROC)GetProcAddress(hHost, "inpd");
		if (!g_pInpd)
			return FALSE;

		g_pOutp		= (OUTP_PROC)GetProcAddress(hHost, "outp");
		if (!g_pOutp)
			return FALSE;

		g_pOutpw	= (OUTPW_PROC)GetProcAddress(hHost, "outpw");
		if (!g_pOutpw)
			return FALSE;

		g_pOutpd	= (OUTPD_PROC)GetProcAddress(hHost, "outpd");
		if (!g_pOutpd)
			return FALSE;
	}

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

	//first, we'll try to read ISA bridge's ID and return error if it is not
	//NVIDIA ISA bridge (bridge is located at PCI bus 0, device 1, function 0)

	DWORD dwBridgeID = g_pReadBusDataUshort(0, 1<<3, 0);
	if (dwBridgeID != 0x10DE)
		return FALSE;

	//second, we'll try to read MCP's system control I/O base address from
	//ISA bridge's configuration registers 0x64-0x65

	g_port = g_pReadBusDataUshort(0, 1<<3, 0x64);
	if (!(g_port & 1))
		return FALSE;
		//return error if it is not I/O base address
	g_port &= 0xff00;
		//mask BAR flags
	if (!g_port)
		return FALSE;
		//return error if we've not detected valid I/O base addres

	//third, we'll try to read SMI function code from MCP's scratch register 7
	//(bits 24-31)

	g_fnCode = g_pInp(g_port + 0x9f);

	if (!g_fnCode && (g_pReadBusDataUlong(0, 1<<3, 0x2C) == 0x815A1043))
		//workaround for ASUS A8N-SLI mobos (ASUS BIOS team as usual forgot
		//to init something (this time NVSU SMI protocol properly, so MCP's
		//scratchpad register 7 doesn't contain valid SMI function code)
		//if such condition is detected, the plugin will use hardcoded SMI
		//function code 
		g_fnCode = 0xC8;
	
	if (!g_fnCode)
		//return error if we've not detected valid SMI function code
		return FALSE;

	switch (dwIndex)
		//now the MCP's system control I/O base port and SMI function code are
		//detected, so we can return requested information to RivaTuner
	{
	case 0:
		if (GetCPUTemperature() == FLT_MAX)
			return FALSE;
			//try to perform CPU temperature reading to verify if BIOS supports it,
			//return error if it is not supported

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

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

		strcpy(pDesc->szDesc	, "CPU temperature reported by BIOS NVSU SMI interface");

		return TRUE;
	case 1:
		if (GetSystemTemperature() == FLT_MAX)
			return FALSE;
			//try to perform system temperature reading to verify if BIOS 
			//supports it, return error if it is not supported

		strcpy(pDesc->szName	, "System temperature");
		strcpy(pDesc->szDim		, "°C");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, "SYS");

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

		strcpy(pDesc->szDesc	, "System temperature reported by BIOS NVSU SMI interface");

		return TRUE;

	case 2:
		if (GetCPUFanSpeed() == FLT_MAX)
			return FALSE;
			//try to perform CPU fan speed reading to verify if BIOS 
			//supports it, return error if it is not supported

		strcpy(pDesc->szName	, "CPU fan speed");
		strcpy(pDesc->szDim		, "RPM");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, "CPU");

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

		strcpy(pDesc->szDesc	, "CPU fan speed reported by BIOS NVSU SMI interface");

		return TRUE;

	case 3:
		if (GetSystemFanSpeed() == FLT_MAX)
			return FALSE;
			//try to perform system fan speed reading to verify if BIOS 
			//supports it, return error if it is not supported

		strcpy(pDesc->szName	, "System fan speed");
		strcpy(pDesc->szDim		, "RPM");
		if (pDesc->dwVersion >= 0x00010002)
			strcpy(pDesc->szGroup, "SYS");

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

		strcpy(pDesc->szDesc	, "System fan speed reported by BIOS NVSU SMI interface");

		return TRUE;
	}

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

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

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

	switch (dwIndex)
	{
	case 0:
		return GetCPUTemperature();
	case 1:
		return GetSystemTemperature();
	case 2:
		return GetCPUFanSpeed();
	case 3:
		return GetSystemFanSpeed();
	}

	return FLT_MAX;
}
/////////////////////////////////////////////////////////////////////////////
