windows-nt/Source/XPSP1/NT/base/ntos/mm/dynmem.c

2279 lines
62 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1999 Microsoft Corporation
Module Name:
dynmem.c
Abstract:
This module contains the routines which implement dynamically adding
and removing physical memory from the system.
Author:
Landy Wang (landyw) 05-Feb-1999
Revision History:
--*/
#include "mi.h"
FAST_MUTEX MmDynamicMemoryMutex;
LOGICAL MiTrimRemovalPagesOnly = FALSE;
#if DBG
ULONG MiShowStuckPages;
ULONG MiDynmemData[9];
#endif
#if defined (_MI_COMPRESSION)
extern PMM_SET_COMPRESSION_THRESHOLD MiSetCompressionThreshold;
#endif
//
// Leave the low 3 bits clear as this will be inserted into the PFN PteAddress.
//
#define PFN_REMOVED ((PMMPTE)(INT_PTR)(int)0x99887768)
PFN_COUNT
MiRemovePhysicalPages (
IN PFN_NUMBER StartPage,
IN PFN_NUMBER EndPage
);
NTSTATUS
MiRemovePhysicalMemory (
IN PPHYSICAL_ADDRESS StartAddress,
IN OUT PLARGE_INTEGER NumberOfBytes,
IN LOGICAL PermanentRemoval,
IN ULONG Flags
);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,MmRemovePhysicalMemory)
#pragma alloc_text(PAGE,MmMarkPhysicalMemoryAsBad)
#pragma alloc_text(PAGELK,MmAddPhysicalMemory)
#pragma alloc_text(PAGELK,MmAddPhysicalMemoryEx)
#pragma alloc_text(PAGELK,MiRemovePhysicalMemory)
#pragma alloc_text(PAGELK,MmMarkPhysicalMemoryAsGood)
#pragma alloc_text(PAGELK,MmGetPhysicalMemoryRanges)
#pragma alloc_text(PAGELK,MiRemovePhysicalPages)
#endif
NTSTATUS
MmAddPhysicalMemory (
IN PPHYSICAL_ADDRESS StartAddress,
IN OUT PLARGE_INTEGER NumberOfBytes
)
/*++
Routine Description:
A wrapper for MmAddPhysicalMemoryEx.
Arguments:
StartAddress - Supplies the starting physical address.
NumberOfBytes - Supplies a pointer to the number of bytes being added.
If any bytes were added (ie: STATUS_SUCCESS is being
returned), the actual amount is returned here.
Return Value:
NTSTATUS.
Environment:
Kernel mode. PASSIVE level. No locks held.
--*/
{
return MmAddPhysicalMemoryEx (StartAddress, NumberOfBytes, 0);
}
NTSTATUS
MmAddPhysicalMemoryEx (
IN PPHYSICAL_ADDRESS StartAddress,
IN OUT PLARGE_INTEGER NumberOfBytes,
IN ULONG Flags
)
/*++
Routine Description:
This routine adds the specified physical address range to the system.
This includes initializing PFN database entries and adding it to the
freelists.
Arguments:
StartAddress - Supplies the starting physical address.
NumberOfBytes - Supplies a pointer to the number of bytes being added.
If any bytes were added (ie: STATUS_SUCCESS is being
returned), the actual amount is returned here.
Flags - Supplies relevant flags describing the memory range.
Return Value:
NTSTATUS.
Environment:
Kernel mode. PASSIVE level. No locks held.
--*/
{
ULONG i;
PMMPFN Pfn1;
KIRQL OldIrql;
LOGICAL Inserted;
LOGICAL Updated;
MMPTE TempPte;
PMMPTE PointerPte;
PMMPTE LastPte;
PFN_NUMBER NumberOfPages;
PFN_NUMBER start;
PFN_NUMBER count;
PFN_NUMBER StartPage;
PFN_NUMBER EndPage;
PFN_NUMBER PageFrameIndex;
PFN_NUMBER Page;
PFN_NUMBER LastPage;
PFN_NUMBER TotalPagesAllowed;
PFN_COUNT PagesNeeded;
PPHYSICAL_MEMORY_DESCRIPTOR OldPhysicalMemoryBlock;
PPHYSICAL_MEMORY_DESCRIPTOR NewPhysicalMemoryBlock;
PPHYSICAL_MEMORY_RUN NewRun;
LOGICAL PfnDatabaseIsPhysical;
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
#ifdef _MI_MESSAGE_SERVER
if (NumberOfBytes->LowPart & 0x1) {
MI_INSTRUMENT_QUEUE((PVOID)StartAddress);
return STATUS_NOT_SUPPORTED;
}
else if (NumberOfBytes->LowPart & 0x2) {
#if defined(_WIN64)
StartAddress->QuadPart = (ULONG_PTR)(MI_INSTRUMENTR_QUEUE());
#else
StartAddress->LowPart = PtrToUlong(MI_INSTRUMENTR_QUEUE());
#endif
return STATUS_NOT_SUPPORTED;
}
#endif
if (BYTE_OFFSET(StartAddress->LowPart) != 0) {
return STATUS_INVALID_PARAMETER_1;
}
if (BYTE_OFFSET(NumberOfBytes->LowPart) != 0) {
return STATUS_INVALID_PARAMETER_2;
}
#if defined (_MI_COMPRESSION)
if (Flags & ~MM_PHYSICAL_MEMORY_PRODUCED_VIA_COMPRESSION) {
return STATUS_INVALID_PARAMETER_3;
}
#else
if (Flags != 0) {
return STATUS_INVALID_PARAMETER_3;
}
#endif
//
// The system must be configured for dynamic memory addition. This is
// critical as only then is the database guaranteed to be non-sparse.
//
if (MmDynamicPfn == 0) {
return STATUS_NOT_SUPPORTED;
}
if (MI_IS_PHYSICAL_ADDRESS(MmPfnDatabase)) {
PfnDatabaseIsPhysical = TRUE;
}
else {
PfnDatabaseIsPhysical = FALSE;
}
StartPage = (PFN_NUMBER)(StartAddress->QuadPart >> PAGE_SHIFT);
NumberOfPages = (PFN_NUMBER)(NumberOfBytes->QuadPart >> PAGE_SHIFT);
EndPage = StartPage + NumberOfPages;
if (EndPage - 1 > MmHighestPossiblePhysicalPage) {
//
// Truncate the request into something that can be mapped by the PFN
// database.
//
EndPage = MmHighestPossiblePhysicalPage + 1;
NumberOfPages = EndPage - StartPage;
}
//
// Ensure that the memory being added does not exceed the license
// restrictions.
//
if (ExVerifySuite(DataCenter) == TRUE) {
TotalPagesAllowed = MI_DTC_MAX_PAGES;
}
else if ((MmProductType != 0x00690057) &&
(ExVerifySuite(Enterprise) == TRUE)) {
TotalPagesAllowed = MI_ADS_MAX_PAGES;
}
else {
TotalPagesAllowed = MI_DEFAULT_MAX_PAGES;
}
if (MmNumberOfPhysicalPages + NumberOfPages > TotalPagesAllowed) {
//
// Truncate the request appropriately.
//
NumberOfPages = TotalPagesAllowed - MmNumberOfPhysicalPages;
EndPage = StartPage + NumberOfPages;
}
//
// The range cannot wrap.
//
if (StartPage >= EndPage) {
return STATUS_INVALID_PARAMETER_1;
}
ExAcquireFastMutex (&MmDynamicMemoryMutex);
OldPhysicalMemoryBlock = MmPhysicalMemoryBlock;
i = (sizeof(PHYSICAL_MEMORY_DESCRIPTOR) +
(sizeof(PHYSICAL_MEMORY_RUN) * (MmPhysicalMemoryBlock->NumberOfRuns + 1)));
NewPhysicalMemoryBlock = ExAllocatePoolWithTag (NonPagedPool,
i,
' mM');
if (NewPhysicalMemoryBlock == NULL) {
ExReleaseFastMutex (&MmDynamicMemoryMutex);
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// The range cannot overlap any ranges that are already present.
//
start = 0;
MmLockPagableSectionByHandle (ExPageLockHandle);
LOCK_PFN (OldIrql);
#if defined (_MI_COMPRESSION)
//
// Adding compression-generated ranges can only be done if the hardware
// has already successfully announced itself.
//
if (Flags & MM_PHYSICAL_MEMORY_PRODUCED_VIA_COMPRESSION) {
if (MiSetCompressionThreshold == NULL) {
UNLOCK_PFN (OldIrql);
MmUnlockPagableImageSection(ExPageLockHandle);
ExReleaseFastMutex (&MmDynamicMemoryMutex);
ExFreePool (NewPhysicalMemoryBlock);
return STATUS_NOT_SUPPORTED;
}
}
#endif
do {
count = MmPhysicalMemoryBlock->Run[start].PageCount;
Page = MmPhysicalMemoryBlock->Run[start].BasePage;
if (count != 0) {
LastPage = Page + count;
if ((StartPage < Page) && (EndPage > Page)) {
UNLOCK_PFN (OldIrql);
MmUnlockPagableImageSection(ExPageLockHandle);
ExReleaseFastMutex (&MmDynamicMemoryMutex);
ExFreePool (NewPhysicalMemoryBlock);
return STATUS_CONFLICTING_ADDRESSES;
}
if ((StartPage >= Page) && (StartPage < LastPage)) {
UNLOCK_PFN (OldIrql);
MmUnlockPagableImageSection(ExPageLockHandle);
ExReleaseFastMutex (&MmDynamicMemoryMutex);
ExFreePool (NewPhysicalMemoryBlock);
return STATUS_CONFLICTING_ADDRESSES;
}
}
start += 1;
} while (start != MmPhysicalMemoryBlock->NumberOfRuns);
//
// Fill any gaps in the (sparse) PFN database needed for these pages,
// unless the PFN database was physically allocated and completely
// committed up front.
//
PagesNeeded = 0;
if (PfnDatabaseIsPhysical == FALSE) {
PointerPte = MiGetPteAddress (MI_PFN_ELEMENT(StartPage));
LastPte = MiGetPteAddress ((PCHAR)(MI_PFN_ELEMENT(EndPage)) - 1);
while (PointerPte <= LastPte) {
if (PointerPte->u.Hard.Valid == 0) {
PagesNeeded += 1;
}
PointerPte += 1;
}
if (MmAvailablePages < PagesNeeded) {
UNLOCK_PFN (OldIrql);
MmUnlockPagableImageSection(ExPageLockHandle);
ExReleaseFastMutex (&MmDynamicMemoryMutex);
ExFreePool (NewPhysicalMemoryBlock);
return STATUS_INSUFFICIENT_RESOURCES;
}
TempPte = ValidKernelPte;
PointerPte = MiGetPteAddress (MI_PFN_ELEMENT(StartPage));
while (PointerPte <= LastPte) {
if (PointerPte->u.Hard.Valid == 0) {
PageFrameIndex = MiRemoveZeroPage(MI_GET_PAGE_COLOR_FROM_PTE (PointerPte));
MiInitializePfn (PageFrameIndex, PointerPte, 0);
TempPte.u.Hard.PageFrameNumber = PageFrameIndex;
*PointerPte = TempPte;
}
PointerPte += 1;
}
MmResidentAvailablePages -= PagesNeeded;
}
//
// If the new range is adjacent to an existing range, just merge it into
// the old block. Otherwise use the new block as a new entry will have to
// be used.
//
NewPhysicalMemoryBlock->NumberOfRuns = MmPhysicalMemoryBlock->NumberOfRuns + 1;
NewPhysicalMemoryBlock->NumberOfPages = MmPhysicalMemoryBlock->NumberOfPages + NumberOfPages;
NewRun = &NewPhysicalMemoryBlock->Run[0];
start = 0;
Inserted = FALSE;
Updated = FALSE;
do {
Page = MmPhysicalMemoryBlock->Run[start].BasePage;
count = MmPhysicalMemoryBlock->Run[start].PageCount;
if (Inserted == FALSE) {
//
// Note overlaps into adjacent ranges were already checked above.
//
if (StartPage == Page + count) {
MmPhysicalMemoryBlock->Run[start].PageCount += NumberOfPages;
OldPhysicalMemoryBlock = NewPhysicalMemoryBlock;
MmPhysicalMemoryBlock->NumberOfPages += NumberOfPages;
//
// Coalesce below and above to avoid leaving zero length gaps
// as these gaps would prevent callers from removing ranges
// the span them.
//
if (start + 1 < MmPhysicalMemoryBlock->NumberOfRuns) {
start += 1;
Page = MmPhysicalMemoryBlock->Run[start].BasePage;
count = MmPhysicalMemoryBlock->Run[start].PageCount;
if (StartPage + NumberOfPages == Page) {
MmPhysicalMemoryBlock->Run[start - 1].PageCount +=
count;
MmPhysicalMemoryBlock->NumberOfRuns -= 1;
//
// Copy any remaining entries.
//
if (start != MmPhysicalMemoryBlock->NumberOfRuns) {
RtlMoveMemory (&MmPhysicalMemoryBlock->Run[start],
&MmPhysicalMemoryBlock->Run[start + 1],
(MmPhysicalMemoryBlock->NumberOfRuns - start) * sizeof (PHYSICAL_MEMORY_RUN));
}
}
}
Updated = TRUE;
break;
}
if (StartPage + NumberOfPages == Page) {
MmPhysicalMemoryBlock->Run[start].BasePage = StartPage;
MmPhysicalMemoryBlock->Run[start].PageCount += NumberOfPages;
OldPhysicalMemoryBlock = NewPhysicalMemoryBlock;
MmPhysicalMemoryBlock->NumberOfPages += NumberOfPages;
Updated = TRUE;
break;
}
if (StartPage + NumberOfPages <= Page) {
if (start + 1 < MmPhysicalMemoryBlock->NumberOfRuns) {
if (StartPage + NumberOfPages <= MmPhysicalMemoryBlock->Run[start + 1].BasePage) {
//
// Don't insert here - the new entry really belongs
// (at least) one entry further down.
//
continue;
}
}
NewRun->BasePage = StartPage;
NewRun->PageCount = NumberOfPages;
NewRun += 1;
Inserted = TRUE;
Updated = TRUE;
}
}
*NewRun = MmPhysicalMemoryBlock->Run[start];
NewRun += 1;
start += 1;
} while (start != MmPhysicalMemoryBlock->NumberOfRuns);
//
// If the memory block has not been updated, then the new entry must
// be added at the very end.
//
if (Updated == FALSE) {
ASSERT (Inserted == FALSE);
NewRun->BasePage = StartPage;
NewRun->PageCount = NumberOfPages;
Inserted = TRUE;
}
//
// Repoint the MmPhysicalMemoryBlock at the new chunk, free the old after
// releasing the PFN lock.
//
if (Inserted == TRUE) {
OldPhysicalMemoryBlock = MmPhysicalMemoryBlock;
MmPhysicalMemoryBlock = NewPhysicalMemoryBlock;
}
//
// Note that the page directory (page parent entries on Win64) must be
// filled in at system boot so that already-created processes do not fault
// when referencing the new PFNs.
//
//
// Walk through the memory descriptors and add pages to the
// free list in the PFN database.
//
PageFrameIndex = StartPage;
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
if (EndPage - 1 > MmHighestPhysicalPage) {
MmHighestPhysicalPage = EndPage - 1;
}
while (PageFrameIndex < EndPage) {
ASSERT (Pfn1->u2.ShareCount == 0);
ASSERT (Pfn1->u3.e2.ShortFlags == 0);
ASSERT (Pfn1->u3.e2.ReferenceCount == 0);
ASSERT64 (Pfn1->UsedPageTableEntries == 0);
ASSERT (Pfn1->OriginalPte.u.Long == ZeroKernelPte.u.Long);
ASSERT (Pfn1->u4.PteFrame == 0);
ASSERT ((Pfn1->PteAddress == PFN_REMOVED) ||
(Pfn1->PteAddress == (PMMPTE)(UINT_PTR)0));
//
// Initialize the color for NUMA purposes.
//
MiDetermineNode (PageFrameIndex, Pfn1);
//
// Set the PTE address to the physical page for
// virtual address alignment checking.
//
Pfn1->PteAddress = (PMMPTE)(PageFrameIndex << PTE_SHIFT);
MiInsertPageInFreeList (PageFrameIndex);
PageFrameIndex += 1;
Pfn1 += 1;
}
MmNumberOfPhysicalPages += (PFN_COUNT)NumberOfPages;
//
// Only non-compression ranges get to contribute to ResidentAvailable as
// adding compression ranges to this could crash the system.
//
// For the same reason, compression range additions also need to subtract
// from AvailablePages the amount the above MiInsertPageInFreeList added.
//
#if defined (_MI_COMPRESSION)
if (Flags & MM_PHYSICAL_MEMORY_PRODUCED_VIA_COMPRESSION) {
MmAvailablePages -= (PFN_COUNT) NumberOfPages;
MiNumberOfCompressionPages += NumberOfPages;
}
else {
MmResidentAvailablePages += NumberOfPages;
//
// Since real (noncompression-generated) physical memory was added,
// rearm the interrupt to occur at a higher threshold.
//
MiArmCompressionInterrupt ();
}
#else
MmResidentAvailablePages += NumberOfPages;
#endif
UNLOCK_PFN (OldIrql);
InterlockedExchangeAdd ((PLONG)&SharedUserData->NumberOfPhysicalPages,
(LONG) NumberOfPages);
//
// Carefully increase all commit limits to reflect the additional memory -
// notice the current usage must be done first so no one else cuts the
// line.
//
InterlockedExchangeAddSizeT (&MmTotalCommittedPages, PagesNeeded);
InterlockedExchangeAddSizeT (&MmTotalCommitLimitMaximum, NumberOfPages);
InterlockedExchangeAddSizeT (&MmTotalCommitLimit, NumberOfPages);
MmUnlockPagableImageSection(ExPageLockHandle);
ExReleaseFastMutex (&MmDynamicMemoryMutex);
ExFreePool (OldPhysicalMemoryBlock);
//
// Indicate number of bytes actually added to our caller.
//
NumberOfBytes->QuadPart = (ULONGLONG)NumberOfPages * PAGE_SIZE;
return STATUS_SUCCESS;
}
NTSTATUS
MiRemovePhysicalMemory (
IN PPHYSICAL_ADDRESS StartAddress,
IN OUT PLARGE_INTEGER NumberOfBytes,
IN LOGICAL PermanentRemoval,
IN ULONG Flags
)
/*++
Routine Description:
This routine attempts to remove the specified physical address range
from the system.
Arguments:
StartAddress - Supplies the starting physical address.
NumberOfBytes - Supplies a pointer to the number of bytes being removed.
PermanentRemoval - Supplies TRUE if the memory is being permanently
(ie: physically) removed. FALSE if not (ie: just a
bad page detected via ECC which is being marked
"don't-use".
Return Value:
NTSTATUS.
Environment:
Kernel mode. PASSIVE level. No locks held.
--*/
{
ULONG i;
ULONG Additional;
PFN_NUMBER Page;
PFN_NUMBER LastPage;
PFN_NUMBER OriginalLastPage;
PFN_NUMBER start;
PFN_NUMBER PagesReleased;
PMMPFN Pfn1;
PMMPFN StartPfn;
PMMPFN EndPfn;
KIRQL OldIrql;
PFN_NUMBER StartPage;
PFN_NUMBER EndPage;
PFN_COUNT NumberOfPages;
PFN_COUNT ParityPages;
SPFN_NUMBER MaxPages;
PFN_NUMBER PageFrameIndex;
PFN_NUMBER RemovedPages;
PFN_NUMBER RemovedPagesThisPass;
LOGICAL Inserted;
NTSTATUS Status;
PMMPTE PointerPte;
PMMPTE EndPte;
PVOID VirtualAddress;
PPHYSICAL_MEMORY_DESCRIPTOR OldPhysicalMemoryBlock;
PPHYSICAL_MEMORY_DESCRIPTOR NewPhysicalMemoryBlock;
PPHYSICAL_MEMORY_RUN NewRun;
LOGICAL PfnDatabaseIsPhysical;
PFN_NUMBER HighestPossiblePhysicalPage;
PFN_COUNT FluidPages;
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
ASSERT (BYTE_OFFSET(NumberOfBytes->LowPart) == 0);
ASSERT (BYTE_OFFSET(StartAddress->LowPart) == 0);
if (MI_IS_PHYSICAL_ADDRESS(MmPfnDatabase)) {
if (PermanentRemoval == TRUE) {
//
// The system must be configured for dynamic memory addition. This
// is not strictly required to remove the memory, but it's better
// to check for it now under the assumption that the administrator
// is probably going to want to add this range of memory back in -
// better to give the error now and refuse the removal than to
// refuse the addition later.
//
if (MmDynamicPfn == 0) {
return STATUS_NOT_SUPPORTED;
}
}
PfnDatabaseIsPhysical = TRUE;
}
else {
PfnDatabaseIsPhysical = FALSE;
}
if (PermanentRemoval == TRUE) {
HighestPossiblePhysicalPage = MmHighestPossiblePhysicalPage;
FluidPages = 100;
}
else {
HighestPossiblePhysicalPage = MmHighestPhysicalPage;
FluidPages = 0;
}
StartPage = (PFN_NUMBER)(StartAddress->QuadPart >> PAGE_SHIFT);
NumberOfPages = (PFN_COUNT)(NumberOfBytes->QuadPart >> PAGE_SHIFT);
EndPage = StartPage + NumberOfPages;
if (EndPage - 1 > HighestPossiblePhysicalPage) {
//
// Truncate the request into something that can be mapped by the PFN
// database.
//
EndPage = MmHighestPossiblePhysicalPage + 1;
NumberOfPages = (PFN_COUNT)(EndPage - StartPage);
}
//
// The range cannot wrap.
//
if (StartPage >= EndPage) {
return STATUS_INVALID_PARAMETER_1;
}
#if !defined (_MI_COMPRESSION)
if (Flags != 0) {
return STATUS_INVALID_PARAMETER_4;
}
#endif
StartPfn = MI_PFN_ELEMENT (StartPage);
EndPfn = MI_PFN_ELEMENT (EndPage);
ExAcquireFastMutex (&MmDynamicMemoryMutex);
#if DBG
MiDynmemData[0] += 1;
#endif
//
// Attempt to decrease all commit limits to reflect the removed memory.
//
if (MiChargeTemporaryCommitmentForReduction (NumberOfPages + FluidPages) == FALSE) {
#if DBG
MiDynmemData[1] += 1;
#endif
ExReleaseFastMutex (&MmDynamicMemoryMutex);
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Reduce the systemwide commit limit - note this is carefully done
// *PRIOR* to returning this commitment so no one else (including a DPC
// in this very thread) can consume past the limit.
//
InterlockedExchangeAddSizeT (&MmTotalCommitLimit, 0 - NumberOfPages);
InterlockedExchangeAddSizeT (&MmTotalCommitLimitMaximum, 0 - NumberOfPages);
//
// Now that the systemwide commit limit has been lowered, the amount
// we have removed can be safely returned.
//
MiReturnCommitment (NumberOfPages + FluidPages);
MmLockPagableSectionByHandle (ExPageLockHandle);
//
// Check for outstanding promises that cannot be broken.
//
LOCK_PFN (OldIrql);
if (PermanentRemoval == FALSE) {
//
// If it's just the removal of ECC-marked bad pages, then don't
// allow the caller to remove any pages that have already been
// ECC-removed. This is to prevent recursive erroneous charges.
//
for (Pfn1 = StartPfn; Pfn1 < EndPfn; Pfn1 += 1) {
if (Pfn1->u3.e1.ParityError == 1) {
UNLOCK_PFN (OldIrql);
Status = STATUS_INVALID_PARAMETER_2;
goto giveup2;
}
}
}
MaxPages = MI_NONPAGABLE_MEMORY_AVAILABLE() - FluidPages;
if ((SPFN_NUMBER)NumberOfPages > MaxPages) {
#if DBG
MiDynmemData[2] += 1;
#endif
UNLOCK_PFN (OldIrql);
Status = STATUS_INSUFFICIENT_RESOURCES;
goto giveup2;
}
//
// The range must be contained in a single entry. It is
// permissible for it to be part of a single entry, but it
// must not cross multiple entries.
//
Additional = (ULONG)-2;
start = 0;
do {
Page = MmPhysicalMemoryBlock->Run[start].BasePage;
LastPage = Page + MmPhysicalMemoryBlock->Run[start].PageCount;
if ((StartPage >= Page) && (EndPage <= LastPage)) {
if ((StartPage == Page) && (EndPage == LastPage)) {
Additional = (ULONG)-1;
}
else if ((StartPage == Page) || (EndPage == LastPage)) {
Additional = 0;
}
else {
Additional = 1;
}
break;
}
start += 1;
} while (start != MmPhysicalMemoryBlock->NumberOfRuns);
if (Additional == (ULONG)-2) {
#if DBG
MiDynmemData[3] += 1;
#endif
UNLOCK_PFN (OldIrql);
Status = STATUS_CONFLICTING_ADDRESSES;
goto giveup2;
}
for (Pfn1 = StartPfn; Pfn1 < EndPfn; Pfn1 += 1) {
Pfn1->u3.e1.RemovalRequested = 1;
}
if (PermanentRemoval == TRUE) {
MmNumberOfPhysicalPages -= NumberOfPages;
InterlockedExchangeAdd ((PLONG)&SharedUserData->NumberOfPhysicalPages,
0 - NumberOfPages);
}
#if defined (_MI_COMPRESSION)
//
// Only removal of non-compression ranges decrement ResidentAvailable as
// only those ranges actually incremented this when they were added.
//
if ((Flags & MM_PHYSICAL_MEMORY_PRODUCED_VIA_COMPRESSION) == 0) {
MmResidentAvailablePages -= NumberOfPages;
//
// Since real (noncompression-generated) physical memory is being
// removed, rearm the interrupt to occur at a lower threshold.
//
if (PermanentRemoval == TRUE) {
MiArmCompressionInterrupt ();
}
}
#else
MmResidentAvailablePages -= NumberOfPages;
#endif
//
// The free and zero lists must be pruned now before releasing the PFN
// lock otherwise if another thread allocates the page from these lists,
// the allocation will clear the RemovalRequested flag forever.
//
RemovedPages = MiRemovePhysicalPages (StartPage, EndPage);
#if defined (_MI_COMPRESSION)
//
// Compression range removals add back into AvailablePages the same
// amount that MiUnlinkPageFromList removes (as the original addition
// of these ranges never bumps this counter).
//
if (Flags & MM_PHYSICAL_MEMORY_PRODUCED_VIA_COMPRESSION) {
MmAvailablePages += (PFN_COUNT) RemovedPages;
MiNumberOfCompressionPages -= RemovedPages;
}
#endif
if (RemovedPages != NumberOfPages) {
#if DBG
retry:
#endif
Pfn1 = StartPfn;
InterlockedIncrement (&MiDelayPageFaults);
for (i = 0; i < 5; i += 1) {
UNLOCK_PFN (OldIrql);
//
// Attempt to move pages to the standby list. Note that only the
// pages with RemovalRequested set are moved.
//
MiTrimRemovalPagesOnly = TRUE;
MmEmptyAllWorkingSets ();
MiTrimRemovalPagesOnly = FALSE;
MiFlushAllPages ();
KeDelayExecutionThread (KernelMode, FALSE, (PLARGE_INTEGER)&MmHalfSecond);
LOCK_PFN (OldIrql);
RemovedPagesThisPass = MiRemovePhysicalPages (StartPage, EndPage);
RemovedPages += RemovedPagesThisPass;
#if defined (_MI_COMPRESSION)
//
// Compression range removals add back into AvailablePages the same
// amount that MiUnlinkPageFromList removes (as the original
// addition of these ranges never bumps this counter).
//
if (Flags & MM_PHYSICAL_MEMORY_PRODUCED_VIA_COMPRESSION) {
MmAvailablePages += (PFN_COUNT) RemovedPagesThisPass;
MiNumberOfCompressionPages -= RemovedPagesThisPass;
}
#endif
if (RemovedPages == NumberOfPages) {
break;
}
//
// RemovedPages doesn't include pages that were freed directly
// to the bad page list via MiDecrementReferenceCount or by
// ECC marking. So use the above check purely as an optimization -
// and walk here before ever giving up.
//
for ( ; Pfn1 < EndPfn; Pfn1 += 1) {
if (Pfn1->u3.e1.PageLocation != BadPageList) {
break;
}
}
if (Pfn1 == EndPfn) {
RemovedPages = NumberOfPages;
break;
}
}
InterlockedDecrement (&MiDelayPageFaults);
}
if (RemovedPages != NumberOfPages) {
#if DBG
MiDynmemData[4] += 1;
if (MiShowStuckPages != 0) {
RemovedPages = 0;
for (Pfn1 = StartPfn; Pfn1 < EndPfn; Pfn1 += 1) {
if (Pfn1->u3.e1.PageLocation != BadPageList) {
RemovedPages += 1;
}
}
ASSERT (RemovedPages != 0);
DbgPrint("MiRemovePhysicalMemory : could not get %d of %d pages\n",
RemovedPages, NumberOfPages);
if (MiShowStuckPages & 0x2) {
ULONG PfnsPrinted;
ULONG EnoughShown;
PMMPFN FirstPfn;
PFN_COUNT PfnCount;
PfnCount = 0;
PfnsPrinted = 0;
EnoughShown = 100;
//
// Initializing FirstPfn is not needed for correctness
// but without it the compiler cannot compile this code
// W4 to check for use of uninitialized variables.
//
FirstPfn = NULL;
if (MiShowStuckPages & 0x4) {
EnoughShown = (ULONG)-1;
}
DbgPrint("Stuck PFN list: ");
for (Pfn1 = StartPfn; Pfn1 < EndPfn; Pfn1 += 1) {
if (Pfn1->u3.e1.PageLocation != BadPageList) {
if (PfnCount == 0) {
FirstPfn = Pfn1;
}
PfnCount += 1;
}
else {
if (PfnCount != 0) {
DbgPrint("%x -> %x ; ", FirstPfn - MmPfnDatabase,
(FirstPfn - MmPfnDatabase) + PfnCount - 1);
PfnsPrinted += 1;
if (PfnsPrinted == EnoughShown) {
break;
}
PfnCount = 0;
}
}
}
if (PfnCount != 0) {
DbgPrint("%x -> %x ; ", FirstPfn - MmPfnDatabase,
(FirstPfn - MmPfnDatabase) + PfnCount - 1);
}
DbgPrint("\n");
}
if (MiShowStuckPages & 0x8) {
DbgBreakPoint ();
}
if (MiShowStuckPages & 0x10) {
goto retry;
}
}
#endif
UNLOCK_PFN (OldIrql);
Status = STATUS_NO_MEMORY;
goto giveup;
}
#if DBG
for (Pfn1 = StartPfn; Pfn1 < EndPfn; Pfn1 += 1) {
ASSERT (Pfn1->u3.e1.PageLocation == BadPageList);
}
#endif
//
// All the pages in the range have been removed.
//
if (PermanentRemoval == FALSE) {
//
// If it's just the removal of ECC-marked bad pages, then no
// adjustment to the physical memory block ranges or PFN database
// trimming is needed. Exit now.
//
for (Pfn1 = StartPfn; Pfn1 < EndPfn; Pfn1 += 1) {
ASSERT (Pfn1->u3.e1.ParityError == 0);
Pfn1->u3.e1.ParityError = 1;
}
UNLOCK_PFN (OldIrql);
MmUnlockPagableImageSection(ExPageLockHandle);
ExReleaseFastMutex (&MmDynamicMemoryMutex);
NumberOfBytes->QuadPart = (ULONGLONG)NumberOfPages * PAGE_SIZE;
return STATUS_SUCCESS;
}
//
// Update the physical memory blocks and other associated housekeeping.
//
if (Additional == 0) {
//
// The range can be split off from an end of an existing chunk so no
// pool growth or shrinkage is required.
//
NewPhysicalMemoryBlock = MmPhysicalMemoryBlock;
OldPhysicalMemoryBlock = NULL;
}
else {
//
// The range cannot be split off from an end of an existing chunk so
// pool growth or shrinkage is required.
//
UNLOCK_PFN (OldIrql);
i = (sizeof(PHYSICAL_MEMORY_DESCRIPTOR) +
(sizeof(PHYSICAL_MEMORY_RUN) * (MmPhysicalMemoryBlock->NumberOfRuns + Additional)));
NewPhysicalMemoryBlock = ExAllocatePoolWithTag (NonPagedPool,
i,
' mM');
if (NewPhysicalMemoryBlock == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
#if DBG
MiDynmemData[5] += 1;
#endif
goto giveup;
}
OldPhysicalMemoryBlock = MmPhysicalMemoryBlock;
RtlZeroMemory (NewPhysicalMemoryBlock, i);
LOCK_PFN (OldIrql);
}
//
// Remove or split the requested range from the existing memory block.
//
NewPhysicalMemoryBlock->NumberOfRuns = MmPhysicalMemoryBlock->NumberOfRuns + Additional;
NewPhysicalMemoryBlock->NumberOfPages = MmPhysicalMemoryBlock->NumberOfPages - NumberOfPages;
NewRun = &NewPhysicalMemoryBlock->Run[0];
start = 0;
Inserted = FALSE;
do {
Page = MmPhysicalMemoryBlock->Run[start].BasePage;
LastPage = Page + MmPhysicalMemoryBlock->Run[start].PageCount;
if (Inserted == FALSE) {
if ((StartPage >= Page) && (EndPage <= LastPage)) {
if ((StartPage == Page) && (EndPage == LastPage)) {
ASSERT (Additional == -1);
start += 1;
continue;
}
else if ((StartPage == Page) || (EndPage == LastPage)) {
ASSERT (Additional == 0);
if (StartPage == Page) {
MmPhysicalMemoryBlock->Run[start].BasePage += NumberOfPages;
}
MmPhysicalMemoryBlock->Run[start].PageCount -= NumberOfPages;
}
else {
ASSERT (Additional == 1);
OriginalLastPage = LastPage;
MmPhysicalMemoryBlock->Run[start].PageCount =
StartPage - MmPhysicalMemoryBlock->Run[start].BasePage;
*NewRun = MmPhysicalMemoryBlock->Run[start];
NewRun += 1;
NewRun->BasePage = EndPage;
NewRun->PageCount = OriginalLastPage - EndPage;
NewRun += 1;
start += 1;
continue;
}
Inserted = TRUE;
}
}
*NewRun = MmPhysicalMemoryBlock->Run[start];
NewRun += 1;
start += 1;
} while (start != MmPhysicalMemoryBlock->NumberOfRuns);
//
// Repoint the MmPhysicalMemoryBlock at the new chunk.
// Free the old block after releasing the PFN lock.
//
MmPhysicalMemoryBlock = NewPhysicalMemoryBlock;
if (EndPage - 1 == MmHighestPhysicalPage) {
MmHighestPhysicalPage = StartPage - 1;
}
//
// Throw away all the removed pages that are currently enqueued.
//
ParityPages = 0;
for (Pfn1 = StartPfn; Pfn1 < EndPfn; Pfn1 += 1) {
ASSERT (Pfn1->u3.e1.PageLocation == BadPageList);
ASSERT (Pfn1->u3.e1.RemovalRequested == 1);
//
// Some pages may have already been ECC-removed. For these pages,
// the commit limits and resident available pages have already been
// adjusted - tally them here so we can undo the extraneous charge
// just applied.
//
if (Pfn1->u3.e1.ParityError == 1) {
ParityPages += 1;
}
MiUnlinkPageFromList (Pfn1);
ASSERT (Pfn1->u1.Flink == 0);
ASSERT (Pfn1->u2.Blink == 0);
ASSERT (Pfn1->u3.e2.ReferenceCount == 0);
ASSERT64 (Pfn1->UsedPageTableEntries == 0);
Pfn1->PteAddress = PFN_REMOVED;
//
// Note this clears ParityError among other flags...
//
Pfn1->u3.e2.ShortFlags = 0;
Pfn1->OriginalPte.u.Long = ZeroKernelPte.u.Long;
Pfn1->u4.PteFrame = 0;
}
//
// Now that the removed pages have been discarded, eliminate the PFN
// entries that mapped them. Straddling entries left over from an
// adjacent earlier removal are not collapsed at this point.
//
//
PagesReleased = 0;
if (PfnDatabaseIsPhysical == FALSE) {
VirtualAddress = (PVOID)ROUND_TO_PAGES(MI_PFN_ELEMENT(StartPage));
PointerPte = MiGetPteAddress (VirtualAddress);
EndPte = MiGetPteAddress (PAGE_ALIGN(MI_PFN_ELEMENT(EndPage)));
while (PointerPte < EndPte) {
PageFrameIndex = MI_GET_PAGE_FRAME_FROM_PTE (PointerPte);
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
ASSERT (Pfn1->u2.ShareCount == 1);
ASSERT (Pfn1->u3.e2.ReferenceCount == 1);
Pfn1->u2.ShareCount = 0;
MI_SET_PFN_DELETED (Pfn1);
#if DBG
Pfn1->u3.e1.PageLocation = StandbyPageList;
#endif //DBG
MiDecrementReferenceCount (PageFrameIndex);
KeFlushSingleTb (VirtualAddress,
TRUE,
TRUE,
(PHARDWARE_PTE)PointerPte,
ZeroKernelPte.u.Flush);
PagesReleased += 1;
PointerPte += 1;
VirtualAddress = (PVOID)((PCHAR)VirtualAddress + PAGE_SIZE);
}
MmResidentAvailablePages += PagesReleased;
}
#if DBG
MiDynmemData[6] += 1;
#endif
//
// Give back anything that has been double-charged.
//
if (ParityPages != 0) {
MmResidentAvailablePages += ParityPages;
}
UNLOCK_PFN (OldIrql);
//
// Give back anything that has been double-charged.
//
if (ParityPages != 0) {
InterlockedExchangeAddSizeT (&MmTotalCommitLimitMaximum, ParityPages);
InterlockedExchangeAddSizeT (&MmTotalCommitLimit, ParityPages);
}
if (PagesReleased != 0) {
MiReturnCommitment (PagesReleased);
}
MmUnlockPagableImageSection(ExPageLockHandle);
ExReleaseFastMutex (&MmDynamicMemoryMutex);
if (OldPhysicalMemoryBlock != NULL) {
ExFreePool (OldPhysicalMemoryBlock);
}
NumberOfBytes->QuadPart = (ULONGLONG)NumberOfPages * PAGE_SIZE;
return STATUS_SUCCESS;
giveup:
//
// All the pages in the range were not obtained. Back everything out.
//
PageFrameIndex = StartPage;
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
LOCK_PFN (OldIrql);
while (PageFrameIndex < EndPage) {
ASSERT (Pfn1->u3.e1.RemovalRequested == 1);
Pfn1->u3.e1.RemovalRequested = 0;
if (Pfn1->u3.e1.PageLocation == BadPageList) {
MiUnlinkPageFromList (Pfn1);
MiInsertPageInFreeList (PageFrameIndex);
}
Pfn1 += 1;
PageFrameIndex += 1;
}
#if defined (_MI_COMPRESSION)
//
// Only removal of non-compression ranges decrement ResidentAvailable as
// only those ranges actually incremented this when they were added.
//
if ((Flags & MM_PHYSICAL_MEMORY_PRODUCED_VIA_COMPRESSION) == 0) {
MmResidentAvailablePages += NumberOfPages;
}
else {
//
// Compression range removals add back into AvailablePages the same
// amount that MiUnlinkPageFromList removes (as the original
// addition of these ranges never bumps this counter).
//
MmAvailablePages -= (PFN_COUNT) RemovedPages;
MiNumberOfCompressionPages += RemovedPages;
}
#else
MmResidentAvailablePages += NumberOfPages;
#endif
if (PermanentRemoval == TRUE) {
MmNumberOfPhysicalPages += NumberOfPages;
InterlockedExchangeAdd ((PLONG)&SharedUserData->NumberOfPhysicalPages,
NumberOfPages);
#if defined (_MI_COMPRESSION)
//
// Rearm the interrupt to occur at the original threshold.
//
if ((Flags & MM_PHYSICAL_MEMORY_PRODUCED_VIA_COMPRESSION) == 0) {
MiArmCompressionInterrupt ();
}
#endif
}
UNLOCK_PFN (OldIrql);
giveup2:
InterlockedExchangeAddSizeT (&MmTotalCommitLimitMaximum, NumberOfPages);
InterlockedExchangeAddSizeT (&MmTotalCommitLimit, NumberOfPages);
MmUnlockPagableImageSection(ExPageLockHandle);
ExReleaseFastMutex (&MmDynamicMemoryMutex);
return Status;
}
NTSTATUS
MmRemovePhysicalMemory (
IN PPHYSICAL_ADDRESS StartAddress,
IN OUT PLARGE_INTEGER NumberOfBytes
)
/*++
Routine Description:
A wrapper for MmRemovePhysicalMemoryEx.
Arguments:
StartAddress - Supplies the starting physical address.
NumberOfBytes - Supplies a pointer to the number of bytes being removed.
Return Value:
NTSTATUS.
Environment:
Kernel mode. PASSIVE level. No locks held.
--*/
{
return MmRemovePhysicalMemoryEx (StartAddress, NumberOfBytes, 0);
}
NTSTATUS
MmRemovePhysicalMemoryEx (
IN PPHYSICAL_ADDRESS StartAddress,
IN OUT PLARGE_INTEGER NumberOfBytes,
IN ULONG Flags
)
/*++
Routine Description:
This routine attempts to remove the specified physical address range
from the system.
Arguments:
StartAddress - Supplies the starting physical address.
NumberOfBytes - Supplies a pointer to the number of bytes being removed.
Flags - Supplies relevant flags describing the memory range.
Return Value:
NTSTATUS.
Environment:
Kernel mode. PASSIVE level. No locks held.
--*/
{
NTSTATUS Status;
#if defined (_X86_)
BOOLEAN CachesFlushed;
#endif
#if defined(_IA64_)
PVOID VirtualAddress;
PVOID SingleVirtualAddress;
SIZE_T SizeInBytes;
SIZE_T MapSizeInBytes;
PFN_COUNT NumberOfPages;
PFN_COUNT i;
PFN_NUMBER StartPage;
#endif
PAGED_CODE();
#if defined (_MI_COMPRESSION_SUPPORTED_)
if (Flags & MM_PHYSICAL_MEMORY_PRODUCED_VIA_COMPRESSION) {
return STATUS_NOT_SUPPORTED;
}
#else
if (Flags != 0) {
return STATUS_INVALID_PARAMETER_3;
}
#endif
#if defined (_X86_)
//
// Issue a cache invalidation here just as a test to make sure the
// machine can support it. If not, then don't bother trying to remove
// any memory.
//
CachesFlushed = KeInvalidateAllCaches (TRUE);
if (CachesFlushed == FALSE) {
return STATUS_NOT_SUPPORTED;
}
#endif
#if defined(_IA64_)
//
// Pick up at least a single PTE mapping now as we do not want to fail this
// call if no PTEs are available after a successful remove. Resorting to
// actually using this PTE should be a very rare case indeed.
//
SingleVirtualAddress = (PMMPTE)MiMapSinglePage (NULL,
0,
MmCached,
HighPagePriority);
if (SingleVirtualAddress == NULL) {
return STATUS_INSUFFICIENT_RESOURCES;
}
#endif
Status = MiRemovePhysicalMemory (StartAddress, NumberOfBytes, TRUE, Flags);
if (NT_SUCCESS (Status)) {
#if defined (_X86_)
CachesFlushed = KeInvalidateAllCaches (TRUE);
ASSERT (CachesFlushed == TRUE);
#endif
#if defined(_IA64_)
SizeInBytes = (SIZE_T)NumberOfBytes->QuadPart;
//
// Flush the entire TB to remove any KSEG translations that may map the
// pages being removed. Otherwise hardware or software speculation
// can reference the memory speculatively which would crash the machine.
//
KeFlushEntireTb (TRUE, TRUE);
//
// Establish an uncached mapping to the pages being removed.
//
MapSizeInBytes = SizeInBytes;
//
// Initializing VirtualAddress is not needed for correctness
// but without it the compiler cannot compile this code
// W4 to check for use of uninitialized variables.
//
VirtualAddress = NULL;
while (MapSizeInBytes > PAGE_SIZE) {
VirtualAddress = MmMapIoSpace (*StartAddress,
MapSizeInBytes,
MmNonCached);
if (VirtualAddress != NULL) {
break;
}
MapSizeInBytes = MapSizeInBytes >> 1;
}
if (MapSizeInBytes <= PAGE_SIZE) {
StartPage = (PFN_NUMBER)(StartAddress->QuadPart >> PAGE_SHIFT);
NumberOfPages = (PFN_COUNT)(NumberOfBytes->QuadPart >> PAGE_SHIFT);
for (i = 0; i < NumberOfPages; i += 1) {
SingleVirtualAddress = (PMMPTE)MiMapSinglePage (SingleVirtualAddress,
StartPage,
MmCached,
HighPagePriority);
KeSweepCacheRangeWithDrain (TRUE,
SingleVirtualAddress,
PAGE_SIZE);
StartPage += 1;
}
}
else {
//
// Drain all pending transactions and prefetches and perform cache
// evictions. Only drain 4gb max at a time as this API takes a
// ULONG.
//
while (SizeInBytes > _4gb) {
KeSweepCacheRangeWithDrain (TRUE, VirtualAddress, _4gb - 1);
SizeInBytes -= (_4gb - 1);
}
KeSweepCacheRangeWithDrain (TRUE,
VirtualAddress,
(ULONG)SizeInBytes);
MmUnmapIoSpace (VirtualAddress, NumberOfBytes->QuadPart);
}
#endif
}
#if defined(_IA64_)
MiUnmapSinglePage (SingleVirtualAddress);
#endif
return Status;
}
NTSTATUS
MmMarkPhysicalMemoryAsBad (
IN PPHYSICAL_ADDRESS StartAddress,
IN OUT PLARGE_INTEGER NumberOfBytes
)
/*++
Routine Description:
This routine attempts to mark the specified physical address range
as bad so the system will not use it. This is generally done for pages
which contain ECC errors.
Note that this is different from removing pages permanently (ie: physically
removing the memory board) which should be done via the
MmRemovePhysicalMemory API.
The caller is responsible for maintaining a global table so that subsequent
boots can examine it and remove the ECC pages before loading the kernel.
Arguments:
StartAddress - Supplies the starting physical address.
NumberOfBytes - Supplies a pointer to the number of bytes being removed.
Return Value:
NTSTATUS.
Environment:
Kernel mode. PASSIVE level. No locks held.
--*/
{
PAGED_CODE();
return MiRemovePhysicalMemory (StartAddress, NumberOfBytes, FALSE, 0);
}
NTSTATUS
MmMarkPhysicalMemoryAsGood (
IN PPHYSICAL_ADDRESS StartAddress,
IN OUT PLARGE_INTEGER NumberOfBytes
)
/*++
Routine Description:
This routine attempts to mark the specified physical address range
as good so the system will use it. This is generally done for pages
which used to (but presumably no longer do) contain ECC errors.
Note that this is different from adding pages permanently (ie: physically
inserting a new memory board) which should be done via the
MmAddPhysicalMemory API.
The caller is responsible for removing these entries from a global table
so that subsequent boots will use the pages.
Arguments:
StartAddress - Supplies the starting physical address.
NumberOfBytes - Supplies a pointer to the number of bytes being removed.
Return Value:
NTSTATUS.
Environment:
Kernel mode. PASSIVE level. No locks held.
--*/
{
PMMPFN Pfn1;
KIRQL OldIrql;
PFN_NUMBER NumberOfPages;
PFN_NUMBER start;
PFN_NUMBER count;
PFN_NUMBER StartPage;
PFN_NUMBER EndPage;
PFN_NUMBER PageFrameIndex;
PFN_NUMBER Page;
PFN_NUMBER LastPage;
LOGICAL PfnDatabaseIsPhysical;
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
ASSERT (BYTE_OFFSET(NumberOfBytes->LowPart) == 0);
ASSERT (BYTE_OFFSET(StartAddress->LowPart) == 0);
if (MI_IS_PHYSICAL_ADDRESS(MmPfnDatabase)) {
PfnDatabaseIsPhysical = TRUE;
}
else {
PfnDatabaseIsPhysical = FALSE;
}
StartPage = (PFN_NUMBER)(StartAddress->QuadPart >> PAGE_SHIFT);
NumberOfPages = (PFN_NUMBER)(NumberOfBytes->QuadPart >> PAGE_SHIFT);
EndPage = StartPage + NumberOfPages;
ExAcquireFastMutex (&MmDynamicMemoryMutex);
if (EndPage - 1 > MmHighestPhysicalPage) {
//
// Truncate the request into something that can be mapped by the PFN
// database.
//
EndPage = MmHighestPhysicalPage + 1;
NumberOfPages = EndPage - StartPage;
}
//
// The range cannot wrap.
//
if (StartPage >= EndPage) {
ExReleaseFastMutex (&MmDynamicMemoryMutex);
return STATUS_INVALID_PARAMETER_1;
}
//
// The request must lie within an already present range.
//
start = 0;
MmLockPagableSectionByHandle (ExPageLockHandle);
LOCK_PFN (OldIrql);
do {
count = MmPhysicalMemoryBlock->Run[start].PageCount;
Page = MmPhysicalMemoryBlock->Run[start].BasePage;
if (count != 0) {
LastPage = Page + count;
if ((StartPage >= Page) && (EndPage <= LastPage)) {
break;
}
}
start += 1;
} while (start != MmPhysicalMemoryBlock->NumberOfRuns);
if (start == MmPhysicalMemoryBlock->NumberOfRuns) {
UNLOCK_PFN (OldIrql);
MmUnlockPagableImageSection(ExPageLockHandle);
ExReleaseFastMutex (&MmDynamicMemoryMutex);
return STATUS_CONFLICTING_ADDRESSES;
}
//
// Walk through the range and add only pages previously removed to the
// free list in the PFN database.
//
PageFrameIndex = StartPage;
Pfn1 = MI_PFN_ELEMENT (PageFrameIndex);
NumberOfPages = 0;
while (PageFrameIndex < EndPage) {
if ((Pfn1->u3.e1.ParityError == 1) &&
(Pfn1->u3.e1.RemovalRequested == 1) &&
(Pfn1->u3.e1.PageLocation == BadPageList)) {
Pfn1->u3.e1.ParityError = 0;
Pfn1->u3.e1.RemovalRequested = 0;
MiUnlinkPageFromList (Pfn1);
MiInsertPageInFreeList (PageFrameIndex);
NumberOfPages += 1;
}
Pfn1 += 1;
PageFrameIndex += 1;
}
MmResidentAvailablePages += NumberOfPages;
UNLOCK_PFN (OldIrql);
//
// Increase all commit limits to reflect the additional memory.
//
InterlockedExchangeAddSizeT (&MmTotalCommitLimitMaximum, NumberOfPages);
InterlockedExchangeAddSizeT (&MmTotalCommitLimit, NumberOfPages);
MmUnlockPagableImageSection(ExPageLockHandle);
ExReleaseFastMutex (&MmDynamicMemoryMutex);
//
// Indicate number of bytes actually added to our caller.
//
NumberOfBytes->QuadPart = (ULONGLONG)NumberOfPages * PAGE_SIZE;
return STATUS_SUCCESS;
}
PPHYSICAL_MEMORY_RANGE
MmGetPhysicalMemoryRanges (
VOID
)
/*++
Routine Description:
This routine returns the virtual address of a nonpaged pool block which
contains the physical memory ranges in the system.
The returned block contains physical address and page count pairs.
The last entry contains zero for both.
The caller must understand that this block can change at any point before
or after this snapshot.
It is the caller's responsibility to free this block.
Arguments:
None.
Return Value:
NULL on failure.
Environment:
Kernel mode. PASSIVE level. No locks held.
--*/
{
ULONG i;
KIRQL OldIrql;
PPHYSICAL_MEMORY_RANGE p;
PPHYSICAL_MEMORY_RANGE PhysicalMemoryBlock;
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
ExAcquireFastMutex (&MmDynamicMemoryMutex);
i = sizeof(PHYSICAL_MEMORY_RANGE) * (MmPhysicalMemoryBlock->NumberOfRuns + 1);
PhysicalMemoryBlock = ExAllocatePoolWithTag (NonPagedPool,
i,
'hPmM');
if (PhysicalMemoryBlock == NULL) {
ExReleaseFastMutex (&MmDynamicMemoryMutex);
return NULL;
}
p = PhysicalMemoryBlock;
MmLockPagableSectionByHandle (ExPageLockHandle);
LOCK_PFN (OldIrql);
ASSERT (i == (sizeof(PHYSICAL_MEMORY_RANGE) * (MmPhysicalMemoryBlock->NumberOfRuns + 1)));
for (i = 0; i < MmPhysicalMemoryBlock->NumberOfRuns; i += 1) {
p->BaseAddress.QuadPart = (LONGLONG)MmPhysicalMemoryBlock->Run[i].BasePage * PAGE_SIZE;
p->NumberOfBytes.QuadPart = (LONGLONG)MmPhysicalMemoryBlock->Run[i].PageCount * PAGE_SIZE;
p += 1;
}
p->BaseAddress.QuadPart = 0;
p->NumberOfBytes.QuadPart = 0;
UNLOCK_PFN (OldIrql);
MmUnlockPagableImageSection(ExPageLockHandle);
ExReleaseFastMutex (&MmDynamicMemoryMutex);
return PhysicalMemoryBlock;
}
PFN_COUNT
MiRemovePhysicalPages (
IN PFN_NUMBER StartPage,
IN PFN_NUMBER EndPage
)
/*++
Routine Description:
This routine searches the PFN database for free, zeroed or standby pages
that are marked for removal.
Arguments:
StartPage - Supplies the low physical frame number to remove.
EndPage - Supplies the last physical frame number to remove.
Return Value:
Returns the number of pages removed from the free, zeroed and standby lists.
Environment:
Kernel mode, PFN lock held. Since this routine is PAGELK, the caller is
responsible for locking it down and unlocking it on return.
--*/
{
PMMPFN Pfn1;
PMMPFN Pfn2;
PMMPFN PfnNextColored;
PMMPFN PfnNextFlink;
PMMPFN PfnLastColored;
PFN_NUMBER Page;
LOGICAL RemovePage;
ULONG Color;
PMMCOLOR_TABLES ColorHead;
PFN_NUMBER MovedPage;
MMLISTS MemoryList;
PFN_NUMBER PageNextColored;
PFN_NUMBER PageNextFlink;
PFN_NUMBER PageLastColored;
PFN_COUNT NumberOfPages;
PMMPFNLIST ListHead;
LOGICAL RescanNeeded;
MM_PFN_LOCK_ASSERT();
NumberOfPages = 0;
rescan:
//
// Grab all zeroed (and then free) pages first directly from the
// colored lists to avoid multiple walks down these singly linked lists.
// Handle transition pages last.
//
for (MemoryList = ZeroedPageList; MemoryList <= FreePageList; MemoryList += 1) {
ListHead = MmPageLocationList[MemoryList];
for (Color = 0; Color < MmSecondaryColors; Color += 1) {
ColorHead = &MmFreePagesByColor[MemoryList][Color];
MovedPage = (PFN_NUMBER) MM_EMPTY_LIST;
while (ColorHead->Flink != MM_EMPTY_LIST) {
Page = ColorHead->Flink;
Pfn1 = MI_PFN_ELEMENT(Page);
ASSERT ((MMLISTS)Pfn1->u3.e1.PageLocation == MemoryList);
//
// The Flink and Blink must be nonzero here for the page
// to be on the listhead. Only code that scans the
// MmPhysicalMemoryBlock has to check for the zero case.
//
ASSERT (Pfn1->u1.Flink != 0);
ASSERT (Pfn1->u2.Blink != 0);
//
// See if the page is desired by the caller.
//
// Systems utilizing memory compression may have more
// pages on the zero, free and standby lists than we
// want to give out. Explicitly check MmAvailablePages
// instead (and recheck whenever the PFN lock is
// released and reacquired).
//
if ((Pfn1->u3.e1.RemovalRequested == 1) &&
(MmAvailablePages != 0)) {
ASSERT (Pfn1->u3.e1.ReadInProgress == 0);
MiUnlinkFreeOrZeroedPage (Page);
MiInsertPageInList (&MmBadPageListHead, Page);
NumberOfPages += 1;
}
else {
//
// Unwanted so put the page on the end of list.
// If first time, save pfn.
//
if (MovedPage == MM_EMPTY_LIST) {
MovedPage = Page;
}
else if (Page == MovedPage) {
//
// No more pages available in this colored chain.
//
break;
}
//
// If the colored chain has more than one entry then
// put this page on the end.
//
PageNextColored = (PFN_NUMBER)Pfn1->OriginalPte.u.Long;
if (PageNextColored == MM_EMPTY_LIST) {
//
// No more pages available in this colored chain.
//
break;
}
ASSERT (Pfn1->u1.Flink != 0);
ASSERT (Pfn1->u1.Flink != MM_EMPTY_LIST);
ASSERT (Pfn1->u4.PteFrame != MI_MAGIC_AWE_PTEFRAME);
PfnNextColored = MI_PFN_ELEMENT(PageNextColored);
ASSERT ((MMLISTS)PfnNextColored->u3.e1.PageLocation == MemoryList);
ASSERT (PfnNextColored->u4.PteFrame != MI_MAGIC_AWE_PTEFRAME);
//
// Adjust the free page list so Page
// follows PageNextFlink.
//
PageNextFlink = Pfn1->u1.Flink;
PfnNextFlink = MI_PFN_ELEMENT(PageNextFlink);
ASSERT ((MMLISTS)PfnNextFlink->u3.e1.PageLocation == MemoryList);
ASSERT (PfnNextFlink->u4.PteFrame != MI_MAGIC_AWE_PTEFRAME);
PfnLastColored = ColorHead->Blink;
ASSERT (PfnLastColored != (PMMPFN)MM_EMPTY_LIST);
ASSERT (PfnLastColored->OriginalPte.u.Long == MM_EMPTY_LIST);
ASSERT (PfnLastColored->u4.PteFrame != MI_MAGIC_AWE_PTEFRAME);
ASSERT (PfnLastColored->u2.Blink != MM_EMPTY_LIST);
ASSERT ((MMLISTS)PfnLastColored->u3.e1.PageLocation == MemoryList);
PageLastColored = PfnLastColored - MmPfnDatabase;
if (ListHead->Flink == Page) {
ASSERT (Pfn1->u2.Blink == MM_EMPTY_LIST);
ASSERT (ListHead->Blink != Page);
ListHead->Flink = PageNextFlink;
PfnNextFlink->u2.Blink = MM_EMPTY_LIST;
}
else {
ASSERT (Pfn1->u2.Blink != MM_EMPTY_LIST);
ASSERT ((MMLISTS)(MI_PFN_ELEMENT((MI_PFN_ELEMENT(Pfn1->u2.Blink)->u1.Flink)))->u4.PteFrame != MI_MAGIC_AWE_PTEFRAME);
ASSERT ((MMLISTS)(MI_PFN_ELEMENT((MI_PFN_ELEMENT(Pfn1->u2.Blink)->u1.Flink)))->u3.e1.PageLocation == MemoryList);
MI_PFN_ELEMENT(Pfn1->u2.Blink)->u1.Flink = PageNextFlink;
PfnNextFlink->u2.Blink = Pfn1->u2.Blink;
}
#if DBG
if (PfnLastColored->u1.Flink == MM_EMPTY_LIST) {
ASSERT (ListHead->Blink == PageLastColored);
}
#endif
Pfn1->u1.Flink = PfnLastColored->u1.Flink;
Pfn1->u2.Blink = PageLastColored;
if (ListHead->Blink == PageLastColored) {
ListHead->Blink = Page;
}
//
// Adjust the colored chains.
//
if (PfnLastColored->u1.Flink != MM_EMPTY_LIST) {
ASSERT (MI_PFN_ELEMENT(PfnLastColored->u1.Flink)->u4.PteFrame != MI_MAGIC_AWE_PTEFRAME);
ASSERT ((MMLISTS)(MI_PFN_ELEMENT(PfnLastColored->u1.Flink)->u3.e1.PageLocation) == MemoryList);
MI_PFN_ELEMENT(PfnLastColored->u1.Flink)->u2.Blink = Page;
}
PfnLastColored->u1.Flink = Page;
ColorHead->Flink = PageNextColored;
Pfn1->OriginalPte.u.Long = MM_EMPTY_LIST;
ASSERT (PfnLastColored->OriginalPte.u.Long == MM_EMPTY_LIST);
PfnLastColored->OriginalPte.u.Long = Page;
ColorHead->Blink = Pfn1;
}
}
}
}
RescanNeeded = FALSE;
Pfn1 = MI_PFN_ELEMENT (StartPage);
do {
if ((Pfn1->u3.e1.PageLocation == StandbyPageList) &&
(Pfn1->u1.Flink != 0) &&
(Pfn1->u2.Blink != 0) &&
(Pfn1->u3.e2.ReferenceCount == 0) &&
(MmAvailablePages != 0)) {
//
// Systems utilizing memory compression may have more
// pages on the zero, free and standby lists than we
// want to give out. Explicitly check MmAvailablePages
// above instead (and recheck whenever the PFN lock is
// released and reacquired).
//
ASSERT (Pfn1->u3.e1.ReadInProgress == 0);
RemovePage = TRUE;
if (Pfn1->u3.e1.RemovalRequested == 0) {
//
// This page is not directly needed for a hot remove - but if
// it contains a chunk of prototype PTEs (and this chunk is
// in a page that needs to be removed), then any pages
// referenced by transition prototype PTEs must also be removed
// before the desired page can be removed.
//
// The same analogy holds for page table, directory, parent
// and extended parent pages.
//
Pfn2 = MI_PFN_ELEMENT (Pfn1->u4.PteFrame);
if (Pfn2->u3.e1.RemovalRequested == 0) {
#if (_MI_PAGING_LEVELS >= 3)
Pfn2 = MI_PFN_ELEMENT (Pfn2->u4.PteFrame);
if (Pfn2->u3.e1.RemovalRequested == 0) {
RemovePage = FALSE;
}
else if (Pfn2->u2.ShareCount == 1) {
RescanNeeded = TRUE;
}
#if (_MI_PAGING_LEVELS >= 4)
Pfn2 = MI_PFN_ELEMENT (Pfn2->u4.PteFrame);
if (Pfn2->u3.e1.RemovalRequested == 0) {
RemovePage = FALSE;
}
else if (Pfn2->u2.ShareCount == 1) {
RescanNeeded = TRUE;
}
#endif
#else
RemovePage = FALSE;
#endif
}
else if (Pfn2->u2.ShareCount == 1) {
RescanNeeded = TRUE;
}
}
if (RemovePage == TRUE) {
//
// This page is in the desired range - grab it.
//
MiUnlinkPageFromList (Pfn1);
MiRestoreTransitionPte (StartPage);
MiInsertPageInList (&MmBadPageListHead, StartPage);
NumberOfPages += 1;
}
}
StartPage += 1;
Pfn1 += 1;
} while (StartPage < EndPage);
if (RescanNeeded == TRUE) {
//
// A page table, directory or parent was freed by removing a transition
// page from the cache. Rescan from the top to pick it up.
//
#if DBG
MiDynmemData[7] += 1;
#endif
goto rescan;
}
#if DBG
else {
MiDynmemData[8] += 1;
}
#endif
return NumberOfPages;
}