/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

   mmquota.c

Abstract:

    This module contains the routines which implement the quota and
    commitment charging for memory management.

--*/

#include "mi.h"

#define MM_MINIMAL_COMMIT_INCREASE 512

SIZE_T MmPeakCommitment;

LONG MiCommitPopups[2];

extern ULONG_PTR MmAllocatedPagedPool;

#ifdef ALLOC_PRAGMA
#pragma alloc_text(INIT, MiInitializeCommitment)
#endif

#define MI_LOG_COMMIT_CHANGE(NewCommitValue, QuotaCharge)
#define MI_LOG_COMMIT_FAILURE(NewCommitValue, QuotaCharge)

SIZE_T MmSystemCommitReserve = (5 * 1024 * 1024) / PAGE_SIZE;

VOID
MiInitializeCommitment (
    VOID
    )
{
    if (MmNumberOfPhysicalPages < (33 * 1024 * 1024) / PAGE_SIZE) {
        MmSystemCommitReserve = (1 * 1024 * 1024) / PAGE_SIZE;
    }

}


LOGICAL
FASTCALL
MiChargeCommitment (
    IN SIZE_T QuotaCharge,
    IN PEPROCESS Process OPTIONAL
    )

/*++

Routine Description:

    This routine checks to ensure the system has sufficient page file
    space remaining.

    Since this routine is generally used to charge commitment on behalf of
    usermode or other optional actions, this routine does not allow the
    caller to use up the very last morsels of commit on the premise that
    the operating system and drivers can put those to better use than any
    application in order to prevent the appearance of system hangs.

Arguments:

    QuotaCharge - Supplies the quota amount to charge.

    Process - Optionally supplies the current process IF AND ONLY IF
              the working set mutex is held.  If the paging file
              is being extended, the working set mutex is released if
              this is non-null.

Return Value:

    TRUE if there is sufficient space, FALSE if not.

Environment:

    Kernel mode, APCs disabled, WorkingSetLock and AddressCreation mutexes
    *MAY* be held.

--*/

{
    SIZE_T OldCommitValue;
    SIZE_T NewCommitValue;
    SIZE_T CommitLimit;
    PETHREAD Thread;
    MMPAGE_FILE_EXPANSION PageExtend;
    LOGICAL WsHeldSafe;
    LOGICAL WsHeldShared;

    ASSERT ((SSIZE_T)QuotaCharge > 0);

#if DBG
    if (InitializationPhase > 1) {
        ULONG i;

        Thread = PsGetCurrentThread ();

        for (i = 0; i < (ULONG)KeNumberProcessors; i += 1) {
            ASSERT (KiProcessorBlock[i]->IdleThread != &Thread->Tcb);
        }
    }
#endif

    //
    // Initializing WsHeldSafe is not needed for correctness, but without it
    // the compiler cannot compile this code W4 to check for use of
    // uninitialized variables.
    //

    WsHeldSafe = FALSE;
    WsHeldShared = FALSE;

    do {

        OldCommitValue = MmTotalCommittedPages;

        NewCommitValue = OldCommitValue + QuotaCharge;

        while (NewCommitValue + MmSystemCommitReserve > MmTotalCommitLimit) {

            //
            // If the pagefiles are already at the maximum, then don't
            // bother trying to extend them, but do trim the cache.
            //

            if (MmTotalCommitLimit + 100 >= MmTotalCommitLimitMaximum) {

                MiChargeCommitmentFailures[1] += 1;

                MiTrimSegmentCache ();

                if (MmTotalCommitLimit >= MmTotalCommitLimitMaximum) {

                    MI_LOG_COMMIT_FAILURE (NewCommitValue, QuotaCharge);

                    MiCauseOverCommitPopup ();
                    return FALSE;
                }
            }

            Thread = PsGetCurrentThread ();

            if (Process != NULL) {

                //
                // The working set lock may have been acquired safely or
                // unsafely by our caller.  Handle both cases here and below.
                //

                UNLOCK_WS_REGARDLESS (Thread, Process, WsHeldSafe, WsHeldShared);
            }

            //
            // Queue a message to the segment dereferencing / pagefile extending
            // thread to see if the page file can be extended.  This is done
            // in the context of a system thread due to mutexes which may
            // currently be held.
            //

            PageExtend.InProgress = 1;
            PageExtend.ActualExpansion = 0;
            PageExtend.RequestedExpansionSize = QuotaCharge;
            PageExtend.Segment = NULL;
            PageExtend.PageFileNumber = MI_EXTEND_ANY_PAGEFILE;
            KeInitializeEvent (&PageExtend.Event, NotificationEvent, FALSE);

            if ((MiIssuePageExtendRequest (&PageExtend) == FALSE) ||
                (PageExtend.ActualExpansion == 0)) {

                MiCauseOverCommitPopup ();

                MI_LOG_COMMIT_FAILURE (NewCommitValue, QuotaCharge);

                MiChargeCommitmentFailures[0] += 1;

                if (Process != NULL) {
                    LOCK_WS_REGARDLESS (Thread, Process, WsHeldSafe, WsHeldShared);
                }

                return FALSE;
            }

            if (Process != NULL) {
                LOCK_WS_REGARDLESS (Thread, Process, WsHeldSafe, WsHeldShared);
            }

            OldCommitValue = MmTotalCommittedPages;

            NewCommitValue = OldCommitValue + QuotaCharge;
        }

#if defined(_WIN64)
        NewCommitValue = InterlockedCompareExchange64 (
                                (PLONGLONG) &MmTotalCommittedPages,
                                (LONGLONG)  NewCommitValue,
                                (LONGLONG)  OldCommitValue);
#else
        NewCommitValue = InterlockedCompareExchange (
                                (PLONG) &MmTotalCommittedPages,
                                (LONG)  NewCommitValue,
                                (LONG)  OldCommitValue);
#endif
                                                             
    } while (NewCommitValue != OldCommitValue);

    //
    // Success.
    //

    MI_LOG_COMMIT_CHANGE (NewCommitValue + QuotaCharge, QuotaCharge);

    MM_TRACK_COMMIT (MM_DBG_COMMIT_CHARGE_NORMAL, QuotaCharge);

    if (MmTotalCommittedPages > MmPeakCommitment) {
        MmPeakCommitment = MmTotalCommittedPages;
    }

    //
    // Success.  If system commit exceeds 90%, attempt a preemptive pagefile
    // increase anyway.
    //

    NewCommitValue = MmTotalCommittedPages;
    CommitLimit = MmTotalCommitLimit;

    if (NewCommitValue > ((CommitLimit/10)*9)) {

        if (CommitLimit < MmTotalCommitLimitMaximum) {

            //
            // Attempt to expand the paging file, but don't wait
            // to see if it succeeds.
            //

            NewCommitValue = NewCommitValue - ((CommitLimit/100)*85);

            MiIssuePageExtendRequestNoWait (NewCommitValue);
        }
        else {

            //
            // If the pagefiles are already at the maximum, then don't
            // bother trying to extend them, but do trim the cache.
            //

            if (MmTotalCommitLimit + 100 >= MmTotalCommitLimitMaximum) {
                MiTrimSegmentCache ();
            }
        }
    }

    return TRUE;
}

