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

#include "NDTSEnums.h"

#include "NDTSCommonHAL_PLX_V40.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 
{
	HANDLE hDevice ;
	DEVICE_LOCATION device ;
	DMA_CHANNEL_DESC dmaDesc ;
	DMA_TRANSFER_ELEMENT dmaData ;
	PCI_MEMORY pciBuffer ; 
	U32 pCommonBuffer ;
	U32 pPCIBar2VirtualAddress ;
	NDTS_Boolean isDMAChannelOpen ;
} PLXv40PCIState ;

// typedefs for PLX functions
typedef RETURN_CODE (* FPPlxSdkVersion)(U8 *VersionMajor, U8 *VersionMinor, U8 *VersionRevision) ;
typedef RETURN_CODE (* FPPlxPciDeviceFind)(DEVICE_LOCATION *device, U32 *requestLimit) ;
typedef RETURN_CODE (* FPPlxPciDeviceOpen)(DEVICE_LOCATION *pDevice, HANDLE *pDrvHandle) ;
typedef RETURN_CODE (* FPPlxPciDeviceClose)(HANDLE hDevice) ;
typedef BOOLEAN (* FPPlxSerialEepromPresent)(HANDLE hDevice, RETURN_CODE *pReturnCode) ;
typedef RETURN_CODE (* FPPlxSerialEepromReadByOffset)(HANDLE hDevice, U16 offset, U32 *pValue) ;
typedef RETURN_CODE (* FPPlxPciBarMap)(HANDLE hDevice, U8 BarIndex, VOID *pVa) ;
typedef RETURN_CODE (* FPPlxPciBarUnmap)(HANDLE hDevice, VOID *pVa) ;
typedef RETURN_CODE (* FPPlxPciCommonBufferProperties)(HANDLE hDevice, PCI_MEMORY *pMemoryInfo) ;
typedef RETURN_CODE (* FPPlxPciCommonBufferMap)(HANDLE hDevice, VOID *pVa) ;
typedef RETURN_CODE (* FPPlxDmaBlockChannelOpen)(HANDLE hDevice, DMA_CHANNEL channel, DMA_CHANNEL_DESC *pDmaChannelDesc) ;
typedef RETURN_CODE (* FPPlxDmaBlockChannelClose)(HANDLE hDevice, DMA_CHANNEL dmaChannel) ;
typedef RETURN_CODE (* FPPlxDmaBlockTransfer)(HANDLE hDevice, DMA_CHANNEL dmaChannel, DMA_TRANSFER_ELEMENT *dmaData, BOOLEAN returnImmediate) ;

// struct for PLX function pointers
typedef struct 
{
	FPPlxPciDeviceFind fpPlxPciDeviceFind ;
	FPPlxPciDeviceOpen fpPlxPciDeviceOpen ;
	FPPlxPciDeviceClose fpPlxPciDeviceClose ;
	FPPlxSerialEepromPresent fpPlxSerialEepromPresent ;
	FPPlxSerialEepromReadByOffset fpPlxSerialEepromReadByOffset ;
	FPPlxPciBarMap fpPlxPciBarMap ;
	FPPlxPciBarUnmap fpPlxPciBarUnmap ;
	FPPlxPciCommonBufferProperties fpPlxPciCommonBufferProperties ;
	FPPlxPciCommonBufferMap fpPlxPciCommonBufferMap ;
	FPPlxDmaBlockChannelOpen fpPlxDmaBlockChannelOpen ;
	FPPlxDmaBlockChannelClose fpPlxDmaBlockChannelClose ;
	FPPlxDmaBlockTransfer fpPlxDmaBlockTransfer ;
} PLXv40APIFPs ;

// This HAL implementation global state
typedef struct
{
	NDTS_Boolean isAvailable, isInitialised ;
	unsigned long numberOfActiveCards, numberOfAvailableCards ;
	HINSTANCE hDLL ;
	PLXv40APIFPs fpPLXFuncs ;
	PLXv40PCIState *ppPCIStates[NUMBER_OF_BUFFERED_CARDS] ;
} PLXv40GlobalState ;

