/*
 * Copyright (C) 1998-2005 ReactOS Team (and the authors from the programmers section)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 *
 * PROJECT:         ReactOS kernel
 * FILE:            ntoskrnl/mm/marea.c
 * PURPOSE:         Implements memory areas
 *
 * PROGRAMMERS:     Rex Jolliff
 *                  David Welch
 *                  Eric Kohl
 *                  Philip Susi
 *                  Casper Hornstrup
 *                  Eric Kohl
 *                  Ge van Geldorp
 *                  Royce Mitchell III
 *                  Aleksey Bragin
 *                  Jason Filby
 *                  Thomas Weidenmueller
 *                  Gunnar Andre' Dalsnes
 *                  Mike Nordell
 *                  Alex Ionescu
 *                  Filip Navara
 *                  Herve Poussineau
 *                  Steven Edwards
 */

/* INCLUDES *****************************************************************/

#include <ntoskrnl.h>
#define NDEBUG
#include "../cache/section/newmm.h"
#include <debug.h>

#include "ARM3/miarm.h"

MEMORY_AREA MiStaticMemoryAreas[MI_STATIC_MEMORY_AREAS];
ULONG MiStaticMemoryAreaCount;

/* FUNCTIONS *****************************************************************/

/**
 * @name MmIterateFirstNode
 *
 * @param Node
 *        Head node of the MEMORY_AREA tree.
 *
 * @return The leftmost MEMORY_AREA node (ie. the one with lowest
 *         address)
 */

static PMEMORY_AREA MmIterateFirstNode(PMEMORY_AREA Node)
{
   while (Node->LeftChild != NULL)
      Node = Node->LeftChild;

   return Node;
}

/**
 * @name MmIterateNextNode
 *
 * @param Node
 *        Current node in the tree.
 *
 * @return Next node in the tree (sorted by address).
 */

static PMEMORY_AREA MmIterateNextNode(PMEMORY_AREA Node)
{
   if (Node->RightChild != NULL)
   {
      Node = Node->RightChild;
      while (Node->LeftChild != NULL)
         Node = Node->LeftChild;
   }
   else
   {
      PMEMORY_AREA TempNode = NULL;

      do
      {
         /* Check if we're at the end of tree. */
         if (Node->Parent == NULL)
            return NULL;

         TempNode = Node;
         Node = Node->Parent;
      }
      while (TempNode == Node->RightChild);
   }
   return Node;
}

/**
 * @name MmIterateLastNode
 *
 * @param Node
 *        Head node of the MEMORY_AREA tree.
 *
 * @return The rightmost MEMORY_AREA node (ie. the one with highest
 *         address)
 */

static PMEMORY_AREA MmIterateLastNode(PMEMORY_AREA Node)
{
   while (Node->RightChild != NULL)
      Node = Node->RightChild;

   return Node;
}

/**
 * @name MmIteratePreviousNode
 *
 * @param Node
 *        Current node in the tree.
 *
 * @return Previous node in the tree (sorted by address).
 */

static PMEMORY_AREA MmIteratePrevNode(PMEMORY_AREA Node)
{
   if (Node->LeftChild != NULL)
   {
      Node = Node->LeftChild;
      while (Node->RightChild != NULL)
         Node = Node->RightChild;
   }
   else
   {
      PMEMORY_AREA TempNode = NULL;

      do
      {
         /* Check if we're at the end of tree. */
         if (Node->Parent == NULL)
            return NULL;

         TempNode = Node;
         Node = Node->Parent;
      }
      while (TempNode == Node->LeftChild);
   }
   return Node;
}

PMEMORY_AREA NTAPI
MmLocateMemoryAreaByAddress(
   PMMSUPPORT AddressSpace,
   PVOID Address)
{
   PMEMORY_AREA Node = (PMEMORY_AREA)AddressSpace->WorkingSetExpansionLinks.Flink;

   DPRINT("MmLocateMemoryAreaByAddress(AddressSpace %p, Address %p)\n",
           AddressSpace, Address);

   while (Node != NULL)
   {
      if (Address < Node->StartingAddress)
         Node = Node->LeftChild;
      else if (Address >= Node->EndingAddress)
         Node = Node->RightChild;
      else
      {
         DPRINT("MmLocateMemoryAreaByAddress(%p): %p [%p - %p]\n",
                Address, Node, Node->StartingAddress, Node->EndingAddress);
         return Node;
      }
   }

   DPRINT("MmLocateMemoryAreaByAddress(%p): 0\n", Address);
   return NULL;
}