LOGICAL
FASTCALL
MiChargeCommitmentCantExpand (
    IN SIZE_T QuotaCharge,
    IN ULONG MustSucceed
    )

/*++

Routine Description:

    This routine charges the specified commitment without attempting
    to expand paging files and waiting for the expansion.  The routine
    determines if the paging file space is exhausted, and if so,
    it attempts to ascertain if the paging file space could be expanded.

Arguments:

    QuotaCharge - Supplies the quota amount to charge.

    MustSucceed - Supplies TRUE if the charge must succeed.

Return Value:

    TRUE if the commitment was permitted, FALSE if not.

Environment:

    Kernel mode, APCs disabled.

--*/

{
    SIZE_T CommitLimit;
    SIZE_T ExtendAmount;
    SIZE_T OldCommitValue;
    SIZE_T NewCommitValue;

    ASSERT ((SSIZE_T)QuotaCharge > 0);

    ASSERT32 ((QuotaCharge < 0x100000) || (QuotaCharge < MmTotalCommitLimit));

    do {

        OldCommitValue = ReadForWriteAccess (&MmTotalCommittedPages);

        NewCommitValue = OldCommitValue + QuotaCharge;

        if ((NewCommitValue > MmTotalCommitLimit) && (!MustSucceed)) {

            if ((NewCommitValue < MmTotalCommittedPages) ||
                (MmTotalCommitLimit + 100 >= MmTotalCommitLimitMaximum)) {

                MiChargeCommitmentFailures[1] += 1;

                MI_LOG_COMMIT_FAILURE (NewCommitValue, QuotaCharge);

                return FALSE;
            }

            //
            // Attempt to expand the paging file, but don't wait
            // to see if it succeeds.
            //

            MiChargeCommitmentFailures[0] += 1;
            MiIssuePageExtendRequestNoWait (MM_MINIMAL_COMMIT_INCREASE);
            return FALSE;
        }

#if defined(_WIN64)
        NewCommitValue = InterlockedCompareExchange64 (
                                (PLONGLONG) &MmTotalCommittedPages,
                                (LONGLONG)  NewCommitValue,
                                (LONGLONG)  OldCommitValue);
#else
        NewCommitValue = InterlockedCompareExchange (
                                (PLONG) &MmTotalCommittedPages,
                                (LONG)  NewCommitValue,
                                (LONG)  OldCommitValue);
#endif
                                                             
    } while (NewCommitValue != OldCommitValue);

    MI_LOG_COMMIT_CHANGE (NewCommitValue + QuotaCharge, QuotaCharge);

    MM_TRACK_COMMIT (MM_DBG_COMMIT_CHARGE_CANT_EXPAND, QuotaCharge);

    //
    // Success.  If system commit exceeds 90%, attempt a preemptive pagefile
    // increase anyway.
    //

    NewCommitValue = MmTotalCommittedPages;
    CommitLimit = MmTotalCommitLimit;

    if ((NewCommitValue > ((CommitLimit/10)*9)) &&
        (CommitLimit < MmTotalCommitLimitMaximum)) {

        //
        // Attempt to expand the paging file, but don't wait
        // to see if it succeeds.
        //
        // Queue a message to the segment dereferencing / pagefile extending
        // thread to see if the page file can be extended.  This is done
        // in the context of a system thread due to mutexes which may
        // currently be held.
        //

        ExtendAmount = NewCommitValue - ((CommitLimit/100)*85);

        if (QuotaCharge > ExtendAmount) {
            ExtendAmount = QuotaCharge;
        }

        MiIssuePageExtendRequestNoWait (ExtendAmount);
    }

    return TRUE;
}


