/*++
 ****************************************************************************
 * Copyright (c) 1999-2001 Digi International Inc., All Rights Reserved
 *
 * This software contains proprietary and confidential information of Digi
 * International Inc.  By accepting transfer of this copy, Recipient agrees
 * to retain this software in confidence, to prevent disclosure to others,
 * and to make no use of this software other than that for which it was
 * delivered.  This is an unpublished copyrighted work of Digi International
 * Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 * prohibited.
 *
 * Restricted Rights Legend
 *
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 * Technical Data and Computer Software clause at DFARS 252.227-7031 or
 * subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 * Restricted Rights at 48 CFR 52.227-19, as applicable.
 *
 * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 ****************************************************************************

Module Name:

   asynread.c

Abstract:

   This module contains the NT routines responsible for reading data
   from a Digi controller running S/DXB.

Author:

   Sandy Haapala

--*/

#include "digiasyn.h"

VOID
AsynRead_OnCreate( 
	IN PASYN_READ			pReadExt
)
{
	pReadExt->fRxPaused = FALSE;
	pReadExt->cbMaxRxQ = READ_DEFAULT_RX_QUEUE_SIZE; // cbMaxRxQ can be adjusted using IOCTL_SERIAL_SET_QUEUE_SIZE
	pReadExt->cbRxQ = 0;
	pReadExt->cbTotalRead = 0;
}   


VOID
AsynReadSetInQueueSize(
	IN PASYN_PORT_EXT		pPortExt,
	IN ULONG					InSize 
)
{
	PASYN_READ	pReadExt = &pPortExt->Read;
   
	pReadExt->cbMaxRxQ = InSize;
   if ( pReadExt->fRxPaused && pReadExt->cbMaxRxQ > pReadExt->cbRxQ )
	{
		RxEngineUnpause( pPortExt->hRxEngine_LowPriority );
      pReadExt->fRxPaused = FALSE;
	}               	
}


VOID AsynPurgeRxBuffers(
	IN	PASYN_PORT_EXT 	pPortExt
)
{
	PASYN_READ 	pReadExt = &pPortExt->Read;
   
	DigiAcquireSpinLock( &pReadExt->Lock );
	while ( !IsListEmpty( &pReadExt->RxQ ) )
	{
		PRX_BUF	pRxBuf = CONTAINING_RECORD( pReadExt->RxQ.Flink, RX_BUF, ListEntry );
		RemoveEntryList( &pRxBuf->ListEntry );
		RxEngineFreeData( pRxBuf );
	}
	pReadExt->cbRxQ = 0;

	if ( pReadExt->fRxPaused )
	{
		RxEngineUnpause( pPortExt->hRxEngine_LowPriority );
		pReadExt->fRxPaused = FALSE;
	}            
	DigiReleaseSpinLock( & pReadExt->Lock );
   
}   


VOID 
AsynSetReadTimeouts(
	IN PASYN_READ 			pReadExt,	// Pointer to the read structure in the device extension
	IN PSERIAL_TIMEOUTS  pTimeouts	// Set these timeout values.
)
/*++

Description

	Called from the IOCTL handler for IOCTL_SERIAL_SET_TIMEOUTS to setup
   the Read.Timeout structure.
   
--*/
{	

	ULONG             Multiplier;    // Timeout multiplier.
	ULONG             ConstantTime;  // Timeout constant.
	ULONG             Interval;      // Timeout interval.
	READ_TOTAL_TIMEOUT_TYPE	Type;

	DigiDump( DIGIIOCTL|DIGIREAD, ("AsynSetReadTimeouts:\n"
			"\tNew ReadIntervalTimeout         = %d (ms)\n"
	      "\tNew ReadTotalTimeoutMultiplier  = %d\n"
	      "\tNew ReadTotalTimeoutConstant    = %d\n",
	      pTimeouts->ReadIntervalTimeout,
	      pTimeouts->ReadTotalTimeoutMultiplier,
	      pTimeouts->ReadTotalTimeoutConstant ) );
	Interval = pTimeouts->ReadIntervalTimeout;
	Multiplier = pTimeouts->ReadTotalTimeoutMultiplier;
	ConstantTime = pTimeouts->ReadTotalTimeoutConstant;

	Type = NoTotalTimeout;

	// Check for special conditions.
	if ( Interval == MAXULONG )
	{
		//
		//    If constant and multiplier are a zero value
		//    return immediately with whatever data is present.
		//
		if (( Multiplier == 0 ) && ( ConstantTime == 0 ))
		{
			Type = CompleteImmediately;
		}
		else 
		{
			if ( ConstantTime != MAXULONG )
			{
				//
				//    If constant and multiplier are not MAXULONG then return
				//    if any characters are present
				//
				if ( Multiplier != MAXULONG )
				{
					Type = CompleteOnRxDataPresent;
				}
				//
				//    If multiplier is MAXULONG then return if any
				//    characters are present OR when the first character
				//    arrives.
				//
				else
				{
					Type = CompleteOnFirstRxChar;
				}               
			}
		}
	}
	else 
	{
		// If either the multiplier or the constant in not zero, then use timeout.
		if ((Multiplier) || (ConstantTime))
		{
			Type = UseTotalTimeout;
		}
	}   

	DigiAcquireSpinLock( &pReadExt->Lock );

	pReadExt->Timeout.Interval = Interval;
	pReadExt->Timeout.Multiplier = Multiplier;
	pReadExt->Timeout.Constant = ConstantTime;
	pReadExt->Timeout.Type = Type;

	// Set interval timeout variables.   
	if ( (Interval != MAXULONG) && (Interval > 0) )
	{
		pReadExt->Timeout.fIntervalTimeout = TRUE;
		pReadExt->Timeout.IntervalDuration.QuadPart = (LONGLONG)Interval * ONE_MILLISECOND;
	}      
	else
	{
		pReadExt->Timeout.fIntervalTimeout = FALSE;      
		pReadExt->Timeout.IntervalDuration.QuadPart = 0;
	}      
	DigiReleaseSpinLock( &pReadExt->Lock );

}


