/*++
*******************************************************************************
* 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 Name:

   dxbstream.c

Abstract:

	A stream object represents a single dxb channel, or stream.  All activity on a
	channel or stream is managed by the stream object: state information, tx and rx 
	data queues, and handlers for all DStreams API requests (Open, Close, PutMsg, 
	Ioctl, etc.).

	This module contains all stream object methods.

Author:

   Michael Zarns

--*/
#include "digidxb.h"


#define DXB_CHANNEL_NONE		(DXB_MAX_CHANNELS)

STATIC INLINE PVOID GetIrpBuffer( PIRP pIrp ) { return pIrp->AssociatedIrp.SystemBuffer; }

STATIC INLINE VOID SetRequestInProgress( PDXB_IO_REQUEST pRequest )
{
	ASSERT( !pRequest->InProgress );
	pRequest->InProgress = TRUE;
}

STATIC INLINE VOID ClearRequestInProgress( PDXB_IO_REQUEST pRequest )
{
	pRequest->InProgress = FALSE;
}

STATIC INLINE BOOLEAN IsRequestInProgress( PDXB_IO_REQUEST pRequest )
{
	return pRequest->InProgress;
}

STATIC VOID
dxbCancelHandler( 
	IN PDEVICE_OBJECT pDevice, 
	IN PIRP           pIrp );

STATIC INLINE VOID PushRequestIrp( PDXB_IO_REQUEST pRequest, PIRP pIrp )
{
	ASSERT( pRequest->pIrp == NULL );
	ASSERT( !IsRequestInProgress( pRequest ) );
	ClearRequestInProgress( pRequest );
	(void)IoSetCancelRoutine( pIrp, dxbCancelHandler );
	pRequest->pIrp = pIrp;
}

STATIC INLINE PIRP PopRequestIrp( PDXB_IO_REQUEST pRequest )
{
	PIRP pIrp = pRequest->pIrp;
	if ( pIrp != NULL )
	{
		(void)IoSetCancelRoutine( pIrp, NULL );
		pRequest->pIrp = NULL;
		ClearRequestInProgress( pRequest );
	}
	else
	{
		DigiDump( DIGIWARN, ( "DXB!PopRequestIrp: found NULL Irp on pRequest 0x%x.  Cancelled?\n", pRequest ) );
	}
	return pIrp;
}

STATIC INLINE PIRP PeekRequestIrp( PDXB_IO_REQUEST pRequest )
{
	return pRequest->pIrp;
}

//
// The Request field in the following #defines is the name of the request: Open, 
// Close, Ioctl, or Link.
//

