/*
 * 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/section/fault.c
 * PURPOSE:         Consolidate fault handlers for sections
 *
 * PROGRAMMERS:     Arty
 *                  Rex Jolliff
 *                  David Welch
 *                  Eric Kohl
 *                  Emanuele Aliberti
 *                  Eugene Ingerman
 *                  Casper Hornstrup
 *                  KJK::Hyperion
 *                  Guido de Jong
 *                  Ge van Geldorp
 *                  Royce Mitchell III
 *                  Filip Navara
 *                  Aleksey Bragin
 *                  Jason Filby
 *                  Thomas Weidenmueller
 *                  Gunnar Andre' Dalsnes
 *                  Mike Nordell
 *                  Alex Ionescu
 *                  Gregor Anich
 *                  Steven Edwards
 *                  Herve Poussineau
 */

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

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

#define DPRINTC DPRINT

extern KEVENT MmWaitPageEvent;

NTSTATUS
NTAPI
MmNotPresentFaultCachePage
(PMMSUPPORT AddressSpace,
 MEMORY_AREA* MemoryArea,
 PVOID Address,
 BOOLEAN Locked,
 PMM_REQUIRED_RESOURCES Required)
{
	NTSTATUS Status;
	PVOID PAddress;
	ULONG Consumer;
	PMM_CACHE_SECTION_SEGMENT Segment;
	LARGE_INTEGER FileOffset, TotalOffset;
	ULONG Entry;
	ULONG Attributes;
	PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);

	DPRINT("Not Present: %p %p (%p-%p)\n", AddressSpace, Address, MemoryArea->StartingAddress, MemoryArea->EndingAddress);
    
	/*
	 * There is a window between taking the page fault and locking the
	 * address space when another thread could load the page so we check
	 * that.
	 */
	if (MmIsPagePresent(Process, Address))
	{
		DPRINT("Done\n");
		return(STATUS_SUCCESS);
	}

	PAddress = MM_ROUND_DOWN(Address, PAGE_SIZE);
	TotalOffset.QuadPart = (ULONG_PTR)PAddress - (ULONG_PTR)MemoryArea->StartingAddress;

	Segment = MemoryArea->Data.CacheData.Segment;

	TotalOffset.QuadPart += MemoryArea->Data.CacheData.ViewOffset.QuadPart;
	FileOffset = TotalOffset;

	//Consumer = (Segment->Flags & MM_DATAFILE_SEGMENT) ? MC_CACHE : MC_USER;
	Consumer = MC_CACHE;

	if (Segment->FileObject)
	{
		DPRINT("FileName %wZ\n", &Segment->FileObject->FileName);
	}

	DPRINT("Total Offset %08x%08x\n", TotalOffset.HighPart, TotalOffset.LowPart);

	/*
	 * Lock the segment
	 */
	MmLockCacheSectionSegment(Segment);

	/*
	 * Get the entry corresponding to the offset within the section
	 */
	Entry = MiGetPageEntryCacheSectionSegment(Segment, &TotalOffset);

	Attributes = PAGE_READONLY;

	if (Required->State && Required->Page[0])
	{
		DPRINT("Have file and page, set page %x in section @ %x #\n", Required->Page[0], TotalOffset.LowPart);

		if (Required->SwapEntry)
			MmSetSavedSwapEntryPage(Required->Page[0], Required->SwapEntry);

		if (Required->State & 2)
		{
			DPRINT("Set in section @ %x\n", TotalOffset.LowPart);
			Status = MiSetPageEntryCacheSectionSegment
				(Segment, &TotalOffset, Entry = MAKE_PFN_SSE(Required->Page[0]));
			if (!NT_SUCCESS(Status))
			{
				MmReleasePageMemoryConsumer(MC_CACHE, Required->Page[0]);
			}
			MmUnlockCacheSectionSegment(Segment);
			MiSetPageEvent(Process, Address);
			DPRINT("Status %x\n", Status);
			return STATUS_MM_RESTART_OPERATION;
		}
		else
		{
			DPRINT("Set %x in address space @ %x\n", Required->Page[0], Address);
			Status = MmCreateVirtualMapping(Process, Address, Attributes, Required->Page, 1);
			if (NT_SUCCESS(Status))
			{
				MmInsertRmap(Required->Page[0], Process, Address);
			}
			else
			{
				// Drop the reference for our address space ...
				MmReleasePageMemoryConsumer(MC_CACHE, Required->Page[0]);
			}
			MmUnlockCacheSectionSegment(Segment);
			DPRINTC("XXX Set Event %x\n", Status);
			MiSetPageEvent(Process, Address);
			DPRINT("Status %x\n", Status);
			return Status;
		}
	}
	else if (Entry)
	{
		PFN_NUMBER Page = PFN_FROM_SSE(Entry);
		DPRINT("Take reference to page %x #\n", Page);

		MmReferencePage(Page);

		Status = MmCreateVirtualMapping(Process, Address, Attributes, &Page, 1);
		if (NT_SUCCESS(Status))
		{
			MmInsertRmap(Page, Process, Address);
		}
		DPRINT("XXX Set Event %x\n", Status);
		MiSetPageEvent(Process, Address);
		MmUnlockCacheSectionSegment(Segment);
		DPRINT("Status %x\n", Status);
		return Status;
	}
	else
	{
		DPRINT("Get page into section\n");
		/*
		 * If the entry is zero (and it can't change because we have
		 * locked the segment) then we need to load the page.
		 */
		//DPRINT1("Read from file %08x %wZ\n", FileOffset.LowPart, &Section->FileObject->FileName);
		Required->State = 2;
		Required->Context = Segment->FileObject;
		Required->Consumer = Consumer;
		Required->FileOffset = FileOffset;
		Required->Amount = PAGE_SIZE;
		Required->DoAcquisition = MiReadFilePage;
		MiSetPageEntryCacheSectionSegment(Segment, &TotalOffset, MAKE_SWAP_SSE(MM_WAIT_ENTRY));
		MmUnlockCacheSectionSegment(Segment);
		return STATUS_MORE_PROCESSING_REQUIRED;
	}
	ASSERT(FALSE);
	return STATUS_ACCESS_VIOLATION;
}

