/*
 ****************************************************************************
 * Copyright (c) 1990-99 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 File    : read.c
 *
 * Author         : P.Donovan
 *
 * Purpose        :
 *
 *      Read (i.e. receive) routines for Asyn driver.
 *
 *----------------------------------------------------------------------
 */

#include "header.h"
/*
 *----------------------------------------------------------------------
 * Function:  AsynCancelRead.
 *
 * Purpose:
 *
 *    Cancel a read IRP.
 *
 *
 * Method:
 *
 *    o Remove IRP from the Port's internal queue.
 *    o Complete IRP.
 *
 *
 * NOTES:
 *    The Port's Spin-lock must be held when this routine is called.
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynCancelRead(
   IN PPORT_EXT         pPortExt,   // port 
   IN PIRP              pIrp        // Associated work order.
)

{
   DigiDump(DIGICANCEL|DIGIREAD, ("ASYN!CancelRead: "));

   ASSERT( pIrp->IoStatus.Status == STATUS_CANCELLED);

   //
   // Get the driver specific data structures.
   //
   DigiDump(DIGICANCEL|DIGIREAD, ("PortNo=0x%x IRP=0x%x\n", pPortExt->Port, pIrp));

   if (pIrp == pPortExt->TimedReadIrp)
   {
      pPortExt->TimedReadIrp = NULL;
      KeCancelTimer( &pPortExt->Timer.Read);

      //
      // Remove the Interval timer, too.
      //
      if (pPortExt->ReadIntervalActive > ReadIntervalIdle)
      {
         pPortExt->ReadIntervalActive = ReadIntervalIdle;
         pPortExt->ReadIntervalCount = 0;
         KeCancelTimer( &pPortExt->Timer.IntervalRead);
      }

      //
      // Ignore starting the next timer; let normal operations
      // start the timer.  Betting that the application will
      // cancel all IRPs, so don't cause more problems than it
      // is worth.
      //
   }

   //
   // Schedule the IRP for completion
   InsertTailList( &pPortExt->CompleteIrpQ, &pIrp->Tail.Overlay.ListEntry);
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynIsReadDone.
 *
 * Purpose:
 *
 *    Determine whether a read IRP is finished.
 *
 *
 * Method:
 *
 *    o Timeout values determine action to be taken; please be very careful
 *      about maintaining the comments in the following code.
 *    o Start a read timer; when appropriate.
 *    o Remove a read timer when an IRP has completed.
 *
 *
 * Returns:
 *
 *    o TRUE when the READ should be completed, FALSE otherwise.
 *
 *
 * Note:
 *
 *    o The Port SPINLOCK must be held by the caller.
 *
 *
 *----------------------------------------------------------------------
 */
static BOOLEAN
AsynIsReadDone(
   IN PPORT_EXT      pPortExt,      // Port specific information.
   IN PIRP           pIrp,          // Read request to perform.
   IN ULONG          IrpBytes       // Bytes stored in IRP's buffer.
)

