/*++

*******************************************************************************
* Copyright (c) 1994-95 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 copyright work  Digi   International *
* Inc.    Except   as   permitted   by  federal  law,  17 USC 117, copying is *
* strictly prohibited.                                                        *
*                                                                             *
* Digi  International  Inc.  CONFIDENTIAL  -   (Digi    International    Inc. *
* Confidential  when   combined   with   the   aggregated  modules  for  this *
* product) OBJECT CODE ONLY SOURCE MATERIALS                                  *
*                                                                             *
* (C) COPYRIGHT Digi International Inc. 1994-95                               *
*                                                                             *
* All Rights Reserved                                                         *
*                                                                             *
* US   Government   Users   Restricted   Rights   -   Use,   duplication   or *
* disclosure  restricted by GSA ADP Schedule Contract with Digi International *
* Inc.                                                                        *
*                                                                             *
*******************************************************************************

Module Name:

   acontext.c

Abstract:

   This modules contains routines to create delete and service adapter context 
   queue entries.  The adapter context queue is used to manage sending 
   notifications up to Ndis.  
   
   We're an Ndis miniport driver and the miniport wrapper normally handles all 
	spinlock acquisitions, etc.  Normally this is great, but since we're a layered
	driver (i.e. we don't talk directly to hardware, we talk to DigiSxb), we don't
	get interrupts from hardware, and thus the wrapper's acquisition of the 
	necessary spinlocks doesn't happen.  To cure the problem, we need to sync up
	our indications with the wrapper's acquisition of the correct spinlocks.  We
	do this by queueing up all indications, and set an NdisTimer to call us back
	immediately.  Before calling us back, the wrapper grabs the appropriate 
	spinlocks and we can then send up indications to our hearts content :-)

	All manipulations of the queue entries (alloc'ing, freeing, etc.) is managed 
	by routines in this module to hide implementation details of the actual 
	structures used.  

Author:

   Michael Zarns

Revision History:

   $Log: /Components/Windows/NT/Sync/Lan/ACONTEXT.C $
 * 
 * 2     8/13/96 10:47a Mikez
 * Fully abstracted Frame Relay code into three routines:  FrSend,
 * FrReceive and FrInit, and one AdapterObject field: RxControlLength.
 * This revision works and support Frame Relay only.
 * 
 * 1     8/05/96 2:14p Mikez
 * 
 * 2     6/28/96 6:41a Mikez
 * Implemented changes to force transmits (MiniportSends) to complete
 * synchronously and never call NdisMSendComplete.  Also, forced spoofed
 * ARP replies to be processed via the AdapterContextTimerFunction.
 * 
 * 1     3/11/96 3:15p Mikez
 * Revision 1.3  1995/08/23  07:47:16  mikez
 * Fixed IP routing by adding ARP spoofing.  Added IPX routing support.
 * 
 * Revision 1.2  1995/08/17  23:36:08  mikez
 * AdapterContextTimerFunction is now called directly by DigiLanSend to
 * help speed up the notification process for NdisSendCompletes.
 * 
 * Revision 1.1  1995/08/16  12:40:02  mikez
 * Initial revision
 * 

--*/

#include "digilan.h"


VOID
DigiAdapterContextQueueInit(
	IN PDIGILAN_LOGICAL_ADAPTER pAdapter,
	IN NDIS_HANDLE MiniportAdapterHandle
	)
/*++

Description:

	This routine initializes the adapter context fields in the Adapter object.

Parameters:

	pAdapter
		Address of the adapter object.

	MiniportAdapterHandle
		The handle identifying the miniport driver assigned by the wrapper. 

Return Value:

	None

--*/
{
	DigiDump( 
		DIGIFLOW, 
		("DigiAContextQueueInit: Adapter object = 0x%x\n", 
		pAdapter
		) );

	NdisAllocateSpinLock( &pAdapter->AdapterContextLock );


	NdisMInitializeTimer(
	    &pAdapter->AdapterContextTimer,
	    MiniportAdapterHandle,
	    AdapterContextTimerFunction,
	    pAdapter );

	InitializeListHead( &pAdapter->AdapterContextQueue );
}