NTSTATUS
NTAPI
MiCopyPageToPage(PFN_NUMBER DestPage, PFN_NUMBER SrcPage)
{
    PEPROCESS Process;
    KIRQL Irql, Irql2;
    PVOID TempAddress, TempSource;
    
    Process = PsGetCurrentProcess();
    TempAddress = MiMapPageInHyperSpace(Process, DestPage, &Irql);
    if (TempAddress == NULL)
    {
        return(STATUS_NO_MEMORY);
    }
	TempSource = MiMapPageInHyperSpace(Process, SrcPage, &Irql2);
	if (!TempSource) {
		MiUnmapPageInHyperSpace(Process, TempAddress, Irql);
		return(STATUS_NO_MEMORY);
	}

    memcpy(TempAddress, TempSource, PAGE_SIZE);

	MiUnmapPageInHyperSpace(Process, TempSource, Irql2);
    MiUnmapPageInHyperSpace(Process, TempAddress, Irql);
    return(STATUS_SUCCESS);
}

NTSTATUS
NTAPI
MiCowCacheSectionPage
(PMMSUPPORT AddressSpace,
 PMEMORY_AREA MemoryArea,
 PVOID Address,
 BOOLEAN Locked,
 PMM_REQUIRED_RESOURCES Required)
{
   PMM_CACHE_SECTION_SEGMENT Segment;
   PFN_NUMBER NewPage, OldPage;
   NTSTATUS Status;
   PVOID PAddress;
   LARGE_INTEGER Offset;
   PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
    
   DPRINT("MmAccessFaultSectionView(%x, %x, %x, %x)\n", AddressSpace, MemoryArea, Address, Locked);

   Segment = MemoryArea->Data.CacheData.Segment;

   /*
    * Lock the segment
    */
   MmLockCacheSectionSegment(Segment);
 
   /*
    * Find the offset of the page
    */
   PAddress = MM_ROUND_DOWN(Address, PAGE_SIZE);
   Offset.QuadPart = (ULONG_PTR)PAddress - (ULONG_PTR)MemoryArea->StartingAddress +
	   MemoryArea->Data.CacheData.ViewOffset.QuadPart;

#if 0 // XXX Cache sections are not CoW.  For now, treat all access violations this way.
   if ((!Segment->WriteCopy &&
        !MemoryArea->Data.CacheData.WriteCopyView) ||
       Segment->Image.Characteristics & IMAGE_SCN_MEM_SHARED)
#endif
   {
#if 0 // XXX Cache sections don't have regions at present, which streamlines things
       if (Region->Protect == PAGE_READWRITE ||
           Region->Protect == PAGE_EXECUTE_READWRITE)
#endif
       {
           DPRINTC("setting non-cow page %x %x:%x offset %x (%x) to writable\n", Segment, Process, PAddress, Offset.u.LowPart, MmGetPfnForProcess(Process, Address));
           if (Segment->FileObject)
           {
               DPRINTC("file %wZ\n", &Segment->FileObject->FileName);
           }
           ULONG Entry = MiGetPageEntryCacheSectionSegment(Segment, &Offset);
           DPRINT("Entry %x\n", Entry);
           if (Entry &&
               !IS_SWAP_FROM_SSE(Entry) &&
               PFN_FROM_SSE(Entry) == MmGetPfnForProcess(Process, Address)) {
               MiSetPageEntryCacheSectionSegment(Segment, &Offset, DIRTY_SSE(Entry));
           }
           MmSetPageProtect(Process, PAddress, PAGE_READWRITE);
           MmUnlockCacheSectionSegment(Segment);
           DPRINT("Done\n");
           return STATUS_SUCCESS;
       }
#if 0
       else
       {
           DPRINT("Not supposed to be writable\n");
           MmUnlockCacheSectionSegment(Segment);
           return STATUS_ACCESS_VIOLATION;
       }
#endif
   }
   
   if (!Required->Page[0])
   {
	   SWAPENTRY SwapEntry;
	   if (MmIsPageSwapEntry(Process, Address))
	   {
		   MmGetPageFileMapping(Process, Address, &SwapEntry);
		   MmUnlockCacheSectionSegment(Segment);
		   if (SwapEntry == MM_WAIT_ENTRY)
			   return STATUS_SUCCESS + 1; // Wait ... somebody else is getting it right now
		   else
			   return STATUS_SUCCESS; // Nonwait swap entry ... handle elsewhere
	   }
	   Required->Page[1] = MmGetPfnForProcess(Process, Address);
	   Required->Consumer = MC_CACHE;
	   Required->Amount = 1;
	   Required->File = __FILE__;
	   Required->Line = __LINE__;
	   Required->DoAcquisition = MiGetOnePage;
	   MmCreatePageFileMapping(Process, Address, MM_WAIT_ENTRY);
	   MmUnlockCacheSectionSegment(Segment);
	   return STATUS_MORE_PROCESSING_REQUIRED;
   }

   NewPage = Required->Page[0];
   OldPage = Required->Page[1];

   DPRINT("Allocated page %x\n", NewPage);

   /*
    * Unshare the old page.
    */
   MmDeleteRmap(OldPage, Process, PAddress);

   /*
    * Copy the old page
    */
   DPRINT("Copying\n");
   MiCopyPageToPage(NewPage, OldPage);

   /*
    * Set the PTE to point to the new page
    */
   Status = MmCreateVirtualMapping
	   (Process, Address, PAGE_READWRITE, &NewPage, 1);

   if (!NT_SUCCESS(Status))
   {
      DPRINT1("MmCreateVirtualMapping failed, not out of memory\n");
      ASSERT(FALSE);
	  MmUnlockCacheSectionSegment(Segment);
      return(Status);
   }

   MmInsertRmap(NewPage, Process, PAddress);
   MmReleasePageMemoryConsumer(MC_CACHE, OldPage);
   MmUnlockCacheSectionSegment(Segment);

   DPRINT("Address 0x%.8X\n", Address);
   return(STATUS_SUCCESS);
}

