/*
 ****************************************************************************
 * 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 :  sdxb.c
 *
 * Author      :  P. Donovan
 *
 * Purpose     :
 *
 *    Build S/DXB commands.
 *
 *----------------------------------------------------------------------
 */

#include "header.h"

/*
 *----------------------------------------------------------------------
 * Function:  AsynBuildSyncCmd
 *
 * Purpose:
 *    Format a S/DXB Synchronization Command in the supplied Command.
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    Length of the S/DXB Command which is built.
 *
 *
 *----------------------------------------------------------------------
 */
USHORT
AsynBuildSyncCmd(
   PPORT_EXT      pPortExt,
   PSDXB_CMD      pSdxbCmd
)
{
   pSdxbCmd->U.Sync.ctype = SDXB_SYNC;
   pSdxbCmd->U.Sync.flags = DRAIN | SDXB_HIPRI;
//   pSdxbCmd->U.Sync.flags = SDXB_HIPRI;
   pSdxbCmd->U.Sync.sseq = pPortExt->SyncOutSequence++;
   return (sizeof (struct sdxb_sync_command ));
}

/*----------------------------------------------------------------------
 * Function:  AsynBuildBufferSizeCmd
 *
 * Purpose:
 *    Format a S/DXB Buffer Size Command in the supplied Command.
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    Length of the S/DXB Command which is built.
 *
 *
 *----------------------------------------------------------------------
 */
