/*++
 ****************************************************************************
 * 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
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;
    }

    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;

        // Set SERIAL_EV_RXCHAR bit in wait mask.
        AsynSatisfyWait( pPortExt, SERIAL_EV_RXCHAR, FALSE );

        QueueRcvBuffer( pReadExt, pRxBuf, cbData  );

        ProcessReadQueue( pPortExt );

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

        // 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"));

    SRAPRINTIRPEX(DBGTYPE_IRP,DBGLVL_METRICS,("COM",pDevice,pIrp,FALSE));

    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;
    }      

    // Check for a valid buffer
    if ( pIrp->AssociatedIrp.SystemBuffer == NULL )
    {
        Status = STATUS_INVALID_PARAMETER;
        goto Exit;
    }   

    ASSERT( pPortExt->Type == ASYN_TYPE_PORT_EXT );

    // Is Port open?   
    if ( pPortExt->State != OpenState )
    {
        Status = STATUS_CANCELLED;
        goto Exit;
    }
   
    // has port failed?
    if (pPortExt->DeviceStateFlags & PNP_DEVICE_FAILED)
    {
        Status = STATUS_IO_DEVICE_ERROR;
        DigiDump( DIGIREAD, ("In AsynDeviceRead port has failed\n" ));
        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;
        }   		   
    }   
	
    { 
        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)
        // Now Irp is queued - check if adapter has failed and if so
        // cancel all outstanding IO
        // NOTE: This is done AFTER queueing the Irp for a very good
        //       reason! To check before in a synchronized way, we'd
        //       have to acquire and hold the device extension spinlock
        //       across the queue call - I dont like holding multiple
        //       locks because it's an invitation to deadlock city.
        //       Instead, we take the optimistic approach and check in
        //       an unsynchronized manner before and again after the fact. 
        //       No need to acquire the spinlock to do the check because 
        //       the place that SETS the flag also cancels outstanding IO
        AsynCheckForPortFailure(pPortExt);
    else
    {
        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;
}