#define dxbStreamPushIrp( pStream, Request, pIrp )          \
	PushRequestIrp( &(pStream)->Control.##Request, pIrp )

#define dxbStreamPopIrp( pStream, Request )                 \
	PopRequestIrp( &(pStream)->Control.##Request )

#define dxbStreamPeekIrp( pStream, Request )                \
	PeekRequestIrp( &(pStream)->Control.##Request )

#define dxbStreamPeekRequest( pStream, Request )            \
	GetIrpBuffer( dxbStreamPeekIrp( pStream, Request ) )

#define dxbStreamSetInProgress( pStream, Request )          \
	SetRequestInProgress( &(pStream)->Control.##Request )

#define dxbStreamClearInProgress( pStream, Request )        \
	ClearRequestInProgress( &(pStream)->Control.##Request )

#define dxbStreamIsInProgress( pStream, Request )           \
	IsRequestInProgress( &(pStream)->Control.##Request )


STATIC INLINE BOOLEAN
dxbStreamAssignChannel( 
	IN PDXB_DEVICE_EXTENSION  pExt, 
	IN OUT PDXB_STREAM_OBJECT pStream )
{
	ULONG i;
	BOOLEAN Found;
	for ( Found = FALSE, i = 1; i < DXB_MAX_CHANNELS && !Found; i++ )
	{
		DigiAcquireSpinLock( &pExt->Channels[i].DxbLock );
		if ( pExt->Channels[i].State == STREAM_STATE_INACTIVE )
		{
			pExt->Channels[i].pStream = pStream;
			pExt->Channels[i].State = STREAM_STATE_OPENING;
			pStream->Channel = i;
			Found = TRUE;
		}
		DigiReleaseSpinLock( &pExt->Channels[i].DxbLock );
	}
	return Found;
}


STATIC INLINE DXB_STREAM_STATE
dxbStreamGetState( IN PDXB_STREAM_OBJECT pStream )
{
	return pStream->pExt->Channels[pStream->Channel].State;
}


STATIC INLINE VOID 
dxbStreamSetState( IN PDXB_STREAM_OBJECT pStream, IN DXB_STREAM_STATE State )
{
	pStream->pExt->Channels[pStream->Channel].State = State;
}

STATIC INLINE VOID
dxbStreamLock( 
	IN PDXB_STREAM_OBJECT pStream )
{
	ASSERT( MmIsAddressValid( pStream ) );
	DigiAcquireSpinLock( &pStream->pExt->Channels[pStream->Channel].DxbLock );
	DigiDump( DIGISTREAMLOCK, ( "DXB:Stream Locked, channel %d\n", pStream->Channel) );
}

STATIC INLINE PDXB_STREAM_OBJECT
dxbStreamLockByChannelEx( 
	IN PDXB_DEVICE_EXTENSION pExt, 
	IN ULONG                 Channel,
	IN BOOLEAN               IsNullStreamError )
{
	PDXB_STREAM_OBJECT pStream;
 	ASSERT( Channel <= DXB_MAX_CHANNELS );

	DigiAcquireSpinLock( &pExt->Channels[Channel].DxbLock );
	DigiDump( DIGISTREAMLOCK, ( "DXB!Stream Locked, channel %d\n", Channel) );
	
	pStream = pExt->Channels[Channel].pStream;
	if ( pStream == NULL )
	{
		if ( IsNullStreamError )
		{
			DigiDump( DIGIERROR, ( "DXB!StreamLockByChannel: Invalid operation on NULL stream, channel 0x%x!\n", Channel ) );
		}
		DigiReleaseSpinLock( &pExt->Channels[Channel].DxbLock );
	}

	return pStream;
}

#define dxbStreamLockByChannel( pExt, Channel )		dxbStreamLockByChannelEx( (pExt), (Channel), TRUE )


STATIC INLINE VOID
dxbStreamUnlock( 
	IN PDXB_STREAM_OBJECT pStream )
{
	ASSERT( pStream != NULL );
	DigiDump( DIGISTREAMLOCK, ( "DXB!Stream Unlocked, channel %d\n", pStream->Channel) );
	DigiReleaseSpinLock( &pStream->pExt->Channels[pStream->Channel].DxbLock );
}

STATIC INLINE VOID
dxbStreamInitialize( 
	PDXB_STREAM_OBJECT    pStream,
	PDXB_DEVICE_EXTENSION pExt )
{
	RtlZeroMemory( pStream, sizeof *pStream );
	pStream->Type = DXB_TYPE_STREAM_OBJECT;
	pStream->pExt = pExt;
	// Set Invalid Channel number
	pStream->Control.LinkedUnder = pStream->Control.LinkedAbove = DXB_CHANNEL_NONE;

	// Init Data queues
	{
		ULONG i;
		for ( i = DS_PRIORITY_MIN; i < DS_PRIORITY_LIM; i++ )
		{
			InitializeListHead( &pStream->Data.PutQ[i] );
			InitializeListHead( &pStream->Data.GetQ[i] );
			InitializeListHead( &pStream->Data.IncomingQ[i] );
		}
	}

	pStream->Write = pExt->CreditDefault;
	pStream->Read  = pExt->CreditDefault;

	pStream->CntQueued = 0;
}


STATIC INLINE VOID 
dxbStreamFree( 
   PDXB_STREAM_OBJECT pStream )
{
	DigiFreeMem( pStream );
}


STATIC INLINE VOID
dxbStreamDelete( 
   PDXB_STREAM_OBJECT pStream )
/*++

Comments
	Requires stream lock to be held while executing.

--*/
{
	ULONG Chan = pStream->Channel;
	PDXB_DEVICE_EXTENSION pExt = pStream->pExt;

	ASSERT( pExt->Channels[Chan].State != STREAM_STATE_INACTIVE );
	ASSERT( pExt->Channels[Chan].pStream == pStream );
	pExt->Channels[Chan].pStream = NULL;
	pExt->Channels[Chan].State = STREAM_STATE_INACTIVE;

	dxbStreamUnlock( pStream );

	DigiAcquireSpinLock( &pExt->DxbLock );
	RemoveEntryList( &pStream->Entries.StreamList );
	DigiReleaseSpinLock( &pExt->DxbLock );

	dxbStreamFree( pStream );
}


STATIC INLINE PDXB_STREAM_OBJECT
dxbStreamNew( 
	PDXB_DEVICE_EXTENSION pExt,
	HANDLE                OriginalFileObject )
{
	PDXB_STREAM_OBJECT pStream = DigiAllocMemEx( NonPagedPool, sizeof *pStream, '2BxD' );
	if ( pStream == NULL )
		goto Exit;

	dxbStreamInitialize( pStream, pExt );

	if ( !dxbStreamAssignChannel( pExt, pStream ) )
	{
		dxbStreamFree( pStream );
		pStream = NULL;
		goto Exit;
	}

	pStream->OriginalFileObject = OriginalFileObject;

	DigiAcquireSpinLock( &pExt->DxbLock );
	InsertTailList( &pExt->StreamList, &pStream->Entries.StreamList );
	DigiReleaseSpinLock( &pExt->DxbLock );

Exit:
	return pStream;
}


STATIC INLINE NTSTATUS
dxbStreamMarkLinked( 
	PDXB_DEVICE_EXTENSION pExt, 
	ULONG                 ChanUpper, 
	ULONG                 ChanLower )
{
	NTSTATUS Status;

	DigiAcquireSpinLock( &pExt->DxbLock );

	{
		PDXB_STREAM_OBJECT pStream = dxbStreamLockByChannel( pExt, ChanUpper );
		if ( pStream != NULL )
		{
			if ( dxbStreamGetState( pStream ) == STREAM_STATE_READY )
			{
				pStream->Control.LinkedAbove = ChanLower;
				Status = STATUS_SUCCESS;
			}
			else
				Status = STATUS_UNSUCCESSFUL;
			dxbStreamUnlock( pStream );
		}
		else
			Status = STATUS_UNSUCCESSFUL;
	}

	if ( NT_SUCCESS( Status ) )
	{
		PDXB_STREAM_OBJECT pStream = dxbStreamLockByChannel( pExt, ChanLower );
		if ( pStream != NULL )
		{
			if ( dxbStreamGetState( pStream ) == STREAM_STATE_READY ) 
			{
				pStream->Control.LinkedUnder = ChanUpper;
				dxbStreamUnlock( pStream );
				Status = STATUS_SUCCESS;
			}
			else
				Status = STATUS_UNSUCCESSFUL;
		}
		else
				Status = STATUS_UNSUCCESSFUL;
	}

	DigiReleaseSpinLock( &pExt->DxbLock );
	return Status;
}

STATIC INLINE VOID
dxbStreamMarkUnlinked( PDXB_DEVICE_EXTENSION pExt, ULONG Channel )
{
	ULONG ChanLower;
	DigiAcquireSpinLock( &pExt->DxbLock );
	{
		// Mark upper stream as not linked
		PDXB_STREAM_OBJECT pStream = dxbStreamLockByChannel( pExt, Channel );
		ASSERT( pStream != NULL );
		ChanLower = pStream->Control.LinkedAbove;
		pStream->Control.LinkedAbove = DXB_CHANNEL_NONE; // Invalid channel number
		dxbStreamUnlock( pStream );
	}

	{
		// Mark lower stream as not linked
		PDXB_STREAM_OBJECT pStream = dxbStreamLockByChannel( pExt, ChanLower );
		ASSERT( pStream != NULL );
		pStream->Control.LinkedUnder = DXB_CHANNEL_NONE; // Invalid channel number
		dxbStreamUnlock( pStream );
	}
	DigiReleaseSpinLock( &pExt->DxbLock );
}

STATIC INLINE BOOLEAN
dxbStreamLinkedUnder( PDXB_STREAM_OBJECT pStream )
{
	return pStream->Control.LinkedUnder != DXB_CHANNEL_NONE;
}

STATIC INLINE BOOLEAN
dxbStreamLinkedAbove( PDXB_STREAM_OBJECT pStream )
{
	return pStream->Control.LinkedAbove != DXB_CHANNEL_NONE;
}


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

Description:

	Cancel all Irps on a given stream queue array: PutQ, GetQ.
	
	Requires the stream lock be held when this routine is called and released upon 
	return.

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;
	ULONG Priority;
	for ( Priority = DS_PRIORITY_MIN; Priority < DS_PRIORITY_COUNT; Priority++ )
	{
		PLIST_ENTRY pListHead = &pQueue[Priority];
		PLIST_ENTRY pEntry = pListHead->Flink;
		while ( pEntry != pListHead )
		{
			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 );
				(void)IoSetCancelRoutine( pIrp, NULL );
				InsertTailList( pCancelledQ, &pIrp->Tail.Overlay.ListEntry );
				fCancelled = TRUE;
			}
		}
	}

	return fCancelled;
}


STATIC VOID
dxbCancelHandler( 
	IN PDEVICE_OBJECT pDevice, 
	IN PIRP           pOrigIrp )
/*++

Description

	Cancel routine for all pended Irps.  Walk list of stream objects for the specified
	device/adapter completing all Irps held by the stream having the cancel flag set.

--*/
{
	PDXB_DEVICE_EXTENSION pExt;
	ULONG Channel;
	LIST_ENTRY CancelList;

	DigiDump( DIGICANCEL, ( "DXB!CancelHandler: pDevice 0x%x, pOrigIrp 0x%x\n", pDevice, pOrigIrp ) );

	ASSERT( MmIsAddressValid( pOrigIrp ) );
	IoReleaseCancelSpinLock( pOrigIrp->CancelIrql );

	InitializeListHead( &CancelList );
	pExt = pDevice->DeviceExtension;

	// Walk through all channels on this adapter looking for open streams
	for ( Channel = 1; Channel < DXB_MAX_CHANNELS; Channel++ )
	{
		PDXB_STREAM_OBJECT pStream = dxbStreamLockByChannelEx( pExt, Channel, FALSE );

		if ( pStream )
		{
			// Cancel all cancel flagged Irps on this stream

			// Open
			{
				PIRP pIrp = dxbStreamPeekIrp( pStream, Open );
				if ( pIrp != NULL && pIrp->Cancel )
				{
					(void)dxbStreamPopIrp( pStream, Open );
					InsertTailList( &CancelList, &pIrp->Tail.Overlay.ListEntry );
				}
			}

			// Ioctl
			{
				PIRP pIrp = dxbStreamPeekIrp( pStream, Ioctl );
				if ( pIrp != NULL && pIrp->Cancel )
				{
					(void)dxbStreamPopIrp( pStream, Ioctl );
					InsertTailList( &CancelList, &pIrp->Tail.Overlay.ListEntry );
				}
			}

			// Link
			{
				PIRP pIrp = dxbStreamPeekIrp( pStream, Link );
				if ( pIrp != NULL && pIrp->Cancel )
				{
					(void)dxbStreamPopIrp( pStream, Link );
					InsertTailList( &CancelList, &pIrp->Tail.Overlay.ListEntry );
				}
			}

			// PutMsg Queues
			(void)dxbStreamCancelQueue( &pStream->Data.PutQ[0], FALSE, &CancelList );

			// GetMsg Queues
			(void)dxbStreamCancelQueue( &pStream->Data.GetQ[0], FALSE, &CancelList );


			// Close
			{
				PIRP pIrp = dxbStreamPeekIrp( pStream, Close );
				if ( pIrp != NULL && pIrp->Cancel )
				{
					(void)dxbStreamPopIrp( pStream, Close );
					InsertTailList( &CancelList, &pIrp->Tail.Overlay.ListEntry );
				}
			}

			dxbStreamUnlock( pStream );
		}
	}

	while ( !IsListEmpty( &CancelList ) )
	{
		PIRP pIrp;
		PLIST_ENTRY pEntry = RemoveHeadList( &CancelList );
		pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
		ASSERT( pIrp->Type == IO_TYPE_IRP );
		DigiDump( DIGICANCEL, ( "DXB!CancelHandler: Cancelling Irp 0x%x\n", pIrp ) );
		dxbIoCompleteRequest( pIrp, STATUS_CANCELLED, 0 );
	}

	DigiDump( DIGICANCEL, ( "DXB!CancelHandler: leave, pOrigIrp 0x%x\n", pOrigIrp ) );
}


STATIC INLINE BOOLEAN
dxbIsValidSz( 
	PSZ   pBuf,
	ULONG cbMax )
{
	ULONG i;
	for ( i = 0; i < cbMax; i++ )
	{
		if ( pBuf[i] == 0 )
			break;
	}
	return i != cbMax;
}


STATIC INLINE NTSTATUS
dxbStreamDispatchOpen( 
	IN PDXB_STREAM_OBJECT pStream,
	IN PDS_OPEN           pOpen )
{
	NTSTATUS Status = DxbCmdOpen( pStream->pExt, pStream->Channel, pOpen );
	if ( NT_SUCCESS( Status ) )
		dxbStreamSetInProgress( pStream, Open );
	return Status;
}


NTSTATUS
dxbStreamOpen( 
	IN PDXB_DEVICE_EXTENSION pExt,
	IN PIRP                  pIrp,
	IN PIO_STACK_LOCATION    pIos )
{
	NTSTATUS Status;
	PDXB_STREAM_OBJECT pStream;
	PDS_OPEN pOpen;

	ASSERT( pExt );
	ASSERT( pIrp );
	ASSERT( pIos );

	if ( pIos->Parameters.DeviceIoControl.InputBufferLength  != sizeof *pOpen ||
	     pIos->Parameters.DeviceIoControl.OutputBufferLength != sizeof *pOpen )
	{
		Status = STATUS_INVALID_PARAMETER;
		goto Exit;
	}

	pOpen = GetIrpBuffer( pIrp );
	if ( !dxbIsValidSz( pOpen->DeviceName, sizeof pOpen->DeviceName ) ||
	     strlen( pOpen->DeviceName ) == 0 )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	if ( pOpen->FixedCost > MAXUCHAR )
	{
		Status = STATUS_INVALID_PARAMETER_2;
		goto Exit;
	}

	if ( ((pOpen->pUserData == NULL) ^ (pOpen->cbUserData == 0)) || pOpen->cbUserData > MAXUSHORT )
	{
		Status = STATUS_INVALID_PARAMETER_4;
		goto Exit;
	}

	if ( pOpen->pUserData )
	{
		if ( NT_ERROR( DigiLockBuffer( pIrp, pOpen->pUserData, pOpen->cbUserData, &pOpen->pProviderData ) ) )
		{
			Status = STATUS_INVALID_PARAMETER_3;
			goto Exit;
		}
	}
	else
		pOpen->pProviderData = pOpen->pUserData;


	DigiDump( DIGIDSOPEN, 
	          ( "DXB!StreamOpen: pExt 0x%08x, DeviceName: %s\n", pExt, pOpen->DeviceName ) );

	pStream = dxbStreamNew( pExt, pIrp->Tail.Overlay.OriginalFileObject );
	if ( pStream == NULL )
	{
		Status = STATUS_INSUFFICIENT_RESOURCES;
		goto Exit;
	}

	IoMarkIrpPending( pIrp );
	dxbStreamPushIrp( pStream, Open, pIrp );

	Status = dxbStreamDispatchOpen( pStream, pOpen );
	if ( NT_ERROR( Status ) )
		dxbWorkCmdInsertTail( pStream );

	// Override status.  It will complete sometime later.
	Status = STATUS_PENDING;

Exit:
	if ( NT_ERROR( Status ) )
		dxbIoCompleteRequest( pIrp, Status, 0 );

	return Status;
}


VOID
dxbStreamOnOpenComplete( 
	IN PDXB_DEVICE_EXTENSION pExt, 
	IN ULONG                 Chan, 
	IN LONG                  ReturnValue,
	IN UCHAR                 FixedCost )
{
	PIRP pIrp;
	PDXB_STREAM_OBJECT pStream;

	ASSERT( Chan < DXB_MAX_CHANNELS );

	pStream = dxbStreamLockByChannel( pExt, Chan );
	ASSERT( pStream != NULL );
	ASSERT( pStream->Type == DXB_TYPE_STREAM_OBJECT );
	ASSERT( dxbStreamGetState( pStream ) == STREAM_STATE_OPENING );

	pIrp = dxbStreamPopIrp( pStream, Open );
	if ( pIrp )
	{
		PDS_OPEN pDsOpen;
		pDsOpen = GetIrpBuffer( pIrp );
		pDsOpen->ErrorCode = ReturnValue;
		if ( ReturnValue != 0 )
		{
			dxbStreamDelete( pStream );
			pDsOpen->hStream = NULL;
			DigiDump( DIGIDSOPEN, ( "DXB!OpenComplete: Open rejected, value 0x%x\n", ReturnValue ) );
		}
		else
		{
			// Open succeeded!
			dxbStreamSetState( pStream, STREAM_STATE_READY );
			pStream->Write.FCost = FixedCost;
			dxbWorkCreditInsertTail( pStream );
			dxbStreamUnlock( pStream );
			pDsOpen->hStream = (HANDLE)Chan;
			DigiDump( DIGIDSOPEN, ( "DXB:OpenComplete: Open succeeded, hStream 0x%x\n", pDsOpen->hStream ) );
		}

		dxbIoCompleteRequest( pIrp, STATUS_SUCCESS, sizeof *pDsOpen );
	}
	else
	{
		// TODO: Cleanup here, close the stream
		DigiDump( DIGIWARN, ( "DXB!OpenComplete: Irp already cancelled?\n" ) );
		dxbStreamDelete( pStream );
	} 
	return;
}


STATIC BOOLEAN
dxbStreamFreeIncomingQ(
	IN PDXB_STREAM_OBJECT pStream )
/*++

Description:

	Free all incoming data packets on the Incoming queue array.
	
	Requires the stream lock be held when this routine is called and released upon 
	return.

Parameters:

	pStream
		The stream whose incoming messages should be freed.

Return Value:

	TRUE if any messages were freed.

--*/
{
	int i;
	BOOLEAN fFreed = FALSE;

	for ( i = 0; i < DS_PRIORITY_LIM; i++ )
	{
		PLIST_ENTRY ListHead = &pStream->Data.IncomingQ[i];
		while ( !IsListEmpty( ListHead ) )
		{
			PLIST_ENTRY pEntry = RemoveHeadList( ListHead );
			DigiFreeMem( CONTAINING_RECORD( pEntry, DXB_MSG, Entry ) );
			fFreed = TRUE;
		}
	}

	pStream->CntQueued = 0;

	return fFreed;
}


STATIC INLINE NTSTATUS
dxbStreamDispatchClose( 
	IN PDXB_STREAM_OBJECT pStream,
	IN PDS_CLOSE          pClose )
{
	NTSTATUS Status = STATUS_UNSUCCESSFUL;

	ASSERT( !dxbStreamIsInProgress( pStream, Close ) );
	ASSERT( !dxbStreamLinkedUnder(pStream) );

	if ( !dxbStreamLinkedUnder( pStream ) )
	{
		Status = DxbCmdClose( pStream->pExt, pStream->Channel, pClose );
		if ( NT_SUCCESS( Status ) )
			dxbStreamSetInProgress( pStream, Close );
	}
	return Status;
}


NTSTATUS
dxbStreamClose( 
	PDXB_DEVICE_EXTENSION pExt,
	PIRP                  pIrp,
	PIO_STACK_LOCATION    pIos )
{
	PDXB_STREAM_OBJECT pStream;
	PDS_CLOSE pClose;
	NTSTATUS Status;
	LIST_ENTRY CompletedQ;

	ASSERT( pExt );
	ASSERT( pIrp );
	ASSERT( pIos );

	pClose = GetIrpBuffer( pIrp );

	if ( pIos->Parameters.DeviceIoControl.InputBufferLength  != sizeof *pClose )
	{
		Status = STATUS_INVALID_PARAMETER;
		goto Exit;
	}

	if ( (ULONG)pClose->hStream == 0 || (ULONG)pClose->hStream >= DXB_MAX_CHANNELS )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	pStream = dxbStreamLockByChannel( pExt, (ULONG)pClose->hStream );
	DigiDump( DIGIDSCLOSE, ( "DXB!StreamClose: pExt 0x%08x, Chan %d, pStream 0x%08x\n",
	                       pExt, pClose->hStream, pStream ) );

	if ( pStream == NULL )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	if ( dxbStreamGetState( pStream ) != STREAM_STATE_READY )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		dxbStreamUnlock( pStream );
		goto Exit;
	}

	ASSERT( dxbStreamPeekIrp( pStream, Close ) == NULL );

	// Be sure to remove this stream from the credit dpc queue
	dxbWorkCreditRemoveEntry( pStream );

	// Cleanup queues
	InitializeListHead( &CompletedQ );
	(void)dxbStreamCancelQueue( &pStream->Data.PutQ[0], TRUE, &CompletedQ );
	(void)dxbStreamCancelQueue( &pStream->Data.GetQ[0], TRUE, &CompletedQ );
	(void)dxbStreamFreeIncomingQ( pStream );

	// TODO: Cancel Link, Ioctl, Push, Open

	// Complete the request async, mark irp pending
	Status = STATUS_PENDING;
	IoMarkIrpPending( pIrp );
	dxbStreamSetState( pStream, STREAM_STATE_CLOSING );
	dxbStreamPushIrp( pStream, Close, pIrp );
	dxbStreamUnlock( pStream );

	while ( !IsListEmpty( &CompletedQ ) )
	{
		PIRP pCancelledIrp;
		PLIST_ENTRY pEntry = RemoveHeadList( &CompletedQ );
		pCancelledIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
		dxbIoCompleteRequest( pCancelledIrp, STATUS_CANCELLED, 0 );
	}

	dxbWorkCmdInsertTail( pStream );

Exit:
	if ( NT_ERROR( Status ) )
		dxbIoCompleteRequest( pIrp, Status, 0 );

	return Status;
}


VOID 
dxbStreamOnCloseComplete( 
	IN PDXB_DEVICE_EXTENSION pExt, 
	IN ULONG                 Channel )
{
	PDXB_STREAM_OBJECT pStream = dxbStreamLockByChannel( pExt, Channel );
	PIRP pIrp;

	ASSERT( pStream );
	ASSERT( dxbStreamGetState( pStream ) == STREAM_STATE_CLOSING );

	DigiDump( DIGIDSCLOSE, 
	          ( "DXB!CloseComplete: pExt 0x%08x, Chan %d, pStream 0x%08x\n",
	          pExt, Channel, pStream ) );

	pIrp = dxbStreamPopIrp( pStream, Close );

	dxbStreamDelete( pStream );
	if ( pIrp )
		dxbIoCompleteRequest( pIrp, STATUS_SUCCESS, 0 );
	else
	{
		DigiDump( DIGIWARN, ( "dxbStreamOnCloseComplete: Irp already cancelled?\n" ) );
	}
	return;
}


STATIC BOOLEAN
IsQEmptyUp( 
	IN PLIST_ENTRY pQueueHead,
	IN DS_PRIORITY Priority )
/*++

Description:

	Looks at all queues at priority level "Priority" and higher.  Returns TRUE if
	no Msgs of equal or higher priority are pending.

Parameters:

	pStream
		Stream on which to check

	Priority
		DS_PRIORITY_* level.  Checks queues for this and higher priority levels.

Return Value:

	TRUE if no equal/higher priority requests are pending.

--*/
{
	BOOLEAN Empty;
	ULONG i;

	for ( i = Priority, Empty = TRUE; i < DS_PRIORITY_LIM; i++ )
	{
		if ( !IsListEmpty( &pQueueHead[i] ) )
		{
			Empty = FALSE;
			break;
		}
	}
	return Empty;
}


STATIC BOOLEAN
IsQEmptyDown( 
	IN PLIST_ENTRY pQueueHead,
	IN DS_PRIORITY Priority )
/*++

Description:

	Looks at all queues at priority level Priority and *LOWER*.  Returns TRUE if
	no Msgs of equal or lower priority are pending.  Notice, this is different than
	IsQEmptyUp which looks for Priority and *Higher*.

Parameters:

	pStream
		Stream on which to check

	Priority
		DS_PRIORITY_* level.  Checks queues for this and lower priority levels.

Return Value:

	TRUE if no equal/higher priority requests are pending.

--*/
{
    BOOLEAN Empty;
	ULONG i;

	for ( i = Priority, Empty = TRUE; i > DS_PRIORITY_MIN; i-- )
	{
		if ( !IsListEmpty( &pQueueHead[i] ) )
		{
			Empty = FALSE;
			break;
		}
	}
	return Empty;
}


STATIC BOOLEAN 
IsPutQEmpty( 
	IN PDXB_STREAM_OBJECT pStream,
	IN DS_PRIORITY        Priority )
{
	return IsQEmptyUp( pStream->Data.PutQ, Priority );
}


STATIC BOOLEAN 
IsGetQEmpty( 
	IN PDXB_STREAM_OBJECT pStream,
	IN DS_PRIORITY        Priority )
{
	return IsQEmptyDown( pStream->Data.GetQ, Priority );
}


STATIC BOOLEAN 
IsIncomingQEmpty( 
	IN PDXB_STREAM_OBJECT pStream,
	IN DS_PRIORITY        Priority )
{
	return IsQEmptyUp( pStream->Data.IncomingQ, Priority );
}


STATIC INLINE NTSTATUS
dxbStreamDispatchPutMsg( 
	IN PDXB_STREAM_OBJECT pStream,
	IN PDS_PUTMSG         pPutMsg )
{
	NTSTATUS Status;

	if ( pPutMsg->Priority > DS_PRIORITY_LOW )
	{
		// Hi priority messages are not subject to flow control (credit charging)
		Status = DxbCmdPutMsg( pStream->pExt, pStream->Channel, pPutMsg );
		if ( NT_SUCCESS( Status ) )
			pStream->Stats.nPuts[DS_PRIORITY_HIGH]++;
		goto Exit;
	}

	// Otherwise, it's low priority and subject to flow control
	if ( dxbCreditApproved( &pStream->Write, dxbCreditPutMsgCost( &pStream->Write, pPutMsg ) ) )
	{
		Status = DxbCmdPutMsg( pStream->pExt, pStream->Channel, pPutMsg );
		if ( Status == STATUS_SUCCESS )
		{
			ULONG Amount;

			pStream->Stats.nPuts[DS_PRIORITY_LOW]++;

			Amount = dxbCreditPutMsgCost( &pStream->Write, pPutMsg );

			ASSERT( Amount <= pStream->Write.nCredits );

			dxbCreditCharge( &pStream->Write, Amount );

			DigiDump( DIGITXCREDIT, ( "DXB:Credit Write: PutMsg sent:  cb 0x%08x, available 0x%08x\n",
			          dxbCreditPutMsgCost( &pStream->Write, pPutMsg ), dxbCreditAvailable( &pStream->Write ) ) );
		}
	}
	else
	{
		DigiDump( DIGITXCREDIT,    ( "DXB:Credit Write: PutMsg flowcontrolled:         available 0x%08x\n",
		          dxbCreditAvailable( &pStream->Write ) ) );
		//Status = STATUS_DXB_CREDIT_EXCEEDED;
		Status = STATUS_DXB_CMD_QUEUE_FULL;
	}
	
Exit:
	ASSERT( Status == STATUS_SUCCESS || 
	        Status == STATUS_DXB_CREDIT_EXCEEDED || 
	        Status == STATUS_DXB_CMD_QUEUE_FULL );
	return Status;
}


NTSTATUS
dxbStreamDelayedPutMsgs( 
	PDXB_STREAM_OBJECT pStream,
	PLIST_ENTRY        pCompletedList )
{
	NTSTATUS Status = STATUS_SUCCESS;
	int i;

	for ( i = DS_PRIORITY_MAX; i >= 0; i-- )
	{
		while ( !IsListEmpty( &pStream->Data.PutQ[i] ) )
		{
			PIRP pIrp;
			PDS_PUTMSG pPutMsg;
			PLIST_ENTRY pEntry = RemoveHeadList( &pStream->Data.PutQ[i] );
			pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
			ASSERT( pIrp->Type == IO_TYPE_IRP );
			pPutMsg = GetIrpBuffer( pIrp );

			Status = dxbStreamDispatchPutMsg( pStream, pPutMsg );
			if ( NT_ERROR( Status ) )
			{				
				// Replace Irp at head of list
				InsertHeadList( &pStream->Data.PutQ[i], &pIrp->Tail.Overlay.ListEntry );
				goto Exit;
			}
			(void)IoSetCancelRoutine( pIrp, NULL );
			InsertTailList( pCompletedList, &pIrp->Tail.Overlay.ListEntry );
		}
	}

Exit:
	return Status;
}


NTSTATUS
dxbStreamPutMsg( 
   PDXB_DEVICE_EXTENSION pExt,
   PIRP                  pIrp,
   PIO_STACK_LOCATION    pIos )
{
	PDXB_STREAM_OBJECT pStream;
   NTSTATUS Status;
   PDS_PUTMSG pPutMsg;

	if ( pIos->Parameters.DeviceIoControl.InputBufferLength  != sizeof *pPutMsg )
	{
		Status = STATUS_INVALID_PARAMETER;
		goto Exit;
	}

   pPutMsg = GetIrpBuffer( pIrp );

	if ( (ULONG)pPutMsg->hStream == 0 || (ULONG)pPutMsg->hStream >= DXB_MAX_CHANNELS )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	if ( (pPutMsg->pControl == NULL) ^ (pPutMsg->cbControl == 0) )
	{
		Status = STATUS_INVALID_PARAMETER_2;
		goto Exit;
	}

	if ( (pPutMsg->pData == NULL) ^ (pPutMsg->cbData == 0) )
	{
		Status = STATUS_INVALID_PARAMETER_3;
		goto Exit;
	}

	if ( pPutMsg->pControl == NULL && pPutMsg->pData == NULL )
	{
		Status = STATUS_INVALID_PARAMETER_4;
		goto Exit;
	}

	if ( pPutMsg->Priority > DS_PRIORITY_MAX )
	{
		Status = STATUS_INVALID_PARAMETER_4;
		goto Exit;
	}

	if ( NT_ERROR( Status = DigiLockBuffer( pIrp, pPutMsg->pControl, 
	                        pPutMsg->cbControl, &pPutMsg->pProviderControl ) ) )
		goto Exit;

	if ( NT_ERROR( Status = DigiLockBuffer( pIrp, pPutMsg->pData, 
	                        pPutMsg->cbData, &pPutMsg->pProviderData ) ) )
		goto Exit;

	pStream = dxbStreamLockByChannel( pExt, (ULONG)pPutMsg->hStream );
	if ( pStream == NULL )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	if ( dxbStreamGetState( pStream ) != STREAM_STATE_READY )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Unlock;
	}

	pStream->Stats.nPutRequests++;

	if ( IsPutQEmpty( pStream, pPutMsg->Priority ) )
	{
		Status = dxbStreamDispatchPutMsg( pStream, pPutMsg );

		if ( Status == STATUS_SUCCESS )
			goto Unlock;
		
		// I can only receive these two messages:
		// STATUS_DXB_CREDIT_EXCEEDED
		// STATUS_DXB_CMD_QUEUE_FULL		

		if ( Status == STATUS_DXB_CMD_QUEUE_FULL )
			dxbWorkCmdInsertTail( pStream );

	}

	Status = STATUS_PENDING;
	IoMarkIrpPending( pIrp );
	(void)IoSetCancelRoutine( pIrp, dxbCancelHandler );
	InsertTailList( &pStream->Data.PutQ[pPutMsg->Priority], &pIrp->Tail.Overlay.ListEntry );

Unlock:
	dxbStreamUnlock( pStream );

Exit:

	if ( Status != STATUS_PENDING )
		dxbIoCompleteRequest( pIrp, Status, 0 );
	return Status;
}


NTSTATUS
dxbStreamGetMsg( 
	PDXB_DEVICE_EXTENSION pExt,
	PIRP                  pIrp,
	PIO_STACK_LOCATION    pIos )
{
	PDXB_STREAM_OBJECT pStream;
	NTSTATUS Status;
	PDS_GETMSG pGetMsg;

	if ( pIos->Parameters.DeviceIoControl.InputBufferLength  != sizeof *pGetMsg ||
	     pIos->Parameters.DeviceIoControl.OutputBufferLength != sizeof *pGetMsg )
	{
		Status = STATUS_INVALID_PARAMETER;
		goto Exit;
	}

	pGetMsg = GetIrpBuffer( pIrp );

	if ( (ULONG)pGetMsg->hStream == 0 || (ULONG)pGetMsg->hStream >= DXB_MAX_CHANNELS )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	if ( (pGetMsg->pControl == NULL) ^ (pGetMsg->cbMaxControl == 0) )
	{
		Status = STATUS_INVALID_PARAMETER_2;
		goto Exit;
	}

	if ( (pGetMsg->pData == NULL) ^ (pGetMsg->cbMaxData == 0) )
	{
		Status = STATUS_INVALID_PARAMETER_3;
		goto Exit;
	}

	if ( pGetMsg->pControl == NULL && pGetMsg->pData == NULL )
	{
		Status = STATUS_INVALID_PARAMETER_4;
		goto Exit;
	}

	if ( pGetMsg->Priority > DS_PRIORITY_MAX )
	{
		Status = STATUS_INVALID_PARAMETER_4;
		goto Exit;
	}

	if ( NT_ERROR( Status = DigiLockBuffer( pIrp, pGetMsg->pControl, 
	                        pGetMsg->cbMaxControl, &pGetMsg->pProviderControl ) ) )
		goto Exit;

	if ( NT_ERROR( Status = DigiLockBuffer( pIrp, pGetMsg->pData, 
	                        pGetMsg->cbMaxData, &pGetMsg->pProviderData ) ) )
		goto Exit;

	pStream = dxbStreamLockByChannel( pExt, (ULONG)pGetMsg->hStream );
	if ( pStream == NULL )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	if ( dxbStreamGetState( pStream ) != STREAM_STATE_READY )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Unlock;
	}

	// Sanity check queues.  We should never have equal-or-higher priority incoming 
	// messages queued while a GetMsg request is queued.
	ASSERT( IsListEmpty( &pStream->Data.GetQ[DS_PRIORITY_LOW] ) || IsIncomingQEmpty( pStream, DS_PRIORITY_LOW ) );
	ASSERT( IsListEmpty( &pStream->Data.GetQ[DS_PRIORITY_HIGH] )|| IsIncomingQEmpty( pStream, DS_PRIORITY_HIGH ) );

	// Only try to complete this GetMsg request if no previous requests of equal or 
	// higher priority are pending.
	if ( IsGetQEmpty( pStream, pGetMsg->Priority ) )
	{
		// Find highest priority incoming queue with pending messages
		PLIST_ENTRY pList = NULL;
		{
			LONG i;
			for ( i = DS_PRIORITY_MAX; i >= pGetMsg->Priority; i-- )
			{
				if ( !IsListEmpty( &pStream->Data.IncomingQ[i] ) )
				{
					pList = &pStream->Data.IncomingQ[i];
					break;
				}
			}
		}

		if ( pList != NULL )
		{
			// List found!  Grab msg and complete the GetMsg request.
			PDXB_MSG pMsg;
			PLIST_ENTRY pEntry = NULL;

			pEntry = RemoveHeadList( pList );
			pMsg = CONTAINING_RECORD( pEntry, DXB_MSG, Entry );

			if ( pMsg->cbCtrl > pGetMsg->cbMaxControl || 
			     pMsg->cbData > pGetMsg->cbMaxData )
			{
				// GetMsg can't hold this msg.  Replace msg on the queue, fail the request.
				DigiDump( DIGIERROR, ( "DXB:StreamGetMsg: failing insufficient GetMsg request!\n" 
				                       "    cbMaxControl 0x%08x, needed 0x%08x\n"
				                       "    cbMaxData 0x%08x,    needed 0x%08x\n"
				                       "    GetMsg Priority %d,      Incoming Priority %d\n",
				                       pGetMsg->cbMaxControl, pMsg->cbCtrl, 
				                       pGetMsg->cbMaxData, pMsg->cbData, 
				                       pGetMsg->Priority, pList == &pStream->Data.IncomingQ[1] ) );
				InsertHeadList( pList, &pMsg->Entry );
				Status = STATUS_BUFFER_TOO_SMALL;
				goto Unlock;
			}

			pGetMsg->cbControl = pMsg->cbCtrl;
			if ( pMsg->cbCtrl != 0 )
				RtlCopyMemory( pGetMsg->pProviderControl, pMsg->pCtrl, pMsg->cbCtrl );

			pGetMsg->cbData = pMsg->cbData;
			if ( pMsg->cbData != 0 )
				RtlCopyMemory( pGetMsg->pProviderData, pMsg->pData, pMsg->cbData );

			pStream->Stats.nGets[pGetMsg->Priority]++;
			pStream->CntQueued -= 1;

			dxbCreditMarkRefundable( &pStream->Read, dxbCreditEventCost( &pStream->Read, pMsg->cbCtrl, pMsg->cbData ) );
			dxbWorkCreditInsertTail( pStream );
			DigiFreeMem( pMsg );
			Status = STATUS_SUCCESS;
			goto Unlock;
		}	
	}

	IoMarkIrpPending( pIrp );
	(void)IoSetCancelRoutine( pIrp, dxbCancelHandler );
	InsertTailList( &pStream->Data.GetQ[pGetMsg->Priority], &pIrp->Tail.Overlay.ListEntry );

	Status = STATUS_PENDING;

Unlock:
	dxbStreamUnlock( pStream );

Exit:

	if ( Status != STATUS_PENDING )
	{		
		dxbIoCompleteRequest( pIrp, Status, NT_SUCCESS( Status ) ? sizeof *pGetMsg : 0 );
	}

	return Status;
}


