/*++

Copyright (c) 1997 FORE Systems, Inc.
Copyright (c) 1997 Microsoft Corporation

Module Name:

	utils.c

Abstract:

	Utility routines.
	
Author:

	Larry Cleeton, FORE Systems	(v-lcleet@microsoft.com, lrc@fore.com)		

Environment:

	Kernel mode

Revision History:

--*/

#include "precomp.h"
#pragma hdrstop

VOID
AtmLaneInitGlobals(
	VOID
	)
/*++

Routine Description:

	Initialize the global data structures.

Arguments:

	None

Return Value:

	None

--*/
{
	TRACEIN(InitGlobals);
	
	NdisZeroMemory(pAtmLaneGlobalInfo, sizeof(ATMLANE_GLOBALS));

	INIT_GLOBAL_LOCK(pAtmLaneGlobalInfo);

	NdisInitializeListHead(&pAtmLaneGlobalInfo->AdapterList);

#if DBG_TRACE
	//
	// Init trace log
	//
	pTraceLogSpace = NULL;
	InitTraceLog(&TraceLog, NULL, 0);

	// allocate space and init trace log if configured

	if (DbgLogSize > 0)
	{
		ALLOC_MEM(&pTraceLogSpace, DbgLogSize);
		if (pTraceLogSpace  == NULL)
		{
			DBGP((0, "Failed to allocate %d bytes space for trace log\n",
				DbgLogSize));
		}
		else
		{
			InitTraceLog(
					&TraceLog, 
					pTraceLogSpace, 
					DbgLogSize);
		}
	}
#endif	// DBG_TRACE

	TRACEOUT(InitGlobals);

	return;
}


PATMLANE_ADAPTER
AtmLaneAllocAdapter(
	IN	PNDIS_STRING			pDeviceName,
	IN	PVOID					SystemSpecific1
)
/*++

Routine Description:

	Allocates an Adapter data structure.

Arguments:

	pDeviceName		- Points to name of adapter device
	SystemSpecific1	- What we got into our BindAdapter handler.

Return Value:

	Pointer to allocated Adapter structure or NULL.

--*/
{
	PATMLANE_ADAPTER	pAdapter;
	NDIS_STATUS			Status;
	ULONG				TotalLength;
	PNDIS_STRING		pConfigString;

	TRACEIN(AllocAdapter);

	//
	//	Initialize
	//
	pAdapter = NULL_PATMLANE_ADAPTER;
	pConfigString = (PNDIS_STRING)SystemSpecific1;
	
	do
	{
		//
		//	Allocate everything.  Adapter struct size plus two 
		//  UNICODE string buffers with extra WCHAR each for NULL termination.
		//
		TotalLength =   sizeof(ATMLANE_ADAPTER) + 
		                pDeviceName->MaximumLength + sizeof(WCHAR) + 
		                pConfigString->MaximumLength + sizeof(WCHAR);

		ALLOC_MEM(&pAdapter, TotalLength);

		if (NULL_PATMLANE_ADAPTER == pAdapter)
		{
			Status = NDIS_STATUS_RESOURCES;
			break;
		}
		
		//
		// 	Zero it.
		//
		NdisZeroMemory(pAdapter, TotalLength);

		//
		// 	Debugging info.
		//
#if DBG
		pAdapter->atmlane_adapter_sig =  atmlane_adapter_signature;
#endif

		//
		//	Init lock.
		//
		INIT_ADAPTER_LOCK(pAdapter);

		//
		//	Init blocking objects.
		//
		INIT_BLOCK_STRUCT(&pAdapter->Block);
		INIT_BLOCK_STRUCT(&pAdapter->UnbindBlock);

		//
		//	Init ElanList
		//
		NdisInitializeListHead(&pAdapter->ElanList);

		//
		//  Copy in the device name
		//
		pAdapter->DeviceName.MaximumLength = pDeviceName->MaximumLength + sizeof(WCHAR);
		pAdapter->DeviceName.Length = pDeviceName->Length;
		pAdapter->DeviceName.Buffer = (PWCHAR)((PUCHAR)pAdapter + sizeof(ATMLANE_ADAPTER));
		NdisMoveMemory(pAdapter->DeviceName.Buffer,
					   pDeviceName->Buffer,
					   pDeviceName->Length);
		pAdapter->DeviceName.Buffer[pDeviceName->Length/sizeof(WCHAR)] = ((WCHAR)0);


		//
		//  Copy in the Config string - we will use this to open the
		//  registry section for this adapter at a later point.
		//
		pAdapter->ConfigString.MaximumLength = pConfigString->MaximumLength;
		pAdapter->ConfigString.Length = pConfigString->Length;
		pAdapter->ConfigString.Buffer = (PWCHAR)((PUCHAR)pAdapter + 
										sizeof(ATMLANE_ADAPTER) + 
										pAdapter->DeviceName.MaximumLength);

		NdisMoveMemory(pAdapter->ConfigString.Buffer,
					   pConfigString->Buffer,
					   pConfigString->Length);
		pAdapter->ConfigString.Buffer[pConfigString->Length/sizeof(WCHAR)] = ((WCHAR)0);

		//
		//	Link into global Adapter list.
		//
		ACQUIRE_GLOBAL_LOCK(pAtmLaneGlobalInfo);
		InsertTailList(&pAtmLaneGlobalInfo->AdapterList, &pAdapter->Link);
		RELEASE_GLOBAL_LOCK(pAtmLaneGlobalInfo);

	} while (FALSE);
	
	TRACEOUT(AllocAdapter);

	return pAdapter;
}


VOID
AtmLaneDeallocateAdapter(
	IN	PATMLANE_ADAPTER	pAdapter
)
/*++

Routine Description:

	Deallocate an Adapter structure. It is assumed that all
	references to this structure have gone, so it is not necessary
	to acquire a lock to it.

	Also unlink this from the global Adapter list.

Arguments:

	pAdapter		- Pointer to Adapter structure to be deallocated.

Return Value:

	None

--*/
{
	PATMLANE_NAME	pName;


	STRUCT_ASSERT(pAdapter, atmlane_adapter);

	TRACEIN(DeallocateAdapter);

	ASSERT(pAdapter->RefCount == 0);

	//
	//  Unlink from global Adapter list.
	//
	ACQUIRE_GLOBAL_LOCK(pAtmLaneGlobalInfo);
	RemoveEntryList(&pAdapter->Link);
	RELEASE_GLOBAL_LOCK(pAtmLaneGlobalInfo);
	
	//
	//	Free the lock.
	//
	FREE_ADAPTER_LOCK(pAdapter);

#if DBG
	pAdapter->atmlane_adapter_sig++;
#endif

    //
    //  Free string buffers that may have been allocated
    //
	if (NULL != pAdapter->CfgUpperBindings.Buffer)
	{
		FREE_MEM(pAdapter->CfgUpperBindings.Buffer);
	}
	if (NULL != pAdapter->CfgElanName.Buffer)
	{
		FREE_MEM(pAdapter->CfgElanName.Buffer);
	}


	//
	//	Free the name lists that may have been allocated.
	//
	while (pAdapter->UpperBindingsList)
	{
		DBGP((1, "DeallocateAdapter: pname 0x%x\n"));
		pName = pAdapter->UpperBindingsList;
		pAdapter->UpperBindingsList = pName->pNext;
		FREE_MEM(pName);
	}
	while (pAdapter->ElanNameList)
	{
		DBGP((1, "DeallocateAdapter: pname 0x%x\n"));
		pName = pAdapter->ElanNameList;
		pAdapter->ElanNameList = pName->pNext;
		FREE_MEM(pName);
	}


	//
	//  Finally free the Adapter structure.
	//
	FREE_MEM(pAdapter);

	TRACEOUT(DeallocateAdapter);

	return;
}

BOOLEAN
AtmLaneReferenceAdapter(
	IN	PATMLANE_ADAPTER	pAdapter,
	IN	PUCHAR				String
	)
/*++

Routine Description:

	Add a references to an Adapter structure.
	NOTE: The caller is assumed to possess the Adapter's lock.

Arguments:

	pAdapter	-	Pointer to the Adapter structure.


Return Value:

	None.

--*/
{
	BOOLEAN			bReferenced;

	TRACEIN(ReferenceAdapter);

	STRUCT_ASSERT(pAdapter, atmlane_adapter);
	
	if ((pAdapter->Flags & ADAPTER_FLAGS_DEALLOCATING) == 0)
	{
		pAdapter->RefCount++;
		bReferenced = TRUE;
	}
	else
	{
		bReferenced = FALSE;
	}
	
	DBGP((5, "ReferenceAdapter: Adapter %x (%s) new count %d\n",
			 pAdapter, String, pAdapter->RefCount));

	TRACEOUT(ReferenceAdapter);

	return bReferenced;
}

ULONG
AtmLaneDereferenceAdapter(
	IN	PATMLANE_ADAPTER	pAdapter,
	IN	PUCHAR				String
	)
/*++

Routine Description:

	Subtract a reference from an Adapter structure. 
	If the reference count becomes zero, deallocate it.
	NOTE: The caller is assumed to posses the Adapter's lock.

Arguments:

	pAdapter	-	Pointer to an adapter structure.


Return Value:

	None.

--*/
{
	ULONG		rc;

	TRACEIN(DereferenceAdapter);

	STRUCT_ASSERT(pAdapter, atmlane_adapter);

	ASSERT(pAdapter->RefCount > 0);

	rc = --(pAdapter->RefCount);

	if (rc == 0)
	{
	    pAdapter->Flags |= ADAPTER_FLAGS_DEALLOCATING;
		RELEASE_ADAPTER_LOCK(pAdapter);
		AtmLaneDeallocateAdapter(pAdapter);
	}

	DBGP((5, "DereferenceAdapter: Adapter %x (%s) new count %d\n", 
		pAdapter, String, rc));

	TRACEOUT(DereferenceAdapter);

	return (rc);
}
	
