/*

Copyright (c) 1992  Microsoft Corporation

Module Name:

	atkmem.c

Abstract:

	This module contains the routines which allocates and free memory. Only
	the non-paged pool is used.

	!!! For profiling, we use spinlock acquire/release for CurAllocCount/CurAllocSize

Author:

	Nikhil Kamkolkar	(NikhilK@microsoft.com)
	Jameel Hyder (JameelH@microsoft.com)

Revision History:
	25 Apr 1992	 Initial Version (JameelH)

--*/

#include <atalk.h>
#pragma hdrstop
#define	FILENUM	ATKMEM

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, AtalkInitMemorySystem)
#pragma alloc_text(PAGE, AtalkDeInitMemorySystem)
#endif


VOID
AtalkInitMemorySystem(
	VOID
)
{
	LONG	i;

	for (i = 0; i < NUM_BLKIDS; i++)
		INITIALIZE_SPIN_LOCK(&atalkBPLock[i]);

	AtalkTimerInitialize(&atalkBPTimer,
						 atalkBPAgePool,
						 BLOCK_POOL_TIMER);
	AtalkTimerScheduleEvent(&atalkBPTimer);
}


VOID
AtalkDeInitMemorySystem(
	VOID
)
{
	LONG		i, j, NumBlksPerChunk;
	PBLK_CHUNK	pChunk, pFree;
	
	for (i = 0; i < NUM_BLKIDS; i++)
	{
		NumBlksPerChunk = atalkNumBlks[i];
		for (pChunk = atalkBPHead[i];
			 pChunk != NULL;
			 NOTHING)
		{
			DBGPRINT(DBG_COMP_RESOURCES, DBG_LEVEL_INFO,
					("AtalkDeInitMemorySystem: Freeing %lx\n", pChunk));
			if ((pChunk->bc_NumFree != NumBlksPerChunk) ||
				(pChunk->bc_NumAlloc != 0))
			{
				DBGPRINT(DBG_COMP_RESOURCES, DBG_LEVEL_ERR,
						("AtalkDeInitMemorySystem: Unfreed blocks from blockid %d, Chunk %lx\n",
						i, pChunk));
				ASSERT(0);
			}

			if (pChunk->bc_BlkId >= BLKID_NEED_NDIS_INT)
			{
				PBLK_HDR	pBlkHdr;

				// We need to free the Ndis stuff for these guys
				for (j = 0, pBlkHdr = pChunk->bc_FreeHead;
					 j < NumBlksPerChunk;
					 j++, pBlkHdr = pBlkHdr->bh_Next)
				{
					PBUFFER_HDR	pBufHdr;

					pBufHdr = (PBUFFER_HDR)((PBYTE)pBlkHdr + sizeof(BLK_HDR));
					ASSERT(pBufHdr->bh_NdisPkt == NULL);
					AtalkNdisFreeBuffer(pBufHdr->bh_NdisBuffer);
				}
			}
			pFree = pChunk;
			pChunk = pChunk->bc_Next;
			AtalkFreeMemory(pFree);
		}
		atalkBPHead[i] = NULL;
	}
}


PVOID FASTCALL
AtalkAllocMem(
#ifdef	TRACK_MEMORY_USAGE
	IN	ULONG	Size,
	IN	ULONG	FileLine
#else
	IN	ULONG	Size
#endif	// TRACK_MEMORY_USAGE
)
/*++

Routine Description:

	Allocate a block of non-paged memory. This is just a wrapper over ExAllocPool.
 	Allocation failures are error-logged. We always allocate a ULONG more than
 	the specified size to accomodate the size. This is used by AtalkFreeMemory
 	to update the statistics.

Arguments:


Return Value:


--*/
{
	PBYTE	pBuf;
	BOOLEAN	zeroed;
#ifdef	PROFILING
	TIME	TimeS, TimeE, TimeD;
#endif

	//	round up the size so that we can put a signature at the end
	//	that is on a ULONG boundary
	zeroed = ((Size & ZEROED_MEMORY_TAG) == ZEROED_MEMORY_TAG);

	Size = DWORDSIZEBLOCK(Size & ~ZEROED_MEMORY_TAG) +
#if	DBG
			sizeof(DWORD) +				// For the signature
#endif
			sizeof(ULONG_PTR);

#ifdef	PROFILING
	TimeS = KeQueryPerformanceCounter(NULL);
#endif

	// Do the actual memory allocation. Allocate four extra bytes so
	// that we can store the size of the allocation for the free routine.
	if ((pBuf = ExAllocatePoolWithTag(NonPagedPool, Size, ATALK_TAG)) == NULL)
	{
		LOG_ERROR(EVENT_ATALK_MEMORYRESOURCES, STATUS_INSUFFICIENT_RESOURCES, NULL, 0);
		DBGPRINT(DBG_COMP_RESOURCES, DBG_LEVEL_FATAL,
				("AtalkAllocMemory: failed - size %lx\n", Size));
		return NULL;
	}

#ifdef	PROFILING
	INTERLOCKED_INCREMENT_LONG(&AtalkStatistics.stat_CurAllocCount,
							   &AtalkStatsLock.SpinLock);
	INTERLOCKED_INCREMENT_LONG(&AtalkStatistics.stat_ExAllocPoolCount,
							   &AtalkStatsLock.SpinLock);
	TimeE = KeQueryPerformanceCounter(NULL);
	TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart;
	INTERLOCKED_ADD_LARGE_INTGR(&AtalkStatistics.stat_ExAllocPoolTime,
								 TimeD,
								 &AtalkStatsLock.SpinLock);
#endif

	INTERLOCKED_ADD_ULONG(&AtalkStatistics.stat_CurAllocSize,
						  Size,
						  &AtalkStatsLock.SpinLock);

	ASSERTMSG("AtalkAllocMemory: Allocation has exceeded Limit !!!\n",
				AtalkStatistics.stat_CurAllocSize < (ULONG)AtalkMemLimit);

	// Save the size of this block in the four extra bytes we allocated.
	*((PULONG)pBuf) = Size;

#if DBG
	*((PULONG)(pBuf+Size-sizeof(ULONG))) = ATALK_MEMORY_SIGNATURE;
	DBGPRINT(DBG_COMP_RESOURCES, DBG_LEVEL_INFO,
			("AtalkAllocMemory: Allocated %lx bytes @%lx\n", Size, pBuf));
#endif

	AtalkTrackMemoryUsage((PVOID)pBuf, Size, TRUE, FileLine);

#if	DBG
	Size -= sizeof(ULONG);
#endif

	pBuf += sizeof(ULONG_PTR);

	if (zeroed)
	{
		RtlZeroMemory(pBuf, Size - sizeof(ULONG_PTR));
	}

	// Return a pointer to the memory after the size longword.
	return (pBuf);
}




