/*++

Copyright (c) 1989  Microsoft Corporation

Module Name:

    heapmgr.c

Abstract:

    This module contains initialization and termination routines for
    server FSP heap, as well as debug routines for memory tracking.

--*/

#include "precomp.h"
#include "heapmgr.tmh"
#pragma hdrstop

// Make the retry time 15 milli-seconds
#define SRV_LOW_PRIORITY_RETRY_TIME -1*1000*10*15

#ifdef POOL_TAGGING
//
// Array correlating BlockType numbers to pool tags.
//
// *** This array must be maintained in concert with the BlockType
//     definitions in srvblock.h!
//

ULONG SrvPoolTags[BlockTypeMax-1] = {
        'fbSL',     // BlockTypeBuffer
        'ncSL',     // BlockTypeConnection
        'peSL',     // BlockTypeEndpoint
        'flSL',     // BlockTypeLfcb
        'fmSL',     // BlockTypeMfcb
        'frSL',     // BlockTypeRfcb
        'rsSL',     // BlockTypeSearch
        'csSL',     // BlockTypeSearchCore
        'lbSL',     // BlockTypeByteRangeLock       for persistent handles
        'ssSL',     // BlockTypeSession
        'hsSL',     // BlockTypeShare
        'rtSL',     // BlockTypeTransaction
        'ctSL',     // BlockTypeTreeConnect
        'poSL',     // BlockTypeOplockBreak
        'dcSL',     // BlockTypeCommDevice
        'iwSL',     // BlockTypeWorkContextInitial
        'nwSL',     // BlockTypeWorkContextNormal
        'rwSL',     // BlockTypeWorkContextRaw
        'swSL',     // BlockTypeWorkContextSpecial
        'dcSL',     // BlockTypeCachedDirectory
        'bdSL',     // BlockTypeDataBuffer
        'btSL',     // BlockTypeTable
        'hnSL',     // BlockTypeNonpagedHeader
        'cpSL',     // BlockTypePagedConnection
        'rpSL',     // BlockTypePagedRfcb
        'mpSL',     // BlockTypePagedMfcb
        'itSL',     // BlockTypeTimer
        'caSL',     // BlockTypeAdminCheck
        'qwSL',     // BlockTypeWorkQueue
        'fsSL',     // BlockTypeDfs
        'rlSL',     // BlockTypeLargeReadX
        'saSL',     // BlockTypeAdapterStatus
        'rsSL',     // BlockTypeShareRemark
        'dsSL',     // BlockTypeShareSecurityDescriptor
        'ivSL',     // BlockTypeVolumeInformation
        'nfSL',     // BlockTypeFSName
        'inSL',     // BlockTypeNameInfo
        'idSL',     // BlockTypeDirectoryInfo
        'cdSL',     // BlockTypeDirCache
        'imSL',     // BlockTypeMisc
        'nsSL',     // BlockTypeSnapShot
#ifdef INCLUDE_SMB_PERSISTENT
        'spSL',     // BlockTypePersistentState
        'bpSL',     // BlockTypePersistentBitMap
        'hpSL',     // BlockTypePersistShareState
#endif
        };

//
// Macro to map from block type to pool tag.
//

#define TAG_FROM_TYPE(_type) SrvPoolTags[(_type)-1]

#else

#define TAG_FROM_TYPE(_type) ignoreme

#endif // def POOL_TAGGING

#ifdef ALLOC_PRAGMA
#pragma alloc_text( PAGE, SrvAllocatePagedPool )
#pragma alloc_text( PAGE, SrvFreePagedPool )
#pragma alloc_text( PAGE, SrvClearLookAsideList )
#endif
#if 0
NOT PAGEABLE -- SrvAllocateNonPagedPool
NOT PAGEABLE -- SrvFreeNonPagedPool
#endif

extern LONG SrvMemoryAllocationRetries;
extern LONG SrvMemoryAllocationRetriesSuccessful;


