/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

	atp.c

Abstract:

	This module contains the Appletalk Transaction Protocol code.

Author:

	Jameel Hyder (jameelh@microsoft.com)
	Nikhil Kamkolkar (nikhilk@microsoft.com)

Revision History:
	19 Jun 1992		Initial Version

Notes:	Tab stop: 4

	25 Mar 1994		JH - Changed the request response paradigm. It now works as follows:
					When a request comes in, a response structure is allocated, initialized
					and linked into the address object either in the hash table if it is a
					XO request or the ALO linear list if it an ALO.
					The GetReq handler is passed a pointer to the response structure. This
					is referenced for the GetReq handler. The GetReq handler must Dereference
					it explicity either in its release handler if a response was posted or
					after a CancelResp is called.

					The respDeref notifies the release handler when the reference goes to 1
					and frees it up when it goes to zero.

					The GetReq structure is now re-used if the handler so specifies. This
					avoids the free-ing and re-allocing of these structures as well as
					the need to call AtalkAtpGetReq() from within the handler.

					Retry and release timers are per-atp-address now instead of one per
					request and one per response. The release handler is not 'started'
					till a response is posted.
--*/

#define	ATP_LOCALS
#define	FILENUM	  	ATP
#include <atalk.h>
#pragma hdrstop

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE_PAP, AtalkAtpOpenAddress)	// Since PAP is the only one which calls
													// at DISPATCH_LEVEL
#pragma alloc_text(PAGE_ATP, AtalkAtpCleanupAddress)
#pragma alloc_text(PAGE_ATP, AtalkAtpCloseAddress)
#pragma alloc_text(PAGE_ATP, AtalkAtpPostReq)
#pragma alloc_text(PAGE_ATP, AtalkAtpSetReqHandler)
#pragma alloc_text(PAGE_ATP, AtalkAtpPostResp)
#pragma alloc_text(PAGE_ATP, AtalkAtpCancelReq)
#pragma alloc_text(PAGE_ATP, AtalkAtpIsReqComplete)
#pragma alloc_text(PAGE_ATP, AtalkAtpCancelResp)
#pragma alloc_text(PAGE_ATP, AtalkAtpCancelRespByTid)
#pragma alloc_text(PAGE_ATP, AtalkAtpPacketIn)
#pragma alloc_text(PAGE_ATP, atalkAtpTransmitReq)
#pragma alloc_text(PAGE_ATP, atalkAtpSendReqComplete)
#pragma alloc_text(PAGE_ATP, atalkAtpTransmitResp)
#pragma alloc_text(PAGE_ATP, atalkAtpSendRespComplete)
#pragma alloc_text(PAGE_ATP, atalkAtpTransmitRel)
#pragma alloc_text(PAGE_ATP, atalkAtpSendRelComplete)
#pragma alloc_text(PAGE_ATP, atalkAtpRespComplete)
#pragma alloc_text(PAGE_ATP, atalkAtpReqComplete)
#pragma alloc_text(PAGE_ATP, atalkAtpGetNextTidForAddr)
#pragma alloc_text(PAGE_ATP, atalkAtpReqRefNextNc)
#pragma alloc_text(PAGE_ATP, atalkAtpReqDeref)
#pragma alloc_text(PAGE_ATP, atalkAtpRespRefNextNc)
#pragma alloc_text(PAGE_ATP, AtalkAtpRespDeref)
#pragma alloc_text(PAGE_ATP, atalkAtpReqTimer)
#pragma alloc_text(PAGE_ATP, atalkAtpRelTimer)
#pragma alloc_text(PAGE_ATP, AtalkAtpGenericRespComplete)

#endif

ATALK_ERROR
AtalkAtpOpenAddress(
	IN		PPORT_DESCRIPTOR		pPort,
	IN		BYTE					Socket,
	IN OUT	PATALK_NODEADDR			pDesiredNode		OPTIONAL,
	IN		USHORT					MaxSinglePktSize,
	IN		BOOLEAN					SendUserBytesAll,
	IN		PATALK_DEV_CTX			pDevCtx				OPTIONAL,
	IN		BOOLEAN					CacheSocket,
	OUT		PATP_ADDROBJ	*		ppAtpAddr
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PATP_ADDROBJ	pAtpAddr;
	ATALK_ERROR		error;

	do
	{
		if ((pAtpAddr = AtalkAllocZeroedMemory(sizeof(ATP_ADDROBJ))) == NULL)
		{
			error = ATALK_RESR_MEM;
			break;
		}

		//	Initialize this structure. Note that packet handler could
		//	entered with this context even before secondary initialization
		//	completes. So we make sure, that it will not touch anything
		//	until then by using the OPEN flag.

#if DBG
		pAtpAddr->atpao_Signature = ATPAO_SIGNATURE;
#endif

		//	Set creation reference count, include one each for release and retry timers
		pAtpAddr->atpao_RefCount 			= (CacheSocket ? 4 : 3);
		pAtpAddr->atpao_NextTid				= 1;
		pAtpAddr->atpao_MaxSinglePktSize 	= MaxSinglePktSize;
		pAtpAddr->atpao_DevCtx 				= pDevCtx;

		if (SendUserBytesAll)
		{
			pAtpAddr->atpao_Flags |= ATPAO_SENDUSERBYTESALL;
		}

		InitializeListHead(&pAtpAddr->atpao_ReqList);
		AtalkTimerInitialize(&pAtpAddr->atpao_RelTimer,
							 atalkAtpRelTimer,
							 ATP_RELEASE_TIMER_INTERVAL);

		InitializeListHead(&pAtpAddr->atpao_RespList);
		AtalkTimerInitialize(&pAtpAddr->atpao_RetryTimer,
							 atalkAtpReqTimer,
							 ATP_RETRY_TIMER_INTERVAL);

		//	Open the ddp socket
		error = AtalkDdpOpenAddress(pPort,
									Socket,
									pDesiredNode,
									AtalkAtpPacketIn,
									pAtpAddr,
									DDPPROTO_ANY,
									pDevCtx,
									&pAtpAddr->atpao_DdpAddr);

		if (!ATALK_SUCCESS(error))
		{
			//	Socket open error will be logged at the ddp level.
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
					("AtalkAtpOpenAddress: AtalkDdpOpenAddress failed %ld\n", error));

			AtalkFreeMemory(pAtpAddr);
			break;
		}

		//	Activate the atp socket. Cache the socket if desired.
		//	This takes port lock on default port.
		if (CacheSocket)
		{
			if (!ATALK_SUCCESS(AtalkIndAtpCacheSocket(pAtpAddr, pPort)))
			{
				pAtpAddr->atpao_RefCount--;
				CacheSocket = FALSE;
			}
		}
		pAtpAddr->atpao_Flags |= (ATPAO_OPEN | ATPAO_TIMERS | (CacheSocket ? ATPAO_CACHED : 0));

		AtalkLockAtpIfNecessary();

		// Start the release timer for responses on this address
		AtalkTimerScheduleEvent(&pAtpAddr->atpao_RelTimer);

		// Start the retry timer for requests on this address
		AtalkTimerScheduleEvent(&pAtpAddr->atpao_RetryTimer);

		*ppAtpAddr = pAtpAddr;
	} while (FALSE);

	return error;
}




ATALK_ERROR
AtalkAtpCleanupAddress(
	IN	PATP_ADDROBJ			pAtpAddr
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PATP_REQ		pAtpReq, pAtpReqNext;
	PATP_RESP		pAtpResp, pAtpRespNext;
	ATP_REQ_HANDLER	ReqHandler;
	ATALK_ERROR		error = ATALK_PENDING;
	KIRQL			OldIrql;
	USHORT			i;
	BOOLEAN			cached, CancelTimers, done, ReEnqueue;

	ACQUIRE_SPIN_LOCK(&pAtpAddr->atpao_Lock, &OldIrql);

	CancelTimers = FALSE;
	done = FALSE;
	if (pAtpAddr->atpao_Flags & ATPAO_TIMERS)
	{
		pAtpAddr->atpao_Flags &= ~ATPAO_TIMERS;
		CancelTimers = TRUE;
	}

	if (pAtpAddr->atpao_Flags & ATPAO_CLEANUP)
	{
		done = TRUE;
	}
	pAtpAddr->atpao_Flags |= ATPAO_CLEANUP;

	RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);

	if (done)
	{
		return error;
	}

	if (CancelTimers)
	{
		//	Cancel the release timer
		if (AtalkTimerCancelEvent(&pAtpAddr->atpao_RelTimer, FALSE))
		{
			AtalkAtpAddrDereference(pAtpAddr);
		}

		//	And also the retry timer
		if (AtalkTimerCancelEvent(&pAtpAddr->atpao_RetryTimer, FALSE))
		{
			AtalkAtpAddrDereference(pAtpAddr);
		}
	}

	ASSERT (pAtpAddr->atpao_RefCount >= 1);	// creation ref

	ACQUIRE_SPIN_LOCK(&pAtpAddr->atpao_Lock, &OldIrql);

	//	Call requests handler if set
	if ((ReqHandler = pAtpAddr->atpao_ReqHandler) != NULL)
	{
		pAtpAddr->atpao_ReqHandler = NULL;

		RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);

		(*ReqHandler)(ATALK_ATP_CLOSING,
					  pAtpAddr->atpao_ReqCtx,
					  NULL,
					  NULL,
					  0,
					  NULL,
					  NULL);

		//	Dereference address object.
		AtalkAtpAddrDereference(pAtpAddr);

		ACQUIRE_SPIN_LOCK(&pAtpAddr->atpao_Lock, &OldIrql);
	}

	//	Cancel all the requests.
	for (i = 0; i < ATP_REQ_HASH_SIZE; i++)
	{
		if ((pAtpReq = pAtpAddr->atpao_ReqHash[i]) == NULL)
		{
			//	If empty, go on to the next index in hash table.
			continue;
		}

		//	Includes the one we are starting with.
		atalkAtpReqRefNextNc(pAtpReq, &pAtpReqNext, &error);
		if (!ATALK_SUCCESS(error))
		{
			//	No requests left on this index. Go to the next one.
			continue;
		}

		while (TRUE)
		{
			if ((pAtpReq = pAtpReqNext) == NULL)
			{
				break;
			}

			if ((pAtpReqNext = pAtpReq->req_Next) != NULL)
			{
				atalkAtpReqRefNextNc(pAtpReq->req_Next, &pAtpReqNext, &error);
				if (!ATALK_SUCCESS(error))
				{
					//	No requests left on this index. Go to the next one.
					pAtpReqNext = NULL;
				}
			}

			//	Cancel this request.
			RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);

			AtalkAtpCancelReq(pAtpAddr,
							  pAtpReq->req_Tid,
							  &pAtpReq->req_Dest);

			ASSERTMSG("RefCount incorrect\n", (pAtpReq->req_RefCount >= 1));

			AtalkAtpReqDereference(pAtpReq);
			ACQUIRE_SPIN_LOCK(&pAtpAddr->atpao_Lock, &OldIrql);
		}
	}

	//	Cancel all pending responses.
	for (i = 0; i < ATP_RESP_HASH_SIZE; i++)
	{
		if ((pAtpResp = pAtpAddr->atpao_RespHash[i]) == NULL)
		{
			//	If empty, go on to the next index in hash table.
			continue;
		}

		//	Includes the one we are starting with.
		atalkAtpRespRefNextNc(pAtpResp, &pAtpRespNext, &error);
		if (!ATALK_SUCCESS(error))
		{
			//	No requests left on this index. Go to the next one.
			continue;
		}

		while (TRUE)
		{
			if ((pAtpResp = pAtpRespNext) == NULL)
			{
				break;
			}

			if ((pAtpRespNext = pAtpResp->resp_Next) != NULL)
			{
				atalkAtpRespRefNextNc(pAtpResp->resp_Next, &pAtpRespNext, &error);
				if (!ATALK_SUCCESS(error))
				{
					//	No requests left on this index. Go to the next one.
					pAtpRespNext = NULL;
				}
			}

			//	Cancel this response
			RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);
			AtalkAtpCancelResp(pAtpResp);
			ACQUIRE_SPIN_LOCK(&pAtpAddr->atpao_Lock, &OldIrql);
		}
	}

	//	if the socket was cached, uncache it, remove reference.
	cached = FALSE;
	if (pAtpAddr->atpao_Flags & ATPAO_CACHED)
	{
		cached = TRUE;
		pAtpAddr->atpao_Flags &= ~ATPAO_CACHED;
	}

	RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);

	if (cached)
	{
		AtalkIndAtpUnCacheSocket(pAtpAddr);
		AtalkAtpAddrDereference(pAtpAddr);
	}

	return error;
}