KEVENT MmWaitPageEvent;

typedef struct _WORK_QUEUE_WITH_CONTEXT
{
	WORK_QUEUE_ITEM WorkItem;
	PMMSUPPORT AddressSpace;
	PMEMORY_AREA MemoryArea;
	PMM_REQUIRED_RESOURCES Required;
	NTSTATUS Status;
	KEVENT Wait;
	AcquireResource DoAcquisition;
} WORK_QUEUE_WITH_CONTEXT, *PWORK_QUEUE_WITH_CONTEXT;

VOID
NTAPI
MmpFaultWorker
(PWORK_QUEUE_WITH_CONTEXT WorkItem)
{
	DPRINT("Calling work\n");
	WorkItem->Status = 
		WorkItem->Required->DoAcquisition
		(WorkItem->AddressSpace,
		 WorkItem->MemoryArea,
		 WorkItem->Required);
	DPRINT("Status %x\n", WorkItem->Status);
	KeSetEvent(&WorkItem->Wait, IO_NO_INCREMENT, FALSE);
}

NTSTATUS
NTAPI
MmpSectionAccessFaultInner
(KPROCESSOR_MODE Mode,
 PMMSUPPORT AddressSpace,
 ULONG_PTR Address,
 BOOLEAN FromMdl,
 PETHREAD Thread)
{
   MEMORY_AREA* MemoryArea;
   NTSTATUS Status;
   BOOLEAN Locked = FromMdl;
   MM_REQUIRED_RESOURCES Resources = { 0 };

   DPRINT("MmAccessFault(Mode %d, Address %x)\n", Mode, Address);

   if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
   {
      DPRINT1("Page fault at high IRQL was %d\n", KeGetCurrentIrql());
      return(STATUS_UNSUCCESSFUL);
   }

   /*
    * Find the memory area for the faulting address
    */
   if (Address >= (ULONG_PTR)MmSystemRangeStart)
   {
      /*
       * Check permissions
       */
      if (Mode != KernelMode)
      {
         DPRINT("MmAccessFault(Mode %d, Address %x)\n", Mode, Address);
         return(STATUS_ACCESS_VIOLATION);
      }
      AddressSpace = MmGetKernelAddressSpace();
   }
   else
   {
      AddressSpace = &PsGetCurrentProcess()->Vm;
   }

   if (!FromMdl)
   {
      MmLockAddressSpace(AddressSpace);
   }

   do
   {
      MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, (PVOID)Address);
      if (MemoryArea == NULL || 
		  MemoryArea->DeleteInProgress)
      {
         if (!FromMdl)
         {
            MmUnlockAddressSpace(AddressSpace);
         }
		 DPRINT("Address: %x\n", Address);
         return (STATUS_ACCESS_VIOLATION);
      }

	  DPRINT
		  ("Type %x (%x -> %x)\n", 
		   MemoryArea->Type, 
		   MemoryArea->StartingAddress, 
		   MemoryArea->EndingAddress);

	  Resources.DoAcquisition = NULL;

	  // Note: fault handlers are called with address space locked
	  // We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed
	  Status = MiCowCacheSectionPage
		  (AddressSpace, MemoryArea, (PVOID)Address, Locked, &Resources);

	  if (!FromMdl)
	  {
		  MmUnlockAddressSpace(AddressSpace);
	  }

	  if (Status == STATUS_SUCCESS + 1)
	  {
		  // Wait page ...
		  DPRINT("Waiting for %x\n", Address);
		  MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace), Address);
		  DPRINT("Restarting fault %x\n", Address);
		  Status = STATUS_MM_RESTART_OPERATION;
	  }
	  else if (Status == STATUS_MM_RESTART_OPERATION)
	  {
		  // Clean slate
		  RtlZeroMemory(&Resources, sizeof(Resources));
	  }
	  else if (Status == STATUS_MORE_PROCESSING_REQUIRED)
	  {
		  if (Thread->ActiveFaultCount > 0)
		  {
			  WORK_QUEUE_WITH_CONTEXT Context = { };
			  DPRINT("Already fault handling ... going to work item (%x)\n", Address);
			  Context.AddressSpace = AddressSpace;
			  Context.MemoryArea = MemoryArea;
			  Context.Required = &Resources;
			  KeInitializeEvent(&Context.Wait, NotificationEvent, FALSE);
			  ExInitializeWorkItem(&Context.WorkItem, (PWORKER_THREAD_ROUTINE)MmpFaultWorker, &Context);
			  DPRINT("Queue work item\n");
			  ExQueueWorkItem(&Context.WorkItem, DelayedWorkQueue);
			  DPRINT("Wait\n");
			  KeWaitForSingleObject(&Context.Wait, 0, KernelMode, FALSE, NULL);
			  Status = Context.Status;
			  DPRINT("Status %x\n", Status);
		  }
		  else
		  {
			  Status = Resources.DoAcquisition(AddressSpace, MemoryArea, &Resources);
		  }
		  
		  if (NT_SUCCESS(Status))
		  {
			  Status = STATUS_MM_RESTART_OPERATION;
		  }
	  }
	  
	  if (!FromMdl)
	  {
		  MmLockAddressSpace(AddressSpace);
	  }
   }
   while (Status == STATUS_MM_RESTART_OPERATION);

   if (!NT_SUCCESS(Status) && MemoryArea->Type == 1)
   {
	   DPRINT1("Completed page fault handling %x %x\n", Address, Status);
	   DPRINT1
		   ("Type %x (%x -> %x)\n", 
			MemoryArea->Type, 
			MemoryArea->StartingAddress, 
			MemoryArea->EndingAddress);
   }

   if (!FromMdl)
   {
      MmUnlockAddressSpace(AddressSpace);
   }

   return(Status);
}

