/*
 *****************************************************************************
 * 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    :  utils.c
 *
 * Author         : P.Donovan
 *
 * Purpose:
 *
 *    General utility functions used by Asyn driver.
 *
 *----------------------------------------------------------------------
 */

#include "header.h"

/*
 *----------------------------------------------------------------------
 *  Function:  AsynUniStringInit.
 *
 *  Purpose:
 *
 *      Create a unicode string and initialize it.
 *
 *
 *  Method:
 *
 *      o Initialize the UNICODE_STRING.
 *      o Allocate a non-paged poll buffer for the string.
 *
 *
 *  Returns:
 *
 *      o STATUS_SUCCESS or STATUS_INSUFFICIENT_RESOURCES is returned.
 *
 *
 *----------------------------------------------------------------------
 */
NTSTATUS
AsynUniStringInit(
   IN PUNICODE_STRING  BaseString,   // Unicode string structure.
   IN USHORT           BaseSize      // Max. length of base string.
)

{
   /*
    * Set the unicode string to a "NULL" string.
    */
   BaseString->Length = 0;

   /*
    * Add room for the NULL termination character(s).
    */
   BaseString->MaximumLength = BaseSize + sizeof(WCHAR);

   /*
    * Allocate the buffer from non-paged pool.
    */
   BaseString->Buffer = DigiAllocMem(NonPagedPool, BaseString->MaximumLength);

   /*
    * Error return when insufficient memory exists.
    */
   if (BaseString->Buffer == NULL)
   {
      return (STATUS_INSUFFICIENT_RESOURCES);
   }

   /*
    * Zero the Buffer; this will always insure that the buffer can
    ** be used as a null terminated string.
    */
   RtlZeroMemory(BaseString->Buffer, BaseString->MaximumLength);

   return (STATUS_SUCCESS);
}

/*
 *----------------------------------------------------------------------
 *  Function:  AsynUniStringInitBuffer.
 *
 *  Purpose:
 *
 *    Create a unicode string and initialize it using the user supplied
 *    buffer.
 *
 *
 *  Method:
 *
 *    o Initialize the UNICODE_STRING.
 *
 *
 *  Returns:
 *
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynUniStringInitBuffer(
   IN PUNICODE_STRING   BaseString, // Unicode string structure.
   IN USHORT            BaseSize,   // Max. length of base string.
   IN PWSTR             Buffer      // User supplied string buffer.
)
{
   /*
    * Set the unicode string to a "NULL" string.
    */
   BaseString->Length = 0;
   BaseString->MaximumLength = BaseSize;
   BaseString->Buffer = Buffer;

   /*
    * Zero the Buffer; this will always insure that the buffer can
    * be used as a null terminated string.
    */
   RtlZeroMemory(BaseString->Buffer, BaseString->MaximumLength);
}

/*
 *----------------------------------------------------------------------
 *  Function:  AsynInitPool.
 *
 *  Purpose:
 *
 *      
 *
 *
 *  Method:
 *
 *
 *
 *  Returns:
 *
 *
 *----------------------------------------------------------------------
 */
NTSTATUS
AsynInitPool(
   PPOOL_LIST  pPool,                  // Initiaized Queue head
   USHORT      Size                    // Size of each decriptor element
   )
{
	InitializeListHead( &pPool->List);
   pPool->Size = Size;
   pPool->HighTide = 0;
   pPool->LowTide = 0;
   pPool->Count = 0;
   KeInitializeSpinLock(&pPool->PoolLock);

   return (STATUS_SUCCESS);
}

/*
 *----------------------------------------------------------------------
 *  Function:  AsynAllocPool.
 *
 *  Purpose:
 *
 *      Allocate a pool of the specified descriptors
 *
 *
 *  Method:
 *
 *       o Allocate a descriptor
 *       o Add to queue
 *
 *
 *  Returns:
 *
 *       o Error status
 *
 *
 *----------------------------------------------------------------------
 */
NTSTATUS
AsynAllocPool(
   PPOOL_LIST  pPool,                   // Initiaized Queue head
   USHORT      Count,                  // Number of descriptors to allocate
   USHORT      Size                    // Size of each decriptor element
   )
{
   PLIST_ENTRY pEntry;
   USHORT i;                           // Count


   for (i=0; i<Count; i++)
   {
      pEntry = (PLIST_ENTRY)DigiAllocMem( NonPagedPool, Size);
      if ( !pEntry)
      {
         return (STATUS_UNSUCCESSFUL);
      }
      InitializeListHead( pEntry);
#ifdef DBG
      ((PASYN_LIST)pEntry)->EntryState = EntryFree;
#endif
      InsertTailList( &pPool->List, pEntry);
      pPool->Count++;
   }
   
   if (pPool->HighTide < pPool->Count)
   {
      pPool->HighTide = pPool->Count;
   }
   pPool->LowTide = pPool->Count;
   pPool->Size = Size;

   return (STATUS_SUCCESS);
}