ATALK_ERROR
AtalkAtpCloseAddress(
	IN	PATP_ADDROBJ			pAtpAddr,
	IN	ATPAO_CLOSECOMPLETION	pCloseCmp	OPTIONAL,
	IN	PVOID					pCloseCtx	OPTIONAL
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	KIRQL			OldIrql;
	BOOLEAN			cleanup;

	//	Cancel all the pending get requests.
	ACQUIRE_SPIN_LOCK(&pAtpAddr->atpao_Lock, &OldIrql);
	if ((pAtpAddr->atpao_Flags & ATPAO_CLOSING) == 0)
	{
		cleanup = TRUE;
		if (pAtpAddr->atpao_Flags & ATPAO_CLEANUP)
			cleanup = FALSE;

		pAtpAddr->atpao_Flags |= ATPAO_CLOSING;
		pAtpAddr->atpao_CloseComp = pCloseCmp;
		pAtpAddr->atpao_CloseCtx = pCloseCtx;
		RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);

		if (cleanup)
			AtalkAtpCleanupAddress(pAtpAddr);

		//	Remove the creation reference
		AtalkAtpAddrDereference(pAtpAddr);
	}
	else
	{
		//	We are already closing! this should never happen!
		ASSERT ((pAtpAddr->atpao_Flags & ATPAO_CLOSING) != 0);
		KeBugCheck(0);
	}

	return ATALK_PENDING;
}




ATALK_ERROR
AtalkAtpPostReq(
	IN		PATP_ADDROBJ			pAtpAddr,
	IN		PATALK_ADDR				pDest,
	OUT		PUSHORT					pTid,
	IN		USHORT					Flags,
	IN		PAMDL					pReq,
	IN		USHORT					ReqLen,
	IN		PBYTE					pUserBytes		OPTIONAL,
	IN OUT	PAMDL					pResp			OPTIONAL,
	IN  	USHORT					RespLen			OPTIONAL,
	IN		SHORT					RetryCnt,
	IN		LONG					RetryInterval	OPTIONAL,	// In timer ticks
	IN		RELEASE_TIMERVALUE		RelTimerVal,
	IN		ATP_RESP_HANDLER		pCmpRoutine		OPTIONAL,
	IN		PVOID					pCtx			OPTIONAL
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PATP_REQ		pAtpReq;
	KIRQL			OldIrql;
	ULONG			index;
	ATALK_ERROR		error = ATALK_NO_ERROR;

	//	Verify relevant parameters.
	do
	{
#ifdef	ATP_STRICT
		// NOTE: These checks need to be added to the TDI interface if/when ATP is
		//		 opened upto user mode.
		if ((ReqLen < 0)									||
			(ReqLen > pAtpAddr->atpao_MaxSinglePktSize)		||
			(RespLen < 0)									||
			(RespLen > (pAtpAddr->atpao_MaxSinglePktSize * ATP_MAX_RESP_PKTS)))
		{
			error = ATALK_BUFFER_TOO_BIG;
			break;
		}

		if ((RetryCnt < 0) && (RetryCnt != ATP_INFINITE_RETRIES))
		{
			error = ATALK_ATP_INVALID_RETRYCNT;
			break;
		}

		if ((RelTimerVal < FIRSTVALID_TIMER) || (RelTimerVal > LAST_VALID_TIMER))
		{
			error = ATALK_ATP_INVALID_TIMERVAL;
			break;
		}

		if (RetryInterval < 0)
		{
			error = ATALK_ATP_INVALID_RELINT;
			break;
		}
#endif
		// The only valid values for Flags are ATP_REQ_EXACTLY_ONCE and ATP_REQ_REMOTE
		ASSERT ((Flags & ~(ATP_REQ_EXACTLY_ONCE | ATP_REQ_REMOTE)) == 0);

		if (RetryInterval == 0)
		{
			RetryInterval = ATP_DEF_RETRY_INTERVAL;
		}

		//	Reference the address object.
		AtalkAtpAddrReference(pAtpAddr, &error);
		if (!ATALK_SUCCESS(error))
		{
			break;
		}

		if ((pAtpReq = (PATP_REQ)AtalkBPAllocBlock(BLKID_ATPREQ)) == NULL)
		{
			AtalkAtpAddrDereference(pAtpAddr);
			error = ATALK_RESR_MEM;
			break;
		}
	} while (FALSE);

	if (!ATALK_SUCCESS(error))
		return error;

	//	We have memory allocated and the address object referenced at this
	//	point. Initialize the request structure.
#if DBG
	RtlZeroMemory(pAtpReq, sizeof(ATP_REQ));
	pAtpReq->req_Signature = ATP_REQ_SIGNATURE;
#endif

	//	Initial reference count - for creation.
	//	Also another ref for this routine itself. Ran into a situation
	//	where a thread posting the request was preempted and a close called.
	//	So at the point where the first thread is doing the transmit call,
	//	the request structure is already freed.
	pAtpReq->req_RefCount		= 2;
	pAtpReq->req_pAtpAddr		= pAtpAddr;

	pAtpReq->req_RetryInterval	= RetryInterval;
	pAtpReq->req_RetryCnt		= RetryCnt;
	pAtpReq->req_RelTimerValue	= RelTimerVal;

	pAtpReq->req_Dest 	  		= *pDest;
	pAtpReq->req_Buf			= pReq;
	pAtpReq->req_BufLen			= ReqLen;

	if (RetryCnt != 0)
		Flags |= ATP_REQ_RETRY_TIMER;
	pAtpReq->req_Flags = Flags;

	if (pUserBytes != NULL)
	{
		RtlCopyMemory(pAtpReq->req_UserBytes,
					  pUserBytes,
					  ATP_USERBYTES_SIZE);
	}
	else
	{
		pAtpReq->req_dwUserBytes = 0;
	}

	pAtpReq->req_RespBuf 	= pResp;
	pAtpReq->req_RespBufLen = RespLen;
	atalkAtpBufferSizeToBitmap( pAtpReq->req_Bitmap,
								RespLen,
								pAtpAddr->atpao_MaxSinglePktSize);
	pAtpReq->req_RespRecdLen = 0;
	pAtpReq->req_RecdBitmap = 0;

	//	Setup the ndis buffer descriptors for the response buffer
	AtalkIndAtpSetupNdisBuffer(pAtpReq, pAtpAddr->atpao_MaxSinglePktSize);

	pAtpReq->req_Comp = pCmpRoutine;
	pAtpReq->req_Ctx = pCtx;

	INITIALIZE_SPIN_LOCK(&pAtpReq->req_Lock);

	ACQUIRE_SPIN_LOCK(&pAtpAddr->atpao_Lock, &OldIrql);
	atalkAtpGetNextTidForAddr(pAtpAddr,
							  pDest,
							  &pAtpReq->req_Tid,
							  &index);

	DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
			("AtalkAtpPostReq: Tid %lx for %lx.%lx.%lx\n",
			pAtpReq->req_Tid, pDest->ata_Network,
			pDest->ata_Node, pDest->ata_Socket));

	//	Get the index where this request is supposed to go.
	//	Need to know the tid.
	index = ATP_HASH_TID_DESTADDR(pAtpReq->req_Tid, pDest, ATP_REQ_HASH_SIZE);

	//	Put this in the request queue
	AtalkLinkDoubleAtHead(pAtpAddr->atpao_ReqHash[index],
						  pAtpReq,
						  req_Next,
						  req_Prev);

	if (RetryCnt != 0)
	{
		// Set the time stamp when this should be retried
		pAtpReq->req_RetryTimeStamp = AtalkGetCurrentTick() + RetryInterval;

		InsertTailList(&pAtpAddr->atpao_ReqList, &pAtpReq->req_List);
	}

#ifdef	PROFILING
	INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AtpNumRequests,
								   &AtalkStatsLock.SpinLock);
#endif
	RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);

	//	Return the tid
	*pTid = pAtpReq->req_Tid;

	//	Now send the request
	atalkAtpTransmitReq(pAtpReq);

	//	Remove the ref added at the beginning of this routine.
	AtalkAtpReqDereference(pAtpReq);

	return ATALK_NO_ERROR;
}



VOID
AtalkAtpSetReqHandler(
	IN		PATP_ADDROBJ			pAtpAddr,
	IN		ATP_REQ_HANDLER			ReqHandler,
	IN		PVOID					ReqCtx		OPTIONAL
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	KIRQL			OldIrql;
	ATALK_ERROR		error;

	ASSERT (ReqHandler != NULL);

	//	Set the request handler in the address object
	ACQUIRE_SPIN_LOCK(&pAtpAddr->atpao_Lock, &OldIrql);

	ASSERT((pAtpAddr->atpao_Flags & ATPAO_CLOSING) == 0);
	pAtpAddr->atpao_RefCount++;
	pAtpAddr->atpao_ReqHandler = ReqHandler;
	pAtpAddr->atpao_ReqCtx = ReqCtx;

	RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);
}