VOID 
AsynGetReadTimeouts(
	IN PASYN_READ 			pReadExt,	// Pointer to the read structure in the device extension
	IN PSERIAL_TIMEOUTS  pTimeouts	
)
/*++

Description

	Called from the IOCTL handler for IOCTL_SERIAL_GET_TIMEOUTS to retrieve the 
   read timeouts settings from the Read.Timeout structure.
   
--*/
{	
	DigiAcquireSpinLock( &pReadExt->Lock );
	pTimeouts->ReadIntervalTimeout = pReadExt->Timeout.Interval;
	pTimeouts->ReadTotalTimeoutMultiplier = pReadExt->Timeout.Multiplier;
	pTimeouts->ReadTotalTimeoutConstant = pReadExt->Timeout.Constant;
	DigiReleaseSpinLock( &pReadExt->Lock );
   
}


STATIC VOID 
StopReadTimeouts(
	IN PASYN_READ pReadExt			// Pointer to the read structure in the device extension
)
/*++

Description

	Called to stop any read timers that are currently active for read timeout handling.
   
   The port's read spinlock must be held when this routine is called.
   
--*/
{
	if ( pReadExt->Timeout.fTotalActive )
	{
		KeCancelTimer( &pReadExt->Timeout.Timer );
		pReadExt->Timeout.fTotalActive = FALSE;
	}

	if ( pReadExt->Timeout.fIntervalActive )
	{
		KeCancelTimer( &pReadExt->Timeout.IntervalTimer );
		pReadExt->Timeout.fIntervalActive = FALSE;
	}
	pReadExt->Timeout.pIrp = NULL;
}



STATIC VOID 
StartReadTimeouts(
	IN PASYN_READ pReadExt,		// Pointer to the read structure in the device extension
	IN PIRP pIrp
)
/*++

Description

	Called to start the timers for read timeout handling.
   
   The port's read spinlock must be held when this routine is called.
   
--*/

{
	// If a timer is already active for an irp, do not restart the timers.
	if ( pReadExt->Timeout.fTotalActive || pReadExt->Timeout.fIntervalActive )
	{
		goto Exit;
	}

	if ( pReadExt->Timeout.Type != NoTotalTimeout )
	{
		LARGE_INTEGER 	Timeout;
		ULONG				Multiplier;

		// Set total timeout timer
		if ( pReadExt->Timeout.Type == CompleteOnFirstRxChar )
			Multiplier = 0;
		else
			Multiplier = pReadExt->Timeout.Multiplier;

		// Compute the timeout based upon the bytes to be received.
		Timeout.QuadPart = ((LONGLONG)( (IoGetCurrentIrpStackLocation( pIrp ))->Parameters.Read.Length
				 * Multiplier) + pReadExt->Timeout.Constant ) * ONE_MILLISECOND;
		pReadExt->Timeout.pIrp = pIrp;
		pReadExt->Timeout.fTotalActive = TRUE;
		DigiDump( (DIGIFLOW|DIGIREAD), ("Read Total Timeout set: (%08x:%08x)\n", Timeout.HighPart, Timeout.LowPart ));
		KeSetTimer( &pReadExt->Timeout.Timer, Timeout, &pReadExt->Timeout.Dpc);
	}

    // Start the Interval timer.   
    if ( pReadExt->Timeout.fIntervalTimeout )
    {
        // track the last byte received
        pReadExt->Timeout.cbInterval = (ULONG)pIrp->IoStatus.Information; 
        pReadExt->Timeout.pIrp = pIrp;
        pReadExt->Timeout.fIntervalActive = TRUE;
        DigiDump( (DIGIFLOW|DIGIREAD), ("Read Interval Timeout set: (%08x:%08x)\n", 
        pReadExt->Timeout.IntervalDuration.HighPart, pReadExt->Timeout.IntervalDuration.LowPart ));
        KeSetTimer( &pReadExt->Timeout.IntervalTimer, pReadExt->Timeout.IntervalDuration, &pReadExt->Timeout.IntervalDpc);
    }         

Exit:      
	;      
   
}