NDIS_STATUS
AtmLaneAllocElan(
	IN		PATMLANE_ADAPTER	pAdapter,
	IN OUT	PATMLANE_ELAN		*ppElan
)
/*++

Routine Description:

	Allocates an ELAN data structure.

Arguments:

	None

Return Value:

	NDIS_STATUS_SUCCESS or NDIS_STATUS_RESOURCES.

--*/
{
	NDIS_STATUS				Status;
	PATMLANE_ELAN			pElan;
	PATMLANE_MAC_ENTRY *	pMacTable;
	PATMLANE_TIMER_LIST		pTimerList;
	USHORT					NameBufferSize;
	UINT					i;
	ULONG					SapSize;
	PCO_SAP					pLesSapInfo;
	PCO_SAP					pBusSapInfo;
	PCO_SAP					pDataSapInfo;
	ULONG					ElanNumber;

	TRACEIN(AllocElan);

	//
	//  Initialize
	//

	Status = NDIS_STATUS_SUCCESS;
	pElan = NULL_PATMLANE_ELAN;
	pMacTable = (PATMLANE_MAC_ENTRY *)NULL;

	pLesSapInfo = pBusSapInfo = pDataSapInfo = (PCO_SAP)NULL;
	
	SapSize = sizeof(CO_SAP)+sizeof(ATM_SAP)+sizeof(ATM_ADDRESS);

	do
	{
		//
		//	Allocate everything.
		//
		ALLOC_MEM(&pElan, sizeof(ATMLANE_ELAN));
		ALLOC_MEM((PVOID *)&pMacTable, ATMLANE_MAC_TABLE_SIZE*sizeof(PATMLANE_MAC_ENTRY));
		ALLOC_MEM(&pLesSapInfo, SapSize);
		ALLOC_MEM(&pBusSapInfo, SapSize);
		ALLOC_MEM(&pDataSapInfo, SapSize);

		if (NULL_PATMLANE_ELAN != pElan)
		{
			//
			// 	Zero the Elan structure now so that we clean up properly
			//  if any errors occur later on.
			//
			NdisZeroMemory(pElan, sizeof(ATMLANE_ELAN));
		}

		if ((NULL_PATMLANE_ELAN == pElan) ||
			(NULL == pMacTable) ||
			(NULL == pLesSapInfo) ||
			(NULL == pBusSapInfo) ||
			(NULL == pDataSapInfo))
		{
			Status = NDIS_STATUS_RESOURCES;
			break;
		}

		//
		//  Allocate timer structures
		//
		for (i = 0; i < ALT_CLASS_MAX; i++)
		{
			pTimerList = &(pElan->TimerList[i]);
#if DBG
			pTimerList->atmlane_timerlist_sig = atmlane_timerlist_signature;
#endif
			ALLOC_MEM(&(pTimerList->pTimers), 
						sizeof(ATMLANE_TIMER) * AtmLaneTimerListSize[i]);
			if (NULL_PATMLANE_TIMER == pTimerList->pTimers)
			{
				Status = NDIS_STATUS_RESOURCES;
				break;
			}
		}

		if (Status != NDIS_STATUS_SUCCESS)
		{
			break;
		}

		//
		//	Continue initializing the ELAN structure
		//
#if DBG
		//
		// 	Signatures, for debugging.
		//
		pElan->atmlane_elan_sig =  atmlane_elan_signature;
		pElan->LesSap.atmlane_sap_sig = atmlane_sap_signature;
		pElan->BusSap.atmlane_sap_sig = atmlane_sap_signature;
		pElan->DataSap.atmlane_sap_sig = atmlane_sap_signature;
#endif

		//
		//	Initialize state fields.
		//
		pElan->AdminState = ELAN_STATE_INIT;
		pElan->State = ELAN_STATE_ALLOCATED;
		NdisInitializeWorkItem(&pElan->EventWorkItem, AtmLaneEventHandler, pElan);
		
		//
		//	Initialize spinlocks.
		//
#if SENDLIST
		NdisAllocateSpinLock(&pElan->SendListLock);
#endif // SENDLIST
		INIT_ELAN_LOCK(pElan);
		INIT_ELAN_MAC_TABLE_LOCK(pElan);
		INIT_ELAN_ATM_LIST_LOCK(pElan);
		INIT_ELAN_TIMER_LOCK(pElan);
		INIT_BLOCK_STRUCT(&pElan->Block);
		INIT_BLOCK_STRUCT(&pElan->InitBlock);
		INIT_BLOCK_STRUCT(&pElan->AfBlock);
		INIT_HEADER_LOCK(pElan);

		//
		//	Init event queue.
		//
		InitializeListHead(&pElan->EventQueue);

		//
		//  Initialize timer wheels.
		//
		for (i = 0; i < ALT_CLASS_MAX; i++)
		{
			pTimerList = &(pElan->TimerList[i]);
			NdisZeroMemory(
				pTimerList->pTimers,
				sizeof(ATMLANE_TIMER) * AtmLaneTimerListSize[i]
				);
			pTimerList->MaxTimer = AtmLaneMaxTimerValue[i];
			pTimerList->TimerPeriod = AtmLaneTimerPeriod[i];
			pTimerList->ListContext = (PVOID)pElan;
			pTimerList->TimerListSize = AtmLaneTimerListSize[i];

			INIT_SYSTEM_TIMER(
					&(pTimerList->NdisTimer),
					AtmLaneTickHandler,
					(PVOID)pTimerList
					);
		}

		//
		//	Initialize all sub-components.
		//
		NdisZeroMemory(pMacTable, ATMLANE_MAC_TABLE_SIZE*sizeof(PATMLANE_MAC_ENTRY));
		NdisZeroMemory(pLesSapInfo, SapSize);
		NdisZeroMemory(pBusSapInfo, SapSize);
		NdisZeroMemory(pDataSapInfo, SapSize);

		//
		//	Link sub-components to the Elan structure.
		//
		pElan->pMacTable = pMacTable;

		pElan->LesSap.pInfo = pLesSapInfo;
		pElan->BusSap.pInfo = pBusSapInfo;
		pElan->DataSap.pInfo = pDataSapInfo;
				
		//
		//	Link the Elan to the adapter.
		//
		pElan->pAdapter = pAdapter;
		ACQUIRE_ADAPTER_LOCK(pAdapter);

		//
		//  Find a free ELAN number.
		//
		for (ElanNumber = 0; ElanNumber <= pAdapter->ElanCount; ElanNumber++)
		{
			PATMLANE_ELAN		pThisElan = NULL;
			PLIST_ENTRY			p;

			for (p = pAdapter->ElanList.Flink;
 				 p != &pAdapter->ElanList;
 				 p = p->Flink)
			{
				pThisElan = CONTAINING_RECORD(p, ATMLANE_ELAN, Link);

				if (pThisElan->ElanNumber == ElanNumber)
				{
					break;
				}
			}

			//
			//  See if we made it to the end of the list without hitting
			//  the current ElanNumber. If so, use this ElanNumber.
			//
			if (p == &pAdapter->ElanList)
			{
				break;
			}
		}

		DBGP((0, "%d Assign ElanNumber to ELAN %x\n", ElanNumber, pElan));
		(VOID)AtmLaneReferenceAdapter(pAdapter, "elan");
		InsertTailList(&pAdapter->ElanList,	&pElan->Link);
		pElan->ElanNumber = ElanNumber;
		pAdapter->ElanCount++;
		RELEASE_ADAPTER_LOCK(pAdapter);

		//
		//	Cache NdisAdapterHandle.
		//
		pElan->NdisAdapterHandle = pAdapter->NdisAdapterHandle;

		//
		//	Generate a MAC Address for the elan
		//
		AtmLaneGenerateMacAddr(pElan);		

		//
		//	Set the rest of the LANE Run-time parameters to defaults
		//
		pElan->ControlTimeout 		= LANE_C7_DEF;
		pElan->MaxUnkFrameCount 	= LANE_C10_DEF;
		pElan->MaxUnkFrameTime	 	= LANE_C11_DEF;
		pElan->VccTimeout 			= LANE_C12_DEF;
		pElan->MaxRetryCount 		= LANE_C13_DEF;
		pElan->AgingTime		 	= LANE_C17_DEF;
		pElan->ForwardDelayTime		= LANE_C18_DEF;
		pElan->ArpResponseTime	 	= LANE_C20_DEF;
		pElan->FlushTimeout 		= LANE_C21_DEF;
		pElan->PathSwitchingDelay 	= LANE_C22_DEF;
		pElan->ConnComplTimer 		= LANE_C28_DEF;

		//
		//	Calc the bus rate limiter parameters
		//			
		pElan->LimitTime 			= pElan->MaxUnkFrameTime * 1000;
		pElan->IncrTime 			= pElan->LimitTime / pElan->MaxUnkFrameCount;

		Status = NDIS_STATUS_SUCCESS;
		break;

	} while (FALSE);

	if (NDIS_STATUS_SUCCESS != Status)
	{
		//
		//	Failure cleanup.
		//
		if (NULL_PATMLANE_ELAN != pElan)
		{
			for (i = 0; i < ALT_CLASS_MAX; i++)
			{
				pTimerList = &(pElan->TimerList[i]);
				if (NULL != pTimerList->pTimers)
				{
					FREE_MEM(pTimerList->pTimers);
				}
			}
		}
		if (NULL != pLesSapInfo)
		{
			FREE_MEM(pLesSapInfo);
		}
		if (NULL != pBusSapInfo)
		{
			FREE_MEM(pBusSapInfo);
		}
		if (NULL != pDataSapInfo)
		{
			FREE_MEM(pDataSapInfo);
		}
		if (NULL != pMacTable)
		{
			FREE_MEM(pMacTable);
		}
		if (NULL_PATMLANE_ELAN != pElan)
		{
			FREE_MEM(pElan);
			pElan = NULL_PATMLANE_ELAN;
		}
	}
	//
	// 	Output pElan
	//
	*ppElan = pElan;

	TRACEOUT(AllocElan);

	return Status;
}


VOID
AtmLaneDeallocateElan(
	IN	PATMLANE_ELAN		pElan
)
/*++

Routine Description:

	Deallocate an Elan structure. It is assumed that all
	references to this structure have gone, so it is not necessary
	to acquire a lock to it.

	Also delink this from the Adapter's Elan list.

Arguments:

	pElan		- Pointer to Elan structure to be deallocated.

Return Value:

	None

--*/
{
	PATMLANE_ADAPTER		pAdapter;
	PATMLANE_ATM_ENTRY		pAtmEntry;
	PATMLANE_ATM_ENTRY		pNext;
	UINT					i;
	
	TRACEIN(DeallocateElan);

	STRUCT_ASSERT(pElan, atmlane_elan);

	ASSERT(pElan->RefCount == 0);

	DBGP((0, "%d Deleting ELAN %p\n", pElan->ElanNumber, pElan));


	//
	//	Free all subcomponents
	//

	//
	//	MAC Table
	//
	if ((PATMLANE_MAC_ENTRY *)NULL != pElan->pMacTable)
	{
		FREE_MEM(pElan->pMacTable);
		pElan->pMacTable = (PATMLANE_MAC_ENTRY *)NULL;
	}

	//
	//	ATM Entry List
	//
	for (pAtmEntry = pElan->pAtmEntryList;
		 pAtmEntry != NULL_PATMLANE_ATM_ENTRY;
		 pAtmEntry = (PATMLANE_ATM_ENTRY)pNext)
	{
		pNext = (PVOID)pAtmEntry->pNext;
		FREE_MEM(pAtmEntry);
	}
	pElan->pAtmEntryList = NULL_PATMLANE_ATM_ENTRY;

	//
	//  Timers
	//
	for (i = 0; i < ALT_CLASS_MAX; i++)
	{
		PATMLANE_TIMER_LIST pTimerList = &(pElan->TimerList[i]);
		if (NULL != pTimerList->pTimers)
		{
			FREE_MEM(pTimerList->pTimers);
		}
		pTimerList->pTimers = NULL_PATMLANE_TIMER;
	}

	//
	//	ProtocolPacketPool
	//	ProtocolBufferPool
	//	ProtocolBufList
	//
	AtmLaneDeallocateProtoBuffers(pElan);

	//
	//	TransmitPacketPool
	//
	if (pElan->TransmitPacketPool != NULL_NDIS_HANDLE)
	{
		NdisFreePacketPool(pElan->TransmitPacketPool);
		pElan->TransmitPacketPool = NULL_NDIS_HANDLE;
	}

	//
	//	ReceivePacketPool
	//
	if (pElan->ReceivePacketPool != NULL_NDIS_HANDLE)
	{
		NdisFreePacketPool(pElan->ReceivePacketPool);
		pElan->ReceivePacketPool = NULL_NDIS_HANDLE;
	}

	//
	//	ReceiveBufferPool
	//
	if (pElan->ReceiveBufferPool != NULL_NDIS_HANDLE)
	{
		NdisFreeBufferPool(pElan->ReceiveBufferPool);
		pElan->ReceiveBufferPool = NULL_NDIS_HANDLE;
	}

	//
	//	HeaderBufList
	//	pHeaderTrkList
	//
	AtmLaneDeallocateHeaderBuffers(pElan);

	//
	//	PadBufList
	//	pPadTrkList
	//
	AtmLaneDeallocatePadBufs(pElan);
		
	//
	//	Free the config strings
	//
	if (NULL != pElan->CfgBindName.Buffer)
	{
		FREE_MEM(pElan->CfgBindName.Buffer);
	}
	if (NULL != pElan->CfgDeviceName.Buffer)
	{
		FREE_MEM(pElan->CfgDeviceName.Buffer);
	}
	if (NULL != pElan->CfgElanName.Buffer)
	{
		FREE_MEM(pElan->CfgElanName.Buffer);
	}

	//
	//	Free the Sap info
	//

	if (NULL != pElan->LesSap.pInfo)
	{
		FREE_MEM(pElan->LesSap.pInfo);
	}
	if (NULL != pElan->BusSap.pInfo)
	{
		FREE_MEM(pElan->BusSap.pInfo);
	}
	if (NULL != pElan->DataSap.pInfo)
	{
		FREE_MEM(pElan->DataSap.pInfo);
	}
	
	//
	//	Free the locks.
	//
#if SENDLIST
	NdisFreeSpinLock(&pElan->SendListLock);
#endif // SENDLIST
	FREE_ELAN_LOCK(pElan);
	FREE_ELAN_MAC_TABLE_LOCK(pElan);
	FREE_ELAN_ATM_LIST_LOCK(pElan);
	FREE_ELAN_TIMER_LOCK(pElan);
	FREE_BLOCK_STRUCT(&pElan->Block);
	FREE_HEADER_LOCK(pElan);

	AtmLaneUnlinkElanFromAdapter(pElan);

#if DBG
	pElan->atmlane_elan_sig++;
#endif
	//
	//  Finally free the Elan structure.
	//
	FREE_MEM(pElan);

	TRACEOUT(DeallocateElan);

	return;
}

VOID
AtmLaneReferenceElan(
	IN	PATMLANE_ELAN	pElan,
	IN	PUCHAR			String
	)
/*++

Routine Description:

	Add a references to an Elan structure.
	NOTE: The caller is assumed to possess the Elan's lock.

Arguments:

	pElan	-	Pointer to the Elan structure.


Return Value:

	None.

--*/
{
	TRACEIN(ReferenceElan);

	STRUCT_ASSERT(pElan, atmlane_elan);
	
	pElan->RefCount++;
	
	DBGP((5, "ReferenceElan: Elan %p/%x (%s) new count %d\n",
			 pElan, pElan->Flags, String, pElan->RefCount));

	TRACEOUT(ReferenceElan);

	return;
}

ULONG
AtmLaneDereferenceElan(
	IN	PATMLANE_ELAN		pElan,
	IN	PUCHAR				String
	)
/*++

Routine Description:

	Subtract a reference from an Elan structure. 
	If the reference count becomes zero, deallocate it.
	NOTE: The caller is assumed to posses the Elan's lock.

Arguments:

	pElan	-	Pointer to an Elan structure.


Return Value:

	None.

--*/
{
	ULONG		rc;
#if DBG
	ULONG		Flags = pElan->Flags;
#endif

	TRACEIN(DereferenceElan);

	STRUCT_ASSERT(pElan, atmlane_elan);

	ASSERT(pElan->RefCount > 0);

	rc = --(pElan->RefCount);

	if (rc == 0)
	{
		pElan->Flags |= ELAN_DEALLOCATING;
		RELEASE_ELAN_LOCK(pElan);
		AtmLaneDeallocateElan(pElan);
	}
	
	DBGP((5, "DereferenceElan: Elan %p/%x (%s) new count %d\n",
			pElan, Flags, String, rc));

	TRACEOUT(DereferenceElan);

	return (rc);
}