ATALK_ERROR
AtalkAtpPostResp(
	IN		PATP_RESP				pAtpResp,
	IN		PATALK_ADDR				pDest,
	IN OUT	PAMDL					pResp,
	IN  	USHORT					RespLen,
	IN		PBYTE					pUserBytes	OPTIONAL,
	IN		ATP_REL_HANDLER			pCmpRoutine,
	IN		PVOID					pCtx		OPTIONAL
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PATP_ADDROBJ	pAtpAddr;
	BOOLEAN			addrlocked = FALSE, resplocked = FALSE;
	BOOLEAN			DerefAddr = FALSE, DerefResp = FALSE;
	SHORT			ResponseLen;
	KIRQL			OldIrql;
	ATALK_ERROR		error;

	ASSERT(VALID_ATPRS(pAtpResp));
	ASSERT ((pAtpResp->resp_Flags & (ATP_RESP_VALID_RESP		|
									 ATP_RESP_REL_TIMER			|
									 ATP_RESP_HANDLER_NOTIFIED)) == 0);

	DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
			("AtalkAtpPostResp: Posting response for Resp %lx, Tid %x %s\n",
			pAtpResp, pAtpResp->resp_Tid,
			(pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE) ? "XO" : "ALO"));

	pAtpAddr = pAtpResp->resp_pAtpAddr;
	ASSERT(VALID_ATPAO(pAtpAddr));

	do
	{
		KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

		if ((RespLen < 0) ||
			(RespLen > (pAtpAddr->atpao_MaxSinglePktSize * ATP_MAX_RESP_PKTS)))
		{
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
					("AtalkAtpPostResp: Invalid buffer size %ld", RespLen));
			error = ATALK_BUFFER_INVALID_SIZE;
			break;
		}

		ACQUIRE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
		addrlocked = TRUE;

		atalkAtpAddrRefNonInterlock(pAtpAddr, &error);
		if (!ATALK_SUCCESS(error))
		{
			break;
		}
		DerefAddr = TRUE;

		atalkAtpBitmapToBufferSize( ResponseLen,
									pAtpResp->resp_Bitmap,
									pAtpAddr->atpao_MaxSinglePktSize);
		if (ResponseLen < RespLen)
		{
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
					("AtalkAtpPostResp: bitmap resplen (%d) < specified (%d)\n",
					ResponseLen, RespLen));
			error = ATALK_BUFFER_TOO_BIG;
			break;
		}


		AtalkAtpRespReferenceByPtrDpc(pAtpResp, &error);
		if (!ATALK_SUCCESS(error))
		{
			break;
		}
		DerefResp = TRUE;

		ACQUIRE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);
		resplocked = TRUE;

		if (pAtpResp->resp_Flags & (ATP_RESP_CLOSING | ATP_RESP_CANCELLED))
		{
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
					("AtalkAtpPostResp: Closing/Cancelled %x", pAtpResp->resp_Flags));
			error = ATALK_ATP_RESP_CLOSING;
			break;
		}

		if (pAtpResp->resp_Flags & ATP_RESP_VALID_RESP)
		{
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
					("AtalkAtpPostResp: Already posted !\n"));
			error = ATALK_ATP_RESP_TOOMANY;
			break;
		}

		//	No response was previously posted. OK to proceed.
		pAtpResp->resp_Flags |= ATP_RESP_VALID_RESP;

		pAtpResp->resp_Buf 		= pResp;
		pAtpResp->resp_BufLen	= RespLen;
		pAtpResp->resp_Comp 	= pCmpRoutine;

		ASSERT(pCmpRoutine != NULL);

		pAtpResp->resp_Ctx		= pCtx;
		pAtpResp->resp_Dest		= *pDest;
		pAtpResp->resp_UserBytesOnly = (pAtpResp->resp_Bitmap == 0) ? TRUE : FALSE;

		if (ARGUMENT_PRESENT(pUserBytes))
		{
			pAtpResp->resp_dwUserBytes = *(UNALIGNED ULONG *)pUserBytes;
		}
		else
		{
			pAtpResp->resp_dwUserBytes = 0;
		}

		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
				("AtalkAtpPostResp: Posting response for %s request id %x\n",
				(pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE) ? "XO" : "ALO",
				pAtpResp->resp_Tid));

		//	Now setup to start the release timer, but only for XO
		if (pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE)
		{
			pAtpResp->resp_Flags |= ATP_RESP_REL_TIMER;
			InsertTailList(&pAtpAddr->atpao_RespList, &pAtpResp->resp_List);
		}

		// For ALO set the comp status right here.
		pAtpResp->resp_CompStatus = error = ATALK_NO_ERROR;
	} while (FALSE);

	if (addrlocked)
	{
		if (resplocked)
			RELEASE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);
		RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
	}

	if (ATALK_SUCCESS(error))
	{
		//	Send the response.
		ASSERT(pAtpResp->resp_Flags & ATP_RESP_VALID_RESP);
		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_WARN,
				("AtalkAtpPostResp: Transmitting response %lx\n", pAtpResp));
		atalkAtpTransmitResp(pAtpResp);
	}

	//	Dereference the address object.
	if (DerefAddr)
		AtalkAtpAddrDereferenceDpc(pAtpAddr);

	if (DerefResp)
		AtalkAtpRespDereferenceDpc(pAtpResp);

	// for ALO transactions, we are done so take away the creation reference
	if ((pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE) == 0)
	{
		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
				("AtalkAtpPostResp: Removing creation reference for ALO request %lx Tid %x\n",
				pAtpResp, pAtpResp->resp_Tid));
		AtalkAtpRespDereferenceDpc(pAtpResp);
	}

	if (OldIrql != DISPATCH_LEVEL)
		KeLowerIrql(OldIrql);

	return error;
}



ATALK_ERROR
AtalkAtpCancelReq(
	IN		PATP_ADDROBJ		pAtpAddr,
	IN		USHORT				Tid,
	IN		PATALK_ADDR			pDest
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error;
	KIRQL			OldIrql;
	PATP_REQ		pAtpReq;

	//	Find the request.
	ACQUIRE_SPIN_LOCK(&pAtpAddr->atpao_Lock, &OldIrql);
	atalkAtpReqReferenceByAddrTidDpc(pAtpAddr,
									 pDest,
									 Tid,
									 &pAtpReq,
									 &error);

	if (ATALK_SUCCESS(error))
	{
		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_WARN,
				("AtalkAtpCancelReq: Cancelling req tid %x\n", Tid));

		//	Request is referenced for us. Remove the creation reference.
		ACQUIRE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);

		// Do not cancel a request that has just about been satisfied anyway !!!
		if (pAtpReq->req_Flags & ATP_REQ_RESPONSE_COMPLETE)
		{
			error = ATALK_ATP_REQ_CLOSING;
		}

		RELEASE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);

		RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);

		if (ATALK_SUCCESS(error))
		{
			//	Try to remove the creation reference
			atalkAtpReqComplete(pAtpReq, ATALK_ATP_REQ_CANCELLED);
		}

		//	Remove the reference added at the beginning.
		AtalkAtpReqDereference(pAtpReq);
	}
	else RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);

	return error;
}




BOOLEAN
AtalkAtpIsReqComplete(
	IN		PATP_ADDROBJ		pAtpAddr,
	IN		USHORT				Tid,
	IN		PATALK_ADDR			pDest
	)
/*++

Routine Description:

	This is always called at DISPATCH_LEVEL - only by PAP.

Arguments:

Return Value:


--*/
{
	PATP_REQ		pAtpReq;
	ATALK_ERROR		error;
	BOOLEAN			rc = FALSE;

	ASSERT (KeGetCurrentIrql() == DISPATCH_LEVEL);

	//	Find the request.
	ACQUIRE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
	atalkAtpReqReferenceByAddrTidDpc(pAtpAddr,
									 pDest,
									 Tid,
									 &pAtpReq,
									 &error);

	if (ATALK_SUCCESS(error))
	{
		ACQUIRE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);

		// Do not cancel a request that has just about been satisfied anyway !!!
		if (pAtpReq->req_Flags & ATP_REQ_RESPONSE_COMPLETE)
		{
			rc = TRUE;
		}

		RELEASE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);
		AtalkAtpReqDereferenceDpc(pAtpReq);
	}

	RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

	return rc;
}


ATALK_ERROR
AtalkAtpCancelResp(
	IN		PATP_RESP			pAtpResp
	)
/*++

Routine Description:

	NOTE: A Response can be cancelled in two states:
		- *before* a response is posted
			In this case no release handler is there so an extra dereference needs to be done
		- *after* a response is posted
			In this case a release handler is associated which will do the final dereference.

Arguments:


Return Value:


--*/
{
	BOOLEAN			extraDeref = FALSE, CompleteResp = FALSE;
	KIRQL			OldIrql;
	ATALK_ERROR		error;

	DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
			("AtalkAtpCancelResp: Cancelling response for tid %x %s\n",
			pAtpResp->resp_Tid,
			(pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE) ? "XO" : "ALO"));

	AtalkAtpRespReferenceByPtr(pAtpResp, &error);

	if (ATALK_SUCCESS(error))
	{
		//	Remove the creation reference for it.
		//	Only XO responses can be cancelled, if a repsonse has been posted.
		ACQUIRE_SPIN_LOCK(&pAtpResp->resp_Lock, &OldIrql);

		ASSERT (!(pAtpResp->resp_Flags & ATP_RESP_CANCELLED));

		if ((pAtpResp->resp_Flags & ATP_RESP_VALID_RESP) == 0)
			extraDeref  = TRUE;

		pAtpResp->resp_Flags |= ATP_RESP_CANCELLED;

		if (pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE)
		{
			if (pAtpResp->resp_Flags & ATP_RESP_REL_TIMER)
			{
				ASSERT (pAtpResp->resp_Flags & ATP_RESP_VALID_RESP);
			}
			CompleteResp = TRUE;
		}
		else if ((pAtpResp->resp_Flags & ATP_RESP_VALID_RESP) == 0)
			CompleteResp = TRUE;

		RELEASE_SPIN_LOCK(&pAtpResp->resp_Lock, OldIrql);

		if (extraDeref)
			AtalkAtpRespDereference(pAtpResp);

		if (CompleteResp)
		{
			//	Try to remove the creation reference
			atalkAtpRespComplete(pAtpResp, ATALK_ATP_RESP_CANCELLED);
		}

		// Remove the reference added at the beginning.
		AtalkAtpRespDereference(pAtpResp);
	}
	else
	{
		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
				("AtalkAtpCancelResp: Failed to reference resp %lx, flags %x, tid %x\n",
				pAtpResp, pAtpResp->resp_Flags, pAtpResp->resp_Tid));
	}

	return error;
}




ATALK_ERROR
AtalkAtpCancelRespByTid(
	IN		PATP_ADDROBJ			pAtpAddr,
	IN		PATALK_ADDR				pSrcAddr,
	IN		USHORT					Tid
/*++

Routine Description:


Arguments:


Return Value:


--*/
	)
{
	ATALK_ERROR	error;
	PATP_RESP	pAtpResp;

	ASSERT (VALID_ATPAO(pAtpAddr));

	ACQUIRE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

	atalkAtpRespReferenceByAddrTidDpc(pAtpAddr, pSrcAddr, Tid, &pAtpResp, &error);

	RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

	if (ATALK_SUCCESS(error))
	{
		error = AtalkAtpCancelResp(pAtpResp);
		AtalkAtpRespDereferenceDpc(pAtpResp);
	}

	return error;
}


