/*
* Copyright (c) 1997-2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/

#include <e32std.h>
#include "CdcDataInterface.h"
#include "ActiveReader.h"
#include "ActiveWriter.h"
#include "AcmPanic.h"
#include "AcmUtils.h"
#include "ActiveReadOneOrMoreReader.h"
#include "ActiveDataAvailableNotifier.h"
#include "inifile.h"
#include <usb/usblogger.h>

#ifdef __FLOG_ACTIVE
_LIT8(KLogComponent, "ECACM");
#endif

#ifdef __HEADLESS_ACM_TEST_CODE__
#pragma message ("Building headless ACM (performance test code for RDevUsbcClient)")
#endif // __HEADLESS_ACM_TEST_CODE__

CCdcDataInterface::CCdcDataInterface(const TDesC16& aIfcName)
/**
 * Overloaded Constructor using interface name.
 * @param aIfcName contains the interface name
 */
 :	CCdcInterfaceBase(aIfcName),
 	iPacketSize(KDefaultMaxPacketTypeBulk)
	{
	}

CCdcDataInterface* CCdcDataInterface::NewL(const TDesC16& aIfcName)
/**
 * Create a new CCdcDataInterface object and construct it using interface name
 * This call will return an object with a valid USB configuration
 *
 * @param aParent Observer.
 * @param aIfcName Contains the interface name
 * @return A pointer to the new object
 */
	{
	LOG_STATIC_FUNC_ENTRY

	LOGTEXT2(_L("\tData Ifc Name = %S"), &aIfcName);

	CCdcDataInterface* self = new (ELeave) CCdcDataInterface(aIfcName);
	CleanupStack::PushL(self);
	self->ConstructL();
	CLEANUPSTACK_POP(self);
	return self;
	}

void CCdcDataInterface::ConstructL()
/**
 * Construct the object
 * This call registers the object with the USB device driver
 *
 * @param aParent Observer.
 */
	{
	BaseConstructL();

	iReadOneOrMoreReader  = CActiveReadOneOrMoreReader::NewL(*this, iLdd, EEndpoint2);
	iReader = CActiveReader::NewL(*this, iLdd, EEndpoint2);
	iDataAvailableNotifier = CActiveDataAvailableNotifier::NewL(*this, iLdd, EEndpoint2);
	iWriter = CActiveWriter::NewL(*this, iLdd, EEndpoint1);
	iLinkState = CLinkStateNotifier::NewL(*this, iLdd);
	
	iLinkState->Start();

	LOGTEXT(_L8("\tchecking ecacm.ini"));
	iHostCanHandleZLPs = EFalse;
	GetHostZLPHandlingFromFile();
	LOGTEXT(_L8("\tfinished checking ecacm.ini"));
	}