USHORT
AsynBuildBufferSizeCmd(
   PPORT_EXT      pPortExt,
   PSDXB_CMD      pSdxbCmd
)
{
   pSdxbCmd->U.BufferSize.ctype = SDXB_BUFSIZE;
   return (sizeof (struct sdxb_bufsize_command ));
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynBuildModemCmd
 *
 * Purpose:
 *    Format a S/DXB Modem Control Status Command in the supplied Command.
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    Length of the S/DXB Command which is built.
 *
 *
 *----------------------------------------------------------------------
 */
USHORT
AsynBuildModemCmd(
   PPORT_EXT      pPortExt,
   PSDXB_CMD      pSdxbCmd
)
{
   pSdxbCmd->U.ModemControl.ctype = SDXB_MSTAT;
   pSdxbCmd->U.ModemControl.flags = pPortExt->SdxbValues.Mflags;
   pSdxbCmd->U.ModemControl.mint = pPortExt->SdxbValues.Mint;
   pSdxbCmd->U.ModemControl.mout = pPortExt->SdxbValues.Mout;
   
   //
   // Record changes to DTR & RTS immediately
   //
   pPortExt->SdxbValues.Mstat = 
      (pPortExt->SdxbValues.Mstat & ~(SDXB_DTR | SDXB_RTS)) |
      pPortExt->SdxbValues.Mout & (SDXB_DTR | SDXB_RTS);

   return (sizeof (struct sdxb_mstat_command));
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynBuildEventCmd
 *
 * Purpose:
 *    Format an S/DXB Event Status Command in the supplied Command.
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    Length of the S/DXB Command which is built.
 *
 *
 *----------------------------------------------------------------------
 */
USHORT
AsynBuildEventCmd(
   PPORT_EXT      pPortExt,
   PSDXB_CMD      pSdxbCmd
)
{
   pSdxbCmd->U.EventStatus.ctype = SDXB_EVENT;
   pSdxbCmd->U.EventStatus.flags = 0;
   pSdxbCmd->U.EventStatus.eint = pPortExt->SdxbValues.Eint;
   return (sizeof (struct sdxb_event_command));
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynBuildLineErrorCmd
 *
 * Purpose:
 *    Format a S/DXB Line Error Status Command in the supplied Command.
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    Length of the S/DXB Command which is built.
 *
 *
 *----------------------------------------------------------------------
 */
USHORT
AsynBuildLineErrorCmd(
   PPORT_EXT      pPortExt,
   PSDXB_CMD      pSdxbCmd
)
{
   pSdxbCmd->U.LineError.ctype = SDXB_LERR;
   pSdxbCmd->U.LineError.flags = pPortExt->SdxbValues.LerrFlags;
   pSdxbCmd->U.LineError.ltime = pPortExt->SdxbValues.Ltime;
   return (sizeof (struct sdxb_lerr_command));
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynBuildSetPortCmd
 *
 * Purpose:
 *    Format a S/DXB Port Parameters Command in the supplied Command.
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    Length of the S/DXB Command which is built.
 *
 *
 *----------------------------------------------------------------------
 */
USHORT
AsynBuildSetPortCmd(
   PPORT_EXT      pPortExt,
   PSDXB_CMD      pSdxbCmd
)
{
   pSdxbCmd->U.PortParameters.ctype = SDXB_PARAM;
   pSdxbCmd->U.PortParameters.cflags = pPortExt->SdxbValues.Cflags;
   pSdxbCmd->U.PortParameters.iflags = pPortExt->SdxbValues.Iflags;
   pSdxbCmd->U.PortParameters.oflags = pPortExt->SdxbValues.Oflags;
   pSdxbCmd->U.PortParameters.xflags = pPortExt->SdxbValues.Xflags;
   pSdxbCmd->U.PortParameters.fflags = 0;
   pSdxbCmd->U.PortParameters.txaccm = 0;
   pSdxbCmd->U.PortParameters.rxaccm = 0;
   pSdxbCmd->U.PortParameters.speed = pPortExt->SdxbValues.Speed;
   pSdxbCmd->U.PortParameters.mflow = pPortExt->SdxbValues.Mflow;
   pSdxbCmd->U.PortParameters.mctrl = pPortExt->SdxbValues.Mctrl;
   pSdxbCmd->U.PortParameters.xon = pPortExt->SpecialChars.XonChar;
   pSdxbCmd->U.PortParameters.xoff = pPortExt->SpecialChars.XoffChar;
   pSdxbCmd->U.PortParameters.xxon = 0;
   pSdxbCmd->U.PortParameters.xxoff = 0;
   pSdxbCmd->U.PortParameters.lnext = 0;
   pSdxbCmd->U.PortParameters.spcr = pPortExt->SpecialChars.EventChar;
   pSdxbCmd->U.PortParameters.mspesc = pPortExt->EscapeChar;
   pSdxbCmd->U.PortParameters.idel = 0;
   pSdxbCmd->U.PortParameters.msbr = pPortExt->SpecialChars.BreakChar;  
   pSdxbCmd->U.PortParameters.mser = pPortExt->SpecialChars.ErrorChar; 
   pSdxbCmd->U.PortParameters.maxlen = pPortExt->SdxbValues.Maxlen;
   return (sizeof (struct sdxb_param_command));
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynBuildSeqNumberCmd
 *
 * Purpose:
 *    Format a S/DXB Port Parameters Command in the supplied Command.
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    Length of the S/DXB Command which is built.
 *
 *
 *----------------------------------------------------------------------
 */
USHORT
AsynBuildSeqNumberCmd(
   PPORT_EXT      pPortExt,
   PSDXB_CMD      pSdxbCmd
)
{
   pSdxbCmd->U.SequenceNumber.ctype = SDXB_SEQ;
   pSdxbCmd->U.SequenceNumber.flags = 0;
   pSdxbCmd->U.SequenceNumber.rmax = pPortExt->SdxbValues.Rmax;
   pSdxbCmd->U.SequenceNumber.rtime = pPortExt->SdxbValues.Rtime;
   pSdxbCmd->U.SequenceNumber.tmax = pPortExt->SdxbValues.Tmax;
   pSdxbCmd->U.SequenceNumber.ttime = pPortExt->SdxbValues.Ttime;
   return (sizeof (struct sdxb_seq_command ));
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynBuildBreakCmd
 *
 * Purpose:
 *    Format a S/DXB Break Command in the supplied Command.
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    Length of the S/DXB Command which is built.
 *
 *
 *----------------------------------------------------------------------
 */
USHORT
AsynBuildBreakCmd(
   PPORT_EXT      pPortExt,
   PSDXB_CMD      pSdxbCmd
)
{
   pSdxbCmd->U.SendBreak.ctype = SDXB_BREAK;
   if (pPortExt->OpenStateMask & BreakOnMask)
      pSdxbCmd->U.SendBreak.btime = 0xffff;
   else
      pSdxbCmd->U.SendBreak.btime = 0;
   return (sizeof (struct sdxb_break_command));
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynBuildImmedCharCmd
 *
 * Purpose:
 *    Format a S/DXB Send Immediate Char Command in the supplied Command.
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    Length of the S/DXB Command which is built.
 *
 *
 *----------------------------------------------------------------------
 */
USHORT
AsynBuildImmedCharCmd(
   PPORT_EXT      pPortExt,
   PSDXB_CMD      pSdxbCmd
)
{
   pSdxbCmd->U.SendImmediate.ctype = SDXB_SCIMMED;
   pSdxbCmd->U.SendImmediate.sic = pPortExt->SendImmChar;
   return (sizeof (struct sdxb_scimmed_command));
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynBuildFlush
 *
 * Purpose:
 *    Format a S/DXB Flush Command in the supplied Command.
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    Length of the S/DXB Command which is built.
 *
 *
 *----------------------------------------------------------------------
 */
USHORT
AsynBuildFlush(
   PPORT_EXT      pPortExt,
   PSDXB_CMD      pSdxbCmd
)
{
   pSdxbCmd->U.FlushBuffers.ctype = SDXB_FLUSH;
   pSdxbCmd->U.FlushBuffers.flags = pPortExt->SdxbValues.FlushFlags;
   return (sizeof (struct sdxb_flush_command ));
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynBuildPause
 *
 * Purpose:
 *    Format a S/DXB Pause Command in the supplied Command.
 *
 *
 *  Method:
 *
 *
 *  Returns:
 *    Length of the S/DXB Command which is built.
 *
 *
 *----------------------------------------------------------------------
 */
USHORT
AsynBuildPauseCmd(
   PPORT_EXT      pPortExt,
   PSDXB_CMD      pSdxbCmd
)
{
   pSdxbCmd->U.Pause.ctype = SDXB_PAUSE;
   pSdxbCmd->U.Pause.pset = pPortExt->SdxbValues.Pset;
   pSdxbCmd->U.Pause.preset = pPortExt->SdxbValues.Preset;
   return (sizeof (struct sdxb_pause_command));
}



/*
 ***********************************************************************
 *
 * S/DXB Process Responses Routines
 *
 ***********************************************************************
 */

/*
 *----------------------------------------------------------------------
 * Function:  AsynProcessSync
 *
 * Purpose:
 *    Process an S/DXB Sync response
 *
 *
 *  Method:
 *    o Traverse the Port's Put Q searching for SYNC requests
 *    o Any SYNC requests with a sequence number <= the sequence number of
 *      the SYNC response are deemed to be completed. This involves :-
 *       o Completing any associated IRP with SUCCESS
 *       o Based on the source of the request, perform actions specific
 *         to that request.  e.g. for OPEN set the port state to OpenedState
 *       o Returning the PutEvent to the Free Pool.
 *    o If Wait Event Mask is set check completion of a pending wait.
 *      In particular we are looking for Waits on transmit data complete.
 *
 *
 *  Returns:
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynProcessSync(
   PPORT_EXT      pPortExt,
   PVOID          Context
)
{
   PSYNC_RSP_SDXB pSync;
   PLIST_ENTRY    pEntry;
   PLIST_ENTRY    pEntryNext;
   PPUT_MSG_EV    pPutEvent;
#if DBG   
   BOOLEAN        fDebugFoundPut = FALSE;
#endif

   pSync = (PSYNC_RSP_SDXB)Context;

   DigiDump(DIGISDXB | DIGIFLOW, ("ASYN!SDXB: RX Port=%d SYNC #=0x%x\n",
                          pPortExt->Port, pSync->sseq));

   //
   // Keep applying this sync to all appropriate Put Descriptors,
   // or until this sync causes a close.
   for ( pEntry = pPortExt->PutQ.Flink; pPortExt && (pEntry != &pPortExt->PutQ);
      pEntry = pEntryNext)
   {

      pEntryNext = pEntry->Flink;
      pPutEvent = (PPUT_MSG_EV)pEntry;
//      if ((pPutEvent->Command == SYNC_CMD) || (pPutEvent->Command == DATA_CMD))
      {
         // Found originating request
         if ((SHORT)(pSync->sseq - pPutEvent->Sequence) >= 0)
         {
            PIRP        pCompleteNextWriteIrp = NULL;
            PIRP        pCompleteIrp = NULL;
#if DBG
            fDebugFoundPut = TRUE;
#endif            
            //
            // Actions specific to originating SYNC_CMD
            //
            switch (pPutEvent->Reason)
            {
               case REASON_OPEN:
                  if (pPortExt->State == OpeningState)  {

                     // We can get sync response with Seq number
                     // that matches an Event with Reason 0 (which 
                     // is REASON_OPEN).  Sanity check to ensure
                     // that we only change the state when we expect
                     // to change it.  Irp is always NULL if it's a
                     // "false" REASON_OPEN.  -RFM
 
                     pPortExt->State = OpenState;
                  } 
                  pPutEvent->State = PutEnded;
                  if (pPutEvent->Irp)
                  {
                     DigiDump(DIGIOPEN | DIGIIRP, ("ASYN!ProcessSync: Completing OPEN Port=%d IRP=0x%x\n",
                                          pPortExt->Port, pPutEvent->Irp));
                     pCompleteIrp = pPutEvent->Irp;
                  }
                  break;
               case REASON_CLOSE:
                  pPutEvent->State = PutEnded;
                  DigiDump(DIGIOPEN | DIGIIRP, ("ASYN!ProcessSync: Completing Close Port=%d IRP=0x%x\n",
                                          pPortExt->Port, pPutEvent->Irp));

                  RemoveEntryList( &pPutEvent->ListEntry);
                  AsynFreeEntry( &pPortExt->PutEventPool, &pPutEvent->ListEntry);
                  AsynCloseRsp( pPortExt);
                  //
                  // The Port is now closed and the Put Event descriptor is
                  // in the free pool.
                  // Do not refer to these again, this will mean that we break
                  // out of our for loop.
                  pPutEvent = NULL;
                  pPortExt = NULL;
                  break;
               case REASON_WRITE:
               case REASON_FLUSH:
                  {
                     PIRP        pNextWriteIrp = NULL;

                     if (pPutEvent->State == PutAwaitingCompletion)
                     {
                        // The Completion routine has not been called.  This is
                        // very early to receive this response!  But that's okay
                        // when the completion routine is called the work order
                        // will end.
                        pPutEvent->State = PutAwaitingCompletionResponseRxed;
                     }
                     else
                     {
                        pPutEvent->State = PutEnded;
                     }
                     if (pPutEvent->Irp)
                     {
                        DigiDump(DIGIFLOW, ("ASYN!ProcessSync: Completing WRITE/FLUSH Port=%d IRP=0x%x\n",
                                             pPortExt->Port, pPutEvent->Irp));
                        pCompleteIrp = pPutEvent->Irp;
                        pCompleteIrp->IoStatus.Status = pPutEvent->Status;
                        if (pPutEvent->Reason == REASON_WRITE) {
                           if (pPortExt->OpenStateMask & TxDataPurgeMask) {
                              // This SYNC operation posted because of a
                              // SDXB_FLUSH_OUTPUT operation.  Because
                              // of the flush, not all the data was
                              // written (probably).  A problem here
                              // is that we don't know precisely how
                              // much data is written, so for now I'll
                              // just make up a number.

                              pPortExt->OpenStateMask &= ~TxDataPurgeMask;
                              pCompleteIrp->IoStatus.Information =
                                   (IoGetCurrentIrpStackLocation(
                                   pPutEvent->Irp)->Parameters.Write.Length
                                   / 100
                              );
                           } else {
                              // This SYNC operation finished because
                              // the write associated with it completed.

                              pCompleteIrp->IoStatus.Information =
                                 IoGetCurrentIrpStackLocation(
                                 pPutEvent->Irp)->Parameters.Write.Length;
                           }
                        } else {
                           // this is REASON_FLUSH
                           pCompleteIrp->IoStatus.Information = 0;
                        }
                     }
   
                     if (pPutEvent == pPortExt->TimedWritePutEvent)
                     {
                         KeCancelTimer( &pPortExt->Timer.Write);
                         pPortExt->TimedWritePutEvent = NULL;
                     }
                     if (pPutEvent->Reason == REASON_WRITE)
                     {
                        if (pPortExt->WaitMask & SERIAL_EV_TXEMPTY)
                        {
                           pPortExt->HistoryMask |= SERIAL_EV_TXEMPTY;
                        }
                        //
                        // Update the Sequence numbers
                        //
                        if (pPortExt->TxBytesPending)
                        {
                           ULONG    TxTotal;
      
                           TxTotal = pPutEvent->PendingTxBytes;
                           pPortExt->TxBytesPending -= TxTotal;
                           pPortExt->TotalWriteBytes += TxTotal;
                           pPortExt->SdxbValues.ToutLast = pPortExt->SdxbValues.Tout;
                           pPortExt->SdxbValues.Tout += TxTotal;
#if DBG
                           if (pPortExt->TxBytesPending >= 0xffff0000) {
                              DigiDump(DIGIALL, ("*** Invalid TxBytesPending=0x%x Port=%d, TxTotal=0x%x\n", 
                                                pPortExt->TxBytesPending, pPortExt->Port, TxTotal));
                              PORT_ERROR(pPortExt);
                              if (gDebugMask)
                              {
                                 DbgBreakPoint();
                              }
                              //pPortExt->TxBytesPending = 0;
                           }
#endif
                        }
                     }

                     // If this is the only WRITE in the Put Queue,
                     // then TxBytesPending should now be zero!
                     ASSERT( !pPortExt->TxBytesPending || 
                             ( pPortExt->TxBytesPending && 
                                 !IsListEmpty(&pPortExt->PutQ)));

                     //
                     // See if we should start up one of the Queued writes
                     //
                     if (!IsListEmpty(&pPortExt->WriteQueue))
                     {
                        AsynPortWrite( pPortExt);
                     }
                     break;
                  }
               case REASON_SET_WAIT:
                  if (pPortExt->DxbCount > 0)
                     pPortExt->DxbCount--;    // ioctl overflow protection
                  // fall through
               case REASON_SET_CHARS:
               case REASON_SET_HANDFLOW:
               case REASON_SET_LSRMST:
                  if (pPutEvent->State == PutAwaitingCompletion)
                  {
                     pPutEvent->State = PutAwaitingCompletionResponseRxed;
                  }
                  else
                  {
                     // PutAwaitingCompletionResponseRxed
                     // The response has already been received.
                     pPutEvent->State = PutEnded;
                     if (pPutEvent->Irp)
                     {
                        DigiDump(DIGIFLOW, ("ASYN!ProcessSync: Completing SET Port=%d IRP=0x%x\n",
                                             pPortExt->Port, pPutEvent->Irp));
                        pCompleteIrp = pPutEvent->Irp;
                     }
                  }
						if ( REASON_SET_HANDFLOW == pPutEvent->Reason ) 
						{
							if ( pPortExt->DxbCount > 0 )

							{
								pPortExt->DxbCount--;
							}
						}
                  break;
               case REASON_PURGE:
                  if (pPutEvent->State == PutAwaitingCompletion)
                  {
                     pPutEvent->State = PutAwaitingCompletionResponseRxed;
                  }
                  else
                  {
                     // PutAwaitingCompletionResponseRxed
                     // The response has already been received.
                     pPutEvent->State = PutEnded;
                     if (pPutEvent->Irp)
                     {
                        DigiDump(DIGIFLOW, ("ASYN!ProcessSync: Completing PURGE Port=%d IRP=0x%x\n",
                                             pPortExt->Port, pPutEvent->Irp));
                        pCompleteIrp = pPutEvent->Irp;
                     }
                  }
                  pPortExt->OpenStateMask &= ~(RxDataPurgeMask | TxDataPurgeMask);
                  
                  //
                  // See if we should start up one of the Queued writes
                  //
                  if (!IsListEmpty(&pPortExt->WriteQueue))
                  {
                     AsynPortWrite( pPortExt);
                  }
                  break;

               case REASON_SET_DTR:
               case REASON_CLR_DTR:
               case REASON_SET_RTS:
               case REASON_CLR_RTS:
                  if (pPutEvent->State == PutAwaitingCompletion)
                  {
                     pPutEvent->State = PutAwaitingCompletionResponseRxed;
                  }
                  else
                  {
                     // PutAwaitingCompletionResponseRxed
                     // The response has already been received.
                     pPutEvent->State = PutEnded;
                     if (pPutEvent->Irp)
                     {
                        DigiDump(DIGIFLOW, ("ASYN!ProcessSync: Completing SET/CLR DTR/RTS Port=%d IRP=0x%x\n",
                                             pPortExt->Port, pPutEvent->Irp));
                        pCompleteIrp = pPutEvent->Irp;
                     }
                  }
                  break;

               case REASON_LERR:
               case REASON_PAUSE:
               case REASON_UNPAUSE:
                  if (pPortExt->DxbCount > 0)
                     pPortExt->DxbCount--;    // ioctl overflow protection
						// intentionally falling through to default code.

               default:
                  if (pPutEvent->State == PutAwaitingCompletion)
                  {
                     DigiDump(DIGIFLOW1, ("Sync: P=%d pPut=0x%x state==PutAwaitingCompletionResponseRxed\n",
                                          pPortExt->Port, pPutEvent));
                     pPutEvent->State = PutAwaitingCompletionResponseRxed;
                  }
                  else
                  {
                     pPutEvent->State = PutEnded;
                  }
                  break;
            }
            if (pPortExt)
            {
               if (pCompleteIrp)
               {
                  pPutEvent->Irp = NULL;
                  pCompleteIrp->IoStatus.Status = STATUS_SUCCESS;
                  InsertTailList( &pPortExt->CompleteIrpQ, 
                                  &pCompleteIrp->Tail.Overlay.ListEntry);
               }
               // Put the Put Event descriptor back in the free pool
               if (pPutEvent && (pPutEvent->State == PutEnded))
               {
                  DigiDump(DIGIFLOW1, ("Sync: RX Sync P=%d Free pPut=0x%x\n",
                                       pPortExt->Port, pPutEvent));
                  RemoveEntryList( &pPutEvent->ListEntry);
                  AsynFreeEntry( &pPortExt->PutEventPool, &pPutEvent->ListEntry);
               }
            }
         }
      }
   }
#if DBG   
   if (!fDebugFoundPut)
   {
      if (pPortExt->State != OpenState)
      {
         DigiDump(DIGIALL, ("ASYN!ProcessSync: Ignoring Port=0x%x Sseq=0x%x, InSeq=0x%x PortState=0x%x\n",
            pPortExt->Port, pSync->sseq, pPortExt->SyncInSequence, pPortExt->State));
      }
      else
      {
         if (pPortExt->OpenStateMask & TxDataPurgeMask)
         {
            DigiDump(DIGIALL, ("ASYN!ProcessSync: TxPurgeState: Ignoring Port=0x%x Sseq=0x%x, InSeq=0x%x PortState=0x%x\n",
               pPortExt->Port, pSync->sseq, pPortExt->SyncInSequence, pPortExt->State));
         }
         else
         {
 				// Removed error handling code here.  This is now expected behavior when 
				// we kickstart the receive engine after a read during Rx80Full condition.
         }
      }
   }
#endif
   //
   // This will not apply to a Close-Rsp, which by now will have closed the
   // Port.
   if (pPortExt)
   {
      pPortExt->SyncInSequence = pSync->sseq;
      if (pPortExt->WaitMaskIrp)
      {
          AsynCheckWaitEvents( pPortExt);
      }
   }
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynProcessBufferSize
 *
 * Purpose:
 *    Process an S/DXB Buffer Size response
 *
 *
 *  Method:
 *    o Save the Buffer Size parameters in the Port structure.
 *
 *
 *  Returns:
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynProcessBufferSize(
   PPORT_EXT      pPortExt,
   PVOID          Context
)
{
   PBUFFER_SIZE_RSP_SDXB    pBufferSize;

   pBufferSize = (PBUFFER_SIZE_RSP_SDXB)Context;
   DigiDump(DIGISDXB | DIGIFLOW, ("ASYN!SDXB: RX Port=%d BUF_SIZE In=0x%x\n",
                          pPortExt->Port, pBufferSize->insize));

   pPortExt->SdxbValues.Insize = pBufferSize->insize;
   pPortExt->SdxbValues.Outsize = pBufferSize->outsize;
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynProcessModemCtrl
 *
 * Purpose:
 *    Process an S/DXB Modem Control Status response
 *
 *
 *  Method:
 *    o Save the Modem Status in the Port structure.
 *    o If Wait Event Mask is set check completion of a pending wait.
 *      In particular we are looking for Waits on Modem Status changes.
 *
 *
 *  Returns:
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynProcessModemCtrl(
   PPORT_EXT      pPortExt,
   PVOID          Context
)
{
   PMODEM_CTRL_RSP_SDXB
                  pModemCtrl;

   pModemCtrl = (PMODEM_CTRL_RSP_SDXB)Context;
   DigiDump(DIGISDXB | DIGIFLOW, ("ASYN!SDXB: RX Port=%d MODM_CTRL Mstat=0x%x History=0x%x\n",
                          pPortExt->Port, pModemCtrl->mstat, pPortExt->HistoryMask));

   pPortExt->SdxbValues.Mlast = pPortExt->SdxbValues.Mstat;
   pPortExt->SdxbValues.Mstat = pModemCtrl->mstat;

//------------------------------------------------------------ MLZ 12/22/98 {
// Added to monitor RI.  Currently it is not being sent by firmware.
	if ( pModemCtrl->mstat & SDXB_RI )
	{
		DigiDump( DIGIALL, ( "Asyn!Modem: RI received on port %d\n", pPortExt->Port ) );
	}
//------------------------------------------------------------ MLZ 12/22/98 }

   if ( pPortExt->SdxbValues.Mlast != pPortExt->SdxbValues.Mstat)
   {
      if (!IsListEmpty( &pPortExt->Trace.Queue))
      {
         AsynTraceModemStatus( pPortExt);
      }
   }
   if (pPortExt->WaitMask)
   {
      if (pPortExt->WaitMask & SERIAL_EV_CTS)
      {
         if ( (pPortExt->SdxbValues.Mstat & SDXB_CTS) != 
            (pPortExt->SdxbValues.Mlast & SDXB_CTS) )
         {
            pPortExt->HistoryMask |= SERIAL_EV_CTS;
         }
      }
   
      if (pPortExt->WaitMask & SERIAL_EV_DSR)
      {
         if ( (pPortExt->SdxbValues.Mstat & SDXB_DSR) != 
            (pPortExt->SdxbValues.Mlast & SDXB_DSR) )
         {
            pPortExt->HistoryMask |= SERIAL_EV_DSR;
         }
      }
      if (pPortExt->WaitMask & SERIAL_EV_RLSD)
      {
         if ( (pPortExt->SdxbValues.Mstat & SDXB_DCD) != 
            (pPortExt->SdxbValues.Mlast & SDXB_DCD) )
         {
            pPortExt->HistoryMask |= SERIAL_EV_RLSD;
         }
      }
      if (pPortExt->WaitMask & SERIAL_EV_RING)
      {
         if ( (pPortExt->SdxbValues.Mstat & SDXB_RI) != 
            (pPortExt->SdxbValues.Mlast & SDXB_RI) )
         {
            pPortExt->HistoryMask |= SERIAL_EV_RING;
         }
      }
   
      if (pPortExt->WaitMaskIrp)
      {
          AsynCheckWaitEvents( pPortExt);
      }
   }
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynProcessEvent
 *
 * Purpose:
 *    Process an S/DXB Event Status response
 *
 *
 *  Method:
 *    o Save the Event Status in the Port structure.
 *
 *
 *  Returns:
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynProcessEvent(
   PPORT_EXT      pPortExt,
   PVOID          Context
)
{
   PEVENT_RSP_SDXB   pEvent;

   pEvent = (PEVENT_RSP_SDXB)Context;

   DigiDump(DIGISDXB | DIGIFLOW, ("ASYN!SDXB: RX Port=%d EVENT Estat=0x%x\n",
                          pPortExt->Port, pEvent->estat));
   pPortExt->SdxbValues.Elast = pPortExt->SdxbValues.Estat;
   pPortExt->SdxbValues.Estat = pEvent->estat;

   if ((pPortExt->OpenStateMask & ImmWriteMask) && 
      !(pPortExt->SdxbValues.Estat & SDXB_TXI))
   {
      pPortExt->OpenStateMask &= ~ImmWriteMask;

      AsynFindAndCompletePut( pPortExt, SDXB_SCIMMED, STATUS_SUCCESS);
   }
}


/*
 *----------------------------------------------------------------------
 * Function:  AsynProcessLineErr
 *
 * Purpose:
 *    Process an S/DXB Line Error response
 *
 *
 *  Method:
 *    o Save the Line Error Status in the Port structure.
 *    o If Wait Event Mask is set check completion of a pending wait.
 *      In particular we are looking for Waits on line errors.
 *
 *
 *  Returns:
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynProcessLineErr(
   PPORT_EXT      pPortExt,
   PVOID          Context
)
{
   PLINE_ERR_RSP_SDXB   pLineErr;

   pLineErr = (PLINE_ERR_RSP_SDXB)Context;

   DigiDump(DIGISDXB | DIGIFLOW, ("ASYN!SDXB: RX Port=%d LIN_ERR Np=0x%x\n",
                          pPortExt->Port, pLineErr->nparity));

   pPortExt->SdxbValues.LerrFlags = 0;
   pPortExt->SdxbValues.NorunLast = pPortExt->SdxbValues.Norun;
   pPortExt->SdxbValues.NoflowLast = pPortExt->SdxbValues.Noflow;
   pPortExt->SdxbValues.NframeLast = pPortExt->SdxbValues.Nframe;
   pPortExt->SdxbValues.NparityLast = pPortExt->SdxbValues.Nparity;
   pPortExt->SdxbValues.NbreakLast = pPortExt->SdxbValues.Nbreak;

   pPortExt->SdxbValues.Norun = pLineErr->norun;
   pPortExt->SdxbValues.Noflow = pLineErr->noflow;
   pPortExt->SdxbValues.Nframe = pLineErr->nframe;
   pPortExt->SdxbValues.Nparity = pLineErr->nparity;
   pPortExt->SdxbValues.Nbreak = pLineErr->nbreak;

	//
	// Update CommErr counters
	//

	pPortExt->CommErr.Overrun +=
		(USHORT)(pPortExt->SdxbValues.Norun - pPortExt->SdxbValues.NorunLast);

	pPortExt->CommErr.Framing +=
		(USHORT)(pPortExt->SdxbValues.Nframe - pPortExt->SdxbValues.NframeLast);

	pPortExt->CommErr.Parity +=
		(USHORT)(pPortExt->SdxbValues.Nparity - pPortExt->SdxbValues.NparityLast);

	pPortExt->CommErr.Break +=
		(USHORT)(pPortExt->SdxbValues.Nbreak - pPortExt->SdxbValues.NbreakLast);

   //
   // Update Performance Statistics as necessary
   //
   if (pPortExt->SdxbValues.Norun != pPortExt->SdxbValues.NorunLast)
   {
      pPortExt->PerfStats.SerialOverrunErrorCount += 
         pPortExt->SdxbValues.Norun - pPortExt->SdxbValues.NorunLast;
   }
   if (pPortExt->SdxbValues.Nframe != pPortExt->SdxbValues.NframeLast)
   {
      pPortExt->PerfStats.FrameErrorCount += 
         pPortExt->SdxbValues.Nframe - pPortExt->SdxbValues.NframeLast;
   }
   if (pPortExt->SdxbValues.Nparity != pPortExt->SdxbValues.NparityLast)
   {
      pPortExt->PerfStats.ParityErrorCount += 
         pPortExt->SdxbValues.Nparity - pPortExt->SdxbValues.NparityLast;
   }

   //
   // Update the Wait Event Mask
   //
   if (pPortExt->WaitMask & SERIAL_EV_ERR)
   {
      if ((pPortExt->SdxbValues.Norun != pPortExt->SdxbValues.NorunLast) ||
          (pPortExt->SdxbValues.Noflow != pPortExt->SdxbValues.NoflowLast) ||
          (pPortExt->SdxbValues.Nframe != pPortExt->SdxbValues.NframeLast) ||
          (pPortExt->SdxbValues.Nparity != pPortExt->SdxbValues.NparityLast))
      {
         pPortExt->HistoryMask |= SERIAL_EV_ERR;
      }
   }
   if (pPortExt->WaitMask & SERIAL_EV_BREAK)
   {
      if ((pPortExt->SdxbValues.Nbreak != pPortExt->SdxbValues.NbreakLast))
      {
         pPortExt->HistoryMask |= SERIAL_EV_BREAK;
      }
   }
   if (pPortExt->WaitMaskIrp)
   {
       AsynCheckWaitEvents( pPortExt);
   }
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynProcessPortCap
 *
 * Purpose:
 *    Process an S/DXB Port Capabilities response
 *
 *
 *  Method:
 *    o Save the Port Capabilities in the Port structure.
 *
 *
 *  Returns:
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynProcessPortCap(
   PPORT_EXT      pPortExt,
   PVOID          Context
)
{
   // At present we ignore this

   DigiDump(DIGISDXB | DIGIFLOW, ("ASYN!SDXB: RX Port=%d PORT_CAP \n",
                          pPortExt->Port));
}


/*
 *----------------------------------------------------------------------
 * Function:  AsynProcessPortParam
 *
 * Purpose:
 *    Process an S/DXB Port Parameter response
 *
 *
 *  Method:
 *    o Ignore this response.
 *
 *
 *  Returns:
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynProcessPortParam(
   PPORT_EXT      pPortExt,
   PVOID          Context
)
{
   PPORT_PARAM_RSP_SDXB_COMP pPortParam;

   pPortParam = (PPORT_PARAM_RSP_SDXB_COMP)Context;

   DigiDump(DIGISDXB | DIGIFLOW, ("ASYN!SDXB: RX Port=%d PORT_PARM Speed=%d\n",
                          pPortExt->Port, pPortParam->speed));
   if (pPortExt->SdxbValues.Speed != pPortParam->speed)
   {
      pPortExt->SdxbValues.Speed = pPortParam->speed;	// copy actual bit rate
      
      DigiDump(DIGIALL, ("ASYN!SDXB: Port=%d ***Speed Mismatch*** wanted=%d got=%d\n",
                             pPortExt->Port, pPortExt->SdxbValues.Speed, pPortParam->speed));
   }

}

/*
 *----------------------------------------------------------------------
 * Function:  AsynProcessSeqNum
 *
 * Purpose:
 *    Process an S/DXB Sequence Number response
 *
 *
 *  Method:
 *    o Save the Sequence Number parameters in the Port structure.
 *    o Reflect the number of bytes transmitted in the current Write IRP.
 *
 *
 *  Returns:
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynProcessSeqNum(
   PPORT_EXT      pPortExt,
   PVOID          Context
)
{
   PPUT_MSG_EV       pPutEvent;
   PLIST_ENTRY       pEntry;
   PLIST_ENTRY       pEntryNext;
   PSEQ_NUM_RSP_SDXB pSeqNum;
   ULONG             TxLength;

   pSeqNum = (PSEQ_NUM_RSP_SDXB)Context;

   DigiDump(DIGISDXB | DIGIFLOW, ("ASYN!SDXB: RX Port=%d SEQ_NUM Rin=0x%x Tout=0x%x\n",
                          pPortExt->Port, pSeqNum->rin, pSeqNum->tout));

   pPortExt->SdxbValues.RinLast = pPortExt->SdxbValues.Rin;
   pPortExt->SdxbValues.ToutLast = pPortExt->SdxbValues.Tout;
   pPortExt->SdxbValues.Rin = pSeqNum->rin;
   pPortExt->SdxbValues.Tout = pSeqNum->tout;

   TxLength = pPortExt->SdxbValues.Tout - pPortExt->SdxbValues.ToutLast;
   if (pPortExt->TxBytesPending)
   {
      pPortExt->TxBytesPending -= TxLength;
      pPortExt->TotalWriteBytes += TxLength;
   }

   //
   // Update the "bytes transmitted" field in Write IRPs
   //
   for ( pEntry = pPortExt->PutQ.Flink; 
         (pEntry != &pPortExt->PutQ) && TxLength; pEntry = pEntryNext)
   {
      pEntryNext = pEntry->Flink;
      pPutEvent = (PPUT_MSG_EV)pEntry;
      if (pPutEvent->Command == DATA_CMD)
      {
         //
         // Record the number of bytes successfully transmitted in the IRP.
         //
         if (pPutEvent->PendingTxBytes)
         {
            ULONG    ThisTx;

            if (pPutEvent->PendingTxBytes > TxLength) 
            {
               pPutEvent->PendingTxBytes -= TxLength;
               ThisTx = TxLength;
               TxLength = 0;
            }
            else
            {
               TxLength -= pPutEvent->PendingTxBytes;
               ThisTx = pPutEvent->PendingTxBytes;
               pPutEvent->PendingTxBytes = 0;
            }
            if (pPutEvent->Irp)
            {
               pPutEvent->Irp->IoStatus.Information += ThisTx;
            }
         }
      }
   }
	//
	// See if we should start up one of the Queued writes
	//
	if (!IsListEmpty(&pPortExt->WriteQueue))
	{
		AsynPortWrite( pPortExt);
	}
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynProcessSpecialChar
 *
 * Purpose:
 *    Process an S/DXB Special Character response
 *
 *
 *  Method:
 *    o If Wait Event Mask is set check completion of a pending wait.
 *      In particular we are looking for Waits on reception of Special Character.
 *
 *
 *  Returns:
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynProcessSpecialChar(
   PPORT_EXT      pPortExt,
   PVOID          Context
)
{
   DigiDump(DIGISDXB | DIGIFLOW, ("ASYN!SDXB: RX Port=%d SPX_CHAR \n", pPortExt->Port));

   pPortExt->OpenStateMask |= SpecialCharRxedMask;

   //
   // This wait event will be acted upon when we get the next data block.
   //

}

/*
 *----------------------------------------------------------------------
 * Function:  AsynProcessProtocolError
 *
 * Purpose:
 *    Process an S/DXB Protocol Error
 *
 *
 *  Method:
 *    o Display a debug message.
 *
 *
 *  Returns:
 *    void
 *
 *
 *----------------------------------------------------------------------
 */
void
AsynProcessProtocolError(
   PPORT_EXT      pPortExt,
   PVOID          Context
)
{
   DigiDump(DIGIALL, ("***ASYN!SDXB: RX Port=%d PROTO_ERR Cflag=0x%x\n",
                          pPortExt->Port, ((PSDXB_CMD_HEAD)Context)->Ctype));
   PORT_ERROR(pPortExt);
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynCompressSdxbRsp
 *
 * Purpose:
 *
 *    Validate an S/Dxb Response and pack it into an S/Dxb Compressed
 *    Response.
 *    The only differences between a response and a compressed response is
 *    1. A compressed response does not support Protocol-Error type.
 *    2. The Port-Parameters Response in compressed format is reduced to
 *       just the header and the speed.
 *
 * Method:
 *    o Copy and validate response into our local data area
 *
 * Returns:
 *
 *    OK    - Non zero length of compressed Response.
 *    BAD   - Zero
 *
 *
 *----------------------------------------------------------------------
 */
ULONG
AsynCompressSdxbRsp(
   IN PPORT_EXT         pPortExt,
   IN PVOID             pControlBuffer,
   IN ULONG             ControlLength,
   OUT PSDXB_COMP_RSP   pRsp
)
{
   ULONG                RspLength = 0;
   NTSTATUS             Status;

//   DigiDump(DIGIFLOW, ("ASYN!AsynCompressSdxbRsp: Port=%d\n", pPortExt->Port));

   if (ControlLength == 0)
   {
      PORT_ERROR(pPortExt);
   }
   else
   {
      switch ( ((PSDXB_CMD_HEAD)pControlBuffer)->Ctype)
      {
         case SDXB_SYNC:
            Status = AsynSdxbValidateLength( pPortExt, ControlLength,
                                             sizeof(pRsp->U.Sync), "SYNC");
            if (Status == STATUS_SUCCESS)
            {
               RtlCopyMemory( &pRsp->U, pControlBuffer, ControlLength);
               RspLength = ControlLength;
            }
            break;
         case SDXB_BUFSIZE:
            Status = AsynSdxbValidateLength( pPortExt, ControlLength,
                                             sizeof(pRsp->U.Buffer), "BUF_SIZE");
            if (Status == STATUS_SUCCESS)
            {
               RtlCopyMemory( &pRsp->U, pControlBuffer, ControlLength);
               RspLength = ControlLength;
            }
            break;
         case SDXB_MSTAT:
            Status = AsynSdxbValidateLength( pPortExt, ControlLength,
                                             sizeof(pRsp->U.Modem), "MODEM_CTRL");
            if (Status == STATUS_SUCCESS)
            {
               RtlCopyMemory( &pRsp->U, pControlBuffer, ControlLength);
               RspLength = ControlLength;
            }
            break;
         case SDXB_EVENT:
            Status = AsynSdxbValidateLength( pPortExt, ControlLength,
                                             sizeof(pRsp->U.Event), "EVENT");
            if (Status == STATUS_SUCCESS)
            {
               RtlCopyMemory( &pRsp->U, pControlBuffer, ControlLength);
               RspLength = ControlLength;
            }
            break;
         case SDXB_LERR:
            Status = AsynSdxbValidateLength( pPortExt, ControlLength,
                                             sizeof(pRsp->U.Line), "LINE_ERR");
            if (Status == STATUS_SUCCESS)
            {
               RtlCopyMemory( &pRsp->U, pControlBuffer, ControlLength);
               RspLength = ControlLength;
            }
            break;
         case SDXB_PCAP:
            Status = AsynSdxbValidateLength( pPortExt, ControlLength,
                                             sizeof(pRsp->U.PortCap),
                                             "PORT_CAP");
            if (Status == STATUS_SUCCESS)
            {
               RtlCopyMemory( &pRsp->U, pControlBuffer, ControlLength);
               RspLength = ControlLength;
            }
            break;
         case SDXB_PARAM:
            if (pPortExt->DxbCount > 0)
               pPortExt->DxbCount--;    // ioctl overflow protection
            Status = AsynSdxbValidateLength( pPortExt, ControlLength,
                                             sizeof(PORT_PARAM_RSP_SDXB),
                                             "PORT_PARM");
            if (Status == STATUS_SUCCESS)
            {
               pRsp->U.PortParam.ctype =
                  ((PPORT_PARAM_RSP_SDXB)pControlBuffer)->ctype;
               pRsp->U.PortParam.speed =
                  ((PPORT_PARAM_RSP_SDXB)pControlBuffer)->speed;
               RspLength = sizeof(pRsp->U.PortParam);
            }
            break;
         case SDXB_SEQ:
            Status = AsynSdxbValidateLength( pPortExt, ControlLength,
                                             sizeof(pRsp->U.SeqNum), "SEQ_NUM");
            if (Status == STATUS_SUCCESS)
            {
               RtlCopyMemory( &pRsp->U, pControlBuffer, ControlLength);
               RspLength = sizeof(pRsp->U.SeqNum);
            }
            break;
         case SDXB_SCRECV:
            Status = AsynSdxbValidateLength( pPortExt, ControlLength,
                                             sizeof(pRsp->U.SpxChar), "SPX");
            if (Status == STATUS_SUCCESS)
            {
               RtlCopyMemory( &pRsp->U, pControlBuffer, ControlLength);
               RspLength = sizeof(pRsp->U.SpxChar);
            }
            break;
         case SDXB_WMARK:
         case SDXB_SCIMMED:
         case SDXB_BREAK:
         case SDXB_FLUSH:
         case SDXB_PAUSE:
            // Protocol error, we should never get these as responses
            AsynProcessProtocolError( pPortExt, pControlBuffer);
            Status = STATUS_UNSUCCESSFUL;
            break;
         case SDXB_FREXCP:
         case SDXB_PROTERR:
            // Protocol error
            AsynProcessProtocolError( pPortExt, pControlBuffer);
            Status = STATUS_UNSUCCESSFUL;
            break;
         default:
            // Protocol error, unknown response
            AsynProcessProtocolError( pPortExt, pControlBuffer);
            Status = STATUS_UNSUCCESSFUL;
            break;
      }
   }

   return RspLength;
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynSdxbValidateLength
 *
 * Purpose:
 *
 *    Validate the length of an S/Dxb Response.
 *
 * Method:
 *    o Check the response length against the length for that response type
 *
 * Returns:
 *
 *    OK    - STATUS_SUCCESS
 *    BAD   - STATUS_UNSUCCESSFUL
 *
 *
 *----------------------------------------------------------------------
 */
NTSTATUS
AsynSdxbValidateLength(
   IN PPORT_EXT         pPortExt,
   IN ULONG             RspLength,
   IN ULONG             SdxbLength,
   IN PUCHAR            DebugStr
)
{
   NTSTATUS             Status = STATUS_SUCCESS;

//   DigiDump(DIGIFLOW, ("ASYN!AsynSdxbValidateLength: Port=%d\n", pPortExt->Port));

   if (RspLength != SdxbLength)
   {
      PORT_ERROR(pPortExt);

      DigiDump(DIGIALL, ("*** ASYN!S/DXB: %s with length=0x%x, should be=0x%x\n",
                        DebugStr, RspLength, SdxbLength));
      Status = STATUS_UNSUCCESSFUL;
   }
   return Status;
}


/*
 *----------------------------------------------------------------------
 * Function:  AsynPutMsgEx
 *
 * Purpose:
 *
 *    Wrapper for DsPutMsgEx() to print debugging information.
 *
 * Method:
 *    o Parse the Port and type of Message being put and print them out.
 *    0 Call dsPutMsgEx()
 *
 * Returns:
 *
 *    Whatever DsPutMsgEx returns.
 *
 *----------------------------------------------------------------------
 */
NTSTATUS 
AsynPutMsgEx(
   IN HANDLE   hStream,
   IN PUCHAR   pControlBuffer,
   IN ULONG    ControlLength,
   IN PUCHAR   pDataBuffer,
   IN ULONG    DataLength,
   IN HANDLE   Context,
   IN DS_PRIORITY    Priority,
   IN PDSL_PUTMSG_COMPLETION_ROUTINE CompletionRoutine
   )
{
   PPUT_MSG_EV pPutEvent = (PPUT_MSG_EV)Context;
   NTSTATUS    Status;

#if DBG
   if (pPutEvent)
   {
      DigiDump( DIGISDXB, ("ASYN!SDXB: tx Port=%d ", pPutEvent->PortExt->Port));
   }
   else
   {
      DigiDump( DIGISDXB, ("ASYN!SDXB: No Context "));
   }

   if (pDataBuffer)
   {
      DigiDump( DIGISDXB, ("DATA len=0x%x\n", DataLength));
   }
   else if (pControlBuffer)
   {
      dxb_uint8 Ctype = ((PSDXB_CMD_HEAD)pControlBuffer)->Ctype;

      DigiDump( DIGISDXB, ("%s", 
         Ctype == SDXB_SYNC      ? "SYNC" :
         Ctype == SDXB_BUFSIZE   ? "BUFSIZE" :
         Ctype == SDXB_MSTAT     ? "MSTAT" :
         Ctype == SDXB_EVENT     ? "EVENT" :
         Ctype == SDXB_LERR      ? "LERR" :
         Ctype == SDXB_PCAP      ? "PCAP" :
         Ctype == SDXB_PARAM     ? "PARAM" :
         Ctype == SDXB_SEQ       ? "SEQ" :
         Ctype == SDXB_SCIMMED   ? "SCIMMED" :
         Ctype == SDXB_WMARK     ? "WMARK" :
         Ctype == SDXB_BREAK     ? "BREAK" :
         Ctype == SDXB_FLUSH     ? "FLUSH" :
         Ctype == SDXB_PAUSE     ? "PAUSE" :
         Ctype == SDXB_SCRECV    ? "SPX_CHAR" :
         Ctype == SDXB_FREXCP    ? "FRAME_EXC" :
         Ctype == SDXB_PROTERR   ? "PROTERR" : "***UNKNOWN" ));

      //
      // Allow for printing out interesting options
      //
      if (Ctype == SDXB_SYNC)
      {
         DigiDump( DIGISDXB, (" #=0x%x", ((PSDXB_CMD)pControlBuffer)->U.Sync.sseq));
         if (pPutEvent)
         {
            DigiDump( DIGISDXB, (" Reason=0x%x", pPutEvent->Reason));
         }
      }
      else if (Ctype == SDXB_FLUSH)
      {
         dxb_uint8 Flags = ((PSDXB_CMD)pControlBuffer)->U.FlushBuffers.flags; 

         DigiDump( DIGISDXB, (" Flags=%s", 
            Flags == SDXB_FLUSH_INPUT     ? "RX" :
            Flags == SDXB_FLUSH_OUTPUT    ? "TX" : "RX | TX"));
      }
      else if (Ctype == SDXB_PARAM)
      {
         if (!(gDebugMask & DIGISDXB))
         {
            if (pPutEvent)
            {
               DigiDump( DIGISDXB1, ("ASYN!SDXB: tx Port=%d PARAM", 
                                 pPutEvent->PortExt->Port));
            }
         }
         DigiDump( DIGISDXB | DIGISDXB1, (" Cflag=0x%x Iflag=0x%x Oflag=0x%x Xflag=0x%x",
                  ((PSDXB_CMD)pControlBuffer)->U.PortParameters.cflags,
                  ((PSDXB_CMD)pControlBuffer)->U.PortParameters.iflags,
                  ((PSDXB_CMD)pControlBuffer)->U.PortParameters.oflags,
                  ((PSDXB_CMD)pControlBuffer)->U.PortParameters.xflags));
         if (!(gDebugMask & DIGISDXB))
         {
            if (pPutEvent)
            {
               DigiDump( DIGISDXB1, ("\n"));
            }
         }
      }
      DigiDump( DIGISDXB, ("\n"));
   }
   if (pPutEvent)
   {
      DigiDump(DIGIFLOW1, ("DsPutMsgEx: P=%d pPutEvent=0x%x\n", 
                     pPutEvent->PortExt->Port, pPutEvent));
   }
#endif   // DBG
   Status = DsPutMsgEx( hStream, pControlBuffer, ControlLength, pDataBuffer, DataLength,
               Context, Priority, CompletionRoutine);
   return Status;
}

