#include <string.h>
#include <crtdbg.h>
#include <windows.h>

#include "NDTSEnums.h"
#include "SDKUtilities.h"
#include "NDTSCommonHAL_PLX_v5x.h"

#define MAJOR_VERSION_NUMBER 0
#define MINOR_VERSION_NUMBER 1

#define NUMBER_OF_BUFFERED_CARDS 16
#define SECONDARY_ID_EEPROM_OFFSET 0x44

// PCI state struct (supercedes PCI_State in RQ SDK)
typedef struct 
{
	PLX_DEVICE_OBJECT deviceObject ;
	PLX_PHYSICAL_MEM pciBuffer ;
	U32 pCommonBuffer ;
	U32 pPCIBar2VirtualAddress ;
	PLX_DMA_PARAMS dmaParams ;
	PLX_DMA_PROP dmaProps ;
	NDTS_Boolean isDMAChannelOpen ;
} PLXv5xPCIState ;

// typedefs for PLX functions
typedef RETURN_CODE (* FPPlxPci_ApiVersion)(U8 *pVersionMajor, U8 *pVersionMinor, U8 *pVersionRevision) ;
typedef RETURN_CODE (* FPPlxPci_DeviceFind)(PLX_DEVICE_KEY *pKey, U8 DeviceNumber) ;
typedef RETURN_CODE (* FPPlxPci_DeviceOpen)(PLX_DEVICE_KEY *pKey, PLX_DEVICE_OBJECT *pDevice) ;
typedef RETURN_CODE (* FPPlxPci_DeviceClose)(PLX_DEVICE_OBJECT *pDevice) ;
typedef U32 (* FPPlxPci_VpdRead)(PLX_DEVICE_OBJECT *pDevice, U16 offset, RETURN_CODE *pReturnCode) ;
typedef PLX_EEPROM_STATUS (* FPPlxPci_EepromPresent)(PLX_DEVICE_OBJECT *pDevice, RETURN_CODE *pReturnCode) ;
typedef RETURN_CODE (* FPPlxPci_EepromWriteByOffset)(PLX_DEVICE_OBJECT *pDevice, U16 offset, U32 value) ;
typedef RETURN_CODE (* FPPlxPci_PciBarMap)(PLX_DEVICE_OBJECT *pDevice, U8 BarIndex, VOID **pVa) ;
typedef RETURN_CODE (* FPPlxPci_PciBarUnmap)(PLX_DEVICE_OBJECT *pDevice, VOID **pVa) ;
typedef RETURN_CODE (* FPPlxPci_CommonBufferProperties)(PLX_DEVICE_OBJECT *pDevice, PLX_PHYSICAL_MEM *pMemoryInfo) ;
typedef RETURN_CODE (* FPPlxPci_CommonBufferMap)(PLX_DEVICE_OBJECT *pDevice, VOID **pVa) ;
typedef RETURN_CODE (* FPPlxPci_DmaChannelOpen)(PLX_DEVICE_OBJECT *pDevice, U8 channel, PLX_DMA_PROP *pDmaProp) ;
typedef RETURN_CODE (* FPPlxPci_DmaChannelClose)(PLX_DEVICE_OBJECT *pDevice, U8 channel) ;
typedef RETURN_CODE (* FPPlxPci_DmaTransferBlock)(PLX_DEVICE_OBJECT *pDevice, U8 channel, PLX_DMA_PARAMS *pDmaParams, U64 Timeout_ms) ;

// struct for PLX function pointers
typedef struct 
{
	FPPlxPci_DeviceFind fpPlxPci_DeviceFind ;
	FPPlxPci_DeviceOpen fpPlxPci_DeviceOpen ;
	FPPlxPci_DeviceClose fpPlxPci_DeviceClose ;
	FPPlxPci_VpdRead fpPlxPci_VpdRead ;
	FPPlxPci_EepromPresent fpPlxPci_EepromPresent ;
	FPPlxPci_EepromWriteByOffset fpPlxPci_EepromWriteByOffset ;
	FPPlxPci_PciBarMap fpPlxPci_PciBarMap ;
	FPPlxPci_PciBarUnmap fpPlxPci_PciBarUnmap ;
	FPPlxPci_CommonBufferProperties fpPlxPci_CommonBufferProperties ;
	FPPlxPci_CommonBufferMap fpPlxPci_CommonBufferMap ;
	FPPlxPci_DmaChannelOpen fpPlxPci_DmaChannelOpen ;
	FPPlxPci_DmaChannelClose fpPlxPci_DmaChannelClose ;
	FPPlxPci_DmaTransferBlock fpPlxPci_DmaTransferBlock ;
} PLXv5xAPIFPs ;