LOGICAL
FASTCALL
MiChargeTemporaryCommitmentForReduction (
    IN SIZE_T QuotaCharge
    )

/*++

Routine Description:

    This routine attempts to charge the specified commitment without
    expanding the paging file.

    This is typically called just prior to reducing the pagefile size.

Arguments:

    QuotaCharge - Supplies the quota amount to charge.

Return Value:

    TRUE if the commitment was permitted, FALSE if not.

Environment:

    Kernel mode, APCs disabled.

--*/

{
    SIZE_T OldCommitValue;
    SIZE_T NewCommitValue;

    ASSERT ((SSIZE_T)QuotaCharge > 0);

    do {

        OldCommitValue = ReadForWriteAccess (&MmTotalCommittedPages);

        NewCommitValue = OldCommitValue + QuotaCharge;

        if (NewCommitValue > MmTotalCommitLimit) {
            return FALSE;
        }

#if defined(_WIN64)
        NewCommitValue = InterlockedCompareExchange64 (
                                (PLONGLONG) &MmTotalCommittedPages,
                                (LONGLONG)  NewCommitValue,
                                (LONGLONG)  OldCommitValue);
#else
        NewCommitValue = InterlockedCompareExchange (
                                (PLONG) &MmTotalCommittedPages,
                                (LONG)  NewCommitValue,
                                (LONG)  OldCommitValue);
#endif
                                                             
    } while (NewCommitValue != OldCommitValue);

    //
    // Success.
    //

    MM_TRACK_COMMIT (MM_DBG_COMMIT_CHARGE_NORMAL, QuotaCharge);

    if (MmTotalCommittedPages > MmPeakCommitment) {
        MmPeakCommitment = MmTotalCommittedPages;
    }

    return TRUE;
}


SIZE_T
MiCalculatePageCommitment (
    IN PVOID StartingAddress,
    IN PVOID EndingAddress,
    IN PMMVAD Vad,
    IN PEPROCESS Process
    )

/*++

Routine Description:

    This routine examines the range of pages from the starting address
    up to and including the ending address and returns the commit charge
    for the pages within the range.

Arguments:

    StartingAddress - Supplies the starting address of the range.

    EndingAddress - Supplies the ending address of the range.

    Vad - Supplies the virtual address descriptor which describes the range.

    Process - Supplies the current process.

Return Value:

    Commitment charge for the range.

Environment:

    Kernel mode, APCs disabled, WorkingSetLock and AddressCreation mutexes
    held.

--*/

