/*
 ****************************************************************************
 * 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:  dispatch.c
 *
 *  Purpose:
 *
 *    Dispatch IRP_MJ_* entry points for Asyn driver. 
 *
 *
 *----------------------------------------------------------------------
 */

#include "header.h"

#define min(a, b)  (((a) < (b)) ? (a) : (b)) 

VOID AsynCancelIrp( IN PDEVICE_OBJECT pDevice, IN PIRP pIrp);

/*
 *----------------------------------------------------------------------
 * Function:  AsynSetCancelFunction
 *
 * Purpose:
 *
 *    Set the IRP Cancel Function appropriate for a given IRP
 *
 * Returns:
 *
 *    o VOID
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynSetCancelFunction(
   IN PIRP        pIrp
)
{
   KIRQL       CurrentIrql;

   IoAcquireCancelSpinLock( &CurrentIrql);
   switch ( (IoGetCurrentIrpStackLocation( pIrp))->MajorFunction )
   {
      case IRP_MJ_CREATE:
         break;

      case IRP_MJ_READ:
         IoSetCancelRoutine( pIrp, AsynCancelIrp);
         break;

      case IRP_MJ_FLUSH_BUFFERS:
      case IRP_MJ_WRITE:
         IoSetCancelRoutine( pIrp, AsynCancelIrp);
         break;

      case IRP_MJ_DEVICE_CONTROL:
	  {
         PIO_STACK_LOCATION      pIrpSp;        // Current IRP stack position.

         pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
         // if this is a XN type of message we want a different cancel routine.
   	     if (pIrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_DIGI_SDXB_XN_GET)
 	        IoSetCancelRoutine( pIrp, AsynCancelXnGetIrp);
		 else
            IoSetCancelRoutine( pIrp, AsynCancelIrp);
         break;
	  }

      case IRP_MJ_CLEANUP:
         break;

      case IRP_MJ_CLOSE:
         IoSetCancelRoutine( pIrp, AsynCancelIrp);
         break;

      default:
         DigiDump( DIGIALL, ("*** ASYN!SetCancelFunction: Irp=0x%x MJ=0x%x\n", 
                             pIrp, (IoGetCurrentIrpStackLocation( pIrp))->MajorFunction ));
         ASSERT( FALSE /* Unsupported IRP_MJ type */);
         break;
   }
   IoReleaseCancelSpinLock( CurrentIrql);
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynDispatchEntry.
 *
 * Purpose:
 *
 *    The dispatch routine entry point for DigiAsyn driver.  
 *    If the IRP is not for a COM Port complete immediately,
 *    else 
 *       o Queue the IRP
 *       o Process the IRP if the Port is not already busy.
 *
 *
 *  Method:
 *
 *  Returns:
 *
 *    o STATUS_PENDING if Port is busy or unable to complete immediately.
 *    o STATUS_SUCCESS if IRP is completed successfuly.
 *    o STATUS_UNSUCCESSFUL on error.
 *
 *
 *----------------------------------------------------------------------
 */
