/*
Copyright Digi International, Inc. 1999-2000
*/


/*
 * Module: write.c
 *
 *                     
 *
 *
 * This file contains routines for output data flow from driver
 * to DxB
 */



#include "header.h"



/*
 * Entry point for write
 */


	




    





VOID
AsynTransmitComplete(HANDLE hOwner, HANDLE context,PVOID pControlBuffer,
						ULONG  controlLength,PVOID pDataBuffer,ULONG dataLength,
						NTSTATUS ioStatus)





/*
 *	Completion routine for write data.
 *
 * No locks are held. 
 *
 * If context is NULL, there is not much to do else
 * complete the Irp depending on the write completion
 * flag.
 */


{


	PPORT_EXT pPortExt = context;	
	PPORT_WRITE_OBJ	pWriteObj = &pPortExt->writeObj;
	PIRP pIrp = NULL;


	KeAcquireSpinLock(&pWriteObj->writeLock, &pWriteObj->irql);

	if (pWriteObj->pCurrentWriteIrp != NULL)
	{
		if (pWriteObj->completeOnWrite == FALSE)
		{
			pIrp = 	pWriteObj->pCurrentWriteIrp;
			pIrp->IoStatus.Status = ioStatus;
			RemoveEntryList(&pIrp->Tail.Overlay.ListEntry);

		}

		pWriteObj->pCurrentWriteIrp = NULL;
	}	

	
	KeReleaseSpinLock(&pWriteObj->writeLock, pWriteObj->irql);
	KeSetEvent(&pWriteObj->writeEvent,0L,FALSE);


	if (pIrp != NULL)
		IoCompleteRequest(pIrp,IO_NO_INCREMENT);

}
		

NTSTATUS
AsynTransmitData(PVOID pBuffer,ULONG length,PPORT_EXT pPortExt)

/*
 * Write Lock is held. 
 *
 ******** IM THIS ROUTINE WRITE LOCK IS RELEASED AND REACQUIRED*******
 *
 * Send the data to the board.
 *
 */


{

	NTSTATUS status;	
	PPORT_WRITE_OBJ	pWriteObj = &pPortExt->writeObj;
	

	pWriteObj->tin += length;				// update the tramsnit in pointer.

	KeReleaseSpinLock(&pWriteObj->writeLock, pWriteObj->irql);
	
	status = DsPutMsgEx( pPortExt->hStream, NULL, 0, pBuffer, 
		                     length, pPortExt, DS_PRIORITY_LOW, 
		                     AsynTransmitComplete );


	KeAcquireSpinLock(&pWriteObj->writeLock, &pWriteObj->irql);

	return (status);

}




	


VOID AsynWriteTimeout( IN PKDPC Dpc, IN PVOID DeferredContext,
                      IN PVOID SystemContext1, IN PVOID SystemContext2 )
/*++

Routine Description:

    This routine is used to complete a Write because its
    timer has expired.

Arguments:

    Dpc - Not Used.

    DeferredContext - Really points to the device extension.

    SystemContext1 - Not Used.

    SystemContext2 - Not Used.

Return Value:

    None.

--*/
{

	PPORT_EXT  pPortExt = DeferredContext;
   	PPORT_WRITE_OBJ	*pWriteObj = &pPortExt->writeObj;
	KIRQL	oldIrql;
	PIRP pIrp, pTimeoutIrp;
	NTSTATUS Status = STATUS_TIMEOUT;

   
	UNREFERENCED_PARAMETER(Dpc);
	UNREFERENCED_PARAMETER(SystemContext1);
	UNREFERENCED_PARAMETER(SystemContext2);

  
	ASSERT(KeGetCurrentIrql()==DISPATCH_LEVEL);
	KeAcquireSpinLockAtDpcLevel( &pWriteObj->writeLock );

	pTimeoutIrp = pWriteObj->pTimeoutIrp;
	
	IoAcquireCancelSpinLock(&oldIrql);
	IoSetCancelRoutine(pTimeoutIrp, NULL);
	IoReleaseCancelSpinLock(oldIrql);

	pWriteObj->pTimeoutIrp = NULL;
	if ( pTimeoutIrp == pWriteObj->pCurrentWriteIrp)
		pWriteObj->pCurrentWriteIrp = NULL;	

	


	RemoveEntryList(&pTimeoutIrp->Tail.Overlay.ListEntry);
	if (pTimeoutIrp->IoStatus.Information !=  0)
		pTimeoutIrp->IoStatus.Status = STATUS_SUCCESS;
	else
		pTimeoutIrp->IoStatus.Status = STATUS_TIMEOUT;

	KeReleaseSpinLockFromDpcLevel( &pWriteObj->writeLock );

		
	DigiIoCompleteRequest(pTimeoutIrp);
	
}		
	