STATIC INLINE NTSTATUS
dxbStreamDispatchIoctl( 
	IN PDXB_STREAM_OBJECT pStream,
	IN PDS_IOCTL          pIoctl )
{
	NTSTATUS Status = DxbCmdIoctl( pStream->pExt, pStream->Channel, pIoctl );
	if ( NT_SUCCESS( Status ) )
	{
		pStream->Stats.nIoctls++;
		dxbStreamSetInProgress( pStream, Ioctl );
	}
	return Status;
}


NTSTATUS
dxbStreamIoctl( 
	PDXB_DEVICE_EXTENSION pExt,
	PIRP                  pIrp,
	PIO_STACK_LOCATION    pIos )
{
	PDXB_STREAM_OBJECT pStream;
	NTSTATUS Status;
	PDS_IOCTL pIoctl;

	if ( pIos->Parameters.DeviceIoControl.InputBufferLength  != sizeof *pIoctl ||
	     pIos->Parameters.DeviceIoControl.OutputBufferLength != sizeof *pIoctl )
	{
		Status = STATUS_INVALID_PARAMETER;
		goto Exit;
	}

	pIoctl = GetIrpBuffer( pIrp );
	if ( (ULONG)pIoctl->hStream == 0 || (ULONG)pIoctl->hStream >= DXB_MAX_CHANNELS )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	if ( ( pIoctl->cbDataIn == 0 ) ^ ( pIoctl->pDataIn == NULL ) )
	{
		Status = STATUS_INVALID_PARAMETER_5;
		goto Exit;
	}

	if ( ( pIoctl->cbMaxDataOut == 0 ) ^ ( pIoctl->pDataOut == NULL ) )
	{
		Status = STATUS_INVALID_PARAMETER_7;
		goto Exit;
	}

	Status = DigiLockBuffer( pIrp, pIoctl->pDataIn, pIoctl->cbDataIn, &pIoctl->pProviderDataIn );

	if ( NT_ERROR( Status ) )
		goto Exit;

	Status = DigiLockBuffer( pIrp, pIoctl->pDataOut, pIoctl->cbMaxDataOut, &pIoctl->pProviderDataOut );

	if ( NT_ERROR( Status ) )
		goto Exit;

	pStream = dxbStreamLockByChannel( pExt, (ULONG)pIoctl->hStream );
	if ( pStream == NULL )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	if ( dxbStreamGetState( pStream ) != STREAM_STATE_READY )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Unlock;
	}

	if ( dxbStreamPeekIrp( pStream, Ioctl ) != NULL )
	{
		Status = STATUS_UNSUCCESSFUL;
		goto Unlock;
	}

	IoMarkIrpPending( pIrp );
	dxbStreamPushIrp( pStream, Ioctl, pIrp );

	Status = dxbStreamDispatchIoctl( pStream, pIoctl );
	if ( NT_ERROR( Status ) )
		dxbWorkCmdInsertTail( pStream );

	Status = STATUS_PENDING;