VOID FASTCALL
AtalkFreeMemory(
	IN	PVOID	pBuf
	)
/*++

Routine Description:

 	Free the block of memory allocated via AtalkAllocMemory. This is
 	a wrapper around ExFreePool.

Arguments:


Return Value:


--*/
{
	PULONG 	pRealBuffer;
	ULONG	Size;
#ifdef	PROFILING
	TIME	TimeS, TimeE, TimeD;
#endif

	// Get a pointer to the block allocated by ExAllocatePool.
	pRealBuffer = (PULONG)((PCHAR)pBuf - sizeof(ULONG_PTR));
	Size = *pRealBuffer;

	AtalkTrackMemoryUsage(pRealBuffer, Size, FALSE, 0);

#if	DBG
	*pRealBuffer = 0;
	// Check the signature at the end
	if (*(PULONG)((PCHAR)pRealBuffer + Size - sizeof(ULONG))
											!= ATALK_MEMORY_SIGNATURE)
	{
		DBGPRINT(DBG_COMP_RESOURCES, DBG_LEVEL_FATAL,
				("AtalkFreeMemory: Memory overrun on block %lx\n", pRealBuffer));
		ASSERT(0);
	}
	*(PULONG)((PCHAR)pRealBuffer + Size - sizeof(ULONG)) = 0;
#endif

	INTERLOCKED_ADD_ULONG(&AtalkStatistics.stat_CurAllocSize,
						  -(LONG)Size,
						  &AtalkStatsLock.SpinLock);
#ifdef	PROFILING
	INTERLOCKED_DECREMENT_LONG(&AtalkStatistics.stat_CurAllocCount,
							   &AtalkStatsLock);
	INTERLOCKED_INCREMENT_LONG(&AtalkStatistics.stat_ExFreePoolCount,
							   &AtalkStatsLock);
	TimeS = KeQueryPerformanceCounter(NULL);
#endif

	// Free the pool and return.
	ExFreePool(pRealBuffer);

#ifdef	PROFILING
	TimeE = KeQueryPerformanceCounter(NULL);
	TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart;
	INTERLOCKED_ADD_LARGE_INTGR(&AtalkStatistics.stat_ExFreePoolTime,
								 TimeD,
								 &AtalkStatsLock.SpinLock);
#endif
}




PBUFFER_DESC
AtalkDescribeBufferDesc(
	IN	PVOID	DataPtr,
	IN	PVOID	FreePtr,
	IN	USHORT	Length,
#ifdef	TRACK_BUFFDESC_USAGE
	IN	USHORT	Flags,
	IN	ULONG	FileLine
#else
	IN	USHORT	Flags
#endif
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PBUFFER_DESC	pBuffDesc;

	if ((pBuffDesc = AtalkAllocBufferDesc(DataPtr,
										  Length,
#ifdef	TRACK_BUFFDESC_USAGE
										  Flags,
										  FileLine
#else
										  Flags
#endif
		)) != NULL)
	{
		pBuffDesc->bd_FreeBuffer = FreePtr;
	}

	return pBuffDesc;
}




PBUFFER_DESC
AtalkAllocBufferDesc(
	IN	PVOID	Ptr	OPTIONAL,
	IN	USHORT	Length,
#ifdef	TRACK_BUFFDESC_USAGE
	IN	USHORT	Flags,
	IN	ULONG	FileLine
#else
	IN	USHORT	Flags
#endif
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PBUFFER_DESC	pBuffDesc = NULL;

	DBGPRINT(DBG_COMP_RESOURCES, DBG_LEVEL_INFO,
			("AtalkAllocBuffDesc: Ptr %lx, Length %x, Flags %x\n",
			Ptr, Length, Flags));


	pBuffDesc = AtalkBPAllocBlock(BLKID_BUFFDESC);

	if (pBuffDesc != NULL)
	{
#if	DBG
		pBuffDesc->bd_Signature = BD_SIGNATURE;
#endif
		pBuffDesc->bd_Length = Length;
		pBuffDesc->bd_Flags = Flags;
		pBuffDesc->bd_Next = NULL;

		//	Depending on whether a char buffer or a PAMDL is being
		//	passed in...
		if (Flags & BD_CHAR_BUFFER)
		{
			if ((Ptr == NULL) &&
				((Ptr = AtalkAllocMemory(Length)) == NULL))
			{
				pBuffDesc->bd_Flags = 0;
				pBuffDesc->bd_CharBuffer = NULL;
				AtalkFreeBuffDesc(pBuffDesc);
				pBuffDesc = NULL;
			}
			else
			{
				pBuffDesc->bd_CharBuffer = pBuffDesc->bd_FreeBuffer = Ptr;
				AtalkTrackBuffDescUsage(pBuffDesc, TRUE, FileLine);
			}
		}
		else
		{
			pBuffDesc->bd_OpaqueBuffer = (PAMDL)Ptr;
		}
	}

	return pBuffDesc;
}




VOID FASTCALL
AtalkFreeBuffDesc(
	IN	PBUFFER_DESC	pBuffDesc
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	ASSERT(VALID_BUFFDESC(pBuffDesc));

	if ((pBuffDesc->bd_Flags & (BD_FREE_BUFFER | BD_CHAR_BUFFER)) ==
									(BD_FREE_BUFFER | BD_CHAR_BUFFER))
		AtalkFreeMemory(pBuffDesc->bd_FreeBuffer);
	AtalkTrackBuffDescUsage(pBuffDesc, FALSE, 0);

	AtalkBPFreeBlock(pBuffDesc);
}