NTSTATUS AsynDispatchEntry(
    IN PDEVICE_OBJECT   pDevice,    // Comm port device
    IN PIRP             pIrp            // Associated work order
)
{
   PPORT_EXT   pPortExt = NULL;        // Port specific information.
   NTSTATUS    Status = STATUS_SUCCESS;// Called function return value.

   pIrp->IoStatus.Information = 0;
   if (pDevice->DeviceType == FILE_DEVICE_CONTROLLER)
   {
      //
      // All controller dispatch functions will complete immediately.
      //
      DigiDump( (DIGIDISPATCH), ("ASYN!DispatchEntry: Controller\n"));
      pIrp->IoStatus.Information = 0;

      switch ( (IoGetCurrentIrpStackLocation( pIrp))->MajorFunction)
      {
         case IRP_MJ_DEVICE_CONTROL:
            Status = AsynIoCard( pDevice, pIrp);
            break;
         case IRP_MJ_FLUSH_BUFFERS:
         case IRP_MJ_WRITE:
            Status = STATUS_ACCESS_VIOLATION;
            break;
      }
   }
   else if (pDevice->DeviceType != FILE_DEVICE_SERIAL_PORT)
   {
      DigiDump(DIGIALL, ("***ASYN!DispatchEntry: For unknown device =0x%x Type=x0%x\n",
                             pDevice, pDevice->DeviceType));
      Status = STATUS_UNSUCCESSFUL;
      pIrp->IoStatus.Information = 0;
   }
   else
	{
      DISPATCH_ELE   Dispatch;

      DigiDump(DIGIDISPATCH, ("ASYN!DispatchEntry: "));
      //
      // Get the driver specific data structures.
      //
      pPortExt = pDevice->DeviceExtension;
   
      KeAcquireSpinLock(&pPortExt->PortLock, &pPortExt->PortIrql);
      DigiDump(DIGIDISPATCH, ("+++[%d]Entry Port=%d Irp=0x%x\n", 
                           pPortExt->PortBusy, pPortExt->Port, pIrp));
   
      Dispatch.Irp = pIrp;
      Dispatch.Status = STATUS_PENDING;
      InsertTailList( &pPortExt->DispatchStatusQ, &Dispatch.ListEntry);

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

      DigiDump(DIGIFLOW|DIGIFLOW1, ("AsynDispatchEntry: P=%d Calling SchedulerLoop\n",
                                       pPortExt->Port));
      AsynSchedulerLoop( pPortExt);

      RemoveEntryList( &Dispatch.ListEntry);
      Status = Dispatch.Status;
      if (Status == STATUS_PENDING)
      {
         //
         // Every pended call needs the cancel function set!
         //
         ASSERT( pIrp && (pIrp->Type == IO_TYPE_IRP));
         AsynSetCancelFunction( pIrp);

         pIrp->IoStatus.Status = STATUS_PENDING;
         IoMarkIrpPending( pIrp);
         DigiDump(DIGIIRP, ("ASYN!DispatchEntry: MarkPendingIrp=0x%x\n", pIrp));
      }
      KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);

      DigiDump(DIGIFLOW, ("ASYN!DispatchEntry: ---[%d]Exit P=%d Status=0x%x Irp=0x%x\n", 
                  pPortExt->PortBusy, pPortExt->Port, Status, pIrp));
   }

   if (!pPortExt)
   {
      //
      // Irp was not directed at a COM Port device.

      if ( Status != STATUS_PENDING)
      {
         pIrp->IoStatus.Status = Status;
         IoCompleteRequest( pIrp, IO_NO_INCREMENT);
      }
   }
   return Status;
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynCancelIrp.
 *
 * Purpose:
 *
 *    General entry point for cancelling all IRPs.
 *
 *
 * Method:
 *
 *    o Clear the IRPs Cancel routine
 *    o Put the IRP in the Cancelled and Timedout IRP queue.
 *    o Invoke the Scheduler if the driver is not Busy.
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynCancelIrp(
   IN PDEVICE_OBJECT    pDevice,    // port device.
   IN PIRP              pIrp        // Associated work order.
)

{
   PPORT_EXT      pPortExt;         // Driver specific information.

   IoReleaseCancelSpinLock(pIrp->CancelIrql);

   DigiDump(DIGICANCEL|DIGIIRP, ("ASYN!CancelIrp: "));

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


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

   IoSetCancelRoutine( pIrp, NULL);

   pIrp->IoStatus.Status = STATUS_CANCELLED;

   // The IRP must always be queued.
   RemoveEntryList( &pIrp->Tail.Overlay.ListEntry);

   //
   // Let the scheduler complete the IRP
   InsertTailList( &pPortExt->CancelledAndTimedoutIrpQ, &pIrp->Tail.Overlay.ListEntry);
   if (pPortExt->PortBusy)
   {
      DigiDump(DIGICANCEL|DIGIIRP, ("ASYN!CancelIrp: Busy, Place in Q."));
      KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
   }
   else
   {
      DigiDump(DIGIDISPATCH, ("ASYN!CancelIrp: +++[%d]Entry P=%d Irp=0x%x\n", 
                  pPortExt->PortBusy, pPortExt->Port, pIrp));
      AsynSchedulerLoop( pPortExt);
      DigiDump(DIGIDISPATCH, ("ASYN!CancelIrp: ---[%d]Exit P=%d\n", 
                        pPortExt->PortBusy, pPortExt->Port));
      KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
   }
}