VOID
DigiAdapterContextQueueDeInit(
	IN PDIGILAN_LOGICAL_ADAPTER pAdapter	
	)
/*++

Description:

	This routine deinitializes the adapter context fields in the Adapter object.

Parameters:

	pAdapter
		Address of the adapter object.	

Return Value:

	None

--*/
{
	BOOLEAN StatusBoolean;

	DigiDump( 
		DIGIFLOW, 
		("DigiAContextQueueDeInit: Adapter object = 0x%x\n", 
		pAdapter
		) );

	NdisMCancelTimer( &pAdapter->AdapterContextTimer, &StatusBoolean );

	// SDB: BUGBUG: maybe we need to wait in case the timer-callback is currently processed ?

	// Clean out the queue:
	NdisAcquireSpinLock( &pAdapter->AdapterContextLock );

	while ( !IsListEmpty( &pAdapter->AdapterContextQueue ) )
	{
		PADAPTER_CONTEXT_QUEUE_ENTRY pEntry;
		PLIST_ENTRY pListEntry = RemoveHeadList( &pAdapter->AdapterContextQueue );
		pEntry = CONTAINING_RECORD( pListEntry, ADAPTER_CONTEXT_QUEUE_ENTRY, ListEntry );
		DigiFreeMem( pEntry );
	}

	NdisReleaseSpinLock( &pAdapter->AdapterContextLock );
}

VOID
DigiInsertQueueEntry(
	IN PDIGILAN_LOGICAL_ADAPTER pAdapter,
	IN PADAPTER_CONTEXT_QUEUE_ENTRY pEntry
	)
/*++

Description:

	Internal function to insert a queue_entry object into the adapter's 
	AdapterContextQueue.  Starts up the NdisTimer to handle the entry if necessary.

Parameters:

	pAdapter
		Address of the Adapter object.

	pEntry
		Address of the object to be inserted into the adapter context queue.

Return Value:

	None

--*/
{
	BOOLEAN Empty;

	NdisAcquireSpinLock( &pAdapter->AdapterContextLock );

	Empty = IsListEmpty( &pAdapter->AdapterContextQueue );

	InsertTailList( &pAdapter->AdapterContextQueue, &pEntry->ListEntry );

	NdisReleaseSpinLock( &pAdapter->AdapterContextLock );

	if ( Empty )
	{
		//
		// Start timer if the queue was empty
		//

		// The specified value of 0 means actually a possible delay of up to 10 ms:
		NdisMSetTimer( &pAdapter->AdapterContextTimer, 0 );
	}
}


PADAPTER_CONTEXT_QUEUE_ENTRY
DigiRemoveQueueEntry(
	IN PDIGILAN_LOGICAL_ADAPTER pAdapter
	)
/*++

Description:

	Internal function to remove the head QueueEntry object from the adapter's 
	AdapterContextQueue.  The address of the QueueEntry object is returned.

Parameters:

	pAdapter
		Address of the Adapter object.

Return Value:

	A pointer to the QueueEntry object removed from the queue, or
	NULL if list is empty.

--*/
{
	PLIST_ENTRY pListEntry;
	PADAPTER_CONTEXT_QUEUE_ENTRY pQueueEntry;

	NdisAcquireSpinLock( &pAdapter->AdapterContextLock );

	if ( IsListEmpty( &pAdapter->AdapterContextQueue ) )
	{
		NdisReleaseSpinLock( &pAdapter->AdapterContextLock );
		return( NULL );
	}

	pListEntry = RemoveHeadList( &pAdapter->AdapterContextQueue );

	NdisReleaseSpinLock( &pAdapter->AdapterContextLock );

	ASSERT( pListEntry != &pAdapter->AdapterContextQueue );

	pQueueEntry = CONTAINING_RECORD( pListEntry, 
												ADAPTER_CONTEXT_QUEUE_ENTRY,
												ListEntry
												);
	return( pQueueEntry );	
}



NDIS_STATUS
DigiQueueSendComplete(
	IN PDIGILAN_LOGICAL_ADAPTER pAdapter,
	IN PNDIS_PACKET pPacket,
	IN NDIS_STATUS Status
	)