Unlock:
	dxbStreamUnlock( pStream );

Exit:
	if ( Status != STATUS_PENDING )
	{
		DigiDump( DIGIERROR, ("***dxbStreamIoctl failed (0x%x)\n",Status) );
		dxbIoCompleteRequest( pIrp, Status, 0 );
	}

	return Status;
}


STATIC INLINE NTSTATUS
dxbStreamDispatchLink( 
	IN PDXB_STREAM_OBJECT pStream,
	IN PDS_LINK           pLink )
{
	NTSTATUS Status = DxbCmdLink( pStream->pExt, pStream->Channel, pLink );

	if ( NT_SUCCESS( Status ) )
	{
		pStream->Stats.nLinks++;
		dxbStreamSetInProgress( pStream, Link );
	}

	return Status;
}


NTSTATUS
dxbStreamLink( 
	PDXB_DEVICE_EXTENSION pExt,
	PIRP                  pIrp,
	PIO_STACK_LOCATION    pIos )
{
	NTSTATUS Status;
	PDS_LINK pLink;

	if ( pIos->Parameters.DeviceIoControl.InputBufferLength  != sizeof *pLink ||
	     pIos->Parameters.DeviceIoControl.OutputBufferLength != sizeof *pLink )
	{
		Status = STATUS_INVALID_PARAMETER;
		goto Exit;
	}

	pLink = GetIrpBuffer( pIrp );
	if ( (ULONG)pLink->hStreamUpper == 0 || (ULONG)pLink->hStreamUpper >= DXB_MAX_CHANNELS )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}
	if ( (ULONG)pLink->hStreamLower == 0 || (ULONG)pLink->hStreamLower >= DXB_MAX_CHANNELS )
	{
		Status = STATUS_INVALID_PARAMETER_2;
		goto Exit;
	}

	Status = dxbStreamMarkLinked( pExt, (ULONG)pLink->hStreamUpper, 
	                              (ULONG)pLink->hStreamLower );
	if ( NT_ERROR(Status) )
		goto Exit;

	{
		PDXB_STREAM_OBJECT pStream;
		pStream = dxbStreamLockByChannel( pExt, (ULONG)pLink->hStreamUpper );
		ASSERT( dxbStreamGetState( pStream ) == STREAM_STATE_READY );

		if ( dxbStreamPeekIrp( pStream, Link ) != NULL )
		{
			Status = STATUS_UNSUCCESSFUL;
			dxbStreamUnlock( pStream );
			dxbStreamMarkUnlinked( pStream->pExt, pStream->Channel );
			goto Exit;
		}

		IoMarkIrpPending( pIrp );
		dxbStreamPushIrp( pStream, Link, pIrp );

		Status = dxbStreamDispatchLink( pStream, pLink );
		if ( NT_ERROR( Status ) )
			dxbWorkCmdInsertTail( pStream );

		dxbStreamUnlock( pStream );
	}

	Status = STATUS_PENDING;

