unit CoreExports;

interface

uses
	WinAPI.Windows, System.SysUtils, System.Classes,
	Core;

function BRD_GetHighestBoardIndex: TBoardIndex; cdecl;
function BRD_GetDeviceID(iBoard: TBoardIndex; pDeviceID: TPUInt32): TWinErrCode; cdecl;
function BRD_GetNameA(iBoard: TBoardIndex; BufLen: TUInt32; pNameBuf: TPAnsiChar = nil; pNameLen: TPUInt32 = nil): TWinErrCode; cdecl;
function BRD_GetNameW(iBoard: TBoardIndex; BufLen: TUInt32; pNameBuf: TPWideChar = nil; pNameLen: TPUInt32 = nil): TWinErrCode; cdecl;
// header files overload BRD_GetNameA() with BRD_GetNameW() as BRD_GetName()
function BRD_GetSerialNumber(iBoard: TBoardIndex; pSN: TPSerialNumber): TWinErrCode; cdecl;
function BRD_Reset(iBoard: TBoardIndex): TWinErrCode; cdecl;



// reserved for future implementation
//function BRD_SleepDevice(iBoard: TBoardIndex): TWinErrCode; cdecl;
//function BRD_GetID(iBoard: TBoardIndex; pID: TPUInt64): TWinErrCode; cdecl;
//function BRD_GetRev(iBoard: TBoardIndex; pRev: TPUInt32): TWinErrCode; cdecl;

procedure InitDLL;





implementation

uses
	Math,
	System.Win.Registry,
	SetupDiW,
	//LogThreads,
	PIOExports, ADCExports, ADCStreamExports;

type
	TStringArray16 = array of UTF16String;

const
	PCIFindKey = 'Software\PCIFind';

function GetDevicePaths(const ClassGUID: TGUID): TStringArray16;
var
	DevInterfaceData: SP_DEVICE_INTERFACE_DATA;
	MemberIndex: TSNative;
	hDevInfo: THandle;

	L: LongWord;
	pDetailData: PSP_DEVICE_INTERFACE_DETAIL_DATA;

	Buf: UTF16String;
	I, RL: TSNative;