PVOID SRVFASTCALL
SrvInterlockedAllocate( PLOOK_ASIDE_LIST l, ULONG NumberOfBytes, PLONG statistics )
{
    PPOOL_HEADER newPool;
    PPOOL_HEADER *pentry = NumberOfBytes > LOOK_ASIDE_SWITCHOVER ?
                                            l->LargeFreeList : l->SmallFreeList;

    PPOOL_HEADER *pend   = pentry + LOOK_ASIDE_MAX_ELEMENTS;

    do {
        //
        // Exchange with the lookaside spot and see if we get anything
        //

        newPool = NULL;
        newPool = (PPOOL_HEADER)InterlockedExchangePointer( pentry, newPool );

        if( newPool == NULL ) {
            continue;
        }

        if( newPool->RequestedSize >= NumberOfBytes ) {
            //
            // The one we got is big enough!  Return it.
            //
            ++(l->AllocHit);
            return newPool + 1;
        }

        //
        // It wasn't big enough, so put it back.
        //
        newPool = (PPOOL_HEADER)InterlockedExchangePointer( pentry, newPool );
        if( newPool == NULL ) {
            continue;
        }

        //
        // Oops, somebody else freed some memory to this spot.  Can we use it?
        //
        if( newPool->RequestedSize >= NumberOfBytes ) {
            //
            // We can use it!
            //
            ++(l->AllocHit);
            return newPool + 1;
        }

        //
        // Can't use the memory -- so really free it and keep looking
        //
        if( statistics ) {
            InterlockedExchangeAdd(
                statistics,
                -(LONG)newPool->RequestedSize
                );
        }

        ExFreePool( newPool );

    } while( ++pentry < pend );

    ++(l->AllocMiss);
    return NULL;
}

PPOOL_HEADER SRVFASTCALL
SrvInterlockedFree( PPOOL_HEADER block )
{
    PPOOL_HEADER *pentry = block->FreeList;
    PPOOL_HEADER *pend   = pentry + LOOK_ASIDE_MAX_ELEMENTS;

    do {

        block = (PPOOL_HEADER)InterlockedExchangePointer( pentry, block );

    } while( block != NULL && ++pentry < pend );

    return block;
}

VOID SRVFASTCALL
SrvClearLookAsideList( PLOOK_ASIDE_LIST l, VOID (SRVFASTCALL *FreeRoutine )( PVOID ) )
{
    PPOOL_HEADER *pentry, *pend, block;

    PAGED_CODE();

    //
    // Clear out the list of large chunks
    //
    pentry = l->LargeFreeList;
    pend   = pentry + LOOK_ASIDE_MAX_ELEMENTS;

    do {
        block = NULL;
        block = (PPOOL_HEADER)InterlockedExchangePointer( pentry, block );

        if( block != NULL ) {
            block->FreeList = NULL;
            FreeRoutine( block + 1 );
        }

    } while( ++pentry < pend );

    //
    // Clear out the list of small chunks
    //
    pentry = l->SmallFreeList;
    pend   = pentry + LOOK_ASIDE_MAX_ELEMENTS;

    do {
        block = NULL;
        block = (PPOOL_HEADER)InterlockedExchangePointer( pentry, block );

        if( block != NULL ) {
            block->FreeList = NULL;
            FreeRoutine( block + 1 );
        }

    } while( ++pentry < pend );
}


PVOID SRVFASTCALL
SrvAllocateNonPagedPool (
    IN CLONG NumberOfBytes
#ifdef POOL_TAGGING
    , IN CLONG BlockType
#endif
    )

/*++

Routine Description:

    This routine allocates nonpaged pool in the server.  A check is
    made to ensure that the server's total nonpaged pool usage is below
    the configurable limit.

Arguments:

    NumberOfBytes - the number of bytes to allocate.

    BlockType - the type of block (used to pass pool tag to allocator)

Return Value:

    PVOID - a pointer to the allocated memory or NULL if the memory could
       not be allocated.

--*/