Exit:
	if ( Status != STATUS_PENDING )
	{
		DigiDump( DIGIERROR, ("***dxbStreamLink failed (0x%x)\n",Status) );
		dxbIoCompleteRequest( pIrp, Status, 0 );
	}

	return Status;
}


NTSTATUS
dxbStreamPurge( 
	PDXB_DEVICE_EXTENSION pExt,
	PIRP                  pIrp,
	PIO_STACK_LOCATION    pIos )
{
	NTSTATUS Status;
	PDS_PURGE pPurge;

	if ( pIos->Parameters.DeviceIoControl.InputBufferLength != sizeof *pPurge )
	{
		Status = STATUS_INVALID_PARAMETER;
		goto Exit;
	}

	pPurge = GetIrpBuffer( pIrp );
	if ( (ULONG)pPurge->hStream == 0 || (ULONG)pPurge->hStream >= DXB_MAX_CHANNELS )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	if ( pPurge->Flags & ~DS_PURGE_ALL )
	{
		Status = STATUS_INVALID_PARAMETER_2;
		goto Exit;
	}
	
	{
		PDXB_STREAM_OBJECT pStream = dxbStreamLockByChannel( pExt, (ULONG)pPurge->hStream );
		LIST_ENTRY CompletedQ;

		if ( pStream == NULL )
		{
			Status = STATUS_INVALID_PARAMETER_1;
			goto Exit;
		}

		if ( dxbStreamGetState( pStream ) != STREAM_STATE_READY )
		{
			Status = STATUS_INVALID_PARAMETER_1;
			dxbStreamUnlock( pStream );
			goto Exit;
		}
	
		InitializeListHead( &CompletedQ );
		if ( pPurge->Flags & DS_PURGE_PUTMSG )
			(void)dxbStreamCancelQueue( &pStream->Data.PutQ[0], TRUE, &CompletedQ );
	
		if ( pPurge->Flags & DS_PURGE_GETMSG )
		{
			(void)dxbStreamCancelQueue( &pStream->Data.GetQ[0], TRUE, &CompletedQ );
			(void)dxbStreamFreeIncomingQ(pStream );
		}

		dxbStreamUnlock( pStream );

		while ( !IsListEmpty( &CompletedQ ) )
		{
			PIRP pCancelledIrp;
			PLIST_ENTRY pEntry = RemoveHeadList( &CompletedQ );
			pCancelledIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
			dxbIoCompleteRequest( pCancelledIrp, STATUS_CANCELLED, 0 );
		}
	}

	Status = STATUS_SUCCESS;

Exit:
	dxbIoCompleteRequest( pIrp, Status, 0 );
	return Status;
}