VOID
AtalkCopyBuffDescToBuffer(
	IN	PBUFFER_DESC	pBuffDesc,
	IN	LONG			SrcOff,
	IN	LONG			BytesToCopy,
	IN	PBYTE			DstBuf
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	NTSTATUS	Status;
	ULONG		BytesCopied;
	LONG		Index = 0;

	ASSERT(VALID_BUFFDESC(pBuffDesc));

	while ((pBuffDesc != NULL) &&
		   (SrcOff > (LONG)pBuffDesc->bd_Length))
	{
		SrcOff -= pBuffDesc->bd_Length;
		pBuffDesc = pBuffDesc->bd_Next;
	}

	do
	{
		LONG	ThisCopy;

		if (pBuffDesc == NULL)
			break;

		ThisCopy = BytesToCopy;
		if (ThisCopy > ((LONG)pBuffDesc->bd_Length - SrcOff))
			ThisCopy = ((LONG)pBuffDesc->bd_Length - SrcOff);
		BytesToCopy -= ThisCopy;

		if (pBuffDesc->bd_Flags & BD_CHAR_BUFFER)
		{
			RtlCopyMemory(DstBuf + Index,
						  pBuffDesc->bd_CharBuffer + SrcOff,
						  ThisCopy);
		}
		else
		{
			Status = TdiCopyMdlToBuffer(pBuffDesc->bd_OpaqueBuffer,
										SrcOff,
										DstBuf + Index,
										0,
										ThisCopy,
										&BytesCopied);
			ASSERT(NT_SUCCESS(Status) && (BytesCopied == (ULONG)ThisCopy));
		}
		Index += ThisCopy;
		SrcOff -= (pBuffDesc->bd_Length - ThisCopy);
		pBuffDesc = pBuffDesc->bd_Next;
	} while (BytesToCopy > 0);
}




VOID
AtalkCopyBufferToBuffDesc(
	IN	PBYTE			SrcBuf,
	IN	LONG			BytesToCopy,
	IN	PBUFFER_DESC	pBuffDesc,
	IN	LONG			DstOff
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	NTSTATUS	Status;
	LONG		Index = 0;

	ASSERT(VALID_BUFFDESC(pBuffDesc));

	while ((DstOff > (LONG)pBuffDesc->bd_Length) &&
		   (pBuffDesc != NULL))
	{
		DstOff -= pBuffDesc->bd_Length;
		pBuffDesc = pBuffDesc->bd_Next;
	}

	do
	{
		LONG	ThisCopy;

		if (pBuffDesc == NULL)
			break;

		ThisCopy = BytesToCopy;
		if (ThisCopy > ((LONG)pBuffDesc->bd_Length - DstOff))
			ThisCopy = ((LONG)pBuffDesc->bd_Length - DstOff);
		BytesToCopy -= ThisCopy;

		if (pBuffDesc->bd_Flags & BD_CHAR_BUFFER)
		{
			RtlCopyMemory(pBuffDesc->bd_CharBuffer + DstOff,
						  SrcBuf + Index,
						  ThisCopy);
		}
		else
		{
			Status = TdiCopyBufferToMdl(SrcBuf,
										Index,
										ThisCopy,
										pBuffDesc->bd_OpaqueBuffer,
										DstOff,
										(PULONG)&ThisCopy);
			ASSERT(NT_SUCCESS(Status) && (ThisCopy == BytesToCopy));
		}
		Index += ThisCopy;
		DstOff -= (pBuffDesc->bd_Length - ThisCopy);
		pBuffDesc = pBuffDesc->bd_Next;
	} while (BytesToCopy > 0);
}




LONG FASTCALL
AtalkSizeBuffDesc(
	IN	PBUFFER_DESC	pBuffDesc
	)
/*++

Routine Description:


Arguments:


Return Value:

--*/
{
	LONG	Size;

	ASSERT(VALID_BUFFDESC(pBuffDesc));

	for (Size = 0; pBuffDesc != NULL; pBuffDesc = pBuffDesc->bd_Next)
		Size += (LONG)pBuffDesc->bd_Length;
	return(Size);
}


PAMDL
AtalkSubsetAmdl(
	IN	PAMDL	pStartingMdl,
	IN	ULONG	TotalOffset,
	IN	ULONG	DesiredLength
	)