{
    PPOOL_HEADER newPool;
    PPOOL_HEADER *FreeList = NULL;
    ULONG newUsage;
    BOOLEAN IsLowPriority = FALSE;
    LARGE_INTEGER interval;

#ifdef POOL_TAGGING
    ASSERT( BlockType > 0 && BlockType < BlockTypeMax );
#endif

    //
    // Pull this allocation off the per-processor free list if we can
    //
    if( SrvWorkQueues ) {

        PWORK_QUEUE queue = PROCESSOR_TO_QUEUE();

        if( NumberOfBytes <= queue->NonPagedPoolLookAsideList.MaxSize ) {

            newPool = SrvInterlockedAllocate(
                                &queue->NonPagedPoolLookAsideList,
                                NumberOfBytes,
                                (PLONG)&SrvStatistics.CurrentNonPagedPoolUsage
                                );

            if( newPool != NULL ) {
                return newPool;
            }

            FreeList = NumberOfBytes > LOOK_ASIDE_SWITCHOVER ?
                                    queue->NonPagedPoolLookAsideList.LargeFreeList :
                                    queue->NonPagedPoolLookAsideList.SmallFreeList ;
        }
    }

    //
    // Account for this allocation in the statistics database and make
    // sure that this allocation will not put us over the limit of
    // nonpaged pool that we can allocate.
    //

    newUsage = InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentNonPagedPoolUsage,
                                       (LONG)NumberOfBytes );
    newUsage += NumberOfBytes;

    if ( newUsage > SrvMaxNonPagedPoolUsage ) {

        //
        // Count the failure, but do NOT log an event.  The scavenger
        // will log an event when it next wakes up.  This keeps us from
        // flooding the event log.
        //

        SrvNonPagedPoolLimitHitCount++;
        SrvStatistics.NonPagedPoolFailures++;

        InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentNonPagedPoolUsage,
                                -(LONG)NumberOfBytes );

        return NULL;

    }

    if (SrvStatistics.CurrentNonPagedPoolUsage > SrvStatistics.PeakNonPagedPoolUsage) {
        SrvStatistics.PeakNonPagedPoolUsage = SrvStatistics.CurrentNonPagedPoolUsage;
    }

    //
    // Do the actual memory allocation.  Allocate extra space so that we
    // can store the size of the allocation for the free routine.
    //
    if( NumberOfBytes > 2*4096 )
    {
        IsLowPriority = TRUE;
    }

    newPool = ExAllocatePoolWithTagPriority(
                NonPagedPool,
                NumberOfBytes + sizeof(POOL_HEADER),
                TAG_FROM_TYPE(BlockType),
                IsLowPriority ? LowPoolPriority : NormalPoolPriority
                );

    if( (newPool == NULL) && IsLowPriority && (KeGetCurrentIrql() <= APC_LEVEL) )
    {
        interval.QuadPart = SRV_LOW_PRIORITY_RETRY_TIME;
        InterlockedIncrement( &SrvMemoryAllocationRetries );

        // Wait and try again
        KeDelayExecutionThread( KernelMode, FALSE, &interval );

        newPool = ExAllocatePoolWithTagPriority(
                NonPagedPool,
                NumberOfBytes + sizeof(POOL_HEADER),
                TAG_FROM_TYPE(BlockType),
                LowPoolPriority
                );

        if( newPool )
        {
            InterlockedIncrement( &SrvMemoryAllocationRetriesSuccessful );
        }
    }


    //
    // If the system couldn't satisfy the request, return NULL.
    //

    if ( newPool != NULL ) {
        //
        // Save the size of this block in the extra space we allocated.
        //

        newPool->RequestedSize = NumberOfBytes;
        newPool->FreeList = FreeList;

        //
        // Return a pointer to the memory after the size longword.
        //

        return (PVOID)( newPool + 1 );
    }

    //
    // Count the failure, but do NOT log an event.  The scavenger
    // will log an event when it next wakes up.  This keeps us from
    // flooding the event log.
    //

    SrvStatistics.NonPagedPoolFailures++;


    InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentNonPagedPoolUsage,
                                -(LONG)NumberOfBytes );

    return NULL;

} // SrvAllocateNonPagedPool