VOID
AtalkAtpPacketIn(
	IN	PPORT_DESCRIPTOR	pPortDesc,
	IN	PDDP_ADDROBJ		pDdpAddr,
	IN	PBYTE				pPkt,
	IN	USHORT				PktLen,
	IN	PATALK_ADDR			pSrcAddr,
	IN	PATALK_ADDR			pDstAddr,
	IN	ATALK_ERROR			ErrorCode,
	IN	BYTE				DdpType,
	IN	PATP_ADDROBJ		pAtpAddr,
	IN	BOOLEAN				OptimizedPath,
	IN	PVOID				OptimizeCtx
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error;
	NTSTATUS		ntStatus;
	USHORT			atpDataSize;
	ULONG			index;
	BYTE			controlInfo, function, relTimer, bitmap;
	USHORT			seqNum, tid, startOffset;
	SHORT			expectedRespSize;
	ULONG			bytesCopied;
	BOOLEAN			sendSts, eomFlag, xoFlag;
	BOOLEAN			RetransmitResp = FALSE;

	PATP_REQ		pAtpReq;
	ATP_REQ_HANDLER	ReqHandler;
	PATP_RESP		pAtpResp;

	BOOLEAN			UnlockAddr = FALSE, DerefAddr = FALSE;
	PBYTE			pDgram 	= pPkt;
	TIME			TimeS, TimeE, TimeD;

	TimeS = KeQueryPerformanceCounter(NULL);

	ASSERT(VALID_ATPAO(pAtpAddr));

	do
	{
		//	Check for incoming errors
		if ((!ATALK_SUCCESS(ErrorCode) &&
			 (ErrorCode != ATALK_SOCKET_CLOSED))	||
			(DdpType != DDPPROTO_ATP))
		{
			//	Drop the packet. Invalid packet error log.
			TMPLOGERR();
			error = ATALK_ATP_INVALID_PKT;
			break;
		}

		//	Make sure that we are not called after the ddp socket is closed.
		ASSERT(pAtpAddr->atpao_Flags & ATPAO_OPEN);

		if (ErrorCode == ATALK_SOCKET_CLOSED)
		{
			//	Our ddp address pointer is no longer valid. It will be potentially
			//	be freed after return from this call! Only valid request on this
			//	ATP request will now be AtpCloseAddress(). Also, we should never
			//	be called with this address object by DDP.
			ACQUIRE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
			pAtpAddr->atpao_DdpAddr = NULL;
			pAtpAddr->atpao_Flags  &= ~ATPAO_OPEN;
			RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

			// If we are coming in via the optimized path and socket closed
			// deref the address object since it was referenced within the
			// indication code.
			if (OptimizedPath)
			{
				AtalkAtpAddrDereferenceDpc(pAtpAddr);
			}
			error = ErrorCode;
			break;
		}

		if (PktLen < ATP_HEADER_SIZE)
		{
			error = ATALK_ATP_INVALID_PKT;
			break;
		}

		//	This must fail if OPEN is not set/initialization.
		error = ATALK_NO_ERROR;
		if (!OptimizedPath)
		{
			AtalkAtpAddrReferenceDpc(pAtpAddr, &error);
		}
	} while (FALSE);

	if (!ATALK_SUCCESS(error))
	{
		return;
	}

	//	Dereference address at the end,unless we want to keep it for some reason.
	DerefAddr = TRUE;

	//	Get the static fields from the ATP header.
	controlInfo = *pDgram++;


	function = (controlInfo & ATP_FUNC_MASK);
	relTimer = (controlInfo & ATP_REL_TIMER_MASK);
	xoFlag   = ((controlInfo & ATP_XO_MASK) != 0);
	eomFlag  = ((controlInfo & ATP_EOM_MASK) != 0);
	sendSts	 = ((controlInfo & ATP_STS_MASK) != 0);

	//	Get the bitmap/sequence number
	bitmap = *pDgram++;
	seqNum = (USHORT)bitmap;

	//	Get the transaction id
	GETSHORT2SHORT(&tid, pDgram);
	pDgram += sizeof(USHORT);

	DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
			("AtalkAtpPacketIn: Packet tid %lx fu %lx ci %lx\n",
			tid, function, controlInfo));

	//	pDgram now points to the user bytes.
	do
	{
		//	Check all the values
		if (relTimer > LAST_VALID_TIMER)
		{
			//	Use a thirty second timer value.
			relTimer = THIRTY_SEC_TIMER;
		}

		atpDataSize = PktLen - ATP_HEADER_SIZE;
		if (atpDataSize > pAtpAddr->atpao_MaxSinglePktSize)
		{
			error = ATALK_ATP_INVALID_PKT;
			break;
		}

		ACQUIRE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
		UnlockAddr = TRUE;

		switch (function)
		{
		  case ATP_REQUEST:
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
					("AtalkAtpPacketIn: Received REQUEST from %lx.%lx.%lx (%d.%d.%d)\n",
					pSrcAddr->ata_Network, pSrcAddr->ata_Node, pSrcAddr->ata_Socket,
					pSrcAddr->ata_Network, pSrcAddr->ata_Node, pSrcAddr->ata_Socket));

			if (xoFlag)
			{
				//	ExactlyOnce Transaction
				//	Check for a queued response. If available use it.
				atalkAtpRespReferenceByAddrTidDpc(pAtpAddr,
												  pSrcAddr,
												  tid,
												  &pAtpResp,
												  &error);

				if (ATALK_SUCCESS(error))
				{
					ASSERT (pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE);

					//	Found a response corresponding to this request. It
					//	is referenced for us. Retransmit it, if there is a
					//	response posted on it.

					//	Check to see if this response has a valid response
					//	posted by the atp client yet. If so reset the release timer.
					ACQUIRE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);

					if (pAtpResp->resp_Flags & ATP_RESP_VALID_RESP)
					{
						if ((pAtpResp->resp_Flags & (ATP_RESP_TRANSMITTING | ATP_RESP_SENT)) == ATP_RESP_SENT)
						{

							RetransmitResp = TRUE;
							if (pAtpResp->resp_Flags & ATP_RESP_REL_TIMER)
							{
								DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
										("AtalkAtpPacketIn: Retransmitting request %lx, tid %x (%x)\n",
										pAtpResp, pAtpResp->resp_Tid, pAtpResp->resp_Flags));

								pAtpResp->resp_RelTimeStamp = AtalkGetCurrentTick() +
																pAtpResp->resp_RelTimerTicks;
								DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_WARN,
										("AtalkAtpPacketIn: Restarted reltimer %lx\n", pAtpResp->resp_Tid));

								//	Set the latest bitmap for the request! We
								//	shouldn't touch this if no valid response is yet
								//	posted, so that we use the one in the first request
								//	packet received.
								pAtpResp->resp_Bitmap	= bitmap;
							}
							else
							{
								error = ATALK_ATP_RESP_CLOSING;

								//	Timer already fired. Drop the request.
								DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
										("AtalkAtpPacketIn: Req recv after Reltimer fired ? Flags %lx\n",
										pAtpResp->resp_Flags));

								ASSERT (pAtpResp->resp_Flags & ATP_RESP_CLOSING);
							}
						}
					}
					else
					{
						error = ATALK_ATP_NO_VALID_RESP;
					}

					RELEASE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);
					RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
					UnlockAddr = FALSE;

					if (ATALK_SUCCESS(error))
					{
						ASSERT(pAtpResp->resp_Flags & ATP_RESP_VALID_RESP);
						if (RetransmitResp)
						{
							DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_WARN,
									("AtalkAtpPacketIn: Retransmitting response %lx\n", pAtpResp));

							INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AtpNumRemoteRetries,
														   &AtalkStatsLock.SpinLock);
							atalkAtpTransmitResp(pAtpResp);
						}
					}

					//	Remove the refererence on this response structure.
					AtalkAtpRespDereferenceDpc(pAtpResp);
					break;
				}
			}

			// Its either an ALO request or an XO request which we have not seen it before
			//	Decode the response bitmap. We're still holding the address spinlock
			atalkAtpBitmapToBufferSize( expectedRespSize,
										bitmap,
										pAtpAddr->atpao_MaxSinglePktSize);
			if (expectedRespSize < 0)
			{
				error = ATALK_ATP_INVALID_PKT;
				break;
			}

			if (xoFlag)
			{
				INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AtpNumXoResponse,
											   &AtalkStatsLock.SpinLock);
			}
			else
			{
				INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AtpNumAloResponse,
											   &AtalkStatsLock.SpinLock);
			}

			//	New request. Check for request handler set
			if ((ReqHandler = pAtpAddr->atpao_ReqHandler) == NULL)
			{
				DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
						("AtalkAtpPacketIn: No GetRequests for request\n"));

				error = ATALK_ATP_NO_GET_REQ;
				break;
			}

			//	Allocate memory for a send response structure.
			if ((pAtpResp =(PATP_RESP)AtalkBPAllocBlock(BLKID_ATPRESP)) == NULL)
			{
				DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
						("AtalkAtpPacketIn: Could not alloc mem for resp\n"));

				error = ATALK_RESR_MEM;
				break;
			}

#if DBG
			RtlZeroMemory(pAtpResp, sizeof(ATP_RESP));
			pAtpResp->resp_Signature = ATP_RESP_SIGNATURE;
#endif
			//	Initialize the send response structure. Note that we do
			//	not have a posted response yet for XO or this is an ALO

			//	Initialize spinlock/list
			INITIALIZE_SPIN_LOCK(&pAtpResp->resp_Lock);

			//  Reference for Creation and indication
			pAtpResp->resp_RefCount = 2;

			//	Remember the destination of this response.
			pAtpResp->resp_Dest = *pSrcAddr;
			pAtpResp->resp_Tid	= tid;
			pAtpResp->resp_Bitmap = bitmap;

			//	Backpointer to the address object
			pAtpResp->resp_pAtpAddr = pAtpAddr;

			//	Remember a response needs to be posted by the atp client.
			pAtpResp->resp_Flags = (OptimizedPath ? ATP_RESP_REMOTE : 0);
			pAtpResp->resp_UserBytesOnly = (bitmap == 0) ? TRUE : FALSE;
			pAtpResp->resp_Comp = NULL;
			pAtpResp->resp_Ctx = NULL;
			pAtpResp->resp_dwUserBytes = *(UNALIGNED ULONG *)(pDgram + ATP_USERBYTES_SIZE);

			if (xoFlag)
			{
				//	Get the index into the hash response array where this
				//	response would be.
				index = ATP_HASH_TID_DESTADDR(tid, pSrcAddr, ATP_RESP_HASH_SIZE);

				DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
							("AtalkAtpPacketIn: XO Req Index %lx resp for %lx-%lx.%lx.%lx %d\n",
							index, tid, pSrcAddr->ata_Network, pSrcAddr->ata_Node,
							pSrcAddr->ata_Socket, AtalkAtpRelTimerTicks[relTimer]));

				//	Put this in the XO response queue - LOCK Should be acquired!
				AtalkLinkDoubleAtHead(pAtpAddr->atpao_RespHash[index],
									  pAtpResp,
									  resp_Next,
									  resp_Prev);

				pAtpResp->resp_Flags |= ATP_RESP_EXACTLY_ONCE;
				pAtpResp->resp_RelTimerTicks = (LONG)AtalkAtpRelTimerTicks[relTimer];
				pAtpResp->resp_RelTimeStamp = AtalkGetCurrentTick() + pAtpResp->resp_RelTimerTicks;
			}
			else
			{
				DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
							("AtalkAtpPacketIn: ALO Req resp for %lx-%lx.%lx %d\n",
							tid, pSrcAddr->ata_Network, pSrcAddr->ata_Node,
							pSrcAddr->ata_Socket));

				//	Put this in the ALO response queue - LOCK Should be acquired!
				AtalkLinkDoubleAtHead(pAtpAddr->atpao_AloRespLinkage,
									  pAtpResp,
									  resp_Next,
									  resp_Prev);
			}

			//	We dont want to have the initial ref go away, as we have
			//	inserted a resp into the addr resp list.
			DerefAddr = FALSE;

			error = ATALK_NO_ERROR;

			RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
			UnlockAddr = FALSE;

			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
					("AtalkAtpPacketIn: Indicating request %lx, tid %x %s\n",
					pAtpResp, tid,
					(pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE) ? "XO" : "ALO"));

#ifdef	PROFILING
			TimeD = KeQueryPerformanceCounter(NULL);
#endif
			(*ReqHandler)(ATALK_NO_ERROR,
						  pAtpAddr->atpao_ReqCtx,
						  pAtpResp,
						  pSrcAddr,
						  atpDataSize,
						  pDgram + ATP_USERBYTES_SIZE,
						  pDgram);

#ifdef	PROFILING
			TimeE = KeQueryPerformanceCounter(NULL);
			TimeE.QuadPart -= TimeD.QuadPart;

			INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AtpNumReqHndlr,
										   &AtalkStatsLock.SpinLock);
			INTERLOCKED_ADD_LARGE_INTGR(&AtalkStatistics.stat_AtpReqHndlrProcessTime,
										 TimeE,
										 &AtalkStatsLock.SpinLock);