NTSTATUS
dxbStreamGetInfo( 
	PDXB_DEVICE_EXTENSION pExt,
	PIRP                  pIrp,
	PIO_STACK_LOCATION    pIos )
{
	NTSTATUS           Status;
	PDXB_STREAM_OBJECT pStream;
	PDS_GETINFO        pInfo;

	if ( pIos->Parameters.DeviceIoControl.InputBufferLength  != sizeof *pInfo ||
	     pIos->Parameters.DeviceIoControl.OutputBufferLength != sizeof *pInfo )
	{
		Status = STATUS_INVALID_PARAMETER;
		goto Exit;
	}

	pInfo = GetIrpBuffer( pIrp );
	if ( (ULONG)pInfo->hStream == 0 || (ULONG)pInfo->hStream >= DXB_MAX_CHANNELS )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	pStream = dxbStreamLockByChannel( pExt, (ULONG)pInfo->hStream );
	if ( pStream == NULL )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Exit;
	}

	if ( dxbStreamGetState( pStream ) != STREAM_STATE_READY )
	{
		Status = STATUS_INVALID_PARAMETER_1;
		goto Unlock;
	}


	pInfo->CntQueued = pStream->CntQueued;

	Status = STATUS_SUCCESS;

Unlock:
	dxbStreamUnlock( pStream );

Exit:
	dxbIoCompleteRequest( pIrp, Status, sizeof *pInfo );
	return Status;
}

NTSTATUS
dxbStreamDelayedDispatch( 
	IN PDXB_STREAM_OBJECT pStream )