/*
 *----------------------------------------------------------------------
 *  Function:  AsynFreePool.
 *
 *  Purpose:
 *
 *      Free pool of the descriptors
 *
 *
 *  Method:
 *
 *       o Free each descriptor until Queue is empty
 *
 *
 *  Returns:
 *
 *       o None
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynFreePool(
   PPOOL_LIST  pPool                    // Initiaized Pool
   )
{
   PLIST_ENTRY pEntry;

   while (!IsListEmpty( &pPool->List))
   {
      pEntry = RemoveHeadList( &pPool->List);
#ifdef DBG
      ASSERT(((PASYN_LIST)pEntry)->EntryState == EntryFree);
#endif
      DigiFreeMem( pEntry);
      pPool->Count--;
   }
   pPool->HighTide = 0;
   pPool->LowTide = 0;
//   pPool->Size = 0;
}

/*
 *----------------------------------------------------------------------
 *  Function:  AsynAllocEntry
 *
 *  Purpose:
 *
 *      Allocate an entry from a pool of entries
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *
 *    STATUS_SUCCESS 
 *    STATUS_UNSUCCESSFUL
 *
 *----------------------------------------------------------------------
 */
PLIST_ENTRY 
AsynAllocEntry( 
   PPOOL_LIST  pPool,                   // Pool list
   PNTSTATUS   pStatus
)
{
   PLIST_ENTRY pEntry;                  // Output entry
   KIRQL       PoolIrql;

   pEntry = NULL;
   *pStatus = STATUS_SUCCESS;

   KeAcquireSpinLock(&pPool->PoolLock, &PoolIrql);
   do
   {
      if (pPool->Count)
      {
         pEntry = RemoveHeadList( &pPool->List);
#ifdef DBG
         ASSERT(((PASYN_LIST)pEntry)->EntryState == EntryFree);
         ((PASYN_LIST)pEntry)->EntryState = EntryAllocated;
#endif
         pPool->Count--;
      }
       //
      // Ran out of Pool elements, try to allocate another element.
      //
      else
      {
         *pStatus = AsynAllocPool( pPool, 1, pPool->Size);
      }
   } while ( !pEntry && (*pStatus == STATUS_SUCCESS) );

   if (pPool->Count < pPool->LowTide)
   {
      pPool->LowTide = pPool->Count;
   }

   KeReleaseSpinLock(&pPool->PoolLock, PoolIrql);
   return (pEntry);
}

/*
 *----------------------------------------------------------------------
 *  Function:  AsynAllocVarRxBufEntry
 *
 *  Purpose:
 *
 *    Allocate an entry from a pool of variable sized entries. 
 *    This call allows for variable buffer entries on the same pool and
 *    searches the pool until it finds and entry at least as large as 
 *    requested.
 *
 *
 *  Method:
 *    o Search the pool for an entry to satisfy the requested size.
 *    o Allocate a new entry if search fails.
 *    o Return failure if the allocate fails.
 *
 *
 *  Returns:
 *
 *    STATUS_SUCCESS 
 *    STATUS_UNSUCCESSFUL
 *
 *----------------------------------------------------------------------
 */