STATIC VOID
CopyReadData( 
	IN	PASYN_READ	pReadExt,	// Pointer to the read structure in the device extension
	IN PIRP			pIrp
)
/*++

Description

	Called to copy data from the receive buffers to the irp.
   
   The port's read spinlock must be held when this routine is called.
   
--*/
{
	if ( !IsListEmpty( &pReadExt->RxQ ) )
	{   
		ULONG cbAvailSpace = (IoGetCurrentIrpStackLocation(pIrp))->Parameters.Read.Length - (ULONG)pIrp->IoStatus.Information;
		PUCHAR pIrpBuffer = (PUCHAR)pIrp->AssociatedIrp.SystemBuffer + (ULONG)pIrp->IoStatus.Information;
		ULONG cbCopied = 0;

		while ( cbAvailSpace && !IsListEmpty( &pReadExt->RxQ ) )
		{
			PRX_BUF	pRxBuf = CONTAINING_RECORD( pReadExt->RxQ.Flink, RX_BUF, ListEntry );
			ULONG		cbToCopy = MIN( cbAvailSpace, pRxBuf->cbRemaining );
			ASSERT( cbToCopy > 0 );

			RtlCopyMemory( pIrpBuffer, (PUCHAR)pRxBuf + sizeof(RX_BUF) + pRxBuf->iNext, cbToCopy );
			pIrpBuffer += cbToCopy;
			cbAvailSpace -= cbToCopy;
			cbCopied += cbToCopy;
			pRxBuf->iNext += cbToCopy;
			pRxBuf->cbRemaining -= cbToCopy;

			if ( pRxBuf->cbRemaining == 0 )
			{
				// No data remaining in the receive buffer, free the buffer
				RemoveEntryList( &pRxBuf->ListEntry );
				RxEngineFreeData( pRxBuf );
			}
		}

		pIrp->IoStatus.Information += cbCopied;	
		pReadExt->cbRxQ -= cbCopied;			
		pReadExt->cbTotalRead += cbCopied;
	}	      
}   

STATIC INLINE BOOLEAN
IsReadDone(
	IN PASYN_READ  pReadExt,
	IN	PIRP			pIrp
)
/*++

Description

	Determine whether this read IRP can be completed now, either because it is
   full or because of the current read timeout settings.
   
   The port's read spinlock must be held when this routine is called.
   
--*/
{
	BOOLEAN fDone = FALSE;

	if ( pIrp->IoStatus.Information == (IoGetCurrentIrpStackLocation( pIrp ))->Parameters.Read.Length )
		fDone = TRUE;
	else
	{
		switch ( pReadExt->Timeout.Type )
		{
			case CompleteImmediately:
				// Complete the irp even if no data has been received yet
				fDone = TRUE;
				break;

			case CompleteOnFirstRxChar:
				// Complete the irp as soon as the first character arrives
			case CompleteOnRxDataPresent:
				// If receive data is present complete the irp now
				if ( pIrp->IoStatus.Information > 0 )
				fDone = TRUE;
				break;

			case UseTotalTimeout:
			case NoTotalTimeout:            
			default:
				break;
		}
	}         
	return fDone;
}   

STATIC VOID 
ProcessReadQueue(
	IN PASYN_PORT_EXT pPortExt	// Pointer to the device extension
)
/*++

Description

	Process the irps that are on the read pending irp queue. 
   
--*/
{
	LIST_ENTRY CompletedList;
	NTSTATUS Status = STATUS_SUCCESS;
	PASYN_READ			pReadExt = &pPortExt->Read;

	InitializeListHead( &CompletedList );

	DigiAcquireSpinLock( &pReadExt->Lock );   
	while ( !IsListEmpty( &pReadExt->PendingIrps) && Status != STATUS_PENDING )
	{ 
		PIRP pIrp = CONTAINING_RECORD( pReadExt->PendingIrps.Flink, IRP,
																			Tail.Overlay.ListEntry);

		if (pIrp->Cancel)
		{
			Status = STATUS_CANCELLED;
			pIrp->IoStatus.Information = 0;
		}
		else
		{      
			// Copy receive data to the waiting IRP
			CopyReadData( pReadExt, pIrp );
			if ( IsReadDone( pReadExt, pIrp ))
			{
				Status = STATUS_SUCCESS;
			}            
			else            
			{
				// The IRP is waiting for more receive data, start the read timeouts.
				StartReadTimeouts( pReadExt, pIrp );
				Status = STATUS_PENDING;
			}
		}         

		if ( Status != STATUS_PENDING )         
		{
			// The IRP can be completed - add it to the list of IRPs to complete 
			//  after the spinlock is released.
			RemoveEntryList( &pIrp->Tail.Overlay.ListEntry );
			IoSetCancelRoutine( pIrp, NULL );
			pIrp->IoStatus.Status = Status;
			InsertTailList(&CompletedList, &pIrp->Tail.Overlay.ListEntry);
			StopReadTimeouts( pReadExt );
		}         
	}       
	DigiReleaseSpinLock( &pReadExt->Lock );

	// Now complete the IRPs on our list
	while ( !IsListEmpty( &CompletedList ))
	{
		PIRP pIrp;
		PLIST_ENTRY pEntry = RemoveHeadList(&CompletedList);

		pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
		ASSERT( pIrp->Type == IO_TYPE_IRP );
		DigiDump( DIGIREAD, ("ProcessReadQueue: Port %d completed IRP %p with status=%x read.length=%x cbRcvQueue = %x\n",pPortExt->iSdxbPort,pIrp,pIrp->IoStatus.Status,pIrp->IoStatus.Information,pPortExt->Read.cbRxQ));
		AsynIoCompleteRequest( pIrp, pIrp->IoStatus.Status, pPortExt );
	}	     
}