VOID
AtmLaneUnlinkElanFromAdapter(
	IN	PATMLANE_ELAN			pElan
)
/*++

Routine Description:

	Unlinks an ELAN structure from the Adapter structure it is linked to.
	Also continues any pending operation on the Adapter.

Arguments:

	pElan		- Pointer to Elan

Return Value:

	None

--*/
{
	PATMLANE_ADAPTER		pAdapter;
	BOOLEAN					CompleteUnbind;

	DBGP((1, "%d UnlinkElanFromAdapter: pElan %p/%x, Ref %d, pAdapter %p\n",
			pElan->ElanNumber, pElan, pElan->Flags, pElan->RefCount, pElan->pAdapter));

	pAdapter = pElan->pAdapter;

	if (pAdapter != NULL_PATMLANE_ADAPTER)
	{
		DBGP((1, "UnlinkElanFromAdapter: pAdapter %x, Flags %x, RefCount %d\n",
					pAdapter,
					pAdapter->Flags, pAdapter->RefCount));
		//
		//  Unlink from adapter list.
		//
		ACQUIRE_ADAPTER_LOCK(pAdapter);
		pElan->pAdapter = NULL_PATMLANE_ADAPTER;
		RemoveEntryList(&pElan->Link);
		pAdapter->ElanCount--;
		AtmLaneDereferenceAdapter(pAdapter, "elan");

		if (IsListEmpty(&pAdapter->ElanList) &&
			(pAdapter->Flags & ADAPTER_FLAGS_UNBIND_COMPLETE_PENDING))
		{
			pAdapter->Flags &= ~ADAPTER_FLAGS_UNBIND_COMPLETE_PENDING;
			CompleteUnbind = TRUE;
		}
		else
		{
			CompleteUnbind = FALSE;
		}

		RELEASE_ADAPTER_LOCK(pAdapter);

		//
		//  If we just freed the last elan structure on this
		//  adapter, and an Unbind operation was in progress, complete
		//  it now.
		//
		if (CompleteUnbind)
		{
			AtmLaneCompleteUnbindAdapter(pAdapter);
		}
	}
}

PATMLANE_ATM_ENTRY
AtmLaneAllocateAtmEntry(
	IN	PATMLANE_ELAN			pElan
)
/*++

Routine Description:

	Allocate an ATM Entry structure, initialize it, and return it.

Arguments:

	pElan		- Pointer to Elan on which the entry is allocated

Return Value:

	Pointer to allocated ATM Entry structure if successful, NULL otherwise.

--*/
{
	PATMLANE_ATM_ENTRY		pAtmEntry;

	TRACEIN(AllocateAtmEntry);

	STRUCT_ASSERT(pElan, atmlane_elan);

	ALLOC_MEM(&pAtmEntry, sizeof(ATMLANE_ATM_ENTRY));
	if (pAtmEntry != NULL_PATMLANE_ATM_ENTRY)
	{
		NdisZeroMemory(pAtmEntry, sizeof(ATMLANE_ATM_ENTRY));
#if DBG
		pAtmEntry->atmlane_atm_sig = atmlane_atm_signature;
#endif
		pAtmEntry->Flags = ATM_ENTRY_IDLE;
		INIT_ATM_ENTRY_LOCK(pAtmEntry);
		pAtmEntry->pElan = pElan;
		
	}

	DBGP((5, "AllocateAtmEntry:ATM Entry: Elan %x, Entry %x\n",
				pElan, pAtmEntry));
				
	TRACEOUT(AllocateAtmEntry);

	return (pAtmEntry);
}


VOID
AtmLaneDeallocateAtmEntry(
	IN	PATMLANE_ATM_ENTRY			pAtmEntry
)
/*++

Routine Description:

	Free an ATM Entry structure. It is assumed that all references
	to the structure have gone. We don't need any locks here.

Arguments:

	pAtmEntry		- Pointer to ATM Entry to be freed.

Return Value:

	None

--*/
{
	TRACEIN(DeallocateAtmEntry);


	STRUCT_ASSERT(pAtmEntry, atmlane_atm);
	ASSERT(pAtmEntry->RefCount == 0);
	ASSERT(pAtmEntry->pVcList == NULL_PATMLANE_VC);

#if DBG
	pAtmEntry->atmlane_atm_sig++;
#endif

	FREE_ATM_ENTRY_LOCK(pAtmEntry);
	FREE_MEM(pAtmEntry);

	DBGP((5, "DeallocateAtmEntry: ATM Entry: %x\n", pAtmEntry));

	TRACEOUT(DeallocateAtmEntry);
}


VOID
AtmLaneReferenceAtmEntry(
	IN	PATMLANE_ATM_ENTRY			pAtmEntry,
	IN	PUCHAR						String
)
/*++

Routine Description:

	Add a reference to the specified ATM Entry.
	NOTE: The caller is assumed to possess a lock for the Entry.

Arguments:

	pAtmEntry			- Pointer to the Entry to be referenced

Return Value:

	None

--*/
{
	TRACEIN(ReferenceAtmEntry);
	
	STRUCT_ASSERT(pAtmEntry, atmlane_atm);

	pAtmEntry->RefCount++;

	DBGP((5, "ReferenceAtmEntry: Entry %x (%s) new count %d\n",
			 pAtmEntry, String, pAtmEntry->RefCount));
			 
	TRACEOUT(ReferenceAtmEntry);
}


ULONG
AtmLaneDereferenceAtmEntry(
	IN	PATMLANE_ATM_ENTRY			pAtmEntry,
	IN	PUCHAR						String
)
/*++

Routine Description:

	Subtract a reference from the specified ATM Entry. If the Entry's
	reference count becomes zero, deallocate it.

	NOTE: The caller is assumed to possess a lock for the Entry.
	SIDE EFFECT: See Return Value below

Arguments:

	pAtmEntry			- Pointer to the Entry to be dereferenced.

Return Value:

	Is the new reference count.
	[IMPORTANT] If the Entry's reference count became zero, the Entry will be
	deallocated -- the Entry lock is, obviously, released in this case.

--*/
{
	ULONG					rc;
	PATMLANE_ELAN			pElan;

	TRACEIN(DereferenceAtmEntry);
	
	STRUCT_ASSERT(pAtmEntry, atmlane_atm);

	if (pAtmEntry->RefCount == 0)
	{
		rc = 0;
	}
	else
	{
		rc = --(pAtmEntry->RefCount);
	}

	if (rc == 0)
	{
		PATMLANE_ATM_ENTRY *	ppAtmEntry;

		DBGP((5, "DerefAtmEntry %x, RefCount is 0\n", pAtmEntry));

		//
		//  Unlink this entry from the Elan's list of ATM Entries.
		//

		//
		//  Acquire locks in the right order. However note that in doing so,
		//  some other thread might stumble across this ATM entry and reference
		//  it (and also dereference it!). To handle this, add a temp ref first.
		//
		pAtmEntry->RefCount++;

		pElan = pAtmEntry->pElan;
		STRUCT_ASSERT(pElan, atmlane_elan);

		RELEASE_ATM_ENTRY_LOCK(pAtmEntry);

		ACQUIRE_ELAN_ATM_LIST_LOCK(pElan);
		ACQUIRE_ATM_ENTRY_LOCK_DPC(pAtmEntry);

		//
		//  Remove the temp ref above. If the ref count is still at 0,
		//  nobody is using this ATM entry and it is safe to remove it
		//  from the list.
		//
		rc = --(pAtmEntry->RefCount);
		
		if (rc == 0)
		{
			//
			//  Safe to delete this ATM entry.
			//
#if DBG 
			if (pAtmEntry->pMacEntryList != NULL)
			{
				DBGP((0, "ATMLANE: derefed pAtmEntry %x, but MACEntryList isn't NULL!\n",
					pAtmEntry));
				ASSERT(FALSE);
			}
#endif // DBG
                
			ppAtmEntry = &(pElan->pAtmEntryList);
			while (*ppAtmEntry != pAtmEntry)
			{
				ASSERT(*ppAtmEntry != NULL_PATMLANE_ATM_ENTRY);
				ppAtmEntry = &((*ppAtmEntry)->pNext);
			}

			*ppAtmEntry = pAtmEntry->pNext;

			pElan->NumAtmEntries--;
		
			//
			//	If ATM Entry is for a LANE server 
			//	then also invalidate elan's cached pointer to it
			//
			switch (pAtmEntry->Type)
			{
				case ATM_ENTRY_TYPE_LECS:
					pElan->pLecsAtmEntry = NULL_PATMLANE_ATM_ENTRY;
					break;
				case ATM_ENTRY_TYPE_LES:
					pElan->pLesAtmEntry = NULL_PATMLANE_ATM_ENTRY;
					break;
				case ATM_ENTRY_TYPE_BUS:
					pElan->pBusAtmEntry = NULL_PATMLANE_ATM_ENTRY;
					break;
			}
		}

		RELEASE_ATM_ENTRY_LOCK_DPC(pAtmEntry);
		RELEASE_ELAN_ATM_LIST_LOCK(pElan);

		if (rc == 0)
		{
			AtmLaneDeallocateAtmEntry(pAtmEntry);
		}
		else
		{
			//
			//  As far as this caller is concerned, the ATM entry is gone.
			//  Return 0.
			//
			rc = 0;
		}
	}

	DBGP((5, "DereferenceAtmEntry: Entry %x (%s) new count %d\n", 
			pAtmEntry, String, rc));

	TRACEOUT(DereferenceAtmEntry);
	
	return (rc);
}

PATMLANE_VC
AtmLaneAllocateVc(
	IN	PATMLANE_ELAN				pElan
)
/*++

Routine Description:

	Allocate an ATMLANE VC structure, initialize it, and return it.

Arguments:

	pElan		- Elan for which this VC is created.

Return Value:

	Pointer to VC if allocated, NULL otherwise.

--*/
{
	PATMLANE_VC			pVc;

	TRACEIN(AllocateVc);

	STRUCT_ASSERT(pElan, atmlane_elan);

	ALLOC_MEM(&pVc, sizeof(ATMLANE_VC));

	if (pVc != NULL_PATMLANE_VC)
	{
		NdisZeroMemory(pVc, sizeof(ATMLANE_VC));
#if DBG
		pVc->atmlane_vc_sig = atmlane_vc_signature;
#endif // DBG
		pVc->pElan = pElan;
		INIT_VC_LOCK(pVc);
	}

	DBGP((3, "Allocated Vc %x\n", pVc));

	TRACEOUT(AllocateVc);

	return (pVc);
}

VOID
AtmLaneDeallocateVc(
	IN	PATMLANE_VC					pVc
)
/*++

Routine Description:

	Deallocate an ATMLANE VC structure. It is assumed that all references
	to this VC have gone, so there is no need to acquire a lock to the VC.

Arguments:

	pVc			- Pointer to the VC to be deallocated

Return Value:

	None

--*/
{
	TRACEIN(DeallocateVc);

	STRUCT_ASSERT(pVc, atmlane_vc);
	ASSERT(pVc->RefCount == 0);

#if DBG
	pVc->atmlane_vc_sig++;
#endif
	FREE_VC_LOCK(pVc);
	FREE_MEM(pVc);

	DBGP((5, "Deallocated Vc %x\n", pVc));

	TRACEOUT(DeallocateVc);

	return;
}

VOID
AtmLaneReferenceVc(
	IN	PATMLANE_VC					pVc,
	IN	PUCHAR						String
)
/*++

Routine Description:

	Add a reference to the specified ATMLANE VC.
	NOTE: The caller is assumed to possess a lock for the VC.

Arguments:

	pVc			- Pointer to the VC to be referenced

Return Value:

	None

--*/
{
	TRACEIN(ReferenceVc);

	STRUCT_ASSERT(pVc, atmlane_vc);

	pVc->RefCount++;

	DBGP((5, "ReferenceVc: Vc %x (%s) new count %d\n",
			 pVc, String, pVc->RefCount));

	TRACEOUT(ReferenceVc);

	return;
}


ULONG
AtmLaneDereferenceVc(
	IN	PATMLANE_VC					pVc,
	IN	PUCHAR						String
)
/*++

Routine Description:

	Subtract a reference from the specified ATMLANE VC. If the VC's
	reference count becomes zero, deallocate it.

	NOTE: The caller is assumed to possess a lock for the VC.
	SIDE EFFECT: See Return Value below

Arguments:

	pVc			- Pointer to the VC to be dereferenced.

Return Value:

	Is the new reference count.
	[IMPORTANT] If the VC's reference count became zero, the VC will be
	deallocated -- the VC lock is, obviously, released in this case.

--*/
{
	ULONG		rv;

	TRACEIN(DereferenceVc);

	STRUCT_ASSERT(pVc, atmlane_vc);
	ASSERT(pVc->RefCount > 0);

	rv = --(pVc->RefCount);
	if (rv == 0)
	{
		RELEASE_VC_LOCK(pVc);
		AtmLaneDeallocateVc(pVc);
	}

	DBGP((5, "DereferenceVc: Vc %x (%s) new count %d\n", 
			pVc, String, rv));

	TRACEOUT(DereferenceVc);

	return (rv);
}