/*++

Routine Description:

	This routine is called to build an Mdl chain from a source Mdl chain and
	offset into it. We assume we don't know the length of the source Mdl chain,
	and we must allocate and build the Mdls for the destination chain, which
	we do from non-paged pool. Note that this routine, unlike the IO subsystem
	routines, sets the SystemVaMapped bit in the generated Mdls to the same
	value as that in the source Mdls.
	
	IT WOULD BE BAD TO USE MmMapLockedPages OR MmProbeAndLockPages ON THE
	DESTINATION MDLS UNLESS YOU TAKE RESPONSIBILITY FOR UNMAPPING THEM!
	
	The MDLs that are returned are mapped and locked. (Actually, the pages in
	them are in the same state as those in the source MDLs.)
	
	If the system runs out of memory while we are building the destination
	MDL chain, we completely clean up the built chain and return with
	NewCurrentMdl and NewByteOffset set to the current values of CurrentMdl
	and TotalOffset. TRUELength is set to 0.

Environment:

	Kernel Mode, Source Mdls locked. It is recommended, although not required,
	that the source Mdls be mapped and locked prior to calling this routine.

Arguments:

	pStartingMdl			- the source appletalk mdl ( == nt mdl)
	TotalOffset 		- Offset within this MDL to start the packet at.
	DesiredLength 	- The number of bytes to insert into the packet.

Return Value:

	pointer to build mdl, NULL if out of resources, or lengths inconsistent.

--*/
{
	PMDL 	Destination=NULL;

	PBYTE 	BaseVa;
	ULONG 	NewMdlLength;
	PMDL 	NewMdl;
    PMDL    pMdl;

	PMDL 	CurrentMdl = (PMDL)pStartingMdl;
    ULONG   CurrentOffset = TotalOffset;
    ULONG   BytesToDescribe = DesiredLength;


    //
    // first make sure that we have enough bytes!
    //
    if (DesiredLength > (ULONG)AtalkSizeMdlChain(pStartingMdl))
    {
	    DBGPRINT(DBG_COMP_UTILS, DBG_LEVEL_ERR,
		    ("AtalkSubsetMdl: req len (%ld) exceeds avl len (%ld) for mdl %lx\n",
		    DesiredLength, AtalkSizeMdlChain(pStartingMdl),pStartingMdl));

        ASSERT(0);
	    return(NULL);
    }

    //
    // first, get to the right Mdl (in most cases, the same as pStartingMdl)
    //
    while (CurrentMdl && (CurrentOffset >= MmGetMdlByteCount (CurrentMdl)))
    {
        CurrentOffset -= MmGetMdlByteCount (CurrentMdl);
        CurrentMdl = CurrentMdl->Next;
        ASSERT(CurrentMdl != NULL);
    }

    while (BytesToDescribe)
    {
        ASSERT(CurrentMdl != NULL);

	    BaseVa = (PBYTE)MmGetMdlVirtualAddress(CurrentMdl) + CurrentOffset;
	    NewMdlLength 	= MmGetMdlByteCount (CurrentMdl) - CurrentOffset;

        // if Mdl has more bytes than what's needed, set available to what's needed
        if (NewMdlLength > BytesToDescribe)
        {
            NewMdlLength = BytesToDescribe;
        }

	    pMdl = IoAllocateMdl(BaseVa,
		    			     NewMdlLength,
			    			 FALSE,
				    		 FALSE,
					    	 NULL);

        // store the first mdl for return
        if (!Destination)
        {
	        Destination = pMdl;

            // subsequent Mdl's must start with offset 0!!
            CurrentOffset = 0;
        }
        else
        {
            // link-in to the earlier mdl
            NewMdl->Next = pMdl;
        }

        NewMdl = pMdl;

        if (pMdl != NULL)
        {
            ATALK_DBG_INC_COUNT(AtalkDbgMdlsAlloced);

#ifdef	PROFILING
		    INTERLOCKED_INCREMENT_LONG(
			    &AtalkStatistics.stat_CurMdlCount,
			    &AtalkStatsLock.SpinLock);
#endif
		    IoBuildPartialMdl(CurrentMdl,
			    			  pMdl,
				    		  BaseVa,
					    	  NewMdlLength);
        }
        else
        {
            // free up whatever was allocated so far
            while (Destination)
            {
                pMdl = Destination->Next;
                IoFreeMdl(Destination);
                ATALK_DBG_DEC_COUNT(AtalkDbgMdlsAlloced);
                Destination = pMdl;
            }
        }

        BytesToDescribe -= NewMdlLength;
        CurrentMdl = CurrentMdl->Next;
    }

    return(Destination);

}




PAMDL
AtalkAllocAMdl(
	IN	PBYTE	pBuffer	OPTIONAL,
	IN	LONG	Size
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	PMDL	pMdl = NULL;

	if (pBuffer == NULL)
	{
		pBuffer = AtalkAllocMemory(Size);
	}

	if ((pBuffer == NULL) ||
		((pMdl = IoAllocateMdl(pBuffer, Size, FALSE, FALSE, NULL)) == NULL))
	{
		LOG_ERROR(EVENT_ATALK_RESOURCES, STATUS_INSUFFICIENT_RESOURCES, NULL, 0);
	}
#ifdef	PROFILING
	else
	{
		INTERLOCKED_INCREMENT_LONG(
			&AtalkStatistics.stat_CurMdlCount,
			&AtalkStatsLock.SpinLock);
	}
#endif

	if (pMdl != NULL)
    {
        ATALK_DBG_INC_COUNT(AtalkDbgMdlsAlloced);

		MmBuildMdlForNonPagedPool(pMdl);
    }

	return(pMdl);
}




LONG FASTCALL
AtalkSizeMdlChain(
	IN	PAMDL	pAMdlChain
	)
/*++

Routine Description:


Arguments:


Return Value:


--*/
{
	LONG	Size;

	for (Size = 0; pAMdlChain != NULL; pAMdlChain = pAMdlChain->Next)
	{
		Size += MmGetMdlByteCount(pAMdlChain);
	}
	return(Size);
}



/***	AtalkBPAllocBlock
 *
 *	Alloc a block of memory from the block pool package. This is written to speed up
 *	operations where a lot of small fixed size allocations/frees happen. Going to
 *	ExAllocPool() in these cases is expensive.
 *
 *	It is important to keep the list of blocks in such a way that all completely free
 *	blocks are at the tail end of the list.
 */
PVOID FASTCALL
AtalkBPAllocBlock(
	IN	BLKID	BlockId
)
{
	PBLK_HDR			pBlk = NULL;
	PBLK_CHUNK			pChunk, *ppChunkHead;
	KIRQL				OldIrql;
	USHORT				BlkSize;
	ATALK_SPIN_LOCK *	pLock;
	NDIS_STATUS			ndisStatus;
#ifdef	PROFILING
	TIME				TimeS, TimeE, TimeD;

	TimeS = KeQueryPerformanceCounter(NULL);
#endif

	ASSERT (BlockId < NUM_BLKIDS);

	BlkSize = atalkBlkSize[BlockId];
	ppChunkHead = &atalkBPHead[BlockId];

	pLock = &atalkBPLock[BlockId];
	ACQUIRE_SPIN_LOCK(pLock, &OldIrql);

	if ((pChunk = *ppChunkHead) != NULL)
	{
		ASSERT(VALID_BC(pChunk));
		ASSERT(pChunk->bc_BlkId == BlockId);
		ASSERT((pChunk->bc_NumFree + pChunk->bc_NumAlloc) == atalkNumBlks[BlockId]);

		if (pChunk->bc_NumFree > 0)
		{
			// This is where we take it off from
			DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_INFO,
					("AtalkBPAllocBlock: Found space in Chunk %lx\n", pChunk));
#ifdef	PROFILING
			INTERLOCKED_INCREMENT_LONG( &AtalkStatistics.stat_NumBPHits,
										&AtalkStatsLock.SpinLock);
#endif
		}

		if (pChunk->bc_NumFree == 0)
		{
			// We do not have space on any of the chunks on this list
			ASSERT(pChunk->bc_NumAlloc == atalkNumBlks[BlockId]);
			pChunk = NULL;
		}
	}
	
	if (pChunk == NULL)
	{
		DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_INFO,
				("AtalkBPAllocBlock: Allocating a new chunk for Id %d\n", BlockId));