#endif
			break;

		  case ATP_RESPONSE:
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
					("AtalkAtpPacketIn: Received RESPONSE from %lx.%lx.%lx, SeqNum %d tid %lx ss %lx\n",
					pSrcAddr->ata_Network, pSrcAddr->ata_Node, pSrcAddr->ata_Socket,
					seqNum, tid, sendSts));

			if (seqNum > (ATP_MAX_RESP_PKTS-1))
			{
				//	Drop the packet. Invalid packet error log.
				TMPLOGERR();
				break;
			}

			//	See if we have a request for this tid and remote address.
			if (OptimizedPath)
			{
				pAtpReq	= (PATP_REQ)OptimizeCtx;
				ASSERT (VALID_ATPRQ(pAtpReq));
				ASSERT (pAtpReq->req_Bitmap == 0);
			}
			else
			{
				atalkAtpReqReferenceByAddrTidDpc(pAtpAddr,
												 pSrcAddr,
												 tid,
												 &pAtpReq,
												 &error);
			}

			if (!ATALK_SUCCESS(error))
			{
				//	We dont have a corresponding pending request. Ignore.
				DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_WARN,
						("AtalkAtpPacketIn: No pending request for tid %lx\n", tid));
				break;
			}

			do
			{
				if (!OptimizedPath)
				{
					//	Check the request bitmap, which could be zero if the user only
					//	wanted the user bytes and passed in a null response buffer.
					ACQUIRE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);

					//	If we are the first packet, copy the response user bytes.
					if (seqNum == 0)
					{
						RtlCopyMemory(pAtpReq->req_RespUserBytes,
									  pDgram,
									  ATP_USERBYTES_SIZE);
					}

					//	Now skip over the user bytes
					pDgram += ATP_USERBYTES_SIZE;

					//	Do we want to keep this response? Check the corresponding
					//	bit in our current bitmap set.
					if (((pAtpReq->req_RecdBitmap & AtpBitmapForSeqNum[seqNum]) != 0) ||
						((pAtpReq->req_Bitmap & AtpBitmapForSeqNum[seqNum]) == 0))
					{
						RELEASE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);
						//	We dont care about this packet. We already received it or weren't
						//	expecting it.
						break;
					}

					//	We want this response. Set bit in the recd bitmap. And
					//	Clear it in the expected packets req_Bitmap.

					//	!!!NOTE!!! 	We can release the spinlock even though the copy
					//				is not done. We have a ref to the req, and it wont
					//				get completed before that is done.
					pAtpReq->req_Bitmap 			&= ~AtpBitmapForSeqNum[seqNum];
					pAtpReq->req_RecdBitmap 		|= AtpBitmapForSeqNum[seqNum];
					pAtpReq->req_RespRecdLen	 	+= atpDataSize;

					DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
							("AtalkAtpPacketIn: req_Bitmap %x, req_RecdBitmap %x\n",
							pAtpReq->req_Bitmap, pAtpReq->req_RecdBitmap));


					//	Now if eom is set, we need to reset all high order bits
					//	of the req_Bitmap. req_RecdBitmap should now indicate all
					//	the buffers we received. The two should be mutually exclusive
					//	at this point.
					if (eomFlag)
					{
						pAtpReq->req_Bitmap &= AtpEomBitmapForSeqNum[seqNum];
						ASSERT((pAtpReq->req_Bitmap & pAtpReq->req_RecdBitmap) == 0);
					}

					if (sendSts)
					{
						//	Reset timer since we are going to retransmit the request
						pAtpReq->req_RetryTimeStamp = AtalkGetCurrentTick() +
															pAtpReq->req_RetryInterval;
					}

					RELEASE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);

					//	Copy the data into the users buffer. Check if there's room.
					startOffset = (USHORT)seqNum * pAtpAddr->atpao_MaxSinglePktSize;
					if (pAtpReq->req_RespBufLen < (startOffset + atpDataSize))
					{
						//	This should be a rare case; packet was in bitmap limits,
						//	but still wouldn't fit into user space.The other way this
						//	could occure is if the responder is sending less than full
						//	responses -- we don't "synch" up the user buffer until all
						//	packets have been received.

						//	We want to give up now, call the comp rotuine signaling
						//	the error -- unthread and free the request control block
						//	cancel the retry timer.

						ASSERT(0);
					}

					if ((atpDataSize > 0) && (pAtpReq->req_RespBuf != NULL))
					{
						//	We have room to copy the data into the users buffer.
						ntStatus = TdiCopyBufferToMdl(pDgram,
													  0,
													  atpDataSize,
													  pAtpReq->req_RespBuf,
													  startOffset,
													  &bytesCopied);

						ASSERT(bytesCopied == atpDataSize);
						ASSERT(NT_SUCCESS(ntStatus));
					}

					if (sendSts)
					{
						// We have reset the retry timer above
						atalkAtpTransmitReq(pAtpReq);
					}

					//	If the bitmap is non-zero, we are still awaiting more responses.
					if (pAtpReq->req_Bitmap != 0)
					{
						break;
					}
				}
				else
				{
					ASSERT (pAtpReq->req_Bitmap == 0);
				}

				//	Ok, we have the entire response !
				//	If an XO request send a release, synch up the user buffer,
				//	Deref the request to have it complete.

				RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
				UnlockAddr = FALSE;

				if (pAtpReq->req_Flags & ATP_REQ_EXACTLY_ONCE)
				{
					atalkAtpTransmitRel(pAtpReq);
				}

				//	Do the synch up! USE RECD_BITMAP!!

				//	Set the response length, the user bytes in the request buffer.

				//	See if we can grab ownership of this request to remove
				//	the creation reference and complete it.
				DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
						("AtalkAtpPacketIn: Completing req %lx tid %x\n",
						pAtpReq, pAtpReq->req_Tid));

				atalkAtpReqComplete(pAtpReq, error);
			} while (FALSE);

			//	Remove reference on the request added at the beginning.
			AtalkAtpReqDereferenceDpc(pAtpReq);
			break;

		  case ATP_RELEASE:
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
					("AtalkAtpPacketIn: Received release for tid %lx!\n", tid));

			atalkAtpRespReferenceByAddrTidDpc(pAtpAddr,
											  pSrcAddr,
											  tid,
											  &pAtpResp,
											  &error);
			if (ATALK_SUCCESS(error))
			{
				DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
						("AtalkAtpPacketIn: Found resp for release for tid %lx!\n",
						pAtpResp->resp_Tid));

				//	We received a release for this response. Cleanup and
				//	complete.
				ACQUIRE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);

				pAtpResp->resp_Flags |= ATP_RESP_RELEASE_RECD;
				if (pAtpResp->resp_Flags & ATP_RESP_REL_TIMER)
				{
					ASSERT (pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE);
				}

				RELEASE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);
			}
			else
			{
				DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
						("AtalkAtpPacketIn: resp not found - release for tid %lx!\n", tid));
			}

			RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
			UnlockAddr = FALSE;

			INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AtpNumRecdRelease,
										   &AtalkStatsLock.SpinLock);

			if (ATALK_SUCCESS(error))
			{
                ATALK_ERROR     ErrorCode = ATALK_NO_ERROR;

                // if client (mac) cancelled the request (possibly because session
                // went away), make sure our completion routine gets called
                if ((pAtpResp->resp_Flags & ATP_RESP_VALID_RESP) == 0)
                {
                    ErrorCode = ATALK_ATP_RESP_CANCELLED;
                    pAtpResp->resp_Flags |= ATP_RESP_VALID_RESP;
                }

				//	Try to have the creation reference removed
				atalkAtpRespComplete(pAtpResp, ErrorCode);

				//	Remove the reference we added at the beginning.
				AtalkAtpRespDereferenceDpc(pAtpResp);
			}
			break;

		  default:
			break;
		}
		if (UnlockAddr)
		{
			RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
		}
	} while (FALSE);

	//	Deref addr added at the beginning of this routine.
	if (DerefAddr)
	{
		AtalkAtpAddrDereferenceDpc(pAtpAddr);
	}

	TimeE = KeQueryPerformanceCounter(NULL);
	TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart;

	INTERLOCKED_ADD_LARGE_INTGR_DPC(&AtalkStatistics.stat_AtpPacketInProcessTime,
									TimeD,
									&AtalkStatsLock.SpinLock);

	INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AtpNumPackets,
								   &AtalkStatsLock.SpinLock);
}




VOID FASTCALL
atalkAtpTransmitReq(
	IN		PATP_REQ	pAtpReq
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error;
	ATP_HEADER		atpHeader;
	BOOLEAN			remote;
	BOOLEAN			DerefReq = FALSE;
	PBUFFER_DESC	pBufDesc = NULL;
	SEND_COMPL_INFO	SendInfo;

	//	Reference the request. This goes away in request send completion.
	AtalkAtpReqReferenceByPtr(pAtpReq, &error);
	if (ATALK_SUCCESS(error))
	{
		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
				("atalkAtpTransmitReq: Transmitting req %lx tid %x\n",
					pAtpReq, pAtpReq->req_Tid));

		//	Build the atp header.
		atpHeader.atph_CmdCtrl = ATP_REQUEST | (UCHAR)(ATP_REL_TIMER_MASK & pAtpReq->req_RelTimerValue);
		if (pAtpReq->req_Flags & ATP_REQ_EXACTLY_ONCE)
			atpHeader.atph_CmdCtrl |= ATP_XO_MASK;

		//	Put in the expected packets bitmap.
		atpHeader.atph_Bitmap = pAtpReq->req_Bitmap;

		//	Put in the tid.
		PUTSHORT2SHORT(&atpHeader.atph_Tid, pAtpReq->req_Tid);

		//	Copy the user bytes.
		atpHeader.atph_dwUserBytes = pAtpReq->req_dwUserBytes;

		//	Build a buffer descriptor, this should hold the above mdl.
		if (pAtpReq->req_BufLen > 0)
		{
			if ((pBufDesc = AtalkAllocBuffDesc(pAtpReq->req_Buf,
											   pAtpReq->req_BufLen,
											   0)) == NULL)
			{
				DerefReq 	= TRUE;
				error 		= ATALK_RESR_MEM;
			}
		}

		remote = (pAtpReq->req_Flags & ATP_REQ_REMOTE) ? TRUE : FALSE;
		//	Call ddp to send the packet. Dont touch request after this call,
		//	as the send completion could potentially lead to it being freed.
		SendInfo.sc_TransmitCompletion = atalkAtpSendReqComplete;
		SendInfo.sc_Ctx1 = pAtpReq;
		SendInfo.sc_Ctx2 = pBufDesc;
		// SendInfo.sc_Ctx3 = NULL;
		if (ATALK_SUCCESS(error) &&
			!ATALK_SUCCESS(error = AtalkDdpSend(pAtpReq->req_pAtpAddr->atpao_DdpAddr,
												&pAtpReq->req_Dest,
												(BYTE)DDPPROTO_ATP,
												remote,
												pBufDesc,
												(PBYTE)&atpHeader,
												ATP_HEADER_SIZE,
												NULL,
												&SendInfo)))
		{
			DerefReq = TRUE;
			if (pBufDesc != NULL)
			{
				//	The flags will indicate that the data buffer is not to be
				//	freed.
				AtalkFreeBuffDesc(pBufDesc);
			}
		}

		if (DerefReq)
		{
			pAtpReq->req_CompStatus = error;
			AtalkAtpReqDereference(pAtpReq);
		}
	}
}