/*++

Description:

	This routine alloc's, initializes, and inserts a QueueEntry object into the 
	adapter context queue.  The object inserted is a QUEUE_ENTRY_SEND_COMPLETE
	entry used to send an NdisSendComplete notification up to Ndis.

Parameters:

	pAdapter
		Address of the adapter object.

	pPacket
		Address of the NDIS_PACKET for which this notification applies.

	Status
		Status to pass up in the NdisSendComplete indication.

Return Value:

	STATUS_SUCCESS, 
	or STATUS_RESOURCES if a queue entry object could not be allocated.

--*/
{
	PADAPTER_CONTEXT_QUEUE_ENTRY pQueueEntry = NULL;

	//
	// Alloc
	//

	pQueueEntry = DigiAllocMemEx( 
						NonPagedPool, 
						sizeof( ADAPTER_CONTEXT_QUEUE_ENTRY),  '1naL' );
	
	if ( !pQueueEntry )
	{
		//
		// Disaster!  We cannot queue the request... try to complete it immediately?
		// This would have the consequence of the wrapper *not* acquiring the 
		// spinlock and a possible crash.  Not completing it will likely halt 
		// communications on the VC, but won't crash the system.  I choose option 2.
		//

		DigiDump( 
			(DIGIERRORS),
			("*** DigiLan: could not alloc space for SendComplete queue entry!\n"
			"      pAdapter = 0x%x\n"
			"      Packet = 0x%x\n"
			"      Status = 0x%x\n",
			pAdapter,
			pPacket,
			Status
			) );
		return( NDIS_STATUS_RESOURCES );
	}

	//
	// Init 
	//

	NdisZeroMemory( pQueueEntry, sizeof( ADAPTER_CONTEXT_QUEUE_ENTRY ) );

	pQueueEntry->Type = DIGI_TYPE_QUEUE_ENTRY_OBJECT;
	pQueueEntry->Request = DIGI_QUEUE_ENTRY_SEND_COMPLETE;

	pQueueEntry->Indication.SendComplete.Adapter = pAdapter;
	pQueueEntry->Indication.SendComplete.Packet = pPacket;
	pQueueEntry->Indication.SendComplete.CompletionStatus = Status;

	//
	// Insert into queue
	//

	DigiInsertQueueEntry( pAdapter, pQueueEntry );
	return( NDIS_STATUS_SUCCESS );
}



NDIS_STATUS
DigiQueueIndicateReceive(
	IN PDIGILAN_LOGICAL_ADAPTER pAdapter,
	IN PDIGILAN_FRAME_BUFFER pBuffer,
	IN PDIGILAN_ETHERNET_FRAME pFrame,
	IN ULONG PayloadLength
	)
/*++

Description:

	This routine alloc's, initializes, and inserts a QueueEntry object into the 
	adapter context queue.  The object inserted is a QUEUE_ENTRY_INDICATE_RECV
	entry used to send an NdisMEthIndicateReceive notification up to Ndis.

Parameters:

	pAdapter
		Adapter object defines the Sxb VC and virtual Ndis adapter on which the
		packet is received.

	pBuffer
		Address of buffer which holds pFrame to be indicated.  This buffer will be
		freed before returning.

	pFrame
		Address of ethernet/802.3 frame to be indicated.  This will reside inside
		pBuffer.

	PayloadLength
		Length of ethernet/802.3 frame's payload.

Return Value:

	STATUS_SUCCESS, or some error value.

--*/
{
	PADAPTER_CONTEXT_QUEUE_ENTRY pQueueEntry;

	// Alloc
	pQueueEntry = DigiAllocMemEx( NonPagedPool, 
	                            sizeof( ADAPTER_CONTEXT_QUEUE_ENTRY ), '2naL' );
	
	if ( !pQueueEntry )
	{
		// Not really a disaster - we could just drop the packet on the floor.
		DigiDump( (DIGIERRORS),
			       ("*** DigiLan: could not alloc space for IndicateRecv queue entry!\n"
			       "      pAdapter = 0x%x\n"
			       "      pBuffer  = 0x%x\n",
			       pAdapter,
			       pBuffer ) );

		return( NDIS_STATUS_RESOURCES );
	}

	//
	// Init 
	//

	NdisZeroMemory( pQueueEntry, sizeof( ADAPTER_CONTEXT_QUEUE_ENTRY ) );

	pQueueEntry->Type = DIGI_TYPE_QUEUE_ENTRY_OBJECT;
	pQueueEntry->Request = DIGI_QUEUE_ENTRY_INDICATE_RECEIVE;

	pQueueEntry->Indication.IndicateRecv.Adapter = pAdapter;
	pQueueEntry->Indication.IndicateRecv.pBuffer = pBuffer;
	pQueueEntry->Indication.IndicateRecv.pFrame = pFrame;
	pQueueEntry->Indication.IndicateRecv.PayloadLength = PayloadLength;

	// Insert into queue
	DigiInsertQueueEntry( pAdapter, pQueueEntry );
	
	return( NDIS_STATUS_SUCCESS );
}