#ifdef	PROFILING
		INTERLOCKED_INCREMENT_LONG( &AtalkStatistics.stat_NumBPMisses,
									&AtalkStatsLock.SpinLock);
#endif
		pChunk = AtalkAllocMemory(atalkChunkSize[BlockId]);
		if (pChunk != NULL)
		{
			LONG		i, j;
			PBLK_HDR	pBlkHdr;
			PBUFFER_HDR	pBufHdr;
			USHORT		NumBlksPerChunk;

#if	DBG
			pChunk->bc_Signature = BC_SIGNATURE;
			pChunk->bc_NumAlloc = 0;
#endif
			NumBlksPerChunk = atalkNumBlks[BlockId];
			ASSERT (NumBlksPerChunk <= 0xFF);
			pChunk->bc_NumFree = (BYTE)NumBlksPerChunk;
			pChunk->bc_BlkId = BlockId;
			pChunk->bc_FreeHead = (PBLK_HDR)((PBYTE)pChunk + sizeof(BLK_CHUNK));

			DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_INFO,
			    ("AtalkBPAllocBlock: Initializing chunk %lx, BlkId=%d\n", pChunk,BlockId));

			// Initialize the blocks in the chunk
			for (i = 0, pBlkHdr = pChunk->bc_FreeHead;
				 i < NumBlksPerChunk;
				 i++, pBlkHdr = pBlkHdr->bh_Next)
			{
				LONG		Size;
#if	DBG
				pBlkHdr->bh_Signature = BH_SIGNATURE;
#endif
				pBlkHdr->bh_Next = (i == (NumBlksPerChunk-1)) ?
										NULL :
										(PBLK_HDR)((PBYTE)pBlkHdr + BlkSize);

				if (BlockId >= BLKID_NEED_NDIS_INT)
				{
                    PCHAR   pStartOfBuf;

					// We need to initialize the Ndis stuff for these guys
					pBufHdr = (PBUFFER_HDR)((PBYTE)pBlkHdr + sizeof(BLK_HDR));

					pBufHdr->bh_NdisPkt = NULL;

                    if (BlockId == BLKID_SENDBUF)
                    {
                        Size = sizeof(BUFFER_HDR) + sizeof(BUFFER_DESC);
                        pStartOfBuf = (PCHAR)pBufHdr + Size;
                    }
                    else if ((BlockId == BLKID_MNP_SMSENDBUF) ||
                             (BlockId == BLKID_MNP_LGSENDBUF) )
                    {
                        // NOTE: the 1 byte of Buffer[1], combined with aligning
                        // effect screws up Size (it's 3 more than what we think!)
                        Size = sizeof(MNPSENDBUF);
                        pStartOfBuf = &(((PMNPSENDBUF)pBufHdr)->Buffer[0]);
                    }
                    else
                    {
                        Size = sizeof(BUFFER_HDR);
                        pStartOfBuf = (PCHAR)pBufHdr + Size;
                    }

					//  Make a NDIS buffer descriptor for this data
					NdisAllocateBuffer(&ndisStatus,
									   &pBufHdr->bh_NdisBuffer,
									   AtalkNdisBufferPoolHandle,
									   pStartOfBuf,
									   (UINT)(BlkSize - sizeof(BLK_HDR) - Size));
				
					if (ndisStatus != NDIS_STATUS_SUCCESS)
					{
						LOG_ERROR(EVENT_ATALK_NDISRESOURCES, ndisStatus, NULL, 0);

		                DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_ERR,
				            ("NdisAllocateBuffer: Ndis Out-of-Resource condition hit\n"));

                        ASSERT(0);
						break;
					}
				
                    ATALK_DBG_INC_COUNT(AtalkDbgMdlsAlloced);

					// More processing for send buffers
					if ((BlockId == BLKID_SENDBUF) ||
                        (BlockId == BLKID_MNP_SMSENDBUF) ||
                        (BlockId == BLKID_MNP_LGSENDBUF))
					{
						PSENDBUF		pSendBuf;
						PBUFFER_DESC	pBuffDesc;
	                    PMNPSENDBUF	    pMnpSendBuf;

					    if (BlockId == BLKID_SENDBUF)
                        {
						    pSendBuf = (PSENDBUF)pBufHdr;
						    pBuffDesc = &pSendBuf->sb_BuffDesc;
						    pBuffDesc->bd_Length 	= MAX_SENDBUF_LEN;
						    pBuffDesc->bd_CharBuffer= pSendBuf->sb_Space;
                        }
                        else if (BlockId == BLKID_MNP_SMSENDBUF)
                        {
						    pMnpSendBuf = (PMNPSENDBUF)pBufHdr;
                            pMnpSendBuf->DataSize = 0;
                            pMnpSendBuf->FreeBuffer = &pMnpSendBuf->Buffer[0];
						    pBuffDesc = &pMnpSendBuf->sb_BuffDesc;
						    pBuffDesc->bd_Length 	= MNP_MINSEND_LEN;
						    pBuffDesc->bd_CharBuffer= &pMnpSendBuf->Buffer[0];
                        }
                        else // if (BlockId == BLKID_MNP_LGSENDBUF)
                        {
						    pMnpSendBuf = (PMNPSENDBUF)pBufHdr;
                            pMnpSendBuf->DataSize = 0;
                            pMnpSendBuf->FreeBuffer = &pMnpSendBuf->Buffer[0];
						    pBuffDesc = &pMnpSendBuf->sb_BuffDesc;
						    pBuffDesc->bd_Length 	= MNP_MAXSEND_LEN;
						    pBuffDesc->bd_CharBuffer= &pMnpSendBuf->Buffer[0];
                        }
#if	DBG
						pBuffDesc->bd_Signature = BD_SIGNATURE;
#endif
						pBuffDesc->bd_Flags 	= BD_CHAR_BUFFER;
						pBuffDesc->bd_Next 		= NULL;
						pBuffDesc->bd_FreeBuffer= NULL;
					}
				}

			}
			if (i != NumBlksPerChunk)
			{
				// This has to be a failure from Ndis !!!
				// Undo a bunch of stuff
				ASSERT (BlockId >= BLKID_NEED_NDIS_INT);
				DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_ERR,
						("AtalkBPAllocBlock: Freeing new chunk (Ndis failure) Id %d\n",
						BlockId));
				pBlkHdr = pChunk->bc_FreeHead;
				for (j = 0, pBlkHdr = pChunk->bc_FreeHead;
					 j < i; j++, pBlkHdr = pBlkHdr->bh_Next)
				{
					PBUFFER_HDR	pBufHdr;

					pBufHdr = (PBUFFER_HDR)((PBYTE)pBlkHdr + sizeof(BLK_HDR));
					AtalkNdisFreeBuffer(pBufHdr->bh_NdisBuffer);
				}
				AtalkFreeMemory(pChunk);
				pChunk = NULL;
			}
			else
			{
				// Successfully initialized the chunk, link it in
				AtalkLinkDoubleAtHead(*ppChunkHead, pChunk, bc_Next, bc_Prev);
#if	DBG
				atalkNumChunksForId[BlockId] ++;
#endif
			}
		}
	}	
	if (pChunk != NULL)
	{
		PBLK_CHUNK	pTmp;

		ASSERT(VALID_BC(pChunk));
		ASSERT(pChunk->bc_BlkId == BlockId);
		DBGPRINT(DBG_COMP_RESOURCES, DBG_LEVEL_INFO,
				("AtalkBPAllocBlock: Allocating a block out of chunk %lx(%d,%d) for Id %d\n",
				pChunk, pChunk->bc_NumFree, pChunk->bc_NumAlloc, BlockId));

		pBlk = pChunk->bc_FreeHead;
		ASSERT(VALID_BH(pBlk));

		pChunk->bc_FreeHead = pBlk->bh_Next;
		pBlk->bh_pChunk = pChunk;
		pChunk->bc_Age = 0;			// Reset age
		pChunk->bc_NumFree --;
#if	DBG
		pChunk->bc_NumAlloc ++;
#endif
		// If the block is now empty, unlink it from here and move it
		// to the first empty slot. We know that all blocks 'earlier' than
		// this are non-empty.
		if ((pChunk->bc_NumFree == 0) &&
			((pTmp = pChunk->bc_Next) != NULL) &&
			(pTmp->bc_NumFree > 0))
		{
			ASSERT(pChunk->bc_NumAlloc == atalkNumBlks[BlockId]);
			AtalkUnlinkDouble(pChunk, bc_Next, bc_Prev);
			for (; pTmp != NULL; pTmp = pTmp->bc_Next)
			{
				ASSERT(VALID_BC(pTmp));
				if (pTmp->bc_NumFree == 0)
				{
					ASSERT(pTmp->bc_NumAlloc == atalkNumBlks[BlockId]);
					// Found a free one. Park it right here.
					AtalkInsertDoubleBefore(pChunk, pTmp, bc_Next, bc_Prev);
					break;
				}
				else if (pTmp->bc_Next == NULL)	// We reached the end
				{
					AtalkLinkDoubleAtEnd(pChunk, pTmp, bc_Next, bc_Prev);
					break;
				}
			}
		}
	}

	if (pBlk != NULL)
	{
        //
        // we allocate Ndis Packets for ARAP guys later, when we really need
        //
		if ((BlockId >= BLKID_NEED_NDIS_INT) &&
            (BlockId != BLKID_MNP_SMSENDBUF) &&
            (BlockId != BLKID_MNP_LGSENDBUF))
		{
			PBUFFER_HDR	pBufHdr;

			// We need to initialize the Ndis stuff for these guys
			pBufHdr = (PBUFFER_HDR)((PBYTE)pBlk + sizeof(BLK_HDR));

			pBufHdr->bh_NdisPkt = NULL;

			//  Allocate an NDIS packet descriptor from the global packet pool
			NdisDprAllocatePacket(&ndisStatus,
								  &pBufHdr->bh_NdisPkt,
								  AtalkNdisPacketPoolHandle);
			
			if (ndisStatus != NDIS_STATUS_SUCCESS)
			{
				LOG_ERROR(EVENT_ATALK_NDISRESOURCES, ndisStatus, NULL, 0);

		        DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_ERR,
				    ("NdisDprAllocatePacket: Ndis Out-of-Resource condition hit\n"));

                ASSERT(0);

                RELEASE_SPIN_LOCK(pLock, OldIrql);
				AtalkBPFreeBlock(pBufHdr);
				return(NULL);
			}

			//  Link the buffer descriptor into the packet descriptor
			RtlZeroMemory(pBufHdr->bh_NdisPkt->ProtocolReserved, sizeof(PROTOCOL_RESD));
			NdisChainBufferAtBack(pBufHdr->bh_NdisPkt,
								  pBufHdr->bh_NdisBuffer);
			((PPROTOCOL_RESD)(pBufHdr->bh_NdisPkt->ProtocolReserved))->Receive.pr_BufHdr = pBufHdr;
		}
		++pBlk;