PMEMORY_AREA NTAPI
MmLocateMemoryAreaByRegion(
   PMMSUPPORT AddressSpace,
   PVOID Address,
   ULONG_PTR Length)
{
   PMEMORY_AREA Node;
   PVOID Extent = (PVOID)((ULONG_PTR)Address + Length);

   /* Special case for empty tree. */
   if (AddressSpace->WorkingSetExpansionLinks.Flink == NULL)
      return NULL;

   /* Traverse the tree from left to right. */
   for (Node = MmIterateFirstNode((PMEMORY_AREA)AddressSpace->WorkingSetExpansionLinks.Flink);
        Node != NULL;
        Node = MmIterateNextNode(Node))
   {
      if (Node->StartingAddress >= Address &&
          Node->StartingAddress < Extent)
      {
         DPRINT("MmLocateMemoryAreaByRegion(%p - %p): %p - %p\n",
                Address, (ULONG_PTR)Address + Length, Node->StartingAddress,
                Node->EndingAddress);
         return Node;
      }
      if (Node->EndingAddress > Address &&
          Node->EndingAddress < Extent)
      {
         DPRINT("MmLocateMemoryAreaByRegion(%p - %p): %p - %p\n",
                Address, (ULONG_PTR)Address + Length, Node->StartingAddress,
                Node->EndingAddress);
         return Node;
      }
      if (Node->StartingAddress <= Address &&
          Node->EndingAddress >= Extent)
      {
         DPRINT("MmLocateMemoryAreaByRegion(%p - %p): %p - %p\n",
                Address, (ULONG_PTR)Address + Length, Node->StartingAddress,
                Node->EndingAddress);
         return Node;
      }
      if (Node->StartingAddress >= Extent)
      {
         DPRINT("Finished MmLocateMemoryAreaByRegion() = NULL\n");
         return NULL;
      }
   }

   return NULL;
}

/**
 * @name MmCompressHelper
 *
 * This is helper of MmRebalanceTree. Performs a compression transformation
 * count times, starting at root.
 */

static VOID
MmCompressHelper(
   PMMSUPPORT AddressSpace,
   ULONG Count)
{
   PMEMORY_AREA Root = NULL;
   PMEMORY_AREA Red = (PMEMORY_AREA)AddressSpace->WorkingSetExpansionLinks.Flink;
   PMEMORY_AREA Black = Red->LeftChild;

   while (Count--)
   {
      if (Root)
         Root->LeftChild = Black;
      else
         AddressSpace->WorkingSetExpansionLinks.Flink = (PVOID)Black;
      Black->Parent = Root;
      Red->LeftChild = Black->RightChild;
      if (Black->RightChild)
         Black->RightChild->Parent = Red;
      Black->RightChild = Red;
      Red->Parent = Black;
      Root = Black;

      if (Count)
      {
         Red = Root->LeftChild;
         Black = Red->LeftChild;
      }
   }
}

/**
 * @name MmRebalanceTree
 *
 * Rebalance a memory area tree using the Tree->Vine->Balanced Tree
 * method described in libavl documentation in chapter 4.12.
 * (http://www.stanford.edu/~blp/avl/libavl.html/)
 */

static VOID
MmRebalanceTree(
   PMMSUPPORT AddressSpace)
{
   PMEMORY_AREA PreviousNode;
   PMEMORY_AREA CurrentNode;
   PMEMORY_AREA TempNode;
   ULONG NodeCount = 0;
   ULONG Vine;   /* Number of nodes in main vine. */
   ULONG Leaves; /* Nodes in incomplete bottom level, if any. */
   INT Height;   /* Height of produced balanced tree. */

   /* Transform the tree into Vine. */

   PreviousNode = NULL;
   CurrentNode = (PMEMORY_AREA)AddressSpace->WorkingSetExpansionLinks.Flink;
   while (CurrentNode != NULL)
   {
      if (CurrentNode->RightChild == NULL)
      {
         PreviousNode = CurrentNode;
         CurrentNode = CurrentNode->LeftChild;
         NodeCount++;
      }
      else
      {
         TempNode = CurrentNode->RightChild;

         CurrentNode->RightChild = TempNode->LeftChild;
         if (TempNode->LeftChild)
            TempNode->LeftChild->Parent = CurrentNode;

         TempNode->LeftChild = CurrentNode;
         CurrentNode->Parent = TempNode;

         CurrentNode = TempNode;

         if (PreviousNode != NULL)
            PreviousNode->LeftChild = TempNode;
         else
            AddressSpace->WorkingSetExpansionLinks.Flink = (PVOID)TempNode;
         TempNode->Parent = PreviousNode;
      }
   }

   /* Transform Vine back into a balanced tree. */

   Leaves = NodeCount + 1;
   for (;;)
   {
      ULONG Next = Leaves & (Leaves - 1);
      if (Next == 0)
         break;
      Leaves = Next;
   }
   Leaves = NodeCount + 1 - Leaves;

   MmCompressHelper(AddressSpace, Leaves);

   Vine = NodeCount - Leaves;
   Height = 1 + (Leaves > 0);
   while (Vine > 1)
   {
      MmCompressHelper(AddressSpace, Vine / 2);
      Vine /= 2;
      Height++;
   }
}

VOID
NTAPI
MiInsertVad(IN PMMVAD Vad,
IN PEPROCESS Process);

ULONG
NTAPI
MiMakeProtectionMask(
    IN ULONG Protect
);

