/*++
*******************************************************************************
* Copyright (c) 1999-2000 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 Name:

	asynwrite.c

Abstract:

	Transmit data path.  Handles queuing, dispatching and complete serial write 
	requests.  Serial write data is passed to SDXB using plain data PutMsg requests
	via Dxb.

	In a few cases we manipulate write requests before sending down to the card.

	1.  Large write requests.  DXB firmware uses "credits" to limit the amount of data
	placed on the card.  Like a charge card, where you use up credit when you purchase
	something, in DXB you use up credits when you place data on the card.  When your 
	credit limit is reached, you can send no more data until credits are returned to 
	you.  DigiDxb, the driver, encapsulates all credit functionality, and with packet-
	based clients, no other client cares about credits.  Asyn cares only because there
	is no theoretical maximum write buffer size.  If a write request of size > max 
	credits were sent to DigiDxb, it could never be placed on the card, because there
	isn't enough credit available.  We thus break up large write requests into multiple
	PutMsg requests.

	2.  Single byte write requests.  Some apps (Tobit FAX) try to write large amounts 
	of data, one byte at a time.  Sending single byte write requests through DXB's 
	message based api is very expensive - and potentially bandwidth limiting while 
	waiting for credit returns on our 20 ms poll period.  So, if we detect a series of 
	single byte write requests, we automatically queue them and dispatch after a 
	timeout or a minimum number of bytes is queued.  

	In any case, the serial API this driver presents expects to be able to set timeouts
	for each write irp and cancel any write irp.  The mechanism available to DXB 
	clients for cancelling requests is based on Purge.  Purge cancels all outstanding 
	write requests.  So, to make sure only a single write request gets purged, we keep
	only one write irp's transmit data request outstanding with DXB at any given time.
	This should not impose a performance penalty for two reasons:
	a)  Onboard buffers on the card are large enough to keep transmitter from draining
	inbetween write requests.
	b)  DXB will succeed all write requests synchronously whenever the data can be 
	immediately placed on the card.  The only time this won't happen is when the port 
	is flow controlled (by lack of credits) and won't accept more data or the dxb cmd
	queue is full and data cannot be placed in the queue.  If the port is flow-
	controlled, then lots of tx data is already buffered on the card.  If the queue is
	full (should rarely happen) then we couldn't write anyway.

Author:

	Michael Zarns

--*/
#include "digiasyn.h"

#define		TIMER_IRP			555
#define		NOT_USED			0x999



VOID AsynWriteDispatch( PVOID Context );		//IN PASYN_PORT_EXT pExt);

VOID AsynWritePendingQueue(			IN PKDPC pPortDpc,
									IN PVOID pExt,
									IN PVOID,
									IN PVOID );

BOOLEAN AsynCancelQueue(			IN PLIST_ENTRY     pQueue,
									IN BOOLEAN         fCancelAll,
									IN OUT PLIST_ENTRY pCancelledQ );

//VOID AsynCancelActiveIrp(			IN PDEVICE_OBJECT	DeviceObject,
//									IN PIRP				pIrp );

VOID AsynWriteTimeout(				IN PKDPC WriteTimeOut,
									IN PVOID Context,
									IN PVOID void1,
									IN PVOID void2 );

NTSTATUS AysnPurgeQueue(			IN PLIST_ENTRY Queue,
									IN NTSTATUS Status,
									IN PASYN_PORT_EXT pPortExt );

VOID AsynCancelActiveIrp(			IN PDEVICE_OBJECT DeviceObject,
									IN PIRP CancelledIrp );




// Offsets into pIrp->Tail.Overlay.DriverContext[] array.  These fields are reserved
// for drivers private use.  For Write Irps, we use them as defined here:
typedef enum
{
    ASYN_SEQUENCE_NUMBER = 0,		// Count of bytes sent so far via DsPutMsg requests
    ASYN_TIMER_IRP,					// This is an Irp used for Write Time Out  
    ASYN_CONTEXT_PUT_COMPLETE,		// Boolean: PutMsg completion routine has been called
    ASYN_CONTEXT_COUNT
} ASYN_IRP_CONTEXT;

// C_ASSERT( ASYN_CONTEXT_COUNT <= (FIELD_OFFSET(IRP,Tail.Overlay.Thread) - FIELD_OFFSET(IRP,Tail.Overlay.DriverContext)) );


STATIC BOOLEAN
AsynCancelQueue(
	IN PLIST_ENTRY     pQueue,
	IN BOOLEAN         fCancelAll,
	IN OUT PLIST_ENTRY pCancelledQ )
/*++

Description:

	Cancel all Irps on a given queue.  Requires the spinlock be held by the caller. 
	Can cancel the entire queue, used for cleanup/close operations, or can look only 
	for Irps with Cancel flag set.  Places all cancelled Irps on the output Cancelled
	queue.  Sets IoStatus.Status = STATUS_CANCELLED.

Parameters:

	pQueue
		The queue to cancel

	fCancelAll
		Determines whether *all* Irps are cancelled, or only those with the cancel 
		flag set.

	pCancelledQ
		Initialized List of Irps which the caller must complete upon return and after 
		releasing the stream lock.

Return Value:

	TRUE if any Irps were cancelled.

--*/
{
    BOOLEAN			fCancelled = FALSE;
    PLIST_ENTRY		pHead = pQueue;
    PLIST_ENTRY		pEntry = pHead->Flink;
    KIRQL			irqLevel;

    while ( pEntry != pHead )
    {
        PIRP pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
        ASSERT( pIrp->Type == IO_TYPE_IRP );
        pEntry = pEntry->Flink;

        if ( fCancelAll || pIrp->Cancel )
        {
            RemoveEntryList( &pIrp->Tail.Overlay.ListEntry );
            pIrp->IoStatus.Status = STATUS_CANCELLED;


            DigiDump( DIGIWRITE, ("In AsynWriteCancelQueue someone wants all the irps cancelled \n" ));

            IoAcquireCancelSpinLock( &irqLevel );
            (void)IoSetCancelRoutine( pIrp, NULL );
            IoReleaseCancelSpinLock( irqLevel );

            InsertTailList( pCancelledQ, &pIrp->Tail.Overlay.ListEntry );
            fCancelled = TRUE;
        }
    }

    return fCancelled;
}


