/*
 ****************************************************************************
 * 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    : write.c
 *
 * Author         : P.Donovan
 *
 * Purpose        :
 *
 *      Write (i.e. transmit) routines for Asyn driver.
 *
 *----------------------------------------------------------------------
 */

#include "header.h"

/*
 *----------------------------------------------------------------------
 * Function:  AsynWriteCleanup
 *
 * Purpose:
 *
 *    Cleanup after a Write IRP is cancelled or timed out.
 *
 *
 * Method:
 *
 *    o Walk this Port's PutQ and find the Put Event.
 *       o If DsApi has not finished calling the Completion Routines
 *          o Mark the Put Event as Aborted.
 *       o Else
 *          o Free this Put Event.
 *    o If this is the only PutEvent in the PutQ or all other PutEvents have 
 *      been aborted
 *       o Purge the DsApi write queue.
 *       o Flush the S/DXB write stream.
 *
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 * NOTE:
 *    The Port Spin-Lock must be held when this routine is called.
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynWriteCleanup(
   IN PPORT_EXT      pPortExt,
   IN PPUT_MSG_EV    pPutEvent      // Put event to cleanup.
)

{
   KIRQL       CurrentIrql;         // Save prior irq level.
   PLIST_ENTRY pEntry;
   PLIST_ENTRY pEntryNext;
   USHORT      PutQCount;
   BOOLEAN     bSafeToPurgePutQ = TRUE;
   PCARD_EXT   pCardExt;
   NTSTATUS    Status;

   DigiDump( DIGIWRITE | DIGIPURGE, ("ASYN!WriteCleanup: Port=%x PutEvent=0x%x\n", 
                                       pPortExt->Port, pPutEvent));

   PutQCount = 0;
   pCardExt = pPortExt->CardExt;

   if (pPutEvent == pPortExt->TimedWritePutEvent)
   {
      pPortExt->TimedWritePutEvent = NULL;
      KeCancelTimer( &pPortExt->Timer.Write);

      //
      // Ignore starting the next timer; let normal operations
      // start the timer.  Betting that the application will
      // cancel all IRPs, so don't cause more problems than it
      // is worth.
      //
   }
   // Mark the Put Event as aborted.  If all entries in the PutQ are aborted
   // the outbound stream can be purged.
   pPutEvent->Status = STATUS_UNSUCCESSFUL;
   pPutEvent->State = PutAborted;

   // The calling routine is responsible for Completing the IRP
   pPutEvent->Irp = NULL;

   for ( pEntry = pPortExt->PutQ.Flink; pEntry != &pPortExt->PutQ;
        pEntry = pEntryNext)
   {
      pEntryNext = pEntry->Flink;
      if (((PPUT_MSG_EV)pEntry)->State != PutAborted)
      {
         // If there is at least one entry on the Put Queue which is not
         // Aborted then we cannot safely purge the Queue.
         bSafeToPurgePutQ = FALSE;
      }
   }
   if (bSafeToPurgePutQ)
   {
      USHORT      CmdLen;                 // Length of S/DXB command.
      SDXB_CMD    SdxbCmd;                // S/DXB command structure.
      PPUT_MSG_EV pPutEventFlush = NULL;

      KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
   
      // It is safe to do a Complete Purge on the Queue.
      // This will complete PutEvents whose Completion Routine have not yet 
      // been called.
      Status = DsPurge( pPortExt->DsPortHandle, DS_PURGE_WRITE | DS_PURGE_PUTMSG);

      if (Status == STATUS_SUCCESS)
      {
         SdxbCmd.U.FlushBuffers.flags = SDXB_FLUSH_OUTPUT;
         pPortExt->OpenStateMask |= TxDataPurgeMask;

         //
         // NOTE: This must be sent HI-Priority.  If it's sent LOW-Priority and
         // the Tx-Stream is blocked, then the purge will never be seen.
         //
         Status = AsynPutEventMsg( pPortExt, &pPutEventFlush, NULL, 
                                   SYNC_CMD, REASON_PURGE, DS_PRIORITY_HIGH, 
                                   PutBuilding, AsynBuildFlush);
         if (Status != STATUS_PENDING)
         {
            goto ExitPutCleanup;
         }
         //
         // Sync command, a convenient tag which tells us when PURGE S/DXB
         // is complete.
         //
         // NOTE: Send this LOW-Priority.  This will guarantee that the flush
         // and all other commands have completed.
         //
         Status = AsynPutEventMsg( pPortExt, &pPutEventFlush, NULL, 
                                   CMD_NONE,REASON_NONE, DS_PRIORITY_LOW,
                                   PutAwaitingCompletion, AsynBuildSyncCmd);
         if (Status != STATUS_PENDING)
         {
            goto ExitPutCleanup;
         }
      }
      else
      {
         ASSERT( Status != STATUS_SUCCESS /* in DsPurge */);
      }