static VOID
MmInsertMemoryArea(
   PMMSUPPORT AddressSpace,
   PMEMORY_AREA marea)
{
   PMEMORY_AREA Node;
   PMEMORY_AREA PreviousNode;
   ULONG Depth = 0;
   PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);

   /* Build a lame VAD if this is a user-space allocation */
   if ((marea->EndingAddress < MmSystemRangeStart) && (marea->Type != MEMORY_AREA_OWNED_BY_ARM3))
   {
       PMMVAD Vad;

       ASSERT(marea->Type == MEMORY_AREA_SECTION_VIEW || marea->Type == MEMORY_AREA_CACHE);
       Vad = ExAllocatePoolWithTag(NonPagedPool, sizeof(MMVAD), TAG_MVAD);
       ASSERT(Vad);
       RtlZeroMemory(Vad, sizeof(MMVAD));
       Vad->StartingVpn = PAGE_ROUND_DOWN(marea->StartingAddress) >> PAGE_SHIFT;
       /*
        * For some strange reason, it is perfectly valid to create a MAREA from 0x1000 to... 0x1000.
        * In a normal OS/Memory Manager, this would be retarded, but ReactOS allows this (how it works
        * I don't even want to know).
        */
        if (marea->EndingAddress != marea->StartingAddress)
        {
            Vad->EndingVpn = PAGE_ROUND_DOWN((ULONG_PTR)marea->EndingAddress - 1) >> PAGE_SHIFT;
        }
        else
        {
            Vad->EndingVpn = Vad->StartingVpn;
        }
       Vad->u.VadFlags.Spare = 1;
       Vad->u.VadFlags.PrivateMemory = 1;
       Vad->u.VadFlags.Protection = MiMakeProtectionMask(marea->Protect);

       /* Insert the VAD */
       MiInsertVad(Vad, Process);
       marea->Vad = Vad;
   }
   else
   {
       marea->Vad = NULL;
   }

   if (AddressSpace->WorkingSetExpansionLinks.Flink == NULL)
   {
      AddressSpace->WorkingSetExpansionLinks.Flink = (PVOID)marea;
      marea->LeftChild = marea->RightChild = marea->Parent = NULL;
      return;
   }

   Node = (PMEMORY_AREA)AddressSpace->WorkingSetExpansionLinks.Flink;
   do
   {
      DPRINT("marea->EndingAddress: %p Node->StartingAddress: %p\n",
             marea->EndingAddress, Node->StartingAddress);
      DPRINT("marea->StartingAddress: %p Node->EndingAddress: %p\n",
             marea->StartingAddress, Node->EndingAddress);
      ASSERT(marea->EndingAddress <= Node->StartingAddress ||
             marea->StartingAddress >= Node->EndingAddress);
      ASSERT(marea->StartingAddress != Node->StartingAddress);

      PreviousNode = Node;

      if (marea->StartingAddress < Node->StartingAddress)
         Node = Node->LeftChild;
      else
         Node = Node->RightChild;

      if (Node)
      {
         Depth++;
         if (Depth == 22)
         {
            MmRebalanceTree(AddressSpace);
            PreviousNode = Node->Parent;
         }
      }
   }
   while (Node != NULL);

   marea->LeftChild = marea->RightChild = NULL;
   marea->Parent = PreviousNode;
   if (marea->StartingAddress < PreviousNode->StartingAddress)
      PreviousNode->LeftChild = marea;
   else
      PreviousNode->RightChild = marea;
}

static PVOID
MmFindGapBottomUp(
   PMMSUPPORT AddressSpace,
   ULONG_PTR Length,
   ULONG_PTR Granularity)
{
    ULONG_PTR LowestAddress, HighestAddress, Candidate;
    PMEMORY_AREA Root, Node;

    /* Get the margins of the address space */
    if (MmGetAddressSpaceOwner(AddressSpace) != NULL)
    {
        LowestAddress = (ULONG_PTR)MM_LOWEST_USER_ADDRESS;
        HighestAddress = (ULONG_PTR)MmHighestUserAddress;
    }
    else
    {
        LowestAddress = (ULONG_PTR)MmSystemRangeStart;
        HighestAddress = MAXULONG_PTR;
    }

    /* Start with the lowest address */
    Candidate = LowestAddress;

    /* Check for overflow */
    if ((Candidate + Length) < Candidate) return NULL;

    /* Get the root of the address space tree */
    Root = (PMEMORY_AREA)AddressSpace->WorkingSetExpansionLinks.Flink;

    /* Go to the node with lowest address in the tree. */
    Node = Root ? MmIterateFirstNode(Root) : NULL;
    while (Node && ((ULONG_PTR)Node->EndingAddress < LowestAddress))
    {
        Node = MmIterateNextNode(Node);
    }

    /* Traverse the tree from low to high addresses */
    while (Node && ((ULONG_PTR)Node->EndingAddress < HighestAddress))
    {
        /* Check if the memory area fits before the current node */
        if ((ULONG_PTR)Node->StartingAddress >= (Candidate + Length))
        {
            DPRINT("MmFindGapBottomUp: %p\n", Candidate);
            ASSERT(Candidate >= LowestAddress);
            return (PVOID)Candidate;
        }

        /* Calculate next possible adress above this node */
        Candidate = ALIGN_UP_BY((ULONG_PTR)Node->EndingAddress, Granularity);

        /* Check for overflow */
        if ((Candidate + Length) < (ULONG_PTR)Node->EndingAddress) return NULL;

        /* Go to the next higher node */
        Node = MmIterateNextNode(Node);
    }

    /* Check if there is enough space after the last memory area. */
    if ((Candidate + Length) <= HighestAddress)
    {
        DPRINT("MmFindGapBottomUp: %p\n", Candidate);
        ASSERT(Candidate >= LowestAddress);
        return (PVOID)Candidate;
    }

    DPRINT("MmFindGapBottomUp: 0\n");
    return NULL;
}


