/*++

Copyright (c) 1999-2000  Microsoft Corporation

Module Name:

    pplasl9x.c

Abstract:

    This file contains the implementation of lookaside
    list manager.

Author:

    Scott Holden (sholden) 14-Apr-2000

--*/

#include "wdm.h"
#include "ndis.h"
#include "cxport.h"
#include "pplasl.h"

// Keep scan period at one second -- this will make TCP/IP more responsive to 
// short bursts.
#define MAXIMUM_SCAN_PERIOD             1
#define MINIMUM_ALLOCATION_THRESHOLD    25
#define MINIMUM_LOOKASIDE_DEPTH         10



LIST_ENTRY PplLookasideListHead;
KSPIN_LOCK PplLookasideLock;
CTETimer PplTimer;
ULONG PplCurrentScanPeriod = 1;

VOID PplTimeout(CTEEvent * Timer, PVOID Context);

BOOLEAN
PplInit(VOID)
{
    InitializeListHead(&PplLookasideListHead);
    CTEInitTimer(&PplTimer);
    KeInitializeSpinLock(&PplLookasideLock);
    CTEStartTimer(&PplTimer, 1000L, PplTimeout, NULL);

    return TRUE;
}

VOID 
PplDeinit(VOID)
{
    CTEStopTimer(&PplTimer);
    return;
}

HANDLE
PplCreatePool(
    IN PALLOCATE_FUNCTION Allocate,
    IN PFREE_FUNCTION Free,
    IN ULONG Flags,
    IN SIZE_T Size,
    IN ULONG Tag,
    IN USHORT Depth
    )
{
    HANDLE PoolHandle;
    SIZE_T PoolSize;
    PNPAGED_LOOKASIDE_LIST Lookaside;

    PoolSize = sizeof(NPAGED_LOOKASIDE_LIST);

    PoolHandle = ExAllocatePoolWithTag(NonPagedPool, PoolSize, Tag);

    if (PoolHandle) {

        Lookaside = (PNPAGED_LOOKASIDE_LIST)PoolHandle;

        ExInitializeSListHead(&Lookaside->L.ListHead);
        Lookaside->L.Depth = MINIMUM_LOOKASIDE_DEPTH;
        Lookaside->L.MaximumDepth = Depth;
        Lookaside->L.TotalAllocates = 0;
        Lookaside->L.AllocateMisses = 0;
        Lookaside->L.TotalFrees = 0;
        Lookaside->L.FreeMisses = 0;
        Lookaside->L.Type = NonPagedPool | Flags;
        Lookaside->L.Tag = Tag;
        Lookaside->L.Size = Size;

        if (Allocate == NULL) {
            Lookaside->L.Allocate = ExAllocatePoolWithTag;
        } else {
            Lookaside->L.Allocate = Allocate;
        }

        if (Free == NULL) {
            Lookaside->L.Free = ExFreePool;
        } else {
            Lookaside->L.Free = Free;
        }

        Lookaside->L.LastTotalAllocates = 0;
        Lookaside->L.LastAllocateMisses = 0;
        
        KeInitializeSpinLock(&Lookaside->Lock);

        //
        // Insert the lookaside list structure the PPL lookaside list.
        //

        ExInterlockedInsertTailList(&PplLookasideListHead,
                                    &Lookaside->L.ListEntry,
                                    &PplLookasideLock);

    }

    return PoolHandle;
}

VOID
PplDestroyPool(
    IN HANDLE PoolHandle
    )
{
    PNPAGED_LOOKASIDE_LIST Lookaside;
    PVOID Entry;
    KIRQL OldIrql;

    if (PoolHandle == NULL) {
        return;
    }

    Lookaside = (PNPAGED_LOOKASIDE_LIST)PoolHandle;

    //
    // Acquire the nonpaged system lookaside list lock and remove the
    // specified lookaside list structure from the list.
    //

    ExAcquireSpinLock(&PplLookasideLock, &OldIrql);
    RemoveEntryList(&Lookaside->L.ListEntry);
    ExReleaseSpinLock(&PplLookasideLock, OldIrql);

    //
    // Remove all pool entries from the specified lookaside structure
    // and free them.
    //

    while ((Entry = ExAllocateFromNPagedLookasideList(Lookaside)) != NULL) {
        (Lookaside->L.Free)(Entry);
    }

    ExFreePool(PoolHandle);

    return;
}

PVOID
PplAllocate(
    IN HANDLE PoolHandle,
    OUT LOGICAL *FromList
    )
{
    PNPAGED_LOOKASIDE_LIST Lookaside;
    PVOID Entry;

    // Assume we'll get the item from the lookaside list.
    //
    *FromList = TRUE;

    Lookaside = (PNPAGED_LOOKASIDE_LIST)PoolHandle;

    Lookaside->L.TotalAllocates += 1;

    Entry = ExInterlockedPopEntrySList(&Lookaside->L.ListHead, &Lookaside->Lock);

    if (Entry == NULL) {
        Lookaside->L.AllocateMisses += 1;
        Entry = (Lookaside->L.Allocate)(Lookaside->L.Type,
                                        Lookaside->L.Size,
                                        Lookaside->L.Tag);
        *FromList = FALSE;
    }

    return Entry;
}