STATIC VOID
AsynWriteCancelQueuedIrp(
	IN PDEVICE_OBJECT pDevice, 
	IN PIRP           pCancelledIrp )
/*++

Description:

	This function is registered to cancel Irps on the Pending or Active queue.  These 
	Irps are either partially sent to Dxb or not at all.

	Walk all write queues looking for cancelled Irps under spinlock control.  Move 
	cancelled irps to a local list, completing them after releasing the spinlock.

Parameters:

	pDevice
		Device Object for this com port.

	pCancelledIrp
		Irp to Cancel

Return Value:

	None

--*/
{
    PASYN_PORT_EXT pExt = pDevice->DeviceExtension;
    PASYN_WRITE pWrite = &pExt->Write;
    BOOLEAN fPurge = FALSE;
    LIST_ENTRY CancelledQueue;
    InitializeListHead( &CancelledQueue );

    // Release Cancel lock.  We don't protect any of our lists or data using it, so 
    // it's not needed.
    ASSERT( MmIsAddressValid(pCancelledIrp) );
    ASSERT( pCancelledIrp->CancelIrql <= DISPATCH_LEVEL );
    IoReleaseCancelSpinLock( pCancelledIrp->CancelIrql );

    // Instead, grab lock that protects the write queues
    DigiAcquireSpinLock( &pWrite->Lock );

    // Irps on PendingQueue have not been processed in anyway yet.  Simply cancel them
    AsynCancelQueue( &pWrite->PendingQueue, FALSE, &CancelledQueue );

    // Active queue is "active", meaning Irps contained in this queue cannot simply be 
    // completed because they are pending in Dxb.  If found here, we must use Dxb's 
    // Purge api to cancel the request.
//	if ( !IsListEmpty(&pWrite->ActiveQueue) )
//	{
//		PLIST_ENTRY pHead = &pWrite->ActiveQueue;
//		PLIST_ENTRY pEntry = pHead->Flink;
//		while ( pEntry != pHead )
//		{
//			PIRP pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
//			ASSERT( pIrp->Type == IO_TYPE_IRP );
//			if ( pIrp->Cancel )
//			{
//				// A cancelled Irp is still pending in DXB.  We have no choice but to 
//				// purge them all.
//				pIrp->IoStatus.Status = STATUS_CANCELLED;
//				fPurge = TRUE;
//			}
//			pEntry = pEntry->Flink;
//		}
//	}

    DigiReleaseSpinLock( &pWrite->Lock );

    // Purge must be called outside spinlock control since our completion routine also
    // acquires the lock.  Only purge if cancelled irp was found in ActiveQ.
    if ( fPurge )
        DsPurge( pExt->DsPortHandle, DS_PURGE_PUTMSG );
	
    // Done searching.  Complete all cancelled Irps.
    while ( !IsListEmpty( &CancelledQueue ) )
    {
        PIRP pIrp;
        PLIST_ENTRY pEntry = RemoveHeadList( &CancelledQueue );
        pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
        ASSERT( pIrp->Type == IO_TYPE_IRP );
        AsynIoCompleteRequest( pIrp, pIrp->IoStatus.Status, pExt );
    }
}
/*******************************************************************************************************
**
**	VOID  AsynCancelActiveIrp( IN PDEVICE_OBJECT DeviceObject, IN PIRP CancelledIrp )
**
**	Purpose: This function is called when I need to cancel the active Irp.  Basically all I will
**		do is set the canel flag and let the loop do the rest.  If the Irp is already gone down
**		the pipe then it is too late for any of that nonsense.
**
*******************************************************************************************************/
VOID AsynCancelActiveIrp( IN PDEVICE_OBJECT DeviceObject, IN PIRP CancelledIrp )
{
    PASYN_PORT_EXT		pExt;
    PASYN_WRITE			pWrite;

    pExt = DeviceObject->DeviceExtension;
    pWrite = &pExt->Write;

    IoReleaseCancelSpinLock( CancelledIrp->CancelIrql );

    if ( CancelledIrp == pWrite->ActiveIrp )
        (ULONG_PTR)CancelledIrp->Tail.Overlay.DriverContext[ASYN_CONTEXT_COUNT] = NOT_USED;

    else
    {
//		SdxbFlush( pExt->DsPortHandle, SDXB_FLUSH_OUTPUT );
	
        DsPurge( pExt->DsPortHandle, DS_PURGE_PUTMSG ); 
//		AsynIoCompleteRequest( CancelledIrp, STATUS_CANCELLED );
    }

}		  
		
NTSTATUS
AsynWriteInitialize(
	IN PASYN_PORT_EXT pExt, 
	IN HANDLE      hkRegistry )