static PVOID
MmFindGapTopDown(
   PMMSUPPORT AddressSpace,
   ULONG_PTR Length,
   ULONG_PTR Granularity)
{
    ULONG_PTR LowestAddress, HighestAddress, Candidate;
    PMEMORY_AREA Root, Node;

    /* Get the margins of the address space */
    if (MmGetAddressSpaceOwner(AddressSpace) != NULL)
    {
        LowestAddress = (ULONG_PTR)MM_LOWEST_USER_ADDRESS;
        HighestAddress = (ULONG_PTR)MmHighestUserAddress;
    }
    else
    {
        LowestAddress = (ULONG_PTR)MmSystemRangeStart;
        HighestAddress = MAXULONG_PTR;
    }

    /* Calculate the highest candidate */
    Candidate = ALIGN_DOWN_BY(HighestAddress + 1 - Length, Granularity);

    /* Check for overflow. */
    if (Candidate > HighestAddress) return NULL;

    /* Get the root of the address space tree */
    Root = (PMEMORY_AREA)AddressSpace->WorkingSetExpansionLinks.Flink;

    /* Go to the node with highest address in the tree. */
    Node = Root ? MmIterateLastNode(Root) : NULL;
    while (Node && ((ULONG_PTR)Node->StartingAddress > HighestAddress))
    {
        Node = MmIteratePrevNode(Node);
    }

    /* Traverse the tree from high to low addresses */
    while (Node && ((ULONG_PTR)Node->StartingAddress > LowestAddress))
    {
        /* Check if the memory area fits after the current node */
        if ((ULONG_PTR)Node->EndingAddress <= Candidate)
        {
            DPRINT("MmFindGapTopDown: %p\n", Candidate);
            return (PVOID)Candidate;
        }

        /* Calculate next possible adress below this node */
        Candidate = ALIGN_DOWN_BY((ULONG_PTR)Node->StartingAddress - Length,
                                  Granularity);

        /* Check for overflow. */
        if (Candidate > (ULONG_PTR)Node->StartingAddress)
            return NULL;

        /* Go to the next lower node */
        Node = MmIteratePrevNode(Node);
    }

    /* Check if the last candidate is inside the given range */
    if (Candidate >= LowestAddress)
    {
        DPRINT("MmFindGapTopDown: %p\n", Candidate);
        return (PVOID)Candidate;
    }

    DPRINT("MmFindGapTopDown: 0\n");
    return NULL;
}


PVOID NTAPI
MmFindGap(
   PMMSUPPORT AddressSpace,
   ULONG_PTR Length,
   ULONG_PTR Granularity,
   BOOLEAN TopDown)
{
   if (TopDown)
      return MmFindGapTopDown(AddressSpace, Length, Granularity);

   return MmFindGapBottomUp(AddressSpace, Length, Granularity);
}

ULONG_PTR NTAPI
MmFindGapAtAddress(
   PMMSUPPORT AddressSpace,
   PVOID Address)
{
   PMEMORY_AREA Node = (PMEMORY_AREA)AddressSpace->WorkingSetExpansionLinks.Flink;
   PMEMORY_AREA RightNeighbour = NULL;
   PVOID LowestAddress  = MmGetAddressSpaceOwner(AddressSpace) ? MM_LOWEST_USER_ADDRESS : MmSystemRangeStart;
   PVOID HighestAddress = MmGetAddressSpaceOwner(AddressSpace) ?
                          (PVOID)((ULONG_PTR)MmSystemRangeStart - 1) : (PVOID)MAXULONG_PTR;

   Address = MM_ROUND_DOWN(Address, PAGE_SIZE);

   if (LowestAddress < MmSystemRangeStart)
   {
      if (Address >= MmSystemRangeStart)
      {
         return 0;
      }
   }
   else
   {
      if (Address < LowestAddress)
      {
         return 0;
      }
   }

   while (Node != NULL)
   {
      if (Address < Node->StartingAddress)
      {
         RightNeighbour = Node;
         Node = Node->LeftChild;
      }
      else if (Address >= Node->EndingAddress)
      {
         Node = Node->RightChild;
      }
      else
      {
         DPRINT("MmFindGapAtAddress: 0\n");
         return 0;
      }
   }

   if (RightNeighbour)
   {
      DPRINT("MmFindGapAtAddress: %p [%p]\n", Address,
             (ULONG_PTR)RightNeighbour->StartingAddress - (ULONG_PTR)Address);
      return (ULONG_PTR)RightNeighbour->StartingAddress - (ULONG_PTR)Address;
   }
   else
   {
      DPRINT("MmFindGapAtAddress: %p [%p]\n", Address,
             (ULONG_PTR)HighestAddress - (ULONG_PTR)Address);
      return (ULONG_PTR)HighestAddress - (ULONG_PTR)Address;
   }
}