{
    PMMPTE PointerPte;
    PMMPTE LastPte;
    PMMPTE PointerPde;
    PMMPTE PointerPpe;
    PMMPTE PointerPxe;
    SIZE_T NumberOfCommittedPages;
    ULONG Waited;

    PointerPxe = MiGetPxeAddress (StartingAddress);
    PointerPpe = MiGetPpeAddress (StartingAddress);
    PointerPde = MiGetPdeAddress (StartingAddress);
    PointerPte = MiGetPteAddress (StartingAddress);

    LastPte = MiGetPteAddress (EndingAddress);

    if (Vad->u.VadFlags.MemCommit == 1) {

        //
        // All the pages are committed within this range.
        //

        NumberOfCommittedPages = BYTES_TO_PAGES ((PCHAR)EndingAddress -
                                                       (PCHAR)StartingAddress);

        //
        // Examine the PTEs to determine how many pages are committed.
        //

        do {

#if (_MI_PAGING_LEVELS >= 4)
retry:
#endif

            while (!MiDoesPxeExistAndMakeValid (PointerPxe,
                                                Process,
                                                MM_NOIRQL,
                                                &Waited)) {
    
                //
                // No PXE exists for the starting address, therefore the page
                // is not committed.
                //
    
                PointerPxe += 1;
                PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe);
                PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
                PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
                if (PointerPte > LastPte) {
                    return NumberOfCommittedPages;
                }
            }

#if (_MI_PAGING_LEVELS >= 4)
            Waited = 0;
#endif

            while (!MiDoesPpeExistAndMakeValid (PointerPpe,
                                                Process,
                                                MM_NOIRQL,
                                                &Waited)) {
    
                //
                // No PPE exists for the starting address, therefore the page
                // is not committed.
                //
    
                PointerPpe += 1;
                PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
                PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
                if (PointerPte > LastPte) {
                    return NumberOfCommittedPages;
                }
#if (_MI_PAGING_LEVELS >= 4)
                if (MiIsPteOnPdeBoundary (PointerPpe)) {
                    PointerPxe = MiGetPteAddress (PointerPpe);
                    goto retry;
                }
#endif
            }

#if (_MI_PAGING_LEVELS < 4)
            Waited = 0;
#endif

            while (!MiDoesPdeExistAndMakeValid (PointerPde,
                                                Process,
                                                MM_NOIRQL,
                                                &Waited)) {
    
                //
                // No PDE exists for the starting address, therefore the page
                // is not committed.
                //
    
                PointerPde += 1;
                PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
                if (PointerPte > LastPte) {
                    return NumberOfCommittedPages;
                }
#if (_MI_PAGING_LEVELS >= 3)
                if (MiIsPteOnPdeBoundary (PointerPde)) {
                    PointerPpe = MiGetPteAddress (PointerPde);
                    PointerPxe = MiGetPdeAddress (PointerPde);
                    Waited = 1;
                    break;
                }
#endif
            }

        } while (Waited != 0);

restart:

        while (PointerPte <= LastPte) {

            if (MiIsPteOnPdeBoundary (PointerPte)) {

                //
                // This is a PDE boundary, check to see if the all the
                // PXE/PPE/PDE pages exist.
                //

                PointerPde = MiGetPteAddress (PointerPte);
                PointerPpe = MiGetPteAddress (PointerPde);
                PointerPxe = MiGetPteAddress (PointerPpe);

                do {

                    if (!MiDoesPxeExistAndMakeValid (PointerPxe,
                                                     Process,
                                                     MM_NOIRQL,
                                                     &Waited)) {
    
                        //
                        // No PDE exists for the starting address, check the VAD
                        // to see if the pages are not committed.
                        //
    
                        PointerPxe += 1;
                        PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe);
                        PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
                        PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
    
                        //
                        // Check next page.
                        //
    
                        goto restart;
                    }
    
#if (_MI_PAGING_LEVELS >= 4)
                    Waited = 0;
#endif
    
                    if (!MiDoesPpeExistAndMakeValid (PointerPpe,
                                                     Process,
                                                     MM_NOIRQL,
                                                     &Waited)) {
    
                        //
                        // No PDE exists for the starting address, check the VAD
                        // to see if the pages are not committed.
                        //
    
                        PointerPpe += 1;
                        PointerPxe = MiGetPteAddress (PointerPpe);
                        PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
                        PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
    
                        //
                        // Check next page.
                        //
    
                        goto restart;
                    }
    
#if (_MI_PAGING_LEVELS < 4)
                    Waited = 0;
#endif
    
                    if (!MiDoesPdeExistAndMakeValid (PointerPde,
                                                     Process,
                                                     MM_NOIRQL,
                                                     &Waited)) {
    
                        //
                        // No PDE exists for the starting address, check the VAD
                        // to see if the pages are not committed.
                        //
    
                        PointerPde += 1;
                        PointerPpe = MiGetPteAddress (PointerPde);
                        PointerPxe = MiGetPteAddress (PointerPpe);
                        PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
    
                        //
                        // Check next page.
                        //
    
                        goto restart;
                    }
                } while (Waited != 0);
            }

            //
            // The PDE exists, examine the PTE.
            //

            if (PointerPte->u.Long != 0) {

                //
                // Has this page been explicitly decommitted?
                //

                if (MiIsPteDecommittedPage (PointerPte)) {

                    //
                    // This page is decommitted, remove it from the count.
                    //

                    NumberOfCommittedPages -= 1;

                }
            }

            PointerPte += 1;
        }

        return NumberOfCommittedPages;
    }

    //
    // Examine non committed range.
    //

    NumberOfCommittedPages = 0;

    do {

#if (_MI_PAGING_LEVELS >= 4)
retry2:
#endif
        while (!MiDoesPxeExistAndMakeValid (PointerPxe,
                                            Process,
                                            MM_NOIRQL,
                                            &Waited)) {
    
    
            //
            // No PXE exists for the starting address, therefore the page
            // is not committed.
            //
    
            PointerPxe += 1;
            PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe);
            PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
            PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
            if (PointerPte > LastPte) {
               return NumberOfCommittedPages;
            }
        }