VOID FASTCALL
atalkAtpSendReqComplete(
	IN	NDIS_STATUS			Status,
	IN	PSEND_COMPL_INFO	pSendInfo
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	if (pSendInfo->sc_Ctx2 != NULL)
	{
		AtalkFreeBuffDesc((PBUFFER_DESC)(pSendInfo->sc_Ctx2));
	}

	AtalkAtpReqDereference((PATP_REQ)(pSendInfo->sc_Ctx1));
}




VOID FASTCALL
atalkAtpTransmitResp(
	IN		PATP_RESP		pAtpResp
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error;
	KIRQL			OldIrql;
	BYTE			i, bitmap, currentBit, seqNum, pktstosend;
	BOOLEAN			RemoteAddr;
	USHORT			bytesSent, bytesToSend, maxSinglePktSize;
	SHORT			remainingBytes;
	PATP_ADDROBJ	pAtpAddr;
	PAMDL			pAmdl[ATP_MAX_RESP_PKTS];
	PBUFFER_DESC	pBufDesc[ATP_MAX_RESP_PKTS];
	ATP_HEADER		atpHeader;
	SEND_COMPL_INFO	SendInfo;

	//	Verify we have a response posted
	ASSERT(pAtpResp->resp_Flags & ATP_RESP_VALID_RESP);

	pAtpAddr = pAtpResp->resp_pAtpAddr;
	ASSERT(VALID_ATPAO(pAtpAddr));

	RemoteAddr = ((pAtpResp->resp_Flags & ATP_RESP_REMOTE) == 0) ? FALSE : TRUE;

	//	send each response packet that is needed.
	seqNum			= 0;
	pktstosend		= 0;
	currentBit		= 1;

	//	Get the max packet size for this atp object
	maxSinglePktSize	= pAtpAddr->atpao_MaxSinglePktSize;

	bitmap			= pAtpResp->resp_Bitmap;
	remainingBytes 	= pAtpResp->resp_BufLen;
	bytesSent		= 0;

	//	Indicate response type.
	atpHeader.atph_CmdCtrl = ATP_RESPONSE;

	//	Put in the tid.
	PUTSHORT2SHORT(&atpHeader.atph_Tid, pAtpResp->resp_Tid);

	ASSERTMSG("atalkAtpTransmitResp: resp len is negative\n", (remainingBytes >= 0));

	KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

	do
	{
		ACQUIRE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);
		pAtpResp->resp_Flags |= ATP_RESP_TRANSMITTING;
		RELEASE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);

		do
		{
			pAmdl[seqNum] = NULL;
			pBufDesc[seqNum]  = NULL;

			if (((bitmap & currentBit) != 0) ||
				((seqNum == 0) && pAtpResp->resp_UserBytesOnly))
			{
				ASSERT(pAtpResp->resp_Flags & ATP_RESP_VALID_RESP);

				bytesToSend = MIN(remainingBytes, maxSinglePktSize);
				pktstosend ++;

				if (bytesToSend != 0)
				{
					ASSERT (pAtpResp->resp_Buf != NULL);
					//	Make an mdl for the proper subsection of the response mdl.
					//	Make a buffer descriptor for the mdl.
					if (((pAmdl[seqNum] = AtalkSubsetAmdl(pAtpResp->resp_Buf,
														  bytesSent,
														  bytesToSend)) == NULL) ||
						((pBufDesc[seqNum] = AtalkAllocBuffDesc(pAmdl[seqNum],
																bytesToSend,
																0)) == NULL))
					{
						ASSERTMSG("atalkAtpTransmitResp: Create mdl or BD failed\n", 0);
						if (pAmdl[seqNum] != NULL)
						{
							AtalkFreeAMdl(pAmdl[seqNum]);
							pAmdl[seqNum] = NULL;
						}
						if (seqNum > 0)
							seqNum --;		// Adjust this.

						break;
					}
				}
			}
			else
			{
				//	We are omitting this. Let us mark it appropriately
				pBufDesc[seqNum]  = (PBUFFER_DESC)-1;
			}

			seqNum 			++;
			currentBit 		<<= 1;
			remainingBytes 	-= maxSinglePktSize;
			bytesSent 		+= maxSinglePktSize;
		} while (remainingBytes > 0);

		ASSERT (seqNum <= ATP_MAX_RESP_PKTS);

		//	Attempt to reference the response structure. If we fail, we abort.
		//	This will go away in the completion routine.
		atalkAtpRespReferenceNDpc(pAtpResp, pktstosend, &error);
		if (!ATALK_SUCCESS(error))
		{
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
					("atalkAtpTransmitResp: response %lx ref (%d) failed\n",
					pAtpResp, seqNum, error));

			// Need to free up the Mdls/Buffdescs
			for (i = 0; i < seqNum; i++)
			{
				if (pAmdl[i] != NULL)
					AtalkFreeAMdl(pAmdl[i]);

				if ((pBufDesc[i] != NULL) && (pBufDesc[i] != (PBUFFER_DESC)-1))
					AtalkFreeBuffDesc(pBufDesc[i]);
			}
			break;
		}

		// Now blast off all the packets
		SendInfo.sc_TransmitCompletion = atalkAtpSendRespComplete;
		SendInfo.sc_Ctx1 = pAtpResp;
		// SendInfo.sc_Ctx3 = pAmdl[i];
		for (i = 0; i < seqNum; i++)
		{
			if (pBufDesc[i] == (PBUFFER_DESC)-1)
				continue;

			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
					("atalkAtpTransmitResp: Sending seq #%d for tid %lx\n",
					i, pAtpResp->resp_Tid));

			//	Indicate if this is the last packet of the response.
			if (i == (seqNum-1))
			{
				atpHeader.atph_CmdCtrl |= ATP_EOM_MASK;
			}

			//	Put in the sequence number
			atpHeader.atph_SeqNum = i;

			//	User bytes only go in the first packet of the response
			//	unless otherwise indicated for this atp object.
			if ((i == 0)	||
				(pAtpAddr->atpao_Flags & ATPAO_SENDUSERBYTESALL))
			{
				atpHeader.atph_dwUserBytes = pAtpResp->resp_dwUserBytes;
			}
			else
			{
				//	Zero the user bytes
				atpHeader.atph_dwUserBytes = 0;
			}

			ASSERT(pAtpResp->resp_Flags & ATP_RESP_VALID_RESP);

			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
					("atalkAtpTransmitResp: Sending seq #%d, BufDesc %lx, Resp %lx\n",
					i, pBufDesc[i], pAtpResp));

			ASSERT ((pBufDesc[i] == NULL) ||
					VALID_BUFFDESC(pBufDesc[i]));
			SendInfo.sc_Ctx2 = pBufDesc[i];
			error = AtalkDdpSend(pAtpAddr->atpao_DdpAddr,
								 &pAtpResp->resp_Dest,
								 (BYTE)DDPPROTO_ATP,
								 RemoteAddr,
								 pBufDesc[i],
								 (PBYTE)&atpHeader,
								 ATP_HEADER_SIZE,
								 NULL,
								 &SendInfo);

			if (!ATALK_SUCCESS(error))
			{
				DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
						("atalkAtpTransmitResp: AtalkDdpSend Failed %ld\n", error));
				//	Call completion so the buffer/mdl can get freed up,
				//	and the reference is removed.
				atalkAtpSendRespComplete(error,
										 &SendInfo);
			}
		}
	} while (FALSE);

	ACQUIRE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);
	pAtpResp->resp_Flags |= ATP_RESP_SENT;
	pAtpResp->resp_Flags &= ~ATP_RESP_TRANSMITTING;
	RELEASE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);

	if (OldIrql != DISPATCH_LEVEL)
		KeLowerIrql(OldIrql);
}




VOID FASTCALL
atalkAtpSendRespComplete(
	IN	NDIS_STATUS			Status,
	IN	PSEND_COMPL_INFO	pSendInfo
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	if (pSendInfo->sc_Ctx2 != NULL)
	{
		PAMDL	pMdl;

		if ((pMdl = ((PBUFFER_DESC)(pSendInfo->sc_Ctx2))->bd_OpaqueBuffer) != NULL)
			AtalkFreeAMdl(pMdl);
		AtalkFreeBuffDesc((PBUFFER_DESC)(pSendInfo->sc_Ctx2));

	}

	AtalkAtpRespDereference((PATP_RESP)(pSendInfo->sc_Ctx1));
}



//	This is used to perform a retry when a release send fails in completion.
#define		ATP_TID_RETRY_MASK	0xF0000000
#define		ATP_TID_MASK		0xFFFF

VOID FASTCALL
atalkAtpTransmitRel(
	IN		PATP_REQ	pAtpReq
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error;
	ATP_HEADER		atpHeader;
	BOOLEAN			remote;
	SEND_COMPL_INFO	SendInfo;

	AtalkAtpAddrReferenceDpc(pAtpReq->req_pAtpAddr, &error);

	if (ATALK_SUCCESS(error))
	{
		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
				("atalkAtpTransmitRel: Sending release for %lx\n", pAtpReq->req_Tid));

		//	Build header for this packet.
		atpHeader.atph_dwUserBytes = 0;

		//	Indicate response type.
		atpHeader.atph_CmdCtrl = ATP_RELEASE;

		//	Put in the bitmap
		atpHeader.atph_Bitmap = pAtpReq->req_RecdBitmap;

		//	Put in the tid.
		PUTSHORT2SHORT(&atpHeader.atph_Tid, pAtpReq->req_Tid);

		remote = (pAtpReq->req_Flags & ATP_REQ_REMOTE) ? TRUE : FALSE;
		SendInfo.sc_TransmitCompletion = atalkAtpSendRelComplete;
		SendInfo.sc_Ctx1 = pAtpReq->req_pAtpAddr;
		SendInfo.sc_Ctx2 = (PVOID)(ATP_TID_RETRY_MASK | pAtpReq->req_Tid);
		SendInfo.sc_Ctx3 = (PVOID)(pAtpReq->req_Dest.ata_Address);
		error = AtalkDdpSend(pAtpReq->req_pAtpAddr->atpao_DdpAddr,
							 &pAtpReq->req_Dest,
							 (BYTE)DDPPROTO_ATP,
							 remote,
							 NULL,
							 (PBYTE)&atpHeader,
							 ATP_HEADER_SIZE,
							 NULL,
							 &SendInfo);

		if (!ATALK_SUCCESS(error))
		{
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
					("atalkAtpTransmitRel: Send release failed %lx\n", error));

			AtalkAtpAddrDereferenceDpc(pAtpReq->req_pAtpAddr);
		}
	}
}




VOID FASTCALL
atalkAtpSendRelComplete(
	IN	NDIS_STATUS			Status,
	IN	PSEND_COMPL_INFO	pSendInfo
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ATALK_ERROR		error;
	ATP_HEADER		atpHeader;
#define	pAtpAddr	((PATP_ADDROBJ)(pSendInfo->sc_Ctx1))
#define	TidAndRetry	(ULONG)(pSendInfo->sc_Ctx2)
#define	DestAddr	(ULONG)(pSendInfo->sc_Ctx3)

	DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
			("atalkAtpSendRelComplete: Send status %lx\n", Status));

	if ((Status == NDIS_STATUS_SUCCESS) ||
		((TidAndRetry & ATP_TID_RETRY_MASK) == 0))
	{
		//	Either successful, or we have already retried.
		AtalkAtpAddrDereference(pAtpAddr);
		return;
	}

	//	Go ahead and retry!
	//	Build header for this packet.
	atpHeader.atph_dwUserBytes = 0;

	//	Indicate response type.
	atpHeader.atph_CmdCtrl = ATP_RELEASE;

	//	Put in the tid.
	PUTSHORT2SHORT(&atpHeader.atph_Tid, (TidAndRetry & ATP_TID_MASK));

	pSendInfo->sc_Ctx2 = NULL;
	pSendInfo->sc_Ctx3 = NULL;
	error = AtalkDdpSend(pAtpAddr->atpao_DdpAddr,
						(PATALK_ADDR)&DestAddr,
						(BYTE)DDPPROTO_ATP,
						FALSE,
						NULL,
						(PBYTE)&atpHeader,
						ATP_HEADER_SIZE,
						NULL,
						pSendInfo);

	if (!ATALK_SUCCESS(error))
	{
		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
				("atalkAtpSendRelComplete: Send release failed %lx\n", error));

		AtalkAtpAddrDereference(pAtpAddr);
	}
