// NVBIOS.cpp: implementation of the CNVBIOS class.
//
// created by Unwinder
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "NVBIOS.h"
//////////////////////////////////////////////////////////////////////
#include "RivaTunerExports.h"
//////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
extern READ_REGISTER_ULONG_EX_PROC	g_pReadRegisterUlongEx;
extern ENABLE_READ_BIOS_EX_PROC		g_pEnableReadBIOSEx;
extern READ_BIOS_EX_PROC			g_pReadBIOSEx;
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CNVBIOS::CNVBIOS()
{
	m_lpImage		= NULL;
	m_dwImageSize	= 0;
	m_dwGpu			= 0;
}
//////////////////////////////////////////////////////////////////////
CNVBIOS::~CNVBIOS()
{
	Uninit();
}
//////////////////////////////////////////////////////////////////////
BOOL CNVBIOS::Init(DWORD dwGpu)
{
	Uninit();

	if (g_pEnableReadBIOSEx(dwGpu, TRUE))
		//enable access to BIOS before actually reading it
	{
		if ((g_pReadBIOSEx(dwGpu, 0) == 0x55) &&
			(g_pReadBIOSEx(dwGpu, 1) == 0xAA))
			//check if VGA BIOS access actually works by verifying standard
			//VGA BIOS signature
		{
			m_dwImageSize = g_pReadBIOSEx(dwGpu, 2) * 512;
				//get ROM size

			if (m_dwImageSize)
			{
				m_lpImage	= new BYTE[m_dwImageSize];
					//allocate VGA BIOS image storage

				for (DWORD dwIndex=0; dwIndex<m_dwImageSize; dwIndex++)
					m_lpImage[dwIndex] = g_pReadBIOSEx(dwGpu, dwIndex);
					//copy BIOS to allocated buffer

				m_lpBIT			= FindBIT();		
					//init pointer to BIT structure
				m_dwGpu			= dwGpu;
	
				return TRUE;
			}
		}

		g_pEnableReadBIOSEx(dwGpu, FALSE);
			//don't forget to disable access to BIOS when we no longer need it
	}

	return FALSE;
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::Uninit()
{
	if (m_lpImage)
		delete [] m_lpImage;

	m_lpImage		= NULL;
	m_dwImageSize	= 0;
}
//////////////////////////////////////////////////////////////////////
LPBYTE CNVBIOS::FindPattern(LPBYTE lpPattern, LPBYTE lpMask, DWORD dwSize, DWORD dwOffset)
{
	if (m_lpImage && m_dwImageSize && lpPattern && dwSize)
	{
		while (dwOffset < m_dwImageSize - dwSize)
		{
			BOOL bMatched = TRUE;

			for (DWORD dwIndex=0; dwIndex<dwSize; dwIndex++)
			{
				BYTE src = m_lpImage[dwOffset + dwIndex];
				BYTE dst = lpPattern[dwIndex];

				if (lpMask)
				{
					src &= lpMask[dwIndex];
					dst &= lpMask[dwIndex];
				}

				if (src != dst)
				{
					bMatched = FALSE;
					break;
				}
			}
			
			if (bMatched)
				return m_lpImage + dwOffset;

			dwOffset++;
		}
	}

	return NULL;
}
//////////////////////////////////////////////////////////////////////
LPBYTE CNVBIOS::FindBIT()
{
	const BYTE sign[] = { 0xFF, 0xB8, 0x42, 0x49, 0x54, 0x00 };

	return FindPattern((LPBYTE)sign, NULL, sizeof(sign), 0);
}
//////////////////////////////////////////////////////////////////////
LPBYTE CNVBIOS::FindBITToken(DWORD dwID)
{
	if (m_lpBIT)
	{
		for (DWORD dwToken=0; dwToken<m_lpBIT[10]; dwToken++)
			//loop though all BIT tokens 
		{
			LPBYTE lpToken = m_lpBIT + m_lpBIT[8] + m_lpBIT[9] * dwToken;
				//get ptr to current BIT token

			if (lpToken[0] == dwID)
				//return ptr to current token if its' ID matches with desired one
				return lpToken;
		}
	}

	return NULL;
}
//////////////////////////////////////////////////////////////////////
LPBYTE CNVBIOS::FindThermalTable()
{
	LPBYTE lpToken = FindBITToken(0x50);
		//try to find performance area token in BIT tokens list

	if (lpToken)
	{
		WORD offPerfArea = *(LPWORD)(lpToken + 0x04);
			//get performance area offset

		if (offPerfArea)
			//once performance area offset is determined, we
			//can get thermal table offset
		{
			WORD offThermTable = *(LPWORD)(m_lpImage + offPerfArea + 0x0C);
		
			if (offThermTable)
				return m_lpImage + offThermTable;
		}
	}

	return NULL;
}
//////////////////////////////////////////////////////////////////////
LPBYTE CNVBIOS::EnumThermalTableEntries(DWORD dwEntry)
{
	LPBYTE lpThermTable = FindThermalTable();
		//try to get ptr to thermal table

	if (lpThermTable)
	{
		if (dwEntry < lpThermTable[3])
			return  lpThermTable + lpThermTable[1] + lpThermTable[2] * dwEntry;
	}

	return NULL;
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::EnumThermalTableEntries(LPNVBIOS_ENUM_THERMAL_TABLE_ENTRIES_PROC lpEnumProc, LPVOID lpContext)
{
	if (lpEnumProc)
		//check if specidied enumeration procedure ptr is valid
	{
		DWORD dwEntry = 0;
			//init thermal table entry index for enumeration

		for (;;)
		{
			LPBYTE lpEntry = EnumThermalTableEntries(dwEntry);
				//enumerate entry by current index

			if (!lpEntry)
				//break loop if we've reached the end of thermal table
				break;

			lpEnumProc(lpEntry, lpContext);
				//call enumeration procedure for the current entry

			dwEntry++;
		}
	}
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::GetThermalTableTemperatureCompensation(LONG* lpResult)
{
	EnumThermalTableEntries(EnumProcGetThermalTableTemperatureCompensation, (LPVOID)lpResult);
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::GetThermalTableDiodeGainMul(LONG* lpResult)
{
	EnumThermalTableEntries(EnumProcGetThermalTableDiodeGainMul, (LPVOID)lpResult);
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::GetThermalTableDiodeGainDiv(LONG* lpResult)
{
	EnumThermalTableEntries(EnumProcGetThermalTableDiodeGainDiv, (LPVOID)lpResult);
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::GetThermalTableDiodeOffsetMul(LONG* lpResult)
{
	EnumThermalTableEntries(EnumProcGetThermalTableDiodeOffsetMul, (LPVOID)lpResult);
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::GetThermalTableDiodeOffsetDiv(LONG* lpResult)
{
	EnumThermalTableEntries(EnumProcGetThermalTableDiodeOffsetDiv, (LPVOID)lpResult);
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::EnumProcGetThermalTableTemperatureCompensation(LPBYTE lpEntry, LPVOID lpContext)
{
	if (*lpEntry == 0x01)
		//process thermal table setup entries
	{
		WORD value = *(LPWORD)(lpEntry + 1);

		if ((value & 0x8F) == 0)
			//check if setup entry specifies temperature compensation
			*(LONG*)lpContext = (value>>9) & 0x7f;
	}
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::EnumProcGetThermalTableDiodeOffsetMul(LPBYTE lpEntry, LPVOID lpContext)
{
	if (*lpEntry == 0x10)
		//process thermal table diode offset multiplier definition entries
		*(LONG*)lpContext = *(SHORT*)(lpEntry + 1);
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::EnumProcGetThermalTableDiodeOffsetDiv(LPBYTE lpEntry, LPVOID lpContext)
{
	if (*lpEntry == 0x11)
		//process thermal table diode offset divider definition entries
		*(LONG*)lpContext = *(SHORT*)(lpEntry + 1);
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::EnumProcGetThermalTableDiodeGainMul(LPBYTE lpEntry, LPVOID lpContext)
{
	if (*lpEntry == 0x12)
		//process thermal table diode gain multiplier definition entries
		*(LONG*)lpContext = *(SHORT*)(lpEntry + 1);
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::EnumProcGetThermalTableDiodeGainDiv(LPBYTE lpEntry, LPVOID lpContext)
{
	if (*lpEntry == 0x13)
		//process thermal table diode gain divider definition entries
		*(LONG*)lpContext = *(SHORT*)(lpEntry + 1);
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::GetThermalTableTemperatureThreshold(DWORD dwChannelID, DWORD dwThresholdID, LONG *lpResult)
{
	DWORD dwCurChannelID	= 0x0F;
		//init current temperature channel ID
	DWORD dwEntry			= 0;
		//init thermal table entry index for enumeration

	for (;;)
	{
		LPBYTE lpEntry = EnumThermalTableEntries(dwEntry);
			//enumerate entry by current index

		if (!lpEntry)
			//break loop if we've reached the end of thermal table
			break;

		WORD value	= *(LPWORD)(lpEntry + 1);
			//get thermal entry value

		if (*lpEntry == 0)
			//process thermal table temperature channed definition entry and redefine current temperature channel ID
			dwCurChannelID = value & 0x0F;

		if ((dwChannelID == dwCurChannelID) && (*lpEntry == dwThresholdID))
			//return temperature threshold if we've found desired temperature channel specific entry
			*lpResult = value;

		dwEntry++;
	}
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::GetThermalTableDiodeOffsetBinMul(LONG dwDiodeOffsetBin, LONG *lpResult)
{
	LONG	dwDiodeOffsetBinInp0;
	LONG	dwDiodeOffsetBinInp1;

	LONG	dwDiodeOffsetBinOut0;
	LONG	dwDiodeOffsetBinOut1;

	DWORD dwEntry = 0;
		//init thermal table entry index for enumeration

	for (;;)
	{
		LPBYTE lpEntry = EnumThermalTableEntries(dwEntry);
			//enumerate entry by current index

		if (!lpEntry)
			//break loop if we've reached the end of thermal table
			break;

		WORD value	= *(LPWORD)(lpEntry + 1);
			//get thermal entry value

		switch (*lpEntry)
		{
		case 0x33:	//process binary diode offset mapping graph input points pair definition entry
			dwDiodeOffsetBinInp0 = (char)((value	) & 0xff);
			dwDiodeOffsetBinInp1 = (char)((value>>8	) & 0xff);
				//save input points, we'll use them when processing subsequent
				//output points pair
			break;
		case 0x34:	//process binary diode offset mapping graph output points pair definition entry
			dwDiodeOffsetBinOut0 = (char)((value	) & 0xff) * 25;
			dwDiodeOffsetBinOut1 = (char)((value>>8	) & 0xff) * 25;
				//convert output points to 0.01°C units

			if ((dwDiodeOffsetBin >= dwDiodeOffsetBinInp0) &&
				(dwDiodeOffsetBin <= dwDiodeOffsetBinInp1))
				//our binary offset belongs to specified range, so we can convert it to °C using
				//linearry interpolated graph
			{
				if (dwDiodeOffsetBinInp0 == dwDiodeOffsetBinInp1)
					*lpResult = dwDiodeOffsetBinOut0;
				else
					*lpResult = dwDiodeOffsetBinOut0 + (dwDiodeOffsetBin - dwDiodeOffsetBinInp0) * (dwDiodeOffsetBinOut1 - dwDiodeOffsetBinOut0) / (dwDiodeOffsetBinInp1 - dwDiodeOffsetBinInp0);
			}
			break;
		}

		dwEntry++;
	}
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::GetThermalTableDiodeOffsetBinDiv(LONG dwDiodeOffsetBin, LONG *lpResult)
{
	LONG	dwDiodeOffsetBinInp0;
	LONG	dwDiodeOffsetBinInp1;

	DWORD dwEntry = 0;
		//init thermal table entry index for enumeration

	for (;;)
	{
		LPBYTE lpEntry = EnumThermalTableEntries(dwEntry);
			//enumerate entry by current index

		if (!lpEntry)
			//break loop if we've reached the end of thermal table
			break;

		WORD value = *(LPWORD)(lpEntry + 1);
			//get thermal entry value

		switch (*lpEntry)
		{
		case 0x33:	//process binary diode offset mapping graph input points pair definition entry
			dwDiodeOffsetBinInp0 = (char)((value	) & 0xff);
			dwDiodeOffsetBinInp1 = (char)((value>>8	) & 0xff);
				//save input points, we'll use them when processing subsequent
				//output points pair
			break;
		case 0x34:	//process binary diode offset mapping graph output points pair definition entry
			if ((dwDiodeOffsetBin >= dwDiodeOffsetBinInp0) &&
				(dwDiodeOffsetBin <= dwDiodeOffsetBinInp1))
				//our binary offset belongs to specified range, so we can return fixed divider used
				//for linear interpolation
				*lpResult = 100;
			break;
		}

		dwEntry++;
	}
}
//////////////////////////////////////////////////////////////////////
LPBYTE CNVBIOS::FindVariableVirtualizationDefTable()
{
	LPBYTE lpToken = FindBITToken(0x56);
		//try to find variable virtualization area token in BIT tokens list

	if (lpToken)
	{
		WORD offVarArea = *(LPWORD)(lpToken + 0x04);
			//get variable virtualization area offset

		if (offVarArea)
			//once variable virtualization area offset is determined, we
			//can get variable virtualization definition table offset
		{
			WORD offVarDefTable = *(LPWORD)(m_lpImage + offVarArea);
		
			if (offVarDefTable)
				return m_lpImage + offVarDefTable;
		}
	}

	return NULL;
}
//////////////////////////////////////////////////////////////////////
LPBYTE CNVBIOS::FindVariableVirtualizationImpTable()
{
	LPBYTE lpToken = FindBITToken(0x56);
		//try to find variable virtualization area token in BIT tokens list

	if (lpToken)
	{
		WORD offVarArea = *(LPWORD)(lpToken + 0x04);
			//get variable virtualization area offset

		if (offVarArea)
			//once variable virtualization area offset is determined, we
			//can get variable virtualization implementation table offset
		{
			WORD offVarImpTable = *(LPWORD)(m_lpImage + offVarArea + 0x02);
		
			if (offVarImpTable)
				return m_lpImage + offVarImpTable;
		}
	}

	return NULL;
}
//////////////////////////////////////////////////////////////////////
LPBYTE CNVBIOS::EnumVariableVirtualizationDefTableEntries(DWORD dwEntry)
{
	LPBYTE lpVarDefTable = FindVariableVirtualizationDefTable();
		//try to get ptr to variable virtualization definition table

	if (lpVarDefTable)
	{
		if (dwEntry < lpVarDefTable[2])
			return  lpVarDefTable + 3 + lpVarDefTable[1] * dwEntry;
	}

	return NULL;
}
//////////////////////////////////////////////////////////////////////
LPBYTE CNVBIOS::EnumVariableVirtualizationImpTableEntries(DWORD dwEntry)
{
	LPBYTE lpVarImpTable = FindVariableVirtualizationImpTable();
		//try to get ptr to variable virtualization implementation table

	if (lpVarImpTable)
	{
		if (dwEntry < lpVarImpTable[2])
			return  lpVarImpTable + 3 + lpVarImpTable[1] * dwEntry;
	}

	return NULL;
}
//////////////////////////////////////////////////////////////////////
void CNVBIOS::GetVirtualVariable(DWORD dwVarID, LONG *lpResult)
{
	DWORD dwDefEntry = 0;
		//init definition entry index for enumeration

	for (;;)
	{
		LPBYTE lpDefEntry = EnumVariableVirtualizationDefTableEntries(dwDefEntry);
			//enumerate definition entry by current index

		if (!lpDefEntry)
			//break loop if we've reached the end of definition table
			break;

		if (*lpDefEntry == dwVarID)
			//we've found requested variable definition
		{
			WORD value = *(LPWORD)(lpDefEntry + 1);
				//get definition entry value

			DWORD dwDefParam0	= (value		) & 0x1F;
			DWORD dwDefParam1	= (value>>0x05	) & 0x1F;
			DWORD dwImpEntry	= (value>>0x0A	) & 0x0F;
				//extract definition paramaters

			LPBYTE lpImpEntry = EnumVariableVirtualizationImpTableEntries(dwImpEntry);
				//get implementation entry by index specified in variable definition

			if (lpImpEntry)
			{
				DWORD dwImpParam0 = *(LPDWORD)(lpImpEntry + 1);
				DWORD dwImpParam1 = *(LPDWORD)(lpImpEntry + 5);
				DWORD dwImpParam2 = *(LPDWORD)(lpImpEntry + 9);

				switch (*lpImpEntry)
					//VGA BIOS virtaulization implemntation tokens allow multiple  hardware
					//access scenarios (via IO ports, MMIO registers etc), but we'll handle
					//only DWORD MMIO registers access implementation tokens
				{
				case 0x34:	//DWORD MMIO register

					//def parameters : bit range (the first and the last indices)
					//imp parameters : register address

					DWORD dwMask = (0xffffffff>>(31 - dwDefParam1 + dwDefParam0));
						//convert bit indices into the mask shifted to bit 0
					
					*lpResult = (g_pReadRegisterUlongEx(m_dwGpu, dwImpParam0)>>dwDefParam0) & dwMask; 
						//read MMIO register and get the requested bits
					break;
				}
			}
		}

		dwDefEntry++;
	}
}
//////////////////////////////////////////////////////////////////////