NTSTATUS
NTAPI
MmAccessFaultCacheSection
(KPROCESSOR_MODE Mode,
 ULONG_PTR Address,
 BOOLEAN FromMdl)
{
	PETHREAD Thread;
	PMMSUPPORT AddressSpace;
	NTSTATUS Status;
	
	DPRINT("MmpAccessFault(Mode %d, Address %x)\n", Mode, Address);
	
	Thread = PsGetCurrentThread();

	if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
	{
		DPRINT1("Page fault at high IRQL %d, address %x\n", KeGetCurrentIrql(), Address);
		return(STATUS_UNSUCCESSFUL);
	}
	
	/*
	 * Find the memory area for the faulting address
	 */
	if (Address >= (ULONG_PTR)MmSystemRangeStart)
	{
		/*
		 * Check permissions
		 */
		if (Mode != KernelMode)
		{
			DPRINT1("Address: %x:%x\n", PsGetCurrentProcess(), Address);
			return(STATUS_ACCESS_VIOLATION);
		}
		AddressSpace = MmGetKernelAddressSpace();
	}
	else
	{
		AddressSpace = &PsGetCurrentProcess()->Vm;
	}
	
	Thread->ActiveFaultCount++;	
	Status = MmpSectionAccessFaultInner(Mode, AddressSpace, Address, FromMdl, Thread);
	Thread->ActiveFaultCount--;

	return(Status);
}