#define MAX_DLL_NAME_LENGTH 512

// This HAL implementation global state
typedef struct
{
	NDTS_Boolean isAvailable, isInitialised ;
	unsigned long numberOfActiveCards, numberOfAvailableCards ;
	HINSTANCE hDLL ;
	char pDLLName[MAX_DLL_NAME_LENGTH] ;
	PLXv5xAPIFPs fpPLXFuncs ;
	PLXv5xPCIState *ppPCIStates[NUMBER_OF_BUFFERED_CARDS] ;
} PLXv5xGlobalState ;

PLXv5xGlobalState plxv5xGlobalState = {NDTS_BFalse, NDTS_BFalse, 0, 0, 0} ;

NDTS_Boolean NDTS_HAL_PLX_v5x_GetHALIDGeneric(NDTS_HAL_ID *pHALID, const char *pName, const char *pDLLName, const unsigned long majorVersion, const unsigned long minorVersion)
{
	NDTS_HAL_ID plx5xHALID ;
	NDTS_Boolean isAvailable = NDTS_BFalse ;

	strcpy(plx5xHALID.pName, pName) ;
	plx5xHALID.majorVersionNumber = MAJOR_VERSION_NUMBER ;
	plx5xHALID.minorVersionNumber = MINOR_VERSION_NUMBER ;

	// Get plxapi version number
	{
		HINSTANCE hDLL;
		FPPlxPci_ApiVersion fpPlxPci_ApiVersion ;

		hDLL = LoadLibrary(pDLLName) ;
		if (hDLL != NULL)
		{
			fpPlxPci_ApiVersion = (FPPlxPci_ApiVersion) GetProcAddress(hDLL, "PlxPci_ApiVersion") ;
			if (fpPlxPci_ApiVersion != 0)
			{
				U8 majorIndex = 0, minorIndex = 0, revisionIndex = 0 ;
				RETURN_CODE rc = ApiSuccess ;
				NDTS_Boolean isDLLAvailable = NDTS_BFalse ;
				rc = (*fpPlxPci_ApiVersion)(&majorIndex, &minorIndex, &revisionIndex) ;
				isDLLAvailable = rc == ApiSuccess && majorIndex == majorVersion && minorIndex == minorVersion ? NDTS_BTrue : NDTS_BFalse ;
				if (isDLLAvailable)
				{
					FPPlxPci_DeviceFind fpPlxPci_DeviceFind = (FPPlxPci_DeviceFind) GetProcAddress(hDLL, "PlxPci_DeviceFind") ;
					if (fpPlxPci_DeviceFind != 0)
					{
						PLX_DEVICE_KEY key ;
						memset(&key, PCI_FIELD_IGNORE, sizeof(key)) ;
						rc = (*fpPlxPci_DeviceFind)(&key, 0) ;
						isAvailable = rc != ApiNoActiveDriver ;
					}
				}
			}

			FreeLibrary(hDLL) ;
		}

		plx5xHALID.isAvailable = isAvailable ;
		plxv5xGlobalState.isAvailable = isAvailable ;

		strcpy(plxv5xGlobalState.pDLLName, pDLLName) ;
	}

	// Set initialise function pointer
	plx5xHALID.fpNDTS_HAL_InitialiseHAL = &NDTS_HAL_PLX_v5x_InitialiseHAL ;

	// Return our HAL ID
	*pHALID = plx5xHALID ;

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_GetBoardsIDs(unsigned short vendorID, unsigned short deviceID, NDTS_CardSecondaryIDs cardID, NDTS_HAL_BoardID **ppBoardIDs, unsigned long numberOfBufferedBoardIDs, unsigned long *pNumberOfBoards)
{
	RETURN_CODE rc ;
	PLX_DEVICE_KEY deviceKey ;
	NDTS_Boolean isPLXDeviceFound = NDTS_BFalse,
		         isOK = NDTS_BTrue ;
	U8 deviceIndex = 0 ;
	unsigned long numberOfBoards = 0,
		          i = 0 ;
	U8 ignoreField8 = (U8) PCI_FIELD_IGNORE ;
 	U16 ignoreField16 = (U16) PCI_FIELD_IGNORE ;

	do
	{
		deviceKey.bus = ignoreField8 ;
		deviceKey.slot = ignoreField8 ;
		deviceKey.function = ignoreField8 ;
		deviceKey.VendorId = vendorID ;
		deviceKey.DeviceId = deviceID ;
		deviceKey.SubVendorId = ignoreField16 ;
		deviceKey.SubDeviceId = ignoreField16 ;
		deviceKey.Revision = ignoreField8 ;

		rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceFind)(&deviceKey, deviceIndex) ;
		isPLXDeviceFound = rc == ApiSuccess ;
		if (isPLXDeviceFound)
		{
			NDTS_Boolean isNDTSDeviceFound = NDTS_BFalse ;
			PLX_DEVICE_OBJECT deviceObject ;
			U32 subSystemID ;
			NDTS_CardSecondaryIDs cardSecondaryID ;

			rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceOpen)(&deviceKey, &deviceObject) ;
			if (rc != ApiSuccess)
			{
				NDTS_HAL_HandleHALError(NDTS_HAL_GetBoardIDs_OpenDeviceFail) ;
				deviceIndex++ ;
				continue ;
			}

			subSystemID = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_VpdRead)(&deviceObject, SECONDARY_ID_EEPROM_OFFSET, &rc) ;
			if (rc != ApiSuccess)
			{
				deviceIndex++ ;
				continue;
			}

			rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceClose)(&deviceObject) ;
			if (rc != ApiSuccess)
			{
				NDTS_HAL_HandleHALError(NDTS_HAL_GetBoardIDs_CloseDeviceFail) ;
				deviceIndex++ ;
				continue;
			}

			cardSecondaryID = (NDTS_CardSecondaryIDs) subSystemID ;

			isNDTSDeviceFound = cardSecondaryID == cardID || cardID == NDTS_HAL_WILDCARD_SECONDARY_DEVICE_ID ? NDTS_BTrue : NDTS_BFalse ;

			if (isNDTSDeviceFound)
				if (plxv5xGlobalState.numberOfAvailableCards < NUMBER_OF_BUFFERED_CARDS)
				{
					ppBoardIDs[numberOfBoards] = (NDTS_HAL_BoardID *) malloc(sizeof(NDTS_HAL_BoardID)) ;

					ppBoardIDs[numberOfBoards]->busIndex = deviceKey.bus ;
					ppBoardIDs[numberOfBoards]->slotIndex = deviceKey.slot ;
					ppBoardIDs[numberOfBoards]->vendorID = deviceKey.VendorId ;
					ppBoardIDs[numberOfBoards]->deviceID = deviceKey.DeviceId ;
					ppBoardIDs[numberOfBoards]->secondaryDeviceID = cardSecondaryID ;
					ppBoardIDs[numberOfBoards]->cardIndex = numberOfBoards ;
					ppBoardIDs[numberOfBoards]->internalCardIndex = plxv5xGlobalState.numberOfAvailableCards ;
					ppBoardIDs[numberOfBoards]->isActive = NDTS_BFalse ;

					numberOfBoards++ ;
					plxv5xGlobalState.numberOfAvailableCards++ ;
				}
				else
				{
					NDTS_HAL_HandleHALError(NDTS_HAL_GetBoardIDs_InsufficientInternalBoardStateBufferSize) ;
					isOK = NDTS_BFalse ;
				}
		}

		deviceIndex++ ;
	}
	while (isPLXDeviceFound && isOK) ;

	for (i = numberOfBoards ; i < numberOfBufferedBoardIDs ; i++)
		ppBoardIDs[i] = 0 ;

	*pNumberOfBoards = numberOfBoards ;

	return isOK ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_InitialiseHAL(NDTS_HAL_Funcs *pHALFuncs)
{
	// Check if HAL is available
	if (plxv5xGlobalState.isAvailable != NDTS_BTrue)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_HALIsNotAvailable) ;
		return NDTS_BFalse ;
	}

	// Load DLL if not already loaded
	if (plxv5xGlobalState.hDLL == 0)
	{
		plxv5xGlobalState.hDLL = LoadLibrary(plxv5xGlobalState.pDLLName) ;

		if (plxv5xGlobalState.hDLL == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLL) ;
			return NDTS_BFalse ;
		}
	}

	// Set our exposed function pointers
	{
		pHALFuncs->fpNDTS_HAL_GetBoardsIDs = &NDTS_HAL_PLX_v5x_GetBoardsIDs ;
		pHALFuncs->fpNDTS_HAL_AddBoard = &NDTS_HAL_PLX_v5x_AddBoard ;
		pHALFuncs->fpNDTS_HAL_RemoveBoard = &NDTS_HAL_PLX_v5x_RemoveBoard ;
		pHALFuncs->fpNDTS_HAL_OpenDMAChannel = &NDTS_HAL_PLX_v5x_OpenDMAChannel ;
		pHALFuncs->fpNDTS_HAL_CloseDMAChannel = &NDTS_HAL_PLX_v5x_CloseDMAChannel ;
		pHALFuncs->fpNDTS_HAL_GetCommonBufferInfo = &NDTS_HAL_PLX_v5x_GetCommonBufferInfo ;
		pHALFuncs->fpNDTS_HAL_PerformDMARead = &NDTS_HAL_PLX_v5x_PerformDMARead ;
		pHALFuncs->fpNDTS_HAL_PerformDMAWrite = &NDTS_HAL_PLX_v5x_PerformDMAWrite ;
		pHALFuncs->fpNDTS_HAL_WriteSecondaryDeviceID = &NDTS_HAL_PLX_v5x_WriteSecondaryDeviceID ;
		pHALFuncs->fpNDTS_HAL_SetWord = &NDTS_HAL_PLX_v5x_SetWord ;
		pHALFuncs->fpNDTS_HAL_GetWord = &NDTS_HAL_PLX_v5x_GetWord ;
		pHALFuncs->fpNDTS_HAL_BoardByteCopy = &NDTS_HAL_PLX_v5x_BoardByteCopy ;
	}

	// Set our internal function pointers
	{
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceFind = (FPPlxPci_DeviceFind) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_DeviceFind") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceFind == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceOpen = (FPPlxPci_DeviceOpen) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_DeviceOpen") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceOpen == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceClose = (FPPlxPci_DeviceClose) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_DeviceClose") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceClose == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_VpdRead = (FPPlxPci_VpdRead) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_VpdRead") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_VpdRead == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_EepromPresent = (FPPlxPci_EepromPresent) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_EepromPresent") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_EepromPresent == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_EepromWriteByOffset = (FPPlxPci_EepromWriteByOffset) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_EepromWriteByOffset") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_EepromWriteByOffset == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_PciBarMap = (FPPlxPci_PciBarMap) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_PciBarMap") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_PciBarMap == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_PciBarUnmap = (FPPlxPci_PciBarUnmap) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_PciBarUnmap") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_PciBarUnmap == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_CommonBufferProperties = (FPPlxPci_CommonBufferProperties) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_CommonBufferProperties") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_CommonBufferProperties == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_CommonBufferMap = (FPPlxPci_CommonBufferMap) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_CommonBufferMap") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_CommonBufferMap == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DmaChannelOpen = (FPPlxPci_DmaChannelOpen) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_DmaChannelOpen") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DmaChannelOpen == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DmaChannelClose = (FPPlxPci_DmaChannelClose) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_DmaChannelClose") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DmaChannelClose == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DmaTransferBlock = (FPPlxPci_DmaTransferBlock) GetProcAddress(plxv5xGlobalState.hDLL, "PlxPci_DmaTransferBlock") ;
		if (plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DmaTransferBlock == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
	}

	// Zero ppPCIStates pointers if this is the first time this funcion has been called.
	if (!plxv5xGlobalState.isInitialised)
	{
		unsigned long i ;

		for (i = 0 ; i < NUMBER_OF_BUFFERED_CARDS ; i++)
			plxv5xGlobalState.ppPCIStates[i] = 0 ;

		// Good to go...
		plxv5xGlobalState.isInitialised = NDTS_BTrue ;
	}

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_AddBoard(NDTS_HAL_BoardID *pBoardID)
{
	if (!plxv5xGlobalState.isInitialised)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_HALIsNotInitialised) ;
		return NDTS_BFalse ;
	}

	// Get some memory from the heap
	plxv5xGlobalState.ppPCIStates[pBoardID->internalCardIndex] = (PLXv5xPCIState *) malloc(sizeof(PLXv5xPCIState)) ;

	// Locate appropriate card
	{
		RETURN_CODE rc ;
		PLXv5xPCIState *pPCIState = plxv5xGlobalState.ppPCIStates[pBoardID->internalCardIndex] ;
		PLX_DEVICE_OBJECT deviceObject ;
		unsigned long deviceIndex = 0,
			          maxDeviceIndex = 8,
					  potentialDeviceIndex = 0 ;
		NDTS_Boolean isCardTypeFound = NDTS_BFalse, isCardFound = NDTS_BFalse ;
		U8 ignoreField8 = (U8) PCI_FIELD_IGNORE ;
		U16 ignoreField16 = (U16) PCI_FIELD_IGNORE ;

		memset(&deviceObject, 0, sizeof(deviceObject)) ;

		do
		{
			RETURN_CODE rc ;
			PLX_DEVICE_KEY deviceKey ;
			NDTS_Boolean isPLXDeviceFound = NDTS_BFalse ;
			U8 u8DeviceIndex = (U8) deviceIndex ;

			deviceKey.bus = ignoreField8 ;
			deviceKey.slot = ignoreField8 ;
			deviceKey.function = ignoreField8 ;
			deviceKey.VendorId = pBoardID->vendorID ;
			deviceKey.DeviceId = pBoardID->deviceID ;
			deviceKey.SubVendorId = ignoreField16 ;
			deviceKey.SubDeviceId = ignoreField16 ;
			deviceKey.Revision = ignoreField8 ;

			rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceFind)(&deviceKey, u8DeviceIndex) ;
			isPLXDeviceFound = rc == ApiSuccess ;
			if (isPLXDeviceFound)
			{
				U32 subSystemID ;

				rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceOpen)(&deviceKey, &deviceObject) ;
				if (rc != ApiSuccess)
				{
					NDTS_HAL_HandleHALError(NDTS_HAL_GetBoardIDs_OpenDeviceFail) ;
					deviceIndex++ ;
					continue;
				}

				subSystemID = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_VpdRead)(&deviceObject, SECONDARY_ID_EEPROM_OFFSET, &rc) ;
				if (rc != ApiSuccess)
				{
					deviceIndex++ ;
					continue;
				}

				isCardTypeFound = (NDTS_CardSecondaryIDs) subSystemID == pBoardID->secondaryDeviceID ? NDTS_BTrue : NDTS_BFalse ;

				if (isCardTypeFound)
				{
					isCardFound = potentialDeviceIndex == pBoardID->cardIndex ? NDTS_BTrue : NDTS_BFalse ;
					potentialDeviceIndex++ ;
				}
			}

			if (!isCardFound)
			{
				rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceClose)(&deviceObject) ;
				if (rc != ApiSuccess)
				{
					NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_CloseDeviceFail) ;
					return NDTS_BFalse ;
				}
			}

			deviceIndex++ ;
		}
		while (!isCardFound && deviceIndex < maxDeviceIndex) ;

		if (isCardFound)
			pPCIState->deviceObject = deviceObject ;
		else
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_CardNotFound) ;
			return NDTS_BFalse ;
		}

		{
			void *pvPCIBar2VirtualAddress = 0 ;

			rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_PciBarMap)(&pPCIState->deviceObject, 2, &pvPCIBar2VirtualAddress) ;
			if (rc != ApiSuccess)
			{
				NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_BarMapFail) ;
				return NDTS_BFalse ;
			}

			pPCIState->pPCIBar2VirtualAddress = (U32) pvPCIBar2VirtualAddress ;
		}

		rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_CommonBufferProperties)(&pPCIState->deviceObject, &pPCIState->pciBuffer) ;
		if (rc != ApiSuccess)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_GetCommonBufferPropertiesFail) ;
			return NDTS_BFalse ;
		}

		{
			void *pvCommonBuffer = 0 ;

			rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_CommonBufferMap)(&pPCIState->deviceObject, &pvCommonBuffer) ;
			if (rc != ApiSuccess)
			{
				NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_CommonBufferMapFail) ;
				return NDTS_BFalse ;
			}

			pPCIState->pCommonBuffer = (U32) pvCommonBuffer ;
		}
	}

	plxv5xGlobalState.numberOfActiveCards++ ;

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_RemoveBoard(NDTS_HAL_BoardID *pBoardID)
{
	PLXv5xPCIState *pPCIState = 0 ;
	RETURN_CODE rc = ApiSuccess ;
	NDTS_Boolean isOK = NDTS_BTrue ;
	unsigned long internalCardIndex = pBoardID->internalCardIndex ;

	pPCIState = plxv5xGlobalState.ppPCIStates[internalCardIndex] ;

	rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_PciBarUnmap)(&pPCIState->deviceObject, (void **) &pPCIState->pPCIBar2VirtualAddress) ;
	if (rc != ApiSuccess)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_RemoveBoard_BarUnmapFail) ;
		isOK = NDTS_BFalse ;
	}

	rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceClose)(&pPCIState->deviceObject) ;
	if (rc  != ApiSuccess)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_RemoveBoard_CloseDeviceFail) ;
		isOK = NDTS_BFalse ;
	}

	free(pPCIState) ;
	plxv5xGlobalState.ppPCIStates[pBoardID->internalCardIndex] = 0 ;

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_OpenDMAChannel(unsigned long internalCardIndex)
{
	PLXv5xPCIState *pPCIState = 0 ;
	RETURN_CODE rc = ApiSuccess ;
	NDTS_Boolean isOK ;

	isOK = NDTS_HAL_PLX_v5x_CheckCardIndexState(internalCardIndex) ;
	if (!isOK)
		return NDTS_BFalse ;

	pPCIState = plxv5xGlobalState.ppPCIStates[internalCardIndex] ;

	memset(&pPCIState->dmaProps, 0, sizeof(pPCIState->dmaProps)) ;

	/*
		PLX_DMA_PROP:
											Tried	(-: on, x: no help, *: breaks)
			U8 ReadyInput           :1;			-
			U8 Burst                :1;			-
			U8 BurstInfinite        :1;			-
			U8 WriteInvalidMode     :1;			x
			U8 LocalAddrConst       :1;			x
			U8 DualAddressCycles    :1;			x
			U8 ValidMode            :1;			x
			U8 TransferCountClear   :1;			x
			U8 ValidStopControl     :1;			x
			U8 DemandMode           :1;			*
			U8 EOTPin               :1;			*
			U8 EOTEndLink           :1;			x
			U8 StopTransferMode     :1;			x
			U8 LocalBusWidth        :2;			-
			U8 WaitStates           :4;			x
	*/

	pPCIState->dmaProps.ReadyInput = 1; // Enable READY# input
	pPCIState->dmaProps.Burst = 1; // Use burst of 4LW
	pPCIState->dmaProps.LocalBusWidth = 2; // Local device is 32-bit port
	pPCIState->dmaProps.BurstInfinite = 1 ;
	
	rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DmaChannelOpen)(&pPCIState->deviceObject, 0, &pPCIState->dmaProps) ;
	if (rc != ApiSuccess)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_OpenDMAChannel_DMABlockChannelOpenFail) ;
		return NDTS_BFalse ;
	}

	pPCIState->isDMAChannelOpen = NDTS_BTrue ;

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_CloseDMAChannel(unsigned long internalCardIndex)
{
	PLXv5xPCIState *pPCIState = 0 ;
	RETURN_CODE rc = ApiSuccess ;
	NDTS_Boolean isOK ;

	isOK = NDTS_HAL_PLX_v5x_CheckCardIndexState(internalCardIndex) ;
	if (!isOK)
		return NDTS_BFalse ;

	pPCIState = plxv5xGlobalState.ppPCIStates[internalCardIndex] ;

	rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DmaChannelClose)(&pPCIState->deviceObject, 0) ;
	if (rc != ApiSuccess)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_CloseDMAChannel_DMABlockChannelCloseFail) ;
		return NDTS_BFalse ;
	}

	pPCIState->isDMAChannelOpen = NDTS_BFalse ;

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_GetCommonBufferInfo(unsigned long internalCardIndex, void **ppPhysicalAddress, unsigned long *pNumberOfBufferBytes)
{
	PLXv5xPCIState *pPCIState = 0 ;
	NDTS_Boolean isOK ;

	isOK = NDTS_HAL_PLX_v5x_CheckCardIndexState(internalCardIndex) ;
	if (!isOK)
		return NDTS_BFalse ;

	pPCIState = plxv5xGlobalState.ppPCIStates[internalCardIndex] ;

	*ppPhysicalAddress = (void *) pPCIState->pCommonBuffer ;
	*pNumberOfBufferBytes = pPCIState->pciBuffer.Size ;

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_PerformDMARead(unsigned long internalCardIndex, unsigned long localAddress, unsigned long numberOfBytes)
{
	PLXv5xPCIState *pPCIState = 0 ;
	RETURN_CODE rc = ApiSuccess ;
	NDTS_Boolean isOK ;

	isOK = NDTS_HAL_PLX_v5x_CheckCardIndexState(internalCardIndex) ;
	if (!isOK)
		return NDTS_BFalse ;

	pPCIState = plxv5xGlobalState.ppPCIStates[internalCardIndex] ;

	if (!pPCIState->isDMAChannelOpen)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_PerformDMARead_DMAChannelNotOpen) ;
		return NDTS_BFalse ;
	}

	pPCIState->dmaParams.u.PciAddrLow = (U32) pPCIState->pciBuffer.PhysicalAddr ;
	pPCIState->dmaParams.PciAddrHigh = 0x0 ;
	pPCIState->dmaParams.LocalAddr = localAddress ;
	pPCIState->dmaParams.ByteCount = numberOfBytes ;
	pPCIState->dmaParams.LocalToPciDma = 1 ;
	pPCIState->dmaParams.TerminalCountIntr = 0 ;

	rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DmaTransferBlock)(&pPCIState->deviceObject, 0, &pPCIState->dmaParams, PLX_TIMEOUT_INFINITE) ;
	if (rc != ApiSuccess)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_PerformDMARead_DMABlockTransferFail) ;
		return NDTS_BFalse ;
	}

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_PerformDMAWrite(unsigned long internalCardIndex, unsigned long localAddress, unsigned long numberOfBytes)
{
	PLXv5xPCIState *pPCIState = 0 ;
	RETURN_CODE rc = ApiSuccess ;
	NDTS_Boolean isOK ;

	isOK = NDTS_HAL_PLX_v5x_CheckCardIndexState(internalCardIndex) ;
	if (!isOK)
		return NDTS_BFalse ;

	pPCIState = plxv5xGlobalState.ppPCIStates[internalCardIndex] ;

	if (!pPCIState->isDMAChannelOpen)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_PerformDMAWrite_DMAChannelNotOpen) ;
		return NDTS_BFalse ;
	}

	pPCIState->dmaParams.u.PciAddrLow = (U32) pPCIState->pciBuffer.PhysicalAddr ;
	pPCIState->dmaParams.PciAddrHigh = 0x0 ;
	pPCIState->dmaParams.LocalAddr = localAddress ;
	pPCIState->dmaParams.ByteCount = numberOfBytes ;
	pPCIState->dmaParams.LocalToPciDma = 0 ;
	pPCIState->dmaParams.TerminalCountIntr = 0 ;

	rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DmaTransferBlock)(&pPCIState->deviceObject, 0, &pPCIState->dmaParams, PLX_TIMEOUT_INFINITE) ;
	if (rc != ApiSuccess)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_PerformDMAWrite_DMABlockTransferFail) ;
		return NDTS_BFalse ;
	}

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_SetWord(unsigned long internalCardIndex, unsigned long address, unsigned long value)
{
	NDTS_Boolean isOK ;

	isOK = NDTS_HAL_PLX_v5x_CheckCardIndexState(internalCardIndex) ;
	if (!isOK)
		return NDTS_BFalse ;

	*(volatile unsigned long *) (plxv5xGlobalState.ppPCIStates[internalCardIndex]->pPCIBar2VirtualAddress + address) = value ;

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_GetWord(unsigned long internalCardIndex, unsigned long address, unsigned long *pValue)
{
	NDTS_Boolean isOK ;

	isOK = NDTS_HAL_PLX_v5x_CheckCardIndexState(internalCardIndex) ;
	if (!isOK)
		return NDTS_BFalse ;

	*pValue = *(volatile unsigned long *) (plxv5xGlobalState.ppPCIStates[internalCardIndex]->pPCIBar2VirtualAddress + address) ;

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_WriteSecondaryDeviceID(unsigned long busIndex, unsigned long slotIndex, NDTS_CardSecondaryIDs cardID)
{
	RETURN_CODE rc ;
	PLX_DEVICE_KEY deviceKey ;
	NDTS_Boolean isPLXDeviceFound = NDTS_BFalse,
		         isWritten = NDTS_BFalse ;
	U8 deviceIndex = 0 ;
	U8 ignoreField8 = (U8) PCI_FIELD_IGNORE ;
	U16 ignoreField16 = (U16) PCI_FIELD_IGNORE ;

	deviceKey.bus = (U8) busIndex ;
	deviceKey.slot = (U8) slotIndex ;
	deviceKey.function = ignoreField8 ;
	deviceKey.VendorId = 0x10b5 ;
	deviceKey.DeviceId = 0x5602 ;
	deviceKey.SubVendorId = ignoreField16 ;
	deviceKey.SubDeviceId = ignoreField16 ;
	deviceKey.Revision = ignoreField8 ;

	rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceFind)(&deviceKey, deviceIndex) ;
	isPLXDeviceFound = rc == ApiSuccess ;
	if (!isPLXDeviceFound)
	{
		deviceKey.bus = (U8) busIndex ;
		deviceKey.slot = (U8) slotIndex ;
		deviceKey.function = ignoreField8 ;
		deviceKey.VendorId = 0x10b5 ;
		deviceKey.DeviceId = 0x86e1 ;
		deviceKey.SubVendorId = ignoreField16 ;
		deviceKey.SubDeviceId = ignoreField16 ;
		deviceKey.Revision = ignoreField8 ;

		rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceFind)(&deviceKey, deviceIndex) ;
		isPLXDeviceFound = rc == ApiSuccess ;
	}

	if (isPLXDeviceFound)
	{
		PLX_DEVICE_OBJECT deviceObject ;

		rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceOpen)(&deviceKey, &deviceObject) ;
		if (rc == ApiSuccess)
		{
			(*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_EepromPresent)(&deviceObject, &rc);
			if (rc == ApiSuccess)
			{
				rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_EepromWriteByOffset)(&deviceObject, SECONDARY_ID_EEPROM_OFFSET, (U32) cardID);
				if (rc == ApiSuccess)
					isWritten = NDTS_BTrue ;
				else
					NDTS_HAL_HandleHALError(NDTS_HAL_WriteSecondaryDeviceID_IDWriteFail) ;
			}
			else
				NDTS_HAL_HandleHALError(NDTS_HAL_WriteSecondaryDeviceID_IDWriteFail) ;

			rc = (*plxv5xGlobalState.fpPLXFuncs.fpPlxPci_DeviceClose)(&deviceObject) ;
			if (rc != ApiSuccess)
				NDTS_HAL_HandleHALError(NDTS_HAL_WriteSecondaryDeviceID_DeviceCloseFail) ;
		}
		else
			NDTS_HAL_HandleHALError(NDTS_HAL_WriteSecondaryDeviceID_DeviceOpenFail) ;
	}
	else
		NDTS_HAL_HandleHALError(NDTS_HAL_WriteSecondaryDeviceID_NonNDTSCard) ;

	return isWritten ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_BoardByteCopy(unsigned long internalCardIndex, unsigned long localAddress, void *pOut, unsigned long numberOfBytes)
{
	PLXv5xPCIState *pPCIState = 0 ;
	NDTS_Boolean isOK ;

	isOK = NDTS_HAL_PLX_v5x_CheckCardIndexState(internalCardIndex) ;
	if (!isOK)
		return NDTS_BFalse ;

	pPCIState = plxv5xGlobalState.ppPCIStates[internalCardIndex] ;

	SDKUtilities_ByteCopy((char *) pPCIState->pPCIBar2VirtualAddress + localAddress, pOut, numberOfBytes) ;

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v5x_CheckCardIndexState(unsigned long internalCardIndex)
{
	NDTS_Boolean isOK = NDTS_BTrue ;

	if (!plxv5xGlobalState.isInitialised)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_CheckCardIndexState_HALIsNotInitialised) ;
		isOK = NDTS_BFalse ;
	}

	if (internalCardIndex >= plxv5xGlobalState.numberOfAvailableCards)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_CheckCardIndexState_InvalidCardIndex) ;
		isOK = NDTS_BFalse ;
	}

	if (plxv5xGlobalState.ppPCIStates[internalCardIndex] == 0)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_CheckCardIndexState_MissingState) ;
		isOK = NDTS_BFalse ;
	}

	return isOK ;
}