/*++

Description:

	Initialize ASYN_WRITE structure.  Gather config from registry if required.

Parameters:

	pWrite
		Write data structure.  This contains the per-port data needed by other routines
		in this module.

	hkRegistry
		Handle to previously opened registry key.  We may search for configurable params
		here.

Return Value:

	STATUS_SUCCESS, or failure.

--*/
{
    NTSTATUS		Status = STATUS_SUCCESS;
    PASYN_WRITE		pWrite = &pExt->Write;
    int				counter = 0;

    // Init these varibles until I can put the short write into the code path.
    pWrite->fQueuing = FALSE;
    pWrite->cbMaxPutMsg = 0xffffffff;

    DigiInitializeSpinLock( &pWrite->Lock );
    DigiInitializeSpinLock( &pWrite->Sequence.WaitLock );

    InitializeListHead( &pWrite->PendingQueue );
    InitializeListHead( &pWrite->ActiveQueue );
    InitializeListHead( &pWrite->Sequence.WaitTillCompleteQueue );
    InitializeListHead( &pWrite->Sequence.FlushList );

//	KeInitializeEvent( &pWrite->ThreadEvent,
//					   SynchronizationEvent,
//					   FALSE );
    KeInitializeEvent(&pWrite->WorkEvent,SynchronizationEvent, FALSE);
    KeInitializeEvent(&pWrite->HaltEvent,SynchronizationEvent, FALSE);

    // Init for the sequence counters and the wait till done code.
    pWrite->Sequence.CurrentCountOut = 0;
    pWrite->Sequence.CurrentCountIn = 0;
    pWrite->Sequence.CurrentCountReference = 0;
    pWrite->TimeoutActive = FALSE;

    pWrite->Sequence.WaitEmptyEvent = FALSE;
    pWrite->Sequence.WaitFlushEvent = FALSE;

    KeInitializeEvent( &pWrite->Sequence.FlushEvent,
                       SynchronizationEvent,
                       FALSE );

    KeInitializeEvent( &pWrite->Sequence.EmptyEvent,
                       SynchronizationEvent,
                       FALSE );

    KeInitializeEvent( &pWrite->Sequence.CancelEvent,
                       SynchronizationEvent,
                       FALSE );

    KeInitializeEvent( &pWrite->Sequence.ClearEvent,
                       SynchronizationEvent,
                       FALSE );

    // This is for the write time out.
    KeInitializeTimer( &pWrite->WriteTimer.Timer );
    KeInitializeDpc( &pWrite->WriteTimer.TimeOutDPC, AsynWriteTimeout, pExt );

    pWrite->WriteTimer.TimerInUse = FALSE;
    pWrite->WriteTimer.TimerOn = FALSE;
    pWrite->WriteTimer.TimerLength.QuadPart = 0;

	
//	Status = PsCreateSystemThread( &pWrite->WorkerThread,
//								   THREAD_ALL_ACCESS,
//								   NULL,
//								   NULL,
//								   NULL,
//								   AsynWriteDispatch,
//								   pExt );

    // Extra code to store handle to thread so we can kill it later. Also some error checking to cover ourselves
    // if the thread creation or thread reference fails.

    {	
        HANDLE hThread;

        Status = PsCreateSystemThread( &hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, AsynWriteDispatch, pExt );
        if (NT_SUCCESS(Status))
        {
            PVOID pThread;
            Status = ObReferenceObjectByHandle( hThread, THREAD_ALL_ACCESS, NULL, KernelMode, &pThread, NULL );
            ZwClose( hThread );

            if ( NT_SUCCESS( Status ) )
                pWrite->pWorkerThread = pThread;
            else
                // Tell thread to stop.  
                KeSetEvent( &pWrite->HaltEvent, EVENT_INCREMENT, FALSE );
        }                    
    }	
	
    return Status;
}

/*********************************************************************************************************
**
**	VOID AysnCompleteWaitingIrps( IN ASYN_WRITE pWrite )
**
**	Purpose: We are waiting till we get notification from the card that the write is complete.  Then
**		we will actually complete the irp.  Now why this is not synch call I have no idea but I get
**		paid to tell the computer what to do, not to tell the customer what to do.  I believe that
**		this is some sales driven feature for some customer who doesn't know what they want.
**
*********************************************************************************************************/
VOID AsynCompleteWaitingIrp( IN PASYN_PORT_EXT pPortExt )
{
    PLIST_ENTRY			listEntry;
    PIRP				pIrp;
    ULONG_PTR			sequenceNumber;
    PASYN_WRITE			pWrite = &pPortExt->Write;

    DigiAcquireSpinLock( &pWrite->Sequence.WaitLock );

    while ( !IsListEmpty( &pWrite->Sequence.WaitTillCompleteQueue ) )
    {
        listEntry = RemoveHeadList( &pWrite->Sequence.WaitTillCompleteQueue );
        pIrp = CONTAINING_RECORD( listEntry, IRP, Tail.Overlay.ListEntry );

        if ( !pIrp )
            continue;

        sequenceNumber = (ULONG_PTR)pIrp->Tail.Overlay.DriverContext[ASYN_SEQUENCE_NUMBER];
        if ( sequenceNumber <= pWrite->Sequence.CurrentCountOut )
        {
            DigiReleaseSpinLock( &pWrite->Sequence.WaitLock );
            AsynIoCompleteRequest( pIrp, pIrp->IoStatus.Status, pPortExt );
            DigiAcquireSpinLock( &pWrite->Sequence.WaitLock );

            if ( sequenceNumber == pWrite->Sequence.CurrentCountOut )
                break;
        }
        else
        {
            InsertHeadList( &pWrite->Sequence.WaitTillCompleteQueue, &pIrp->Tail.Overlay.ListEntry );
            break;
        }

    }

    DigiReleaseSpinLock( &pWrite->Sequence.WaitLock );
}

STATIC VOID
AsynWriteIrpComplete(
	IN HANDLE   hOwner,
	IN HANDLE   Context,
	IN PVOID    pControl,
	IN ULONG    cbControl,
	IN PVOID    pData,
	IN ULONG    cbData,
	IN NTSTATUS IoStatus )