/*
 *----------------------------------------------------------------------
 * Function:  AsynDispatchIrps.
 *
 * Purpose:
 *
 *    Successively get the Next Irp from the supplied Queue and call 
 *    appropriate dispatch routine.
 *
 *
 * Method:
 *
 *    o While not busy calling DsPutMsg AND the Queue is not empty
 *        o Remove the first Irp from the Queue
 *        o Call dispatch routine
 *        o If Irp is not pending
 *             o Insert Irp in the Completed Irp Queue
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 * NOTES:   The Spin-Lock protecting the Queue must be held prior to 
 *          calling this routine.
 *
 *----------------------------------------------------------------------
 */
VOID
AsynDispatchIrps(
   IN PPORT_EXT      pPortExt,      // Port specific information.
   IN PLIST_ENTRY    pQueue         // Queue of IRPs.
)
{
   PIRP        pIrp;                // Queued IRP to finish.
   PIO_STACK_LOCATION
               pIrpSp;              // Current IRP stack position.
   NTSTATUS    Status;

   DigiDump( DIGIFLOW, ("ASYN!DispatchIrps: PortExt=0x%x\n", pPortExt));

#if NT5_BUILD   
   while (pPortExt->PnpState == PNPSTATE_OK && !IsListEmpty( pQueue))
#else   
   while (!IsListEmpty( pQueue))
#endif   
   {
      PIO_STACK_LOCATION   pIrpSp;        // Current IRP stack position.
      PLIST_ENTRY          p;             // LIST_ENTRY intermediate value.
      PIRP                 pIrp = NULL;

      p = RemoveHeadList( pQueue);
      pIrp = CONTAINING_RECORD( p, IRP, Tail.Overlay.ListEntry);
      pIrpSp = IoGetCurrentIrpStackLocation( pIrp);

      switch (pIrpSp->MajorFunction )
      {
         ULONG    PurgeFlag;

         case IRP_MJ_CREATE:
            DigiDump( DIGIFLOW | DIGIOPEN, ("ASYN!DispatchIrps: OPEN Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));

            Status = AsynPortOpen( pPortExt, pIrp);
            break;

         case IRP_MJ_FLUSH_BUFFERS:
            DigiDump( DIGIFLOW, ("ASYN!DispatchIrps: FLUSH Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));
            InsertTailList( &pPortExt->WriteQueue, &pIrp->Tail.Overlay.ListEntry);
            Status = AsynPortWrite( pPortExt);
            break;

         case IRP_MJ_READ:
            DigiDump( DIGIFLOW, ("ASYN!DispatchIrps: READ Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));
            Status = AsynPortRead( pPortExt, pIrp);
            break;

         case IRP_MJ_WRITE:
            DigiDump( DIGIFLOW, ("ASYN!DispatchIrps: WRITE Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));
            InsertTailList( &pPortExt->WriteQueue, &pIrp->Tail.Overlay.ListEntry);
            Status = AsynPortWrite( pPortExt);
            break;

         case IRP_MJ_DEVICE_CONTROL:
            DigiDump( DIGIFLOW, ("ASYN!DispatchIrps: IOCTL Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));
            Status = AsynIoctl( pPortExt, pIrp);
            break;

         case IRP_MJ_CLEANUP:
            DigiDump( DIGIFLOW, ("ASYN!DispatchIrps: CLEANUP Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));
            Status = AsynCleanup( pPortExt, pIrp);
            break;

         case IRP_MJ_CLOSE:
            DigiDump( DIGIFLOW | DIGICLOSE, ("ASYN!DispatchIrps: CLOSE Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));

            Status = AsynPortClose( pPortExt, pIrp);
            break;

         default:
            ASSERT(pIrpSp->MajorFunction == pIrpSp->MajorFunction);
            Status = STATUS_INVALID_PARAMETER;
            break;
      }
   }
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynCancelledAndTimedoutIrps.
 *
 * Purpose:
 *
 *    Successively get the Next Irp from the supplied Queue and call 
 *    appropriate dispatch routine.
 *
 *
 * Method:
 *
 *    o While not busy calling DsPutMsg AND the Queue is not empty
 *        o Remove the first Irp from the Queue
 *        o Call dispatch routine
 *        o If Irp is not pending
 *             o Insert Irp in the Completed Irp Queue
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 * NOTES:   The Spin-Lock protecting the Queue must be held prior to 
 *          calling this routine.
 *
 *----------------------------------------------------------------------
 */
VOID
AsynCancelledAndTimedoutIrps(
   IN PPORT_EXT   pPortExt       // Port 
)
{
   PLIST_ENTRY pQueue = &pPortExt->CancelledAndTimedoutIrpQ; // Queue of IRPs.

   DigiDump( DIGIFLOW, ("ASYN!CancelledAndTimedoutIrps: PortExt=0x%x\n", pPortExt));

   //
   // Service the Cancelled and Timedout IRP Queue
   //
   while (!IsListEmpty( pQueue))
   {
      PIO_STACK_LOCATION   pIrpSp;        // Current IRP stack position.
      PLIST_ENTRY          p;             // LIST_ENTRY intermediate value.
      PIRP                 pIrp = NULL;

      p = RemoveHeadList( pQueue);
      pIrp = CONTAINING_RECORD( p, IRP, Tail.Overlay.ListEntry);
      pIrpSp = IoGetCurrentIrpStackLocation( pIrp);

      switch (pIrpSp->MajorFunction )
      {
         ULONG    PurgeFlag;

         case IRP_MJ_CREATE:
            DigiDump( DIGIFLOW | DIGIOPEN, ("ASYN!CancelledAndTimedoutIrps: OPEN Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));

            ASSERT(FALSE /* No Cancel routine */);
            break;

         case IRP_MJ_FLUSH_BUFFERS:
            DigiDump( DIGIFLOW, ("ASYN!CancelledAndTimedoutIrps: WRITE Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));
            ASSERT( pIrp->Cancel);
            AsynCancelWrite( pPortExt, pIrp);
            break;

         case IRP_MJ_READ:
            DigiDump( DIGIFLOW, ("ASYN!CancelledAndTimedoutIrps: READ Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));

            // Only READ Cancels are handled here, the Read-Timeouts are fully
            // handled in-line in the TimeoutDPCs.
            ASSERT( pIrp->Cancel);
            AsynCancelRead( pPortExt, pIrp);
            break;

         case IRP_MJ_WRITE:
            DigiDump( DIGIFLOW, ("ASYN!CancelledAndTimedoutIrps: WRITE Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));
            if ( pIrp->Cancel)
            {
               AsynCancelWrite( pPortExt, pIrp);
            }
            else
            {
               // Write Timed out
               AsynWriteTimedoutCleanup( pPortExt, pIrp);
            }
            break;

         case IRP_MJ_DEVICE_CONTROL:
            DigiDump( DIGIFLOW, ("ASYN!CancelledAndTimedoutIrps: IOCTL Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));
            ASSERT( pIrp->Cancel);
            AsynCancelIoctl( pPortExt, pIrp);
            break;

         case IRP_MJ_CLEANUP:
            DigiDump( DIGIFLOW, ("ASYN!CancelledAndTimedoutIrps: CLEANUP Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));

            ASSERT(FALSE /* No Cancel routine */);
            break;

         case IRP_MJ_CLOSE:
            DigiDump( DIGIFLOW | DIGICLOSE, ("ASYN!CancelledAndTimedoutIrps: CLOSE Port=%d Irp=0x%x\n", 
                                pPortExt->Port, pIrp));

            ASSERT( pIrp->Cancel);
            AsynCancelClose( pPortExt, pIrp);
            break;

         default:
            ASSERT(pIrpSp->MajorFunction == pIrpSp->MajorFunction);
            break;
      }
   }
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynValidRsp
 *
 * Purpose:
 *
 *    Process a valid response.
 *
 *
 * Method:
 *
 *    o Call appropriate Data or Response processing routine.
 *
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */
VOID 
AsynValidRsp( 
   IN PPORT_EXT      pPortExt, 
   IN PGET_MSG_EV    pGetEvent)
{
   if ( (pGetEvent->DataLength == 0) && (pGetEvent->RspLength == 0) )
   {
      PORT_ERROR(pPortExt);
   }
   //
   // Post a receive "type-ahead" buffer on the GetMsg queue.
   //
   if (pGetEvent->DataLength)
   {
		ASSERT( !IsListEmpty( &pPortExt->RxQ));
		AsynProcessData( pPortExt, pGetEvent->PData, pGetEvent->DataLength, (PRX_BUF)pPortExt->RxQ.Blink );
   }
   if (pGetEvent->RspLength)
   {
      switch ( ((PSDXB_CMD_HEAD)&pGetEvent->Rsp)->Ctype)
      {
         case SDXB_SYNC:
            AsynProcessSync( pPortExt, 
                             &pGetEvent->Rsp.U.Sync);
            break;
         case SDXB_BUFSIZE:
            AsynProcessBufferSize( pPortExt, 
                                   &pGetEvent->Rsp.U.Buffer);
            break;
         case SDXB_MSTAT:
            AsynProcessModemCtrl( pPortExt, 
                                  &pGetEvent->Rsp.U.Modem);
            break;
         case SDXB_EVENT:
            AsynProcessEvent( pPortExt, 
                              &pGetEvent->Rsp.U.Event);
            break;
         case SDXB_LERR:
            AsynProcessLineErr( pPortExt, 
                                &pGetEvent->Rsp.U.Line);
            break;
         case SDXB_PCAP:
            AsynProcessPortCap( pPortExt, 
                                &pGetEvent->Rsp.U.PortCap);
            break;
         case SDXB_PARAM:
            AsynProcessPortParam( pPortExt, 
                                  &pGetEvent->Rsp.U.PortParam);
            break;
         case SDXB_SEQ:
            AsynProcessSeqNum( pPortExt, 
                               &pGetEvent->Rsp.U.SeqNum);
            break;
         case SDXB_SCRECV:
            AsynProcessSpecialChar( pPortExt, 
                                    &pGetEvent->Rsp.U.SpxChar);
            break;
         case SDXB_WMARK:
         case SDXB_SCIMMED:
         case SDXB_BREAK:
         case SDXB_FLUSH:
         case SDXB_PAUSE:
         case SDXB_FREXCP:
         case SDXB_PROTERR:
         default:
            // These invalid types should have invoked exception
            // code at GetMsgCompletion time.  No need to invoke
            // this exception code again.  Just ignore this time.
            break;
      }
   }
   pGetEvent->RspLength = 0;

}

/*
 *----------------------------------------------------------------------
 * Function:  AsynPostGetMsg
 *
 * Purpose:
 *
 *    Register an asynchronous Get Message with the DsApi
 *
 *
 * Method:
 *
 *    o Call DsGetMsgEx() specifying a data buffer if there is room in 
 *      the type-ahead buffer, or without a data buffer if there is no 
 *      more room.
 *
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */

VOID
AsynPostGetMsg( 
   IN PPORT_EXT      pPortExt,
   IN NTSTATUS       LastGetMsgStatus
)
{
   PRX_BUF           pRxBuf;
   NTSTATUS          Status;

   //
   // Current Rx buffer
   //
   if (IsListEmpty( &pPortExt->RxQ))
   {
      //
      // This is the first time
      pRxBuf = (PRX_BUF)AsynAllocEntry( &pPortExt->ReadBufferPool, &Status);
      if (Status == STATUS_SUCCESS)
      {
         pRxBuf->Size = DEF_RX_BUF_SIZE;
         pRxBuf->Len = 0;
         pRxBuf->Start = 0;

         pPortExt->RxBufCount++;
         InsertTailList( &pPortExt->RxQ, &pRxBuf->ListEntry);
      }
   }
   else 
   {
      pRxBuf = (PRX_BUF)(pPortExt->RxQ.Blink);
   }

   if (pRxBuf)
   {
      ASSERT(pRxBuf->Size >= (pRxBuf->Len + pRxBuf->Start));

      //
      // Decide whether to allocate a fresh Receive Buffer.
      //

      //
      // Was the current Rx Buffer big enough?
      //
      if ( (LastGetMsgStatus == STATUS_BUFFER_TOO_SMALL) ||
      //
      // Is there enough space in Current Rx buffer to comfortably
      // fit maximum expected packet?
      //
           ((pRxBuf->Size - (pRxBuf->Len + pRxBuf->Start)) 
               < (SDXB_MAXLEN_DEF/2)) )
      {
         if ( (LastGetMsgStatus == STATUS_BUFFER_TOO_SMALL) &&
               ((pRxBuf->Len + pRxBuf->Start) == 0) )
         {
               if ( (pRxBuf->Size == DEF_RX_BUF_SIZE) &&
                    (pPortExt->SdxbValues.Insize > DEF_RX_BUF_SIZE) )
            {
               KIRQL    CurrentIrql;

               //
               // The standard buffer size already attempted and we still don't
               // have room for the data.  Try again with a buffer size equal 
               // to the firmware's receive buffer size.
               // If that number is not reasonable only accept high priority 
               // messages from now on.
               //
               DigiDump(DIGIALL|DIGITHREAD, ("***ASYN!PostGetMsg: Port=%d Buffer too small!\n",
                                     pPortExt->Port));
               pRxBuf = (PRX_BUF)AsynAllocVarRxBufEntry( &pPortExt->VariableReadBufferPool, 
                                                pPortExt->SdxbValues.Insize, 
                                                &Status);
               if (Status == STATUS_SUCCESS)
               {
                  pRxBuf->Size = pPortExt->SdxbValues.Insize;
                  pRxBuf->Start = 0;
                  pRxBuf->Len = 0;
                  pPortExt->RxBufCount++;
                  InsertTailList( &pPortExt->RxQ, &pRxBuf->ListEntry);
               }

            }
            else if ((pRxBuf->Size != DEF_RX_BUF_SIZE) ||
                     (pPortExt->SdxbValues.Insize <= DEF_RX_BUF_SIZE) )
            {
               //
               // Already tried a customized buffer size and still the buffer 
               // is too small. Give up!
               pRxBuf = NULL;
            }

            if (!pRxBuf)
            {
               if (pPortExt->WaitMask & SERIAL_EV_RX80FULL)
               {
                  pPortExt->HistoryMask |= SERIAL_EV_RX80FULL;
               }
               pPortExt->OpenStateMask |= RxDataFullMask;
            }
         }
         else if (pPortExt->RxBufCount >= MAX_RX_BUF_PER_PORT)
         {
            // Reached the maximum allowed buffer usage for this port
            pRxBuf = NULL;
            DigiDump(DIGIALL|DIGITHREAD, ("*** ASYN!PostGetMsg: Port=%d RxBufCount=0x%x reached\n",
                                  pPortExt->Port, pPortExt->RxBufCount));
         }
         else
         {
            //
            // Allocate a fresh Receive Buffer from the standard pool
            //
            DigiDump(DIGITHREAD, ("ASYN!PostGetMsg: Port=%d Acquiring new Rx Old=0x%x Count=0x%x Len=0x%x\n",
                                  pPortExt->Port, pRxBuf, pPortExt->RxBufCount,
                                  pRxBuf->Len + pRxBuf->Start));
            pRxBuf = (PRX_BUF)AsynAllocEntry( &pPortExt->ReadBufferPool, &Status);
            DigiDump(DIGITHREAD, ("ASYN!PostGetMsg: New Rx=0x%x\n", pRxBuf));
            if (Status == STATUS_SUCCESS)
            {
               pRxBuf->Size = DEF_RX_BUF_SIZE;
               pRxBuf->Len = 0;
               pRxBuf->Start = 0;

               pPortExt->RxBufCount++;
               InsertTailList( &pPortExt->RxQ, &pRxBuf->ListEntry);

            }
         }
      }
      //
      // This is a compromise; provide an indication that the internal
      // buffer is getting full, implying the adapter buffer MAY BE 
      // filling.
      //
      if (pPortExt->RxBufCount >= MAX_RX_BUF_PER_PORT)
      {
         if (pPortExt->WaitMask & SERIAL_EV_RX80FULL)
         {
            pPortExt->HistoryMask |= SERIAL_EV_RX80FULL;
         }
         pPortExt->OpenStateMask |= RxDataFullMask;
      }
   }

   if (!(pPortExt->OpenStateMask & RxDataFullMask))
   {
#ifdef DBG
      pPortExt->Debug.LastGetMsgData = 
                         &pRxBuf->Data[pRxBuf->Start + pRxBuf->Len];
      pPortExt->Debug.LastGetMsgLen = 
                         pRxBuf->Size - (pRxBuf->Start + pRxBuf->Len); 
      pPortExt->Debug.LastRxStart = pRxBuf->Start;
      pPortExt->Debug.LastRxLen = pRxBuf->Len; 
#endif  // DBG
      ASSERT(pRxBuf->EntryState == EntryAllocated);
      ASSERT((LONG)pPortExt->Debug.LastGetMsgLen > 0);

      DigiDump(DIGITHREAD, ("*** ASYN!PostGetMsg: Port=%d CSize=0x%x Addr=0x%x\n", 
                            pPortExt->Port, sizeof(SDXB_CMD),
                           &pRxBuf->Data[pRxBuf->Start + pRxBuf->Len]));
	   KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
      Status = DsGetMsgEx( pPortExt->DsPortHandle,
//             MAX ((sizeof(SDXB_CMD)), (sizeof (SDXB_COMP_RSP)) ),
   			 sizeof (union ISDN_primitives) + ISDN_MESSAGE_ENCAP_SIZE,
             &pRxBuf->Data[pRxBuf->Start + pRxBuf->Len], 
             pRxBuf->Size - (pRxBuf->Start + pRxBuf->Len),
             (HANDLE)pPortExt,
             DS_PRIORITY_ANY,
             AsynGetMsgComplete
		);
   }
   else
   {
      //
      // The receive buffers are exhausted, only allow hi-priority
      // messages until the user reads the receive data.
      //
      DigiDump(DIGITHREAD, ("ASYN!PostGetMsg: Port=%d Addr=0x0\n", 
                            pPortExt->Port));
	   KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
      Status = DsGetMsgEx( pPortExt->DsPortHandle,
//      		MAX ((sizeof(SDXB_CMD)), (sizeof (SDXB_COMP_RSP)) ),
			sizeof (union ISDN_primitives) + ISDN_MESSAGE_ENCAP_SIZE,
         	(PUCHAR)NULL,
         	0,
         	(HANDLE)pPortExt,
         	DS_PRIORITY_HIGH,
         	AsynGetMsgComplete
		);
   }
   KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);
   if (Status != STATUS_PENDING)
   {
      DigiDump(DIGITHREAD, ("*** ASYN!PostGetMsg: DsGetMsgEx() failed=0x%x\n",
                           Status));
   }
   if (pPortExt->WaitMaskIrp)
   {
       AsynCheckWaitEvents( pPortExt);
   }
}

/*
 *----------------------------------------------------------------------
 * Function:  	  AsynXnGetResponses
 *
 * Purpose:
 *
 *    Process S/DXB XnGetresponses 
 *
 *
 * Method:
 *
 *
 * NOTES:
 *    The Port's Spin-lock must be held when this routine is called.
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynXnGetResponses(
   IN PPORT_EXT   pPortExt        // COM Port
)
{
   PLIST_ENTRY pList;                 // LIST_ENTRY intermediate value.   
   PIRP        pIrp;                   // Queued IRP.
   PXN_GET_LIST pXnGetList ;
   PXN_GET     pXnGet ;
   
   DigiDump(DIGITHREAD, ("ASYN!SdxbXnGetResponses:\n"));

   //
   // Not currently busy, there is an XN Get SDXB-Response in the Queue
   // and there is an xn get irp waiting.
   //
   while (!IsListEmpty( &pPortExt->XnGetQueue) &&
          !IsListEmpty( &pPortExt->XnGetPktQueue))
   {
      pList = RemoveHeadList( &pPortExt->XnGetQueue);
	  pIrp = CONTAINING_RECORD(pList, IRP, Tail.Overlay.ListEntry) ;
	  pList = RemoveHeadList( &pPortExt->XnGetPktQueue );
	  pXnGetList = CONTAINING_RECORD(pList, XN_GET_LIST, List) ;

	  pXnGet = (PXN_GET)pIrp->AssociatedIrp.SystemBuffer ;

      // copy data
      // copy data
  	  if ((pXnGetList->XnGetEntry->cbControl > pXnGet->cbMaxControl) ||
  	      (pXnGetList->XnGetEntry->cbData > pXnGet->cbMaxData) )
	  {
	     pIrp->IoStatus.Information = 0 ;
		 pIrp->IoStatus.Status = STATUS_BUFFER_TOO_SMALL;
	  }
	  else
	  {
	     RtlCopyMemory( pXnGet->pProviderControl, pXnGetList->XnGetEntry->pControl, 
	           	           pXnGetList->XnGetEntry->cbControl);
		 pXnGet->cbControl = pXnGetList->XnGetEntry->cbControl ;
         RtlCopyMemory (pXnGet->pProviderData, pXnGetList->XnGetEntry->pData, 
	        	               pXnGetList->XnGetEntry->cbData);
		 pXnGet->cbData = pXnGetList->XnGetEntry->cbData ;

    	 pIrp->IoStatus.Information = sizeof( XN_GET ) ;
		 pIrp->IoStatus.Status = STATUS_SUCCESS;
	  }

      // let's free our memory.
      if (pXnGetList->XnGetEntry->pControl)
         DigiFreeMem (pXnGetList->XnGetEntry->pControl);
      if (pXnGetList->XnGetEntry->pData)
         DigiFreeMem (pXnGetList->XnGetEntry->pData);
      DigiFreeMem (pXnGetList->XnGetEntry) ;
      DigiFreeMem (pXnGetList) ;

      // insert into completed irp queue
      InsertTailList( &pPortExt->CompleteIrpQ, &pIrp->Tail.Overlay.ListEntry) ;

	  // better unlock our memory.
	  DigiUnlockBuffers(pIrp);
   }
}
	     
/*
 *----------------------------------------------------------------------
 * Function:  AsynSdxbResponses
 *
 * Purpose:
 *
 *    Process S/DXB responses 
 *
 *
 * Method:
 *
 *
 * NOTES:
 *    The Port's Spin-lock must be held when this routine is called.
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynSdxbResponses(
   IN PPORT_EXT   pPortExt        // COM Port
)
{
   PGET_MSG_EV    pGetEvent;

   DigiDump(DIGITHREAD, ("ASYN!SdxbResponses:\n"));

   //
   // Not currently busy AND there is an SDXB-Response in the Queue
   //
   while (!IsListEmpty( &pPortExt->SdxbRspQ))
   {
      pGetEvent = (PGET_MSG_EV)RemoveHeadList( &pPortExt->SdxbRspQ);

      //
      // Process valid response
      //
      if ( (pGetEvent->Status == STATUS_SUCCESS) &&
           ( (pPortExt->State == OpeningState) || 
             (pPortExt->State == OpenState) ||
             (pPortExt->State == ClosingState) ) )
      {
         AsynValidRsp( pPortExt, pGetEvent);
      }
      else
      {
         DigiDump(DIGISDXB |DIGITHREAD, 
                   ("*** ASYN!SdxbResponses Ignoring Get Port=%d State=0x%x Status=0x%x\n",
                    pPortExt->Port, pPortExt->State, pGetEvent->Status));
      }

      //
      // Post the next GetMsg
      //
      if ((pPortExt->State == OpeningState) || 
          (pPortExt->State == OpenState) ||
          (pPortExt->State == ClosingState))
      {
         AsynPostGetMsg( pPortExt, pGetEvent->Status);
      }
      else
      {
         DigiDump(DIGITHREAD, 
                   ("*** ASYN!SdxbResponses DON'T PortGet Port=%d State=0x%x\n",
                    pPortExt->Port, pPortExt->State));
      }
      // Put the event descriptor back in the free pool
      AsynFreeEntry( &pPortExt->GetEventPool, &pGetEvent->ListEntry);
   }
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynSchedulerLoop
 *
 * Purpose:
 *
 *    Process the event queues.
 *
 *  Method:
 *
 * NOTES:
 *    The Port's Spin-lock must be held when this routine is called.
 *
 *  Returns:
 *
 *    o STATUS_PENDING if Port is busy or unable to complete immediately.
 *    o STATUS_SUCCESS if IRP is completed successfuly.
 *    o STATUS_UNSUCCESSFUL on error.
 *
 *
 *----------------------------------------------------------------------
 */
VOID AsynSchedulerLoop(
   IN PPORT_EXT   pPortExt       // Port specific information.
)
{
   ULONG       LoopCount = 0;

   pPortExt->PortBusy++;
   do
   {
      LoopCount++;
         
      AsynDispatchIrps( pPortExt, &pPortExt->DispatchQ);
      AsynSdxbResponses( pPortExt);
      AsynCancelledAndTimedoutIrps( pPortExt);
	  AsynXnGetResponses( pPortExt );

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

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

   } while (!IsListEmpty( &pPortExt->DispatchQ) || 
            !IsListEmpty( &pPortExt->SdxbRspQ) ||
            !IsListEmpty( &pPortExt->CancelledAndTimedoutIrpQ) ||
            !IsListEmpty( &pPortExt->CompleteIrpQ) ||
            (!IsListEmpty( &pPortExt->XnGetQueue) && 
             !IsListEmpty( &pPortExt->XnGetPktQueue)) );

   pPortExt->PortBusy--;
}
