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