STATIC VOID
AsynReadTimedOut(
	IN PKDPC			pPortDpc,      // DPC object for Port
	IN PASYN_PORT_EXT	pPortExt,  		// Pointer to the read structure of the device extension
	IN PVOID			SystemArg1,    // System argument.
	IN PVOID			SystemArg2     // System argument.
)
/*++

Description

	DPC for the read timer. Complete the irp which timed out. 
   
--*/
{
	PASYN_READ			pReadExt = &pPortExt->Read;

	UNREFERENCED_PARAMETER( pPortDpc);
	UNREFERENCED_PARAMETER( SystemArg1);
	UNREFERENCED_PARAMETER( SystemArg2);

	DigiAcquireSpinLock( &pReadExt->Lock );
	pReadExt->Timeout.fTotalActive = FALSE;

	// Insure that the timeout is justified; rather than race on a completed read IRP.
	if ((pReadExt->Timeout.pIrp == NULL) || ( IsListEmpty( &pReadExt->PendingIrps)))
	{
		DigiReleaseSpinLock( &pReadExt->Lock );
		goto Exit;
	}
   
	{
		PLIST_ENTRY p;                // LIST_ENTRY intermediate value.
		PIRP pIrp;

		p = RemoveHeadList( &pReadExt->PendingIrps );
		pIrp = CONTAINING_RECORD( p, IRP, Tail.Overlay.ListEntry);

		if ( pIrp == pReadExt->Timeout.pIrp )
		{
			// The IRP timed out.
			StopReadTimeouts( pReadExt );
			IoSetCancelRoutine( pIrp, NULL);
			DigiReleaseSpinLock( &pReadExt->Lock );
			DigiDump( DIGIREAD|DIGITIMEOUT, ("AsynReadTimedOut: completed IRP %x with status=STATUS_TIMEOUT read.length=%x \n",pIrp,pIrp->IoStatus.Information));

			AsynIoCompleteRequest( pIrp, STATUS_TIMEOUT, pPortExt );

		}      
		else
		{
			// Put IRP back on head of queue
			InsertHeadList( &pReadExt->PendingIrps, &pIrp->Tail.Overlay.ListEntry);
			DigiReleaseSpinLock( &pReadExt->Lock );
		}
	}   

Exit:   
	// Process the read queue to start the timer for the next IRP
	ProcessReadQueue( pPortExt );		
}