PATMLANE_MAC_ENTRY
AtmLaneAllocateMacEntry(
	IN	PATMLANE_ELAN			pElan
)
/*++

Routine Description:

	Allocate an ATMLANE MAC Entry structure, initialize it, and
	return it.

Arguments:

	pElan		- Pointer to ATMLANE Interface on which this MAC
				  Entry is allocated.

Return Value:

	Pointer to allocated MAC Entry structure if successful,
	NULL otherwise.

--*/
{
	PATMLANE_MAC_ENTRY		pMacEntry;

	TRACEIN(AllocateMacEntry);

	ALLOC_MEM(&pMacEntry, sizeof(ATMLANE_MAC_ENTRY));

	if (pMacEntry != NULL_PATMLANE_MAC_ENTRY)
	{
		NdisZeroMemory(pMacEntry, sizeof(ATMLANE_MAC_ENTRY));
#if DBG
		pMacEntry->atmlane_mac_sig = atmlane_mac_signature;
#endif // DBG
		pMacEntry->pElan = pElan;
		pMacEntry->Flags = MAC_ENTRY_NEW;
		INIT_MAC_ENTRY_LOCK(pMacEntry);

		INIT_SYSTEM_TIMER(
					&pMacEntry->BusTimer, 
					AtmLaneBusSendTimer, 
					pMacEntry);

		pMacEntry->LimitTime = pElan->LimitTime;
		pMacEntry->IncrTime = pElan->IncrTime;
	}

	DBGP((5, "AllocateMacEntry: Allocated Entry %x\n", pMacEntry));

	TRACEOUT(AllocateMacEntry);
	return (pMacEntry);
}

VOID
AtmLaneDeallocateMacEntry(
	IN	PATMLANE_MAC_ENTRY			pMacEntry
)
/*++

Routine Description:

	Deallocate an ATMLANE Mac Entry. It is assumed that all references
	to this Mac Entry have gone, so there is no need to acquire its
	lock.

Arguments:

	pMacEntry			- Pointer to the Mac Entry to be deallocated.

Return Value:

	None

--*/
{
	TRACEIN(DeallocateMacEntry);

	STRUCT_ASSERT(pMacEntry, atmlane_mac);
	ASSERT(pMacEntry->RefCount == 0);

#if DBG
	pMacEntry->atmlane_mac_sig++;
#endif

	FREE_MAC_ENTRY_LOCK(pMacEntry);
	FREE_MEM(pMacEntry);

	DBGP((5,"DeallocateMacEntry: Deallocated Entry %x\n", pMacEntry));

	TRACEOUT(DeallocateMacEntry);
	return;
}

VOID
AtmLaneReferenceMacEntry(
	IN	PATMLANE_MAC_ENTRY			pMacEntry,
	IN	PUCHAR						String
)
/*++

Routine Description:

	Add a reference to an ATMLANE Mac Entry.
	NOTE: The caller is assumed to possess a lock for the Mac Entry.

Arguments:

	pMacEntry			- Pointer to an ATMLANE Mac Entry.

Return Value:

	None

--*/
{
	TRACEIN(ReferenceMacEntry);

	STRUCT_ASSERT(pMacEntry, atmlane_mac);

	pMacEntry->RefCount++;

	DBGP((5, "ReferenceMacEntry: Entry %x (%s) new count %d\n",
			pMacEntry, String, pMacEntry->RefCount));

	TRACEOUT(ReferenceMacEntry);
	return;
}

ULONG
AtmLaneDereferenceMacEntry(
	IN	PATMLANE_MAC_ENTRY			pMacEntry,
	IN	PUCHAR						String
)
/*++

Routine Description:

	Subtract a reference from an ATMLANE MAC Entry. If the reference
	count becomes zero, deallocate it.
	NOTE: It is assumed that the caller holds a lock to the MAC Entry.
	See SIDE EFFECT below.

Arguments:

	pMacEntry			- Pointer to ATMLANE MAC Entry

Return Value:

	The resulting reference count. If this is zero, then there are two
	SIDE EFFECTS: (1) the MAC Entry lock is released (2) the structure
	is freed.

--*/
{
	ULONG		rc;
	
	TRACEIN(DereferenceMacEntry);

	STRUCT_ASSERT(pMacEntry, atmlane_mac);

	rc = --(pMacEntry->RefCount);

	if (rc == 0)
	{
		PVOID	Caller, CallersCaller;

		RELEASE_MAC_ENTRY_LOCK(pMacEntry);

		//
		//  Save away the caller's address for debugging purposes...
		//
		RtlGetCallersAddress(&Caller, &CallersCaller);
		pMacEntry->Timer.ContextPtr = Caller;

		AtmLaneDeallocateMacEntry(pMacEntry);
	}

	DBGP((5, "DereferenceMacEntry: Entry %x (%s) new count %d\n",
			pMacEntry, String, rc));

	TRACEOUT(DereferenceMacEntry);
	return (rc);
}

PNDIS_PACKET
AtmLaneAllocProtoPacket(
	IN	PATMLANE_ELAN			pElan
)
/*++

Routine Description:

	Allocate an NDIS packet for use as a LANE control frame.

Arguments:

	pElan		- Pointer to ATMLANE ELAN structure

Return Value:

	Pointer to NDIS packet if allocated, NULL otherwise.

--*/
{
	NDIS_STATUS		Status;
	PNDIS_PACKET	pNdisPacket;

	TRACEIN(AllocProtoPacket);

	NdisAllocatePacket(
			&Status,
			&pNdisPacket,
			pElan->ProtocolPacketPool
		);
		
	if (pNdisPacket != (PNDIS_PACKET)NULL)
	{
		//
		//	Init ProtocolReserved and mark packet owned by ATMLANE
		//
		ZERO_SEND_RSVD(pNdisPacket);
	#if PROTECT_PACKETS
		INIT_SENDPACKET_LOCK(pNdisPacket);
	#endif	// PROTECT_PACKETS
		SET_FLAG(
				PSEND_RSVD(pNdisPacket)->Flags,
				PACKET_RESERVED_OWNER_MASK,
				PACKET_RESERVED_OWNER_ATMLANE
				);
		
#if PKT_HDR_COUNTS
		InterlockedDecrement(&pElan->ProtPktCount);
		if ((pElan->ProtPktCount % 20) == 0)
		{
			DBGP((1, "ProtPktCount %d\n", pElan->ProtPktCount));
		}
#endif
	}	

	TRACEOUT(AllocProtoPacket);

	return (pNdisPacket);
}

VOID
AtmLaneFreeProtoPacket(
	IN	PATMLANE_ELAN			pElan,
	IN	PNDIS_PACKET			pNdisPacket
)
/*++

Routine Description:

	Allocate an NDIS packet used as a LANE control frame.

Arguments:

	pElan			- Pointer to ATMLANE ELAN structure

	pNdisPacket		- pointer to NDIS_PACKET to free.

Return Value:

	None
	
--*/
{
	TRACEIN(FreeProtoPacket);

	if (pNdisPacket != (PNDIS_PACKET)NULL)
	{
#if PROTECT_PACKETS
		FREE_SENDPACKET_LOCK(pNdisPacket);
#endif	// PROTECT_PACKETS
		NdisFreePacket(pNdisPacket);
#if PKT_HDR_COUNTS
		InterlockedIncrement(&pElan->ProtPktCount);
		if ((pElan->ProtPktCount % 20) == 0 && 
			pElan->ProtPktCount != pElan->MaxProtocolBufs)
		{
			DBGP((1, "ProtPktCount %d\n", pElan->ProtPktCount));
		}
#endif
	}

	TRACEOUT(FreeProtoPacket);
	return;
}

PNDIS_BUFFER
AtmLaneGrowHeaders(
	IN	PATMLANE_ELAN			pElan
)
/*++

Routine Description:

	Allocate a bunch of header buffers on the specified ATMLANE Elan.
	Return one of them.

	We allocate a new Buffer tracker structure, a new NDIS Buffer pool, and
	finally a chunk of system memory that we break down into header buffers.
	These header buffers are then attached to NDIS Buffers before they are
	inserted into the list of free header buffers for this Interface.

	Caller is assumed to hold appropriate lock.

Arguments:

	pElan		- Pointer to ATMLANE Elan structure

Return Value:

	Pointer to allocated NDIS buffer if successful, NULL otherwise.

--*/
{
	PATMLANE_BUFFER_TRACKER		pTracker;		// for new set of buffers
	PUCHAR						pSpace;
	PNDIS_BUFFER				pNdisBuffer;
	PNDIS_BUFFER				pReturnBuffer;
	PNDIS_BUFFER				pBufferList;	// allocated list
	INT							i;				// iteration counter
	NDIS_STATUS					Status;

	TRACEIN(GrowHeaders);

	//
	//  Initialize
	//
	pTracker = NULL_PATMLANE_BUFFER_TRACKER;
	pReturnBuffer = (PNDIS_BUFFER)NULL;

	do
	{
		if (pElan->CurHeaderBufs >= pElan->MaxHeaderBufs)
		{
			DBGP((2, "GrowHeaders: Elan %x, CurHdrBufs %d > MaxHdrBufs %d\n",
					pElan, pElan->CurHeaderBufs, pElan->MaxHeaderBufs));
			break;
		}

		//
		//  Allocate and initialize Buffer tracker
		//
		ALLOC_MEM(&pTracker, sizeof(ATMLANE_BUFFER_TRACKER));
		if (pTracker == NULL_PATMLANE_BUFFER_TRACKER)
		{
			DBGP((0, "GrowHeaders: Elan %x, alloc failed for tracker\n",
					pElan));
			break;
		}

		NdisZeroMemory(pTracker, sizeof(ATMLANE_BUFFER_TRACKER));

		//
		//  Get the NDIS Buffer pool
		//
		NdisAllocateBufferPool(
				&Status,
				&(pTracker->NdisHandle),
				DEF_HDRBUF_GROW_SIZE
			);

		if (Status != NDIS_STATUS_SUCCESS)
		{
			DBGP((0, 
				"GrowHeaders: Elan %x, NdisAllocateBufferPool err status %x\n",
					pElan, Status));
			break;
		}

		//
		//  Allocate system space for a bunch of header buffers
		//	Note we use RealHeaderBufSize here so that the
		//	buffers end up on ULONG boundaries.
		//
		ALLOC_MEM(&(pTracker->pPoolStart),  
					pElan->RealHeaderBufSize * DEF_HDRBUF_GROW_SIZE);
		if (pTracker->pPoolStart == (PUCHAR)NULL)
		{
			DBGP((0, "GrowHeaders: Elan %x, could not alloc buf space %d bytes\n",
					pElan, pElan->HeaderBufSize * DEF_HDRBUF_GROW_SIZE));
			break;
		}

		//
		//  Make NDIS buffers out of the allocated space, and put them
		//  into the free header buffer list. Retain one for returning
		//  to caller.
		//
		pBufferList = (PNDIS_BUFFER)NULL;
		pSpace = pTracker->pPoolStart;
		for (i = 0; i < DEF_HDRBUF_GROW_SIZE; i++)
		{
			NdisAllocateBuffer(
					&Status,
					&pNdisBuffer,
					pTracker->NdisHandle,
					pSpace,
					pElan->HeaderBufSize
				);

			if (Status != NDIS_STATUS_SUCCESS)
			{
				DBGP((0, 
					"GrowHeaders: NdisAllocateBuffer failed: Elan %x, status %x\n",
							pElan, Status));
				break;
			}

			if (i == 0)
			{
				pReturnBuffer = pNdisBuffer;
			}
			else
			{
				NDIS_BUFFER_LINKAGE(pNdisBuffer) = pBufferList;
				pBufferList = pNdisBuffer;
			}
			pSpace += pElan->RealHeaderBufSize;
		}

		if (i > 0)
		{
			//
			//  Successfully allocated at least one more header buffer
			//
			pTracker->pNext = pElan->pHeaderTrkList;
			pElan->pHeaderTrkList = pTracker;
			pElan->CurHeaderBufs += i;

			pNdisBuffer = pBufferList;
			while (pNdisBuffer != (PNDIS_BUFFER)NULL)
			{
				pBufferList = NDIS_BUFFER_LINKAGE(pNdisBuffer);
				NDIS_BUFFER_LINKAGE(pNdisBuffer) = NULL;
				AtmLaneFreeHeader(pElan, pNdisBuffer, TRUE);
				pNdisBuffer = pBufferList;
			}
		}

	} while (FALSE);

	if (pReturnBuffer == (PNDIS_BUFFER)NULL)
	{
		//
		//  Failed to allocate. Undo all.
		//
		if (pTracker != NULL_PATMLANE_BUFFER_TRACKER)
		{
			if (pTracker->pPoolStart != (PUCHAR)NULL)
			{
				FREE_MEM(pTracker->pPoolStart);
			}
			if (pTracker->NdisHandle != (NDIS_HANDLE)NULL)
			{
				NdisFreeBufferPool(pTracker->NdisHandle);
			}
			FREE_MEM(pTracker);
		}
	}

	DBGP((2, "GrowHeaders: Elan %x, RetBuf %x, New Tracker %x\n",
				pElan, pReturnBuffer, pTracker));

	TRACEOUT(GrowHeaders);

	return (pReturnBuffer);
}