#if (_MI_PAGING_LEVELS >= 4)
        Waited = 0;
#endif

        while (!MiDoesPpeExistAndMakeValid (PointerPpe,
                                            Process,
                                            MM_NOIRQL,
                                            &Waited)) {
    
    
            //
            // No PPE exists for the starting address, therefore the page
            // is not committed.
            //
    
            PointerPpe += 1;
            PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
            PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
            if (PointerPte > LastPte) {
               return NumberOfCommittedPages;
            }
#if (_MI_PAGING_LEVELS >= 4)
            if (MiIsPteOnPdeBoundary (PointerPpe)) {
                PointerPxe = MiGetPteAddress (PointerPpe);
                goto retry2;
            }
#endif
        }

#if (_MI_PAGING_LEVELS < 4)
        Waited = 0;
#endif

        while (!MiDoesPdeExistAndMakeValid (PointerPde,
                                            Process,
                                            MM_NOIRQL,
                                            &Waited)) {
    
            //
            // No PDE exists for the starting address, therefore the page
            // is not committed.
            //
    
            PointerPde += 1;
            PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
            if (PointerPte > LastPte) {
               return NumberOfCommittedPages;
            }
#if (_MI_PAGING_LEVELS >= 3)
            if (MiIsPteOnPdeBoundary (PointerPde)) {
                PointerPpe = MiGetPteAddress (PointerPde);
                PointerPxe = MiGetPdeAddress (PointerPde);
                Waited = 1;
                break;
            }
#endif
        }

    } while (Waited != 0);

restart2:

    while (PointerPte <= LastPte) {

        if (MiIsPteOnPdeBoundary (PointerPte)) {

            //
            // This is a PDE boundary, check to see if the entire
            // PXE/PPE/PDE pages exist.
            //

            PointerPde = MiGetPteAddress (PointerPte);
            PointerPpe = MiGetPteAddress (PointerPde);
            PointerPxe = MiGetPdeAddress (PointerPde);

            do {

                if (!MiDoesPxeExistAndMakeValid (PointerPxe,
                                                 Process,
                                                 MM_NOIRQL,
                                                 &Waited)) {
    
                    //
                    // No PXE exists for the starting address, check the VAD
                    // to see if the pages are not committed.
                    //
    
                    PointerPxe += 1;
                    PointerPpe = MiGetVirtualAddressMappedByPte (PointerPxe);
                    PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
                    PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
    
                    //
                    // Check next page.
                    //
    
                    goto restart2;
                }
    
#if (_MI_PAGING_LEVELS >= 4)
                Waited = 0;
#endif

                if (!MiDoesPpeExistAndMakeValid (PointerPpe,
                                                 Process,
                                                 MM_NOIRQL,
                                                 &Waited)) {
    
                    //
                    // No PPE exists for the starting address, check the VAD
                    // to see if the pages are not committed.
                    //
    
                    PointerPpe += 1;
                    PointerPxe = MiGetPteAddress (PointerPpe);
                    PointerPde = MiGetVirtualAddressMappedByPte (PointerPpe);
                    PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
    
                    //
                    // Check next page.
                    //
    
                    goto restart2;
                }
    
#if (_MI_PAGING_LEVELS < 4)
                Waited = 0;
#endif
    
                if (!MiDoesPdeExistAndMakeValid (PointerPde,
                                                 Process,
                                                 MM_NOIRQL,
                                                 &Waited)) {
    
                    //
                    // No PDE exists for the starting address, check the VAD
                    // to see if the pages are not committed.
                    //
    
                    PointerPde += 1;
                    PointerPpe = MiGetPteAddress (PointerPde);
                    PointerPxe = MiGetPteAddress (PointerPpe);
                    PointerPte = MiGetVirtualAddressMappedByPte (PointerPde);
    
                    //
                    // Check next page.
                    //
    
                    goto restart2;
                }

            } while (Waited != 0);
        }

        //
        // The PDE exists, examine the PTE.
        //

        if ((PointerPte->u.Long != 0) &&
             (!MiIsPteDecommittedPage (PointerPte))) {

            //
            // This page is committed, count it.
            //

            NumberOfCommittedPages += 1;
        }

        PointerPte += 1;
    }

    return NumberOfCommittedPages;
}

VOID
MiReturnPageTablePageCommitment (
    IN PVOID StartingAddress,
    IN PVOID EndingAddress,
    IN PEPROCESS CurrentProcess,
    IN PMMVAD PreviousVad,
    IN PMMVAD NextVad
    )