VOID
AsynReadIntervalTimedOut(
	IN PKDPC            pPortDpc,       // DPC object for Port.
	IN PASYN_PORT_EXT	pPortExt,       // Pointer to the read structure in the device extension
	IN PVOID            SystemArg1,     // System argument.
	IN PVOID            SystemArg2      // System argument.
)
/*++

Description

	DPC for the interval read timer. Check whether any new characters 
   were received during the timer interval. If not, the irp has timed out. 
   
--*/
{
	PASYN_READ		pReadExt = &pPortExt->Read;
	UNREFERENCED_PARAMETER(pPortDpc);
	UNREFERENCED_PARAMETER(SystemArg1);
	UNREFERENCED_PARAMETER(SystemArg2);

	DigiAcquireSpinLock( &pReadExt->Lock );

	pReadExt->Timeout.fIntervalActive = FALSE;

	// Insure that the timeout is justified; rather than race on a completed read IRP
	if ((pReadExt->Timeout.pIrp == NULL) || ( IsListEmpty( &pReadExt->PendingIrps)))
	{
		DigiReleaseSpinLock( &pReadExt->Lock );
		goto Exit;
	}
   
    {
        PIRP pIrp;

        PLIST_ENTRY p = RemoveHeadList( &pReadExt->PendingIrps );
        pIrp = CONTAINING_RECORD( p, IRP, Tail.Overlay.ListEntry);

        if ( pIrp == pReadExt->Timeout.pIrp )
        {

            DigiDump(DIGIREAD | DIGITIMEOUT, ("AsynReadIntervalTimedOut: IntervalCount =0x%x IoStatus.Info=0x%x \n", 
                                                        pReadExt->Timeout.cbInterval, pIrp->IoStatus.Information));
			               
            if ((pReadExt->Timeout.cbInterval == 0) || (pIrp->IoStatus.Information > pReadExt->Timeout.cbInterval))
            {
                // Either no characters have been received yet or a character was received 
                // within the timer interval. Restart the timer.
                pReadExt->Timeout.cbInterval = (ULONG)pIrp->IoStatus.Information;
                InsertHeadList( &pReadExt->PendingIrps, &pIrp->Tail.Overlay.ListEntry);
                KeSetTimer( &pReadExt->Timeout.IntervalTimer, pReadExt->Timeout.IntervalDuration, &pReadExt->Timeout.IntervalDpc);
                pReadExt->Timeout.fIntervalActive = TRUE;
                DigiReleaseSpinLock( &pReadExt->Lock );
            }
            else
            {
                StopReadTimeouts( pReadExt );
                IoSetCancelRoutine( pIrp, NULL);
                DigiReleaseSpinLock( &pReadExt->Lock );
                DigiDump( DIGIREAD|DIGITIMEOUT, ("AsynReadIntervalTimedOut: completed IRP %x with status=STATUS_TIMEOUT read.length=%x \n",pIrp,pIrp->IoStatus.Information));
                AsynIoCompleteRequest( pIrp, STATUS_TIMEOUT, pPortExt );
            }
        }
        else
        {
            // Put IRP back on head of queue
            InsertHeadList( &pReadExt->PendingIrps, &pIrp->Tail.Overlay.ListEntry);
            DigiReleaseSpinLock( &pReadExt->Lock );
        }
    }	
Exit:   
    ProcessReadQueue( pPortExt );

}



NTSTATUS 
AsynReadInit(
	IN PASYN_PORT_EXT pPortExt		// Pointer to the read structure in the device extension
)
/*++

Description

	Called to initialize the read data structure in the port extension during 
   port initialization.
   
--*/
{
	PASYN_READ	pReadExt = &pPortExt->Read;
	NTSTATUS	Status = STATUS_SUCCESS;

	DigiDump(DIGIINIT|DIGIREAD,("AsynReadInit Entry\n"));

	DigiInitializeSpinLock( &pReadExt->Lock );

	KeInitializeDpc( &pReadExt->Timeout.IntervalDpc, AsynReadIntervalTimedOut, pPortExt);
	KeInitializeTimer( &pReadExt->Timeout.IntervalTimer);

	KeInitializeDpc( &pReadExt->Timeout.Dpc, AsynReadTimedOut, pPortExt);
	KeInitializeTimer( &pReadExt->Timeout.Timer);

	InitializeListHead( &pReadExt->RxQ );
	InitializeListHead( &pReadExt->PendingIrps );

	return Status;
}       




VOID
AsynCancelRead(
	IN	PDEVICE_OBJECT 	pDeviceObject,
	IN PIRP              pIrp       
)
/*++

Description

	Cancel routine for read irps
   
--*/
{
	PASYN_PORT_EXT	pPortExt = pDeviceObject->DeviceExtension;
	PASYN_READ		pReadExt = &pPortExt->Read;

	IoReleaseCancelSpinLock( pIrp->CancelIrql );

	DigiDump(DIGICANCEL|DIGIREAD, ("AsynCancelRead: Port=0x%x IRP=0x%x\n",
															pPortExt->iSdxbPort, pIrp));

	DigiAcquireSpinLock( &pReadExt->Lock );
	if (pIrp == pReadExt->Timeout.pIrp )
	{
		StopReadTimeouts( pReadExt );
	}

	// Find the Irp on the pending queue
	if ( RemoveIrpFromQueue( &pReadExt->PendingIrps, pIrp ) )
	{
		DigiDump( DIGICANCEL|DIGIREAD , ("\tCancelling read Irp: 0x%x\n", pIrp ));

		// The IRP is all ours now, let everything run.
		DigiReleaseSpinLock( &pReadExt->Lock );

		pIrp->IoStatus.Information = 0;
		DigiDump( DIGIREAD|DIGICANCEL, ("AsynCancelRead: completed IRP %x with status=STATUS_CANCELLED read.length=%x \n",pIrp,pIrp->IoStatus.Information));
		AsynIoCompleteRequest( pIrp, STATUS_CANCELLED, pPortExt );
	}
	else      
	{
		DigiDump(DIGICANCEL|DIGIREAD, ("\tIrp:%x was not found.\n", pIrp));
		DigiReleaseSpinLock( &pReadExt->Lock );
	}
}

VOID
AsynCancelReadIrps(
   IN PASYN_PORT_EXT      pPortExt          // Port specific information.
)
/*++

Description

	Called to cancel all of the read irps on the pending queue.
   
--*/