PNDIS_BUFFER
AtmLaneAllocateHeader(
	IN	PATMLANE_ELAN			pElan,
	OUT	PUCHAR *				pBufferAddress
)
/*++

Routine Description:

	Allocate an NDIS Buffer to be used for LECID a MAC packet. 
	We pick up the buffer at the top of the pre-allocated
	buffer list, if one exists. Otherwise, we try to grow this list and
	allocate.

Arguments:

	pElan			- Pointer to ATMLANE Elan
	pBufferAddress	- Place to return virtual address of allocated buffer

Return Value:

	Pointer to NDIS buffer if successful, NULL otherwise.

--*/
{
	PNDIS_BUFFER			pNdisBuffer;
	NDIS_STATUS				Status;
	ULONG					Length;

	TRACEIN(AllocateHeader);

	ACQUIRE_HEADER_LOCK(pElan);

	pNdisBuffer = pElan->HeaderBufList;
	if (pNdisBuffer != (PNDIS_BUFFER)NULL)
	{
		pElan->HeaderBufList = NDIS_BUFFER_LINKAGE(pNdisBuffer);
		NDIS_BUFFER_LINKAGE(pNdisBuffer) = NULL;
		NdisQueryBuffer(pNdisBuffer, (PVOID)pBufferAddress, &Length);
	}
	else
	{
		pNdisBuffer = AtmLaneGrowHeaders(pElan);
		if (pNdisBuffer != (PNDIS_BUFFER)NULL)
		{
			NDIS_BUFFER_LINKAGE(pNdisBuffer) = NULL;
			NdisQueryBuffer(pNdisBuffer, (PVOID)pBufferAddress, &Length);
		}
	}

	DBGP((5, "AllocateHeader: Buffer %x, Elan %x\n",
					pNdisBuffer, pElan));

	RELEASE_HEADER_LOCK(pElan);

	TRACEOUT(AllocateHeader);
	return (pNdisBuffer);
}

VOID
AtmLaneFreeHeader(
	IN	PATMLANE_ELAN				pElan,
	IN	PNDIS_BUFFER				pNdisBuffer,
	IN	BOOLEAN						LockHeld
)
/*++

Routine Description:

	Deallocate a header buffer.

Arguments:

	pElan			- Pointer to ATMLANE Elan from which the buffer came
	pNdisBuffer		- Pointer to NDIS buffer being freed
	LockHeld		- TRUE if appropriate lock already held

Return Value:

	None

--*/
{
	TRACEIN(FreeHeader);

	if (!LockHeld)
	{
		ACQUIRE_HEADER_LOCK(pElan);
	}

	NDIS_BUFFER_LINKAGE(pNdisBuffer) = pElan->HeaderBufList;
	pElan->HeaderBufList = pNdisBuffer;

	DBGP((5, "FreeHeader: Buffer %x, Elan %x\n",
					pNdisBuffer, pElan));
					
	if (!LockHeld)
	{
		RELEASE_HEADER_LOCK(pElan);
	}
	
	TRACEOUT(FreeHeader);
}

VOID
AtmLaneDeallocateHeaderBuffers(
	IN	PATMLANE_ELAN				pElan
)
/*++

Routine Description:

	Deallocate everything pertaining to header buffers on an Elan.

Arguments:

	pElan				- Pointer to ATMLANE Elan.

Return Value:

	None

--*/
{
	PNDIS_BUFFER				pNdisBuffer;
	NDIS_STATUS					Status;
	PATMLANE_BUFFER_TRACKER		pTracker;
	PATMLANE_BUFFER_TRACKER		pNextTracker;

	TRACEIN(DeallocateHeaderBuffers);

	//
	//  Free all NDIS buffers in the header buffer list.
	//
	ACQUIRE_HEADER_LOCK(pElan);
	do
	{
		pNdisBuffer = pElan->HeaderBufList;
		if (pNdisBuffer != (PNDIS_BUFFER)NULL)
		{
			pElan->HeaderBufList = NDIS_BUFFER_LINKAGE(pNdisBuffer);
			NDIS_BUFFER_LINKAGE(pNdisBuffer) = NULL;
			NdisFreeBuffer(pNdisBuffer);
		}
		else
		{
			//
			//  No more NDIS buffers.
			//
			break;
		}
	}
	while (TRUE);

	//
	//  Now free all the buffer trackers.
	//
	pTracker = pElan->pHeaderTrkList;

	while (pTracker != NULL_PATMLANE_BUFFER_TRACKER)
	{
		pNextTracker = pTracker->pNext;
		if (pTracker->pPoolStart != (PUCHAR)NULL)
		{
			FREE_MEM(pTracker->pPoolStart);
			pTracker->pPoolStart = (PUCHAR)NULL;
		}
		if (pTracker->NdisHandle != (NDIS_HANDLE)NULL)
		{
			NdisFreeBufferPool(pTracker->NdisHandle);
			pTracker->NdisHandle = (NDIS_HANDLE)NULL;
		}
		FREE_MEM(pTracker);
		pTracker = pNextTracker;
	}

	RELEASE_HEADER_LOCK(pElan);

	TRACEOUT(DeallocateHeaderBuffers);
}

PNDIS_BUFFER
AtmLaneGrowPadBufs(
	IN	PATMLANE_ELAN			pElan
)
/*++

Routine Description:

	Allocate a bunch of packet pad buffers on the specified ATMLANE Elan.
	Return one of them.

	We allocate a new Buffer tracker structure, a new NDIS Buffer pool, and
	finally a chunk of system memory (if not allocated already, only need one).
	This buffer is then attached to the NDIS Buffers before they are
	inserted into the list of free pad buffers for this Interface.

	Caller is assumed to hold appropriate lock.

Arguments:

	pElan		- Pointer to ATMLANE Elan structure

Return Value:

	Pointer to allocated NDIS buffer if successful, NULL otherwise.

--*/
{
	PATMLANE_BUFFER_TRACKER		pTracker;		// for new set of buffers
	PUCHAR						pSpace;
	PNDIS_BUFFER				pNdisBuffer;
	PNDIS_BUFFER				pReturnBuffer;
	PNDIS_BUFFER				pBufferList;	// allocated list
	INT							i;				// iteration counter
	NDIS_STATUS					Status;

	TRACEIN(GrowPadBufs);

	//
	//  Initialize
	//
	pTracker = NULL_PATMLANE_BUFFER_TRACKER;
	pReturnBuffer = (PNDIS_BUFFER)NULL;

	do
	{
		if (pElan->CurPadBufs >= pElan->MaxPadBufs)
		{
			DBGP((0, "GrowPadBufs: Max Reached! Elan %x, CurPadBufs %d > MaxPadBufs %d\n",
					pElan, pElan->CurPadBufs, pElan->MaxPadBufs));
			break;
		}

		//
		//  Allocate and initialize Buffer tracker
		//
		ALLOC_MEM(&pTracker, sizeof(ATMLANE_BUFFER_TRACKER));
		if (pTracker == NULL_PATMLANE_BUFFER_TRACKER)
		{
			DBGP((0, "GrowPadBufs: Elan %x, alloc failed for tracker\n",
					pElan));
			break;
		}

		NdisZeroMemory(pTracker, sizeof(ATMLANE_BUFFER_TRACKER));

		//
		//  Get the NDIS Buffer pool
		//
		NdisAllocateBufferPool(
				&Status,
				&(pTracker->NdisHandle),
				DEF_HDRBUF_GROW_SIZE
			);

		if (Status != NDIS_STATUS_SUCCESS)
		{
			DBGP((0, 
				"GrowPadBufs: Elan %x, NdisAllocateBufferPool err status %x\n",
					pElan, Status));
			break;
		}

		//
		//  Allocate system space for a single pad buffer.
		//
		ALLOC_MEM(&(pTracker->pPoolStart), pElan->PadBufSize);
		if (pTracker->pPoolStart == (PUCHAR)NULL)
		{
			DBGP((0, "GrowPadBufs: Elan %x, could not alloc buf space %d bytes\n",
					pElan, pElan->PadBufSize * DEF_HDRBUF_GROW_SIZE));
			break;
		}

		//
		//  Make NDIS buffers out of the allocated space, and put them
		//  into the free pad buffer list. Retain one for returning
		//  to caller.  NOTE we put same pad buffer in each ndis buffer header
		//	since contents is irrelevent.
		//
		pBufferList = (PNDIS_BUFFER)NULL;
		pSpace = pTracker->pPoolStart;
		for (i = 0; i < DEF_HDRBUF_GROW_SIZE; i++)
		{
			NdisAllocateBuffer(
					&Status,
					&pNdisBuffer,
					pTracker->NdisHandle,
					pSpace,
					pElan->PadBufSize
				);

			if (Status != NDIS_STATUS_SUCCESS)
			{
				DBGP((0, 
					"GrowPadBufs: NdisAllocateBuffer failed: Elan %x, status %x\n",
							pElan, Status));
				break;
			}

			if (i == 0)
			{
				pReturnBuffer = pNdisBuffer;
			}
			else
			{
				NDIS_BUFFER_LINKAGE(pNdisBuffer) = pBufferList;
				pBufferList = pNdisBuffer;
			}
		}

		if (i > 0)
		{
			//
			//  Successfully allocated at least one more pad buffer
			//
			pTracker->pNext = pElan->pPadTrkList;
			pElan->pPadTrkList = pTracker;
			pElan->CurPadBufs += i;

			pNdisBuffer = pBufferList;
			while (pNdisBuffer != (PNDIS_BUFFER)NULL)
			{
				pBufferList = NDIS_BUFFER_LINKAGE(pNdisBuffer);
				NDIS_BUFFER_LINKAGE(pNdisBuffer) = NULL;
				AtmLaneFreePadBuf(pElan, pNdisBuffer, TRUE);
				pNdisBuffer = pBufferList;
			}
		}

	} while (FALSE);

	if (pReturnBuffer == (PNDIS_BUFFER)NULL)
	{
		//
		//  Failed to allocate. Undo all.
		//
		if (pTracker != NULL_PATMLANE_BUFFER_TRACKER)
		{
			if (pTracker->pPoolStart != (PUCHAR)NULL)
			{
				FREE_MEM(pTracker->pPoolStart);
			}
			if (pTracker->NdisHandle != (NDIS_HANDLE)NULL)
			{
				NdisFreeBufferPool(pTracker->NdisHandle);
			}
			FREE_MEM(pTracker);
		}
	}

	DBGP((2, "GrowPadBufs: Elan %x, RetBuf %x, New Tracker %x\n",
				pElan, pReturnBuffer, pTracker));

	TRACEOUT(GrowPadBufs);

	return (pReturnBuffer);
}

PNDIS_BUFFER
AtmLaneAllocatePadBuf(
	IN	PATMLANE_ELAN			pElan,
	OUT	PUCHAR *				pBufferAddress
)
/*++

Routine Description:

	Allocate an NDIS Buffer to be used to pad a MAC packet to min length.
	We pick up the buffer at the top of the pre-allocated
	buffer list, if one exists. Otherwise, we try to grow this list and
	allocate.

Arguments:

	pElan			- Pointer to ATMLANE Elan
	pBufferAddress	- Place to return virtual address of allocated buffer

Return Value:

	Pointer to NDIS buffer if successful, NULL otherwise.

--*/
{
	PNDIS_BUFFER			pNdisBuffer;
	NDIS_STATUS				Status;
	ULONG					Length;

	TRACEIN(AtmLaneAllocatePadBuf);

	ACQUIRE_HEADER_LOCK(pElan);

	pNdisBuffer = pElan->PadBufList;
	if (pNdisBuffer != (PNDIS_BUFFER)NULL)
	{
		pElan->PadBufList = NDIS_BUFFER_LINKAGE(pNdisBuffer);
		NDIS_BUFFER_LINKAGE(pNdisBuffer) = NULL;
		NdisQueryBuffer(pNdisBuffer, (PVOID)pBufferAddress, &Length);
	}
	else
	{
		pNdisBuffer = AtmLaneGrowPadBufs(pElan);
		if (pNdisBuffer != (PNDIS_BUFFER)NULL)
		{
			NDIS_BUFFER_LINKAGE(pNdisBuffer) = NULL;
			NdisQueryBuffer(pNdisBuffer, (PVOID)pBufferAddress, &Length);
		}
	}

	DBGP((5, "AllocatePadBuf: Buffer %x, Elan %x\n",
					pNdisBuffer, pElan));

	RELEASE_HEADER_LOCK(pElan);

	TRACEOUT(AllocatePadBuf);
	return (pNdisBuffer);
}

VOID
AtmLaneFreePadBuf(
	IN	PATMLANE_ELAN				pElan,
	IN	PNDIS_BUFFER				pNdisBuffer,
	IN	BOOLEAN						LockHeld
)
/*++

Routine Description:

	Deallocate a Pad buffer.

Arguments:

	pElan			- Pointer to ATMLANE Elan from which the buffer came
	pNdisBuffer		- Pointer to NDIS buffer being freed
	LockHeld		- TRUE if appropriate lock already held

Return Value:

	None

--*/
{
	TRACEIN(FreePadBuf);

	if (!LockHeld)
	{
		ACQUIRE_HEADER_LOCK(pElan);
	}

	NDIS_BUFFER_LINKAGE(pNdisBuffer) = pElan->PadBufList;
	pElan->PadBufList = pNdisBuffer;

	DBGP((5, "FreePadBuf: Buffer %x, Elan %x\n",
					pNdisBuffer, pElan));
					
	if (!LockHeld)
	{
		RELEASE_HEADER_LOCK(pElan);
	}
	
	TRACEOUT(FreePadBuf);
}

