/*++
*******************************************************************************
* Copyright (c) 1990-98 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:

   isdnqueue.c

Abstract:

   Code to manage the adapter context queue.  All indications passed up to Ndis, Tapi,
	Wan *must* pass through the adapter context queue.  

	Because this is a "layered driver" in the Ndis sense, we cannot directly call 
	Ndis routines (i.e. NdisMWanIndicateReceive or NdisMWanSendComplete routines), from
	code not running within an Ndis context.  To ensure that all indications are passed
	up in order, we require that all indications pass through this queue, enforcing the
	correct order.

Author:

   Michael Zarns

--*/

#include "digix25.h"

typedef enum ENTRY_TYPE
{
	ENTRY_TYPE_UNKNOWN,
	ENTRY_TYPE_TAPI_EVENT,
	ENTRY_TYPE_WAN_LINEUP,
	ENTRY_TYPE_WAN_LINEDOWN,
	ENTRY_TYPE_SEND_COMPLETE,
	ENTRY_TYPE_INDICATE_RECEIVE,
	ENTRY_TYPE_INDICATE_FRAGMENT,
	ENTRY_TYPE_TAPI_SET_COMPLETE,
	ENTRY_TYPE_TAPI_QUERY_COMPLETE,
	ENTRY_TYPE_COUNT
} ENTRY_TYPE;

typedef struct ENTRY_TAPI_EVENT
{
	PX25_CALL pCall;
	HTAPI_LINE htLine;
	ULONG      ulMsg;
	ULONG_PTR  Param1;
	ULONG_PTR  Param2;
	ULONG_PTR  Param3;
} ENTRY_TAPI_EVENT, *PENTRY_TAPI_EVENT;

typedef struct ENTRY_WAN_LINEUP
{
	PX25_CALL  pCall;
	HTAPI_CALL  htCall;
	ULONG       LinkSpeed;
} ENTRY_WAN_LINEUP, *PENTRY_WAN_LINEUP;

typedef struct ENTRY_WAN_LINEDOWN
{
	PX25_CALL pCall;
} ENTRY_WAN_LINEDOWN, *PENTRY_WAN_LINEDOWN;

typedef struct ENTRY_SEND_COMPLETE
{
	PX25_CALL       pCall;
	PNDIS_WAN_PACKET pPacket;
	NDIS_STATUS      Status;
} ENTRY_SEND_COMPLETE, *PENTRY_SEND_COMPLETE;

typedef struct ENTRY_INDICATE_RECEIVE
{
	PX25_CALL pCall;
	PVOID      pData;
	ULONG      cbData;
} ENTRY_INDICATE_RECEIVE, *PENTRY_INDICATE_RECEIVE;

typedef struct ENTRY_INDICATE_FRAGMENT
{
	PX25_CALL pCall;
	PVOID      pData;
	ULONG      cbData;
} ENTRY_INDICATE_FRAGMENT, *PENTRY_INDICATE_FRAGMENT;



typedef struct ENTRY_TAPI_X_COMPLETE
{
	PX25_ADAPTER pAdapter;
	NDIS_STATUS   Status;
} ENTRY_TAPI_SET_COMPLETE, *PENTRY_TAPI_SET_COMPLETE,
  ENTRY_TAPI_QUERY_COMPLETE, *PENTRY_TAPI_QUERY_COMPLETE;

typedef struct QUEUE_ENTRY
{
	X25_OBJECT_TYPE  Type;
	ENTRY_TYPE        Indication;
	LIST_ENTRY        ListEntry;
	union
	{
		ENTRY_TAPI_EVENT          TapiEvent;
		ENTRY_WAN_LINEUP          WanLineUp;
		ENTRY_WAN_LINEDOWN        WanLineDown;
		ENTRY_INDICATE_RECEIVE    IndicateReceive;
		ENTRY_INDICATE_FRAGMENT	  IndicateFragment;
		ENTRY_SEND_COMPLETE       SendComplete;
		ENTRY_TAPI_SET_COMPLETE   TapiSetComplete;
		ENTRY_TAPI_QUERY_COMPLETE TapiQueryComplete;
	};
} QUEUE_ENTRY, *PQUEUE_ENTRY;