{
   LARGE_INTEGER     ReadTimeout;   // Timeout value.
   ULONG             Multiplier;    // Timeout multiplier.
   ULONG             ConstantTime;  // Timeout constant.
   ULONG             Interval;      // Timeout interval.
   BOOLEAN           bDone;         // Read IRP completion status.
   BOOLEAN           bSetTimer;     // Compute the timeout.
   PIO_STACK_LOCATION pIrpSp;       // Current IRP stack position.

   DigiDump(DIGIREAD, ("ASYN!IsReadDone: Irp=0x%x Cancel=%d TimedIrp=0x%x\n",
                        pIrp, pIrp->Cancel, pPortExt->TimedReadIrp));

   if (pIrp->Cancel)
   {
      return (TRUE);
   }
   //
   // Do not restart the timer!
   //
   else if (pPortExt->TimedReadIrp)
   {
      return (FALSE);
   }

   bDone = FALSE;
   bSetTimer = FALSE;

   Interval = pPortExt->TimeoutValues.ReadIntervalTimeout;
   Multiplier = pPortExt->TimeoutValues.ReadTotalTimeoutMultiplier;
   ConstantTime = pPortExt->TimeoutValues.ReadTotalTimeoutConstant;

   pIrpSp = IoGetCurrentIrpStackLocation( pIrp);

   //
   // Check for special return conditions.
   //
   if (Interval == MAXULONG)
   {
      //
      // 1) If constant and multiplier are a zero value
      //    return immediately with whatever data is present.
      //
      if ((Multiplier == 0) && (ConstantTime == 0))
      {
         bDone = TRUE;
      }
      else if (ConstantTime != MAXULONG)
      {
         //
         // 2) If constant and multiplier are not MAXULONG then return
         //    if any characters are present;
         //    otherwise, use the timeouts as specified.
         //
         if (Multiplier != MAXULONG)
         {
            if (IrpBytes)
               bDone = TRUE;
            else
               bSetTimer = TRUE;
         }
         //
         // 3) If multiplier is MAXULONG then return if any
         //    characters are present OR when the first character
         //    arrives.
         //
         else
         {
            if (IrpBytes)
               bDone = TRUE;
            else
            {
               DigiDump(DIGIREAD, ("ASYN!IsReadDone: Wait for first char!\n"));
               pIrpSp->Parameters.Read.Length = 1;

               Multiplier = 0;
               bSetTimer = TRUE;
            }
         }
      }
      else
      {
         //
         // Infinite timeout value is the same as not having a timeout!
         //
      }
   }
   else if ((Multiplier) || (ConstantTime))
   {
      bSetTimer = TRUE;
   }

   if (bSetTimer)
   {
      //
      // Compute the timeout based upon the remaining bytes to be received.
      //
      ReadTimeout.QuadPart =
         ((LONGLONG)(UInt32x32To64( pIrpSp->Parameters.Read.Length, Multiplier)
               + ConstantTime)) * -1 * MILLISECOND;

      pPortExt->TimedReadIrp = pIrp;
      KeSetTimer( &pPortExt->Timer.Read, ReadTimeout, &pPortExt->Timer.ReadDpc);
   }

   if ((Interval != MAXULONG) && (Interval) && (!bDone))
   {
      ReadTimeout.QuadPart = UInt32x32To64( Interval, MILLISECOND) * -1;

      if (pPortExt->TimedReadIrp)
      {
         pPortExt->ReadIntervalActive = ReadDurationTimeout;
      }
      else
      {
         pPortExt->ReadIntervalActive = ReadIntervalTimeout;
         pPortExt->TimedReadIrp = pIrp;
      }

      pPortExt->ReadIntervalCount = pIrp->IoStatus.Information;
      KeSetTimer( &pPortExt->Timer.IntervalRead, ReadTimeout,
                  &pPortExt->Timer.IntervalReadDpc);

      pPortExt->IntervalTimeToUse.QuadPart = ReadTimeout.QuadPart;
   }

   return (bDone);
}

/*
 *----------------------------------------------------------------------
 * Function: AsynQueueRead.
 *
 * Purpose:
 *
 *    Read data from the S/DXB module.
 *
 *
 * Method:
 *
 *    o Get the driver specific CardExt and Port values.
 *    o Queue the request; this may also send some data.
 *
 *
 * Returns:
 *
 *    o STATUS_PENDING or a pertinent error status is returned.
 *
 *
 *----------------------------------------------------------------------
 */
static NTSTATUS
AsynQueueRead(
    IN PPORT_EXT     pPortExt,      // Port info
    IN PIRP          pIrp           // Associated work order.
)