VOID
AsynCancelWrite(
    IN DEVICE_OBJECT    *pPortDevice,    
    IN IRP              *pIrp)

{
    PPORT_EXT     *pPortExt;               
	PPORT_WRITE_OBJ	*pWriteObj;
	


    IoSetCancelRoutine(pIrp, NULL);
    IoReleaseCancelSpinLock(pIrp->CancelIrql);

	pPortExt = PortDevice->DeviceExtension;
	pWriteObj = &pPortExt->writeObj;    
    KeAcquireSpinLock(&pWriteObj->lock, &pWriteObj->irql);

	// PARAG TODO - Check the return status.


    pIrp->IoStatus.Status = STATUS_CANCELLED;
    pIrp->IoStatus.Information = 0;

    if (pIrp == pWriteObj->pTimedWriteIrp)
    {
        pWriteObj->pTimedWriteIrp = NULL;
        KeCancelTimer(&pWriteObj->timeoutWrite);

	}

	if (pIrp == pWriteObj->pCurrentWriteIrp)
		pWriteObj->pCurrentWriteIrp = NULL;

    RemoveEntryList(&pIrp->Tail.Overlay.ListEntry);
    KeReleaseSpinLock(&pWriteObj->lock, pWriteObj->irql);

    IoCompleteRequest(Irp, IO_SERIAL_INCREMENT);
}

   



//PARAG - TODO - Termination condition for this threac ??
   
   
   