VOID
AtmLaneDeallocatePadBufs(
	IN	PATMLANE_ELAN				pElan
)
/*++

Routine Description:

	Deallocate everything pertaining to Pad buffers on an Elan.

Arguments:

	pElan				- Pointer to ATMLANE Elan.

Return Value:

	None

--*/
{
	PNDIS_BUFFER				pNdisBuffer;
	NDIS_STATUS					Status;
	PATMLANE_BUFFER_TRACKER		pTracker;
	PATMLANE_BUFFER_TRACKER		pNextTracker;

	TRACEIN(DeallocatePadBufs);

	//
	//  Free all NDIS buffers in the Pad buffer list.
	//
	ACQUIRE_HEADER_LOCK(pElan);
	do
	{
		pNdisBuffer = pElan->PadBufList;
		if (pNdisBuffer != (PNDIS_BUFFER)NULL)
		{
			pElan->PadBufList = NDIS_BUFFER_LINKAGE(pNdisBuffer);
			NDIS_BUFFER_LINKAGE(pNdisBuffer) = NULL;
			NdisFreeBuffer(pNdisBuffer);
		}
		else
		{
			//
			//  No more NDIS buffers.
			//
			break;
		}
	}
	while (TRUE);

	//
	//  Now free all the buffer trackers.
	//
	pTracker = pElan->pPadTrkList;

	while (pTracker != NULL_PATMLANE_BUFFER_TRACKER)
	{
		pNextTracker = pTracker->pNext;
		if (pTracker->pPoolStart != (PUCHAR)NULL)
		{
			FREE_MEM(pTracker->pPoolStart);
			pTracker->pPoolStart = (PUCHAR)NULL;
		}
		if (pTracker->NdisHandle != (NDIS_HANDLE)NULL)
		{
			NdisFreeBufferPool(pTracker->NdisHandle);
			pTracker->NdisHandle = (NDIS_HANDLE)NULL;
		}
		FREE_MEM(pTracker);
		pTracker = pNextTracker;
	}

	RELEASE_HEADER_LOCK(pElan);

	TRACEOUT(DeallocatePadBufs);
}

PNDIS_BUFFER
AtmLaneAllocateProtoBuffer(
	IN	PATMLANE_ELAN				pElan,
	IN	ULONG						Length,
	OUT	PUCHAR *					pBufferAddress
)
/*++

Routine Description:

	Allocate a buffer to be used for a LANE protocol message. Attach
	it to an NDIS_BUFFER structure and return a pointer to this.

Arguments:

	pElan			- Pointer to ATMLANE Elan
	Length			- Length, in bytes, of the buffer.
	pBufferAddress	- Place to return virtual address of allocated buffer.

Return Value:

	Pointer to NDIS Buffer if successful, NULL otherwise.

--*/
{
	PNDIS_BUFFER		pNdisBuffer;
	NDIS_STATUS			Status;

	TRACEIN(AllocateProtobuffer);
	
	//
	//  Initialize
	//
	pNdisBuffer = NULL;

	ACQUIRE_ELAN_LOCK(pElan);

	ASSERT(Length <= pElan->ProtocolBufSize);

	*pBufferAddress = pElan->ProtocolBufList;
	if (*pBufferAddress != (PUCHAR)NULL)
	{
		NdisAllocateBuffer(
				&Status,
				&pNdisBuffer,
				pElan->ProtocolBufferPool,
				*pBufferAddress,
				Length
			);

		if (Status == NDIS_STATUS_SUCCESS)
		{
			pElan->ProtocolBufList = *((PUCHAR *)*pBufferAddress);
		}
	}

	RELEASE_ELAN_LOCK(pElan);

	DBGP((5, 
		"AllocateProtoBuffer:  ELan %x, pNdisBuffer %x, Length %d, Loc %x\n",
				pElan, pNdisBuffer, Length, *pBufferAddress));

	TRACEOUT(AllocateProtoBuffer);
	
	return (pNdisBuffer);
}


VOID
AtmLaneFreeProtoBuffer(
	IN	PATMLANE_ELAN				pElan,
	IN	PNDIS_BUFFER				pNdisBuffer
)
/*++

Routine Description:

	Free an NDIS buffer (and associated memory) used for a protocol
	packet. We return the associated memory to the ProtocolBufList
	in the Elan structure, and the NDIS buffer to NDIS.

Arguments:

	pElan			- Pointer to ATMLANE Elan structure
	pNdisBuffer		- Pointer to NDIS buffer to be freed

Return Value:

	None

--*/
{
	PUCHAR *		pBufferLinkage;
	ULONG			Length;

	TRACEIN(FreeProtoBuffer);

#if 0
	pBufferLinkage = (PUCHAR *)NdisBufferVirtualAddress(pNdisBuffer);
#else
	NdisQueryBuffer(pNdisBuffer, (PVOID)&pBufferLinkage, &Length);
#endif

	ACQUIRE_ELAN_LOCK(pElan);

	*pBufferLinkage = pElan->ProtocolBufList;
	pElan->ProtocolBufList = (PUCHAR)pBufferLinkage;

	RELEASE_ELAN_LOCK(pElan);

	NdisFreeBuffer(pNdisBuffer);

	DBGP((5, "FreeProtoBuffer: Elan %x, pNdisBuffer %x, Loc %x\n",
			pElan, pNdisBuffer, (ULONG_PTR)pBufferLinkage));

	TRACEOUT(FreeProtoBuffer);
	return;
}


NDIS_STATUS
AtmLaneInitProtoBuffers(
	IN	PATMLANE_ELAN			pElan
)
/*++

Routine Description:

	Initialize the protocol buffer pool for an elan.

	Allocate a chunk of memory to be used for ATMLANE protocol messages.
	We prepare a linked list of protocol buffers, and attach it to the
	Interface structure.

Arguments:

	pElan			- Pointer to Interface on which we need to allocate
					  protocol buffers.
Return Value:

	NDIS_STATUS_SUCCESS if successful, NDIS_STATUS_RESOURCES if we run
	into a resource failure.

--*/
{
	NDIS_STATUS			Status;
	PUCHAR				pSpace;
	ULONG				i;

	TRACEIN(InitProtoBuffers);

	do
	{
		NdisAllocatePacketPool(
				&Status,
				&(pElan->ProtocolPacketPool),
				pElan->MaxProtocolBufs,
				sizeof(SEND_PACKET_RESERVED)
				);
#if PKT_HDR_COUNTS
		pElan->ProtPktCount = pElan->MaxProtocolBufs;
		DBGP((1, "ProtPktCount %d\n", pElan->ProtPktCount));
#endif

		if (Status != NDIS_STATUS_SUCCESS)
		{
			break;
		}

		NdisAllocateBufferPool(
				&Status,
				&(pElan->ProtocolBufferPool),
				pElan->MaxProtocolBufs
				);

		if (Status != NDIS_STATUS_SUCCESS)
		{
			break;
		}

		//
		//  Allocate a big chunk of system memory that we can divide up into
		//  protocol buffers.
		//
		ALLOC_MEM(
				&(pElan->ProtocolBufTracker),
				(pElan->ProtocolBufSize * pElan->MaxProtocolBufs)
				);

		if (pElan->ProtocolBufTracker == (PUCHAR)NULL)
		{
			Status = NDIS_STATUS_RESOURCES;
			break;
		}

		Status = NDIS_STATUS_SUCCESS;

		//
		//  Make all protocol buffers free.
		//
		pSpace = pElan->ProtocolBufTracker;
		{
			PUCHAR	LinkPtr;

			LinkPtr = (PUCHAR)NULL;
			for (i = 0; i < pElan->MaxProtocolBufs; i++)
			{
				*((PUCHAR *)pSpace) = LinkPtr;
				LinkPtr = pSpace;
				pSpace += pElan->ProtocolBufSize;
			}
			pSpace -= pElan->ProtocolBufSize;
			pElan->ProtocolBufList = pSpace;
		}
	}
	while (FALSE);

	if (Status != NDIS_STATUS_SUCCESS)
	{
		//
		//  Undo everything.
		//
		AtmLaneDeallocateProtoBuffers(pElan);
	}

	TRACEOUT(InitProtoBuffers);
	
	return (Status);
}


VOID
AtmLaneDeallocateProtoBuffers(
	IN	PATMLANE_ELAN			pElan
)
/*++

Routine Description:

	Free the protocol buffer pool for an interface.

Arguments:

	pElan		- Pointer to ATMLANE elan structure

Return Value:

	None

--*/
{
	if (pElan->ProtocolPacketPool != (NDIS_HANDLE)NULL)
	{
		NdisFreePacketPool(pElan->ProtocolPacketPool);
		pElan->ProtocolPacketPool = NULL;
	}

	if (pElan->ProtocolBufferPool != (NDIS_HANDLE)NULL)
	{
		NdisFreeBufferPool(pElan->ProtocolBufferPool);
		pElan->ProtocolBufferPool = NULL;
	}

	if (pElan->ProtocolBufTracker != (PUCHAR)NULL)
	{
		FREE_MEM(pElan->ProtocolBufTracker);
		pElan->ProtocolBufTracker = (PUCHAR)NULL;
	}
}

VOID
AtmLaneLinkVcToAtmEntry(
	IN	PATMLANE_VC					pVc,
	IN	PATMLANE_ATM_ENTRY			pAtmEntry,
	IN	BOOLEAN						ServerIncoming
)
/*++

Routine Description:

	Link an ATMLANE VC to an ATM Entry. The caller is assumed to
	hold locks to both structures.

Arguments:

	pVc					- Pointer to ATMLANE VC structure
	pAtmEntry			- Pointer to ATMLANE ATM Entry structure
	ServerIncoming		- Incoming call from server 

Return Value:

	None

--*/
{
	PATMLANE_VC *		ppNext;
	PATMLANE_VC			pVcEntry;
	BOOLEAN				WasRunning;

	TRACEIN(LinkVcToAtmEntry);

	DBGP((2, "LinkVcToAtmEntry: pVc %x to pAtmEntry %x ServerIncoming %s\n",
			pVc, pAtmEntry, ServerIncoming?"TRUE":"FALSE"));

	//
	//  Back pointer from VC to ATM Entry.
	//
	pVc->pAtmEntry = pAtmEntry;
	
	//
	//	If server incoming connection cache the VC
	//	special location in the AtmEntry.
	//
	if (ServerIncoming)
	{
		pAtmEntry->pVcIncoming = pVc;
		pVc->pNextVc = NULL_PATMLANE_VC;
	}
	else
	{
		//
		//	Otherwise...
		//
		//	Add VC to the list in ascending calling party ATM address order
		//
		ppNext = &pAtmEntry->pVcList;
		while (*ppNext != NULL_PATMLANE_VC)
		{
			if (memcmp(
					&pVc->CallingAtmAddress.Address, 
					(*ppNext)->CallingAtmAddress.Address, 
					ATM_ADDRESS_LENGTH) < 0)
			{
				// 
				//	Calling address is less than existing VC.
				//
				break;
			}
			else
			{
				//
				//	Calling address is equal or greater than existing VC.
				//	Move on to next.
				//			
				ppNext = &((*ppNext)->pNextVc);
			}
		}

		//
		//  Found the place we were looking for. Insert the VC here.
		//
		pVc->pNextVc = *ppNext;
		*ppNext = pVc;

	}

	//
	//	Add the VC reference to the ATM entry.
	//
	AtmLaneReferenceAtmEntry(pAtmEntry, "vc");	// VC reference

	//
	//	Add the ATM Entry reference to the VC.
	//
	AtmLaneReferenceVc(pVc, "atm");

	//
	//	If this VC is not the first in the list, i.e., not the lowest
	//	calling party number, then set the timeout to the fast VC 
	//	timeout value.  This will get rid of redundant DataDirect VCs quickly 
	//	ONLY if they don't get used within the fast timeout period. 
	//	Otherwise the timeout handler to keep the VC and set
	//	the timeout to the normal C12-VccTimeout value.
	//
	if (pVc != pAtmEntry->pVcList)
	{
		pVc->AgingTime = FAST_VC_TIMEOUT;
	}

	TRACEOUT(LinkVcToAtmEntry);
}