{
	LIST_ENTRY CancelList;
	PASYN_READ	pReadExt = &pPortExt->Read;

	DigiDump( DIGICANCEL, ("Entering AsynCancelReadIrps\n") );

	InitializeListHead( &CancelList );

	DigiAcquireSpinLock( &pReadExt->Lock );   

	StopReadTimeouts( pReadExt );

	// Mark each of the IRPs as cancelled and place on a queue to
	// complete after the spinlock can be released.   
	while ( !IsListEmpty( &pReadExt->PendingIrps) )
	{
		PIRP pIrp;
		PLIST_ENTRY pEntry = RemoveHeadList( &pReadExt->PendingIrps );

		pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
		ASSERT( pIrp->Type == IO_TYPE_IRP );
		pIrp->Cancel = TRUE;
		IoSetCancelRoutine( pIrp, NULL );
		pIrp->IoStatus.Information = 0;
		InsertTailList( &CancelList, &pIrp->Tail.Overlay.ListEntry );
	}      
	DigiReleaseSpinLock( &pReadExt->Lock );

	// Now complete each IRP on the cancel queue   
	while ( !IsListEmpty( &CancelList ))
	{ 
		PIRP pIrp;
		PLIST_ENTRY pEntry = RemoveHeadList(&CancelList);

		pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
		DigiDump( DIGIREAD|DIGICANCEL, ("AsynCancelReadIrps: completed IRP %x with status=STATUS_CANCELLED read.length=%x \n",pIrp,pIrp->IoStatus.Information));
		AsynIoCompleteRequest( pIrp, STATUS_CANCELLED, pPortExt );
	}   
}  


STATIC VOID
ParseReadDataInPlace( 
	IN PASYN_PORT_EXT	pPortExt,
	IN PRX_BUF			pRxBuf,
	IN ULONG			cbTotal,
	OUT PULONG			pcbValid
)
/*++

Description

	Called after buffer has been received from 
	S/DXB but before it's placed on the queue.

	The buffer is parsed for parity/break/frame  
	error information, which was inserted into the 
	buffer by the firmware. This fcn. removes the 
	error indicators and signals any pending wait 
	mask, if applicable.

	The fcn. modifies the buffer in-place and returns 
	the number of actual data characters in pcbValid, 
	which will be less than or equal to cbTotal.

	The impetus for this fcn. is Vantive Failure # 7511.

--*/
{
	// Hmpf. 
	// These macros should be added to Firmware\Dense\Inc\sdxb_prot.h.
	#define SDXB_ERRCHAR_PREFIX				0xFF
	#define SDXB_ERRCHAR_PARITY				0x04
	#define SDXB_ERRCHAR_FRAMING			0x08
	#define SDXB_ERRCHAR_PARITY_AND_FRAMING	0x0C
	#define SDXB_ERRCHAR_BREAK				0x10

	// In the interest of code reuse, the meat of this
	// fcn. was CxPx'd from elsewhere. Unfortunately,
	// that elsewhere was FEP5...

	PASYN_SDXB pSdxbExt = &pPortExt->SdxbValues;

	PUCHAR	pDataBufferFrom;	// current read posit
	PUCHAR	pDataBufferTo;		// current write posit

	ULONG	bytesAvailable = cbTotal;

	ULONG	eventReason = 0;

	*pcbValid = 0;

	if ( 0 == bytesAvailable )
	{
		// not much to do in this case
		return;
	}

	// Note that pRxBuf->iNext has not been set yet.
	pDataBufferFrom = (PUCHAR)pRxBuf + sizeof(RX_BUF);
	pDataBufferTo = pDataBufferFrom;

	while ( 0 < bytesAvailable )
	{
		UCHAR rxByte = *pDataBufferFrom++;
		bytesAvailable--;

		// Is this the error indication byte?
		if (SDXB_ERRCHAR_PREFIX == rxByte && 0 < bytesAvailable)
		{
			// Yes -- so process the error message...

			UCHAR statusByte = *pDataBufferFrom++;
			bytesAvailable--;

			// Examine the status byte -- but remember 
			// that two consecutive SDXB_ERRCHAR_PREFIX 
			// bytes is interpreted as a literal.
			if (SDXB_ERRCHAR_PREFIX != statusByte && 0 < bytesAvailable)
			{
				// Finally -- the next byte is the 
				// data byte that was received in error.
				rxByte = *pDataBufferFrom++;
				bytesAvailable--;

				// Interpret and process the error
				if ( SDXB_ERRCHAR_BREAK == statusByte )
				{
					eventReason |= SERIAL_EV_BREAK;
					pSdxbExt->CommErr |= SERIAL_ERROR_BREAK;
				}
				else if ( SDXB_ERRCHAR_PARITY == statusByte )
				{
					eventReason |= SERIAL_EV_ERR;
					pSdxbExt->CommErr |= SERIAL_ERROR_PARITY;
					// The following is omitted; see PortOnSdxbLerr.
					//InterlockedIncrement(&pSdxbExt->PerfStats.ParityErrorCount);
				}
				else if ( SDXB_ERRCHAR_FRAMING == statusByte )
				{
					eventReason |= SERIAL_EV_ERR;
					pSdxbExt->CommErr |= SERIAL_ERROR_FRAMING;
					// The following is omitted; see PortOnSdxbLerr.
					//InterlockedIncrement(&pSdxbExt->PerfStats.FrameErrorCount);
				}
				else if ( SDXB_ERRCHAR_PARITY_AND_FRAMING == statusByte )
				{
					eventReason |= SERIAL_EV_ERR;
					pSdxbExt->CommErr |= SERIAL_ERROR_PARITY | SERIAL_ERROR_FRAMING;
					// The following is omitted; see PortOnSdxbLerr.
					//InterlockedIncrement(&pSdxbExt->PerfStats.ParityErrorCount);
					//InterlockedIncrement(&pSdxbExt->PerfStats.FrameErrorCount);
				}

				// TODO: Implement SERIAL_ERROR_ABORT processing.
				//       I.e., if a line status error occurred, we 
				//       are expected to flush the buffers and to 
				//       cancel all current read and write requests.
				/*
				if ( SERIAL_ERROR_ABORT & DeviceExt->ControlHandShake )
				{
				}
				*/
			}
		}

		// We could check for the event character,
		// but this functionality is handled via
		// messages, and the event will be signalled
		// by PortOnSdxbScrecv.
		/*
		if (pSdxbExt->PortParams.spcr == rxByte)
		{
			eventReason |= SERIAL_EV_RXFLAG;
		}
		*/

		*pDataBufferTo++ = rxByte;

		(*pcbValid)++;
	}

	if (0 < *pcbValid)
	{
		eventReason |= SERIAL_EV_RXCHAR;
	}

	if (0 != eventReason)
	{
		AsynSatisfyWait(pPortExt, eventReason, FALSE);
	}
}