/*++

Description:

	Completion routine for DsPutMsg.  This completion routine is used for the *final* 
	PutMsg request associated with a Write Irp.  This means the Irp may be completed.

Parameters:

	None

Return Value:

	None

--*/
{
    PASYN_PORT_EXT	pExt = hOwner;
    PASYN_WRITE		pWrite = &pExt->Write;
    PIRP			pIrp = (PIRP)Context;


    DigiAcquireSpinLock( &pWrite->Lock );

    if ( (ULONG_PTR)pIrp->Tail.Overlay.DriverContext[ASYN_TIMER_IRP] == TIMER_IRP  )
    {
        KeCancelTimer( &pWrite->WriteTimer.Timer );
        pWrite->WriteTimer.TimerInUse = FALSE;
    }

    IoSetCancelRoutine( pIrp, NULL );

    if ( pIrp->PendingReturned )
        IoMarkIrpPending( pIrp );


    ASSERT( pExt->Type == ASYN_TYPE_PORT_EXT );

    // Add bytes written to total count
    if ( NT_SUCCESS( IoStatus ) )
        pIrp->IoStatus.Information += cbData;

    // Be sure to return accurate status
    if ( pIrp->Cancel )
    {
        pIrp->IoStatus.Status = STATUS_CANCELLED;
        DigiReleaseSpinLock( &pWrite->Lock );
        DigiDump( DIGIWRITE, ("In AsynWriteIrpComplete for some reason this irp is maked cancelled.\n" ));
        goto COMPLETE;
    }

    if ( IoStatus == STATUS_CANCELLED )
    {
        if ( pWrite->TimeoutActive )
            IoStatus = STATUS_TIMEOUT;
    }
    else
        pWrite->TimeoutActive = FALSE;

    pIrp->IoStatus.Status = IoStatus;
	

    pExt->SdxbValues.PerfStats.TransmittedCount += (ULONG)pIrp->IoStatus.Information;

    DigiReleaseSpinLock( &pWrite->Lock );



    if ( pWrite->Sequence.WaitTillDone )
    {
        if ( (ULONG_PTR)pIrp->Tail.Overlay.DriverContext[ASYN_SEQUENCE_NUMBER] <= pWrite->Sequence.CurrentCountOut )
            goto COMPLETE;
		
        DigiAcquireSpinLock( &pWrite->Sequence.WaitLock );
        InsertTailList( &pWrite->Sequence.WaitTillCompleteQueue, &pIrp->Tail.Overlay.ListEntry );
        DigiReleaseSpinLock( &pWrite->Sequence.WaitLock );
        return;
    }
	 
 COMPLETE:
    if ( !NT_SUCCESS( pIrp->IoStatus.Status ) )
        DigiDump( DIGIWRITE, ("Some how got an invalid status and did not catch where it came from\n"));

    AsynIoCompleteRequest( pIrp, pIrp->IoStatus.Status, pExt );

}


STATIC VOID
AsynWritePartialComplete(
	IN HANDLE   hOwner,
	IN HANDLE   Context,
	IN PVOID    pControl,
	IN ULONG    cbControl,
	IN PVOID    pData,
	IN ULONG    cbData,
	IN NTSTATUS Status )
/*++

Description:

	Completion routine for DsPutMsg.  This completion routine is used in PutMsg 
	requests when this PutMsg is *not* the final request for the Irp.  Some write Irps
	are broken into chunks, where this routine is used on non-final chunks.  The irp
	should not be completed here since there's more work to do.

Parameters:

	None

Return Value:

	None

--*/
{
    PASYN_PORT_EXT pExt = hOwner;
    PASYN_WRITE pWrite = &pExt->Write;
    PIRP pIrp = Context;

    ASSERT( pExt->Type == ASYN_TYPE_PORT_EXT );


//	DigiDump( DIGIWRITE, ( "Asyn!WritePartialComplete: Write Irp 0x%x, cb 0x%x, status 0x%x\n", Context, cbData, pIrp->Tail.Overlay.DriverContext[ASYN_CONTEXT_PUT_INPROGRESS], IoStatus ) );

//	if ( Context == pWrite->pActiveIrp )
//	{
    // Add bytes written to total count
    if ( NT_SUCCESS( Status ) )
        pIrp->IoStatus.Information += cbData;

    // Be sure to return accurate status
    pIrp->IoStatus.Status = Status;
//	}
//	else
//	{
//		DigiDump( DIGIERROR, ( "Asyn!WritePartialComplete: Error - Context 0x%x != pActiveIrp 0x%x\n", Context, pWrite->pActiveIrp ) );
//	}   
//	DigiReleaseSpinLock( &pWrite->Lock );


   
}