VOID
NTAPI
MiRemoveNode(IN PMMADDRESS_NODE Node,
IN PMM_AVL_TABLE Table);

#if DBG

static
VOID
MiRosCheckMemoryAreasRecursive(
   PMEMORY_AREA Node)
{
    /* Check if the allocation is ok */
    ExpCheckPoolAllocation(Node, NonPagedPool, 'ERAM');

    /* Check some fields */
    ASSERT(Node->Magic == 'erAM');
    ASSERT(PAGE_ALIGN(Node->StartingAddress) == Node->StartingAddress);
    ASSERT(Node->EndingAddress != NULL);
    ASSERT(PAGE_ALIGN(Node->EndingAddress) == Node->EndingAddress);
    ASSERT((ULONG_PTR)Node->StartingAddress < (ULONG_PTR)Node->EndingAddress);
    ASSERT((Node->Type == 0) ||
           (Node->Type == MEMORY_AREA_CACHE) ||
          // (Node->Type == MEMORY_AREA_CACHE_SEGMENT) ||
           (Node->Type == MEMORY_AREA_SECTION_VIEW) ||
           (Node->Type == MEMORY_AREA_OWNED_BY_ARM3) ||
           (Node->Type == (MEMORY_AREA_OWNED_BY_ARM3 | MEMORY_AREA_STATIC)));

    /* Recursively check children */
    if (Node->LeftChild != NULL)
        MiRosCheckMemoryAreasRecursive(Node->LeftChild);
    if (Node->RightChild != NULL)
        MiRosCheckMemoryAreasRecursive(Node->RightChild);
}

VOID
NTAPI
MiRosCheckMemoryAreas(
   PMMSUPPORT AddressSpace)
{
    PMEMORY_AREA RootNode;
    PEPROCESS AddressSpaceOwner;
    BOOLEAN NeedReleaseLock;

    NeedReleaseLock = FALSE;

    /* Get the address space owner */
    AddressSpaceOwner = CONTAINING_RECORD(AddressSpace, EPROCESS, Vm);

    /* Check if we already own the address space lock */
    if (AddressSpaceOwner->AddressCreationLock.Owner != KeGetCurrentThread())
    {
        /* We must own it! */
        MmLockAddressSpace(AddressSpace);
        NeedReleaseLock = TRUE;
    }

    /* Check all memory areas */
    RootNode = (PMEMORY_AREA)AddressSpace->WorkingSetExpansionLinks.Flink;
    MiRosCheckMemoryAreasRecursive(RootNode);

    /* Release the lock, if we acquired it */
    if (NeedReleaseLock)
    {
        MmUnlockAddressSpace(AddressSpace);
    }
}

extern KGUARDED_MUTEX PspActiveProcessMutex;

VOID
NTAPI
MiCheckAllProcessMemoryAreas(VOID)
{
    PEPROCESS Process;
    PLIST_ENTRY Entry;

    /* Acquire the Active Process Lock */
    KeAcquireGuardedMutex(&PspActiveProcessMutex);

    /* Loop the process list */
    Entry = PsActiveProcessHead.Flink;
    while (Entry != &PsActiveProcessHead)
    {
        /* Get the process */
        Process = CONTAINING_RECORD(Entry, EPROCESS, ActiveProcessLinks);

        /* Check memory areas */
        MiRosCheckMemoryAreas(&Process->Vm);

        Entry = Entry->Flink;
    }

    /* Release the lock */
    KeReleaseGuardedMutex(&PspActiveProcessMutex);
}

#endif

/**
 * @name MmFreeMemoryArea
 *
 * Free an existing memory area.
 *
 * @param AddressSpace
 *        Address space to free the area from.
 * @param MemoryArea
 *        Memory area we're about to free.
 * @param FreePage
 *        Callback function for each freed page.
 * @param FreePageContext
 *        Context passed to the callback function.
 *
 * @return Status
 *
 * @remarks Lock the address space before calling this function.
 */
VOID
NTAPI
MiDeletePte(IN PMMPTE PointerPte,
            IN PVOID VirtualAddress,
            IN PEPROCESS CurrentProcess,
            IN PMMPTE PrototypePte);