{
   PCARD_EXT   pCardExt;               // Driver specific information.
   KIRQL       CurrentIrql;               // Save prior irq level.
   NTSTATUS    Status;                 // Called function return value.
   ULONG       IrpReadBytes;
   ULONG       CopyBytes;
   PRX_BUF     pRxBuf;                 // Pre-allocated receive buffer
   PSERIAL_TIMEOUTS pTimeouts;
   PIO_STACK_LOCATION
               pIrpSp;              // Current IRP stack position.
   PUCHAR      pIrpBuffer;          // IRP Data buffer.

   //
   // Other IRPs ahead of this IRP.
   //
   if (! IsListEmpty( &pPortExt->ReadQueue))
   {
       //
       // Queue the READ IRP.
       //
       InsertTailList( &pPortExt->ReadQueue, &pIrp->Tail.Overlay.ListEntry);

       return (STATUS_PENDING);
   }
   pCardExt = pPortExt->CardExt;

   pIrpSp = IoGetCurrentIrpStackLocation( pIrp);
   IrpReadBytes = pIrpSp->Parameters.Read.Length;

   pIrpBuffer = (PUCHAR) pIrp->AssociatedIrp.SystemBuffer;

   //
   // Data is waiting for an IRPs arrival.
   //
   while ((pPortExt->TotalRxBytesBuffered) && (IrpReadBytes))
   {
      DigiDump(DIGIREAD, ("ASYN!QueueRead: BUFFER bytes 0x%x\n", pPortExt->TotalRxBytesBuffered));

      pRxBuf = (PRX_BUF)pPortExt->RxQ.Flink;
      ASSERT(pRxBuf != (PRX_BUF)&pPortExt->RxQ);

      //
      // MIN(bytes available in First buffer, space in READ IRP buffer)
      //
      CopyBytes = pRxBuf->Len;
      if ( (CopyBytes == 0) && (pPortExt->RxBufCount <= 1) )
      {
         PORT_ERROR(pPortExt);
         DigiDump(DIGIALL, ("ASYN!QueueRead: CopyBytes=0 B.Len=0x%x B.Start=0x%x\n",
                            pRxBuf->Len, pRxBuf->Start));
         // Attempt to correct situation
         DbgBreakPoint();
         pPortExt->TotalRxBytesBuffered = 0;
      }

      if (CopyBytes > IrpReadBytes)
         CopyBytes = IrpReadBytes;

      RtlCopyMemory( pIrpBuffer, &pRxBuf->Data[ pRxBuf->Start ], CopyBytes);

      //
      // If we have finished reading a buffer, recycle that Read Buffer, 
      // with the proviso that we always want to keep at least one buffer,
      // namely the last buffer.
      //
      if ( (pRxBuf != (PRX_BUF)pPortExt->RxQ.Blink) &&
           (CopyBytes == pRxBuf->Len) )
      {
         ASSERT((pPortExt->RxBufCount != 1) && (pPortExt->RxBufCount != 0));

         RemoveEntryList( &pRxBuf->ListEntry);

         pRxBuf->Start = 0;
         pRxBuf->Len = 0;
         if (pRxBuf->Size == DEF_RX_BUF_SIZE)
         {
            AsynFreeEntry( &pPortExt->ReadBufferPool, &pRxBuf->ListEntry);
         }
         else
         {
            AsynFreeEntry( &pPortExt->VariableReadBufferPool, &pRxBuf->ListEntry);
         }
         pPortExt->RxBufCount--;

         //
         // There should be room to accept more date now.
         //
         if ( pPortExt->OpenStateMask & RxDataFullMask )
         {
            PPUT_MSG_EV pPutEvent = NULL;
            pPortExt->OpenStateMask &= ~RxDataFullMask;
            
            // We're in a state s.t. RxDataFullMask was TRUE, which means we've 
            // stopped sending CData GetMsg request to keep from receiving data.  Now,
            // we're ready to receive more data.  We cannot simply send a new GetMsg 
            // from here, so we need a way to kickstart the receive engine again.  Do 
            // it by sending down a SYNC command, which causes the card to reply with 
            // a sync response at which time we look at the DataFullMask again and 
            // see that it's time to send another data GetMsg.  Note: the 
            // REASON_UNPAUSE does nothing except to help cleanup the request when 
            // the PutMsg completes!
            
            // In standard-style, don't hold the port lock while sending a message.
            KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql );
            (void)AsynPutEventMsg( pPortExt, &pPutEvent, NULL, 0, 
                                   REASON_UNPAUSE, DS_PRIORITY_LOW, 
                                   PutAwaitingCompletion, AsynBuildSyncCmd );
            KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);
         }
      }
      else
      {
         pRxBuf->Start += CopyBytes; 
         pRxBuf->Len -= CopyBytes;
      }
      pPortExt->TotalRxBytesBuffered -= CopyBytes;
      pIrp->IoStatus.Information += CopyBytes;

      pIrpBuffer += CopyBytes;
      IrpReadBytes -= CopyBytes;
      pPortExt->TotalReadBytes += CopyBytes;
   }

   //
   // Do completion processing.
   //
   if ((IrpReadBytes == 0) ||
       (AsynIsReadDone( pPortExt, pIrp, pIrp->IoStatus.Information)))
   {
      Status = STATUS_SUCCESS;
      DigiDump(DIGIREAD, ("ASYN!QueueRead: Read %d bytes IoStatus=0x%x Status=0x%x\n", 
            pIrp->IoStatus.Information, pIrp->IoStatus.Status, Status));
   }
   else
   {
      //
      // Queue the READ IRP.
      //
      InsertHeadList( &pPortExt->ReadQueue, &pIrp->Tail.Overlay.ListEntry);
      Status = STATUS_PENDING;
   }

   return (Status);
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynPortRead.
 *
 * Purpose:
 *
 *    Transmit data to the S/DXB module.
 *
 *
 * Method:
 *
 *    o Get the driver specific CardExt and Port values.
 *    o Send DsPutMsgEx(DATA) messages followed by one DsPutMsgEx(SYNC) to 
 *      S/DXB.
 *
 *
 * Returns:
 *
 *    o STATUS_PENDING or a pertinent error status is returned.
 *
 * NOTES:   The Port Spin-Lock must be held prior to calling this routine.
 *
 *----------------------------------------------------------------------
 */