STATIC VOID
QueueRcvBuffer(
	IN PASYN_READ pReadExt,
	IN PRX_BUF pRxBuf,
	IN ULONG   cbData									 
)
/*++

Description

	Called to put a receive buffer on the port's RxQ.
   
--*/
{
	ASSERT( pRxBuf != NULL );
	if ( pRxBuf == NULL )
		goto Exit;
	   
	if ( cbData == 0 )
	{
		RxEngineFreeData( pRxBuf );
		goto Exit;
	}

	// This is the only place in the driver that I (lnb)
	// think we access RX_BUF.cbData. So what's it for?
	pRxBuf->cbData = cbData;
	pRxBuf->iNext = 0;
	pRxBuf->cbRemaining = cbData;
	(void) ExInterlockedInsertTailList(&pReadExt->RxQ, &pRxBuf->ListEntry, &pReadExt->Lock.Lock);

	pReadExt->cbRxQ += cbData;

Exit:         
	;	      
}



VOID 
AsynOnData(
	IN PASYN_PORT_EXT pPortExt,
	IN PRX_BUF 			pRxBuf,
	IN ULONG				cbData
)
/*++

Description

	Called when data buffer has been received from S/DXB.
   
--*/
{
	DigiDump(DIGIREAD,("AsynOnData Entry pRxBuf=%x Data count=%x\n",pRxBuf,cbData));

	DigiAcquireSpinLock( &pPortExt->Lock );

	if (pPortExt->State != OpenState)
	{
		// Port is not open; discard the receive data
		DigiReleaseSpinLock( &pPortExt->Lock );
		RxEngineFreeData( pRxBuf );
		goto Exit;
	}

	DigiReleaseSpinLock( &pPortExt->Lock );

	{  
		PASYN_READ pReadExt = &pPortExt->Read;

		ULONG cbActualData;

		// If we were to allow the user to select btw. accurate
		// parity/break/frame error reporting and not having to
		// parse the data stream, we need to change this if 
		// test. For now, however, errors are always in the data
		// stream, so we always have to parse't.
		if ( TRUE ) // ( TRUE == m_bParseIncomingForErrorIndicators )
		{
			ParseReadDataInPlace( pPortExt, pRxBuf, cbData, &cbActualData );
		}
		else
		{
			cbActualData = cbData;
		}

		// This'll free the buffer if there's no data; 
		// otherwise, it'll queue it
		QueueRcvBuffer( pReadExt, pRxBuf, cbActualData );
        
        // Vantive 8085: Satisfy the wait after updating the current receive 
        //      queue byte count (in QueueRcvBuffer). 
        if (0 < cbActualData)
        {
            // Set SERIAL_EV_RXCHAR bit in wait mask.
            AsynSatisfyWait( pPortExt, SERIAL_EV_RXCHAR, FALSE );
        }
        
		ProcessReadQueue( pPortExt );

		pPortExt->SdxbValues.PerfStats.ReceivedCount += cbActualData;

		// There's no limitation on our receive queue size, but to prevent 
		// an infinitely large receive queue, force the port to be flow
		// controlled when the RxQ count gets larger than the queue size
		// requested by the app.
		if ( !pReadExt->fRxPaused && pReadExt->cbRxQ > pReadExt->cbMaxRxQ )
		{
			RxEnginePause( pPortExt->hRxEngine_LowPriority );
			pReadExt->fRxPaused = TRUE;
			AsynSatisfyWait( pPortExt, SERIAL_EV_RX80FULL, FALSE );
		}         
	}   	
Exit:
	;   
}       