#if	DBG
		atalkBlksForId[BlockId] ++;
#endif
	}

	RELEASE_SPIN_LOCK(pLock, OldIrql);

#ifdef	PROFILING
	INTERLOCKED_INCREMENT_LONG(&AtalkStatistics.stat_BPAllocCount,
							   &AtalkStatsLock.SpinLock);
	TimeE = KeQueryPerformanceCounter(NULL);
	TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart;
	INTERLOCKED_ADD_LARGE_INTGR(&AtalkStatistics.stat_BPAllocTime,
								 TimeD,
								 &AtalkStatsLock.SpinLock);
#endif
	return pBlk;
}


/***	atalkBPFreeBlock
 *
 *	Return a block to its owning chunk.
 */
VOID FASTCALL
AtalkBPFreeBlock(
	IN	PVOID		pBlock
)
{
	PBLK_CHUNK			pChunk;
	PBLK_HDR			pBlkHdr;
	BLKID				BlockId;
	KIRQL				OldIrql;
	ATALK_SPIN_LOCK *	pLock;
#ifdef	PROFILING
	TIME				TimeS, TimeE, TimeD;

	TimeS = KeQueryPerformanceCounter(NULL);
#endif

	pBlkHdr = (PBLK_HDR)((PCHAR)pBlock - sizeof(BLK_HDR));
	ASSERT(VALID_BH(pBlkHdr));

	pChunk = pBlkHdr->bh_pChunk;
	ASSERT(VALID_BC(pChunk));

	BlockId = pChunk->bc_BlkId;
	pLock = &atalkBPLock[BlockId];

	ACQUIRE_SPIN_LOCK(pLock, &OldIrql);

	if (BlockId >= BLKID_NEED_NDIS_INT)
	{
		PBUFFER_HDR	pBufHdr;

		// We need to free the ndis packet here - if present
		pBufHdr = (PBUFFER_HDR)pBlock;

		if (pBufHdr->bh_NdisPkt != NULL)
		{
		    NdisDprFreePacket(pBufHdr->bh_NdisPkt);
		    pBufHdr->bh_NdisPkt = NULL;
		}
	}

	DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_INFO,
			("AtalkBPFreeBlock: Returning Block %lx to chunk %lx for Id %d\n",
			pBlkHdr, pChunk, BlockId));

	ASSERT (pChunk->bc_NumFree < atalkNumBlks[BlockId]);
