xref: /reactos/ntoskrnl/cache/section/fault.c (revision 7e069ccd)
1 /*
2  * Copyright (C) 1998-2005 ReactOS Team (and the authors from the programmers section)
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  *
18  *
19  * PROJECT:         ReactOS kernel
20  * FILE:            ntoskrnl/cache/section/fault.c
21  * PURPOSE:         Consolidate fault handlers for sections
22  *
23  * PROGRAMMERS:     Arty
24  *                  Rex Jolliff
25  *                  David Welch
26  *                  Eric Kohl
27  *                  Emanuele Aliberti
28  *                  Eugene Ingerman
29  *                  Casper Hornstrup
30  *                  KJK::Hyperion
31  *                  Guido de Jong
32  *                  Ge van Geldorp
33  *                  Royce Mitchell III
34  *                  Filip Navara
35  *                  Aleksey Bragin
36  *                  Jason Filby
37  *                  Thomas Weidenmueller
38  *                  Gunnar Andre' Dalsnes
39  *                  Mike Nordell
40  *                  Alex Ionescu
41  *                  Gregor Anich
42  *                  Steven Edwards
43  *                  Herve Poussineau
44  */
45 
46 /*
47 
48 I've generally organized fault handling code in newmm as handlers that run
49 under a single lock acquisition, check the state, and either take necessary
50 action atomically, or place a wait entry and return a continuation to the
51 caller.  This lends itself to code that has a simple, structured form,
52 doesn't make assumptions about lock taking and breaking, and provides an
53 obvious, graphic seperation between code that may block and code that isn't
54 allowed to.  This file contains the non-blocking half.
55 
56 In order to request a blocking operation to happen outside locks, place a
57 function pointer in the provided MM_REQUIRED_RESOURCES struct and return
58 STATUS_MORE_PROCESSING_REQUIRED.  The function indicated will receive the
59 provided struct and take action outside of any mm related locks and at
60 PASSIVE_LEVEL.  The same fault handler will be called again after the
61 blocking operation succeeds.  In this way, the fault handler can accumulate
62 state, but will freely work while competing with other threads.
63 
64 Fault handlers in this file should check for an MM_WAIT_ENTRY in a page
65 table they're using and return STATUS_SUCCESS + 1 if it's found.  In that
66 case, the caller will wait on the wait entry event until the competing thread
67 is finished, and recall this handler in the current thread.
68 
69 Another thing to note here is that we require mappings to exactly mirror
70 rmaps, so each mapping should be immediately followed by an rmap addition.
71 
72 */
73 
74 /* INCLUDES *****************************************************************/
75 
76 #include <ntoskrnl.h>
77 #include "newmm.h"
78 #define NDEBUG
79 #include <debug.h>
80 #include <mm/ARM3/miarm.h>
81 
82 #define DPRINTC DPRINT
83 
84 extern KEVENT MmWaitPageEvent;
85 extern PMMWSL MmWorkingSetList;
86 
87 /*
88 
89 Multiple stage handling of a not-present fault in a data section.
90 
91 Required->State is used to accumulate flags that indicate the next action
92 the handler should take.
93 
94 State & 2 is currently used to indicate that the page acquired by a previous
95 callout is a global page to the section and should be placed in the section
96 page table.
97 
98 Note that the primitive tail recursion done here reaches the base case when
99 the page is present.
100 
101 */
102 
103 NTSTATUS
104 NTAPI
105 MmNotPresentFaultCachePage (
106     _In_ PMMSUPPORT AddressSpace,
107     _In_ MEMORY_AREA* MemoryArea,
108     _In_ PVOID Address,
109     _In_ BOOLEAN Locked,
110     _Inout_ PMM_REQUIRED_RESOURCES Required)
111 {
112     NTSTATUS Status;
113     PVOID PAddress;
114     ULONG Consumer;
115     PMM_SECTION_SEGMENT Segment;
116     LARGE_INTEGER FileOffset, TotalOffset;
117     ULONG_PTR Entry;
118     ULONG Attributes;
119     PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
120     KIRQL OldIrql;
121 
122     DPRINT("Not Present: %p %p (%p-%p)\n",
123            AddressSpace,
124            Address,
125            MA_GetStartingAddress(MemoryArea),
126            MA_GetEndingAddress(MemoryArea));
127 
128     /*
129      * There is a window between taking the page fault and locking the
130      * address space when another thread could load the page so we check
131      * that.
132      */
133     if (MmIsPagePresent(Process, Address))
134     {
135         DPRINT("Done\n");
136         return STATUS_SUCCESS;
137     }
138 
139     PAddress = MM_ROUND_DOWN(Address, PAGE_SIZE);
140     TotalOffset.QuadPart = (ULONG_PTR)PAddress -
141                            MA_GetStartingAddress(MemoryArea);
142 
143     Segment = MemoryArea->Data.SectionData.Segment;
144 
145     TotalOffset.QuadPart += MemoryArea->Data.SectionData.ViewOffset.QuadPart;
146     FileOffset = TotalOffset;
147 
148     //Consumer = (Segment->Flags & MM_DATAFILE_SEGMENT) ? MC_CACHE : MC_USER;
149     Consumer = MC_CACHE;
150 
151     if (Segment->FileObject)
152     {
153         DPRINT("FileName %wZ\n", &Segment->FileObject->FileName);
154     }
155 
156     DPRINT("Total Offset %08x%08x\n", TotalOffset.HighPart, TotalOffset.LowPart);
157 
158     /* Lock the segment */
159     MmLockSectionSegment(Segment);
160 
161     /* Get the entry corresponding to the offset within the section */
162     Entry = MmGetPageEntrySectionSegment(Segment, &TotalOffset);
163 
164     Attributes = PAGE_READONLY;
165 
166     if (Required->State && Required->Page[0])
167     {
168         DPRINT("Have file and page, set page %x in section @ %x #\n",
169                Required->Page[0],
170                TotalOffset.LowPart);
171 
172         if (Required->SwapEntry)
173             MmSetSavedSwapEntryPage(Required->Page[0], Required->SwapEntry);
174 
175         if (Required->State & 2)
176         {
177             DPRINT("Set in section @ %x\n", TotalOffset.LowPart);
178             Status = MmSetPageEntrySectionSegment(Segment,
179                                                   &TotalOffset,
180                                                   Entry = MAKE_PFN_SSE(Required->Page[0]));
181             if (!NT_SUCCESS(Status))
182             {
183                 MmReleasePageMemoryConsumer(MC_CACHE, Required->Page[0]);
184             }
185             MmUnlockSectionSegment(Segment);
186             MiSetPageEvent(Process, Address);
187             DPRINT("Status %x\n", Status);
188             return STATUS_MM_RESTART_OPERATION;
189         }
190         else
191         {
192             DPRINT("Set %x in address space @ %p\n", Required->Page[0], Address);
193             Status = MmCreateVirtualMapping(Process,
194                                             Address,
195                                             Attributes,
196                                             Required->Page,
197                                             1);
198             if (NT_SUCCESS(Status))
199             {
200                 MmInsertRmap(Required->Page[0], Process, Address);
201             }
202             else
203             {
204                 /* Drop the reference for our address space ... */
205                 MmReleasePageMemoryConsumer(MC_CACHE, Required->Page[0]);
206             }
207             MmUnlockSectionSegment(Segment);
208             DPRINTC("XXX Set Event %x\n", Status);
209             MiSetPageEvent(Process, Address);
210             DPRINT("Status %x\n", Status);
211             return Status;
212         }
213     }
214     else if (MM_IS_WAIT_PTE(Entry))
215     {
216         // Whenever MM_WAIT_ENTRY is required as a swap entry, we need to
217         // ask the fault handler to wait until we should continue.  Rathern
218         // than recopy this boilerplate code everywhere, we just ask them
219         // to wait.
220         MmUnlockSectionSegment(Segment);
221         return STATUS_SUCCESS + 1;
222     }
223     else if (Entry)
224     {
225         PFN_NUMBER Page = PFN_FROM_SSE(Entry);
226         DPRINT("Take reference to page %x #\n", Page);
227 
228         if (MiGetPfnEntry(Page) == NULL)
229         {
230             DPRINT1("Found no PFN entry for page 0x%x in page entry 0x%x (segment: 0x%p, offset: %08x%08x)\n",
231                     Page,
232                     Entry,
233                     Segment,
234                     TotalOffset.HighPart,
235                     TotalOffset.LowPart);
236             KeBugCheck(CACHE_MANAGER);
237         }
238 
239         OldIrql = MiAcquirePfnLock();
240         MmReferencePage(Page);
241         MiReleasePfnLock(OldIrql);
242 
243         Status = MmCreateVirtualMapping(Process, Address, Attributes, &Page, 1);
244         if (NT_SUCCESS(Status))
245         {
246             MmInsertRmap(Page, Process, Address);
247         }
248         DPRINT("XXX Set Event %x\n", Status);
249         MiSetPageEvent(Process, Address);
250         MmUnlockSectionSegment(Segment);
251         DPRINT("Status %x\n", Status);
252         return Status;
253     }
254     else
255     {
256         DPRINT("Get page into section\n");
257         /*
258          * If the entry is zero (and it can't change because we have
259          * locked the segment) then we need to load the page.
260          */
261         //DPRINT1("Read from file %08x %wZ\n", FileOffset.LowPart, &Section->FileObject->FileName);
262         Required->State = 2;
263         Required->Context = Segment->FileObject;
264         Required->Consumer = Consumer;
265         Required->FileOffset = FileOffset;
266         Required->Amount = PAGE_SIZE;
267         Required->DoAcquisition = MiReadFilePage;
268 
269         MmSetPageEntrySectionSegment(Segment,
270                                      &TotalOffset,
271                                      MAKE_SWAP_SSE(MM_WAIT_ENTRY));
272 
273         MmUnlockSectionSegment(Segment);
274         return STATUS_MORE_PROCESSING_REQUIRED;
275     }
276     ASSERT(FALSE);
277     return STATUS_ACCESS_VIOLATION;
278 }
279 
280 NTSTATUS
281 NTAPI
282 MiCopyPageToPage(PFN_NUMBER DestPage, PFN_NUMBER SrcPage)
283 {
284     PEPROCESS Process;
285     KIRQL Irql, Irql2;
286     PVOID TempAddress, TempSource;
287 
288     Process = PsGetCurrentProcess();
289     TempAddress = MiMapPageInHyperSpace(Process, DestPage, &Irql);
290     if (TempAddress == NULL)
291     {
292         return STATUS_NO_MEMORY;
293     }
294     TempSource = MiMapPageInHyperSpace(Process, SrcPage, &Irql2);
295     if (!TempSource) {
296         MiUnmapPageInHyperSpace(Process, TempAddress, Irql);
297         return STATUS_NO_MEMORY;
298     }
299 
300     memcpy(TempAddress, TempSource, PAGE_SIZE);
301 
302     MiUnmapPageInHyperSpace(Process, TempSource, Irql2);
303     MiUnmapPageInHyperSpace(Process, TempAddress, Irql);
304     return STATUS_SUCCESS;
305 }
306 
307 /*
308 
309 This function is deceptively named, in that it does the actual work of handling
310 access faults on data sections.  In the case of the code that's present here,
311 we don't allow cow sections, but we do need this to unset the initial
312 PAGE_READONLY condition of pages faulted into the cache so that we can add
313 a dirty bit in the section page table on the first modification.
314 
315 In the ultimate form of this code, CoW is reenabled.
316 
317 */
318 
319 NTSTATUS
320 NTAPI
321 MiCowCacheSectionPage (
322     _In_ PMMSUPPORT AddressSpace,
323     _In_ PMEMORY_AREA MemoryArea,
324     _In_ PVOID Address,
325     _In_ BOOLEAN Locked,
326     _Inout_ PMM_REQUIRED_RESOURCES Required)
327 {
328     PMM_SECTION_SEGMENT Segment;
329     PFN_NUMBER NewPage, OldPage;
330     NTSTATUS Status;
331     PVOID PAddress;
332     LARGE_INTEGER Offset;
333     PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace);
334 
335     DPRINT("MmAccessFaultSectionView(%p, %p, %p, %u)\n",
336            AddressSpace,
337            MemoryArea,
338            Address,
339            Locked);
340 
341     Segment = MemoryArea->Data.SectionData.Segment;
342 
343    /* Lock the segment */
344     MmLockSectionSegment(Segment);
345 
346    /* Find the offset of the page */
347     PAddress = MM_ROUND_DOWN(Address, PAGE_SIZE);
348     Offset.QuadPart = (ULONG_PTR)PAddress - MA_GetStartingAddress(MemoryArea) +
349                       MemoryArea->Data.SectionData.ViewOffset.QuadPart;
350 
351     if (!Segment->WriteCopy /*&&
352         !MemoryArea->Data.SectionData.WriteCopyView*/ ||
353         Segment->Image.Characteristics & IMAGE_SCN_MEM_SHARED)
354     {
355 #if 0
356         if (Region->Protect == PAGE_READWRITE ||
357             Region->Protect == PAGE_EXECUTE_READWRITE)
358 #endif
359         {
360             ULONG_PTR Entry;
361             DPRINTC("setting non-cow page %p %p:%p offset %I64x (%Ix) to writable\n",
362                     Segment,
363                     Process,
364                     PAddress,
365                     Offset.QuadPart,
366                     MmGetPfnForProcess(Process, Address));
367             if (Segment->FileObject)
368             {
369                 DPRINTC("file %wZ\n", &Segment->FileObject->FileName);
370             }
371             Entry = MmGetPageEntrySectionSegment(Segment, &Offset);
372             DPRINT("Entry %x\n", Entry);
373             if (Entry &&
374                 !IS_SWAP_FROM_SSE(Entry) &&
375                 PFN_FROM_SSE(Entry) == MmGetPfnForProcess(Process, Address)) {
376 
377                 MmSetPageEntrySectionSegment(Segment,
378                                              &Offset,
379                                              DIRTY_SSE(Entry));
380             }
381             MmSetPageProtect(Process, PAddress, PAGE_READWRITE);
382             MmSetDirtyPage(Process, PAddress);
383             MmUnlockSectionSegment(Segment);
384             DPRINT("Done\n");
385             return STATUS_SUCCESS;
386         }
387 #if 0
388         else
389         {
390             DPRINT("Not supposed to be writable\n");
391             MmUnlockSectionSegment(Segment);
392             return STATUS_ACCESS_VIOLATION;
393         }
394 #endif
395     }
396 
397     if (!Required->Page[0])
398     {
399         SWAPENTRY SwapEntry;
400         if (MmIsPageSwapEntry(Process, Address))
401         {
402             MmGetPageFileMapping(Process, Address, &SwapEntry);
403             MmUnlockSectionSegment(Segment);
404             if (SwapEntry == MM_WAIT_ENTRY)
405                 return STATUS_SUCCESS + 1; // Wait ... somebody else is getting it right now
406             else
407                 return STATUS_SUCCESS; // Nonwait swap entry ... handle elsewhere
408         }
409         /* Call out to acquire a page to copy to.  We'll be re-called when
410          * the page has been allocated. */
411         Required->Page[1] = MmGetPfnForProcess(Process, Address);
412         Required->Consumer = MC_CACHE;
413         Required->Amount = 1;
414         Required->File = __FILE__;
415         Required->Line = __LINE__;
416         Required->DoAcquisition = MiGetOnePage;
417         MmCreatePageFileMapping(Process, Address, MM_WAIT_ENTRY);
418         MmUnlockSectionSegment(Segment);
419         return STATUS_MORE_PROCESSING_REQUIRED;
420     }
421 
422     NewPage = Required->Page[0];
423     OldPage = Required->Page[1];
424 
425     DPRINT("Allocated page %x\n", NewPage);
426 
427     /* Unshare the old page */
428     MmDeleteRmap(OldPage, Process, PAddress);
429 
430    /* Copy the old page */
431     DPRINT("Copying\n");
432     MiCopyPageToPage(NewPage, OldPage);
433 
434    /* Set the PTE to point to the new page */
435     Status = MmCreateVirtualMapping(Process,
436                                     Address,
437                                     PAGE_READWRITE,
438                                     &NewPage,
439                                     1);
440 
441     if (!NT_SUCCESS(Status))
442     {
443         DPRINT1("MmCreateVirtualMapping failed, not out of memory\n");
444         ASSERT(FALSE);
445         MmUnlockSectionSegment(Segment);
446         return Status;
447     }
448 
449     MmInsertRmap(NewPage, Process, PAddress);
450     MmReleasePageMemoryConsumer(MC_CACHE, OldPage);
451     MmUnlockSectionSegment(Segment);
452 
453     DPRINT("Address 0x%p\n", Address);
454     return STATUS_SUCCESS;
455 }
456 
457 KEVENT MmWaitPageEvent;
458 
459 typedef struct _WORK_QUEUE_WITH_CONTEXT
460 {
461     WORK_QUEUE_ITEM WorkItem;
462     PMMSUPPORT AddressSpace;
463     PMEMORY_AREA MemoryArea;
464     PMM_REQUIRED_RESOURCES Required;
465     NTSTATUS Status;
466     KEVENT Wait;
467     AcquireResource DoAcquisition;
468 } WORK_QUEUE_WITH_CONTEXT, *PWORK_QUEUE_WITH_CONTEXT;
469 
470 /*
471 
472 This is the work item used do blocking resource acquisition when a fault
473 handler returns STATUS_MORE_PROCESSING_REQUIRED.  It's used to allow resource
474 acquisition to take place on a different stack, and outside of any locks used
475 by fault handling, making recursive fault handling possible when required.
476 
477 */
478 
479 _Function_class_(WORKER_THREAD_ROUTINE)
480 VOID
481 NTAPI
482 MmpFaultWorker(PVOID Parameter)
483 {
484     PWORK_QUEUE_WITH_CONTEXT WorkItem = Parameter;
485 
486     DPRINT("Calling work\n");
487     WorkItem->Status = WorkItem->Required->DoAcquisition(WorkItem->AddressSpace,
488                                                          WorkItem->MemoryArea,
489                                                          WorkItem->Required);
490     DPRINT("Status %x\n", WorkItem->Status);
491     KeSetEvent(&WorkItem->Wait, IO_NO_INCREMENT, FALSE);
492 }
493 
494 /*
495 
496 This code seperates the action of fault handling into an upper and lower
497 handler to allow the inner handler to optionally be called in work item
498 if the stack is getting too deep.  My experiments show that the third
499 recursive page fault taken at PASSIVE_LEVEL must be shunted away to a
500 worker thread.  In the ultimate form of this code, the primary fault handler
501 makes this decision by using a thread-local counter to detect a too-deep
502 fault stack and call the inner fault handler in a worker thread if required.
503 
504 Note that faults are taken at passive level and have access to ordinary
505 driver entry points such as those that read and write files, and filesystems
506 should use paged structures whenever possible.  This makes recursive faults
507 both a perfectly normal occurrance, and a worthwhile case to handle.
508 
509 The code below will repeatedly call MiCowSectionPage as long as it returns
510 either STATUS_SUCCESS + 1 or STATUS_MORE_PROCESSING_REQUIRED.  In the more
511 processing required case, we call out to a blocking resource acquisition
512 function and then recall the faut handler with the shared state represented
513 by the MM_REQUIRED_RESOURCES struct.
514 
515 In the other case, we wait on the wait entry event and recall the handler.
516 Each time the wait entry event is signalled, one thread has removed an
517 MM_WAIT_ENTRY from a page table.
518 
519 In the ultimate form of this code, there is a single system wide fault handler
520 for each of access fault and not present and each memory area contains a
521 function pointer that indicates the active fault handler.  Since the mm code
522 in reactos is currently fragmented, I didn't bring this change to trunk.
523 
524 */
525 
526 NTSTATUS
527 NTAPI
528 MmpSectionAccessFaultInner(KPROCESSOR_MODE Mode,
529                            PMMSUPPORT AddressSpace,
530                            ULONG_PTR Address,
531                            BOOLEAN FromMdl,
532                            PETHREAD Thread)
533 {
534     MEMORY_AREA* MemoryArea;
535     NTSTATUS Status;
536     BOOLEAN Locked = FromMdl;
537     MM_REQUIRED_RESOURCES Resources = { 0 };
538     WORK_QUEUE_WITH_CONTEXT Context;
539 
540     RtlZeroMemory(&Context, sizeof(WORK_QUEUE_WITH_CONTEXT));
541 
542     DPRINT("MmAccessFault(Mode %d, Address %Ix)\n", Mode, Address);
543 
544     if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
545     {
546         DPRINT1("Page fault at high IRQL was %u\n", KeGetCurrentIrql());
547         return STATUS_UNSUCCESSFUL;
548     }
549 
550     /* Find the memory area for the faulting address */
551     if (Address >= (ULONG_PTR)MmSystemRangeStart)
552     {
553         /* Check permissions */
554         if (Mode != KernelMode)
555         {
556             DPRINT("MmAccessFault(Mode %d, Address %Ix)\n", Mode, Address);
557             return STATUS_ACCESS_VIOLATION;
558         }
559         AddressSpace = MmGetKernelAddressSpace();
560     }
561     else
562     {
563         AddressSpace = &PsGetCurrentProcess()->Vm;
564     }
565 
566     if (!FromMdl)
567     {
568         MmLockAddressSpace(AddressSpace);
569     }
570 
571     do
572     {
573         MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, (PVOID)Address);
574         if (MemoryArea == NULL ||
575             MemoryArea->DeleteInProgress)
576         {
577             if (!FromMdl)
578             {
579                 MmUnlockAddressSpace(AddressSpace);
580             }
581             DPRINT("Address: %Ix\n", Address);
582             return STATUS_ACCESS_VIOLATION;
583         }
584 
585         DPRINT("Type %x (%p -> %p)\n",
586                MemoryArea->Type,
587                MA_GetStartingAddress(MemoryArea),
588                MA_GetEndingAddress(MemoryArea));
589 
590         Resources.DoAcquisition = NULL;
591 
592         // Note: fault handlers are called with address space locked
593         // We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed
594         Status = MiCowCacheSectionPage(AddressSpace,
595                                        MemoryArea,
596                                        (PVOID)Address,
597                                        Locked,
598                                        &Resources);
599 
600         if (!FromMdl)
601         {
602             MmUnlockAddressSpace(AddressSpace);
603         }
604 
605         if (Status == STATUS_SUCCESS + 1)
606         {
607             /* Wait page ... */
608             DPRINT("Waiting for %Ix\n", Address);
609             MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace), Address);
610             DPRINT("Restarting fault %Ix\n", Address);
611             Status = STATUS_MM_RESTART_OPERATION;
612         }
613         else if (Status == STATUS_MM_RESTART_OPERATION)
614         {
615             /* Clean slate */
616             RtlZeroMemory(&Resources, sizeof(Resources));
617         }
618         else if (Status == STATUS_MORE_PROCESSING_REQUIRED)
619         {
620             if (Thread->ActiveFaultCount > 0)
621             {
622                 DPRINT("Already fault handling ... going to work item (%Ix)\n",
623                        Address);
624                 Context.AddressSpace = AddressSpace;
625                 Context.MemoryArea = MemoryArea;
626                 Context.Required = &Resources;
627                 KeInitializeEvent(&Context.Wait, NotificationEvent, FALSE);
628 
629                 ExInitializeWorkItem(&Context.WorkItem,
630                                      MmpFaultWorker,
631                                      &Context);
632 
633                 DPRINT("Queue work item\n");
634                 ExQueueWorkItem(&Context.WorkItem, DelayedWorkQueue);
635                 DPRINT("Wait\n");
636                 KeWaitForSingleObject(&Context.Wait, 0, KernelMode, FALSE, NULL);
637                 Status = Context.Status;
638                 DPRINT("Status %x\n", Status);
639             }
640             else
641             {
642                 Status = Resources.DoAcquisition(AddressSpace, MemoryArea, &Resources);
643             }
644 
645             if (NT_SUCCESS(Status))
646             {
647                 Status = STATUS_MM_RESTART_OPERATION;
648             }
649         }
650 
651         if (!FromMdl)
652         {
653             MmLockAddressSpace(AddressSpace);
654         }
655     }
656     while (Status == STATUS_MM_RESTART_OPERATION);
657 
658     if (!NT_SUCCESS(Status) && MemoryArea->Type == 1)
659     {
660         DPRINT1("Completed page fault handling %Ix %x\n", Address, Status);
661         DPRINT1("Type %x (%p -> %p)\n",
662                 MemoryArea->Type,
663                 MA_GetStartingAddress(MemoryArea),
664                 MA_GetEndingAddress(MemoryArea));
665     }
666 
667     if (!FromMdl)
668     {
669         MmUnlockAddressSpace(AddressSpace);
670     }
671 
672     return Status;
673 }
674 
675 /*
676 
677 This is the outer fault handler mentioned in the description of
678 MmpSectionAccsesFaultInner.  It increments a fault depth count in the current
679 thread.
680 
681 In the ultimate form of this code, the lower fault handler will optionally
682 use the count to keep the kernel stack from overflowing.
683 
684 */
685 
686 NTSTATUS
687 NTAPI
688 MmAccessFaultCacheSection(KPROCESSOR_MODE Mode,
689                           ULONG_PTR Address,
690                           BOOLEAN FromMdl)
691 {
692     PETHREAD Thread;
693     PMMSUPPORT AddressSpace;
694     NTSTATUS Status;
695 
696     DPRINT("MmpAccessFault(Mode %d, Address %Ix)\n", Mode, Address);
697 
698     Thread = PsGetCurrentThread();
699 
700     if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
701     {
702         DPRINT1("Page fault at high IRQL %u, address %Ix\n",
703                 KeGetCurrentIrql(),
704                 Address);
705         return STATUS_UNSUCCESSFUL;
706     }
707 
708     /* Find the memory area for the faulting address */
709     if (Address >= (ULONG_PTR)MmSystemRangeStart)
710     {
711         /* Check permissions */
712         if (Mode != KernelMode)
713         {
714             DPRINT1("Address: %p:%Ix\n", PsGetCurrentProcess(), Address);
715             return STATUS_ACCESS_VIOLATION;
716         }
717         AddressSpace = MmGetKernelAddressSpace();
718     }
719     else
720     {
721         AddressSpace = &PsGetCurrentProcess()->Vm;
722     }
723 
724     Thread->ActiveFaultCount++;
725     Status = MmpSectionAccessFaultInner(Mode,
726                                         AddressSpace,
727                                         Address,
728                                         FromMdl,
729                                         Thread);
730     Thread->ActiveFaultCount--;
731 
732     return Status;
733 }
734 
735 /*
736 
737 As above, this code seperates the active part of fault handling from a carrier
738 that can use the thread's active fault count to determine whether a work item
739 is required.  Also as above, this function repeatedly calls the active not
740 present fault handler until a clear success or failure is received, using a
741 return of STATUS_MORE_PROCESSING_REQUIRED or STATUS_SUCCESS + 1.
742 
743 */
744 
745 NTSTATUS
746 NTAPI
747 MmNotPresentFaultCacheSectionInner(KPROCESSOR_MODE Mode,
748                                    PMMSUPPORT AddressSpace,
749                                    ULONG_PTR Address,
750                                    BOOLEAN FromMdl,
751                                    PETHREAD Thread)
752 {
753     BOOLEAN Locked = FromMdl;
754     PMEMORY_AREA MemoryArea;
755     MM_REQUIRED_RESOURCES Resources = { 0 };
756     WORK_QUEUE_WITH_CONTEXT Context;
757     NTSTATUS Status = STATUS_SUCCESS;
758 
759     RtlZeroMemory(&Context, sizeof(WORK_QUEUE_WITH_CONTEXT));
760 
761     if (!FromMdl)
762     {
763         MmLockAddressSpace(AddressSpace);
764     }
765 
766     /* Call the memory area specific fault handler */
767     do
768     {
769         MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, (PVOID)Address);
770         if (MemoryArea == NULL || MemoryArea->DeleteInProgress)
771         {
772             Status = STATUS_ACCESS_VIOLATION;
773             if (MemoryArea)
774             {
775                 DPRINT1("Type %x DIP %x\n",
776                         MemoryArea->Type,
777                         MemoryArea->DeleteInProgress);
778             }
779             else
780             {
781                 DPRINT1("No memory area\n");
782             }
783             DPRINT1("Process %p, Address %Ix\n",
784                     MmGetAddressSpaceOwner(AddressSpace),
785                     Address);
786             break;
787         }
788 
789         DPRINTC("Type %x (%p -> %08Ix -> %p) in %p\n",
790                 MemoryArea->Type,
791                 MA_GetStartingAddress(MemoryArea),
792                 Address,
793                 MA_GetEndingAddress(MemoryArea),
794                 PsGetCurrentThread());
795 
796         Resources.DoAcquisition = NULL;
797 
798         // Note: fault handlers are called with address space locked
799         // We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed
800 
801         Status = MmNotPresentFaultCachePage(AddressSpace,
802                                             MemoryArea,
803                                             (PVOID)Address,
804                                             Locked,
805                                             &Resources);
806 
807         if (!FromMdl)
808         {
809             MmUnlockAddressSpace(AddressSpace);
810         }
811 
812         if (Status == STATUS_SUCCESS)
813         {
814             ; // Nothing
815         }
816         else if (Status == STATUS_SUCCESS + 1)
817         {
818             /* Wait page ... */
819             DPRINT("Waiting for %Ix\n", Address);
820             MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace), Address);
821             DPRINT("Done waiting for %Ix\n", Address);
822             Status = STATUS_MM_RESTART_OPERATION;
823         }
824         else if (Status == STATUS_MM_RESTART_OPERATION)
825         {
826             /* Clean slate */
827             DPRINT("Clear resource\n");
828             RtlZeroMemory(&Resources, sizeof(Resources));
829         }
830         else if (Status == STATUS_MORE_PROCESSING_REQUIRED)
831         {
832             if (Thread->ActiveFaultCount > 2)
833             {
834                 DPRINTC("Already fault handling ... going to work item (%Ix)\n", Address);
835                 Context.AddressSpace = AddressSpace;
836                 Context.MemoryArea = MemoryArea;
837                 Context.Required = &Resources;
838                 KeInitializeEvent(&Context.Wait, NotificationEvent, FALSE);
839 
840                 ExInitializeWorkItem(&Context.WorkItem,
841                                      (PWORKER_THREAD_ROUTINE)MmpFaultWorker,
842                                      &Context);
843 
844                 DPRINT("Queue work item\n");
845                 ExQueueWorkItem(&Context.WorkItem, DelayedWorkQueue);
846                 DPRINT("Wait\n");
847                 KeWaitForSingleObject(&Context.Wait, 0, KernelMode, FALSE, NULL);
848                 Status = Context.Status;
849                 DPRINTC("Status %x\n", Status);
850             }
851             else
852             {
853                 DPRINT("DoAcquisition %p\n", Resources.DoAcquisition);
854 
855                 Status = Resources.DoAcquisition(AddressSpace,
856                                                  MemoryArea,
857                                                  &Resources);
858 
859                 DPRINT("DoAcquisition %p -> %x\n",
860                        Resources.DoAcquisition,
861                        Status);
862             }
863 
864             if (NT_SUCCESS(Status))
865             {
866                 Status = STATUS_MM_RESTART_OPERATION;
867             }
868         }
869         else if (NT_SUCCESS(Status))
870         {
871             ASSERT(FALSE);
872         }
873 
874         if (!FromMdl)
875         {
876             MmLockAddressSpace(AddressSpace);
877         }
878     }
879     while (Status == STATUS_MM_RESTART_OPERATION);
880 
881     DPRINTC("Completed page fault handling: %p:%Ix %x\n",
882             MmGetAddressSpaceOwner(AddressSpace),
883             Address,
884             Status);
885 
886     if (!FromMdl)
887     {
888         MmUnlockAddressSpace(AddressSpace);
889     }
890 
891     MiSetPageEvent(MmGetAddressSpaceOwner(AddressSpace), Address);
892     DPRINT("Done %x\n", Status);
893 
894     return Status;
895 }
896 
897 /*
898 
899 Call the inner not present fault handler, keeping track of the fault count.
900 In the ultimate form of this code, optionally use a worker thread the handle
901 the fault in order to sidestep stack overflow in the multiple fault case.
902 
903 */
904 
905 NTSTATUS
906 NTAPI
907 MmNotPresentFaultCacheSection(KPROCESSOR_MODE Mode,
908                               ULONG_PTR Address,
909                               BOOLEAN FromMdl)
910 {
911     PETHREAD Thread;
912     PMMSUPPORT AddressSpace;
913     NTSTATUS Status;
914 
915     Address &= ~(PAGE_SIZE - 1);
916     DPRINT("MmNotPresentFault(Mode %d, Address %Ix)\n", Mode, Address);
917 
918     Thread = PsGetCurrentThread();
919 
920     if (KeGetCurrentIrql() >= DISPATCH_LEVEL)
921     {
922         DPRINT1("Page fault at high IRQL %u, address %Ix\n",
923                 KeGetCurrentIrql(),
924                 Address);
925 
926         ASSERT(FALSE);
927         return STATUS_UNSUCCESSFUL;
928     }
929 
930     /* Find the memory area for the faulting address */
931     if (Address >= (ULONG_PTR)MmSystemRangeStart)
932     {
933         /* Check permissions */
934         if (Mode != KernelMode)
935         {
936             DPRINTC("Address: %x\n", Address);
937             return STATUS_ACCESS_VIOLATION;
938         }
939         AddressSpace = MmGetKernelAddressSpace();
940     }
941     else
942     {
943         AddressSpace = &PsGetCurrentProcess()->Vm;
944     }
945 
946     Thread->ActiveFaultCount++;
947     Status = MmNotPresentFaultCacheSectionInner(Mode,
948                                                 AddressSpace,
949                                                 Address,
950                                                 FromMdl,
951                                                 Thread);
952     Thread->ActiveFaultCount--;
953 
954     ASSERT(Status != STATUS_UNSUCCESSFUL);
955     ASSERT(Status != STATUS_INVALID_PARAMETER);
956     DPRINT("MmAccessFault %p:%Ix -> %x\n",
957            MmGetAddressSpaceOwner(AddressSpace),
958            Address,
959            Status);
960 
961     return Status;
962 }
963