ExitPutCleanup:
      KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);

      if (pPutEventFlush && (Status != STATUS_PENDING))
      {
         AsynPutEventCleanup( &pPutEventFlush);
      }
   }
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynPortWrite.
 *
 * Purpose:
 *
 *    Transmit data to the S/DXB module.
 *
 *
 * Method:
 *
 *    o Get the driver specific CardExt and Port values.
 *    o Send DsPutMsgEx(DATA) messages followed by one DsPutMsgEx(SYNC) to 
 *      S/DXB.
 *
 *
 * Returns:
 *
 *    o STATUS_PENDING or a pertinent error status is returned.
 *
 *
 *----------------------------------------------------------------------
 */
NTSTATUS
AsynPortWrite(
   PPORT_EXT   pPortExt                // Port specific information.
)
{
   PCARD_EXT   pCardExt;               // Driver specific information.
   KIRQL       CurrentIrql;            // Save prior irq level.
   NTSTATUS    Status = STATUS_PENDING;// Called function return value.
   LIST_ENTRY  *p;                     // LIST_ENTRY intermediate value.
   SDXB_CMD    SdxbCmd;                // S/DXB command structure.
   USHORT      CmdLen;                 // Length of S/DXB command.
   ULONG       TotalWriteBytes;        // Max. bytes to write for IRP.
   ULONG       BytesWritten;
   ULONG       ThisWrite;
   PSERIAL_TIMEOUTS pTimeouts;
   PIRP        pIrp;                   // Irp on the top of Write Queue
   PIO_STACK_LOCATION
               pIrpSp;                 // Current IRP stack position.

   DigiDump(DIGIWRITE, ("ASYN!PortWrite: Port=%d\n", pPortExt->Port));

   if (pPortExt->State != OpenState)
   {
      Status = STATUS_UNSUCCESSFUL;
      goto PortWriteExit;
   }

   //
   // If the driver setting "QueueWritesInAdapter" is FALSE
   // then only process the next Irp if the adapter is not currently 
   // transmitting data.
   //
   if ( (!pPortExt->QWritesInAdapter && pPortExt->TxBytesPending) ||

   //
   // If the driver is already building a transmit we don't want to pre-empt it.
   //
         (pPortExt->OpenStateMask & TxingMask) ||
   //
   // If the driver is Purging the outbound data, we must wait for it 
   // to complete.
   //
         (pPortExt->OpenStateMask & TxDataPurgeMask) )
   {
      goto PortWriteExit;
   }

   ASSERT( !IsListEmpty(&pPortExt->WriteQueue));
   p = RemoveHeadList( &pPortExt->WriteQueue);
   pIrp = CONTAINING_RECORD( p, IRP, Tail.Overlay.ListEntry);
 
   DigiDump(DIGIWRITE, ("ASYN!PortWrite: Port=%d Handle=0x%x\n", 
                        pPortExt->Port, pIrp));
   pPortExt->OpenStateMask |= TxingMask;

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

   pIrpSp = IoGetCurrentIrpStackLocation( pIrp);

   if (pIrpSp->MajorFunction == IRP_MJ_FLUSH_BUFFERS)
   {
      //
      // IRP_MJ_FLUSH_BUFFERS
      //

      PPUT_MSG_EV pPutEvent = NULL;
   
      DigiDump(DIGIWRITE, ("ASYN!PortWrite: FLUSH"));
   
      Status = AsynPutEventMsg( pPortExt, &pPutEvent, pIrp, 
                                SYNC_CMD, REASON_FLUSH, DS_PRIORITY_LOW, 
                                PutAwaitingCompletion, AsynBuildSyncCmd);
      KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);
   
      pIrp->IoStatus.Status = Status;
      if (Status != STATUS_PENDING)
      {
         InsertTailList( &pPortExt->CompleteIrpQ, &pIrp->Tail.Overlay.ListEntry);
      }
      if (pPutEvent && (Status != STATUS_PENDING))
      {
         AsynPutEventCleanup( &pPutEvent);
      }
      pPortExt->OpenStateMask &= ~TxingMask;
      return Status;
   }
   else
   {
      //
      // IRP_MJ_WRITE
      //

      PPUT_MSG_EV pPutEvent = NULL;

      TotalWriteBytes = pIrpSp->Parameters.Write.Length;

      //
      // Zero length writes are completed immediately.
      //
      if (TotalWriteBytes == 0)
      {
         pIrp->IoStatus.Status = Status = STATUS_SUCCESS;
         KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);
	      pPortExt->OpenStateMask &= ~TxingMask;
         goto PortWriteExit;
      }
      //
      // Must have a non-zero buffer.
      //
      else if (pIrp->AssociatedIrp.SystemBuffer == NULL)
      {
         pIrp->IoStatus.Status = Status = STATUS_INVALID_PARAMETER;
         KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);
	      pPortExt->OpenStateMask &= ~TxingMask;
         goto PortWriteExit;
      }
      pPutEvent = (PPUT_MSG_EV)AsynAllocEntry( &pPortExt->PutEventPool, &Status);
      if (Status != STATUS_SUCCESS)
      {
         KeAcquireSpinLock( &pPortExt->PortLock, &pPortExt->PortIrql);
	      pPortExt->OpenStateMask &= ~TxingMask;
         goto PortWriteExit;
      }
   
      // Keep a Put Event descriptor waiting in the Pending queue.
      // This descriptor will be used to track the DsPutMsgEx calls
      // for this Write
      pPutEvent->Irp = pIrp;
      InitializeListHead( &pIrp->Tail.Overlay.ListEntry);
      pPutEvent->Status = STATUS_SUCCESS;
      pPutEvent->PortExt = pPortExt;
   
      pPutEvent->Command = DATA_CMD;
      pPutEvent->Sequence = pPortExt->SyncOutSequence;
      pPutEvent->PutMsgRef = 0;
      pPutEvent->State = PutBuilding;
      pPutEvent->Reason = REASON_WRITE;
      pPutEvent->PendingTxBytes = 0;
      InsertTailList( &pPortExt->PutQ, &pPutEvent->ListEntry);
   
      BytesWritten = 0;
      while (BytesWritten < TotalWriteBytes)
      {
         if (TotalWriteBytes - BytesWritten < pPortExt->SdxbValues.Outsize/2)
         {
            ThisWrite = TotalWriteBytes - BytesWritten;
         }
         else
         {
            // Do a complete Buffer at a time.
            // This may not be the optimum block to send at a time.
            // Need to do some measurements on this.
            ThisWrite = pPortExt->SdxbValues.Outsize/2;
         }
         pPutEvent->PutMsgRef++;
         Status = AsynPutMsgEx( pPortExt->DsPortHandle, (PUCHAR)NULL, 0,
                                (PUCHAR)pIrp->AssociatedIrp.SystemBuffer +
                                    BytesWritten,
                                ThisWrite,
                                (HANDLE)pPutEvent,
                                DS_PRIORITY_LOW, AsynPutMsgComplete);
         if (Status != STATUS_PENDING)
            break;
         BytesWritten += ThisWrite;
         pPutEvent->PendingTxBytes += ThisWrite;
      }
      pPortExt->TxBytesPending += BytesWritten;
      //
      // Update Performance Statistics as necessary
      //
      pPortExt->PerfStats.TransmittedCount += BytesWritten;
   
      if (Status == STATUS_PENDING)
      {
         pPutEvent->PutMsgRef++;
         pPutEvent->State = PutAwaitingCompletion;
         CmdLen = AsynBuildSyncCmd( pPortExt, &SdxbCmd);
         Status = AsynPutMsgEx( pPortExt->DsPortHandle, (PUCHAR)&SdxbCmd,
                                CmdLen, (PUCHAR)NULL, 0, (HANDLE)pPutEvent,
                                DS_PRIORITY_LOW, AsynPutMsgComplete);
   
         //DigiDump(DIGIFLOW1, ("ASYN!Write: P=%d WRITE IRP=0x%x\n", pPortExt->Port, pIrp));
      }
   
      if (Status == STATUS_PENDING)
      {
         //
         // Determine if a timer should be started for this write IRP; please
         // note that only one timer is active for the entire write interface
         // for this port!
         //
         pTimeouts = &pPortExt->TimeoutValues;
      
         if ( (pPortExt->TimedWritePutEvent == NULL) &&
              ((pTimeouts->WriteTotalTimeoutMultiplier) ||
               (pTimeouts->WriteTotalTimeoutConstant)) )
         {
            LARGE_INTEGER TotalTime;
      
            TotalTime.QuadPart =
               ((LONGLONG)((UInt32x32To64( TotalWriteBytes,
                                           pTimeouts->WriteTotalTimeoutMultiplier)
                                 + pTimeouts->WriteTotalTimeoutConstant)))
               * -1 *MILLISECOND;
      
            // Keep a record of the complete work order and IRP.
            pPortExt->TimedWritePutEvent = pPutEvent;
            KeSetTimer( &pPortExt->Timer.Write, TotalTime,
                        &pPortExt->Timer.WriteDpc);
         }
      }

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

      // Indicate we are no longer in the middle of a transmit.
      pPortExt->OpenStateMask &= ~TxingMask;

      // Clean up write as best we can.
      if ((Status != STATUS_PENDING) && (Status != STATUS_SUCCESS))
      {
         AsynWriteCleanup( pPortExt, pPutEvent);
      }
   }