/*++

Routine Description:

    This routine returns commitment for COMPLETE page table pages which
    span the virtual address range.  For example (assuming 4k pages),
    if the StartingAddress =  64k and the EndingAddress = 5mb, no
    page table charges would be freed as a complete page table page is
    not covered by the range.  However, if the StartingAddress was 4mb
    and the EndingAddress was 9mb, 1 page table page would be freed.

Arguments:

    StartingAddress - Supplies the starting address of the range.

    EndingAddress - Supplies the ending address of the range.

    CurrentProcess - Supplies a pointer to the current process.

    PreviousVad - Supplies a pointer to the previous VAD, NULL if none.

    NextVad - Supplies a pointer to the next VAD, NULL if none.

Return Value:

    None.

Environment:

    Kernel mode, APCs disabled, AddressCreation mutex held.

--*/

{
    RTL_BITMAP VadBitMap;
    ULONG NumberToClear;
    ULONG StartBit;
    ULONG EndBit;
    LONG FirstPage;
    LONG LastPage;
    LONG PreviousPage;
    LONG NextPage;
#if (_MI_PAGING_LEVELS >= 3)
    LONG FirstPdPage;
    LONG LastPdPage;
    LONG PreviousPdPage;
    LONG NextPdPage;
#endif
#if (_MI_PAGING_LEVELS >= 4)
    LONG FirstPpPage;
    LONG LastPpPage;
    LONG PreviousPpPage;
    LONG NextPpPage;
#endif

    //
    // Check to see if any page table pages would be freed.
    //

    ASSERT (StartingAddress != EndingAddress);

    StartBit = (ULONG) (((ULONG_PTR) MI_64K_ALIGN (StartingAddress)) / X64K);
    EndBit = (ULONG) (((ULONG_PTR) MI_64K_ALIGN (EndingAddress)) / X64K);

    if (PreviousVad == NULL) {
        PreviousPage = -1;
#if (_MI_PAGING_LEVELS >= 3)
        PreviousPdPage = -1;
#endif
#if (_MI_PAGING_LEVELS >= 4)
        PreviousPpPage = -1;
#endif
    }
    else {
        PreviousPage = MiGetPdeIndex (MI_VPN_TO_VA (PreviousVad->EndingVpn));
#if (_MI_PAGING_LEVELS >= 3)
        PreviousPdPage = MiGetPpeIndex (MI_VPN_TO_VA (PreviousVad->EndingVpn));
#endif
#if (_MI_PAGING_LEVELS >= 4)
        PreviousPpPage = MiGetPxeIndex (MI_VPN_TO_VA (PreviousVad->EndingVpn));
#endif
        if (MI_64K_ALIGN (MI_VPN_TO_VA (PreviousVad->EndingVpn)) ==
            MI_64K_ALIGN (StartingAddress)) {
                StartBit += 1;
        }
    }

    if (NextVad == NULL) {
        NextPage = MiGetPdeIndex (MM_HIGHEST_USER_ADDRESS) + 1;
#if (_MI_PAGING_LEVELS >= 3)
        NextPdPage = MiGetPpeIndex (MM_HIGHEST_USER_ADDRESS) + 1;
#endif
#if (_MI_PAGING_LEVELS >= 4)
        NextPpPage = MiGetPxeIndex (MM_HIGHEST_USER_ADDRESS) + 1;
#endif
    }
    else {
        NextPage = MiGetPdeIndex (MI_VPN_TO_VA (NextVad->StartingVpn));
#if (_MI_PAGING_LEVELS >= 3)
        NextPdPage = MiGetPpeIndex (MI_VPN_TO_VA (NextVad->StartingVpn));
#endif
#if (_MI_PAGING_LEVELS >= 4)
        NextPpPage = MiGetPxeIndex (MI_VPN_TO_VA (NextVad->StartingVpn));
#endif
        if (MI_64K_ALIGN (MI_VPN_TO_VA (NextVad->StartingVpn)) ==
            MI_64K_ALIGN (EndingAddress)) {
                EndBit -= 1;
        }
    }

    ASSERT (PreviousPage <= NextPage);
    ASSERT64 (PreviousPdPage <= NextPdPage);
#if (_MI_PAGING_LEVELS >= 4)
    ASSERT64 (PreviousPpPage <= NextPpPage);
#endif

    FirstPage = MiGetPdeIndex (StartingAddress);

    LastPage = MiGetPdeIndex (EndingAddress);

    if (PreviousPage == FirstPage) {

        //
        // A VAD is within the starting page table page.
        //

        FirstPage += 1;
    }

    if (NextPage == LastPage) {

        //
        // A VAD is within the ending page table page.
        //

        LastPage -= 1;
    }

    if (StartBit <= EndBit) {

        //
        // Initialize the bitmap inline for speed.
        //

        VadBitMap.SizeOfBitMap = MiLastVadBit + 1;
        VadBitMap.Buffer = VAD_BITMAP_SPACE;

#if defined (_WIN64) || defined (_X86PAE_)

        //
        // Only the first (PAGE_SIZE*8*64K) of VA space on NT64 is bitmapped.
        //

        if (EndBit > MiLastVadBit) {
            EndBit = MiLastVadBit;
        }

        if (StartBit <= MiLastVadBit) {
            RtlClearBits (&VadBitMap, StartBit, EndBit - StartBit + 1);

            if (MmWorkingSetList->VadBitMapHint > StartBit) {
                MmWorkingSetList->VadBitMapHint = StartBit;
            }
        }
#else
        RtlClearBits (&VadBitMap, StartBit, EndBit - StartBit + 1);

        if (MmWorkingSetList->VadBitMapHint > StartBit) {
            MmWorkingSetList->VadBitMapHint = StartBit;
        }
#endif
    }

    //
    // Indicate that the page table page is not in use.
    //

    if (FirstPage > LastPage) {
        return;
    }

    NumberToClear = 1 + LastPage - FirstPage;

    while (FirstPage <= LastPage) {
#if (_MI_PAGING_LEVELS >= 3)
        ASSERT ((ULONG)FirstPage < MmWorkingSetList->MaximumUserPageTablePages);
#endif
        ASSERT (MI_CHECK_BIT (MmWorkingSetList->CommittedPageTables,
                              FirstPage));

        MI_CLEAR_BIT (MmWorkingSetList->CommittedPageTables, FirstPage);
        FirstPage += 1;
    }

    MmWorkingSetList->NumberOfCommittedPageTables -= NumberToClear;

#if (_MI_PAGING_LEVELS >= 4)

    //
    // Return page directory parent charges here.
    //

    FirstPpPage = MiGetPxeIndex (StartingAddress);

    LastPpPage = MiGetPxeIndex (EndingAddress);

    if (PreviousPpPage == FirstPpPage) {

        //
        // A VAD is within the starting page directory parent page.
        //

        FirstPpPage += 1;
    }

    if (NextPpPage == LastPpPage) {

        //
        // A VAD is within the ending page directory parent page.
        //

        LastPpPage -= 1;
    }

    //
    // Indicate that the page directory page parent is not in use.
    //

    if (FirstPpPage <= LastPpPage) {

        MmWorkingSetList->NumberOfCommittedPageDirectoryParents -= (1 + LastPpPage - FirstPpPage);

        NumberToClear += (1 + LastPpPage - FirstPpPage);
    
        while (FirstPpPage <= LastPpPage) {
            ASSERT (MI_CHECK_BIT (MmWorkingSetList->CommittedPageDirectoryParents,
                                  FirstPpPage));
    
            MI_CLEAR_BIT (MmWorkingSetList->CommittedPageDirectoryParents, FirstPpPage);
            FirstPpPage += 1;
        }
    }
    
#endif

#if (_MI_PAGING_LEVELS >= 3)

    //
    // Return page directory charges here.
    //

    FirstPdPage = MiGetPpeIndex (StartingAddress);

    LastPdPage = MiGetPpeIndex (EndingAddress);

    if (PreviousPdPage == FirstPdPage) {

        //
        // A VAD is within the starting page directory page.
        //

        FirstPdPage += 1;
    }

    if (NextPdPage == LastPdPage) {

        //
        // A VAD is within the ending page directory page.
        //

        LastPdPage -= 1;
    }

    //
    // Indicate that the page directory page is not in use.
    //

    if (FirstPdPage <= LastPdPage) {

        MmWorkingSetList->NumberOfCommittedPageDirectories -= (1 + LastPdPage - FirstPdPage);

        NumberToClear += (1 + LastPdPage - FirstPdPage);
    
        while (FirstPdPage <= LastPdPage) {
#if (_MI_PAGING_LEVELS >= 4)
            ASSERT ((ULONG)FirstPdPage < MmWorkingSetList->MaximumUserPageDirectoryPages);
#endif
            ASSERT (MI_CHECK_BIT (MmWorkingSetList->CommittedPageDirectories,
                                  FirstPdPage));
    
            MI_CLEAR_BIT (MmWorkingSetList->CommittedPageDirectories, FirstPdPage);
            FirstPdPage += 1;
        }
    }
    
#endif

    MiReturnCommitment (NumberToClear);
    MM_TRACK_COMMIT (MM_DBG_COMMIT_RETURN_PAGETABLES, NumberToClear);
    PsReturnProcessPageFileQuota (CurrentProcess, NumberToClear);

    if (CurrentProcess->JobStatus & PS_JOB_STATUS_REPORT_COMMIT_CHANGES) {
        PsChangeJobMemoryUsage (PS_JOB_STATUS_REPORT_COMMIT_CHANGES, -(SSIZE_T)NumberToClear);
    }
    CurrentProcess->CommitCharge -= NumberToClear;

    MI_INCREMENT_TOTAL_PROCESS_COMMIT (0 - NumberToClear);

    return;
}