STATIC VOID 
AsynWriteDispatch( PVOID Context )
/*++

Description:

	Dispatches write requests.  Method:
	- Check for ActiveIrp.  If found, it is a large write request that is partially 
	  sent to Dxb but more needs to be sent.  Continue by sending the next segment.
	- Check for QueueingSmallPackets.  If so, copy data from Irps to Buffer and send
	  the buffer as a single PutMsg request.
	- Otherwise, grab first Irp on the PendingQueue and try to send it.

Return Value:

	Returns completion status of the first Irp processed.

--*/
{
    NTSTATUS			Status;
    PASYN_PORT_EXT		pExt	= (PASYN_PORT_EXT)Context;
    PASYN_WRITE			pWrite	= &pExt->Write;
    PIRP				pIrp;
    PLIST_ENTRY			list;
    PKTHREAD			currentThread;
    PIO_STACK_LOCATION	pIos;
    
    enum
	{
            HALT_EVENT = 0,
            WORK_EVENT,
            WRITE_EVENT_COUNT
	};

    KEVENT *Events[WRITE_EVENT_COUNT];

    Events[HALT_EVENT] = &pWrite->HaltEvent;
    Events[WORK_EVENT] = &pWrite->WorkEvent;

    currentThread = KeGetCurrentThread();
    KeSetPriorityThread( currentThread , LOW_REALTIME_PRIORITY );

    while ( TRUE )
    {
        pIrp = NULL;

        DigiAcquireSpinLock( &pWrite->Lock );
	
        if ( !IsListEmpty(&pWrite->PendingQueue) )
        {
            list = RemoveHeadList( &pWrite->PendingQueue );

            pIrp = CONTAINING_RECORD( list, IRP, Tail.Overlay.ListEntry );

            // We're gonna use this later.  Zero it out now.
            RtlZeroMemory( pIrp->Tail.Overlay.DriverContext, sizeof *pIrp->Tail.Overlay.DriverContext );
        }

        if ( pIrp==NULL )
        {
            if ( pWrite->Sequence.WaitFlushEvent )
            {
                KeSetEvent( &pWrite->Sequence.FlushEvent, 0 , FALSE ); // tell flush event was done
            }
            DigiReleaseSpinLock( &pWrite->Lock );

            {            
                NTSTATUS eventSignaled = KeWaitForMultipleObjects( WRITE_EVENT_COUNT, (PVOID)&Events, WaitAny, Executive, KernelMode, FALSE, NULL, NULL );
            
                if ( eventSignaled == HALT_EVENT )
                    PsTerminateSystemThread( STATUS_SUCCESS );            
                else
                {
                    ASSERT( eventSignaled == WORK_EVENT );            
                    continue;
                }
            }                            
        }


        IoSetCancelRoutine( pIrp, NULL );
        DigiReleaseSpinLock( &pWrite->Lock );


        if ( pIrp->Cancel )
        {
            DigiDump( DIGIWRITE, ("In AsynWriteDispatch someone wants the irp cancelled\n" ));
            AsynIoCompleteRequest( pIrp, STATUS_CANCELLED, pExt );
            continue;
        }

        pIos = IoGetCurrentIrpStackLocation( pIrp );

        // Process the now active irp, e.g. try to send the data down to S/DXB.
        ASSERT( pIrp );
        {
            ULONG		cbTotal = pIos->Parameters.Write.Length;
            ULONG		maxWrite = 	pWrite->cbMaxPutMsg / 2;
            PUCHAR	pBuf = (PUCHAR)pIrp->AssociatedIrp.SystemBuffer;

            DigiAcquireSpinLock( &pWrite->Lock );
            pWrite->ActiveIrp = pIrp;
            IoSetCancelRoutine( pIrp, AsynCancelActiveIrp );
			
            if ( (pWrite->WriteTimer.TimerOn) && (!pWrite->WriteTimer.TimerInUse) )
            {
                // Here is where I will set up the write time out.

                pWrite->WriteTimer.TimerInUse = TRUE;
                (ULONG_PTR)pIrp->Tail.Overlay.DriverContext[ASYN_TIMER_IRP] = TIMER_IRP;
				
                pWrite->WriteTimer.TimerLength.QuadPart = (LONGLONG)(( cbTotal * pWrite->WriteTimer.TimeoutMultiplier ) + 
                                                                     pWrite->WriteTimer.TimeoutConstant ) * MILLISECOND * -1;

                KeSetTimer( &pWrite->WriteTimer.Timer, pWrite->WriteTimer.TimerLength, &pWrite->WriteTimer.TimeOutDPC );
            }
            DigiReleaseSpinLock( &pWrite->Lock );

            while ( cbTotal > 0 )
            {
                if ( (ULONG_PTR)pIrp->Tail.Overlay.DriverContext[ASYN_CONTEXT_COUNT] == NOT_USED )
                {
                    pIrp->IoStatus.Status = STATUS_CANCELLED;
                    DigiDump( DIGIWRITE, ("In AsynWriteDispatch someone wants the irp cancelled, not used\n" ));
                    cbTotal = 1;
                }
                if ( cbTotal > maxWrite )
                {
                    DigiDump( DIGIWRITE, ("About to write a large write %d and irp %x\n", maxWrite, pIrp ) );
                    Status = DsPutMsgEx( pExt->DsPortHandle, NULL, 0, pBuf, maxWrite, pIrp, DS_PRIORITY_LOW, AsynWritePartialComplete );
                    pBuf += maxWrite;
                    cbTotal -= maxWrite;
                    DigiDump( DIGIWRITE, ("I have %d left to write %d\n and the irp is %x\n", cbTotal, pIrp ));

                }
                else
                {
                    DigiDump( DIGIWRITE, ("Complete the Irp and the size is %d and irp %x\n", cbTotal, pIrp ));
                    pWrite->Sequence.CurrentCountIn += pIos->Parameters.Write.Length;
                    (ULONG_PTR)pIrp->Tail.Overlay.DriverContext[ASYN_SEQUENCE_NUMBER] = pWrite->Sequence.CurrentCountIn;
                    if ( !NT_SUCCESS( pIrp->IoStatus.Status ) )
                        DigiDump( DIGIWRITE, ("I have a bad status that I am passing to the put msg \n"));

                    Status = DsPutMsgEx( pExt->DsPortHandle, NULL, 0, pBuf, cbTotal, pIrp, DS_PRIORITY_LOW, AsynWriteIrpComplete );
                    cbTotal = 0;
                }
		if ( Status != STATUS_PENDING )
		{
		    // If DsPutMsgEx returns immediately, our completion routine will not 
		    // be called.
				  
		    DigiAcquireSpinLock( &pWrite->Lock );

		    if ( (ULONG_PTR)pIrp->Tail.Overlay.DriverContext[ASYN_TIMER_IRP] == TIMER_IRP  )
		    {
		        KeCancelTimer( &pWrite->WriteTimer.Timer );
			pWrite->WriteTimer.TimerInUse = FALSE;
		    }

		    IoSetCancelRoutine( pIrp, NULL );

		    if ( pIrp->Cancel )
		      pIrp->IoStatus.Status = STATUS_CANCELLED;
		    
		    DigiReleaseSpinLock( &pWrite->Lock );
		    
		    AsynIoCompleteRequest( pIrp, Status, pExt );
		    
		    cbTotal = 0;
		}
            }
            DigiAcquireSpinLock( &pWrite->Lock );
            pWrite->ActiveIrp = NULL;
            DigiReleaseSpinLock( &pWrite->Lock );

        }	
    }
}