PortWriteExit:

   if (Status != STATUS_PENDING)
   {
      // No need to update pIrp->IoStatus.Information here!
      // This field (which reflects the number of bytes written) is
      // update in the DsPut Completion routines and when
      // Sequence-Number SDXB indications are received.

      DigiDump( DIGIFLOW1, ("ASYN!Write: P=%d CompleteIrp=0x%x Status=0x%x ",
                              pPortExt->Port, pIrp, Status));
      pIrp->IoStatus.Status = Status;
      InsertTailList( &pPortExt->CompleteIrpQ, &pIrp->Tail.Overlay.ListEntry);
   }

   return Status;
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynWriteTimedOut.
 *
 * Purpose:
 *
 *    Timeout a Write IRP and cleanup after it.
 *
 *
 * Method:
 *
 *    o Perform the necessary completion processing.
 *
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynWriteTimedOut(
   IN PKDPC          pPortDpc,      // DPC object for Port.
   IN PPORT_EXT      pPortExt,      // Port specific information.
   IN PVOID          SystemArg1,    // System argument.
   IN PVOID          SystemArg2     // System argument.
)

{
   PIRP        pIrp;           // Queued IRP to finish.
   PIRP        pNewWriteIrp;        // Queued IRP to start timer.
   PIO_STACK_LOCATION
               pIrpSp;              // Current IRP stack position.
   LARGE_INTEGER
               WriteTimeout;        // Timeout value.
   KIRQL       CurrentIrql;         // Save prior irq level.
   PLIST_ENTRY pPutQ = NULL;         // PutQueue which the PutEvent is in.
   PLIST_ENTRY pEntry;
   PPUT_MSG_EV pPutEvent = NULL;

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

   DigiDump( DIGIWRITE|DIGITIMEOUT, ("ASYN!WriteTimedOut: Port=%d\n", pPortExt->Port));

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

   if (pPortExt->TimedWritePutEvent)
   {
      pIrp = pPortExt->TimedWritePutEvent->Irp;
      pPutEvent = pPortExt->TimedWritePutEvent;

      if (pIrp)
      {
         //
         // Remove the Cancel routine.
         //
         IoSetCancelRoutine( pIrp, NULL);

         pIrp->IoStatus.Status = STATUS_TIMEOUT;

         //
         // The cleanup cannot be done in-line.
         // May need to generate an SDXB(FLUSH) and other SDXB packets, 
         // this means Releasing the spin-lock, which should only be done 
         // in the context of the AsynSchedulerLoop()
         InsertTailList( &pPortExt->CancelledAndTimedoutIrpQ, &pIrp->Tail.Overlay.ListEntry);
         if (!pPortExt->PortBusy)
         {
            DigiDump(DIGIDISPATCH, ("ASYN!WriteTimedOut: +++[%d]Entry P=%d Irp=0x%x\n", 
                        pPortExt->PortBusy, pPortExt->Port, pIrp));
            AsynSchedulerLoop( pPortExt);
            DigiDump(DIGIDISPATCH, ("ASYN!WriteTimedOut: ---[%d]Exit P=%d\n", 
                        pPortExt->PortBusy, pPortExt->Port));
         }
      }
   }
   KeReleaseSpinLock( &pPortExt->PortLock, pPortExt->PortIrql);
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynWriteTimedOutCleanup.
 *
 * Purpose:
 *
 *    Cleanup a Timedout Write IRP
 *
 *
 * Method:
 *
 *    o Flush the Tx Stream if necessary.
 *
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 * NOTES:
 *    The Port's Spin-lock must be held when this routine is called.
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynWriteTimedoutCleanup(
   IN PPORT_EXT      pPortExt,      // Port specific information.
   IN PIRP           pIrp           // Queued IRP to finish.
)
{
   PIRP        pNewWriteIrp;        // Queued IRP to start timer.
   PIO_STACK_LOCATION
               pIrpSp;              // Current IRP stack position.
   LARGE_INTEGER
               WriteTimeout;        // Timeout value.
   KIRQL       CurrentIrql;         // Save prior irq level.
   PLIST_ENTRY pPutQ = NULL;         // PutQueue which the PutEvent is in.
   PLIST_ENTRY pEntry;
   PPUT_MSG_EV pPutEvent = NULL;

   DigiDump( DIGIWRITE|DIGITIMEOUT, ("ASYN!WriteTimedoutCleanup: Port=%d Irp=0x%x\n", 
                                      pPortExt->Port, pIrp));

   if (pPortExt->TimedWritePutEvent)
   {
      if (pIrp == pPortExt->TimedWritePutEvent->Irp)
      {
         pPutEvent = pPortExt->TimedWritePutEvent;
         pPortExt->TimedWritePutEvent = NULL;
   
         //
         // Find the Put Event.
         //
         for ( pEntry = pPortExt->PutQ.Flink; pEntry != &pPortExt->PutQ;
              pEntry = pEntry->Flink)
         {
            if (pEntry == (PLIST_ENTRY)pPutEvent)
            {
               pPutQ = &pPortExt->PutQ;
               break;
            }
         }
         ASSERT( pPutQ);
   
         //
         // Clean up write as best we can.
         //
         AsynWriteCleanup( pPortExt, pPutEvent);
         InsertTailList( &pPortExt->CompleteIrpQ, &pIrp->Tail.Overlay.ListEntry);
      }
   }
}

/*
 *----------------------------------------------------------------------
 * Function:  AsynCancelWrite.
 *
 * Purpose:
 *
 *    Cancel a write or flush IRP which has been sent to the adapter.
 *
 *
 * Method:
 *
 *    o Inser the IRP in the Completed queue.
 *
 * NOTES:
 *    The Port's Spin-lock must be held when this routine is called.
 *
 * Returns:
 *
 *    o Nothing is returned.
 *
 *
 *----------------------------------------------------------------------
 */
VOID
AsynCancelWrite(
   IN PPORT_EXT         pPortExt,   // Port which is open
   IN PIRP              pIrp        // Associated work order.
)
{
   PLIST_ENTRY pEntry;
   PLIST_ENTRY pEntryNext;
   PPUT_MSG_EV pPutEvent = NULL;
   KIRQL       CurrentIrql;

   DigiDump(DIGICANCEL|DIGIWRITE, ("ASYN!CancelWrite: "));

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

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

   //
   // See if this IRP has spawned an associated PutEvent
   //
   pPutEvent = AsynUtilFindPutEvent( pPortExt, pIrp, &pPortExt->PutQ);

   if (pPutEvent)
   {
      AsynWriteCleanup( pPortExt, pPutEvent);
   }
   else
   {
      PLIST_ENTRY pEntry, pEntryNext;
      BOOLEAN fFoundIrp = FALSE;

      //
      // Never even got to generate a Put Event, 
      // IRP must be still in a Queue awaiting service
      //
      pIrp->IoStatus.Information = 0;
   }

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