NTSTATUS
NTAPI
MmNotPresentFaultCacheSectionInner
(KPROCESSOR_MODE Mode,
 PMMSUPPORT AddressSpace,
 ULONG_PTR Address,
 BOOLEAN FromMdl,
 PETHREAD Thread)
{
	BOOLEAN Locked = FromMdl;
	PMEMORY_AREA MemoryArea;
	MM_REQUIRED_RESOURCES Resources = { 0 };
	NTSTATUS Status = STATUS_SUCCESS;

	if (!FromMdl)
	{
		MmLockAddressSpace(AddressSpace);
	}
	
	/*
	 * Call the memory area specific fault handler
	 */
	do
	{
		MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, (PVOID)Address);
		if (MemoryArea == NULL || MemoryArea->DeleteInProgress)
		{
			Status = STATUS_ACCESS_VIOLATION;
			if (MemoryArea)
			{
				DPRINT1("Type %x DIP %x\n", MemoryArea->Type, MemoryArea->DeleteInProgress);
			}
			else
			{
				DPRINT1("No memory area\n");
			}
			DPRINT1("Process %x, Address %x\n", MmGetAddressSpaceOwner(AddressSpace), Address);
			break;
		}
		
		DPRINTC
			("Type %x (%x -> %x -> %x) in %x\n", 
			 MemoryArea->Type, 
			 MemoryArea->StartingAddress, 
			 Address,
			 MemoryArea->EndingAddress,
			 PsGetCurrentThread());
		
		Resources.DoAcquisition = NULL;
		
		// Note: fault handlers are called with address space locked
		// We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed

		Status = MmNotPresentFaultCachePage
			(AddressSpace, MemoryArea, (PVOID)Address, Locked, &Resources);
		
		if (!FromMdl)
		{
			MmUnlockAddressSpace(AddressSpace);
		}
		
		if (Status == STATUS_SUCCESS)
		{
			; // Nothing
		}
		else if (Status == STATUS_SUCCESS + 1)
		{
			// Wait page ...
			DPRINT("Waiting for %x\n", Address);
			MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace), Address);
			DPRINT("Done waiting for %x\n", Address);
			Status = STATUS_MM_RESTART_OPERATION;
		}
		else if (Status == STATUS_MM_RESTART_OPERATION)
		{
			// Clean slate
			DPRINT("Clear resource\n");
			RtlZeroMemory(&Resources, sizeof(Resources));
		}
		else if (Status == STATUS_MORE_PROCESSING_REQUIRED)
		{
			if (Thread->ActiveFaultCount > 1)
			{
				WORK_QUEUE_WITH_CONTEXT Context = { };
				DPRINTC("Already fault handling ... going to work item (%x)\n", Address);
				Context.AddressSpace = AddressSpace;
				Context.MemoryArea = MemoryArea;
				Context.Required = &Resources;
				KeInitializeEvent(&Context.Wait, NotificationEvent, FALSE);
				ExInitializeWorkItem(&Context.WorkItem, (PWORKER_THREAD_ROUTINE)MmpFaultWorker, &Context);
				DPRINT("Queue work item\n");
				ExQueueWorkItem(&Context.WorkItem, DelayedWorkQueue);
				DPRINT("Wait\n");
				KeWaitForSingleObject(&Context.Wait, 0, KernelMode, FALSE, NULL);
				Status = Context.Status;
				DPRINTC("Status %x\n", Status);
			}
			else
			{
				DPRINT("DoAcquisition %x\n", Resources.DoAcquisition);
				Status = Resources.DoAcquisition
					(AddressSpace, MemoryArea, &Resources);
				DPRINT("DoAcquisition %x -> %x\n", Resources.DoAcquisition, Status);
			}

			if (NT_SUCCESS(Status))
			{
				Status = STATUS_MM_RESTART_OPERATION;
			}
		}
		else if (NT_SUCCESS(Status))
		{
			ASSERT(FALSE);
		}

		if (!FromMdl)
		{
			MmLockAddressSpace(AddressSpace);
		}
	}
	while (Status == STATUS_MM_RESTART_OPERATION);

	DPRINTC("Completed page fault handling: %x:%x %x\n", MmGetAddressSpaceOwner(AddressSpace), Address, Status);
	if (!FromMdl)
	{
		MmUnlockAddressSpace(AddressSpace);
	}

	MiSetPageEvent(MmGetAddressSpaceOwner(AddressSpace), Address);
	DPRINT("Done %x\n", Status);

	return Status;
}