STATIC INLINE VOID
InsertQueueEntry( 
	PX25_ADAPTER pAdapter,
	PQUEUE_ENTRY  pEntry )
{
	BOOLEAN fEmpty;

	ASSERT( pAdapter->Type == X25_TYPE_ADAPTER );
	ASSERT( pEntry->Type == X25_TYPE_QUEUE_ENTRY );

	DigiAcquireSpinLock( &pAdapter->Queue.Lock );
	fEmpty = IsListEmpty( &pAdapter->Queue.ListHead );
	InsertTailList( &pAdapter->Queue.ListHead, &pEntry->ListEntry );
	DigiReleaseSpinLock( &pAdapter->Queue.Lock );

	if ( fEmpty )
		NdisMSetTimer( &pAdapter->Queue.Timer, 0 );
}



STATIC INLINE PQUEUE_ENTRY
NewQueueEntry( 
	ENTRY_TYPE Indication )
{
	PQUEUE_ENTRY pEntry = DigiAllocMem( NonPagedPool, sizeof *pEntry );
	if ( pEntry )
	{
		NdisZeroMemory( pEntry, sizeof *pEntry );
		pEntry->Type       = X25_TYPE_QUEUE_ENTRY;
		pEntry->Indication = Indication;
	}
	return pEntry;
}


STATIC INLINE VOID
FreeQueueEntry( 
	PQUEUE_ENTRY pEntry )
{
	DigiFreeMem( pEntry );
}


STATIC INLINE VOID
X25QueueEntry( 
	PX25_ADAPTER pAdapter,
	PQUEUE_ENTRY  pEntry )
{
	BOOLEAN fEmpty;

	ASSERT( pAdapter->Type == X25_TYPE_ADAPTER );
	ASSERT( pEntry->Type == X25_TYPE_QUEUE_ENTRY );

	DigiAcquireSpinLock( &pAdapter->Queue.Lock );
	fEmpty = IsListEmpty( &pAdapter->Queue.ListHead );
	InsertTailList( &pAdapter->Queue.ListHead, &pEntry->ListEntry );
	DigiReleaseSpinLock( &pAdapter->Queue.Lock );

	if ( fEmpty )
		NdisMSetTimer( &pAdapter->Queue.Timer, 0 );
}


STATIC INLINE PQUEUE_ENTRY
RemoveQueueEntry( 
	IN PX25_ADAPTER pAdapter )
{
	PQUEUE_ENTRY pEntry = NULL;

	DigiAcquireSpinLock( &pAdapter->Queue.Lock );
	if ( !IsListEmpty( &pAdapter->Queue.ListHead ) )
	{
		PLIST_ENTRY pListEntry = RemoveHeadList( &pAdapter->Queue.ListHead );
		pEntry = CONTAINING_RECORD( pListEntry, QUEUE_ENTRY, ListEntry );
		ASSERT( pEntry->Type == X25_TYPE_QUEUE_ENTRY );
	}
	DigiReleaseSpinLock( &pAdapter->Queue.Lock );

	return pEntry;
}


STATIC INLINE VOID
PushRxComplete( 
	PLIST_ENTRY pListHead, 
	PX25_CALL pCall )
{
	if ( IsListEmpty( &pCall->RxCompleteListEntry ) )
		InsertTailList( pListHead, &pCall->RxCompleteListEntry );
}

STATIC INLINE PX25_CALL
PopRxComplete( 
	PLIST_ENTRY pListHead )
{
	PLIST_ENTRY pListEntry;
	PX25_CALL pCall;
	ASSERT( !IsListEmpty( pListHead ) );

	pListEntry = RemoveHeadList( pListHead );
	pCall = CONTAINING_RECORD( pListEntry, X25_CALL, RxCompleteListEntry );
	ASSERT( pCall->Type == X25_TYPE_CALL );
	InitializeListHead( pListEntry );
	return pCall;
}


STATIC VOID
X25QueueTimer(
	IN PVOID Ignored1, 
	IN PVOID Context, 
	IN PVOID error, 
	IN PVOID Ignored3 )