/*++

Description:

	Dxb delayed command processing routine.  Called by DxbCmdDpc to process DStreams
	API requests that were not completed at request time.  Typically this happens
	when the stream was flow-controlled or the command queue was full.

Parameters:

	pStream
		Stream object which has work to do.

Return Value:

	STATUS_SUCCESS if successful, 
	STATUS_CMD_QUEUE_FULL if full, 
	or STATUS_DXB_CREDIT_EXCEEDED if stream is flow-controlled.

--*/
{
	BOOLEAN IsDxbCmdQFull = FALSE;
	NTSTATUS Status = STATUS_SUCCESS;

	dxbStreamLock( pStream );
	switch ( dxbStreamGetState( pStream ) )
	{
		case STREAM_STATE_READY:
		{
			// Look for an Ioctl first
			if ( dxbStreamPeekIrp( pStream, Ioctl ) && 
			     !dxbStreamIsInProgress( pStream, Ioctl ) )
			{
				Status = dxbStreamDispatchIoctl( pStream, dxbStreamPeekRequest( pStream, Ioctl ) );
				if ( NT_ERROR( Status ) )
				{
					dxbWorkCmdInsertTail( pStream );
					dxbStreamUnlock( pStream );
					IsDxbCmdQFull = TRUE;
					break;
				}
			}

			// Then look for a Link
			if ( dxbStreamPeekIrp( pStream, Link ) && 
			     !dxbStreamIsInProgress( pStream, Link ) )
			{
				Status = dxbStreamDispatchLink( pStream, dxbStreamPeekRequest( pStream, Link ) );
				if ( NT_ERROR( Status ) )
				{
					dxbStreamUnlock( pStream );
					IsDxbCmdQFull = TRUE;
					break;
				}
			}

			// Then handle PutMsg requests
			{
				LIST_ENTRY CompletedList;
				InitializeListHead( &CompletedList );

				Status = dxbStreamDelayedPutMsgs( pStream, &CompletedList );
				if ( Status == STATUS_DXB_CMD_QUEUE_FULL )
					IsDxbCmdQFull = TRUE;

				dxbStreamUnlock( pStream );
				
				// Now complete the PutMsg IRPs that were sent down to the adapter
				while ( !IsListEmpty( &CompletedList ) )
				{
					PIRP pIrp;
					PLIST_ENTRY pEntry = RemoveHeadList(&CompletedList);
					
					pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
					ASSERT( pIrp->Type == IO_TYPE_IRP );
					dxbIoCompleteRequest( pIrp, STATUS_SUCCESS, 0 );
				}
			}
			break;
		}

		case STREAM_STATE_OPENING:
		{
			Status = dxbStreamDispatchOpen( pStream, dxbStreamPeekRequest( pStream, Open ) );
			if ( NT_ERROR( Status ) )
				IsDxbCmdQFull = TRUE;

			dxbStreamUnlock( pStream );
			break;
		}

		case STREAM_STATE_CLOSING:
		{
			ASSERT( !dxbStreamIsInProgress( pStream, Close ) );
			if ( !dxbStreamLinkedUnder( pStream ) )
			{
				Status = dxbStreamDispatchClose( pStream, dxbStreamPeekRequest( pStream, Close ) );
				if ( NT_ERROR( Status ) )
					IsDxbCmdQFull = TRUE;
				dxbStreamUnlock( pStream );
			}
			else
			{
				PDXB_DEVICE_EXTENSION pExt = pStream->pExt;
				ULONG Channel = pStream->Channel;
				dxbStreamUnlock( pStream );
				dxbStreamOnCloseComplete( pExt, Channel );
				Status = STATUS_SUCCESS;
			}

			break;
		}

		case STREAM_STATE_CLOSED:
		case STREAM_STATE_INACTIVE:
		default:
			DigiDump( DIGIERROR, 
			          ( "DxbCmdThread: Found stream 0x%08x, state %s in WorkQ!\n", pStream,
			          dxbStreamGetState( pStream ) == STREAM_STATE_CLOSED ? "CLOSED" :
						 dxbStreamGetState( pStream ) == STREAM_STATE_INACTIVE ? "INACTIVE" : "UNKNOWN") );

			dxbStreamUnlock( pStream );
			break;
	}

	if ( IsDxbCmdQFull )
	{
		dxbWorkCmdInsertHead( pStream );
		Status = STATUS_DXB_CMD_QUEUE_FULL;
	}
	else
		Status = STATUS_SUCCESS;

	return Status;
}


NTSTATUS
dxbStreamDispatchCreditAwardMessage( 
	PDXB_STREAM_OBJECT pStream )
{
	NTSTATUS Status;
	ULONG nCredits;
	dxbStreamLock( pStream );
	if ( dxbStreamGetState( pStream ) != STREAM_STATE_READY )
	{
		DigiDump( DIGIERROR, ("***dxbStreamDispatchCreditAwardMessage: stream not ready\n") );
		Status = STATUS_UNSUCCESSFUL;
		goto Exit;
	}

	nCredits = dxbCreditRefundable( &pStream->Read );
	ASSERT( nCredits <= dxbCreditCap( &pStream->Read ) );

	if ( nCredits > MAXUSHORT )
		nCredits = MAXUSHORT;

	Status = DxbCmdCreditAward( pStream->pExt, pStream->Channel, (USHORT)nCredits );
	if ( NT_SUCCESS( Status ) )
	{
		dxbCreditRefund( &pStream->Read, nCredits );
		DigiDump( DIGIRXCREDIT, ( "DXB:Credit Read: credit Award : cb 0x%08x, available 0x%08x\n",
		                        nCredits, dxbCreditAvailable( &pStream->Read ) ) );
	}
	else
	{
		Status = STATUS_DXB_CMD_QUEUE_FULL;
		dxbWorkCreditInsertHead( pStream );
	}

Exit:
	dxbStreamUnlock( pStream );
	return Status;
}


ULONG 
dxbStreamOnIoctlComplete( 
	IN PDXB_DEVICE_EXTENSION pExt, 
	IN ULONG                 Channel,
	IN ULONG                 QOut,
	IN LONG                  ReturnValue,
	IN LONG                  ErrorCode,
	IN ULONG                 cbData )
{
	PDXB_STREAM_OBJECT pStream = dxbStreamLockByChannel( pExt, Channel );
	PIRP pIrp = NULL;
	PDS_IOCTL pIoctl;
	PVOID pData = NULL;
	ULONG NewQOut;
	NTSTATUS Status;

	ASSERT( pStream != NULL );
	if ( pStream )
	{
		pIrp = dxbStreamPopIrp( pStream, Ioctl );
		if ( pIrp )
		{
			pIoctl = GetIrpBuffer( pIrp );
			dxbStreamUnlock( pStream );
		}
		else
		{
			DigiDump( DIGIWARN, ( "ISDN!StreamOnIoctlComplete: Irp already cancelled?\n" ) );
			pData = NULL;
			dxbStreamUnlock( pStream );
			goto Exit;
		}

		if ( cbData > pIoctl->cbMaxDataOut )
		{
			// Output buffer too small, abort!
			pData = NULL;
			Status = STATUS_BUFFER_OVERFLOW;
			goto Exit;
		}

		if ( ReturnValue == 0 && ErrorCode == 0 )
		{
			pData = pIoctl->pProviderDataOut;
			pIoctl->cbDataOut = cbData;
		}
		else
		{
			pData = NULL;
			pIoctl->cbDataOut = 0;
		}

		pIoctl->ReturnValue = ReturnValue;
		pIoctl->ErrorCode   = ErrorCode;
		Status = STATUS_SUCCESS;
	}

Exit:
	NewQOut = DxbReadCData( pExt, QOut, NULL, 0, pData, cbData );
	if ( pIrp )
		dxbIoCompleteRequest( pIrp, Status, sizeof *pIoctl );
	return NewQOut;
}


VOID
dxbStreamOnLinkComplete( 
	IN PDXB_DEVICE_EXTENSION pExt, 
	IN ULONG                 Channel,
	IN LONG                  ReturnValue,
	IN LONG                  ErrorCode )
{
	PDXB_STREAM_OBJECT pStream; 
	PIRP pIrp = NULL;
	PDS_LINK pLink;

	pStream = dxbStreamLockByChannel( pExt, Channel );
	ASSERT( pStream != NULL );
	if ( pStream )
	{
		pIrp = dxbStreamPopIrp( pStream, Link );
		if ( pIrp )
		{
			pLink = GetIrpBuffer( pIrp );
			ASSERT( dxbStreamGetState( pStream ) == STREAM_STATE_READY );
			dxbStreamUnlock( pStream );
		}
		else
		{
			DigiDump( DIGIWARN, ( "ISDN!StreamOnIoctlComplete: Irp already cancelled?\n" ) );
			dxbStreamUnlock( pStream );
			goto Exit;
		}
		
#if DBG
		{
			// Verify lower stream state
			PDXB_STREAM_OBJECT pLower;
			pLower = dxbStreamLockByChannel( pExt, (ULONG)pLink->hStreamLower );
			ASSERT( pLower != NULL );
			ASSERT( dxbStreamGetState( pLower ) == STREAM_STATE_READY );
			dxbStreamUnlock( pLower );
		}	
#endif

		pLink->ErrorCode = ErrorCode;
		if ( ErrorCode != 0 )
		{
			// Error
			dxbStreamMarkUnlinked( pExt, Channel );
			pLink->MuxId = -1;
		}
		else
			pLink->MuxId = ReturnValue;

		dxbIoCompleteRequest( pIrp, STATUS_SUCCESS, sizeof *pLink );
	}

Exit:
	return;
}


VOID
dxbStreamOnCreditEvent( 
	IN PDXB_DEVICE_EXTENSION pExt, 
	IN ULONG                 Channel,
	IN ULONG                 nCredits )
{
	PDXB_STREAM_OBJECT pStream = dxbStreamLockByChannel( pExt, Channel );

	ASSERT( pStream );
	if ( pStream )
	{
		dxbCreditDeposit( &pStream->Write, nCredits );
		DigiDump( DIGITXCREDIT, ( "DXB:Credit Write: Credit Event: cb 0x%08x, available 0x%08x\n",
		                        nCredits, dxbCreditAvailable( &pStream->Write ) ) );

		if ( !IsPutQEmpty( pStream, DS_PRIORITY_LOW ) )
			dxbWorkCmdInsertTail( pStream );
		dxbStreamUnlock( pStream );
	}
	return;
}