VOID SRVFASTCALL
SrvFreeNonPagedPool (
    IN PVOID Address
    )

/*++

Routine Description:

    Frees the memory allocated by a call to SrvAllocateNonPagedPool.
    The statistics database is updated to reflect the current nonpaged
    pool usage.

Arguments:

    Address - the address of allocated memory returned by
        SrvAllocateNonPagedPool.

Return Value:

    None.

--*/

{
    PPOOL_HEADER actualBlock = (PPOOL_HEADER)Address - 1;

    //
    // See if we can stash this bit of memory away in the NonPagedPoolFreeList
    //
    if( actualBlock->FreeList ) {

        actualBlock = SrvInterlockedFree( actualBlock );
    }

    if( actualBlock != NULL ) {

        //
        // Update the nonpaged pool usage statistic.
        //
        InterlockedExchangeAdd(
                (PLONG)&SrvStatistics.CurrentNonPagedPoolUsage,
                -(LONG)actualBlock->RequestedSize
                );

        //
        // Free the pool and return.
        //

        ExFreePool( actualBlock );
    }

} // SrvFreeNonPagedPool


PVOID SRVFASTCALL
SrvAllocatePagedPool (
    IN POOL_TYPE PoolType,
    IN CLONG NumberOfBytes
#ifdef POOL_TAGGING
    , IN CLONG BlockType
#endif
    )

/*++

Routine Description:

    This routine allocates Paged pool in the server.  A check is
    made to ensure that the server's total Paged pool usage is below
    the configurable limit.

Arguments:

    NumberOfBytes - the number of bytes to allocate.

    BlockType - the type of block (used to pass pool tag to allocator)

Return Value:

    PVOID - a pointer to the allocated memory or NULL if the memory could
       not be allocated.

--*/