begin
	RL := 0;
	SetLength(Result, RL);

	hDevInfo := SetupDiGetClassDevs(@ClassGUID, nil, 0, DIGCF_PRESENT or DIGCF_DEVICEINTERFACE);
	if hDevInfo = 0 then Exit;

	DevInterfaceData.cbSize := sizeof(DevInterfaceData);
	MemberIndex := 0;
	while SetupDiEnumDeviceInterfaces(hDevInfo, nil, ClassGUID, MemberIndex, DevInterfaceData) do begin
		L := 0;
		SetupDiGetDeviceInterfaceDetail(hDevInfo, @DevInterfaceData, nil, 0, @L, nil);

		GetMem(pDetailData, 2 * L);
		if sizeof(Pointer) = 8 then
			pDetailData.cbSize := 8 //The fixed portion is 4. Dunno where 8 comes from.
		else
			pDetailData.cbSize := 6 //The fixed portion is 4. Dunno where 6 comes from.
		;

		if not SetupDiGetDeviceInterfaceDetail(hDevInfo, @DevInterfaceData, pDetailData, L, nil, nil) then begin
			FreeMem(pDetailData);
			Inc(MemberIndex);
			Continue;
		end;

		SetString(Buf, PWideChar(@pDetailData.DevicePath[0]), L - 4);
		FreeMem(pDetailData);
		I := Pos(#0, Buf);
		if I <> 0 then SetLength(Buf, I - 1);

		SetLength(Result, RL + 1);
		Result[RL] := Buf;
		Inc(RL);
		Buf := '';

		Inc(MemberIndex);
	end;
	SetupDiDestroyDeviceInfoList(hDevInfo);
end;

procedure LoadCardList;
var
	iBoard, iSequencer, iPath, iCh: TSNative;
	DevicePath: TStringArray16;
	CardData: TCardInfo;
	BytesTransferred: TUInt32;
	//Version: TUInt32;
	{$IFDEF DEBUG}
		Buf: AnsiString;
	{$ENDIF}

begin
	DevicePath := GetDevicePaths(AIOWDMClassGUID);
	SetLength(Dev, Length(DevicePath));
	iBoard := 0;
	try
		{$IFDEF DEBUG}
			Buf := 'Device Paths: ' + AnsiString(IntToStr(Length(DevicePath)));
			for iPath := 0 to High(Dev) do
				Buf := Buf + AnsiString(#13#10 + DevicePath[iPath])
			;
			AnsiMessageBox(Buf, 'AIOAIO', MB_ICONINFORMATION);
		{$ENDIF}

		for iPath := 0 to high(Dev) do begin
			Dev[iBoard].DeviceID := 0;
			Dev[iBoard].BaseClock := 0;
			for iCh := 0 to DACCt-1 do Dev[iBoard].LastDACRanges[iCh] := rcNoChange;
			Dev[iBoard].ImmediateMode := imNative16;
			Dev[iBoard].RegisterMap := rmAIO;
			Dev[iBoard].hThreadingMutex := INVALID_HANDLE_VALUE;
			for iSequencer := 0 to High(Dev[iBoard].SequencerAcq) do with Dev[iBoard].SequencerAcq[iSequencer] do begin
				hThreadingMutex := INVALID_HANDLE_VALUE;
				AcquireThread := nil;
				bInCallback := False;
				ChunkingType := ctAuto;
				ChunkingScalar := 0;
				CallbackType := ctNone;
				pCallback := nil;
			end;
			{$IFDEF DEBUG}
				Dev[iBoard].DebugMask := 0;
				Dev[iBoard].LogFil := nil;
			{$ENDIF}

			Dev[iBoard].SymbolicLink := DevicePath[iPath];

			Dev[iBoard].hDevice := CreateFile(
				@Dev[iBoard].SymbolicLink[1],
				GENERIC_READ or GENERIC_WRITE,
				FILE_SHARE_READ or FILE_SHARE_WRITE,
				nil,
				OPEN_EXISTING,
				FILE_FLAG_OVERLAPPED,
				0
			);
			if Dev[iBoard].hDevice = INVALID_HANDLE_VALUE then begin
				{$IFDEF DEBUG}
					AnsiMessageBox('Failed to open device from path.'#13#10 + AnsiString(Dev[iBoard].SymbolicLink), 'AIOAIO', MB_ICONEXCLAMATION);
				{$ENDIF}
				Continue;
			end;

			//Fetch DeviceID.
			ZeroMemory(@CardData, SizeOf(CardData));
			BytesTransferred := 0;
			if not DeviceIOControl(Dev[iBoard].hDevice, IOCTL_QUERY_INFO, nil, 0, @CardData, SizeOf(CardData), BytesTransferred, nil) then begin
				{$IFDEF DEBUG}
					AnsiMessageBox('Error ' + AnsiString(IntToStr(Length(DevicePath))) + ' from IOCTL_QUERY_INFO.', 'AIOAIO', MB_ICONEXCLAMATION);
				{$ENDIF}
				Continue;
			end;
			if BytesTransferred < SizeOf(CardData) then begin
				{$IFDEF DEBUG}
					AnsiMessageBox('Buffer too small from IOCTL_QUERY_INFO.'#13#10'Expected: ' + AnsiString(IntToStr(SizeOf(CardData))) + #13#10'Got: ' + AnsiString(IntToStr(BytesTransferred)), 'AIOAIO', MB_ICONEXCLAMATION);
				{$ENDIF}
				Continue;
			end;

			Dev[iBoard].DeviceID := CardData.DeviceID;

			//Configure device parameters.
			with Dev[iBoard] do
				case DeviceID of
					$C0E8, $C0E9, $C0EA, $C0EB, //mPCIe-AIO16-16x
					$80E8, $80E9, $80EA, //mPCIe-AI16-16x
					$C058, $C059, $C05A, //mPCIe-AIO12-16x
					$8058, $8059, $805A: //mPCIe-AI12-16x
					begin
						BaseClock := PIO_In32(iBoard, BaseClockOffset);

						Dev[iBoard].ImmediateMode := imNative32;
						Dev[iBoard].RegisterMap := rmHMAIO;
					end;

					$C0EC, $C0ED, $C0EE, $C0EF, //mPCIe-ADIO16-16x
					$80EC, $80ED, $80EE, //mPCIe-ADI16-16x
					$C05C, $C05D, $C05E, //mPCIe-ADIO12-16x
					$805C, $805D, $805E: //mPCIe-ADI12-16x
					begin
						BaseClock := PIO_In32(iBoard, BaseClockOffset);
						Dev[iBoard].ImmediateMode := imNative32;
						Dev[iBoard].RegisterMap := rmADIO;
					end;

					$C2EC, $C2ED, $C2EE, $C2EF, //PCIe-ADIO16-16x
					$82EC, $82ED, $82EE, //PCIe-ADI16-16x
					$C25C, $C25D, $C25E, //PCIe-ADIO12-16x
					$825C, $825D, $825E: //PCIe-ADI12-16x
					begin
						BaseClock := PIO_In32(iBoard, BaseClockOffset);
						Dev[iBoard].ImmediateMode := imNative32;
						Dev[iBoard].RegisterMap := rmHMAIO;
					end;

					//$1100, //Debug with PLDA ID.
					$C2E8, $C2E9, $C2EA, //mPCIe-AIO16-16x prototype
					$82E8, $82E9, $82EA, //mPCIe-AI16-16x prototype
					$C258, $C259, $C25A, //mPCIe-AIO12-16x prototype
					$8258, $8259, $825A: //mPCIe-AI12-16x prototype
					begin
						BaseClock := PIO_In32(iBoard, BaseClockOffset);
						for iSequencer := 0 to High(Dev[iBoard].SequencerAcq) do
							PIO_Out16(iBoard, ADCStartStopOffset + dSequencer*iSequencer, (BaseADCCmd or ADCCmd_Continuous) and not ADCCmd_Go)
						;
					end;
					{
					$0C60: begin //`@ Debug with a PCI-DIO-48.
						BaseClock := 10000000;
					end;
					$0100: begin //`@ Debug with a base ID.
						BaseClock := PIO_In32(iBoard, BaseClockOffset);
					end;
					}
					$0C40: begin
						BaseClock := 10000000;
					end;
					$0E63: begin
						BaseClock := 10000000;
					end;
					else begin
						{$IFDEF DEBUG}
							AnsiMessageBox('Unrecognized device ID ' + AnsiString(IntToHex(Dev[iBoard].DeviceID, 1)) + '.', 'AIOAIO', MB_ICONEXCLAMATION);
						{$ENDIF}
						Continue;
					end;
				end
			;

			Dev[iBoard].hThreadingMutex := CreateMutex(nil, False, nil);
			for iSequencer := 0 to High(Dev[iBoard].SequencerAcq) do
				Dev[iBoard].SequencerAcq[iSequencer].hThreadingMutex := CreateMutex(nil, False, nil)
			;

			//Count this in Dev.
			Inc(iBoard);
		end;
	finally
		SetLength(Dev, iBoard);
	end;
end;

function GetOS: TOS;
var
	V: OSVERSIONINFO;
	VerNum: LongWord;
	VerNums: packed record Min, Maj: Word; end absolute VerNum;

begin
	Result := osDunno;

	V.dwOSVersionInfoSize := sizeof(OSVERSIONINFO);
	GetVersionEx(V);
	VerNums.Min := V.dwMinorVersion;
	VerNums.Maj := V.dwMajorVersion;
	case V.dwPlatformId of
		VER_PLATFORM_WIN32s: Result := osWin95; // Win32s doesn't have WDM, does it?
		VER_PLATFORM_WIN32_WINDOWS: if VerNum < $4000A then
			Result := osWin95
		else
			Result := osWin98
		;
		VER_PLATFORM_WIN32_NT: if VerNum < $50000 then
			Result := osNT
		else if VerNum < $60000 then
			Result := osNT5
		else
			Result := osNTVistaCore
		;
	end;
end;

procedure DLLHandler(Reason: Integer);
var
	iBoard, iSequencer: TSNative;

begin
	if (Reason = DLL_PROCESS_DETACH) then begin
		for iBoard := 0 to High(Dev) do begin
			ADC_AcquireEnd(iBoard, 0);
			CloseAndInvalidateHandle(Dev[iBoard].hDevice);
			CloseAndInvalidateHandle(Dev[iBoard].hThreadingMutex);
			for iSequencer := 0 to High(Dev[iBoard].SequencerAcq) do
				CloseAndInvalidateHandle(Dev[iBoard].SequencerAcq[iSequencer].hThreadingMutex)
			;
		end;
		//FreeAndNil(LogThread);
	end;
end;

procedure InitDLL;
begin
	if OS <> osDunno then Exit;

	IsMultiThread := True;
	DLLProc := @DLLHandler;
	OS := GetOS;
	//LogThread := TLogThread.Create;
	LoadCardList;
end;



function BRD_GetHighestBoardIndex: TBoardIndex;
begin
	try
		Result := High(Dev);
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function BRD_GetDeviceID(iBoard: TBoardIndex; pDeviceID: TPUInt32): TWinErrCode;
var
	DeviceID: TUInt32;

begin
	try
		Result := ValidateBoardIndex(iBoard);
		if Result <> ERROR_SUCCESS then Exit;

		DeviceID := Dev[iBoard].DeviceID;

		if pDeviceID = nil then
			Result := ERROR_INVALID_ADDRESS
		else try
			pDeviceID^ := DeviceID;
		except
			Result := ERROR_INVALID_ADDRESS;
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function BRD_GetNameA(iBoard: TBoardIndex; BufLen: TUInt32; pNameBuf: TPAnsiChar = nil; pNameLen: TPUInt32 = nil): TWinErrCode;
var
	NameBuf: AnsiString;
	NameBufW: WideString;

begin
	try
		SetLength(NameBufW, BufLen);
		Result := BRD_GetNameW(iBoard, BufLen, @NameBufW[1], pNameLen);
		if Result <> ERROR_SUCCESS then Exit;

		NameBuf := AnsiString(NameBufW);
		Move(NameBuf[1], pNameBuf^, Min(BufLen, pNameLen^));
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function BRD_GetNameW(iBoard: TBoardIndex; BufLen: TUInt32; pNameBuf: TPWideChar = nil; pNameLen: TPUInt32 = nil): TWinErrCode;
var
	Reg: TRegistry;
	CompanyStr: String;
	NameBuf: WideString;

begin
	try
		Result := ValidateBoardIndex(iBoard);
		if Result <> ERROR_SUCCESS then Exit;

		try
			if pNameLen <> nil then pNameLen^ := 0;
			if (BufLen > 0) and (pNameBuf <> nil) then ZeroMemory(pNameBuf, SizeOf(pNameBuf^)*BufLen);
		except
			Result := ERROR_INVALID_ADDRESS;
			Exit;
		end;

		Reg := TRegistry.Create(KEY_QUERY_VALUE or KEY_WOW64_64KEY);
		Reg.RootKey := HKEY_LOCAL_MACHINE;
		try
			if not Reg.OpenKey(PCIFindKey, False) then begin
				Result := ERROR_BAD_NET_NAME;
				Exit;
			end;
			CompanyStr := Reg.ReadString('Company');
			Reg.CloseKey;

			if not Reg.OpenKey('Software\' + CompanyStr + '\Cardlist', False) then begin
				Result := ERROR_BAD_NET_NAME;
				Exit;
			end;
			NameBuf := Reg.ReadString(IntToHex(Dev[iBoard].DeviceID, 4));
			Reg.CloseKey;

			if pNameLen <> nil then pNameLen^ := Length(NameBuf);
			BufLen := Min(BufLen, Length(NameBuf));
			if (BufLen > 0) and (pNameBuf <> nil) then Move(NameBuf[1], pNameBuf^, SizeOf(pNameBuf^)*BufLen);

		finally
			Reg.Free;
		end;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function BRD_GetSerialNumber(iBoard: TBoardIndex; pSN: TPSerialNumber): TWinErrCode;
begin
	try
		Result := ValidateBoardIndex(iBoard);
		if Result <> ERROR_SUCCESS then Exit;

		try
			if pSN <> nil then pSN^ := 0;
		except
			Result := ERROR_INVALID_ADDRESS;
			Exit;
		end;

		pSN^ := 0; //`@
		Result := ERROR_NOT_SUPPORTED;
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

function BRD_Reset(iBoard: TBoardIndex): TWinErrCode;
begin
	try
		Result := ValidateBoardIndex(iBoard);
		if Result <> ERROR_SUCCESS then Exit;

		case Dev[iBoard].RegisterMap of
			rmAIO: Result := PIO_Out8(iBoard, ResetOffset, $FF);
			rmADIO, rmHMAIO: Result := PIO_Out32(iBoard, ResetOffset, $1);
			else begin
				Result := ERROR_BAD_TOKEN_TYPE;
				Exit;
			end;
		end;

		{$IFDEF DEBUG}
			Dev[iBoard].DebugMask := 1 or (1 shl 5);
		{$ENDIF}
	except
		Result := ERROR_INTERNAL_ERROR;
	end;
end;

end.