NDIS_STATUS									
DigiQueueIndicateStatus(
	IN PDIGILAN_LOGICAL_ADAPTER pAdapter,
	IN NDIS_STATUS GeneralStatus,
	IN PVOID StatusBuffer,
	IN UINT	BufferLength	
	)
/*++

Description:

	This routine alloc's, initializes, and inserts a QueueEntry object into the 
	adapter context queue.  The object inserted is a QUEUE_ENTRY_INDICATE_STATUS
	entry used to send an NdisMIndicateStatus notification up to Ndis.

Parameters:

	pAdapter
		Address of the adapter object.

	GeneralStatus
		Specifies the general status change in the line.

	StatusBuffer
		Points to an allocated buffer having detailed information about the
		change in status.

	BufferLength
		Length of the StatusBuffer.

Return Value:

	STATUS_SUCCESS, 
	or STATUS_RESOURCES if a queue entry object could not be allocated.

--*/
{
	PADAPTER_CONTEXT_QUEUE_ENTRY pQueueEntry = NULL;

	//
	// Alloc
	//

	pQueueEntry = DigiAllocMemEx( 
						NonPagedPool, 
						sizeof( ADAPTER_CONTEXT_QUEUE_ENTRY),  '3naL' );
	
	if ( !pQueueEntry )
	{
		//
		// No big disaster, Everthing works just fine except that
		// NDIS doesnt get to know the change in status of the VC 
		//

		DigiDump( 
			(DIGIERRORS),
			("*** DigiLan: could not alloc space for Indicate status queue entry!\n"
			"      pAdapter = 0x%x\n"
			"      GeneralStatus = 0x%x\n",
			pAdapter,
		  	GeneralStatus
			) );
		return( NDIS_STATUS_RESOURCES );
	}

	//
	// Init 
	//

	NdisZeroMemory( pQueueEntry, sizeof( ADAPTER_CONTEXT_QUEUE_ENTRY ) );

	pQueueEntry->Type = DIGI_TYPE_QUEUE_ENTRY_OBJECT;
	pQueueEntry->Request = DIGI_QUEUE_ENTRY_INDICATE_STATUS;

	pQueueEntry->Indication.IndicateStatus.Adapter = pAdapter;
	pQueueEntry->Indication.IndicateStatus.GeneralStatus = GeneralStatus;
	pQueueEntry->Indication.IndicateStatus.StatusBuffer = StatusBuffer;
	pQueueEntry->Indication.IndicateStatus.BufferLength = BufferLength;

	//
	// Insert into queue
	//

	DigiInsertQueueEntry( pAdapter, pQueueEntry );
	return( NDIS_STATUS_SUCCESS );
}


VOID
AdapterContextTimerFunction(
	IN PVOID SystemSpecific1,
	IN PVOID FunctionContext,
	IN PVOID SystemSpecific2,
	IN PVOID SystemSpecific3
	)