STATIC INLINE VOID
QueueReadIrp(
	IN	PASYN_READ	pReadExt,
	IN	PIRP			pIrp 
)
/*++

Description

	Called to mark a read irp as pending and to put the irp on the ports's 
   pending queue.
   
   The port's read spinlock must be held when this routine is called.
      
--*/
{
	IoSetCancelRoutine(pIrp, AsynCancelRead);
	IoMarkIrpPending(pIrp);
	InsertTailList(&pReadExt->PendingIrps, &pIrp->Tail.Overlay.ListEntry);
}  


       
NTSTATUS
AsynDeviceRead(
	IN PDEVICE_OBJECT			pDevice,
	IN	PIRP						pIrp
)
/*++

Description

	This is the dispatch routine for read. It validates the parameters
   for the read request and if all is ok, and there are no outstanding
   read requests, it will read waiting data from S/DXB. Otherwise, it will
   queue the request to wait for incoming data.
   
--*/

{
	NTSTATUS    Status = STATUS_SUCCESS;               
	PASYN_PORT_EXT 	pPortExt = pDevice->DeviceExtension;;

	DigiDump( DIGIFLOW, ("DigiAsyn!AsynDeviceRead\n"));
	pIrp->IoStatus.Information = 0;

	// Check for a valid device object
	if (pDevice->DeviceType != FILE_DEVICE_SERIAL_PORT)
	{
		DigiDump(DIGIREAD, ("*** AsynDeviceRead: unknown device =0x%x Type=x0%x\n", 
																	pDevice, pDevice->DeviceType));
		Status = STATUS_UNSUCCESSFUL;
		goto Exit;
	}      

	ASSERT( pPortExt->Type == ASYN_TYPE_PORT_EXT );

	// Is Port open?   
	if ( pPortExt->State != OpenState )
	{
		Status = STATUS_CANCELLED;
		goto Exit;
	}
   
	{
		ULONG cbToRead = IoGetCurrentIrpStackLocation(pIrp)->Parameters.Read.Length;

		DigiDump( DIGIREAD, (" Read IRP %x received with read.length = %x \n",pIrp,cbToRead));

		// Check for a zero length read request
		if ( cbToRead == 0)
		{
			Status = STATUS_SUCCESS;
			goto Exit;
		}   		   
        
	    // Check for a valid buffer
	    if ( pIrp->AssociatedIrp.SystemBuffer == NULL )
	    {
		    Status = STATUS_INVALID_PARAMETER;
		    goto Exit;
	    }   
	}   
	
	{ 
		PASYN_READ pReadExt = &pPortExt->Read;
		  
		DigiAcquireSpinLock( &pReadExt->Lock );

		if ( IsListEmpty( &pReadExt->PendingIrps ))
		{
			// No IRPS are queued yet,
			//   copy any waiting receive data to this IRP's buffer
			CopyReadData( pReadExt, pIrp );

			if ( IsReadDone( pReadExt, pIrp ))
				Status = STATUS_SUCCESS;
			else
			{
				StartReadTimeouts( pReadExt, pIrp );
				Status = STATUS_PENDING;
			}
		}
		else
		{
			// Other IRPs are already on the pending queue,
			//   this IRP will be pended and queued after them.
			Status = STATUS_PENDING;
		}

		if ( Status == STATUS_PENDING )
			QueueReadIrp( pReadExt, pIrp );
		else
      {
			// Check whether the Rx Engine can be unpaused 
			if ( pReadExt->fRxPaused && pReadExt->cbRxQ < pReadExt->cbMaxRxQ )
			{
				RxEngineUnpause( pPortExt->hRxEngine_LowPriority );
				pReadExt->fRxPaused = FALSE;
			}            
		}			   
		DigiReleaseSpinLock( &pReadExt->Lock );
	}
Exit:   

	if (Status != STATUS_PENDING)
	{
		DigiDump( DIGIREAD, ("AsynDeviceRead: Port %d completed IRP %p with status=%x read.length=%x cbRcvQueue = %x\n",pPortExt->iSdxbPort,pIrp,Status,pIrp->IoStatus.Information,pPortExt->Read.cbRxQ));
		AsynIoCompleteRequest( pIrp, Status, pPortExt );
	}
	return Status;
}