/*++

Description:

	Ndis Timer callback routine.  This routine processes entries in the adapter context
	queue, passing indications up to ndis.  It must be called from an ndis context, 
	either via NdisSetTimer or called directly from an Ndis Api, i.e. IsdnSend.

--*/
{
	PX25_ADAPTER const pAdapter = Context;
	PQUEUE_ENTRY pEntry;
	LIST_ENTRY RxCompleteList;
	InitializeListHead( &RxCompleteList );\


	ASSERT( pAdapter->Type == X25_TYPE_ADAPTER );
	DigiDump( DIGIQUEUE, ( "X25!QueueTimer: pAdapter 0x%x\n", pAdapter ) );
	while ( pAdapter->Queue.Ready && ( NULL != ( pEntry = RemoveQueueEntry( pAdapter ) ) ) )
	{
		switch ( pEntry->Indication )
		{
			case ENTRY_TYPE_TAPI_EVENT:
			{
				PENTRY_TAPI_EVENT pEvent = &pEntry->TapiEvent;
				DigiDump( DIGIQUEUE, ( "X25!QueueTimer: TapiEvent adapter 0x%x\n", pAdapter ) );

				X25TapiIndicate( pEvent->pCall, pEvent->htLine, pEvent->ulMsg, pEvent->Param1, pEvent->Param2, pEvent->Param3 );
				break;
			}

			case ENTRY_TYPE_WAN_LINEUP:
			{
				PENTRY_WAN_LINEUP pLineup = &pEntry->WanLineUp;
				DigiDump( DIGIQUEUE, ( "X25!QueueTimer: WAN_LINE_UP adapter 0x%x\n", pAdapter ) );

				X25WanLineup( pLineup->pCall, pLineup->htCall, pLineup->LinkSpeed );
				break;
			}

			case ENTRY_TYPE_WAN_LINEDOWN:
			{
				PENTRY_WAN_LINEDOWN pLinedown = &pEntry->WanLineDown;
				DigiDump( DIGIQUEUE, ( "X25!QueueTimer: LINE DOWN adapter 0x%x\n", pAdapter ) );

				X25WanLinedown( pLinedown->pCall );
				break;
			}

			case ENTRY_TYPE_SEND_COMPLETE:
			{
				PENTRY_SEND_COMPLETE pComplete = &pEntry->SendComplete;
				DigiDump( DIGIQUEUE, ( "X25!QueueTimer: Send Complete adapter 0x%x\n", pAdapter ) );
				NdisMWanSendComplete( pComplete->pCall->pAdapter->hMiniport, pComplete->pPacket, pComplete->Status );
				break;
			}


			case ENTRY_TYPE_INDICATE_RECEIVE:
			{
				NDIS_STATUS Status;
				PENTRY_INDICATE_RECEIVE pReceive = &pEntry->IndicateReceive;
				DigiDump( DIGIQUEUE, ( "X25!QueueTimer: Rx Indication adapter 0x%x\n", pAdapter ) );

				(void)NdisMWanIndicateReceive( &Status, pReceive->pCall->pAdapter->hMiniport, pReceive->pCall->hLink, pReceive->pData, pReceive->cbData );
				RxEngineFreeData( pReceive->pData );
				PushRxComplete( &RxCompleteList, pReceive->pCall );
				break;
			}

			case ENTRY_TYPE_INDICATE_FRAGMENT:
			{
				PENTRY_INDICATE_FRAGMENT pFragment = &pEntry->IndicateFragment;
				NDIS_MAC_FRAGMENT MacFragment;
			   /*
			   DigiDump( (DIGIFLOW|DIGIX25), ("WanFragment: CallObject=0x%x, Error=0x%x\n",
                                     CallObject,
                                     FragmentErrors) );

			
			   AdapterObject = CallObject->pAdapter;
			   ASSERT( AdapterObject->Type == X25_ADAPTER_OBJECT );
			   */
			   MacFragment.NdisLinkContext = pFragment->pCall->hLink;
			   MacFragment.Errors = (ULONG)(ULONG_PTR)error;
			   DigiDump( DIGIQUEUE, ( "X25!QueueTimer: Indicate Fragment adapter 0x%x\n", pAdapter ) );

			   NdisMIndicateStatus( pFragment->pCall->pAdapter->hMiniport,
                        NDIS_STATUS_WAN_FRAGMENT, &MacFragment,
	                        sizeof(MacFragment) );
			   NdisMIndicateStatusComplete( pFragment->pCall->pAdapter->hMiniport );
			   	break;

			}


			case ENTRY_TYPE_TAPI_SET_COMPLETE:
			{
				PENTRY_TAPI_SET_COMPLETE pComplete = &pEntry->TapiSetComplete;
				DigiDump( DIGIQUEUE, ( "X25!QueueTimer: Set Complete adapter 0x%x\n", pAdapter ) );
				X25TapiSetComplete( pComplete->pAdapter, pComplete->Status );
				break;
			}

			case ENTRY_TYPE_TAPI_QUERY_COMPLETE:
			{
				PENTRY_TAPI_QUERY_COMPLETE pComplete = &pEntry->TapiQueryComplete;
				DigiDump( DIGIQUEUE, ( "X25!QueueTimer: Query Complete adapter 0x%x\n", pAdapter ) );

				X25TapiQueryComplete( pComplete->pAdapter, pComplete->Status );
				break;
			}

			default:
				DigiDump( DIGIBREAK, ( "X25!QueueTimer: Invalid queue entry, Indication 0x%x\n", pEntry->Indication ) );
				break;
		}
		FreeQueueEntry( pEntry );
	}

	while ( !IsListEmpty( &RxCompleteList ) )
	{
		PX25_CALL pCall = PopRxComplete( &RxCompleteList );
		ASSERT( pCall->Type == X25_TYPE_CALL );
		DigiDump( DIGIQUEUE, ( "X25!QueueTimer: Rx Complete 2 adapter 0x%x\n", pAdapter ) );

		NdisMWanIndicateReceiveComplete( pAdapter->hMiniport,pCall->hLink  );
	}
}