NTSTATUS
AsynDeviceWrite(
	IN PDEVICE_OBJECT pDevice, 
	IN PIRP           pIrp )
/*++

Description: Serial Write handler.

Parameters: pDevice, pIrp

Return Value: STATUS_PENDING.

--*/
{
    NTSTATUS		Status = STATUS_PENDING;
    PASYN_PORT_EXT	pExt = pDevice->DeviceExtension;
    PASYN_WRITE		pWrite = &pExt->Write;
    KIRQL		finalIrq, startIrq = KeGetCurrentIrql();
	
    SRAPRINTIRPEX(DBGTYPE_IRP,DBGLVL_METRICS,("COM",pDevice,pIrp,FALSE));

    if (pDevice->DeviceType != FILE_DEVICE_SERIAL_PORT)
    {
        DigiDump(DIGIWRITE, ("*** AsynDeviceWrite: unknown device =0x%x Type=x0%x\n", 
                             pDevice, pDevice->DeviceType));
        Status = STATUS_UNSUCCESSFUL;
        goto Exit;
    } 
   
    // Is Port open?   
    if ( pExt->State != OpenState )
    {
        Status = STATUS_CANCELLED;
        DigiDump( DIGIWRITE, ("In AsynDeviceWrite and failed the open port test\n" ));
        goto Exit;
    }

    // has port failed?
    if (pExt->DeviceStateFlags & PNP_DEVICE_FAILED)
    {
        Status = STATUS_IO_DEVICE_ERROR;
        DigiDump( DIGIWRITE, ("In AsynDeviceWrite port has failed\n" ));
        goto Exit;
    }

    ASSERT ( pIrp->Type == IO_TYPE_IRP );
   
    // Check first for a write length of 0
    if ( IoGetCurrentIrpStackLocation(pIrp)->Parameters.Write.Length==0 )
    {
        // We assume a write of 0 length is valid.  Just complete the request and return
        pIrp->IoStatus.Information = 0;
        Status = STATUS_SUCCESS;
        goto Exit;
    }      

    // Check for a null buffer
    if ( pIrp->AssociatedIrp.SystemBuffer == NULL )
    {
        // This is most definitely a No No!
        DigiDump( DIGIERROR, ("Port %d: AsynDeviceWrite - Invalid Irp->AssociatedIrp.SystemBuffer!\n", pExt->iSdxbPort) );

        pIrp->IoStatus.Information = 0;
        Status = STATUS_INVALID_PARAMETER;
        goto Exit;
    }
   
    if (SRACHECKLEVEL2(DBGTYPE_IRP,DBGLVL_DETAILED) &&
        pIrp->AssociatedIrp.SystemBuffer) {
        ULONG Length = IoGetCurrentIrpStackLocation(pIrp)->Parameters.Write.Length;

        if (Length) {
            if (Length > 0x20)
                Length = 0x20;

            SraDbgPrintBufferEx("    ",0,1,pIrp->AssociatedIrp.SystemBuffer,Length);
        }
    }

    {
        DigiAcquireSpinLock( &pWrite->Lock );

        IoMarkIrpPending( pIrp );
        RtlZeroMemory( &pIrp->Tail.Overlay.DriverContext, sizeof( pIrp->Tail.Overlay.DriverContext ) );
        pIrp->IoStatus.Information = 0;

//		DigiDump( DIGIWRITE, (" Write IRP %x received with Write.length = %x \n",pIrp,(IoGetCurrentIrpStackLocation( pIrp ))->Parameters.Write.Length));

        IoSetCancelRoutine( pIrp, AsynWriteCancelQueuedIrp );

        InsertTailList( &pWrite->PendingQueue, &pIrp->Tail.Overlay.ListEntry );
        Status = STATUS_PENDING;
        DigiReleaseSpinLock( &pWrite->Lock );

//		ASSERT( startIrq == PASSIVE_LEVEL );
        KeSetEvent( &pWrite->WorkEvent, 0, FALSE );		
    }

    finalIrq = KeGetCurrentIrql();
    if ( finalIrq != startIrq )
    {
        DigiDump( DIGIWRITE, (" Asyn!DeviceWrite Error: enterIrql 0x%x != finalIrql 0x%x\n", startIrq, finalIrq ) );
    }
Exit:
    if (Status == STATUS_PENDING) {
        // Now Irp is queued - check if adapter has failed and if so
        // cancel all outstanding IO
        // NOTE: This is done AFTER queueing the Irp for a very good
        //       reason! To check before in a synchronized way, we'd
        //       have to acquire and hold the device extension spinlock
        //       across the queue call - I dont like holding multiple
        //       locks because it's an invitation to deadlock city.
        //       Instead, we take the optimistic approach and check in
        //       an unsynchronized manner before and again after the fact. 
        //       No need to acquire the spinlock to do the check because 
        //       the place that SETS the flag also cancels outstanding IO
        // NOTE ALSO: This check does NOT affect whether or not STATUS_PENDING
        //            is returned to the caller - we ALWAYS do this.
        AsynCheckForPortFailure(pExt);
    }
    else 
        AsynIoCompleteRequest( pIrp, Status, pExt );

    return Status;
}
/************************************************************************************************************
**
**	NTSTATUS  AsynPurgeQueue( IN PLIST_ENTRY  Queue, IN NTSTATUS Status )
**
**	Purpose:  There are serval places where I will empty a queue or Irps and complete the Irps.  Well
**		here is the function that will do that.
**
**	Given:	The queue to be canceled and the status that is to be given to the irps.  If the status is 
**			negitave 1, this means that the irps have already been given a status and to use that to complete them
**
**************************************************************************************************************/
NTSTATUS AysnPurgeQueue( IN PLIST_ENTRY Queue, IN NTSTATUS Status, IN PASYN_PORT_EXT pPortExt )
{
    PIRP			tempIrp;
    PLIST_ENTRY		listEntry;
    PASYN_WRITE		pWrite = &pPortExt->Write;

    DigiAcquireSpinLock( &pWrite->Lock );
    while ( !IsListEmpty(Queue) )
    {	
        listEntry = RemoveHeadList( Queue );
        tempIrp = CONTAINING_RECORD( listEntry, IRP, Tail.Overlay.ListEntry );
        ASSERT( tempIrp->Type == IO_TYPE_IRP );

        if ( !tempIrp )
            continue;

        DigiReleaseSpinLock( &pWrite->Lock );
        if ( Status == -1 )
            AsynIoCompleteRequest( tempIrp, tempIrp->IoStatus.Status, pPortExt  );
        else
            AsynIoCompleteRequest( tempIrp, Status, pPortExt );
        DigiAcquireSpinLock( &pWrite->Lock );
		
    }
    DigiReleaseSpinLock( &pWrite->Lock );

    return STATUS_SUCCESS;
}