PRX_BUF_HEADER
AsynAllocVarRxBufEntry( 
   PPOOL_LIST  pPool,                  // Pool list
   ULONG       Size,
   PNTSTATUS   pStatus
)
{
   PRX_BUF_HEADER pEntry;              // Output entry
   ULONG          i;                   // Counter
   KIRQL          PoolIrql;

   pEntry = NULL;
   *pStatus = STATUS_SUCCESS;
   i = 1;
   KeAcquireSpinLock(&pPool->PoolLock, &PoolIrql);
   for ( pEntry = (PRX_BUF_HEADER)pPool->List.Flink;
        (PLIST_ENTRY)pEntry != &pPool->List; 
      (PLIST_ENTRY)pEntry = pEntry->ListEntry.Flink)
   {
      if ( pEntry->Size >= Size)
      {
         RemoveEntryList( &pEntry->ListEntry);
#ifdef DBG
         ASSERT(((PASYN_LIST)pEntry)->EntryState == EntryFree);
         ((PASYN_LIST)pEntry)->EntryState = EntryAllocated;
#endif
         pPool->Count--;
         goto AllocExit;
      }
   }
   //
   // Ran out of Pool elements or no suitable size found.
   // Try to allocate another element.
   //
   pEntry = NULL;
   *pStatus = AsynAllocPool( pPool, 1, (USHORT)(sizeof(*pEntry) + Size));
   if (*pStatus == STATUS_SUCCESS)
   {
      pEntry = (PRX_BUF_HEADER)RemoveTailList( &pPool->List);
#ifdef DBG
         ASSERT(((PASYN_LIST)pEntry)->EntryState == EntryFree);
         ((PASYN_LIST)pEntry)->EntryState = EntryAllocated;
#endif
         pPool->Count--;
   }
AllocExit:
   if (pPool->Count < pPool->LowTide)
   {
      pPool->LowTide = pPool->Count;
   }
   KeReleaseSpinLock(&pPool->PoolLock, PoolIrql);
   return (pEntry);
}

/*
 *----------------------------------------------------------------------
 *  Function:  AsynFreeEntry
 *
 *  Purpose:
 *
 *      Return an entry to a pool of entries
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    void
 *
 *----------------------------------------------------------------------
 */
void
AsynFreeEntry( 
   PPOOL_LIST  pPool,                   // Pool list
   PLIST_ENTRY pEntry                   // Output entry
)
{
   KIRQL  PoolIrql;

   DigiDump(DIGIFLOW1, ("ASYN!FreeEntry: Entry=0x%x\n", pEntry));
#ifdef DBG
   ASSERT(((PASYN_LIST)pEntry)->EntryState == EntryAllocated);
   ((PASYN_LIST)pEntry)->EntryState = EntryFree;
#endif
   KeAcquireSpinLock(&pPool->PoolLock, &PoolIrql);
   InsertHeadList( &pPool->List, pEntry);
   pPool->Count++;
   if (pPool->HighTide < pPool->Count)
   {
      pPool->HighTide = pPool->Count;
   }
   KeReleaseSpinLock(&pPool->PoolLock, PoolIrql);
}
/*
 *----------------------------------------------------------------------
 *  Function:  AsynLogError.
 *
 *  Purpose:
 *
 *      Place the error message into the event log.
 *
 *
 *  Method:
 *
 *      o Get an error log packet.
 *      o Configure the error log packet accordingly.
 *      o Form the error message.
 *      o Send the error message.
 *
 *
 *  Returns:
 *
 *      o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynLogError(
   DRIVER_OBJECT       *DriverObject,  /* Object device. */
   NTSTATUS            Error,          /* Specific error code. */
   IN PWSTR            LogMessage,     /* Additional Message. */
   NTSTATUS            ErrorStatus     /* Additional status information. */
)

{
   PUCHAR      message;                // Message being logged.
   USHORT      size;                   // Size of error log packet.
   PIO_ERROR_LOG_PACKET 
               errorLog;               // Error Log packet.
   UNICODE_STRING 
               uniLogMessage;          // Unicode string for message.

   DigiDump( DIGIALL, ("ASYN!LogError: Error=0x%x\n", Error));

   /*
    * Place LogMessage into uniLogMessage.
    */
   RtlInitUnicodeString(&uniLogMessage, LogMessage);

   size = sizeof(IO_ERROR_LOG_PACKET) + uniLogMessage.Length + sizeof(WCHAR);

   errorLog = IoAllocateErrorLogEntry(DriverObject, (UCHAR) size);
   if (errorLog == NULL)
      return;

   /*
    * Sets unused values in errorLog to zero AND sets NULL WCHARs in the
    * message area.
    */
   RtlZeroMemory(errorLog, size);

   errorLog->ErrorCode = Error;
   errorLog->FinalStatus = ErrorStatus;
   errorLog->DumpDataSize = 0;

   /*
    * Format and copy the extra information into the error log packet.
    */
   if (uniLogMessage.Length > 0)
   {
      message = (PUCHAR) &errorLog->DumpData[0];

      errorLog->NumberOfStrings = 1;
      errorLog->StringOffset = (USHORT) (message - (PUCHAR) errorLog);

      RtlCopyMemory(message, LogMessage, uniLogMessage.Length);
   }

   IoWriteErrorLogEntry(errorLog);
}