AsynWriteThread(PPORT_EXT pPort)
{

/*
 * This thread checks for the IRps in the write queue and services 
 * them
 */   

	PPORT_WRITE_OBJ	*pWriteObj = &pPortExt->writeObj;
	for(;;)
	{


		KeWaitForSingleObject(&pWriteObj->writeEvent,                                                      
		   	Executive, 
			KernelMode, 
			FALSE,                  /* Don't alert. */
			(PLARGE_INTEGER) NULL);  /* No timeout value. */
			

		KeAcquireSpinLock(&pWriteObj->writeLock, &pWriteObj->irql);
		
		
		if ((pWriteObj->pCurrentWriteIrp == NULL) && (!IsListEmpty( &pWriteObj->writeIrpQueue))

		{
			p = RemoveHeadList(&pWriteObj->writeIrpQueue);
			pIrp = CONTAINING_RECORD(p,IRP,Tail.Overlay.ListEntry);
			AsynProcessWrite(PPORT_EXT pPortExt,PIRP pIrp) ;
		}

		KeReleaseSpinLock(&pWriteObj->writeLock, pWriteObj->irql);

	}

}



NTSTATUS
AsynQueueWrite (PIRP pIrp,PPORT_EXT pPortExt)
{

/* 
 *	Write Lock is held when called
 *
 *  Send the buffer to the card. If there is not enouh
 *  room, put the Irp back on the writeIrpQ. If there is
 *  enough room and all the data can be sent,do it.
 *  else send  as much data as possible and queue the IRP
 *  back at  the head  of the WriteIrpQueue.
 */


 
{

	ULONG	length;											// number of bytes to be written
	ULONG	space;											// spcace on the card buffer
	PPORT_WRITE_OBJ	pWriteObj = &pPortExt->writeObj;		// write structure pointer
	NTSTATUS status;
	ULONG  bytes;											// number of bytes actually written
   

	length = IoGetCurrentIrpStackLocation(pIrp)->Parameters.Write.Length -
					pIrp->IoStatus.Information; 


//PARAG - TODO - VErify and put in a macro; optimize

  

	if ( pWriteObj->txSequenceNumber > pWriteObj->tin)
		space =  pWriteObj->txSequenceNumber -  pWriteObj->tin;
	else
		space =   pWriteObj->tmax - pWriteObj->tin + 
			pWriteObj->txSequenceNumber;




	bytes = length;
	
	if (space < length)
	{
		bytes = space;
		InsertHeadList(&pWriteObj->writeIrpQueue,&pIrp->Tail.Overlay.ListEntry);
		
		
	}
	else
	{
		InsertTailList(&pWriteObj->writePendedQueue,&pIrp->Tail.Overlay.ListEntry);
		pWriteObj->pCurrentWriteIrp = pIrp;
	}

  	if (length != 0)
	{
		pIrp->IoStatus.Information += bytes;		
		status = AsynTransmitData(pIrp->AssociatedIrp.SystemBuffer +  pIrp->IoStatus.Information,
						bytes,pPortExt);
		
	}	

	if ((bytes != length) && (NT_SUCCESS(status))
		status = STATUS_PENDING;   

	return (status);

}																						 








AsynProcessWrite(PPORT_EXT pPortExt,PIRP pIrp)
/*
 *	Write Lock is Held when called
 *
 *	Further process the write request.
 *	All basic processing has been completed
 */


{

	PPORT_WRITE_OBJ	pWriteObj = &pPortExt->writeObj;
	ULONG length = IoGetCurrentIrpStackLocation(pIrp)->Parameters.Write.Length;
	ULONG space ;
	NTSTATUS status;
	PCHAR	irpBuffer;	

	/*
	 * If there is data in the shortwritebuffer and the current write is not a "short write"
	 * put it on the queue and empty the short write buffer
	 */

	if ((pWriteObj->indexShortWBufIn != pWriteObj->indexShortWBufOut) && (length >  pWriteObj->shortBufferTHold) )
	{

				
		InsertHeadList(&pWriteObj->writeIrpQueue,&pIrp->Tail.Overlay.ListEntry);
		AsynEmptyShortBuffer(pPortExt);
		status = STATUS_PENDING;
				
	}


	else

	{
	/*
	 * If the current write is a short write, buffer it in the
	 * short buffer. If the current write is a "big" write
	 * proceed to the physical write routine.
	 */

		if (length <= pWriteObj->shortBufferTHold)			// small packet ?
		{
			if 	(pWriteObj->smallPacketBuffer == TRUE )		 // Yes, buffer this small write.
			{	
				ULONG i = 0;

				// PARAG - TODO - verify this. Put in a macro


				if ( pWriteObj->indexShortWBufOut > pWriteObj->indexShortWBufIn)
					space =  pWriteObj->indexShortWBufOut -  pWriteObj->indexShortWBufIn;
				else
					space =   pWriteObj->shortWBufferMax - pWriteObj->indexShortWBufIn + 
								pWriteObj->indexShortWBufOut;

				
				
				(ULONG)irpBuffer = (ULONG)pIrp->AssociatedIrp.SystemBuffer +  (ULONG)pIrp->IoStatus.Information;

				while ((space != 0) && (length != 0))
				{																					
					pWriteObj->shortWriteBuffer[pWriteObj->indexShortWBufIn++] = irpBuffer[i++];
					--space;
					--length;
				
					if ( pWriteObj->indexShortWBufIn > pWriteObj->shortWBufferMax )
							 pWriteObj->indexShortWBufIn = 0;

				}
				if (length != 0)
				{
				/* The short write buffer ran out of space.
				 * Put this Irp back on the queue. Update the
				 * number of bytes already written.
				 */

					InsertHeadList(&pWriteObj->writeIrpQueue,&pIrp->Tail.Overlay.ListEntry);
					pIrp->IoStatus.Information = i;
					AsynEmptyShortBuffer(pPortExt);
					status = STATUS_PENDING;

				}
				else
					status = STATUS_SUCCESS;


			}

			else
			/*
			 * This could be first of the "short write". Let's send this one to the
			 * card and buffer the subseqent writes.
			 */
			{
				pWriteObj->smallPacketBuffer = TRUE;			// flag set to buffer subsequent writes
				status = AsynQueueWrite(pIrp,pPortExt);

			}
		
			
		}
		else		//  Short buffer is empty and this packet should not be buffered. 
			{
			pWriteObj->smallPacketBuffer = FALSE;
			status = AsynQueueWrite(pIrp,pPortExt);
			}


	}

	return (status);

}
	


NTSTATUS
DigiAsynWrite(
    IN DEVICE_OBJECT    *pPortDevice,   // NT device object for this port 
    IN IRP              *pIrp            // IRP
)

{
	PPORT_EXT	pPortExt;
	NTSTATUS    status;                 /* Called function return value. */
	PPORT_WRITE_OBJ	pWriteObj;

	DigiDump( DIGIWRITE, ( "DigiAsyn!DigiAsynWrite: pPortDevice 0x%x pIrp %x\n",
								 pPortDevice,pIrp ) );
	 

    /*
    ** Get the driver specific data structures.
    */
    pPortExt = pPortDevice->DeviceExtension;

	
	KeAcquireSpinLock(&pPortExt->portLock, &pPortExt->portIrql);
	if (pPortExt->state != OpenState)
	{
		status = STATUS_UNSUCCESSFUL;
		KeReleaseSpinLock(&pPortExt->portLock, pPortExt->portIrql);
		goto Exit;
	}

	KeReleaseSpinLock(&pPortExt->portLock, pPortExt->portIrql);

	

    /*
    ** Zero length writes are completed immediately.
    */
    if (IoGetCurrentIrpStackLocation(pIrp)->Parameters.Write.Length == 0)
    {
        status = STATUS_SUCCESS;
		goto Exit;
    }
    
    /*
    ** Must have a non-zero buffer.
    */

    if (pIrp->AssociatedIrp.SystemBuffer == NULL)
    {
        status = STATUS_INVALID_PARAMETER;
		goto Exit;
    }

	pWriteObj = &pPortExt->writeObj;

	KeAcquireSpinLock(&pWriteObj->writeLock, &pWriteObj->irql);

	if (!IsListEmpty(&pWriteObj->writeIrpQueue) || (pWriteObj->pCurrentWriteIrp != NULL) )
	{

	/*
	 * If there is a write in progress queue the Irp in the write queue
	 */

		InsertTailList(&pWriteObj->writeIrpQueue,&pIrp->Tail.Overlay.ListEntry);
		status = STATUS_PENDING;
		
	}
	else
	{
	/*
	 * No writes in progress or pending. Let's proceed further
	 */
		status = AsynProcessWrite(pPortExt,pIrp);
	}

	if (status == STATUS_PENDING)
		IoMarkIrpPending(pIrp);

	KeReleaseSpinLock(&pWriteObj->writeLock, pWriteObj->irql);

Exit:

	pIrp->IoStatus.Status = status;

	if (status != STATUS_PENDING)
		IoCompleteRequest(pIrp,IO_NO_INCREMENT);
   
	return (status);

}



NTSTATUS
AsynWriteInit(PPORT_EXT pPortExt)



/*
 * Initialize the write data structures in the Port 
 * extention. This can be called from DriverEntry
 * or AddDevice as the case may be
 */


{
	NTSTATUS status = STATUS_SUCCESS;
	PPORT_WRITE_OBJ	pWriteObj = &pPortExt->writeObj;

	pWriteObj->shortWriteBuffer = DigiAllocMem(NonPagedPool, DIGI_SZ_SHORTWRITEBUFFER);

	if (pWriteObj->shortWriteBuffer  == NULL)
	{
		status = STATUS_UNSUCCESSFUL;
		goto Exit;
															 
	}

	pWriteObj->shortWBufferMax =  DIGI_SZ_SHORTWRITEBUFFER -1 ;
	pWriteObj->indexShortWBufIn =  pWriteObj->indexShortWBufIn = 0; 

/*	
	Parag-TODO 
	Get these values from registy or from board


	pPortExt->ttime
	pPortExt->tmax
	pPortExt->tsize
	pPortExt->tout

	pPortExt->txSequenceNumber;

*/
	

	
	KeInitializeSpinLock(&pWriteObj->writeLock);

	KeInitializeDpc(&pWriteObj->timeoutWriteDpc, 
                        asynWriteTimeout,pPortExt);
		
	KeInitializeDpc(&pWriteObj->shortWBufDpc, 
                        asynWriteShortBuffer,pPortExt);



Exit:
	
	return (status);

}


		