/*++

Description:

	This routine handles all notifications up to ndis.  This includes 
	SendComplete, IndicateRecv, IndicateStatus and SendResourcesAvailable.
	It handles all requests currently found on the queue.

Parameters:

	FunctionContext
		Address of a DIGILAN_LOGICAL_ADAPTER object.
	
	SystemSpecificX
		Not used.

Return Value:

	None

--*/
{
	PDIGILAN_LOGICAL_ADAPTER pAdapter;
	PADAPTER_CONTEXT_QUEUE_ENTRY pEntry;
	BOOLEAN ReceiveIndicated = FALSE;

#if DBG
	ULONG EntriesProcessed = 0;
#endif

	DigiDump( DIGIFLOW, ("Enter AdapterContextTimerFunction\n") );

	pAdapter = (PDIGILAN_LOGICAL_ADAPTER)FunctionContext;

	

	while( NULL != ( pEntry = DigiRemoveQueueEntry( pAdapter ) ) )
	{
		//
		// Sanity check
		//

		ASSERT( DIGI_TYPE_QUEUE_ENTRY_OBJECT == pEntry->Type );

		//
		// Handle the request
		//
		switch ( pEntry->Request )
		{
			case DIGI_QUEUE_ENTRY_SEND_COMPLETE:

				DigiDump( DIGITX, 
				          ("DigiLan: NdisSendComplete\n"
				          "   pAdapter   = 0x%x\n"
				          "   NdisPacket = 0x%x\n",
				          pAdapter,
				          pEntry->Indication.SendComplete.Packet ) );

				//SDB: why was the following code commented out , who is MLZ ?

//------------------------------------------------------------ MLZ 06/27/96 {
#if 1
				ASSERT( pEntry->Indication.SendComplete.Packet->Private.Head );

				NdisMSendComplete(
						pAdapter->MiniportAdapterHandle,
						pEntry->Indication.SendComplete.Packet,
						pEntry->Indication.SendComplete.CompletionStatus
						);
#endif
//------------------------------------------------------------ MLZ 06/27/96 }
				break;

			case DIGI_QUEUE_ENTRY_INDICATE_STATUS:						

				DigiDump( DIGITX, 
				          ("DigiLan: NdisIndicateStatus\n"
				          "   pAdapter   = 0x%x\n"
				          "   GeneralStatus = 0x%x\n",
				          pAdapter,
				          pEntry->Indication.IndicateStatus.GeneralStatus ) );

				NdisMIndicateStatus( 
						pAdapter->MiniportAdapterHandle,
				 		pEntry->Indication.IndicateStatus.GeneralStatus,
						pEntry->Indication.IndicateStatus.StatusBuffer,
						pEntry->Indication.IndicateStatus.BufferLength);
				
				break;

			case DIGI_QUEUE_ENTRY_INDICATE_RECEIVE:

				ReceiveIndicated = TRUE;

				DigiLanIndicateReceive( pAdapter,
				                        pEntry->Indication.IndicateRecv.pBuffer,
				                        pEntry->Indication.IndicateRecv.pFrame,
				                        pEntry->Indication.IndicateRecv.PayloadLength );
				break;

			case DIGI_QUEUE_ENTRY_SEND_RESOURCES_AVAILABLE:
				
				DigiDump( DIGITX, 
				          ("DigiLan: NdisSendResourcesAvailable\n"
				          "   pAdapter   = 0x%x\n",
				          pAdapter ) );
				break;

			default:

				DigiDump( DIGIERRORS, ("*** DigiLan: Bad Queue Entry!\n") );

				break;
		}
		
		DigiFreeMem( pEntry );

#if DBG
		EntriesProcessed++;
#endif

	}

	if ( ReceiveIndicated )
		NdisMEthIndicateReceiveComplete( pAdapter->MiniportAdapterHandle );

#if DBG
	//
	// Debug only - we'll display this message only if we've processed several 
	// entries...
	//
	if ( EntriesProcessed > 5 )
	{
		DigiDump( 
			DIGITIMER, 
			("DigiLan AdapterContextTimer: %d queue entries processed\n", 
			EntriesProcessed) 
			);
	}
#endif

	DigiDump( DIGIFLOW, ("Leave AdapterContextTimerFunction\n") );
}