VOID
MiCauseOverCommitPopup (
    VOID
    )

/*++

Routine Description:

    This function causes an over commit popup to occur (if the popup has never
    been sent before).

Arguments:

    None.

Return Value:

    None.

--*/

{
    LONG PopupNumber;

    //
    // Give the user a meaningful message - either to increase the minimum,
    // maximum, or both.
    //

    if ((MmTotalCommittedPages > MmTotalCommitLimitMaximum - 100) ||
        (MmTotalCommitLimit == MmTotalCommitLimitMaximum)) {

        if (InterlockedIncrement (&MiCommitPopups[0]) > 1) {
            InterlockedDecrement (&MiCommitPopups[0]);
            return;
        }
        PopupNumber = STATUS_COMMITMENT_LIMIT;
    }
    else {
        if (InterlockedIncrement (&MiCommitPopups[1]) > 1) {
            InterlockedDecrement (&MiCommitPopups[1]);
            return;
        }
        PopupNumber = STATUS_COMMITMENT_MINIMUM;
    }

    IoRaiseInformationalHardError (PopupNumber, NULL, NULL);
}


SIZE_T MmTotalPagedPoolQuota;
SIZE_T MmTotalNonPagedPoolQuota;

BOOLEAN
MmRaisePoolQuota(
    IN POOL_TYPE PoolType,
    IN SIZE_T OldQuotaLimit,
    OUT PSIZE_T NewQuotaLimit
    )