NTSTATUS NTAPI
MmFreeMemoryArea(
   PMMSUPPORT AddressSpace,
   PMEMORY_AREA MemoryArea,
   PMM_FREE_PAGE_FUNC FreePage,
   PVOID FreePageContext)
{
   PMEMORY_AREA *ParentReplace;
   ULONG_PTR Address;
   PVOID EndAddress;

   /* Make sure we own the address space lock! */
   ASSERT(CONTAINING_RECORD(AddressSpace, EPROCESS, Vm)->AddressCreationLock.Owner == KeGetCurrentThread());

    /* Check magic */
    ASSERT(MemoryArea->Magic == 'erAM');

   if (MemoryArea->Type != MEMORY_AREA_OWNED_BY_ARM3)
   {
       PEPROCESS CurrentProcess = PsGetCurrentProcess();
       PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);

       if (Process != NULL &&
           Process != CurrentProcess)
       {
          KeAttachProcess(&Process->Pcb);
       }

       EndAddress = MM_ROUND_UP(MemoryArea->EndingAddress, PAGE_SIZE);
       for (Address = (ULONG_PTR)MemoryArea->StartingAddress;
            Address < (ULONG_PTR)EndAddress;
            Address += PAGE_SIZE)
       {
            BOOLEAN Dirty = FALSE;
            SWAPENTRY SwapEntry = 0;
            PFN_NUMBER Page = 0;

             if (MmIsPageSwapEntry(Process, (PVOID)Address))
             {
                MmDeletePageFileMapping(Process, (PVOID)Address, &SwapEntry);
             }
             else
             {
                MmDeleteVirtualMapping(Process, (PVOID)Address, FALSE, &Dirty, &Page);
             }
             if (FreePage != NULL)
             {
                FreePage(FreePageContext, MemoryArea, (PVOID)Address,
                         Page, SwapEntry, (BOOLEAN)Dirty);
             }
#if (_MI_PAGING_LEVELS == 2)
            /* Remove page table reference */
            ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
            if((SwapEntry || Page) && ((PVOID)Address < MmSystemRangeStart))
            {
                ASSERT(AddressSpace != MmGetKernelAddressSpace());
                if(MmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)] == 0)
                {
                    /* No PTE relies on this PDE. Release it */
                    KIRQL OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
                    PMMPDE PointerPde = MiAddressToPde(Address);
                    ASSERT(PointerPde->u.Hard.Valid == 1);
                    MiDeletePte(PointerPde, MiPdeToPte(PointerPde), Process, NULL);
                    ASSERT(PointerPde->u.Hard.Valid == 0);
                    KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
                }
            }
#endif
       }

       if (Process != NULL &&
           Process != CurrentProcess)
       {
          KeDetachProcess();
       }

       if (MemoryArea->Vad)
       {
           ASSERT(MemoryArea->EndingAddress < MmSystemRangeStart);
           ASSERT(MemoryArea->Type == MEMORY_AREA_SECTION_VIEW || MemoryArea->Type == MEMORY_AREA_CACHE);

           /* MmCleanProcessAddressSpace might have removed it (and this would be MmDeleteProcessAdressSpace) */
           ASSERT(((PMMVAD)MemoryArea->Vad)->u.VadFlags.Spare != 0);
           if (((PMMVAD)MemoryArea->Vad)->u.VadFlags.Spare == 1)
           {
               MiRemoveNode(MemoryArea->Vad, &Process->VadRoot);
           }

           ExFreePoolWithTag(MemoryArea->Vad, TAG_MVAD);
           MemoryArea->Vad = NULL;
       }
    }

   /* Remove the tree item. */
   {
      if (MemoryArea->Parent != NULL)
      {
         if (MemoryArea->Parent->LeftChild == MemoryArea)
            ParentReplace = &MemoryArea->Parent->LeftChild;
         else
            ParentReplace = &MemoryArea->Parent->RightChild;
      }
      else
         ParentReplace = (PMEMORY_AREA*)&AddressSpace->WorkingSetExpansionLinks.Flink;

      if (MemoryArea->RightChild == NULL)
      {
         *ParentReplace = MemoryArea->LeftChild;
         if (MemoryArea->LeftChild)
            MemoryArea->LeftChild->Parent = MemoryArea->Parent;
      }
      else
      {
         if (MemoryArea->RightChild->LeftChild == NULL)
         {
            MemoryArea->RightChild->LeftChild = MemoryArea->LeftChild;
            if (MemoryArea->LeftChild)
               MemoryArea->LeftChild->Parent = MemoryArea->RightChild;

            *ParentReplace = MemoryArea->RightChild;
            MemoryArea->RightChild->Parent = MemoryArea->Parent;
         }
         else
         {
            PMEMORY_AREA LowestNode;

            LowestNode = MemoryArea->RightChild->LeftChild;
            while (LowestNode->LeftChild != NULL)
               LowestNode = LowestNode->LeftChild;

            LowestNode->Parent->LeftChild = LowestNode->RightChild;
            if (LowestNode->RightChild)
               LowestNode->RightChild->Parent = LowestNode->Parent;

            LowestNode->LeftChild = MemoryArea->LeftChild;
            if (MemoryArea->LeftChild)
               MemoryArea->LeftChild->Parent = LowestNode;

            LowestNode->RightChild = MemoryArea->RightChild;
            MemoryArea->RightChild->Parent = LowestNode;

            *ParentReplace = LowestNode;
            LowestNode->Parent = MemoryArea->Parent;
         }
      }
   }

   ExFreePoolWithTag(MemoryArea, TAG_MAREA);

   DPRINT("MmFreeMemoryAreaByNode() succeeded\n");

   return STATUS_SUCCESS;
}