#if	DBG
	atalkBlksForId[BlockId] --;
	pChunk->bc_NumAlloc --;
#endif

	pChunk->bc_NumFree ++;
	ASSERT((pChunk->bc_NumFree + pChunk->bc_NumAlloc) == atalkNumBlks[BlockId]);
	pBlkHdr->bh_Next = pChunk->bc_FreeHead;
	pChunk->bc_FreeHead = pBlkHdr;

	// If this block's status is changing from a 'none available' to 'available'
	// move him to the head of the list
	if (pChunk->bc_NumFree == 1)
	{
		AtalkUnlinkDouble(pChunk, bc_Next, bc_Prev);
		AtalkLinkDoubleAtHead(atalkBPHead[BlockId],
							pChunk,
							bc_Next,
							bc_Prev);
	}

#ifdef	PROFILING
	INTERLOCKED_INCREMENT_LONG(&AtalkStatistics.stat_BPFreeCount,
							   &AtalkStatsLock.SpinLock);
	TimeE = KeQueryPerformanceCounter(NULL);
	TimeD.QuadPart = TimeE.QuadPart - TimeS.QuadPart;
	INTERLOCKED_ADD_LARGE_INTGR_DPC(&AtalkStatistics.stat_BPFreeTime,
									TimeD,
									&AtalkStatsLock.SpinLock);
#endif

	RELEASE_SPIN_LOCK(pLock, OldIrql);
}



/***	atalkBPAgePool
 *
 *	Age out the block pool of unused blocks
 */
LOCAL LONG FASTCALL
atalkBPAgePool(
	IN PTIMERLIST 	Context,
	IN BOOLEAN		TimerShuttingDown
)
{
	PBLK_CHUNK	pChunk;
	LONG		i, j, NumBlksPerChunk;

	if (TimerShuttingDown)
	{
		return ATALK_TIMER_NO_REQUEUE;
	}

	for (i = 0; i < NUM_BLKIDS; i++)
	{
		NumBlksPerChunk = atalkNumBlks[i];
		ACQUIRE_SPIN_LOCK_DPC(&atalkBPLock[i]);
	
		for (pChunk = atalkBPHead[i];
			 pChunk != NULL; )
		{
			PBLK_CHUNK	pFree;

			ASSERT(VALID_BC(pChunk));

			pFree = pChunk;
			pChunk = pChunk->bc_Next;

			// Since all blocks which are completely used up are at the tail end of
			// the list, if we encounter one, we are done.
			if (pFree->bc_NumFree == 0)
				break;

			if (pFree->bc_NumFree == NumBlksPerChunk)
			{
				DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_WARN,
						("atalkBPAgePool: Aging Chunk %lx, Id %d NumFree %d\n",
						pFree, pFree->bc_BlkId,pFree->bc_NumFree));
	
				if (++(pFree->bc_Age) >= MAX_BLOCK_POOL_AGE)
				{
					DBGPRINT(DBG_COMP_SYSTEM, DBG_LEVEL_WARN,
							("atalkBPAgePool: freeing Chunk %lx, Id %d\n",
							pFree, pFree->bc_BlkId));
					AtalkUnlinkDouble(pFree, bc_Next, bc_Prev);
#ifdef	PROFILING
					INTERLOCKED_INCREMENT_LONG( &AtalkStatistics.stat_NumBPAge,
											&AtalkStatsLock.SpinLock);
#endif
					if (pFree->bc_BlkId >= BLKID_NEED_NDIS_INT)
					{
						PBLK_HDR	pBlkHdr;
	
						// We need to free Ndis stuff for these guys
						pBlkHdr = pFree->bc_FreeHead;
						for (j = 0, pBlkHdr = pFree->bc_FreeHead;
							 j < NumBlksPerChunk;
							 j++, pBlkHdr = pBlkHdr->bh_Next)
						{
							PBUFFER_HDR	pBufHdr;
	
							pBufHdr = (PBUFFER_HDR)((PBYTE)pBlkHdr + sizeof(BLK_HDR));
	
							ASSERT(pBufHdr->bh_NdisPkt == NULL);
							AtalkNdisFreeBuffer(pBufHdr->bh_NdisBuffer);
						}
					}
					AtalkFreeMemory(pFree);
#if	DBG
					atalkNumChunksForId[i] --;
#endif
				}
			}
		}
		RELEASE_SPIN_LOCK_DPC(&atalkBPLock[i]);
	}

	return ATALK_TIMER_REQUEUE;
}