/*++

Routine Description:

    This function is called (with a spinlock) whenever PS detects a quota
    limit has been exceeded. The purpose of this function is to attempt to
    increase the specified quota.

Arguments:

    PoolType - Supplies the pool type of the quota to be raised

    OldQuotaLimit - Supplies the current quota limit for this pool type

    NewQuotaLimit - Returns the new limit

Return Value:

    TRUE - The API succeeded and the quota limit was raised.

    FALSE - We were unable to raise the quota limit.

Environment:

    Kernel mode, QUOTA SPIN LOCK HELD!!

--*/

{
    SIZE_T Limit;
    PMM_PAGED_POOL_INFO PagedPoolInfo;

    if (PoolType == PagedPool) {

        //
        // Check commit limit and make sure at least 1mb is available.
        // Check to make sure 4mb of paged pool still exists.
        //

        PagedPoolInfo = &MmPagedPoolInfo;

        if (MmSizeOfPagedPoolInPages <
            (PagedPoolInfo->AllocatedPagedPool + ((MMPAGED_QUOTA_CHECK) >> PAGE_SHIFT))) {

            return FALSE;
        }

        MmTotalPagedPoolQuota += (MMPAGED_QUOTA_INCREASE);
        *NewQuotaLimit = OldQuotaLimit + (MMPAGED_QUOTA_INCREASE);
        return TRUE;

    } else {

        if ( (ULONG_PTR)(MmAllocatedNonPagedPool + ((1*1024*1024) >> PAGE_SHIFT)) < MmMaximumNonPagedPoolInPages) {
            goto aok;
        }

        //
        // Make sure 200 pages and 5mb of nonpaged pool expansion
        // available.  Raise quota by 64k.
        //

        if ((MmAvailablePages < 200) ||
            (MmResidentAvailablePages < ((MMNONPAGED_QUOTA_CHECK) >> PAGE_SHIFT))) {

            return FALSE;
        }

        if (MmAvailablePages > ((4*1024*1024) >> PAGE_SHIFT)) {
            Limit = (1*1024*1024) >> PAGE_SHIFT;
        } else {
            Limit = (4*1024*1024) >> PAGE_SHIFT;
        }

        if (MmMaximumNonPagedPoolInPages < MmAllocatedNonPagedPool + Limit) {

            return FALSE;
        }
aok:
        MmTotalNonPagedPoolQuota += (MMNONPAGED_QUOTA_INCREASE);
        *NewQuotaLimit = OldQuotaLimit + (MMNONPAGED_QUOTA_INCREASE);
        return TRUE;
    }
}


VOID
MmReturnPoolQuota(
    IN POOL_TYPE PoolType,
    IN SIZE_T ReturnedQuota
    )

/*++

Routine Description:

    Returns pool quota.

Arguments:

    PoolType - Supplies the pool type of the quota to be returned.

    ReturnedQuota - Number of bytes returned.

Return Value:

    NONE.

Environment:

    Kernel mode, QUOTA SPIN LOCK HELD!!

--*/

{

    if (PoolType == PagedPool) {
        MmTotalPagedPoolQuota -= ReturnedQuota;
    } else {
        MmTotalNonPagedPoolQuota -= ReturnedQuota;
    }

    return;
}