TInt CCdcDataInterface::SetUpInterface()
/**
 * Retrieves the device capabilities and searches for suitable input and 
 * output bulk endpoints. If suitable endpoints are found, an interface 
 * descriptor for the endpoints is registered with the LDD.
 */
	{
	LOGTEXT(_L8(">>CCdcDataInterface::SetUpInterface"));

	TUsbDeviceCaps dCaps;
	TInt ret = iLdd.DeviceCaps(dCaps);
	LOGTEXT(_L8("\tchecking result of DeviceCaps"));
	if ( ret )
		{
		LOGTEXT2(_L8("<<CCdcDataInterface::SetUpInterface ret=%d"), ret);
		return ret;
		}

	TInt maxBulkPacketSize = (dCaps().iHighSpeed) ? KMaxPacketTypeBulkHS : KMaxPacketTypeBulkFS;
	const TUint KRequiredNumberOfEndpoints = 2;

	const TUint totalEndpoints = static_cast<TUint>(dCaps().iTotalEndpoints);
	LOGTEXT2(_L8("\tiTotalEndpoints = %d"), totalEndpoints);
	if ( totalEndpoints < KRequiredNumberOfEndpoints )
		{
		LOGTEXT2(_L8("<<CCdcDataInterface::SetUpInterface ret=%d"), 
			KErrGeneral);
		return KErrGeneral;
		}
	
	// Endpoints
	TUsbcEndpointData data[KUsbcMaxEndpoints];
	TPtr8 dataptr(reinterpret_cast<TUint8*>(data), sizeof(data), sizeof(data));
	ret = iLdd.EndpointCaps(dataptr);
	LOGTEXT(_L8("\tchecking result of EndpointCaps"));
	if ( ret )
		{
		LOGTEXT2(_L8("<<CCdcDataInterface::SetUpInterface ret=%d"), ret);
		return ret;
		}

	// 
	TUsbcInterfaceInfoBuf ifc;
	TBool foundIn = EFalse;
	TBool foundOut = EFalse;

	for ( TUint i = 0; !(foundIn && foundOut) && i < totalEndpoints; i++ )
		{
		const TUsbcEndpointCaps* caps = &data[i].iCaps;
		__ASSERT_DEBUG(caps, 
			_USB_PANIC(KAcmPanicCat, EPanicInternalError));
		if (data[i].iInUse)
			continue;

		//get the max packet size it can potentially support
		//it's possible that it can support Isoch (1023) which is greater
		//than max for Bulk at 64
		const TInt maxSize = Min (	maxBulkPacketSize, 
									caps->MaxPacketSize() );

		const TUint KBulkInFlags = KUsbEpTypeBulk | KUsbEpDirIn;
		const TUint KBulkOutFlags = KUsbEpTypeBulk | KUsbEpDirOut;

		// use EEndPoint1 for TX (IN) EEndpoint1
		if (! foundIn && (caps->iTypesAndDir & KBulkInFlags) == KBulkInFlags)
			{

			ifc().iEndpointData[0].iType  = KUsbEpTypeBulk;
			ifc().iEndpointData[0].iDir   = KUsbEpDirIn; 

			//get the max packet size it can potentially support
			//it's possible that it can support Isoch (1023) which is greater
			//than max for Bulk at 64
			ifc().iEndpointData[0].iSize  = maxSize;
			foundIn = ETrue;
			}
		// use EEndPoint2 for RX (OUT) endpoint
		else if (	!foundOut 
			&&		(caps->iTypesAndDir & KBulkOutFlags) == KBulkOutFlags
			)
			{
			// EEndpoint2 is going to be our RX (OUT, read) endpoint
			ifc().iEndpointData[1].iType  = KUsbEpTypeBulk;
			ifc().iEndpointData[1].iDir   = KUsbEpDirOut;

			//get the max packet size it can potentially support
			//it's possible that it can support Isoch (1023) which is greater
			//than max for Bulk at 64
			ifc().iEndpointData[1].iSize  = maxSize;
			foundOut = ETrue;
			}
		}

	if (! (foundIn && foundOut))
		{
		LOGTEXT2(_L8("<<CCdcDataInterface::SetUpInterface ret=%d"), 
			KErrGeneral);
		return KErrGeneral;
		}

		// If the device supports USB High-speed, then we request 64KB buffers
		// (otherwise the default 4KB ones will do).

	TUint bandwidthPriority = (EUsbcBandwidthOUTDefault | EUsbcBandwidthINDefault);
	if (dCaps().iHighSpeed)
		{
		bandwidthPriority = (EUsbcBandwidthOUTPlus2 | EUsbcBandwidthINPlus2);
		}

	ifc().iString = &iIfcName;
	ifc().iTotalEndpointsUsed = KRequiredNumberOfEndpoints;
	ifc().iClass.iClassNum	  = 0x0A; // Table 18- Data Interface Class
	ifc().iClass.iSubClassNum = 0x00; // Section 4.6- unused.
	ifc().iClass.iProtocolNum = 0x00; // Table 19- no class specific protocol required

	// Indicate that this interface does not expect any control transfers 
	// from EP0.
	ifc().iFeatureWord |= KUsbcInterfaceInfo_NoEp0RequestsPlease;

	LOGTEXT(_L8("\tcalling SetInterface"));
	// Zero effectively indicates that alternate interfaces are not used.
	ret = iLdd.SetInterface(0, ifc, bandwidthPriority);

	LOGTEXT2(_L8("<<CCdcDataInterface::SetUpInterface ret=%d"), ret);
	return ret;
	}


void CCdcDataInterface::MLSOStateChange(TInt aPacketSize)
	{
	iPacketSize = aPacketSize;
	}


CCdcDataInterface::~CCdcDataInterface()
/**
 * Destructor. Cancel and destroy the child classes.
 */
	{
	LOG_FUNC

	delete iLinkState;
	delete iReadOneOrMoreReader;
	delete iReader;
	delete iWriter;
	delete iDataAvailableNotifier;
	}

void CCdcDataInterface::Write(MWriteObserver& aObserver, 
						  const TDesC8& aDes, 
						  TInt aLen)