#ifdef	TRACK_MEMORY_USAGE

#define	MAX_PTR_COUNT		4*1024
#define	MAX_MEM_USERS		512
LOCAL	ATALK_SPIN_LOCK		atalkMemTrackLock	= {0};
LOCAL	struct
{
	PVOID	mem_Ptr;
	ULONG	mem_FileLine;
} atalkMemPtrs[MAX_PTR_COUNT]	= {0};

LOCAL	struct
{
	ULONG	mem_FL;
	ULONG	mem_Count;
} atalkMemUsage[MAX_MEM_USERS]	= {0};

BOOLEAN NeverBeenFull=TRUE;

VOID
AtalkTrackMemoryUsage(
	IN	PVOID	pMem,
    IN  ULONG   Size,
	IN	BOOLEAN	Alloc,
	IN	ULONG	FileLine
	)
/*++

Routine Description:

 	Keep track of memory usage by storing and clearing away pointers as and
 	when they are allocated or freed. This helps in keeping track of memory
 	leaks.

Arguments:


Return Value:


--*/
{
	static int		i = 0;
	int				j, k;
	KIRQL				OldIrql;

	ACQUIRE_SPIN_LOCK(&atalkMemTrackLock, &OldIrql);

	if (Alloc)
	{
		for (j = 0; j < MAX_PTR_COUNT; i++, j++)
		{
			i = i & (MAX_PTR_COUNT-1);
			if (atalkMemPtrs[i].mem_Ptr == NULL)
			{
				atalkMemPtrs[i].mem_Ptr = pMem;
				atalkMemPtrs[i++].mem_FileLine = FileLine;
				break;
			}
		}

		for (k = 0; k < MAX_MEM_USERS; k++)
		{
			if (atalkMemUsage[k].mem_FL == FileLine)
			{
				atalkMemUsage[k].mem_Count += Size;
				break;
			}
		}
		if (k == MAX_MEM_USERS)
		{
			for (k = 0; k < MAX_MEM_USERS; k++)
			{
				if (atalkMemUsage[k].mem_FL == 0)
				{
					atalkMemUsage[k].mem_FL = FileLine;
					atalkMemUsage[k].mem_Count = Size;
					break;
				}
			}
			if (k == MAX_MEM_USERS)
			{
				DBGPRINT(DBG_COMP_RESOURCES, DBG_LEVEL_ERR,
					("AtalkTrackMemoryUsage: Out of space on atalkMemUsage !!!\n"));
			}
		}
	}
	else
	{
		for (j = 0, k = i; j < MAX_PTR_COUNT; j++, k--)
		{
			k = k & (MAX_PTR_COUNT-1);
			if (atalkMemPtrs[k].mem_Ptr == pMem)
			{
				atalkMemPtrs[k].mem_Ptr = 0;
				atalkMemPtrs[k].mem_FileLine = 0;
				break;
			}
		}

		for (k = 0; k < MAX_MEM_USERS; k++)
		{
			if (atalkMemUsage[k].mem_FL == FileLine)
			{
                if (atalkMemUsage[k].mem_Count >= Size)
                {
				    atalkMemUsage[k].mem_Count -= Size;
                }
				break;
			}
		}

	}

	RELEASE_SPIN_LOCK(&atalkMemTrackLock, OldIrql);

	if (j == MAX_PTR_COUNT)
	{
        if (NeverBeenFull)
        {
            NeverBeenFull = FALSE;
		    DBGPRINT(DBG_COMP_RESOURCES, DBG_LEVEL_ERR,
			    ("AtalkTrackMemoryUsage: %s\n", Alloc ? "Table Full" : "Can't find"));
        }
	}
}

#endif	// TRACK_MEMORY_USAGE

#ifdef	TRACK_BUFFDESC_USAGE

#define	MAX_BD_COUNT		1024*2
LOCAL	ATALK_SPIN_LOCK	atalkBdTrackLock	= {0};
LOCAL	struct
{
	PVOID	bdesc_Ptr;
	ULONG	bdesc_FileLine;
} atalkBuffDescPtrs[MAX_BD_COUNT]			= {0};


VOID
AtalkTrackBuffDescUsage(
	IN	PVOID	pBuffDesc,
	IN	BOOLEAN	Alloc,
	IN	ULONG	FileLine
	)
/*++

Routine Description:

 	Keep track of buffer-desc usage by storing and clearing away pointers as and
 	when they are allocated or freed. This helps in keeping track of memory
 	leaks.

Arguments:


Return Value:


--*/
{
	static int		i = 0;
	int				j, k;
	KIRQL				OldIrql;

	ACQUIRE_SPIN_LOCK(&atalkBdTrackLock, &OldIrql);

	if (Alloc)
	{
		for (j = 0; j < MAX_BD_COUNT; i++, j++)
		{
			i = i & (MAX_BD_COUNT-1);
			if (atalkBuffDescPtrs[i].bdesc_Ptr == NULL)
			{
				atalkBuffDescPtrs[i].bdesc_Ptr = pBuffDesc;
				atalkBuffDescPtrs[i++].bdesc_FileLine = FileLine;
				break;
			}
		}
	}
	else
	{
		for (j = 0, k = i; j < MAX_BD_COUNT; j++, k--)
		{
			k = k & (MAX_BD_COUNT-1);
			if (atalkBuffDescPtrs[k].bdesc_Ptr == pBuffDesc)
			{
				atalkBuffDescPtrs[k].bdesc_Ptr = 0;
				atalkBuffDescPtrs[k].bdesc_FileLine = 0;
				break;
			}
		}
	}

	RELEASE_SPIN_LOCK(&atalkBdTrackLock, OldIrql);

	if (j == MAX_BD_COUNT)
	{
		DBGPRINT(DBG_COMP_RESOURCES, DBG_LEVEL_INFO,
			("AtalkTrackBuffDescUsage: %s\n", Alloc ? "Table Full" : "Can't find"));
		ASSERT(0);
	}
}

#endif	// TRACK_BUFFDESC_USAGE