BOOLEAN
AtmLaneUnlinkVcFromAtmEntry(
	IN	PATMLANE_VC					pVc
)
/*++

Routine Description:

	Unlink an ATMLANE VC from the ATM Entry it is linked to.
	The caller is assumed to hold a lock for the VC structure.

Arguments:

	pVc				- Pointer to ATMLANE VC structure

Return Value:

	TRUE if we found the VC linked to the list on the ATM entry, and unlinked it.

--*/
{
	PATMLANE_ATM_ENTRY			pAtmEntry;
	PATMLANE_MAC_ENTRY			pMacEntry, pNextMacEntry;
	ULONG						rc;
	PATMLANE_VC *				ppVc;
	BOOLEAN						Found;

	DBGP((3, "UnlinkVcFromAtmEntry: pVc %x from pAtmEntry %x\n",
			pVc, pVc->pAtmEntry));

	pAtmEntry = pVc->pAtmEntry;
	ASSERT(NULL_PATMLANE_ATM_ENTRY != pAtmEntry);
	
	pVc->pAtmEntry = NULL_PATMLANE_ATM_ENTRY;

	//
	//	Reacquire locks in the right order.
	//
	AtmLaneReferenceVc(pVc, "temp");
	RELEASE_VC_LOCK(pVc);
	ACQUIRE_ATM_ENTRY_LOCK(pAtmEntry);
	ACQUIRE_VC_LOCK(pVc);


	//
	//	VC is either a server incoming uni-directional connection,
	//	where it is linked to the AtmEntry via pVcIncoming, or a 
	// 	bi-directional connection that is in the pVcList.
	//
	if (pAtmEntry->pVcIncoming == pVc)
	{
		//
		//  If server incoming VC just remove single entry
		//
		pAtmEntry->pVcIncoming = NULL_PATMLANE_VC;
		Found = TRUE;
	}
	else
	{
		//
		//  Otherwise, find this VC in the ATM Entry's VC list
		//
		ppVc = &(pAtmEntry->pVcList);
		while (*ppVc != NULL_PATMLANE_VC && *ppVc != pVc)
		{
			ppVc = &((*ppVc)->pNextVc);
		}

		//
		//  Remove this VC by making it's predecessor in the list
		//	point to the next VC in the list.
		//
		if (*ppVc == pVc)
		{
			*ppVc = pVc->pNextVc;
			Found = TRUE;
		}
		else
		{
			Found = FALSE;
		}
	}

	rc = AtmLaneDereferenceVc(pVc, "temp");
	if (rc > 0)
	{
		RELEASE_VC_LOCK(pVc);
	}

	//
	//	If no more VC's in list mark AtmEntry as NOT connected
	//
	if (pAtmEntry->pVcList == NULL_PATMLANE_VC)
	{
		SET_FLAG(
				pAtmEntry->Flags,
				ATM_ENTRY_STATE_MASK,
				ATM_ENTRY_VALID);

		DBGP((2, "UnlinkVcFromAtmEntry: Aborting MAC Entries\n"));
		
		pMacEntry = pAtmEntry->pMacEntryList;

		//
		//  Take the MAC entry list out so that we can reference
		//  entries in this list in peace later on below.
		//
		pAtmEntry->pMacEntryList = NULL_PATMLANE_MAC_ENTRY;

		//
		//  Let go of the ATM entry lock while we abort all
		//  the MAC entries in the list above. The ATM entry
		//  won't go away because of the VC reference still on it.
		//  The MAC entries in the list won't go away since they
		//  have the ATM entry reference on them (see UnlinkMacEntry..).
		//
		RELEASE_ATM_ENTRY_LOCK(pAtmEntry);

		while (pMacEntry != NULL)
		{
			pNextMacEntry = pMacEntry->pNextToAtm;

			//
			//  Now abort the MAC Entry. Put this MAC entry back
			//  on the ATM entry's list so that it gets handled
			//  appropriately by AbortMacEntry.
			//
			ACQUIRE_MAC_ENTRY_LOCK(pMacEntry);

			ACQUIRE_ATM_ENTRY_LOCK_DPC(pAtmEntry);

			pMacEntry->pNextToAtm = pAtmEntry->pMacEntryList;
			pAtmEntry->pMacEntryList = pMacEntry;

			ASSERT(pMacEntry->pAtmEntry == pAtmEntry);

			RELEASE_ATM_ENTRY_LOCK_DPC(pAtmEntry);

			AtmLaneAbortMacEntry(pMacEntry);
			//	MacEntry lock released in above

			pMacEntry = pNextMacEntry;
		}

		ACQUIRE_ATM_ENTRY_LOCK(pAtmEntry);

	}

	rc = AtmLaneDereferenceAtmEntry(pAtmEntry, "vc"); // VC reference
	if (rc > 0)	
	{
		RELEASE_ATM_ENTRY_LOCK(pAtmEntry);
	}
	//
	//  else the ATM Entry is gone!
	//

	//
	//  Acquire the VC lock again for the caller's sake
	//
	ACQUIRE_VC_LOCK(pVc);
	return (Found);
}

BOOLEAN
AtmLaneUnlinkMacEntryFromAtmEntry(
	IN	PATMLANE_MAC_ENTRY			pMacEntry
)
/*++

Routine Description:

	Unlink a Mac Entry from the ATM Entry it is linked to.
	Allow for the MAC entry to be absent in the ATM Entry's list.
	The caller is assumed to hold a lock for the Mac Entry.

Arguments:

	pMacEntry			- Pointer to Mac Entry to be unlinked.

Return Value:

	TRUE iff the MAC entry was found and unlinked.

--*/
{
	PATMLANE_ATM_ENTRY		pAtmEntry;
	PATMLANE_MAC_ENTRY *	ppNextMacEntry;
	ULONG					rc;				// Ref Count on ATM Entry
	BOOLEAN					bFound = FALSE;

	pAtmEntry = pMacEntry->pAtmEntry;
	ASSERT(pAtmEntry != NULL_PATMLANE_ATM_ENTRY);

	DBGP((2, "%d UnlinkMacEntryFromAtmEntry: MacEntry %x AtmEntry %x\n",
			pAtmEntry->pElan->ElanNumber,
			pMacEntry, pMacEntry->pAtmEntry));

	ACQUIRE_ATM_ENTRY_LOCK(pAtmEntry);

	//
	//  Locate the position of this MAC Entry in the ATM Entry's list.
	//
	ppNextMacEntry = &(pAtmEntry->pMacEntryList);

	while (*ppNextMacEntry != NULL_PATMLANE_MAC_ENTRY)
	{
		if (*ppNextMacEntry == pMacEntry)
		{
			//
			//  Found it.
			//
			bFound = TRUE;
			break;
		}
		else
		{
			ppNextMacEntry = &((*ppNextMacEntry)->pNextToAtm);
		}
	}

	if (bFound)
	{
		//
		//  Make the predecessor point to the next entry.
		//
		*ppNextMacEntry = pMacEntry->pNextToAtm;

		rc = AtmLaneDereferenceAtmEntry(pAtmEntry, "mac");	// MAC entry reference
		if (rc != 0)
		{
			RELEASE_ATM_ENTRY_LOCK(pAtmEntry);
		}
		//
		//  else the ATM Entry is gone.
		//
	}
	else
	{
		//
		//  The entry wasn't found.
		//
		RELEASE_ATM_ENTRY_LOCK(pAtmEntry);
	}

	return bFound;
}


VOID
AtmLaneStartTimer(
	IN	PATMLANE_ELAN				pElan,
	IN	PATMLANE_TIMER				pTimer,
	IN	ATMLANE_TIMEOUT_HANDLER		TimeoutHandler,
	IN	ULONG						SecondsToGo,
	IN	PVOID						ContextPtr
)
/*++

Routine Description:

	Start an ATMLANE timer. Based on the length (SecondsToGo) of the
	timer, we decide on whether to insert it in the short duration
	timer list or in the long duration timer list in the Elan
	structure.

	NOTE: the caller is assumed to either hold a lock to the structure
	that contains the timer, or ensure that it is safe to access the
	timer structure.

Arguments:

	pElan			- Pointer to the ATMLANE Elan
	pTimer			- Pointer to ATMLANE Timer structure
	TimeoutHandler	- Handler function to be called if this timer expires
	SecondsToGo		- When does this timer go off?
	ContextPtr		- To be passed to timeout handler if this timer expires
	ContextValue	- To be passed to timeout handler if this timer expires
	
Return Value:

	None

--*/
{
	PATMLANE_TIMER_LIST	pTimerList;		// List to which this timer goes
	PATMLANE_TIMER		pTimerListHead; // Head of above list
	ULONG				Index;			// Into timer wheel
	ULONG				TicksToGo;
	INT					i;

	TRACEIN(StartTimer);

	STRUCT_ASSERT(pElan, atmlane_elan);

	DBGP((5,
		"StartTimer: pElan %x, Secs %d, Handler %x, Ctxtp %x, pTimer %x\n",
 	 			pElan, SecondsToGo, TimeoutHandler, ContextPtr, pTimer));

	if (IS_TIMER_ACTIVE(pTimer))
	{
		DBGP((5, 
		"Start timer: pTimer %x: is active (list %x, hndlr %x), stopping it\n",
				pTimer, pTimer->pTimerList, pTimer->TimeoutHandler));
		AtmLaneStopTimer(pTimer, pElan);
	}

	ACQUIRE_ELAN_TIMER_LOCK(pElan);
	
	ASSERT(!IS_TIMER_ACTIVE(pTimer));

	//
	//  Find the list to which this timer should go, and the
	//  offset (TicksToGo)
	//
Try_Again:
	for (i = 0; i < ALT_CLASS_MAX; i++)
	{
		pTimerList = &(pElan->TimerList[i]);
		if (SecondsToGo <= pTimerList->MaxTimer)
		{
			//
			//  Found it.
			//
			TicksToGo = SecondsToGo / (pTimerList->TimerPeriod);
			if (TicksToGo >= 1)
				TicksToGo--;
			break;
		}
	}
	
	if (i == ALT_CLASS_MAX)
	{
		//
		//  Force this timer down!
		//
		SecondsToGo = pTimerList->MaxTimer;
		goto Try_Again;
	}


	//
	//  Find the position in the list for this timer
	//
	Index = pTimerList->CurrentTick + TicksToGo;
	if (Index >= pTimerList->TimerListSize)
	{
		Index -= pTimerList->TimerListSize;
	}
	ASSERT(Index < pTimerList->TimerListSize);

	pTimerListHead = &(pTimerList->pTimers[Index]);

	//
	//  Fill in the timer
	//
	pTimer->pTimerList = pTimerList;
	pTimer->LastRefreshTime = pTimerList->CurrentTick;
	pTimer->Duration = TicksToGo;
	pTimer->TimeoutHandler = TimeoutHandler;
	pTimer->ContextPtr = ContextPtr;
 
 	//
 	//  Insert this timer in the "ticking" list
 	//
 	pTimer->pPrevTimer = pTimerListHead;
 	pTimer->pNextTimer = pTimerListHead->pNextTimer;
 	if (pTimer->pNextTimer != NULL_PATMLANE_TIMER)
 	{
 		pTimer->pNextTimer->pPrevTimer = pTimer;
 	}
 	pTimerListHead->pNextTimer = pTimer;

	//
	//  Start off the system tick timer if necessary.
	//
	pTimerList->TimerCount++;
	if (pTimerList->TimerCount == 1)
	{
		DBGP((5,
			"StartTimer: Starting system timer %x, class %d on Elan %x\n",
					&(pTimerList->NdisTimer), i, pElan));

		START_SYSTEM_TIMER(&(pTimerList->NdisTimer), pTimerList->TimerPeriod);
	}

	RELEASE_ELAN_TIMER_LOCK(pElan);

	//
	//  We're done
	//
	DBGP((5,
		"Started timer %x, Elan %x, Secs %d, Index %d, Head %x\n",
				pTimer,
				pElan,
				SecondsToGo,
				Index,
				pTimerListHead));

	TRACEOUT(StartTimer);

	return;
}


BOOLEAN
AtmLaneStopTimer(
	IN	PATMLANE_TIMER			pTimer,
	IN	PATMLANE_ELAN			pElan
)
/*++

Routine Description:

	Stop an ATMLANE timer, if it is running. We remove this timer from
	the active timer list and mark it so that we know it's not running.

	NOTE: the caller is assumed to either hold a lock to the structure
	that contains the timer, or ensure that it is safe to access the
	timer structure.

	SIDE EFFECT: If we happen to stop the last timer (of this "duration") on
	the Interface, we also stop the appropriate Tick function.

Arguments:

	pTimer			- Pointer to ATMLANE Timer structure
	pElan			- Pointer to interface to which the timer belongs

Return Value:

	TRUE if the timer was running, FALSE otherwise.

--*/
{
	PATMLANE_TIMER_LIST	pTimerList;			// Timer List to which this timer belongs
	BOOLEAN				WasRunning;

	TRACEIN(StopTimer);

	DBGP((5,
		"Stopping Timer %x, Elan %x, List %x, Prev %x, Next %x\n",
					pTimer,
					pElan,
					pTimer->pTimerList,
					pTimer->pPrevTimer,
					pTimer->pNextTimer));

	ACQUIRE_ELAN_TIMER_LOCK(pElan);

	if (IS_TIMER_ACTIVE(pTimer))
	{
		WasRunning = TRUE;

		//
		//  Unlink timer from the list
		//
		ASSERT(pTimer->pPrevTimer);	// the list head always exists

		pTimer->pPrevTimer->pNextTimer = pTimer->pNextTimer;
		if (pTimer->pNextTimer)
		{
			pTimer->pNextTimer->pPrevTimer = pTimer->pPrevTimer;
		}

		pTimer->pNextTimer = pTimer->pPrevTimer = NULL_PATMLANE_TIMER;

		//
		//  Update timer count on Interface, for this class of timers
		//
		pTimerList = pTimer->pTimerList;
		pTimerList->TimerCount--;

		//
		//  If all timers of this class are gone, stop the system tick timer
		//  for this class
		//
		if (pTimerList->TimerCount == 0)
		{
		DBGP((5,
			"Stopping system timer %x, List %x, Elan %x\n",
						&(pTimerList->NdisTimer),
						pTimerList,
						pElan));

			pTimerList->CurrentTick = 0;
			STOP_SYSTEM_TIMER(&(pTimerList->NdisTimer));
		}

		//
		//  Mark stopped timer as not active
		//
		pTimer->pTimerList = (PATMLANE_TIMER_LIST)NULL;

	}
	else
	{
		WasRunning = FALSE;
	}

	RELEASE_ELAN_TIMER_LOCK(pElan);

	TRACEOUT(StopTimer);

	return (WasRunning);
}