/**************************************************************************************************************************
**
**	VOID AsynWriteTimedOut( IN PKDPC WriteTimeOut, IN PVOID Context, IN PVOID void1, IN PVOID void2 )
**
**	Purpose: The write took too long as far as the application is concerned so we timed out.  So I will
**		send down a purge, clean up the write that was sent or should I clean up everthing?
**
**	Given: The dpc and the port device extension
**
**************************************************************************************************************************/
VOID AsynWriteTimeout( IN PKDPC WriteTimeOut, IN PVOID Context, IN PVOID void1, IN PVOID void2 )
{
    PASYN_PORT_EXT	pExt = (PASYN_PORT_EXT)Context;
    PASYN_WRITE		pWrite = &pExt->Write;
    PIRP			pIrp;
    PLIST_ENTRY		listEntry;

	
    KeCancelTimer( &pWrite->WriteTimer.Timer );
    DsPurge( pExt->DsPortHandle, DS_PURGE_PUTMSG );
    pWrite->TimeoutActive = TRUE;
    DigiAcquireSpinLock( &pWrite->Lock );

    while ( !IsListEmpty( &pWrite->PendingQueue ) )
    {
        listEntry = RemoveHeadList( &pWrite->PendingQueue );
        pIrp = CONTAINING_RECORD( listEntry, IRP, Tail.Overlay.ListEntry );

        if ( !pIrp )
            continue;

        // Make sure before I put this on the list that I will put the correct status.info and status.status
        IoSetCancelRoutine( pIrp, NULL );
        DigiReleaseSpinLock( &pWrite->Lock );
        AsynIoCompleteRequest( pIrp, STATUS_TIMEOUT, pExt );
        DigiAcquireSpinLock( &pWrite->Lock );

    }
    DigiReleaseSpinLock( &pWrite->Lock );

}
/*********************************************************************************************************************************
**
**	NTSTATUS  AysnStartStopWriteTimeout( IN PASYN_WRITE pWrite, IN BOOLEAN Start
**
**	Purpose: This function is called by the ioclt handler responding from a ring three app.  It decides wether to start
**		or stop time outs for the writes.
**
**	Given: The write portion of the device extension.  Also wether to start or stop the timer.
**		   TimeLength is given in milliseconds I do the conversion from 100 nanosecods to milliseconds.
**
**	Returns: STATUS SUCCESS always.
**
**********************************************************************************************************************************/
NTSTATUS  AsynSetWriteTimeout( IN PASYN_WRITE pWrite, PSERIAL_TIMEOUTS  TimeOut )
{
    DigiAcquireSpinLock( &pWrite->Lock );
	
    if ( (!TimeOut->WriteTotalTimeoutConstant) && (!TimeOut->WriteTotalTimeoutMultiplier) )
    {
        KeCancelTimer( &pWrite->WriteTimer.Timer );
        pWrite->WriteTimer.TimerOn = FALSE;
        pWrite->WriteTimer.TimerInUse = FALSE;
    }
    else
    {
        pWrite->WriteTimer.TimerOn = TRUE;
        pWrite->WriteTimer.TimeoutMultiplier = TimeOut->WriteTotalTimeoutMultiplier;
        pWrite->WriteTimer.TimeoutConstant = TimeOut->WriteTotalTimeoutConstant;
    }
		
    DigiReleaseSpinLock( &pWrite->Lock );
    return STATUS_SUCCESS;
}