///////////////////////////////////////////////////////////////////////////////
//
// Public API
//

NTSTATUS
X25QueueStart(
	PX25_ADAPTER pAdapter )
/*++

Description:

	Initialize the Adapter Context Timer routines.  This is called from 
	MiniportInitialize before calling any other routine in this module.  IsdnQueueStop
	is then called from MiniportHalt routine to stop and release resources acquired for
	the adapter context timer queue.

--*/
{
	DigiInitializeSpinLock( &pAdapter->Queue.Lock );
	InitializeListHead( &pAdapter->Queue.ListHead );
	NdisMInitializeTimer( &pAdapter->Queue.Timer, pAdapter->hMiniport, X25QueueTimer, pAdapter );
	pAdapter->Queue.Ready = TRUE;
	return STATUS_SUCCESS;
}


VOID
X25QueueStop(
	PX25_ADAPTER pAdapter )
/*++

Description:

	Shutdown the adapter context queue.  Stop the timer, release all acquired resources.

--*/
{
	BOOLEAN fCancelled;

	if ( pAdapter->Queue.Ready )
	{
		// Cancel the timer.  Keep it from firing again.
		pAdapter->Queue.Ready = FALSE;
		NdisMCancelTimer( &pAdapter->Queue.Timer, &fCancelled );

		// Clean out the queue
		DigiAcquireSpinLock( &pAdapter->Queue.Lock );
		while ( !IsListEmpty( &pAdapter->Queue.ListHead ) )
		{
			PQUEUE_ENTRY pEntry;
			PLIST_ENTRY pListEntry = RemoveHeadList( &pAdapter->Queue.ListHead );
			pEntry = CONTAINING_RECORD( pListEntry, QUEUE_ENTRY, ListEntry );
			FreeQueueEntry( pEntry );
		}
		DigiReleaseSpinLock( &pAdapter->Queue.Lock );
	}
}


NTSTATUS
X25QueueTapiIndicate(
	PX25_CALL pCall,
	HTAPI_LINE htLine,
	ULONG      ulMsg,
	ULONG_PTR  Param1,
	ULONG_PTR  Param2,
	ULONG_PTR  Param3 )
/*++

Description:

	Queue up the tapi indication.  Timer will be set and indication sent automatically.

--*/
{
	PQUEUE_ENTRY pEntry = NewQueueEntry( ENTRY_TYPE_TAPI_EVENT );
	if ( !pEntry )
		return NDIS_STATUS_RESOURCES;

	pEntry->TapiEvent.pCall  = pCall;
	pEntry->TapiEvent.htLine = htLine;
	pEntry->TapiEvent.ulMsg  = ulMsg;
	pEntry->TapiEvent.Param1 = Param1;
	pEntry->TapiEvent.Param2 = Param2;
	pEntry->TapiEvent.Param3 = Param3;
	InsertQueueEntry( pCall->pAdapter, pEntry );
	return STATUS_SUCCESS;
}


NTSTATUS
X25QueueWanLineup(
	PX25_CALL pCall, 
	HTAPI_CALL htCall,
	ULONG      LinkSpeed )
/*++

Description:

	Queue up the wan lineup indication.  Timer will be set and indication sent 
	automatically.

--*/
{
	PQUEUE_ENTRY pEntry = NewQueueEntry( ENTRY_TYPE_WAN_LINEUP );
	if ( !pEntry )
		return STATUS_INSUFFICIENT_RESOURCES;

	pEntry->WanLineUp.pCall     = pCall;
	pEntry->WanLineUp.htCall    = htCall;
	pEntry->WanLineUp.LinkSpeed = LinkSpeed;
	InsertQueueEntry( pCall->pAdapter, pEntry );
	return STATUS_SUCCESS;
}