/**
 * Write data down the interface
 * 
 * @param aObserver The observer to notify of completion.
 * @param aDes Descriptor containing the data to be sent
 * @param aLen Length of the data to be sent
 */
	{
	LOG_FUNC

	__ASSERT_DEBUG(!iWriteObserver, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));

	iWriteObserver = &aObserver;

	__ASSERT_DEBUG(iWriter, _USB_PANIC(KAcmPanicCat, EPanicInternalError));
	
	if ( iHostCanHandleZLPs )
		{
		
		// Make sure the driver sends a Zero Length Packet if required (i.e. if the 
		// data size is an exact multiple of the endpoint's packet size). 
		TBool requestZlp = ( aLen % iPacketSize ) ? EFalse : ETrue;
		iWriter->Write(aDes, aLen, requestZlp); 
		}
	else
		{
		iWriter->Write(aDes, aLen, EFalse); 
		}
		
	LOGTEXT(_L8("<<CCdcDataInterface::Write"));
	}

void CCdcDataInterface::WriteCompleted(TInt aError)
/**
 * Called when a write request completes.
 *
 * @param aError Error.
 */
	{
	LOGTEXT2(_L8(">>CCdcDataInterface::WriteCompleted aError=%d"), aError);

#ifdef __HEADLESS_ACM_TEST_CODE__
	// Issue another Read or ReadOneOrMore as appropriate.
	// If the Write completed with an error, we panic, as it's invalidating 
	// the test.
	__ASSERT_DEBUG(aError == KErrNone, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));
	switch ( iHeadlessReadType )
		{
	case ERead:
		LOGTEXT2(_L8("__HEADLESS_ACM_TEST_CODE__- issuing Read for %d bytes"), 
			iHeadlessReadLength);
		__ASSERT_DEBUG(iReader, _USB_PANIC(KAcmPanicCat, EPanicInternalError));
		iReader->Read(iHeadlessAcmBuffer, iHeadlessReadLength);
		break;
	case EReadOneOrMore:
		LOGTEXT2(_L8("__HEADLESS_ACM_TEST_CODE__- issuing ReadOneOrMore for %d bytes"), 
			iHeadlessReadLength);
		__ASSERT_DEBUG(iReadOneOrMoreReader, _USB_PANIC(KAcmPanicCat, EPanicInternalError));
		iReadOneOrMoreReader->ReadOneOrMore(iHeadlessAcmBuffer, iHeadlessReadLength);
		break;
	case EUnknown:
	default:
		_USB_PANIC(KAcmPanicCat, EPanicInternalError);
		break;
		}
#else
	// In case the write observer wants to post another write synchronously on 
	// being informed that this write has completed, use this little 'temp' 
	// fiddle.
	__ASSERT_DEBUG(iWriteObserver, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));
	MWriteObserver* temp = iWriteObserver;
	iWriteObserver = NULL;
	LOGTEXT(_L8("\tcalling WriteCompleted on observer"));
	temp->WriteCompleted(aError);
#endif // __HEADLESS_ACM_TEST_CODE__

	LOGTEXT(_L8("<<CCdcDataInterface::WriteCompleted"));
	}

void CCdcDataInterface::CancelWrite()
/**
 * Cancel an outstanding write request
 */
	{
	LOG_FUNC
	
	__ASSERT_DEBUG(iWriter, _USB_PANIC(KAcmPanicCat, EPanicInternalError));

	iWriter->Cancel();

	iWriteObserver = NULL;
	}

void CCdcDataInterface::Read(MReadObserver& aObserver, 
							 TDes8& aDes, 
							 TInt aMaxLen)
/**
 * Read data from the interface. As the LDD supports an appropriate function, 
 * this request can be passed straight down. 
 *
 * @param aObserver The observer to notify of completion.
 * @param aDes Descriptor to put the read data in
 * @param aMaxLen Number of bytes to read
 */
	{
	LOG_FUNC

#ifdef __HEADLESS_ACM_TEST_CODE__
	LOGTEXT(_L8("__HEADLESS_ACM_TEST_CODE__"));
	// Issue a Read using our special internal buffer.
	iHeadlessReadType = ERead;
	iHeadlessReadLength = aMaxLen;
	static_cast<void>(&aObserver);
	static_cast<void>(&aDes);
	__ASSERT_DEBUG(aMaxLen <= iHeadlessAcmBuffer.MaxLength(), 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));
	__ASSERT_DEBUG(iReader, _USB_PANIC(KAcmPanicCat, EPanicInternalError));
	iReader->Read(iHeadlessAcmBuffer, aMaxLen);