/*
 *----------------------------------------------------------------------
 *  Function:  AsynPutEventMsg
 *
 *  Purpose:
 *
 *    Send a Put Msg to DsApi asynchronously, track it by inserting a
 *    Put Event Descriptor in the Put Queue.
 *
 *
 *  Method:
 *    o Allocate a Put Descriptor if necessary
 *    o Fill in the Put Descriptor fields
 *    o Increment the Put Event reference count and call DsPutMsgEx
 *
 *
 *  Returns:
 *    Pointer to a freshly allocated Put Event, if none was passed.
 *    STATUS_SUCCESS          - Good
 *    STATUS_UNSUCCESSFUL     - Bad
 *    STATUS_PENDING          - Message sent to DXB and is awaiting completion.
 *
 *----------------------------------------------------------------------
 */
NTSTATUS 
AsynPutEventMsg( 
   IN PPORT_EXT         pPortExt, 
   IN OUT PPUT_MSG_EV   *pPutEvent,
   IN PIRP              pIrp,
   IN SDXB_PUT_CMD      Command,
   IN IRP_REASON        Reason,
   IN DS_PRIORITY       Priority,
   IN PUT_EV_STATE      State,
   IN USHORT            (*BuildFunc)()
)
{
   NTSTATUS    Status;                 // Call return value.
   USHORT      CmdLen;                 // Length of S/DXB command.
   SDXB_CMD    SdxbCmd;                // S/DXB command structure.

   //
   // Do we need to alloc a Put Event, or was one passed to us?
   //
   if (!*pPutEvent)
   {
      *pPutEvent = (PPUT_MSG_EV)AsynAllocEntry( &pPortExt->PutEventPool, &Status);
      if (Status != STATUS_SUCCESS)
      {
         return STATUS_UNSUCCESSFUL;
      }
      (*pPutEvent)->PortExt = pPortExt;
      (*pPutEvent)->Irp = pIrp;

      // Make sure the Irps list is always pointing to something meaningful,
      // this enabled the IRP to be always removed from a queue.
      // This is convenient for the cancel and timeout routines.
      if (pIrp)
      {
         InitializeListHead( &pIrp->Tail.Overlay.ListEntry);
      }
      (*pPutEvent)->Status = STATUS_SUCCESS;
      (*pPutEvent)->PutMsgRef = 0;
      (*pPutEvent)->Command = Command;
      (*pPutEvent)->Reason = Reason;
      
      (*pPutEvent)->Sequence = pPortExt->SyncOutSequence;
      //
      // Keep a Put Event descriptor waiting in the Pending queue.
      // This descriptor will be used to track the DsPutMsgEx calls for 
      // this open.
      //
      InsertTailList( &pPortExt->PutQ, &(*pPutEvent)->ListEntry);
   }
   (*pPutEvent)->PutMsgRef++;
   (*pPutEvent)->State = State;
   RtlZeroMemory( &SdxbCmd, sizeof(SdxbCmd));
   CmdLen = (*BuildFunc)( pPortExt, &SdxbCmd);
   Status = AsynPutMsgEx( pPortExt->DsPortHandle, (PUCHAR)&SdxbCmd,
                         CmdLen, (PUCHAR)NULL, 0, (HANDLE)(*pPutEvent), 
                         Priority, AsynPutMsgComplete);

   return Status;
}

/*
 *----------------------------------------------------------------------
 *  Function:  AsynPutEventCleanup
 *
 *  Purpose:
 *
 *    An error has occured talking to the DsApi, clean up the Put 
 *    Descriptor.  
 *    The calling routine is expected to complete the IRP.
 *
 *
 *  Method:
 *    o If the decremented Put Descriptor reference count is zero
 *       o Free the Put Descriptor.
 *
 *
 *  Returns:
 *    None
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynPutEventCleanup(
   IN OUT PPUT_MSG_EV   *pPutEvent
)
{
   NTSTATUS    Status;                 // Call return value.
   USHORT      CmdLen;                 // Length of S/DXB command.
   SDXB_CMD    SdxbCmd;                // S/DXB command structure.
   KIRQL       CurrentIrql;

   //
   // DsPutMsgEx failure, expect one less confirmation from DStreams
   // When an error occurs the calling routine will complete the IRP 
   // immediately, thus remove any reference to the IRP in the EVENT descriptor.
   //
   (*pPutEvent)->Irp = NULL;
   if (--(*pPutEvent)->PutMsgRef)
   {
      //
      // Nobody else is using it, put the event descriptor back in
      // the free pool.
      //
		PPORT_EXT pPortExt = (*pPutEvent)->PortExt;
      RemoveEntryList( &(*pPutEvent)->ListEntry);
      (*pPutEvent)->PortExt = (PPORT_EXT)NULL;
      AsynFreeEntry( &pPortExt->PutEventPool, &(*pPutEvent)->ListEntry);
   }
}

/*
 *----------------------------------------------------------------------
 *  Function:  AsynUtilFindPutEvent
 *
 *  Purpose:
 *
 *       Find a PutEvent in a given Put Queue based on the IRP pointer.
 *
 *
 *  Method:
 *       Look in the supplied Put queue.
 *
 *
 *  Returns:
 *       Pointer to the Put Event.
 *
 *
 *----------------------------------------------------------------------
 */