VOID
AtmLaneRefreshTimer(
	IN	PATMLANE_TIMER				pTimer
)
/*++

Routine Description:

	Refresh a timer that is already running.

	NOTE: The caller is assumed to possess a lock protecting the
	timer structure (i.e. to the structure containing the timer).

	NOTE: We don't acquire the IF Timer Lock here, to optimize
	the refresh operation. So, _within_ the confines of this routine,
	the tick handler may fire, and expire this timer. The only care
	that we take here is to make sure that we don't crash if the
	timer expires while we access the Timer list.

Arguments:

	pTimer		- Pointer to ATMLANE_TIMER structure

Return Value:

	None

--*/
{
	PATMLANE_TIMER_LIST	pTimerList;

	TRACEIN(RefreshTimer);

	if ((pTimerList = pTimer->pTimerList) != (PATMLANE_TIMER_LIST)NULL)
	{
		pTimer->LastRefreshTime = pTimerList->CurrentTick;
	}
	else
	{
		DBGP((5,
			"RefreshTimer: pTimer %x not active: Hnd %x, Ctxtp %x\n",
			 	pTimer,
			 	pTimer->TimeoutHandler,
			 	pTimer->ContextPtr
			 ));
	}

	DBGP((5,
		"Refreshed timer %x, List %x, hnd %x, Ctxtp %x, LastRefresh %d\n",
				pTimer,
				pTimer->pTimerList,
				pTimer->TimeoutHandler,
				pTimer->ContextPtr,
				pTimer->LastRefreshTime));

	TRACEOUT(RefreshTimer);

	return;
}


VOID
AtmLaneTickHandler(
	IN	PVOID						SystemSpecific1,
	IN	PVOID						Context,
	IN	PVOID						SystemSpecific2,
	IN	PVOID						SystemSpecific3
)
/*++

Routine Description:

	This is the handler we register with the system for processing each
	Timer List. This is called every "tick" seconds, where "tick" is
	determined by the granularity of the timer type.

Arguments:

	Context				- Actually a pointer to a Timer List structure
	SystemSpecific[1-3]	- Not used

Return Value:

	None

--*/
{

	PATMLANE_ELAN			pElan;
	PATMLANE_TIMER_LIST		pTimerList;

	PATMLANE_TIMER			pExpiredTimer;		// Start of list of expired timers
	PATMLANE_TIMER			pNextTimer;			// for walking above list
	PATMLANE_TIMER			pTimer;				// temp, for walking timer list
	PATMLANE_TIMER			pPrevExpiredTimer;	// for creating expired timer list

	ULONG					Index;				// into the timer wheel
	ULONG					NewIndex;			// for refreshed timers

	TRACEIN(TickHandler);

	pTimerList = (PATMLANE_TIMER_LIST)Context;
	STRUCT_ASSERT(pTimerList, atmlane_timerlist);

	pElan = (PATMLANE_ELAN)pTimerList->ListContext;
	STRUCT_ASSERT(pElan, atmlane_elan);

	DBGP((5,
		"Tick: pElan %x, List %x, Count %d\n",
		pElan, pTimerList, pTimerList->TimerCount));

	pExpiredTimer = NULL_PATMLANE_TIMER;

	ACQUIRE_ELAN_TIMER_LOCK(pElan);

	if (ELAN_STATE_OPERATIONAL == pElan->AdminState)
	{
		//
		//  Pick up the list of timers scheduled to have expired at the
		//  current tick. Some of these might have been refreshed.
		//
		Index = pTimerList->CurrentTick;
		pExpiredTimer = (pTimerList->pTimers[Index]).pNextTimer;
		(pTimerList->pTimers[Index]).pNextTimer = NULL_PATMLANE_TIMER;

		//
		//  Go through the list of timers scheduled to expire at this tick.
		//  Prepare a list of expired timers, using the pNextExpiredTimer
		//  link to chain them together.
		//
		//  Some timers may have been refreshed, in which case we reinsert
		//  them in the active timer list.
		//
		pPrevExpiredTimer = NULL_PATMLANE_TIMER;

		for (pTimer = pExpiredTimer;
		 	pTimer != NULL_PATMLANE_TIMER;
		 	pTimer = pNextTimer)
		{
			//
			// Save a pointer to the next timer, for the next iteration.
			//
			pNextTimer = pTimer->pNextTimer;

			DBGP((5,
				"Tick Handler: pElan %x, looking at timer %x, next %x\n",
					pElan, pTimer, pNextTimer));

			//
			//  Find out when this timer should actually expire.
			//
			NewIndex = pTimer->LastRefreshTime + pTimer->Duration;
			if (NewIndex >= pTimerList->TimerListSize)
			{
				NewIndex -= pTimerList->TimerListSize;
			}

			//
			//  Check if we are currently at the point of expiry.
			//
			if (NewIndex != Index)
			{
				//
				//  This timer still has some way to go, so put it back.
				//
				DBGP((5,
				"Tick: Reinserting Timer %x: Hnd %x, Durn %d, Ind %d, NewInd %d\n",
					pTimer, pTimer->TimeoutHandler, pTimer->Duration, Index, NewIndex));

				//
				//  Remove it from the expired timer list. Note that we only
				//  need to update the forward (pNextExpiredTimer) links.
				//
				if (pPrevExpiredTimer == NULL_PATMLANE_TIMER)
				{
					pExpiredTimer = pNextTimer;
				}
				else
				{
					pPrevExpiredTimer->pNextExpiredTimer = pNextTimer;
				}

				//
				//  And insert it back into the running timer list.
				//
				pTimer->pNextTimer = (pTimerList->pTimers[NewIndex]).pNextTimer;
				if (pTimer->pNextTimer != NULL_PATMLANE_TIMER)
				{
					pTimer->pNextTimer->pPrevTimer = pTimer;
				}
				pTimer->pPrevTimer = &(pTimerList->pTimers[NewIndex]);
				(pTimerList->pTimers[NewIndex]).pNextTimer = pTimer;
			}
			else
			{
				//
				//  This one has expired. Keep it in the expired timer list.
				//
				pTimer->pNextExpiredTimer = pNextTimer;
				if (pPrevExpiredTimer == NULL_PATMLANE_TIMER)
				{
					pExpiredTimer = pTimer;
				}
				pPrevExpiredTimer = pTimer;

				//
				//  Mark it as inactive.
				//
				ASSERT(pTimer->pTimerList == pTimerList);
				pTimer->pTimerList = (PATMLANE_TIMER_LIST)NULL;

				//
				//  Update the active timer count.
				//
				pTimerList->TimerCount--;
			}
		}

		//
		//  Update current tick index in readiness for the next tick.
		//
		if (++Index == pTimerList->TimerListSize)
		{
			pTimerList->CurrentTick = 0;
		}
		else
		{
			pTimerList->CurrentTick = Index;
		}

		if (pTimerList->TimerCount > 0)
		{
			//
			//  Re-arm the tick handler
			//
			DBGP((5,
				"Tick[%d]: Starting system timer %x, on Elan %x\n",
						pTimerList->CurrentTick, &(pTimerList->NdisTimer), pElan));
			
			START_SYSTEM_TIMER(&(pTimerList->NdisTimer), pTimerList->TimerPeriod);
		}
		else
		{
			pTimerList->CurrentTick = 0;
		}

	}

	RELEASE_ELAN_TIMER_LOCK(pElan);

	//
	//  Now pExpiredTimer is a list of expired timers.
	//  Walk through the list and call the timeout handlers
	//  for each timer.
	//
	while (pExpiredTimer != NULL_PATMLANE_TIMER)
	{
		pNextTimer = pExpiredTimer->pNextExpiredTimer;

		DBGP((5,
			"Expired timer %x: handler %x, next %x\n",
					pExpiredTimer, pExpiredTimer->TimeoutHandler, pNextTimer));

		(*(pExpiredTimer->TimeoutHandler))(
				pExpiredTimer,
				pExpiredTimer->ContextPtr
			);

		pExpiredTimer = pNextTimer;
	}


	TRACEOUT(TickHandler);

	return;
}

ULONG
AtmLaneSystemTimeMs(void)
/*++

Routine Description:

	This routine get the current system clock tick value and
	returns this value converted to milliseconds.
	
Arguments:

	None
	
Return Value:

	The system clock value in milliseconds.

--*/
{
#if BINARY_COMPATIBLE
    LARGE_INTEGER SystemTime;

    NdisGetCurrentSystemTime(&SystemTime);
    
    // comes back in 100 nanosecond units, we want milliseconds

	SystemTime.QuadPart /= 10000;
    
    return SystemTime.LowPart;
#else
	static LARGE_INTEGER Frequency = {0L,0L};
    LARGE_INTEGER SystemTime;

	SystemTime = KeQueryPerformanceCounter(Frequency.LowPart == 0?&Frequency:NULL);

	SystemTime.QuadPart = SystemTime.QuadPart * 1000000 / Frequency.QuadPart;

    return SystemTime.LowPart;
#endif
}


VOID
AtmLaneBitSwapMacAddr(
	IN OUT	PUCHAR		ap
)
/*++

Routine Description:

	This routine swaps (reverses) the bits in each individual
	byte of a MAC Address.  Use for Token Ring MAC addresses.
	
Arguments:

	ap		-	Pointer to array of bytes to bitswap in-place.
	
Return Value:

	None
	
--*/
{
	int 			i;
	unsigned int 	x;

    for (i = 0; i != 6; i++) 
    {
		x = ap[i];
		x = ((x & 0xaau) >> 1) | ((x & 0x55u) << 1);
		x = ((x & 0xccu) >> 2) | ((x & 0x33u) << 2);
		x = ((x & 0xf0u) >> 4) | ((x & 0x0fu) << 4);
		ap[i] = (UCHAR)x;
    }
}

BOOLEAN
AtmLaneCopyUnicodeString(
	IN OUT	PUNICODE_STRING pDestString,
	IN OUT	PUNICODE_STRING pSrcString,
	IN		BOOLEAN			AllocDest,
	IN		BOOLEAN			ConvertToUpper
)
{
/*++

Routine Description:

	This routine optionally allocates space in the destination string
	for the source string plus a terminating null.  It
	copies the source string to the destination string and 
	terminates the destination string with a null.
	
-*/
	BOOLEAN Result 		= TRUE;

	TRACEIN(CopyUnicodeString);

	do
	{
		//	Alloc space for the destination string if requested

		if (AllocDest)
		{
			ALLOC_MEM(&(pDestString->Buffer), pSrcString->Length + sizeof(WCHAR));
			if (NULL == pDestString->Buffer)
			{
				Result = FALSE;
				break;
			}

			//	Init lengths in dest string

			pDestString->Length = 0;
			pDestString->MaximumLength = pSrcString->Length + sizeof(WCHAR);
		}
		
		//	Copy the string

		if (ConvertToUpper)
		{
#ifndef LANE_WIN98
			(VOID)NdisUpcaseUnicodeString(pDestString, pSrcString);
#else
			memcpy(pDestString->Buffer, pSrcString->Buffer, pSrcString->Length);
#endif // LANE_WIN98
		}
		else
		{
			RtlCopyUnicodeString(pDestString, pSrcString);
		}

		//	Null terminate the dest string

		if (pDestString->Length < pDestString->MaximumLength)
		{
			pDestString->Buffer[pDestString->Length/sizeof(WCHAR)] = ((WCHAR)0);
		}
		else
		{
			pDestString->Buffer[(pDestString->MaximumLength - sizeof(WCHAR))/sizeof(WCHAR)] =
				((WCHAR)0);
		}
	
	} while (FALSE);

	TRACEOUT(CopyUnicodeString);
	return Result;
}

PWSTR
AtmLaneStrTok(
	IN	PWSTR	StrToken,
	IN	WCHAR	ChrDelim,
	OUT	PUSHORT	pStrLength
)
{
	static PWSTR 	StrSave = NULL;
	USHORT			StrLength = 0;	
	PWSTR 			StrOut = NULL;

	TRACEIN(StrTok);
	do
	{
		//	check for bad input
	
		if ((StrToken == NULL && StrSave == NULL) ||
			ChrDelim == ((WCHAR)0))
		{
			break;
		}

		//	if starting with new string, reset StrSave

		if (StrToken != NULL)
		{
			StrSave = StrToken;
		}

		//	token starts at start of current string

		StrOut = StrSave;

		//	walk string until delimiter or NULL
		
		while (*StrSave != ChrDelim && *StrSave != ((WCHAR)0))
		{
			StrSave++;
			StrLength++;
		}

		//	If we found a delimiter then NULL it out and
		//	move saved ptr to next token to setup for next 
		//	call on same string.  
		
		if (*StrSave == ChrDelim)
		{
			*StrSave = ((WCHAR)0);
			StrSave++;
		}

		//	If pointing at empty string then return null ptr
	
		if (*StrOut == ((WCHAR)0))
		{
			StrOut = NULL;
		}
		
	} while (FALSE);

	TRACEOUT(StrTok);
	*pStrLength = StrLength * sizeof(WCHAR);
	return StrOut;
}