PLXv40GlobalState plxv40GlobalState = {NDTS_BFalse, NDTS_BFalse, 0, 0, 0} ;


NDTS_Boolean NDTS_HAL_PLX_v40_GetHALID(NDTS_HAL_ID *pHALID)
{
	NDTS_HAL_ID plx40HALID ;
	NDTS_Boolean isAvailable = NDTS_BFalse ;

	strcpy(plx40HALID.pName, "plxapi v4.0") ;
	plx40HALID.majorVersionNumber = MAJOR_VERSION_NUMBER ;
	plx40HALID.minorVersionNumber = MINOR_VERSION_NUMBER ;

	// Get plxapi version number
	{
		HINSTANCE hDLL;
		FPPlxSdkVersion fpPlxSdkVersion ;

		hDLL = LoadLibrary("plxapi") ;
		if (hDLL != NULL)
		{
			fpPlxSdkVersion = (FPPlxSdkVersion) GetProcAddress(hDLL, "PlxSdkVersion") ;
			if (fpPlxSdkVersion != 0)
			{
				U8 majorIndex, minorIndex, revisionIndex ;
				RETURN_CODE rc ;
				rc = (*fpPlxSdkVersion)(&majorIndex, &minorIndex, &revisionIndex) ;
				isAvailable = rc == ApiSuccess && majorIndex == 4 && minorIndex == 0 ? NDTS_BTrue : NDTS_BFalse ;
			}

			FreeLibrary(hDLL) ;
		}

		plx40HALID.isAvailable = isAvailable ;

		plxv40GlobalState.isAvailable = isAvailable ;
	}

	// Set initialise function pointer
	plx40HALID.fpNDTS_HAL_InitialiseHAL = &NDTS_HAL_PLX_v40_InitialiseHAL ;

	// Return our HAL ID
	*pHALID = plx40HALID ;

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v40_GetBoardsIDs(unsigned short vendorID, unsigned short deviceID, unsigned long secondaryDeviceID, NDTS_HAL_BoardID **ppBoardIDs, unsigned long numberOfBufferedBoardIDs, unsigned long *pNumberOfBoards)
{
	unsigned long devNum, i ;
	DEVICE_LOCATION device;
	HANDLE hDevice;
	U32 d;
	RETURN_CODE rc;
	unsigned long numberOfBoards = 0 ;
	NDTS_Boolean isOK = NDTS_BTrue ;

	devNum = FIND_AMOUNT_MATCHED;
	
	device.BusNumber = (U8) -1 ; 
	device.SlotNumber = (U8) -1 ; 
	device.VendorId = vendorID ;
	device.DeviceId = deviceID ; 
	device.SerialNumber[0] = 0 ; 
	
	rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceFind)(&device, &devNum) ;
	if (rc != ApiSuccess)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_GetBoardIDs_FindDeviceFail) ;
		return NDTS_BFalse; // there's a problem so report no devices found
	}

	// Now attempt to open each device and see which are RapiDAQ cards
	d = 0 ;
	while (d < devNum && d < numberOfBufferedBoardIDs && isOK)
	{
		device.BusNumber = (U8) -1 ; 
		device.SlotNumber = (U8) -1 ; 
		device.VendorId = vendorID ;
		device.DeviceId = deviceID ; 
		device.SerialNumber[0] = 0 ; 
		rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceFind)(&device, &d) ;
		if (rc != ApiSuccess)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_GetBoardIDs_FindDeviceFail) ;
			continue;
		}

		// Open the device and see what it is
		rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceOpen)(&device, &hDevice) ;
		if (rc != ApiSuccess)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_GetBoardIDs_OpenDeviceFail) ;
			continue;
		}

		(*plxv40GlobalState.fpPLXFuncs.fpPlxSerialEepromPresent)(hDevice, &rc);
		if (rc == ApiSuccess)
		{
			U32 secondaryId;

			rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxSerialEepromReadByOffset)(hDevice, SECONDARY_ID_EEPROM_OFFSET, &secondaryId);

			if (rc == ApiSuccess && secondaryId == secondaryDeviceID)
			{
				if (plxv40GlobalState.numberOfAvailableCards < NUMBER_OF_BUFFERED_CARDS)
				{
					ppBoardIDs[numberOfBoards] = (NDTS_HAL_BoardID *) malloc(sizeof(NDTS_HAL_BoardID)) ;

					ppBoardIDs[numberOfBoards]->busIndex = device.BusNumber ;
					ppBoardIDs[numberOfBoards]->slotIndex = device.SlotNumber ;
					ppBoardIDs[numberOfBoards]->vendorID = device.VendorId ;
					ppBoardIDs[numberOfBoards]->deviceID = device.DeviceId ;
					ppBoardIDs[numberOfBoards]->secondaryDeviceID = secondaryDeviceID ;
					ppBoardIDs[numberOfBoards]->cardIndex = numberOfBoards ;
					ppBoardIDs[numberOfBoards]->internalCardIndex = plxv40GlobalState.numberOfAvailableCards ;
					ppBoardIDs[numberOfBoards]->isActive = NDTS_BFalse ;

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

		rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceClose)(hDevice) ;
		if (rc != ApiSuccess)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_GetBoardIDs_CloseDeviceFail) ;
			continue;
		}

		d++ ;
	}

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

	*pNumberOfBoards = numberOfBoards ;

	if (devNum > numberOfBufferedBoardIDs)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_GetBoardIDs_InsufficientInputBoardIDBufferSize) ;
		return NDTS_BFalse ;
	}

	return NDTS_BTrue ;
}

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

	// Load DLL if not already loaded
	if (plxv40GlobalState.hDLL == 0)
	{
		plxv40GlobalState.hDLL = LoadLibrary("plxapi") ;

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

	// Set our exposed function pointers
	{
		pHALFuncs->fpNDTS_HAL_GetBoardsIDs = &NDTS_HAL_PLX_v40_GetBoardsIDs ;
		pHALFuncs->fpNDTS_HAL_AddBoard = &NDTS_HAL_PLX_v40_AddBoard ;
		pHALFuncs->fpNDTS_HAL_RemoveBoard = &NDTS_HAL_PLX_v40_RemoveBoard ;
		pHALFuncs->fpNDTS_HAL_OpenDMAChannel = &NDTS_HAL_PLX_v40_OpenDMAChannel ;
		pHALFuncs->fpNDTS_HAL_CloseDMAChannel = &NDTS_HAL_PLX_v40_CloseDMAChannel ;
		pHALFuncs->fpNDTS_HAL_GetCommonBufferInfo = &NDTS_HAL_PLX_v40_GetCommonBufferInfo ;
		pHALFuncs->fpNDTS_HAL_PerformDMARead = &NDTS_HAL_PLX_v40_PerformDMARead ;
		pHALFuncs->fpNDTS_HAL_PerformDMAWrite = &NDTS_HAL_PLX_v40_PerformDMAWrite ;
		pHALFuncs->fpNDTS_HAL_SetWord = &NDTS_HAL_PLX_v40_SetWord ;
		pHALFuncs->fpNDTS_HAL_GetWord = &NDTS_HAL_PLX_v40_GetWord ;
		pHALFuncs->fpNDTS_HAL_BoardByteCopy = &NDTS_HAL_PLX_v40_BoardByteCopy ;
	}
 
	// Set our internal function pointers
	{
		plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceFind = (FPPlxPciDeviceFind) GetProcAddress(plxv40GlobalState.hDLL, "PlxPciDeviceFind") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceFind == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceOpen = (FPPlxPciDeviceOpen) GetProcAddress(plxv40GlobalState.hDLL, "PlxPciDeviceOpen") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceOpen == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceClose = (FPPlxPciDeviceClose) GetProcAddress(plxv40GlobalState.hDLL, "PlxPciDeviceClose") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceClose == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv40GlobalState.fpPLXFuncs.fpPlxSerialEepromPresent = (FPPlxSerialEepromPresent) GetProcAddress(plxv40GlobalState.hDLL, "PlxSerialEepromPresent") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxSerialEepromPresent == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv40GlobalState.fpPLXFuncs.fpPlxSerialEepromReadByOffset = (FPPlxSerialEepromReadByOffset) GetProcAddress(plxv40GlobalState.hDLL, "PlxSerialEepromReadByOffset") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxSerialEepromReadByOffset == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv40GlobalState.fpPLXFuncs.fpPlxPciBarMap = (FPPlxPciBarMap) GetProcAddress(plxv40GlobalState.hDLL, "PlxPciBarMap") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxPciBarMap == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv40GlobalState.fpPLXFuncs.fpPlxPciBarUnmap = (FPPlxPciBarUnmap) GetProcAddress(plxv40GlobalState.hDLL, "PlxPciBarUnmap") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxPciBarUnmap == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv40GlobalState.fpPLXFuncs.fpPlxPciCommonBufferProperties = (FPPlxPciCommonBufferProperties) GetProcAddress(plxv40GlobalState.hDLL, "PlxPciCommonBufferProperties") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxPciCommonBufferProperties == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv40GlobalState.fpPLXFuncs.fpPlxPciCommonBufferMap = (FPPlxPciCommonBufferMap) GetProcAddress(plxv40GlobalState.hDLL, "PlxPciCommonBufferMap") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxPciCommonBufferMap == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv40GlobalState.fpPLXFuncs.fpPlxDmaBlockChannelOpen = (FPPlxDmaBlockChannelOpen) GetProcAddress(plxv40GlobalState.hDLL, "PlxDmaBlockChannelOpen") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxDmaBlockChannelOpen == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv40GlobalState.fpPLXFuncs.fpPlxDmaBlockChannelClose = (FPPlxDmaBlockChannelClose) GetProcAddress(plxv40GlobalState.hDLL, "PlxDmaBlockChannelClose") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxDmaBlockChannelClose == 0)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_InitialiseHAL_CannotFindDLLFunctionPointer) ;
			return NDTS_BFalse ;
		}
		plxv40GlobalState.fpPLXFuncs.fpPlxDmaBlockTransfer = (FPPlxDmaBlockTransfer) GetProcAddress(plxv40GlobalState.hDLL, "PlxDmaBlockTransfer") ;
		if (plxv40GlobalState.fpPLXFuncs.fpPlxDmaBlockTransfer == 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 (!plxv40GlobalState.isInitialised)
	{
		unsigned long i ;

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

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

	return NDTS_BTrue ;
}

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

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

	// Locate appropriate card
	{
		RETURN_CODE rc ;
		PLXv40PCIState *pPCIState = plxv40GlobalState.ppPCIStates[pBoardID->internalCardIndex] ;
		unsigned long deviceIndex = 0,
			          maxDeviceIndex = 8,
					  potentialDeviceIndex = 0 ;
		HANDLE hDevice ;
		NDTS_Boolean isCardTypeFound = NDTS_BFalse, isCardFound = NDTS_BFalse ;

		do
		{
			pPCIState->device.BusNumber = (U8) -1 ; 
			pPCIState->device.SlotNumber = (U8) -1 ; 
			pPCIState->device.VendorId = pBoardID->vendorID ;
			pPCIState->device.DeviceId = pBoardID->deviceID ; 
			pPCIState->device.SerialNumber[0] = 0 ; 

			rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceFind)(&pPCIState->device, &deviceIndex) ;
			if (rc != ApiSuccess)
			{
				NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_FindDeviceFail) ;
				return NDTS_BFalse ;
			}

			rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceOpen)(&pPCIState->device, &hDevice) ;
			if (rc != ApiSuccess)
			{
				NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_OpenDeviceFail) ;
				return NDTS_BFalse ;
			}

			(*plxv40GlobalState.fpPLXFuncs.fpPlxSerialEepromPresent)(hDevice, &rc);
			if (rc == ApiSuccess)
			{
				U32 secondaryId ;

				rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxSerialEepromReadByOffset)(hDevice, SECONDARY_ID_EEPROM_OFFSET, &secondaryId) ;
				if (rc != ApiSuccess)
				{
					NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_SerialEepromPresentFail) ;
					return NDTS_BFalse ;
				}

				isCardTypeFound = rc == ApiSuccess && 
					              secondaryId == pBoardID->secondaryDeviceID ?
									NDTS_BTrue : NDTS_BFalse ;

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

			if (!isCardFound)
			{
				rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceClose)(hDevice) ;
				if (rc != ApiSuccess)
				{
					NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_CloseDeviceFail) ;
					return NDTS_BFalse ;
				}
			}

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

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

		rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciBarMap)(pPCIState->hDevice, 2, &pPCIState->pPCIBar2VirtualAddress) ;
		if (rc != ApiSuccess)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_BarMapFail) ;
			return NDTS_BFalse ;
		}

		rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciCommonBufferProperties)(pPCIState->hDevice, &pPCIState->pciBuffer) ;
		if (rc != ApiSuccess)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_GetCommonBufferPropertiesFail) ;
			return NDTS_BFalse ;
		}

		rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciCommonBufferMap)(pPCIState->hDevice, &pPCIState->pCommonBuffer) ;
		if (rc != ApiSuccess)
		{
			NDTS_HAL_HandleHALError(NDTS_HAL_AddBoard_CommonBufferMapFail) ;
			return NDTS_BFalse ;
		}
	}

	plxv40GlobalState.numberOfActiveCards++ ;

	return NDTS_BTrue ;
}

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

	pPCIState = plxv40GlobalState.ppPCIStates[internalCardIndex] ;

	rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciBarUnmap)(pPCIState->hDevice, &pPCIState->pPCIBar2VirtualAddress) ;
	if (rc != ApiSuccess)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_RemoveBoard_BarUnmapFail) ;
		isOK = NDTS_BFalse ;
	}

	rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxPciDeviceClose)(pPCIState->hDevice) ;
	if (rc  != ApiSuccess)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_RemoveBoard_CloseDeviceFail) ;
		isOK = NDTS_BFalse ;
	}

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

	return NDTS_BTrue ;
}

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

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

	pPCIState = plxv40GlobalState.ppPCIStates[internalCardIndex] ;

	pPCIState->dmaDesc.EnableReadyInput = 1 ;
	pPCIState->dmaDesc.EnableBTERMInput = 1 ;
	pPCIState->dmaDesc.EnableIopBurst = 1 ;
	pPCIState->dmaDesc.EnableWriteInvalidMode = 0 ;
	pPCIState->dmaDesc.EnableDmaEOTPin = 0 ;
	pPCIState->dmaDesc.DmaStopTransferMode = AssertBLAST;
	pPCIState->dmaDesc.HoldIopAddrConst = 0 ;
	pPCIState->dmaDesc.HoldIopSourceAddrConst = 0 ;
	pPCIState->dmaDesc.HoldIopDestAddrConst = 0 ;
	pPCIState->dmaDesc.DemandMode = 0 ;
	pPCIState->dmaDesc.EnableTransferCountClear = 0 ;
	pPCIState->dmaDesc.WaitStates = 0 ;
	pPCIState->dmaDesc.IopBusWidth = 2 ;   // 32-bit
	pPCIState->dmaDesc.EOTEndLink = 0 ;
	pPCIState->dmaDesc.ValidStopControl = 0 ;
	pPCIState->dmaDesc.ValidModeEnable = 0 ;
	pPCIState->dmaDesc.EnableDualAddressCycles = 0 ;
	pPCIState->dmaDesc.Reserved1 = 0 ;
	pPCIState->dmaDesc.TholdForIopWrites = 0 ;
	pPCIState->dmaDesc.TholdForIopReads = 0 ;
	pPCIState->dmaDesc.TholdForPciWrites = 0 ;
	pPCIState->dmaDesc.TholdForPciReads = 0 ;
	pPCIState->dmaDesc.EnableFlybyMode = 0 ;
	pPCIState->dmaDesc.FlybyDirection = 0 ;
	pPCIState->dmaDesc.EnableDoneInt = 1 ;
	pPCIState->dmaDesc.Reserved2 = 0 ;
	pPCIState->dmaDesc.DmaChannelPriority = Channel0Highest;

	rc =  (*plxv40GlobalState.fpPLXFuncs.fpPlxDmaBlockChannelOpen)(pPCIState->hDevice, PrimaryPciChannel0, &pPCIState->dmaDesc) ;
	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_v40_CloseDMAChannel(unsigned long internalCardIndex)
{
	PLXv40PCIState *pPCIState = 0 ;
	RETURN_CODE rc = ApiSuccess ;
	NDTS_Boolean isOK ;

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

	pPCIState = plxv40GlobalState.ppPCIStates[internalCardIndex] ;

	rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxDmaBlockChannelClose)(pPCIState->hDevice, PrimaryPciChannel0) ;
	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_v40_GetCommonBufferInfo(unsigned long internalCardIndex, void **ppPhysicalAddress, unsigned long *pNumberOfBufferBytes)
{
	PLXv40PCIState *pPCIState = 0 ;
	NDTS_Boolean isOK ;

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

	pPCIState = plxv40GlobalState.ppPCIStates[internalCardIndex] ;

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

	return NDTS_BTrue ;
}

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

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

	pPCIState = plxv40GlobalState.ppPCIStates[internalCardIndex] ;

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

	pPCIState->dmaData.u.PciAddrLow = pPCIState->pciBuffer.PhysicalAddr ;
	pPCIState->dmaData.PciAddrHigh = 0x0 ;
	pPCIState->dmaData.LocalAddr = localAddress ;
	pPCIState->dmaData.TransferCount = numberOfBytes ;
	pPCIState->dmaData.LocalToPciDma = 1 ;
	pPCIState->dmaData.TerminalCountIntr = 0 ;

	rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxDmaBlockTransfer)(pPCIState->hDevice, PrimaryPciChannel0, &pPCIState->dmaData, FALSE) ;
	if (rc != ApiSuccess)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_PerformDMARead_DMABlockTransferFail) ;
		return NDTS_BFalse ;
	}

	return NDTS_BTrue ;
}

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

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

	pPCIState = plxv40GlobalState.ppPCIStates[internalCardIndex] ;

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

	pPCIState->dmaData.u.PciAddrLow = pPCIState->pciBuffer.PhysicalAddr ;
	pPCIState->dmaData.PciAddrHigh = 0x0 ;
	pPCIState->dmaData.LocalAddr = localAddress ;
	pPCIState->dmaData.TransferCount = numberOfBytes ;
	pPCIState->dmaData.LocalToPciDma = 0 ;
	pPCIState->dmaData.TerminalCountIntr = 0 ;

	rc = (*plxv40GlobalState.fpPLXFuncs.fpPlxDmaBlockTransfer)(pPCIState->hDevice, PrimaryPciChannel0, &pPCIState->dmaData, FALSE) ;
	if (rc != ApiSuccess)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_PerformDMAWrite_DMABlockTransferFail) ;
		return NDTS_BFalse ;
	}

	return NDTS_BTrue ;
}

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

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

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

	return NDTS_BTrue ;
}

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

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

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

	return NDTS_BTrue ;
}

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

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

	pPCIState = plxv40GlobalState.ppPCIStates[internalCardIndex] ;

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

	return NDTS_BTrue ;
}

NDTS_Boolean NDTS_HAL_PLX_v40_CheckCardIndexState(unsigned long internalCardIndex)
{
	if (!plxv40GlobalState.isInitialised)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_CheckCardIndexState_HALIsNotInitialised) ;
		return NDTS_BFalse ;
	}

	if (internalCardIndex >= plxv40GlobalState.numberOfAvailableCards)
	{
		NDTS_HAL_HandleHALError(NDTS_HAL_CheckCardIndexState_InvalidCardIndex) ;
		return NDTS_BFalse ;
	}

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

	return NDTS_BTrue ;
}