VOID
PplFree(
    IN HANDLE PoolHandle,
    IN PVOID Entry
    )
{
    PNPAGED_LOOKASIDE_LIST Lookaside = (PNPAGED_LOOKASIDE_LIST)PoolHandle;

    Lookaside->L.TotalFrees += 1;
    if (ExQueryDepthSList(&Lookaside->L.ListHead) >= Lookaside->L.Depth) {
        Lookaside->L.FreeMisses += 1;
        (Lookaside->L.Free)(Entry);

    } else {
        ExInterlockedPushEntrySList(&Lookaside->L.ListHead,
                                    (PSINGLE_LIST_ENTRY)Entry,
                                    &Lookaside->Lock);
    }

    return;
}

LOGICAL
PplComputeLookasideDepth (
    IN ULONG Allocates,
    IN ULONG Misses,
    IN USHORT MaximumDepth,
    IN OUT PUSHORT Depth
    )

/*++

Routine Description:

    This function computes the target depth of a lookaside list given the
    total allocations and misses during the last scan period and the current
    depth.

Arguments:

    Allocates - Supplies the total number of allocations during the last
        scan period.

    Misses - Supplies the total number of allocate misses during the last
        scan period.

    MaximumDepth - Supplies the maximum depth the lookaside list is allowed
        to reach.

    Depth - Supplies a pointer to the current lookaside list depth which
        receives the target depth.

Return Value:

    If the target depth is greater than the current depth, then a value of
    TRUE is returned as the function value. Otherwise, a value of FALSE is
    returned.

--*/

{

    LOGICAL Changes;
    ULONG Ratio;
    ULONG Target;

    //
    // If the allocate rate is less than the mimimum threshold, then lower
    // the maximum depth of the lookaside list. Otherwise, if the miss rate
    // is less than .5%, then lower the maximum depth. Otherwise, raise the
    // maximum depth based on the miss rate.
    //

    Changes = FALSE;
    if (Misses >= Allocates) {
        Misses = Allocates;
    }

    if (Allocates == 0) {
        Allocates = 1;
    }

    Ratio = (Misses * 1000) / Allocates;
    Target = *Depth;
    if ((Allocates / PplCurrentScanPeriod) < MINIMUM_ALLOCATION_THRESHOLD) {
        if (Target > (MINIMUM_LOOKASIDE_DEPTH + 10)) {
            Target -= 10;
        } else {
            Target = MINIMUM_LOOKASIDE_DEPTH;
        }

    } else if (Ratio < 5) {
        if (Target > (MINIMUM_LOOKASIDE_DEPTH + 1)) {
            Target -= 1;
        } else {
            Target = MINIMUM_LOOKASIDE_DEPTH;
        }
    } else {
        Changes = TRUE;
        Target += ((Ratio * MaximumDepth) / (1000 * 2)) + 5;
        if (Target > MaximumDepth) {
            Target = MaximumDepth;
        }
    }

    *Depth = (USHORT)Target;
    return Changes;
}

LOGICAL
PplScanLookasideList(
    PNPAGED_LOOKASIDE_LIST Lookaside
    )
{
    LOGICAL Changes;
    ULONG Allocates;
    ULONG Misses;

    Allocates = Lookaside->L.TotalAllocates - Lookaside->L.LastTotalAllocates;
    Lookaside->L.LastTotalAllocates = Lookaside->L.TotalAllocates;
    Misses = Lookaside->L.AllocateMisses - Lookaside->L.LastAllocateMisses;
    Lookaside->L.LastAllocateMisses = Lookaside->L.AllocateMisses;

    Changes = PplComputeLookasideDepth(
        Allocates,
        Misses,
        Lookaside->L.MaximumDepth,
        &Lookaside->L.Depth);

    return Changes;
}

VOID
PplTimeout(
    CTEEvent * Timer, 
    PVOID Context)
{

    LOGICAL Changes;
    KIRQL OldIrql;
    PLIST_ENTRY Entry;
    PNPAGED_LOOKASIDE_LIST Lookaside;

    //
    // Decrement the scan period and check if it is time to dynamically
    // adjust the maximum depth of lookaside lists.
    //

    Changes = FALSE;

    //
    // Scan our Ppl lists.
    //

    ExAcquireSpinLock(&PplLookasideLock, &OldIrql);

    Entry = PplLookasideListHead.Flink;

    while (Entry != &PplLookasideListHead) {
        Lookaside = CONTAINING_RECORD(Entry,
                                      NPAGED_LOOKASIDE_LIST,
                                      L.ListEntry);

        Changes |= PplScanLookasideList(Lookaside);

        Entry = Entry->Flink;
    }

    //
    // If any changes were made to the depth of any lookaside list during
    // this scan period, then lower the scan period to the minimum value.
    // Otherwise, attempt to raise the scan period.
    //

    if (Changes != FALSE) {
        PplCurrentScanPeriod = 1;
    } else {
        if (PplCurrentScanPeriod != MAXIMUM_SCAN_PERIOD) {
            PplCurrentScanPeriod += 1;
        }
    }

    ExReleaseSpinLock(&PplLookasideLock, OldIrql);

    //
    // Restart the timer.
    //

    CTEStartTimer(&PplTimer, PplCurrentScanPeriod * 1000L, PplTimeout, NULL);
    
    return;
}