/**
 * @name MmCreateMemoryArea
 *
 * Create a memory area.
 *
 * @param AddressSpace
 *        Address space to create the area in.
 * @param Type
 *        Type of the memory area.
 * @param BaseAddress
 *        Base address for the memory area we're about the create. On
 *        input it contains either 0 (auto-assign address) or preferred
 *        address. On output it contains the starting address of the
 *        newly created area.
 * @param Length
 *        Length of the area to allocate.
 * @param Attributes
 *        Protection attributes for the memory area.
 * @param Result
 *        Receives a pointer to the memory area on successful exit.
 *
 * @return Status
 *
 * @remarks Lock the address space before calling this function.
 */

NTSTATUS NTAPI
MmCreateMemoryArea(PMMSUPPORT AddressSpace,
                   ULONG Type,
                   PVOID *BaseAddress,
                   ULONG_PTR Length,
                   ULONG Protect,
                   PMEMORY_AREA *Result,
                   BOOLEAN FixedAddress,
                   ULONG AllocationFlags,
                   PHYSICAL_ADDRESS BoundaryAddressMultiple)
{
   PVOID EndAddress;
   ULONG Granularity;
   ULONG_PTR tmpLength;
   PMEMORY_AREA MemoryArea;

   DPRINT("MmCreateMemoryArea(Type 0x%lx, BaseAddress %p, "
          "*BaseAddress %p, Length %p, AllocationFlags %x, "
          "FixedAddress %x, Result %p)\n",
          Type, BaseAddress, *BaseAddress, Length, AllocationFlags,
          FixedAddress, Result);

   Granularity = PAGE_SIZE;
   if ((*BaseAddress) == 0 && !FixedAddress)
   {
      tmpLength = (ULONG_PTR)MM_ROUND_UP(Length, Granularity);
      *BaseAddress = MmFindGap(AddressSpace,
                               tmpLength,
                               Granularity,
                               (AllocationFlags & MEM_TOP_DOWN) == MEM_TOP_DOWN);
      if ((*BaseAddress) == 0)
      {
         DPRINT("No suitable gap\n");
         return STATUS_NO_MEMORY;
      }
   }
   else
   {
      tmpLength = Length + ((ULONG_PTR) *BaseAddress
                         - (ULONG_PTR) MM_ROUND_DOWN(*BaseAddress, Granularity));
      tmpLength = (ULONG_PTR)MM_ROUND_UP(tmpLength, Granularity);
      *BaseAddress = MM_ROUND_DOWN(*BaseAddress, Granularity);

      if (!MmGetAddressSpaceOwner(AddressSpace) && *BaseAddress < MmSystemRangeStart)
      {
         return STATUS_ACCESS_VIOLATION;
      }

      if (MmGetAddressSpaceOwner(AddressSpace) &&
          (ULONG_PTR)(*BaseAddress) + tmpLength > (ULONG_PTR)MmSystemRangeStart)
      {
         DPRINT("Memory area for user mode address space exceeds MmSystemRangeStart\n");
         return STATUS_ACCESS_VIOLATION;
      }

      if (BoundaryAddressMultiple.QuadPart != 0)
      {
         EndAddress = ((char*)(*BaseAddress)) + tmpLength-1;
         ASSERT(((ULONG_PTR)*BaseAddress/BoundaryAddressMultiple.QuadPart) == ((DWORD_PTR)EndAddress/BoundaryAddressMultiple.QuadPart));
      }

      if (MmLocateMemoryAreaByRegion(AddressSpace,
                                     *BaseAddress,
                                     tmpLength) != NULL)
      {
         DPRINT("Memory area already occupied\n");
         return STATUS_CONFLICTING_ADDRESSES;
      }
   }

    //
    // Is this a static memory area?
    //
    if (Type & MEMORY_AREA_STATIC)
    {
        //
        // Use the static array instead of the pool
        //
        ASSERT(MiStaticMemoryAreaCount < MI_STATIC_MEMORY_AREAS);
        MemoryArea = &MiStaticMemoryAreas[MiStaticMemoryAreaCount++];
        Type &= ~MEMORY_AREA_STATIC;
    }
    else
    {
        //
        // Allocate the memory area from nonpaged pool
        //
        MemoryArea = ExAllocatePoolWithTag(NonPagedPool,
                                           sizeof(MEMORY_AREA),
                                           TAG_MAREA);
    }

    if (!MemoryArea) return STATUS_NO_MEMORY;

   RtlZeroMemory(MemoryArea, sizeof(MEMORY_AREA));
   MemoryArea->Type = Type;
   MemoryArea->StartingAddress = *BaseAddress;
   MemoryArea->EndingAddress = (PVOID)((ULONG_PTR)*BaseAddress + tmpLength);
   MemoryArea->Protect = Protect;
   MemoryArea->Flags = AllocationFlags;
   //MemoryArea->LockCount = 0;
   MemoryArea->Magic = 'erAM';
   MemoryArea->DeleteInProgress = FALSE;

   MmInsertMemoryArea(AddressSpace, MemoryArea);

   *Result = MemoryArea;

   DPRINT("MmCreateMemoryArea() succeeded (%p)\n", *BaseAddress);
   return STATUS_SUCCESS;
}