PPUT_MSG_EV 
AsynUtilFindPutEvent( 
   PPORT_EXT   pPortExt, 
   PIRP        pIrp,
   PLIST_ENTRY pPutQueue
)
{
   PPUT_MSG_EV pPutEvent = NULL;
   PLIST_ENTRY pEntry;

   for ( pEntry = pPutQueue->Flink; pEntry != pPutQueue; pEntry = pEntry->Flink)
   {
      if ( ((PPUT_MSG_EV)pEntry)->Irp == pIrp)
      {
         // Found the associated Event
         pPutEvent = (PPUT_MSG_EV)pEntry;
         break;
      }
   }
   return (pPutEvent);
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynFindAndCompletePut
 *
 * Purpose:
 *
 *    Find and complete the first occurance of a given Put command 
 *    type on the port's Put Queue.
 *
 * Method:
 *    o Walk the port's PutQ looking for the given Put Command type.
 *    o When found 
 *          o Complete any associated IRP.
 *          o Free the Put Descriptor.
 *
 *
 * NOTES:
 *    Called with the port's spin lock held.
 *
 * Returns:
 *
 *    Nothing
 *
 *----------------------------------------------------------------------
 */
VOID
AsynFindAndCompletePut(
   PPORT_EXT      pPortExt,
   SDXB_PUT_CMD   CommandType,
   NTSTATUS       Status
   )
{
   PLIST_ENTRY    pEntry;
   PLIST_ENTRY    pEntryNext;
   PPUT_MSG_EV    pPutEvent;

   //
   // Match this up with the PutQ request
   //
   for ( pEntry = pPortExt->PutQ.Flink; pEntry != &pPortExt->PutQ;
      pEntry = pEntryNext)
   {
      pEntryNext = pEntry->Flink;
      pPutEvent = (PPUT_MSG_EV)pEntry;
      if (pPutEvent->Command == CommandType)
      {
         // Found originating request
         if (pPutEvent->Irp)
         {
            pPutEvent->Irp->IoStatus.Status = Status;
            InsertTailList( &pPortExt->CompleteIrpQ, 
                            &pPutEvent->Irp->Tail.Overlay.ListEntry);
            pPutEvent->Irp = NULL;
         }
         // Put the Put Event descriptor back in the free pool
         RemoveEntryList( &pPutEvent->ListEntry);
         AsynFreeEntry( &pPortExt->PutEventPool, &pPutEvent->ListEntry);
      }
   }
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynCompleteIrp
 *
 * Purpose:
 *
 *    Complete the outbound IRP.  The IRP may be a Write, Flush or an
 *    IOCTL.  Essentially anything except a Read.
 *
 * Method:
 *    o Generate an appropriate Trace statement if any.
 *    o Complete the IRP in the usual way.
 *
 *
 * NOTES:
 *    be released beforehand.
 *
 * Returns:
 *
 *    Nothing
 *
 *----------------------------------------------------------------------
 */
VOID
AsynCompleteIrp(
   PPORT_EXT      pPortExt,
   PIRP           pIrp
   )
{
   PIO_STACK_LOCATION   pIrpSp;
   KIRQL                CurrentIrql;

   if (!pIrp)
   {
      DigiDump( DIGIALL, ("*** ASYN!CompleteIrp: Port=%d, Irp is NULL\n", 
         pPortExt->Port));
   }
   pIrpSp = IoGetCurrentIrpStackLocation( pIrp);
   switch (pIrpSp->MajorFunction)
   {
      case IRP_MJ_READ:
         DigiDump(DIGIFLOW, ("ASYN!CompleteIrp: Complete READ IRP=0x%x\n", pIrp));
         KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);
         if (!IsListEmpty( &pPortExt->Trace.Queue))
         {
            AsynTraceReadIrp( pPortExt, pIrp);
         }
         KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
         break;

      case IRP_MJ_WRITE:
         DigiDump(DIGIFLOW, ("ASYN!CompleteIrp: Complete WRITE IRP=0x%x\n", pIrp));
         KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);
         if (!IsListEmpty( &pPortExt->Trace.Queue))
         {
            AsynTraceWriteIrp( pPortExt, pIrp);
         }
         KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
         break;
      case IRP_MJ_DEVICE_CONTROL:
         if (pIrpSp->Parameters.DeviceIoControl.IoControlCode 
               == IOCTL_SERIAL_WAIT_ON_MASK)
         {
            DigiDump(DIGIFLOW, ("ASYN!CompleteIrp: Complete WAIT IRP=0x%x\n", pIrp));
            KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);
            if (!IsListEmpty( &pPortExt->Trace.Queue))
            {
               AsynTraceIoctlIrp( pPortExt, pIrp, FALSE, TRUE);
            }
            KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
         }
         else
            DigiDump(DIGIFLOW, ("ASYN!CompleteIrp: Complete IRP=0x%x\n", pIrp));
         break;

      case IRP_MJ_FLUSH_BUFFERS:
         DigiDump(DIGIFLOW, ("ASYN!CompleteIrp: Complete FLUSH IRP=0x%x\n", pIrp));
         break;

      default:
         DigiDump(DIGIFLOW, ("ASYN!CompleteIrp: Complete IRP=0x%x\n", pIrp));
         break;
   }
   KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);
   IoSetCancelRoutine( pIrp, NULL);
   KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);

   ASSERT( pIrp->IoStatus.Status != STATUS_PENDING);

   DigiDump(DIGIIRP, ("ASYN!CompleteIrp: Irp=0x%x Status=0x%x Info=0x%x\n", 
               pIrp, pIrp->IoStatus.Status, pIrp->IoStatus.Information));
   IoCompleteRequest( pIrp, IO_NO_INCREMENT);
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynIrpStatus
 *
 * Purpose:
 *
 *    Check the status of an IRP and update the Status field in the 
 *    dispatch queue.
 *
 * Method:
 *
 *
 * NOTES:
 *    Port Spin-locks must be held when this routine is called.
 *
 * Returns:
 *
 *    Nothing
 *
 *----------------------------------------------------------------------
 */