NTSTATUS
AsynPortRead(
   PPORT_EXT   pPortExt,               // Port specific information.
   IN PIRP     pIrp                    // Associated work order.
)
{
   PCARD_EXT   pCardExt;               // Driver specific information.
   KIRQL       CurrentIrql;            // Save prior irq level.
   NTSTATUS    Status;                 // Called function return value.
   LIST_ENTRY  *p;                     // LIST_ENTRY intermediate value.
   ULONG       TotalWriteBytes;        // Max. bytes to write for IRP.
   PPUT_MSG_EV pPutEvent = NULL;
   ULONG       BytesWritten;
   ULONG       ThisWrite;
   PSERIAL_TIMEOUTS pTimeouts;

   DigiDump(DIGIREAD, ("ASYN!PortRead: Port=%d Irp=0x%x\n", 
                        pPortExt->Port, pIrp));

   Status = AsynQueueRead( pPortExt, pIrp);
   pIrp->IoStatus.Status = Status;

   if (Status != STATUS_PENDING)
   {
      InsertTailList( &pPortExt->CompleteIrpQ, &pIrp->Tail.Overlay.ListEntry);
   }
   return Status;
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynProcessData
 *
 * Purpose:
 *    Process an S/DXB Data indication
 *
 *
 *  Method:
 *    o Copy Received data into Port's read irp buffer.
 *    o If Wait Event Mask is set check completion of a pending wait.
 *      In particular we are looking for Waits on receive data or 80% buffer full.
 *
 *
 *  Returns:
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynProcessData(
   IN PPORT_EXT   pPortExt,
   IN PUCHAR      Data,
   IN ULONG       Length,
   IN PRX_BUF		pRxBuf
)
{
   PIRP        pIrp;
   ULONG       DataBytes;
   PUCHAR      pIrpBuffer;          // IRP Data buffer.
   PIO_STACK_LOCATION
               pIrpSp;              // Current IRP stack position.
   KIRQL       CurrentIrql;            // Save prior irq level.
   ULONG       CopyBytes;

   DataBytes = Length;

   // We must have a receive buffer 
   ASSERT( pRxBuf );
   // This receive packet must be within the scope of the buffer
   ASSERT( (Data >= (PUCHAR)(pRxBuf->Data + pRxBuf->Start)) &&
           (Data <= (PUCHAR)(pRxBuf->Data + pRxBuf->Size + pRxBuf->Len)) );
   if ((Data < (PUCHAR)(pRxBuf->Data + pRxBuf->Start)) ||
       (Data > (PUCHAR)(pRxBuf->Data + pRxBuf->Size + pRxBuf->Len)) )
   {
      PORT_ERROR(pPortExt);
      DigiDump(DIGIALL, ("ASYN!ProcessData: Data=0x%x RxQ->Data=0x%x RxQ->Start=0x%x\n",
                         Data, 
                         pRxBuf->Data,
                         pRxBuf->Start));
   }
#if DBG   
	if ( pRxBuf == (PRX_BUF)pPortExt->RxQ.Blink )
   {
   	// These are only valid of data is received from s/dxb.
	   ASSERT( pPortExt->Debug.LastGetMsgData == Data);
	   ASSERT( pPortExt->Debug.LastGetMsgLen >= Length);
	}      
#endif
   ASSERT( (Data + Length) <= pRxBuf->Data + pRxBuf->Size);

   DigiDump(DIGISDXB, ("ASYN!SDXB: RX Port=%d DATA Len=0x%x\n",
                         pPortExt->Port, Length));                         
   if (pPortExt->OpenStateMask & RxDataPurgeMask)
   {
      //
      // In the Purge or Flush Data state, ignore data.
      //
      return;
   }

   if (pPortExt->WaitMask & SERIAL_EV_RXCHAR)
   {
      pPortExt->HistoryMask |= SERIAL_EV_RXCHAR;
   }
   if (pPortExt->CardExt->RunTimeOptions & RUNTIME_RXFLAG_IN_DRIVER)
   {
      // The run-time option indicates the driver detects the Special char.
      // Normally this is done in the FEP's S/DXB, but let's do what the 
      // run-time options says.
      if ( (pPortExt->WaitMask & SERIAL_EV_RXFLAG) && 
           ~(pPortExt->OpenStateMask & SpecialCharRxedMask) )
      {
         PUCHAR   FoundChar;
   
         // Scan the buffer for the data.
         FoundChar = memchr( Data, pPortExt->SpecialChars.EventChar, Length);
         if (FoundChar)
         {
            DigiDump(DIGIWAIT, ("ASYN!ProcessData: Found Spx=0x%x the hard way!\n",
               pPortExt->SpecialChars.EventChar));
            pPortExt->HistoryMask |= SERIAL_EV_RXFLAG;
         }
      }
   }

   //
   // Did we receive the Special Character Event ?
   //
   if (pPortExt->OpenStateMask & SpecialCharRxedMask)
   {
      if (pPortExt->WaitMask & SERIAL_EV_RXFLAG)
      {
         if (*Data == pPortExt->SpecialChars.EventChar)
         {
            pPortExt->HistoryMask |= SERIAL_EV_RXFLAG;
         }
         else
         {
            PORT_ERROR(pPortExt);
            DigiDump(DIGIALL, ("*** ASYN!ProcessData: RxChar=0x%x SpecialChar=0x%x\n",
               *Data, pPortExt->SpecialChars.EventChar));
         }
      }
      pPortExt->OpenStateMask &= ~SpecialCharRxedMask;
   }

   if (pPortExt->WaitMaskIrp)
   {
       AsynCheckWaitEvents( pPortExt);
   }
   //
   // If there is a user buffer waiting update it
   //
   while ( !IsListEmpty( &pPortExt->ReadQueue) && DataBytes)
   {
      //
      // Get the IRP to process.
      //
      pIrp = CONTAINING_RECORD( pPortExt->ReadQueue.Flink,
                                IRP, Tail.Overlay.ListEntry);

      pIrpSp = IoGetCurrentIrpStackLocation( pIrp);

      //
      // MIN(bytes available, end of inbound buffer)
      //
      CopyBytes = pIrpSp->Parameters.Read.Length - pIrp->IoStatus.Information;
      if (CopyBytes > DataBytes)
         CopyBytes = DataBytes;

      pIrpBuffer = (PUCHAR)pIrp->AssociatedIrp.SystemBuffer +
                     pIrp->IoStatus.Information;

      RtlCopyMemory( pIrpBuffer, Data + (Length - DataBytes), CopyBytes);
      pIrp->IoStatus.Information += CopyBytes;
      DataBytes -= CopyBytes;

      //
      // Is the Read IRP full ?
      //
      if ( (pIrp->IoStatus.Information == pIrpSp->Parameters.Read.Length) ||
            (AsynIsReadDone( pPortExt, pIrp, pIrp->IoStatus.Information)) )
      {
         pIrp->IoStatus.Status = STATUS_SUCCESS;

         //
         // Must clean up after ourselves.
         //
         if (pPortExt->TimedReadIrp)
         {
            pPortExt->TimedReadIrp = NULL;
            KeCancelTimer( &pPortExt->Timer.Read);

            //
            // Remove the Interval timer, too.
            //
            if (pPortExt->ReadIntervalActive > ReadIntervalIdle)
            {
               pPortExt->ReadIntervalActive = ReadIntervalIdle;
               pPortExt->ReadIntervalCount = 0;
               KeCancelTimer( &pPortExt->Timer.IntervalRead);
            }
         }

         RemoveHeadList( &pPortExt->ReadQueue);

         DigiDump(DIGIREAD, ("ASYN!ProcessData: READ complete Port=%d bytes:0x%x\n",
                             pPortExt->Port, pIrp->IoStatus.Information));

         InsertTailList( &pPortExt->CompleteIrpQ, 
                         &pIrp->Tail.Overlay.ListEntry);
      }
   }
   pPortExt->TotalReadBytes += Length - DataBytes;

   if (pPortExt->WaitMaskIrp)
   {
       AsynCheckWaitEvents( pPortExt);
   }
   //
   // There is not an Irp available to receive all the data.
   // Buffer it so that later on we can pass it to the user.
   //
   if (DataBytes)
   {
      // Update the Rx Buffer length
      pRxBuf->Len += DataBytes;
      pRxBuf->Start += Length - DataBytes;
      pPortExt->TotalRxBytesBuffered += DataBytes;
   }
#if DBG
   {
      ASSERT( pRxBuf->Start + pRxBuf->Len <= pRxBuf->Size);
      if ( pRxBuf->Start + pRxBuf->Len > pRxBuf->Size)
      {
         PORT_ERROR(pPortExt);
         DigiDump(DIGIALL, ("*** ASYN!ProcessData: Length=0x%x DataBytes=0x%x B.Len=0x%x B.Start=0x%x\n",
                            Length, DataBytes, pRxBuf->Len, pRxBuf->Start));
      }
   }
#endif
   //
   // Update Performance Statistics as necessary
   //
   pPortExt->PerfStats.ReceivedCount += Length;
}


/*
 *----------------------------------------------------------------------
 * Function:  AsynReadTimedOut.
 *
 * Purpose:
 *
 *    Complete the timed out Read IRP.
 *
 *
 * Method:
 *
 *    o Perform the necessary completion actions.
 *    o Start the next queued read IRP timer; when appropriate.
 *
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynReadTimedOut(
   IN PKDPC       pPortDpc,      // DPC object for Port.
   IN PPORT_EXT   pPortExt,      // Port specific information.
   IN PVOID       SystemArg1,    // System argument.
   IN PVOID       SystemArg2     // System argument.
)

{
   PLIST_ENTRY p;                // LIST_ENTRY intermediate value.
   PIRP        pIrp;             // Timed out IRP to finish.
   PIRP        pNewIrp;          // New Read IRP.
   KIRQL       CurrentIrql;      // Save prior irq level.
   PIO_STACK_LOCATION
               pIrpSp;           // Current IRP stack position.

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

   KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);

   //
   // Insure that the timeout is justified; rather than race on a completed
   // read IRP.
   //
   if ((pPortExt->TimedReadIrp == NULL) || (IsListEmpty( &pPortExt->ReadQueue)))
   {
      DigiDump(DIGIREAD | DIGITIMEOUT, ("ASYN!ReadTimedOut: Port=%d Race Condition, Ignore\n",
                                       pPortExt->Port));
   
      KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
      return;
   }

   //
   // Cancel the read interval timer.
   //
   if ( pPortExt->ReadIntervalActive > ReadIntervalIdle)
   {
      pPortExt->ReadIntervalActive = ReadIntervalIdle;
      pPortExt->ReadIntervalCount = 0;
      KeCancelTimer( &pPortExt->Timer.IntervalRead);
   }

   p = RemoveHeadList( &pPortExt->ReadQueue);
   pIrp = CONTAINING_RECORD( p, IRP, Tail.Overlay.ListEntry);
   pIrpSp = IoGetCurrentIrpStackLocation( pIrp);

   //
   // When the IRP data buffer is completely full and a timeout has occurred, 
   // treat it as a successful completion of the IRP.
   //
   if (pIrp->IoStatus.Information == pIrpSp->Parameters.Read.Length)
   {
      pIrp->IoStatus.Status = STATUS_SUCCESS;
      DigiDump(DIGIREAD | DIGITIMEOUT, ("ASYN!ReadTimedOut: Port=%d completed IRP=0x%x\n", 
                                       pPortExt->Port, pIrp));
   }
   else
   {
      pIrp->IoStatus.Status = STATUS_TIMEOUT;
      DigiDump(DIGIREAD | DIGITIMEOUT, ("ASYN!ReadTimedOut: Port=%d Timeout IRP=0x%x Len=0x%x WaitLen=0x%x\n", 
                                       pPortExt->Port, pIrp, 
                                       pIrp->IoStatus.Information,
                                       pIrpSp->Parameters.Read.Length));
   }

   pPortExt->TimedReadIrp = NULL;

   InsertTailList( &pPortExt->CompleteIrpQ, &pIrp->Tail.Overlay.ListEntry);

   //
   // Must start a timer for the next queued Read IRP.
   //
   while ((pPortExt->TimedReadIrp == NULL) &&
      (! IsListEmpty(&pPortExt->ReadQueue)))
   {
      p = RemoveHeadList( &pPortExt->ReadQueue);
      pNewIrp = CONTAINING_RECORD( p, IRP, Tail.Overlay.ListEntry);

      //
      // The queued Read IRP needs immediate completion!
      //
      if (AsynIsReadDone( pPortExt, pNewIrp, pNewIrp->IoStatus.Information))
      {
         DigiDump(DIGIREAD | DIGITIMEOUT, ("ASYN!ReadTimedOut: Read Completed IRP=0x%x\n", pNewIrp));

         pNewIrp->IoStatus.Status = STATUS_SUCCESS;

         DigiDump(DIGITIMEOUT|DIGIREAD, ("ASYN!ReadTimedOut:"
                                         "Place New Irp=0x%x in Q.", pNewIrp));
         InsertTailList( &pPortExt->CompleteIrpQ, &pNewIrp->Tail.Overlay.ListEntry);
      }
      else
      {
         //
         // May or may not be using a timer!
         //
         InsertHeadList( &pPortExt->ReadQueue, &pNewIrp->Tail.Overlay.ListEntry);
         break;
      }
   }
   if (!pPortExt->PortBusy)
   {
      DigiDump(DIGIDISPATCH, ("ASYN!ReadTimedOut: +++[%d]Entry P=%d Irp=0x%x\n", 
                  pPortExt->PortBusy, pPortExt->Port, pIrp));
      AsynSchedulerLoop( pPortExt);
      DigiDump(DIGIDISPATCH, ("ASYN!ReadTimedOut: ---[%d]Exit P=%d\n", 
                        pPortExt->PortBusy, pPortExt->Port));
   }
   KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
}
/*
 *----------------------------------------------------------------------
 * Function:  AsynReadIntervalTimedOut.
 *
 * Purpose:
 *
 *    Complete the timed out Read IRP.
 *
 *
 * Method:
 *
 *    o Restart the timer when no data bytes have been received.
 *    o When data is flowing in, restart the timer.
 *    o When data is no longer flowing in, complete the IRP.
 *
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynReadIntervalTimedOut(
   IN PKDPC            pPortDpc,       // DPC object for Port.
   IN PPORT_EXT        pPortExt,       // Port specific information.
   IN PVOID            SystemArg1,     // System argument.
   IN PVOID            SystemArg2      // System argument.
)

{
   LIST_ENTRY  *p;                     // LIST_ENTRY intermediate value.
   PIRP        pIrp;                   // Timed out IRP to finish.
   PIRP        pNewIrp;                // New Read IRP.
   KIRQL       CurrentIrql;            // Save prior irq level.

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

   DigiDump(DIGIREAD | DIGITIMEOUT1, ("ASYN!ReadIntervalTimedOut port=%d\n", pPortExt->Port));

   KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);

   //
   // Insure that the timeout is justified; rather than race on a completed
   // read IRP.
   //
   if ((pPortExt->TimedReadIrp == NULL) ||
      ( IsListEmpty( &pPortExt->ReadQueue)))
   {
      KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
      return;
   }

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

   DigiDump(DIGIREAD | DIGITIMEOUT1, ("ASYN!ReadIntervalTimedOut: IntervalCount=0x%x IoStatus.Info=0x%x \n", 
                        pPortExt->ReadIntervalCount, 
                        pIrp->IoStatus.Information));
   if ((pPortExt->ReadIntervalCount == 0) ||
       (pIrp->IoStatus.Information > pPortExt->ReadIntervalCount))
   {
      pPortExt->ReadIntervalCount = pIrp->IoStatus.Information;

      //
      // No data has been received, restart the timer.
      //
      InsertHeadList( &pPortExt->ReadQueue, &pIrp->Tail.Overlay.ListEntry);
      KeSetTimer( &pPortExt->Timer.IntervalRead, pPortExt->IntervalTimeToUse, 
                  &pPortExt->Timer.IntervalReadDpc);

   }
   else
   {
      DigiDump(DIGIREAD | DIGITIMEOUT1, ("ASYN!ReadIntervalTimedOut: completed IRP=0x%x\n", pIrp));

      //
      // Cancel the normal read timer.
      //
      if (pPortExt->ReadIntervalActive == ReadDurationTimeout)
      {
         KeCancelTimer( &pPortExt->Timer.Read);
      }

      pPortExt->ReadIntervalActive = ReadIntervalIdle;
      pPortExt->ReadIntervalCount = 0;
      pIrp->IoStatus.Status = STATUS_TIMEOUT;

      pPortExt->TimedReadIrp = NULL;

      InsertTailList( &pPortExt->CompleteIrpQ, &pIrp->Tail.Overlay.ListEntry);

      //
      // Must start a timer for the next queued Read IRP.
      //
      while ((pPortExt->TimedReadIrp == NULL) &&
             (! IsListEmpty(&pPortExt->ReadQueue)))
      {
         p = RemoveHeadList( &pPortExt->ReadQueue);
         pNewIrp = CONTAINING_RECORD( p, IRP, Tail.Overlay.ListEntry);

         //
         // The queued Read IRP needs immediate completion!
         //
         if (AsynIsReadDone( pPortExt, pNewIrp, pNewIrp->IoStatus.Information))
         {
            DigiDump(DIGIREAD | DIGITIMEOUT, ("ASYN!ReadIntervalTimedOut: READ completed Port=%d IRP=0x%x\n",
                                 pPortExt->Port, pNewIrp));

            pNewIrp->IoStatus.Status = STATUS_SUCCESS;

            DigiDump(DIGITIMEOUT|DIGIREAD, ("ASYN!ReadIntervalTimedOut: "
                                            "Place New Irp=0x%x in Q.", pNewIrp));
            InsertTailList( &pPortExt->CompleteIrpQ, &pNewIrp->Tail.Overlay.ListEntry);
         }
         else
         {
            //
            // May or may not be using a timer!
            //
            InsertHeadList( &pPortExt->ReadQueue,
                            &pNewIrp->Tail.Overlay.ListEntry);
            break;
         }
      }
      if (!pPortExt->PortBusy)
      {
         DigiDump(DIGIDISPATCH, ("ASYN!ReadIntervalTimedOut: +++[%d]Entry P=%d Irp=0x%x\n", 
                     pPortExt->PortBusy, pPortExt->Port, pIrp));
         AsynSchedulerLoop( pPortExt);
         DigiDump(DIGIDISPATCH, ("ASYN!ReadIntervalTimedOut: ---[%d]Exit P=%d\n", 
                           pPortExt->PortBusy, pPortExt->Port));
      }
   }
   KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
}