STATIC INLINE ULONG
dxbStreamQueueIncomingMessage( 
	PDXB_STREAM_OBJECT pStream,
	ULONG              QOut,
	ULONG              cbCtrl,
	ULONG              cbData,
	DS_PRIORITY        Priority )
{
	ULONG NewQOut;
	PDXB_MSG pMsg;
	ULONG cbAlloc = sizeof *pMsg + dxbAlign32( cbCtrl ) + cbData;

	// This must never happen!  We're queuing, so a request of the same priority cannot
	// be outstanding.
	ASSERT( IsListEmpty( &pStream->Data.GetQ[Priority] ) );

	pMsg = DigiAllocMemEx( NonPagedPool, cbAlloc, '1BxD' );
	if ( pMsg == NULL )
	{
		NewQOut = DxbReadCData( pStream->pExt, QOut, NULL, cbCtrl, NULL, cbData );
		// dxbCreditMarkRefundable( &pStream->Read, dxbCreditEventCost( &pStream->Read, cbCtrl, cbData ) );
		DigiDump( DIGIERROR, ( "***DXB:Credit Read: Drop data    : cb 0x%08x, available 0x%08x\n",
		          dxbCreditEventCost( &pStream->Read, cbCtrl, cbData ), dxbCreditAvailable( &pStream->Read ) ) );
	}
	else
	{
		pMsg->pCtrl = (PUCHAR)pMsg + sizeof *pMsg;
		pMsg->pData = (PUCHAR)pMsg->pCtrl + dxbAlign32( cbCtrl );
		pMsg->Type = DXB_TYPE_DXB_MSG;
		pMsg->cbCtrl = cbCtrl;
		pMsg->cbData = cbData;
		NewQOut = DxbReadCData( pStream->pExt, QOut, 
		                        pMsg->pCtrl, pMsg->cbCtrl, 
		                        pMsg->pData, pMsg->cbData );
		InsertTailList( &pStream->Data.IncomingQ[Priority], &pMsg->Entry );	

		pStream->CntQueued += 1;
	}

	dxbCreditMarkRefundable( &pStream->Read, dxbCreditEventCost( &pStream->Read, cbCtrl, cbData ) );

	return NewQOut;
}


ULONG 
dxbStreamOnDataEvent( 
	IN PDXB_DEVICE_EXTENSION pExt, 
	IN ULONG                 Channel,
	IN ULONG                 QOut,
	IN ULONG                 cbCtrl,
	IN ULONG                 cbData,
	IN DS_PRIORITY           Priority )
{
	PDXB_STREAM_OBJECT pStream; 
	LIST_ENTRY FailedQ;
	ULONG NewQOut;
	PIRP pIrp;
	PDS_GETMSG pGetMsg;
	

	pStream = dxbStreamLockByChannel( pExt, Channel );

	ASSERT( pStream );

	if ( !pStream )
	{
		// Received data on a stream we've already closed.  This should only happen if 
		// firmware has a bug that allows it to send up data after stream close.
		NewQOut = DxbReadCData( pExt, QOut, NULL, cbCtrl, NULL, cbData );

		DigiDump( DIGIERROR, ( "***DXB:StreamOnDataEvent: killed data from closed stream\n" ) );
		goto Exit;
	}

	InitializeListHead( &FailedQ );

	if ( dxbStreamGetState( pStream ) != STREAM_STATE_READY )
	{
		DigiDump( DIGIERROR, ( "***DXB:StreamOnDataEvent: kill data from unready stream\n" ) );

		// Must be closing.  Drop rx data, ignore credit accounting since we're closing.
		dxbStreamUnlock( pStream );
		NewQOut = DxbReadCData( pExt, QOut, NULL, cbCtrl, NULL, cbData );
		goto Exit;
	}

	pStream->Stats.nIncoming[Priority]++;	

	if ( Priority == DS_PRIORITY_LOW )
	{
		ULONG Amount;

		Amount = dxbCreditEventCost( &pStream->Read, cbCtrl, cbData );
		ASSERT( Amount <= pStream->Read.nCredits );	

		dxbCreditCharge( &pStream->Read, Amount );
	}

	DigiDump( DIGIRXCREDIT,    ( "DXB:Credit Read: incoming data: cb 0x%08x, available 0x%08x\n",
	          dxbCreditEventCost( &pStream->Read, cbCtrl, cbData ), dxbCreditAvailable( &pStream->Read ) ) );

	// Sanity check queues.  We should never have equal-or-higher priority incoming 
	// messages queued while a GetMsg request is queued.
	ASSERT( IsListEmpty( &pStream->Data.GetQ[DS_PRIORITY_LOW] )  || IsIncomingQEmpty( pStream, DS_PRIORITY_LOW ) );
	ASSERT( IsListEmpty( &pStream->Data.GetQ[DS_PRIORITY_HIGH] ) || IsIncomingQEmpty( pStream, DS_PRIORITY_HIGH ) );

	// Only try to complete a GetMsg if no earlier Incoming messages of EQUAL OR HIGHER
	// priority are pending - we can never return incoming messages out of order!
	pGetMsg = NULL;
	if ( IsIncomingQEmpty( pStream, Priority ) )
	{
		// Find GetMsg queue of EQUAL OR LOWER priority containing pending requests
		// We can satisfy a low priority GetMsg with high priority data, but we can't
		// satisfy a high priority GetMsg with low priority data.
		int i;
		for ( i = Priority; i >= DS_PRIORITY_MIN; i-- )
		{
			// The pending GetMsg may not be large enough to hold this message.  
			// Repeatedly try to find one that can be completed successfully.
			while ( !IsListEmpty( &pStream->Data.GetQ[i] ) )
			{
				// Request available!  Grab it and check if it's large enough
				PLIST_ENTRY pList  = &pStream->Data.GetQ[i];
				PLIST_ENTRY pEntry = RemoveHeadList( pList );
				pIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
				ASSERT( pIrp->Type == IO_TYPE_IRP );
				(void)IoSetCancelRoutine( pIrp, NULL );
				pGetMsg = GetIrpBuffer( pIrp );

				if ( cbCtrl <= pGetMsg->cbMaxControl && cbData <= pGetMsg->cbMaxData )
				{
					// Found Getmsg to complete.  Stop looking and fill it in.
					goto Continue;
				}

				// This GetMsg request was too small.  Fail it and look for another
				DigiDump( DIGIERROR, ( "DXB:StreamOnDataEvent: failing insufficient GetMsg request!\n" 
				                       "    cbMaxControl 0x%08x, needed 0x%08x\n"
				                       "    cbMaxData 0x%08x,    needed 0x%08x\n"
				                       "    GetMsg->Priority %d,     Incoming Priority %d\n",
				                       pGetMsg->cbMaxControl, cbCtrl, pGetMsg->cbMaxData, cbData,
				                       pGetMsg->Priority, Priority ) );
				InsertTailList( &FailedQ, &pIrp->Tail.Overlay.ListEntry );
				pGetMsg = NULL;
			}
		}
	}

Continue:
	if ( pGetMsg == NULL )
	{
		// No satisfactory request found.  Queue the message		
		NewQOut = dxbStreamQueueIncomingMessage( pStream, QOut, cbCtrl, cbData, Priority );
		dxbStreamUnlock( pStream );
	}
	else
	{
		// Immediately fulfill the GetMsg request
		dxbCreditMarkRefundable( &pStream->Read, dxbCreditEventCost( &pStream->Read, cbCtrl, cbData ) );
		DigiDump( DIGIRXCREDIT, ( "DXB:Credit Read: found GetMsg : cb 0x%08x, available 0x%08x\n",
		          dxbCreditEventCost( &pStream->Read, cbCtrl, cbData ), dxbCreditAvailable( &pStream->Read ) ) );
		dxbWorkCreditInsertTail( pStream );
		dxbStreamUnlock( pStream );

		pGetMsg->cbControl = cbCtrl;
		pGetMsg->cbData = cbData;
		pGetMsg->Priority = Priority;
		NewQOut = DxbReadCData( pExt, QOut, 
		                        pGetMsg->pProviderControl, cbCtrl,
		                        pGetMsg->pProviderData, cbData );
		pStream->Stats.nGets[Priority]++;
	}

	while ( !IsListEmpty( &FailedQ ) )
	{
		PIRP pFailedIrp;
		PLIST_ENTRY pEntry = RemoveHeadList( &FailedQ );
		pFailedIrp = CONTAINING_RECORD( pEntry, IRP, Tail.Overlay.ListEntry );
		dxbIoCompleteRequest( pFailedIrp, STATUS_BUFFER_TOO_SMALL, 0 );
	}

	if ( pGetMsg != NULL )
		dxbIoCompleteRequest( pIrp, STATUS_SUCCESS, sizeof *pGetMsg );

Exit:
	
	return NewQOut;
}