VOID NTAPI
MmMapMemoryArea(PVOID BaseAddress,
                SIZE_T Length,
                ULONG Consumer,
                ULONG Protection)
{
   ULONG i;
   NTSTATUS Status;

   ASSERT(((ULONG_PTR)BaseAddress % PAGE_SIZE) == 0);

   for (i = 0; i < PAGE_ROUND_UP(Length) / PAGE_SIZE; i++)
   {
      PFN_NUMBER Page;

      Status = MmRequestPageMemoryConsumer(Consumer, TRUE, &Page);
      if (!NT_SUCCESS(Status))
      {
         DPRINT1("Unable to allocate page\n");
         KeBugCheck(MEMORY_MANAGEMENT);
      }
      Status = MmCreateVirtualMapping (NULL,
                                       (PVOID)((ULONG_PTR)BaseAddress + (i * PAGE_SIZE)),
                                       Protection,
                                       &Page,
                                       1);
      if (!NT_SUCCESS(Status))
      {
         DPRINT1("Unable to create virtual mapping\n");
         KeBugCheck(MEMORY_MANAGEMENT);
      }
   }
}

VOID
NTAPI
MmDeleteProcessAddressSpace2(IN PEPROCESS Process);

NTSTATUS
NTAPI
MmDeleteProcessAddressSpace(PEPROCESS Process)
{
   PVOID Address;
   PMEMORY_AREA MemoryArea;

   DPRINT("MmDeleteProcessAddressSpace(Process %p (%s))\n", Process,
          Process->ImageFileName);

#ifndef _M_AMD64
   RemoveEntryList(&Process->MmProcessLinks);
#endif
   MmLockAddressSpace(&Process->Vm);

   while ((MemoryArea = (PMEMORY_AREA)Process->Vm.WorkingSetExpansionLinks.Flink) != NULL)
   {
      switch (MemoryArea->Type)
      {
         case MEMORY_AREA_SECTION_VIEW:
             Address = (PVOID)MemoryArea->StartingAddress;
             MmUnlockAddressSpace(&Process->Vm);
             MmUnmapViewOfSection(Process, Address);
             MmLockAddressSpace(&Process->Vm);
             break;

         case MEMORY_AREA_CACHE:
             Address = (PVOID)MemoryArea->StartingAddress;
             MmUnlockAddressSpace(&Process->Vm);
             MmUnmapViewOfCacheSegment(&Process->Vm, Address);
             MmLockAddressSpace(&Process->Vm);
             break;

         case MEMORY_AREA_OWNED_BY_ARM3:
             MmFreeMemoryArea(&Process->Vm,
                              MemoryArea,
                              NULL,
                              NULL);
             break;

         default:
            KeBugCheck(MEMORY_MANAGEMENT);
      }
   }

#if (_MI_PAGING_LEVELS == 2)
    {
        KIRQL OldIrql;
        PMMPDE pointerPde;
        /* Attach to Process */
        KeAttachProcess(&Process->Pcb);

        /* Acquire PFN lock */
        OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);

        for(Address = MI_LOWEST_VAD_ADDRESS;
            Address < MM_HIGHEST_VAD_ADDRESS;
            Address =(PVOID)((ULONG_PTR)Address + (PAGE_SIZE * PTE_COUNT)))
        {
            /* At this point all references should be dead */
            ASSERT(MmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)] == 0);
            pointerPde = MiAddressToPde(Address);
            /* Unlike in ARM3, we don't necesarrily free the PDE page as soon as reference reaches 0,
             * so we must clean up a bit when process closes */
            if(pointerPde->u.Hard.Valid)
                MiDeletePte(pointerPde, MiPdeToPte(pointerPde), Process, NULL);
            ASSERT(pointerPde->u.Hard.Valid == 0);
        }
        /* Release lock */
        KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);

        /* Detach */
        KeDetachProcess();
    }
#endif

   MmUnlockAddressSpace(&Process->Vm);

   DPRINT("Finished MmReleaseMmInfo()\n");
   MmDeleteProcessAddressSpace2(Process);
   return(STATUS_SUCCESS);
}

/* EOF */