VOID
AsynIrpStatus(
   IN PPORT_EXT      pPortExt,
   IN PIRP           pIrp
   )
{
   NTSTATUS    Status = STATUS_PENDING;
   PLIST_ENTRY pList;
   PDISPATCH_ELE pDispatchEntry;
   PIRP        pCompleteIrp;

   // Determine if the IRP is in the completed Queue
   for ( pList = pPortExt->DispatchStatusQ.Blink;
      pList != &pPortExt->DispatchStatusQ; pList = pList->Blink)
   {
      pDispatchEntry = (PDISPATCH_ELE)pList;
      if (pDispatchEntry->Irp == pIrp)
      {
         pDispatchEntry->Status = pIrp->IoStatus.Status;
         // Don't update the status again.
         pDispatchEntry->Irp = NULL;
         break;
      }
   }
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynCompleteQueuedIrps
 *
 * Purpose:
 *
 *    Complete the IRPs in the Completed outbound queue and return the
 *    status of the complete IRP candidate.
 *
 * Method:
 *    o Successively call AsynCompleteIrp for each IRP
 *
 *
 * NOTES:
 *    Spin-locks must not be held when this routine is called.
 *
 * Returns:
 *
 *    STATUS_PENDING - The supplied IRP was not completed at this invocation.
 *    STATUS_*       - The supplied IRP was completed with STATUS_* code.
 *
 *----------------------------------------------------------------------
 */
VOID
AsynCompleteQueuedIrps(
   IN PPORT_EXT      pPortExt
   )
{
   //
   // Complete all IRPs in the complete queue.
   KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);

   while ( !IsListEmpty( &pPortExt->CompleteIrpQ))
   {
      PLIST_ENTRY pList;
      PIRP        pIrp;

      pList = RemoveHeadList( &pPortExt->CompleteIrpQ);
      pIrp = CONTAINING_RECORD( pList, IRP, Tail.Overlay.ListEntry);
      //
      // Update the Status in the DispatchEntry routine.
      AsynIrpStatus( pPortExt, pIrp);

      KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
      AsynCompleteIrp( pPortExt, pIrp);
      KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);
   }
   KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql); 
}