/**********************************************************************************************************************************
**
**	NTSTATUS  AysnGetWriteTimeout( IN PASYN_WRITE pWrite, PSERIAL_TIMEOUTS TimeOut )
**
**	Purpose: This is called by the IOCTL handler.  All we are going to do is put the current values in the supplied
**		struct and send it back.
**
**	Given: The Write portion of the device extension and the time out structure ( Microsoft defined )
**
**
********************************************************************************************************************************/
NTSTATUS AsynGetWriteTimeout( IN PASYN_WRITE pWrite, IN PSERIAL_TIMEOUTS TimeOut )
{
    TimeOut->WriteTotalTimeoutMultiplier = pWrite->WriteTimer.TimeoutMultiplier;
    TimeOut->WriteTotalTimeoutConstant = pWrite->WriteTimer.TimeoutConstant;
    return STATUS_SUCCESS;
}
/************************************************************************************************
**
**	ULONG	AysnWriteCommStatus( IN PAYSN_WRITE pWrite )
**
**	Purpose: An Ioctl was called asking how much data is in the transmit buffer.  So I will 
**		tell them with what accuracy I can.
**
***************************************************************************************************/
ULONG AsynWriteCommStatus( IN PASYN_WRITE pWrite )
{
    ULONG		transmitLength = 0;

    DigiAcquireSpinLock( &pWrite->Lock );
    transmitLength = pWrite->Sequence.CurrentCountIn - pWrite->Sequence.CurrentCountOut;
    DigiReleaseSpinLock( &pWrite->Lock );

    return transmitLength;
}
/***************************************************************************************************
**
**	VOID AsynWriteClearStats( IN PASYN_WRITE pWrite )
**
**	Purpose: This is a user directive to clear out the stats of the transmit buffer.  But since
**		the card only resets during an open and close I'll have to resort to some trickery.
**		This is know as the Hucklebuck.
**
***************************************************************************************************/
VOID AsynWriteClearStats( IN PASYN_WRITE pWrite )
{
// TODO: To be investigated. Removed the following because it caused a hang
//       when dialing a modem.
//	while( pWrite->Sequence.CurrentCountIn > pWrite->Sequence.CurrentCountOut )
//	{
//		KeWaitForSingleObject( &pWrite->Sequence.ClearEvent,
//							   Executive,
//							   KernelMode,
//							   FALSE,
//							   NULL );
//	}
    DigiAcquireSpinLock( &pWrite->Lock );
    pWrite->Sequence.CurrentCountReference = pWrite->Sequence.CurrentCountOut;
    DigiReleaseSpinLock( &pWrite->Lock );
}

/***************************************************************************************************
**
**	ULONG AsynWriteGetWriteStats( IN PASYN_WRITE pWrite )
**
**	Purpose: The user requested the total number of bytes that have been transmitted.  Note that
**		There could still be some pending bytes in the queue.
**
****************************************************************************************************/
ULONG AsynWriteGetWriteStatus( IN PASYN_WRITE pWrite )
{
    ULONG		status;

    // First check to see if there is a wrap.
//	if ( pWrite->Sequence.CurrentCountOut < pWrite->Sequence.CountReference )
//		status = 0xFFFFFFFF - pWrite->Sequence.CountReference + pWrite->Sequence.CurrentCountOut;
//	else
    status = pWrite->Sequence.CurrentCountIn - pWrite->Sequence.CurrentCountReference;

    return status;
}
/***************************************************************************************************
**
**	VOID AsynWritePurge( IN PASYN_WRITE pWrite, IN ULONG Mask )
**
**	Purpose: The user wants to either purge the pending Irps or the transmit queue or both. 
**		This is the function that would do that.
**
****************************************************************************************************/
VOID AsynWritePurge( IN PASYN_PORT_EXT pExt, IN ULONG Mask )
{
    PLIST_ENTRY		pEntry;
    PIRP			pIrp;
    PASYN_WRITE		pWrite = &pExt->Write;

    if ( Mask & SERIAL_PURGE_TXABORT )
    {
        DigiAcquireSpinLock( &pWrite->Lock );
        while ( !IsListEmpty( &pWrite->PendingQueue ) )
        {
            pEntry = RemoveHeadList( &pWrite->PendingQueue );
            pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );

            if ( !pIrp )
                continue;

            IoSetCancelRoutine( pIrp, NULL );
            pIrp->IoStatus.Status = STATUS_CANCELLED;
            pIrp->IoStatus.Information = 0;

            DigiDump( DIGIWRITE, ("In AsynWritePurge someone wants the irp cancelled\n" ));

            DigiReleaseSpinLock( &pWrite->Lock );
            AsynIoCompleteRequest( pIrp, STATUS_CANCELLED, pExt );
            DigiAcquireSpinLock( &pWrite->Lock );

        }
        DigiReleaseSpinLock( &pWrite->Lock );
    }

    if ( Mask & SERIAL_PURGE_TXCLEAR )
    {
//		DsPurge( pExt->DsPortHandle, DS_PURGE_PUTMSG );
        SdxbFlush( pExt->DsPortHandle, SDXB_FLUSH_OUTPUT );
        // Now I am going to clean out the Sequece Wait Queue and fail them
        DigiAcquireSpinLock( &pWrite->Sequence.WaitLock );
        while( !IsListEmpty( &pWrite->Sequence.WaitTillCompleteQueue ) )
        {
            pEntry = RemoveHeadList( &pWrite->Sequence.WaitTillCompleteQueue );
            pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );

            if ( !pIrp )
                continue;

            pIrp->IoStatus.Status = STATUS_CANCELLED;
            pIrp->IoStatus.Information = 0;
            DigiDump( DIGIWRITE, ("In AsynWritePurge someone wants the irp cancelled\n" ));

            DigiReleaseSpinLock( &pWrite->Sequence.WaitLock );
            AsynIoCompleteRequest( pIrp, STATUS_CANCELLED, pExt );
            DigiAcquireSpinLock( &pWrite->Sequence.WaitLock );
        }
        DigiReleaseSpinLock( &pWrite->Sequence.WaitLock );
    }
}

/****************************************************************************************************
**
**	NTSTATUS  AsynWaitTxEmpty( PAYSN_WRITE pWrite )
**
**	Purpose: The user wants to know when the TX buffer is empty.  This means that the current out
**		count will equal the current in count.  If this is not the initial condition then we
**		will hit the semphore and wait for the condition to become true.  When it does fire
**		an event, if it doesn't then time out clean up and return an error.
**
****************************************************************************************************/
VOID  AsynWaitTxEmpty( PASYN_WRITE pWrite, BOOLEAN SetEvent )
{
    DigiAcquireSpinLock( &pWrite->Lock );
    pWrite->Sequence.WaitEmptyEvent = SetEvent;
    DigiReleaseSpinLock( &pWrite->Lock );

}