NTSTATUS
X25QueueWanLinedown(
	PX25_CALL pCall )
/*++

Description:

	Queue up the wan linedown indication.  Timer will be set and indication sent 
	automatically.

--*/
{
	PQUEUE_ENTRY pEntry = NewQueueEntry( ENTRY_TYPE_WAN_LINEDOWN );
	if ( !pEntry )
		return STATUS_INSUFFICIENT_RESOURCES;

	pEntry->WanLineDown.pCall = pCall;
	InsertQueueEntry( pCall->pAdapter, pEntry );
	return STATUS_SUCCESS;
}


NTSTATUS
X25QueueSendComplete(
	PX25_CALL       pCall, 
	PNDIS_WAN_PACKET pPacket,
	NDIS_STATUS      Status )
/*++

Description:

	Queue up the send complete indication.  Timer will be set and indication sent 
	automatically.

--*/
{
	PQUEUE_ENTRY pEntry = NewQueueEntry( ENTRY_TYPE_SEND_COMPLETE );
	if ( !pEntry )
		return STATUS_INSUFFICIENT_RESOURCES;

	pEntry->SendComplete.pCall   = pCall;
	pEntry->SendComplete.pPacket = pPacket;
	pEntry->SendComplete.Status  = Status;
	InsertQueueEntry( pCall->pAdapter, pEntry );
	return STATUS_SUCCESS;
}


NTSTATUS
X25QueueIndicateReceive(
	PX25_CALL pCall, 
	PVOID      pData,
	ULONG      cbData )
/*++

Description:

	Queue up the received data indication.  Timer will be set and indication sent 
	automatically.

--*/
{
	PQUEUE_ENTRY pEntry = NewQueueEntry( ENTRY_TYPE_INDICATE_RECEIVE );
	if ( !pEntry )
		return STATUS_INSUFFICIENT_RESOURCES;

	pEntry->IndicateReceive.pCall  = pCall;
	pEntry->IndicateReceive.pData  = pData;
	pEntry->IndicateReceive.cbData = cbData;
	InsertQueueEntry( pCall->pAdapter, pEntry );
	return STATUS_SUCCESS;
}


NTSTATUS
X25QueueSetComplete(
	PX25_ADAPTER pAdapter,
	NDIS_STATUS  Status )
/*++

Description:

	Queue up the SetInfo complete indication.  Timer will be set and indication sent 
	automatically.

--*/
{
	PQUEUE_ENTRY pEntry = NewQueueEntry( ENTRY_TYPE_TAPI_SET_COMPLETE );
	if ( !pEntry )
		return STATUS_INSUFFICIENT_RESOURCES;

	pEntry->TapiSetComplete.pAdapter = pAdapter;
	pEntry->TapiSetComplete.Status = Status;
	InsertQueueEntry( pAdapter, pEntry );
	return STATUS_SUCCESS;
}


NTSTATUS
X25QueueQueryComplete(
	PX25_ADAPTER pAdapter,
	NDIS_STATUS  Status )
/*++

Description:

	Queue up the QueueInfo complete indication.  Timer will be set and indication sent 
	automatically.

--*/
{
	PQUEUE_ENTRY pEntry = NewQueueEntry( ENTRY_TYPE_TAPI_QUERY_COMPLETE );
	if ( !pEntry )
		return STATUS_INSUFFICIENT_RESOURCES;

	pEntry->TapiQueryComplete.pAdapter  = pAdapter;
	pEntry->TapiQueryComplete.Status = Status;
	InsertQueueEntry( pAdapter, pEntry );
	return STATUS_SUCCESS;
}

NTSTATUS
X25QueueIndicateFragment(
	PX25_CALL pCall, 
	ULONG     error )
/*++

Description:

	Queue up the received data indication.  Timer will be set and indication sent 
	automatically.

--*/
{
	PQUEUE_ENTRY pEntry = NewQueueEntry( ENTRY_TYPE_INDICATE_FRAGMENT );
	if ( !pEntry )
		return STATUS_INSUFFICIENT_RESOURCES;

	pEntry->IndicateReceive.pCall  = pCall;
	InsertQueueEntry( pCall->pAdapter, pEntry );
	return STATUS_SUCCESS;
}