#else
	__ASSERT_DEBUG(!iReadObserver, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));
	iReadObserver = &aObserver;

	__ASSERT_DEBUG(iReader, _USB_PANIC(KAcmPanicCat, EPanicInternalError));
	iReader->Read(aDes, aMaxLen);
#endif // __HEADLESS_ACM_TEST_CODE__
	}

void CCdcDataInterface::ReadOneOrMore(MReadOneOrMoreObserver& aObserver, 
								  TDes8& aDes, 
								  TInt aMaxLen)
/**
 * Read a given amount of data from the interface, but complete if any data 
 * arrives.
 *
 * @param aObserver The observer to notify of completion.
 * @param aDes Descriptor to put the read data in
 * @param aMaxLen Number of bytes to read
 */
	{
	LOG_FUNC

#ifdef __HEADLESS_ACM_TEST_CODE__
	LOGTEXT(_L8("__HEADLESS_ACM_TEST_CODE__"));
	// Issue a ReadOneOrMore using our special internal buffer.
	iHeadlessReadType = EReadOneOrMore;
	iHeadlessReadLength = aMaxLen;
	static_cast<void>(&aObserver);
	static_cast<void>(&aDes);
	__ASSERT_DEBUG(aMaxLen <= iHeadlessAcmBuffer.MaxLength(), 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));
	__ASSERT_DEBUG(iReadOneOrMoreReader, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));
	iReadOneOrMoreReader->ReadOneOrMore(iHeadlessAcmBuffer, aMaxLen);
#else
	__ASSERT_DEBUG(!iReadOneOrMoreObserver, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));
	iReadOneOrMoreObserver = &aObserver;

	__ASSERT_DEBUG(iReadOneOrMoreReader, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));
	iReadOneOrMoreReader->ReadOneOrMore(aDes, aMaxLen);
#endif // __HEADLESS_ACM_TEST_CODE__
	}

void CCdcDataInterface::ReadOneOrMoreCompleted(TInt aError)
/**
 * The completion function, called when a ReadOneOrMore request is completed 
 * by the LDD.
 *
 * @param aError The result of the read request.
 */
	{
	LOGTEXT2(_L8(">>CCdcDataInterface::ReadOneOrMoreCompleted aError=%d"), 
		aError);

#ifdef __HEADLESS_ACM_TEST_CODE__
	LOGTEXT2(_L8("__HEADLESS_ACM_TEST_CODE__- issuing Write for %d bytes"),
		iHeadlessAcmBuffer.Length());
	// Write back the data just read.
	// If the ReadOneOrMore completed with an error, we panic, as it's 
	// invalidating the test.
	__ASSERT_DEBUG(aError == KErrNone, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));
	__ASSERT_DEBUG(iWriter, _USB_PANIC(KAcmPanicCat, EPanicInternalError));
	iWriter->Write(iHeadlessAcmBuffer, iHeadlessAcmBuffer.Length(), EFalse); 
#else
	__ASSERT_DEBUG(iReadOneOrMoreObserver, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));

	// See comment in WriteCompleted.
	MReadOneOrMoreObserver* temp = iReadOneOrMoreObserver;
	iReadOneOrMoreObserver = NULL;
	LOGTEXT(_L8("\tcalling ReadOneOrMoreCompleted on observer"));
	temp->ReadOneOrMoreCompleted(aError);
#endif // __HEADLESS_ACM_TEST_CODE__

	LOGTEXT(_L8("<<CCdcDataInterface::ReadOneOrMoreCompleted"));
	}

void CCdcDataInterface::ReadCompleted(TInt aError)
/**
 * Called by the active reader object when it completes.
 *
 * @param aError Error.
 */
	{
	LOGTEXT2(_L8(">>CCdcDataInterface::ReadCompleted aError=%d"), aError);

#ifdef __HEADLESS_ACM_TEST_CODE__
	LOGTEXT2(_L8("__HEADLESS_ACM_TEST_CODE__- issuing Write for %d bytes"),
		iHeadlessAcmBuffer.Length());
	// Write back the data just read.
	// If the Read completed with an error, we panic, as it's invalidating the 
	// test.				 
	__ASSERT_DEBUG(aError == KErrNone,
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));
	__ASSERT_DEBUG(iWriter, _USB_PANIC(KAcmPanicCat, EPanicInternalError));
	iWriter->Write(iHeadlessAcmBuffer, iHeadlessAcmBuffer.Length(), EFalse); 