#undef	pAtpAddr
#undef	TidAndRetry
#undef	DestAddr
}




VOID  FASTCALL
atalkAtpRespComplete(
	IN	OUT	PATP_RESP	pAtpResp,
	IN		ATALK_ERROR	CompletionStatus
	)
{
	KIRQL	OldIrql;
	BOOLEAN	ownResp = TRUE;

	DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
			("atalkAtpRespComplete: Completing %lx.%lx\n",
				pAtpResp->resp_Tid, CompletionStatus));

	//	See if we can grab ownership of this response to remove
	//	the creation reference and complete it.
	ACQUIRE_SPIN_LOCK(&pAtpResp->resp_Lock, &OldIrql);
	if (pAtpResp->resp_Flags & ATP_RESP_CLOSING)
	{
		ownResp = FALSE;
	}
	pAtpResp->resp_Flags |= ATP_RESP_CLOSING;
	pAtpResp->resp_CompStatus = CompletionStatus;
	RELEASE_SPIN_LOCK(&pAtpResp->resp_Lock, OldIrql);

	//	If we managed to get ownership of the request, call the
	//	Deref for creation.
	if (ownResp)
	{
		AtalkAtpRespDereference(pAtpResp);
	}
}




VOID FASTCALL
atalkAtpReqComplete(
	IN	OUT	PATP_REQ	pAtpReq,
	IN		ATALK_ERROR	CompletionStatus
	)
{
	KIRQL	OldIrql;
	BOOLEAN	ownReq = TRUE;

	DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
			("atalkAtpReqComplete: Completing %lx\n", pAtpReq->req_Tid));

	//	See if we can grab ownership of this resquest to remove
	//	the creation reference and complete it.
	ACQUIRE_SPIN_LOCK(&pAtpReq->req_Lock, &OldIrql);
	if (pAtpReq->req_Flags & ATP_REQ_CLOSING)
	{
		ownReq = FALSE;
	}
	pAtpReq->req_CompStatus = CompletionStatus;
	pAtpReq->req_Flags |= ATP_REQ_CLOSING;
	RELEASE_SPIN_LOCK(&pAtpReq->req_Lock, OldIrql);

	//	If we managed to get ownership of the request, call the deref for creation.
	if (ownReq)
	{
		AtalkAtpReqDereference(pAtpReq);
	}
}




VOID
atalkAtpGetNextTidForAddr(
	IN		PATP_ADDROBJ	pAtpAddr,
	IN		PATALK_ADDR		pRemoteAddr,
	OUT		PUSHORT			pTid,
	OUT		PULONG			pIndex
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	USHORT		TentativeTid;
	ULONG		index;
	PATP_REQ	pAtpReq;

	do
	{
		TentativeTid = pAtpAddr->atpao_NextTid++;
		if (pAtpAddr->atpao_NextTid == 0)
			pAtpAddr->atpao_NextTid = 1;

		//	Check to see if this tid is in use for this address.

		//	!!!NOTE!!!
		//	This will be true even if the tid is in use for a closing
		//	request or a response.

		//	Calculate the hash value of the destination address of this request
		//	and the tid.
		index = ATP_HASH_TID_DESTADDR(TentativeTid, pRemoteAddr, ATP_REQ_HASH_SIZE);

		for (pAtpReq = pAtpAddr->atpao_ReqHash[index];
			 pAtpReq != NULL;
			 pAtpReq = pAtpReq->req_Next)
		{
			if ((ATALK_ADDRS_EQUAL(&pAtpReq->req_Dest, pRemoteAddr)) &&
				(pAtpReq->req_Tid == TentativeTid))
			{
				break;
			}
		}
	} while (pAtpReq != NULL);

	DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
			("atalkAtpGetNextTidForAddr: Tid %lx for %lx.%lx.%lx\n",
				TentativeTid, pRemoteAddr->ata_Network, pRemoteAddr->ata_Node,
				pRemoteAddr->ata_Socket));

	*pTid = TentativeTid;
	*pIndex = index;
}


VOID
atalkAtpReqRefNextNc(
	IN		PATP_REQ		pAtpReq,
	OUT		PATP_REQ	*	ppNextNcReq,
	OUT		PATALK_ERROR	pError
	)
/*++

Routine Description:

	MUST BE CALLED WITH THE ADDRESS LOCK HELD!

Arguments:


Return Value:


--*/
{
	for (NOTHING; pAtpReq != NULL; pAtpReq = pAtpReq->req_Next)
	{
		AtalkAtpReqReferenceByPtrDpc(pAtpReq, pError);
		if (ATALK_SUCCESS(*pError))
		{
			//	Ok, this request is referenced!
			*ppNextNcReq = pAtpReq;
			break;
		}
	}
}




VOID FASTCALL
atalkAtpReqDeref(
	IN		PATP_REQ	pAtpReq,
	IN		BOOLEAN		AtDpc
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	KIRQL		OldIrql;
	BOOLEAN		done = FALSE;

	//	This will call the completion routine and remove it from the
	//	list when ref count goes to 0.
	ASSERT(VALID_ATPRQ(pAtpReq));

	if (AtDpc)
	{
		ACQUIRE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);
	}
	else
	{
		ACQUIRE_SPIN_LOCK(&pAtpReq->req_Lock, &OldIrql);
	}

	if ((--pAtpReq->req_RefCount) == 0)
	{
		ASSERT(pAtpReq->req_Flags & ATP_REQ_CLOSING);
		done = TRUE;
	}

	if (AtDpc)
	{
		RELEASE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);
	}
	else
	{
		RELEASE_SPIN_LOCK(&pAtpReq->req_Lock, OldIrql);
	}

	if (done)
	{
		if (AtDpc)
		{
			ACQUIRE_SPIN_LOCK_DPC(&pAtpReq->req_pAtpAddr->atpao_Lock);
		}
		else
		{
			ACQUIRE_SPIN_LOCK(&pAtpReq->req_pAtpAddr->atpao_Lock, &OldIrql);
		}

		//	Remove it from the list.
		AtalkUnlinkDouble(pAtpReq, req_Next, req_Prev);

		if (pAtpReq->req_Flags & ATP_REQ_RETRY_TIMER)
		{
			pAtpReq->req_Flags &= ~ATP_REQ_RETRY_TIMER;
			RemoveEntryList(&pAtpReq->req_List);
		}

		if (AtDpc)
		{
			RELEASE_SPIN_LOCK_DPC(&pAtpReq->req_pAtpAddr->atpao_Lock);
		}
		else
		{
			RELEASE_SPIN_LOCK(&pAtpReq->req_pAtpAddr->atpao_Lock, OldIrql);
		}

		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
				("atalkAtpReqDeref: Completing req for tid %lx.%d\n",
				pAtpReq->req_Tid, pAtpReq->req_Tid));

		//	Call the completion routine for the request.
		if (pAtpReq->req_Comp != NULL)
		{
			KIRQL	OldIrql;

			// Resp handlers expect to be called at DISPATCH. If the
			// request was cancelled, make it so.
			if (pAtpReq->req_CompStatus == ATALK_ATP_REQ_CANCELLED)
				KeRaiseIrql(DISPATCH_LEVEL, &OldIrql);

			(*pAtpReq->req_Comp)(pAtpReq->req_CompStatus,
								 pAtpReq->req_Ctx,
								 pAtpReq->req_Buf,
								 pAtpReq->req_RespBuf,
								 pAtpReq->req_RespRecdLen,
								 pAtpReq->req_RespUserBytes);

			if (pAtpReq->req_CompStatus == ATALK_ATP_REQ_CANCELLED)
				KeLowerIrql(OldIrql);
		}

		//	Deref the address object
		if (AtDpc)
		{
			AtalkAtpAddrDereferenceDpc(pAtpReq->req_pAtpAddr);
		}
		else
		{
			AtalkAtpAddrDereference(pAtpReq->req_pAtpAddr);
		}

		//	Release the ndis buffer descriptors, if any
		AtalkIndAtpReleaseNdisBuffer(pAtpReq);

		AtalkBPFreeBlock(pAtpReq);
	}
}




VOID
atalkAtpRespRefNextNc(
	IN		PATP_RESP		pAtpResp,
	OUT		PATP_RESP	 *  ppNextNcResp,
	OUT		PATALK_ERROR	pError
	)
/*++

Routine Description:

	MUST BE CALLED WITH THE ADDRESS LOCK HELD!

Arguments:


Return Value:


--*/
{
	PATP_RESP	pNextResp	= NULL;
	ATALK_ERROR	error 		= ATALK_FAILURE;

	for (; pAtpResp != NULL; pAtpResp = pAtpResp->resp_Next)
	{
		AtalkAtpRespReferenceByPtrDpc(pAtpResp, pError);
		if (ATALK_SUCCESS(*pError))
		{
			//	Ok, this request is referenced!
			*ppNextNcResp = pAtpResp;
			break;
		}
	}
}




VOID FASTCALL
AtalkAtpRespDeref(
	IN		PATP_RESP	pAtpResp,
	IN		BOOLEAN		AtDpc
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PATP_ADDROBJ	pAtpAddr;
	KIRQL			OldIrql;
	BOOLEAN			done = FALSE;
	BOOLEAN			NotifyRelHandler = FALSE;

	//	This will call the completion routine when the ref count goes to 1
	//	and remove it from the list when ref count goes to 0. The assumption
	//	here is that the release handler will be the last to Dereference.

	if (AtDpc)
	{
		ACQUIRE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);
	}
	else
	{
		ACQUIRE_SPIN_LOCK(&pAtpResp->resp_Lock, &OldIrql);
	}

	pAtpResp->resp_RefCount--;
	if (pAtpResp->resp_RefCount == 0)
	{
		ASSERT(pAtpResp->resp_Flags & (ATP_RESP_HANDLER_NOTIFIED | ATP_RESP_CANCELLED));

		done = TRUE;
	}
	else if ((pAtpResp->resp_RefCount == 1) &&
			 (pAtpResp->resp_Flags & ATP_RESP_VALID_RESP) &&
			 ((pAtpResp->resp_Flags & ATP_RESP_HANDLER_NOTIFIED) == 0))
	{
		NotifyRelHandler = TRUE;

		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
				("AtalkAtpRespDereference: Notifying release handler for Resp %lx, tid %x %s\n",
				pAtpResp, pAtpResp->resp_Tid,
				(pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE) ? "XO" : "ALO"));
		pAtpResp->resp_Flags |= ATP_RESP_HANDLER_NOTIFIED;
	}

	if (AtDpc)
	{
		RELEASE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);
	}
	else
	{
		RELEASE_SPIN_LOCK(&pAtpResp->resp_Lock, OldIrql);
	}

	if (NotifyRelHandler)
	{
		ASSERT (!done);
		ASSERT (pAtpResp->resp_Comp != NULL);

		//	Call the completion routine.
		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
				("AtalkAtpRespDereference: Calling resp handler for tid %lx %s\n",
				pAtpResp->resp_Tid,
				(pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE) ? "XO" : "ALO"));
		(*pAtpResp->resp_Comp)(pAtpResp->resp_CompStatus, pAtpResp->resp_Ctx);
	}

	else if (done)
	{
		DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
				("AtalkAtpRespDereference: Freeing resp for tid %lx - %lx %s\n",
				pAtpResp->resp_Tid, pAtpResp->resp_CompStatus,
				(pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE) ? "XO" : "ALO"));

		pAtpAddr = pAtpResp->resp_pAtpAddr;
		if (AtDpc)
		{
			ACQUIRE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
		}
		else
		{
			ACQUIRE_SPIN_LOCK(&pAtpAddr->atpao_Lock, &OldIrql);
		}


		//	Remove it from the list.
		AtalkUnlinkDouble(pAtpResp, resp_Next, resp_Prev);

		if (pAtpResp->resp_Flags & ATP_RESP_REL_TIMER)
		{
			ASSERT (pAtpResp->resp_Flags & ATP_RESP_EXACTLY_ONCE);
			pAtpResp->resp_Flags &= ~ATP_RESP_REL_TIMER;
			RemoveEntryList(&pAtpResp->resp_List);
		}

		if (AtDpc)
		{
			RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
		}
		else
		{
			RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);
		}

		//	Deref the address object
		if (AtDpc)
		{
			AtalkAtpAddrDereferenceDpc(pAtpResp->resp_pAtpAddr);
		}
		else
		{
			AtalkAtpAddrDereference(pAtpResp->resp_pAtpAddr);
		}
		AtalkBPFreeBlock(pAtpResp);
	}
}