NTSTATUS
NTAPI
MmNotPresentFaultCacheSection
(KPROCESSOR_MODE Mode,
 ULONG_PTR Address,
 BOOLEAN FromMdl)
{
	PETHREAD Thread;
	PMMSUPPORT AddressSpace;
	NTSTATUS Status;

	Address &= ~(PAGE_SIZE - 1);
	DPRINT("MmNotPresentFault(Mode %d, Address %x)\n", Mode, Address);
	
	Thread = PsGetCurrentThread();

	if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
	{
		DPRINT1("Page fault at high IRQL %d, address %x\n", KeGetCurrentIrql(), Address);
		ASSERT(FALSE);
		return(STATUS_UNSUCCESSFUL);
	}
	
	/*
	 * Find the memory area for the faulting address
	 */
	if (Address >= (ULONG_PTR)MmSystemRangeStart)
	{
		/*
		 * Check permissions
		 */
		if (Mode != KernelMode)
		{
			DPRINTC("Address: %x\n", Address);
			return(STATUS_ACCESS_VIOLATION);
		}
		AddressSpace = MmGetKernelAddressSpace();
	}
	else
	{
		AddressSpace = &PsGetCurrentProcess()->Vm;
	}
	
	Thread->ActiveFaultCount++;	
	Status = MmNotPresentFaultCacheSectionInner
		(Mode, AddressSpace, Address, FromMdl, Thread);
	Thread->ActiveFaultCount--;

	ASSERT(Status != STATUS_UNSUCCESSFUL);
	ASSERT(Status != STATUS_INVALID_PARAMETER);
	DPRINT("MmAccessFault %x:%x -> %x\n", MmGetAddressSpaceOwner(AddressSpace), Address, Status);

	return(Status);
}