{
    PPOOL_HEADER newPool;
    PPOOL_HEADER *FreeList = NULL;
    ULONG newUsage;
    BOOLEAN IsLowPriority = FALSE;
    LARGE_INTEGER interval;

    PAGED_CODE();

#ifdef POOL_TAGGING
    ASSERT( BlockType > 0 && BlockType < BlockTypeMax );
#endif

    //
    // Pull this allocation off the per-processor free list if we can
    //
    if( SrvWorkQueues ) {

        PWORK_QUEUE queue = PROCESSOR_TO_QUEUE();

        if( NumberOfBytes <= queue->PagedPoolLookAsideList.MaxSize ) {

            newPool = SrvInterlockedAllocate(
                                &queue->PagedPoolLookAsideList,
                                NumberOfBytes,
                                (PLONG)&SrvStatistics.CurrentPagedPoolUsage
                              );

            if( newPool != NULL ) {
                return newPool;
            }

            FreeList = NumberOfBytes > LOOK_ASIDE_SWITCHOVER ?
                                    queue->PagedPoolLookAsideList.LargeFreeList :
                                    queue->PagedPoolLookAsideList.SmallFreeList ;
        }
    }

    //
    // Account for this allocation in the statistics database and make
    // sure that this allocation will not put us over the limit of
    // nonpaged pool that we can allocate.
    //


    newUsage = InterlockedExchangeAdd(  (PLONG)&SrvStatistics.CurrentPagedPoolUsage,
                                        (LONG)NumberOfBytes );
    newUsage += NumberOfBytes;

    if ( newUsage > SrvMaxPagedPoolUsage ) {

        //
        // Count the failure, but do NOT log an event.  The scavenger
        // will log an event when it next wakes up.  This keeps us from
        // flooding the event log.
        //

        SrvPagedPoolLimitHitCount++;
        SrvStatistics.PagedPoolFailures++;

        InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentPagedPoolUsage,
                                -(LONG)NumberOfBytes );

        return NULL;
    }

    if (SrvStatistics.CurrentPagedPoolUsage > SrvStatistics.PeakPagedPoolUsage ) {
        SrvStatistics.PeakPagedPoolUsage = SrvStatistics.CurrentPagedPoolUsage;
    }

    //
    // Do the actual memory allocation.  Allocate extra space so that we
    // can store the size of the allocation for the free routine.
    //
    if( NumberOfBytes > 2*4096 )
    {
        IsLowPriority = TRUE;
    }

    newPool = ExAllocatePoolWithTagPriority(
                PoolType,
                NumberOfBytes + sizeof(POOL_HEADER),
                TAG_FROM_TYPE(BlockType),
                IsLowPriority ? LowPoolPriority : NormalPoolPriority
                );

    if( (newPool == NULL) && IsLowPriority && (KeGetCurrentIrql() <= APC_LEVEL) )
    {
        interval.QuadPart = SRV_LOW_PRIORITY_RETRY_TIME;
        InterlockedIncrement( &SrvMemoryAllocationRetries );

        // Wait and try again
        KeDelayExecutionThread( KernelMode, FALSE, &interval );

        newPool = ExAllocatePoolWithTagPriority(
                PoolType,
                NumberOfBytes + sizeof(POOL_HEADER),
                TAG_FROM_TYPE(BlockType),
                LowPoolPriority
                );

        if( newPool )
        {
            InterlockedIncrement( &SrvMemoryAllocationRetriesSuccessful );
        }
    }

    if( newPool != NULL ) {

        newPool->FreeList = FreeList;
        newPool->RequestedSize = NumberOfBytes;

        //
        // Return a pointer to the memory after the POOL_HEADER
        //

        return newPool + 1;
    }

    //
    // If the system couldn't satisfy the request, return NULL.
    //
    // Count the failure, but do NOT log an event.  The scavenger
    // will log an event when it next wakes up.  This keeps us from
    // flooding the event log.
    //

    SrvStatistics.PagedPoolFailures++;

    InterlockedExchangeAdd( (PLONG)&SrvStatistics.CurrentPagedPoolUsage,
                            -(LONG)NumberOfBytes );


    return NULL;


} // SrvAllocatePagedPool

VOID SRVFASTCALL
SrvFreePagedPool (
    IN PVOID Address
    )

/*++

Routine Description:

    Frees the memory allocated by a call to SrvAllocatePagedPool.
    The statistics database is updated to reflect the current Paged
    pool usage.  If this routine is change, look at scavengr.c

Arguments:

    Address - the address of allocated memory returned by
        SrvAllocatePagedPool.

Return Value:

    None.

--*/

{
    PPOOL_HEADER actualBlock = (PPOOL_HEADER)Address - 1;

    PAGED_CODE();

    ASSERT( actualBlock != NULL );

    //
    // See if we can stash this bit of memory away in the PagedPoolFreeList
    //
    if( actualBlock->FreeList ) {

        actualBlock = SrvInterlockedFree( actualBlock );
    }

    if( actualBlock != NULL ) {

        //
        // Update the Paged pool usage statistic.
        //

        ASSERT( SrvStatistics.CurrentPagedPoolUsage >= actualBlock->RequestedSize );

        InterlockedExchangeAdd(
            (PLONG)&SrvStatistics.CurrentPagedPoolUsage,
            -(LONG)actualBlock->RequestedSize
            );

        ASSERT( (LONG)SrvStatistics.CurrentPagedPoolUsage >= 0 );

        //
        // Free the pool and return.
        //

        ExFreePool( actualBlock );
    }

} // SrvFreePagedPool