#else
	__ASSERT_DEBUG(iReadObserver, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));

	// See comment in WriteCompleted.
	MReadObserver* temp = iReadObserver;
	iReadObserver = NULL;
	LOGTEXT(_L8("\tcalled ReadCompleted on observer"));
	temp->ReadCompleted(aError);
#endif // __HEADLESS_ACM_TEST_CODE__

	LOGTEXT(_L8("<<CCdcDataInterface::ReadCompleted"));
	}

void CCdcDataInterface::CancelRead()
/**
 * Cancel an outstanding read request
 */
	{
	LOG_FUNC

	__ASSERT_DEBUG(iReader, _USB_PANIC(KAcmPanicCat, EPanicInternalError));
	__ASSERT_DEBUG(iReadOneOrMoreReader, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));

	iReader->Cancel();
	iReadOneOrMoreReader->Cancel();
	iReadObserver = NULL;
	iReadOneOrMoreObserver = NULL;
	}


void CCdcDataInterface::GetHostZLPHandlingFromFile()
/**
 * Opens the ECACM.ini file to check on the capabilities of the host device.
 * If the ini file cannot be found or read successfully, the default setting i.e. the
 * host device CANNOT handle Zero Length Packets (set in CCdcAcmClass::ConstructL()),
 * is kept.		
 */
	{
	LOG_FUNC
	
	CIniFile* iniFile = NULL;

	_LIT(KEcacmIniFilename, "ecacm.ini" );
	_LIT(KEcacmIniFilePath, "\\system\\data\\" );
	
	TRAPD(error, iniFile = CIniFile::NewL(KEcacmIniFilename, KEcacmIniFilePath));

	if (error == KErrNone)
		{
		TInt hostHandlesZLPs = 1;
	
		_LIT(KHostUSBDeviceDriver, "HostUSBDeviceDriver");
		_LIT(KCanHandleZLP, "CanHandleZLP");
	
		if ( iniFile->FindVar(KHostUSBDeviceDriver, KCanHandleZLP(), hostHandlesZLPs))
			{			
			iHostCanHandleZLPs = (( hostHandlesZLPs == 1 ) ? ETrue : EFalse ); 
		
			LOGTEXT2(_L8("\tecacm.ini: CanHandleZLP=%d"), hostHandlesZLPs);
			}
			
		delete iniFile;	
		}
	}
	
void CCdcDataInterface::NotifyDataAvailableCompleted(TInt aError)
/**
 * Called by the active data available notifier object when it completes.
 *
 * @param aError Error.
 */
	{
	LOGTEXT2(_L8(">>CCdcDataInterface::NotifyDataAvailableCompleted aError=%d"), aError);	
	
	// See comment in WriteCompleted.
	MNotifyDataAvailableObserver* temp = iNotifyDataAvailableObserver;
	iNotifyDataAvailableObserver = NULL;
	LOGTEXT(_L8("\tcalled NotifyDataAvailableCompleted on observer"));
	temp->NotifyDataAvailableCompleted(aError);
	
	LOGTEXT(_L8("<<CCdcDataInterface::NotifyDataAvailableCompleted"));
	}
	
void CCdcDataInterface::NotifyDataAvailable(MNotifyDataAvailableObserver& aObserver)
/**
 * Complete if any data arrives.
 *
 * @param aObserver The observer to notify of completion.
 */
	{
	LOG_FUNC
		
	__ASSERT_DEBUG(!iNotifyDataAvailableObserver, _USB_PANIC(KAcmPanicCat, EPanicInternalError));
	iNotifyDataAvailableObserver = &aObserver;

	__ASSERT_DEBUG(iDataAvailableNotifier, 
		_USB_PANIC(KAcmPanicCat, EPanicInternalError));
	iDataAvailableNotifier->NotifyDataAvailable();
	}

void CCdcDataInterface::CancelNotifyDataAvailable()
/**
 * Cancel notification of arrival of data.
 */
	{
	LOG_FUNC

	__ASSERT_DEBUG(iDataAvailableNotifier, _USB_PANIC(KAcmPanicCat, EPanicInternalError));

	iDataAvailableNotifier->Cancel();
	iNotifyDataAvailableObserver = NULL;	
	}

//
// End of file
