/*++
*******************************************************************************
* 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 "digiisdn.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_TAPI_SET_COMPLETE,
	ENTRY_TYPE_TAPI_QUERY_COMPLETE,
	ENTRY_TYPE_COUNT
} ENTRY_TYPE;

typedef struct ENTRY_TAPI_EVENT
{
	PISDN_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
{
	PISDN_CALL  pCall;
	HTAPI_CALL  htCall;
	ULONG       LinkSpeed;
} ENTRY_WAN_LINEUP, *PENTRY_WAN_LINEUP;

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

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

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

typedef struct ENTRY_TAPI_X_COMPLETE
{
	PISDN_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
{
	ISDN_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_SEND_COMPLETE       SendComplete;
		ENTRY_TAPI_SET_COMPLETE   TapiSetComplete;
		ENTRY_TAPI_QUERY_COMPLETE TapiQueryComplete;
	};
} QUEUE_ENTRY, *PQUEUE_ENTRY;


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


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


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

	ASSERT( pAdapter->Type == ISDN_TYPE_ADAPTER );
	ASSERT( pEntry->Type == ISDN_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 PISDN_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 == ISDN_TYPE_QUEUE_ENTRY );
	}
	DigiReleaseSpinLock( &pAdapter->Queue.Lock );

	return pEntry;
}


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

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

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


STATIC VOID
IsdnQueueTimer(
	IN PVOID Ignored1, 
	IN PVOID Context, 
	IN PVOID Ignored2, 
	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.

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

	ASSERT( pAdapter->Type == ISDN_TYPE_ADAPTER );
	DigiDump( DIGIQUEUE, ( "ISDN!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;
				IsdnTapiIndicate( pEvent->pCall, pEvent->htLine, pEvent->ulMsg, pEvent->Param1, pEvent->Param2, pEvent->Param3 );
				break;
			}

			case ENTRY_TYPE_WAN_LINEUP:
			{
				PENTRY_WAN_LINEUP pLineup = &pEntry->WanLineUp;
				IsdnWanLineup( pLineup->pCall, pLineup->htCall, pLineup->LinkSpeed );
				break;
			}

			case ENTRY_TYPE_WAN_LINEDOWN:
			{
				PENTRY_WAN_LINEDOWN pLinedown = &pEntry->WanLineDown;
				IsdnWanLinedown( pLinedown->pCall );
				break;
			}

			case ENTRY_TYPE_SEND_COMPLETE:
			{
				PENTRY_SEND_COMPLETE pComplete = &pEntry->SendComplete;
				NdisMWanSendComplete( pComplete->pCall->pAdapter->hMiniport, pComplete->pPacket, pComplete->Status );
				break;
			}

			case ENTRY_TYPE_INDICATE_RECEIVE:
			{
				NDIS_STATUS Status;
				PENTRY_INDICATE_RECEIVE pReceive = &pEntry->IndicateReceive;
				(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_TAPI_SET_COMPLETE:
			{
				PENTRY_TAPI_SET_COMPLETE pComplete = &pEntry->TapiSetComplete;
				IsdnTapiSetComplete( pComplete->pAdapter, pComplete->Status );
				break;
			}

			case ENTRY_TYPE_TAPI_QUERY_COMPLETE:
			{
				PENTRY_TAPI_QUERY_COMPLETE pComplete = &pEntry->TapiQueryComplete;
				IsdnTapiQueryComplete( pComplete->pAdapter, pComplete->Status );
				break;
			}

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

	while ( !IsListEmpty( &RxCompleteList ) )
	{
		PISDN_CALL pCall = PopRxComplete( &RxCompleteList );
		ASSERT( pCall->Type == ISDN_TYPE_CALL );
		NdisMWanIndicateReceiveComplete( pAdapter->hMiniport, pCall->hLink );
	}
}


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

NTSTATUS
IsdnQueueStart(
	PISDN_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, IsdnQueueTimer, pAdapter );
	pAdapter->Queue.Ready = TRUE;
	return STATUS_SUCCESS;
}


VOID
IsdnQueueStop(
	PISDN_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
IsdnQueueTapiIndicate(
	PISDN_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
IsdnQueueWanLineup(
	PISDN_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
IsdnQueueWanLinedown(
	PISDN_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
IsdnQueueSendComplete(
	PISDN_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
IsdnQueueIndicateReceive(
	PISDN_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
IsdnQueueSetComplete(
	PISDN_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
IsdnQueueQueryComplete(
	PISDN_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;
}