VOID FASTCALL
AtalkAtpAddrDeref(
	IN OUT	PATP_ADDROBJ	pAtpAddr,
	IN		BOOLEAN			AtDpc
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	KIRQL	OldIrql;
	BOOLEAN	done = FALSE;

	if (AtDpc)
	{
		ACQUIRE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
	}
	else
	{
		ACQUIRE_SPIN_LOCK(&pAtpAddr->atpao_Lock, &OldIrql);
	}

	ASSERT(pAtpAddr->atpao_RefCount > 0);
	if (--(pAtpAddr->atpao_RefCount) == 0)
	{
		done = TRUE;
		ASSERT(pAtpAddr->atpao_Flags & ATPAO_CLOSING);
	}

	if (AtDpc)
	{
		RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
	}
	else
	{
		RELEASE_SPIN_LOCK(&pAtpAddr->atpao_Lock, OldIrql);
	}

	if (done)
	{
		//	Call the close completion routine.
		if (pAtpAddr->atpao_CloseComp != NULL)
		{
			(*pAtpAddr->atpao_CloseComp)(ATALK_NO_ERROR, pAtpAddr->atpao_CloseCtx);
		}

		// 	This address is done for. Close the ddp socket.
		AtalkDdpCloseAddress(pAtpAddr->atpao_DdpAddr, NULL, NULL);

		//	Free up the memory
		AtalkFreeMemory(pAtpAddr);

		AtalkUnlockAtpIfNecessary();
	}
}




LOCAL LONG FASTCALL
atalkAtpReqTimer(
	IN	PTIMERLIST			pTimer,
	IN	BOOLEAN				TimerShuttingDown
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PATP_REQ		pAtpReq;
	PATP_ADDROBJ	pAtpAddr;
	PLIST_ENTRY		pList, pListNext;
	ATALK_ERROR		error;
	LONG			now;
	BOOLEAN			retry;
#ifdef	PROFILING
	LARGE_INTEGER	TimeS, TimeE, TimeD;

	TimeS = KeQueryPerformanceCounter(NULL);
#endif

	pAtpAddr = CONTAINING_RECORD(pTimer, ATP_ADDROBJ, atpao_RetryTimer);
	ASSERT(VALID_ATPAO(pAtpAddr));

	DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
			("atalkAtpReqTimer: Entered for address %lx\n", pAtpAddr));

	if (TimerShuttingDown ||
		(pAtpAddr->atpao_Flags & (ATPAO_CLOSING|ATPAO_CLEANUP)))
	{
		AtalkAtpAddrDereferenceDpc(pAtpAddr);
		return ATALK_TIMER_NO_REQUEUE;
	}

	now = AtalkGetCurrentTick();

	ACQUIRE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

	for (pList = pAtpAddr->atpao_ReqList.Flink;
		 pList != &pAtpAddr->atpao_ReqList;
		 pList = pListNext)
	{
		pAtpReq = CONTAINING_RECORD(pList, ATP_REQ, req_List);
		ASSERT (VALID_ATPRQ(pAtpReq));

		ACQUIRE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);

		pListNext = pAtpReq->req_List.Flink;

		//	If either we are closing this request or have not timed out yet, skip.
		if (((pAtpReq->req_Flags & (ATP_REQ_CLOSING		|
									ATP_REQ_RETRY_TIMER	|
									ATP_REQ_RESPONSE_COMPLETE)) != ATP_REQ_RETRY_TIMER) ||
			(now < pAtpReq->req_RetryTimeStamp))

		{
			RELEASE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);
			continue;
		}

		//	If retry count == 0, we have reached the end of the road.
		if ((pAtpReq->req_RetryCnt == ATP_INFINITE_RETRIES) ||
			(--(pAtpReq->req_RetryCnt) > 0))
		{
			//	Transmit the request again!
			retry = TRUE;
			pAtpReq->req_RetryTimeStamp = (now + pAtpReq->req_RetryInterval);
		}
		else
		{
			//	We should now be Dereferenced for creation.
			retry = FALSE;
		}

		RELEASE_SPIN_LOCK_DPC(&pAtpReq->req_Lock);

		if (retry)
		{
			// We do not want to update statistics for requests are that are never going to
			// be responded to (like tickle packets). Detect these and skip updating the
			// stats for these
			if (pAtpReq->req_RespBufLen > 0)	// i.e. response expected
			{
				INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AtpNumLocalRetries,
											   &AtalkStatsLock.SpinLock);
			}

			AtalkAtpReqReferenceByPtrDpc(pAtpReq, &error);

			RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

			if (ATALK_SUCCESS(error))
			{
				atalkAtpTransmitReq(pAtpReq);
				AtalkAtpReqDereferenceDpc(pAtpReq);
			}
		}
		else
		{
			//	We have run out of retries - complete with an error
			ASSERT (pAtpReq->req_RetryCnt == 0);
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
					("atalkAtpReqTimer: Request %lx, tid %x timed out !!!\n",
					pAtpReq, pAtpReq->req_Tid));

			AtalkAtpReqReferenceByPtrDpc(pAtpReq, &error);

			RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

			if (ATALK_SUCCESS(error))
			{
				atalkAtpReqComplete(pAtpReq, ATALK_ATP_REQ_TIMEOUT);
				AtalkAtpReqDereferenceDpc(pAtpReq);
			}
		}

		ACQUIRE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

		// Start over
		pListNext = pAtpAddr->atpao_ReqList.Flink;
	}

	RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

#ifdef	PROFILING
	TimeE = KeQueryPerformanceCounter(NULL);
	TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart;

	INTERLOCKED_ADD_LARGE_INTGR_DPC(&AtalkStatistics.stat_AtpReqTimerProcessTime,
									TimeD,
									&AtalkStatsLock.SpinLock);

	INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AtpNumReqTimer,
								   &AtalkStatsLock.SpinLock);
#endif

	return ATALK_TIMER_REQUEUE;
}




LOCAL LONG FASTCALL
atalkAtpRelTimer(
	IN	PTIMERLIST			pTimer,
	IN	BOOLEAN				TimerShuttingDown
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PATP_ADDROBJ	pAtpAddr;
	PATP_RESP		pAtpResp;
	PLIST_ENTRY		pList, pListNext;
	LONG			now;
#ifdef	PROFILING
	LARGE_INTEGER	TimeS, TimeE, TimeD;

	TimeS = KeQueryPerformanceCounter(NULL);
#endif

	pAtpAddr = CONTAINING_RECORD(pTimer, ATP_ADDROBJ, atpao_RelTimer);
	ASSERT(VALID_ATPAO(pAtpAddr));

	if (TimerShuttingDown ||
		(pAtpAddr->atpao_Flags & (ATPAO_CLOSING|ATPAO_CLEANUP)))
	{
		AtalkAtpAddrDereferenceDpc(pAtpAddr);
		return ATALK_TIMER_NO_REQUEUE;
	}

	now = AtalkGetCurrentTick();

	ACQUIRE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

	for (pList = pAtpAddr->atpao_RespList.Flink;
		 pList != &pAtpAddr->atpao_RespList;
		 pList = pListNext)
	{
		BOOLEAN	derefResp;

		pAtpResp = CONTAINING_RECORD(pList, ATP_RESP, resp_List);

		ACQUIRE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);
		derefResp = TRUE;

		ASSERT (VALID_ATPRS(pAtpResp));
		ASSERT (pAtpResp->resp_Flags & (ATP_RESP_EXACTLY_ONCE|ATP_RESP_VALID_RESP|ATP_RESP_REL_TIMER));

		pListNext = pAtpResp->resp_List.Flink;

		if ((pAtpResp->resp_Flags &
				(ATP_RESP_CLOSING			|
				 ATP_RESP_REL_TIMER			|
				 ATP_RESP_TRANSMITTING		|
				 ATP_RESP_SENT				|
				 ATP_RESP_HANDLER_NOTIFIED	|
				 ATP_RESP_RELEASE_RECD)) == (ATP_RESP_REL_TIMER | ATP_RESP_SENT))
		{
			DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_INFO,
					("atalkAtpRelTimer: Checking req tid %lx (%x)\n",
					pAtpResp->resp_Tid, pAtpResp->resp_Flags));

			if (now >= pAtpResp->resp_RelTimeStamp)
			{
				DBGPRINT(DBG_COMP_ATP, DBG_LEVEL_ERR,
						("atalkAtpRelTimer: Releasing req %lx tid %lx (%x)\n",
						pAtpResp, pAtpResp->resp_Tid, pAtpResp->resp_Flags));

				RELEASE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);

				RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);
				derefResp = FALSE;

				INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AtpNumRespTimeout,
											   &AtalkStatsLock.SpinLock);

				//	Try to have the creation reference removed
				atalkAtpRespComplete(pAtpResp, ATALK_ATP_RESP_TIMEOUT);

				ACQUIRE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

				// Start over
				pListNext = pAtpAddr->atpao_RespList.Flink;
			}
		}

		if (derefResp)
		{
			RELEASE_SPIN_LOCK_DPC(&pAtpResp->resp_Lock);
		}
	}

	RELEASE_SPIN_LOCK_DPC(&pAtpAddr->atpao_Lock);

#ifdef	PROFILING
	TimeE = KeQueryPerformanceCounter(NULL);
	TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart;

	INTERLOCKED_ADD_LARGE_INTGR_DPC(&AtalkStatistics.stat_AtpRelTimerProcessTime,
									TimeD,
									&AtalkStatsLock.SpinLock);

	INTERLOCKED_INCREMENT_LONG_DPC(&AtalkStatistics.stat_AtpNumRelTimer,
								   &AtalkStatsLock.SpinLock);
#endif

	return ATALK_TIMER_REQUEUE;
}


VOID FASTCALL
AtalkAtpGenericRespComplete(
	IN	ATALK_ERROR				ErrorCode,
	IN	PATP_RESP				pAtpResp
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	AtalkAtpRespDereference(pAtpResp);
}

