2 * Copyright (C) 1998-2005 ReactOS Team (and the authors from the programmers section)
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.
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.
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.
19 * PROJECT: ReactOS kernel
20 * FILE: ntoskrnl/mm/section/fault.c
21 * PURPOSE: Consolidate fault handlers for sections
37 * Thomas Weidenmueller
38 * Gunnar Andre' Dalsnes
46 /* INCLUDES *****************************************************************/
52 #include "../mm/ARM3/miarm.h"
54 #define DPRINTC DPRINT
56 extern KEVENT MmWaitPageEvent
;
57 extern PMMWSL MmWorkingSetList
;
61 MmNotPresentFaultCachePage
62 (PMMSUPPORT AddressSpace
,
63 MEMORY_AREA
* MemoryArea
,
66 PMM_REQUIRED_RESOURCES Required
)
71 PMM_SECTION_SEGMENT Segment
;
72 LARGE_INTEGER FileOffset
, TotalOffset
;
75 PEPROCESS Process
= MmGetAddressSpaceOwner(AddressSpace
);
77 DPRINT("Not Present: %p %p (%p-%p)\n", AddressSpace
, Address
, MemoryArea
->StartingAddress
, MemoryArea
->EndingAddress
);
80 * There is a window between taking the page fault and locking the
81 * address space when another thread could load the page so we check
84 if (MmIsPagePresent(Process
, Address
))
87 return(STATUS_SUCCESS
);
90 PAddress
= MM_ROUND_DOWN(Address
, PAGE_SIZE
);
91 TotalOffset
.QuadPart
= (ULONG_PTR
)PAddress
- (ULONG_PTR
)MemoryArea
->StartingAddress
;
93 Segment
= MemoryArea
->Data
.SectionData
.Segment
;
95 TotalOffset
.QuadPart
+= MemoryArea
->Data
.SectionData
.ViewOffset
.QuadPart
;
96 FileOffset
= TotalOffset
;
98 //Consumer = (Segment->Flags & MM_DATAFILE_SEGMENT) ? MC_CACHE : MC_USER;
101 if (Segment
->FileObject
)
103 DPRINT("FileName %wZ\n", &Segment
->FileObject
->FileName
);
106 DPRINT("Total Offset %08x%08x\n", TotalOffset
.HighPart
, TotalOffset
.LowPart
);
111 MmLockSectionSegment(Segment
);
114 * Get the entry corresponding to the offset within the section
116 Entry
= MmGetPageEntrySectionSegment(Segment
, &TotalOffset
);
118 Attributes
= PAGE_READONLY
;
120 if (Required
->State
&& Required
->Page
[0])
122 DPRINT("Have file and page, set page %x in section @ %x #\n", Required
->Page
[0], TotalOffset
.LowPart
);
124 if (Required
->SwapEntry
)
125 MmSetSavedSwapEntryPage(Required
->Page
[0], Required
->SwapEntry
);
127 if (Required
->State
& 2)
129 DPRINT("Set in section @ %x\n", TotalOffset
.LowPart
);
130 Status
= MmSetPageEntrySectionSegment
131 (Segment
, &TotalOffset
, Entry
= MAKE_PFN_SSE(Required
->Page
[0]));
132 if (!NT_SUCCESS(Status
))
134 MmReleasePageMemoryConsumer(MC_CACHE
, Required
->Page
[0]);
136 MmUnlockSectionSegment(Segment
);
137 MiSetPageEvent(Process
, Address
);
138 DPRINT("Status %x\n", Status
);
139 return STATUS_MM_RESTART_OPERATION
;
143 DPRINT("Set %x in address space @ %x\n", Required
->Page
[0], Address
);
144 Status
= MmCreateVirtualMapping(Process
, Address
, Attributes
, Required
->Page
, 1);
145 #if (_MI_PAGING_LEVELS == 2)
146 /* Reference Page Directory Entry */
147 if(Address
< MmSystemRangeStart
)
149 MmWorkingSetList
->UsedPageTableEntries
[MiGetPdeOffset(Address
)]++;
150 ASSERT(MmWorkingSetList
->UsedPageTableEntries
[MiGetPdeOffset(Address
)] <= PTE_COUNT
);
153 if (NT_SUCCESS(Status
))
155 MmInsertRmap(Required
->Page
[0], Process
, Address
);
159 // Drop the reference for our address space ...
160 MmReleasePageMemoryConsumer(MC_CACHE
, Required
->Page
[0]);
162 MmUnlockSectionSegment(Segment
);
163 DPRINTC("XXX Set Event %x\n", Status
);
164 MiSetPageEvent(Process
, Address
);
165 DPRINT("Status %x\n", Status
);
171 PFN_NUMBER Page
= PFN_FROM_SSE(Entry
);
172 DPRINT("Take reference to page %x #\n", Page
);
174 MmReferencePage(Page
);
176 Status
= MmCreateVirtualMapping(Process
, Address
, Attributes
, &Page
, 1);
177 #if (_MI_PAGING_LEVELS == 2)
178 /* Reference Page Directory Entry */
179 if(Address
< MmSystemRangeStart
)
181 MmWorkingSetList
->UsedPageTableEntries
[MiGetPdeOffset(Address
)]++;
182 ASSERT(MmWorkingSetList
->UsedPageTableEntries
[MiGetPdeOffset(Address
)] <= PTE_COUNT
);
185 if (NT_SUCCESS(Status
))
187 MmInsertRmap(Page
, Process
, Address
);
189 DPRINT("XXX Set Event %x\n", Status
);
190 MiSetPageEvent(Process
, Address
);
191 MmUnlockSectionSegment(Segment
);
192 DPRINT("Status %x\n", Status
);
197 DPRINT("Get page into section\n");
199 * If the entry is zero (and it can't change because we have
200 * locked the segment) then we need to load the page.
202 //DPRINT1("Read from file %08x %wZ\n", FileOffset.LowPart, &Section->FileObject->FileName);
204 Required
->Context
= Segment
->FileObject
;
205 Required
->Consumer
= Consumer
;
206 Required
->FileOffset
= FileOffset
;
207 Required
->Amount
= PAGE_SIZE
;
208 Required
->DoAcquisition
= MiReadFilePage
;
209 MmSetPageEntrySectionSegment(Segment
, &TotalOffset
, MAKE_SWAP_SSE(MM_WAIT_ENTRY
));
210 MmUnlockSectionSegment(Segment
);
211 return STATUS_MORE_PROCESSING_REQUIRED
;
214 return STATUS_ACCESS_VIOLATION
;
219 MiCopyPageToPage(PFN_NUMBER DestPage
, PFN_NUMBER SrcPage
)
223 PVOID TempAddress
, TempSource
;
225 Process
= PsGetCurrentProcess();
226 TempAddress
= MiMapPageInHyperSpace(Process
, DestPage
, &Irql
);
227 if (TempAddress
== NULL
)
229 return(STATUS_NO_MEMORY
);
231 TempSource
= MiMapPageInHyperSpace(Process
, SrcPage
, &Irql2
);
233 MiUnmapPageInHyperSpace(Process
, TempAddress
, Irql
);
234 return(STATUS_NO_MEMORY
);
237 memcpy(TempAddress
, TempSource
, PAGE_SIZE
);
239 MiUnmapPageInHyperSpace(Process
, TempSource
, Irql2
);
240 MiUnmapPageInHyperSpace(Process
, TempAddress
, Irql
);
241 return(STATUS_SUCCESS
);
246 MiCowCacheSectionPage
247 (PMMSUPPORT AddressSpace
,
248 PMEMORY_AREA MemoryArea
,
251 PMM_REQUIRED_RESOURCES Required
)
253 PMM_SECTION_SEGMENT Segment
;
254 PFN_NUMBER NewPage
, OldPage
;
257 LARGE_INTEGER Offset
;
258 PEPROCESS Process
= MmGetAddressSpaceOwner(AddressSpace
);
260 DPRINT("MmAccessFaultSectionView(%x, %x, %x, %x)\n", AddressSpace
, MemoryArea
, Address
, Locked
);
262 Segment
= MemoryArea
->Data
.SectionData
.Segment
;
267 MmLockSectionSegment(Segment
);
270 * Find the offset of the page
272 PAddress
= MM_ROUND_DOWN(Address
, PAGE_SIZE
);
273 Offset
.QuadPart
= (ULONG_PTR
)PAddress
- (ULONG_PTR
)MemoryArea
->StartingAddress
+
274 MemoryArea
->Data
.SectionData
.ViewOffset
.QuadPart
;
276 if (!Segment
->WriteCopy
/*&&
277 !MemoryArea->Data.SectionData.WriteCopyView*/ ||
278 Segment
->Image
.Characteristics
& IMAGE_SCN_MEM_SHARED
)
281 if (Region
->Protect
== PAGE_READWRITE
||
282 Region
->Protect
== PAGE_EXECUTE_READWRITE
)
286 DPRINTC("setting non-cow page %x %x:%x offset %x (%x) to writable\n", Segment
, Process
, PAddress
, Offset
.u
.LowPart
, MmGetPfnForProcess(Process
, Address
));
287 if (Segment
->FileObject
)
289 DPRINTC("file %wZ\n", &Segment
->FileObject
->FileName
);
291 Entry
= MmGetPageEntrySectionSegment(Segment
, &Offset
);
292 DPRINT("Entry %x\n", Entry
);
294 !IS_SWAP_FROM_SSE(Entry
) &&
295 PFN_FROM_SSE(Entry
) == MmGetPfnForProcess(Process
, Address
)) {
296 MmSetPageEntrySectionSegment(Segment
, &Offset
, DIRTY_SSE(Entry
));
298 MmSetPageProtect(Process
, PAddress
, PAGE_READWRITE
);
299 MmUnlockSectionSegment(Segment
);
301 return STATUS_SUCCESS
;
306 DPRINT("Not supposed to be writable\n");
307 MmUnlockSectionSegment(Segment
);
308 return STATUS_ACCESS_VIOLATION
;
313 if (!Required
->Page
[0])
316 if (MmIsPageSwapEntry(Process
, Address
))
318 MmGetPageFileMapping(Process
, Address
, &SwapEntry
);
319 MmUnlockSectionSegment(Segment
);
320 if (SwapEntry
== MM_WAIT_ENTRY
)
321 return STATUS_SUCCESS
+ 1; // Wait ... somebody else is getting it right now
323 return STATUS_SUCCESS
; // Nonwait swap entry ... handle elsewhere
325 Required
->Page
[1] = MmGetPfnForProcess(Process
, Address
);
326 Required
->Consumer
= MC_CACHE
;
327 Required
->Amount
= 1;
328 Required
->File
= __FILE__
;
329 Required
->Line
= __LINE__
;
330 Required
->DoAcquisition
= MiGetOnePage
;
331 MmCreatePageFileMapping(Process
, Address
, MM_WAIT_ENTRY
);
332 MmUnlockSectionSegment(Segment
);
333 return STATUS_MORE_PROCESSING_REQUIRED
;
336 NewPage
= Required
->Page
[0];
337 OldPage
= Required
->Page
[1];
339 DPRINT("Allocated page %x\n", NewPage
);
342 * Unshare the old page.
344 MmDeleteRmap(OldPage
, Process
, PAddress
);
350 MiCopyPageToPage(NewPage
, OldPage
);
353 * Set the PTE to point to the new page
355 Status
= MmCreateVirtualMapping
356 (Process
, Address
, PAGE_READWRITE
, &NewPage
, 1);
358 if (!NT_SUCCESS(Status
))
360 DPRINT1("MmCreateVirtualMapping failed, not out of memory\n");
362 MmUnlockSectionSegment(Segment
);
366 MmInsertRmap(NewPage
, Process
, PAddress
);
367 MmReleasePageMemoryConsumer(MC_CACHE
, OldPage
);
368 MmUnlockSectionSegment(Segment
);
370 DPRINT("Address 0x%.8X\n", Address
);
371 return(STATUS_SUCCESS
);
374 KEVENT MmWaitPageEvent
;
376 typedef struct _WORK_QUEUE_WITH_CONTEXT
378 WORK_QUEUE_ITEM WorkItem
;
379 PMMSUPPORT AddressSpace
;
380 PMEMORY_AREA MemoryArea
;
381 PMM_REQUIRED_RESOURCES Required
;
384 AcquireResource DoAcquisition
;
385 } WORK_QUEUE_WITH_CONTEXT
, *PWORK_QUEUE_WITH_CONTEXT
;
390 (PWORK_QUEUE_WITH_CONTEXT WorkItem
)
392 DPRINT("Calling work\n");
394 WorkItem
->Required
->DoAcquisition
395 (WorkItem
->AddressSpace
,
396 WorkItem
->MemoryArea
,
398 DPRINT("Status %x\n", WorkItem
->Status
);
399 KeSetEvent(&WorkItem
->Wait
, IO_NO_INCREMENT
, FALSE
);
404 MmpSectionAccessFaultInner
405 (KPROCESSOR_MODE Mode
,
406 PMMSUPPORT AddressSpace
,
411 MEMORY_AREA
* MemoryArea
;
413 BOOLEAN Locked
= FromMdl
;
414 MM_REQUIRED_RESOURCES Resources
= { 0 };
415 WORK_QUEUE_WITH_CONTEXT Context
;
417 RtlZeroMemory(&Context
, sizeof(WORK_QUEUE_WITH_CONTEXT
));
419 DPRINT("MmAccessFault(Mode %d, Address %x)\n", Mode
, Address
);
421 if (KeGetCurrentIrql() >= DISPATCH_LEVEL
)
423 DPRINT1("Page fault at high IRQL was %d\n", KeGetCurrentIrql());
424 return(STATUS_UNSUCCESSFUL
);
428 * Find the memory area for the faulting address
430 if (Address
>= (ULONG_PTR
)MmSystemRangeStart
)
435 if (Mode
!= KernelMode
)
437 DPRINT("MmAccessFault(Mode %d, Address %x)\n", Mode
, Address
);
438 return(STATUS_ACCESS_VIOLATION
);
440 AddressSpace
= MmGetKernelAddressSpace();
444 AddressSpace
= &PsGetCurrentProcess()->Vm
;
449 MmLockAddressSpace(AddressSpace
);
454 MemoryArea
= MmLocateMemoryAreaByAddress(AddressSpace
, (PVOID
)Address
);
455 if (MemoryArea
== NULL
||
456 MemoryArea
->DeleteInProgress
)
460 MmUnlockAddressSpace(AddressSpace
);
462 DPRINT("Address: %x\n", Address
);
463 return (STATUS_ACCESS_VIOLATION
);
467 ("Type %x (%x -> %x)\n",
469 MemoryArea
->StartingAddress
,
470 MemoryArea
->EndingAddress
);
472 Resources
.DoAcquisition
= NULL
;
474 // Note: fault handlers are called with address space locked
475 // We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed
476 Status
= MiCowCacheSectionPage
477 (AddressSpace
, MemoryArea
, (PVOID
)Address
, Locked
, &Resources
);
481 MmUnlockAddressSpace(AddressSpace
);
484 if (Status
== STATUS_SUCCESS
+ 1)
487 DPRINT("Waiting for %x\n", Address
);
488 MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace
), Address
);
489 DPRINT("Restarting fault %x\n", Address
);
490 Status
= STATUS_MM_RESTART_OPERATION
;
492 else if (Status
== STATUS_MM_RESTART_OPERATION
)
495 RtlZeroMemory(&Resources
, sizeof(Resources
));
497 else if (Status
== STATUS_MORE_PROCESSING_REQUIRED
)
499 if (Thread
->ActiveFaultCount
> 0)
501 DPRINT("Already fault handling ... going to work item (%x)\n", Address
);
502 Context
.AddressSpace
= AddressSpace
;
503 Context
.MemoryArea
= MemoryArea
;
504 Context
.Required
= &Resources
;
505 KeInitializeEvent(&Context
.Wait
, NotificationEvent
, FALSE
);
506 ExInitializeWorkItem(&Context
.WorkItem
, (PWORKER_THREAD_ROUTINE
)MmpFaultWorker
, &Context
);
507 DPRINT("Queue work item\n");
508 ExQueueWorkItem(&Context
.WorkItem
, DelayedWorkQueue
);
510 KeWaitForSingleObject(&Context
.Wait
, 0, KernelMode
, FALSE
, NULL
);
511 Status
= Context
.Status
;
512 DPRINT("Status %x\n", Status
);
516 Status
= Resources
.DoAcquisition(AddressSpace
, MemoryArea
, &Resources
);
519 if (NT_SUCCESS(Status
))
521 Status
= STATUS_MM_RESTART_OPERATION
;
527 MmLockAddressSpace(AddressSpace
);
530 while (Status
== STATUS_MM_RESTART_OPERATION
);
532 if (!NT_SUCCESS(Status
) && MemoryArea
->Type
== 1)
534 DPRINT1("Completed page fault handling %x %x\n", Address
, Status
);
536 ("Type %x (%x -> %x)\n",
538 MemoryArea
->StartingAddress
,
539 MemoryArea
->EndingAddress
);
544 MmUnlockAddressSpace(AddressSpace
);
552 MmAccessFaultCacheSection
553 (KPROCESSOR_MODE Mode
,
558 PMMSUPPORT AddressSpace
;
561 DPRINT("MmpAccessFault(Mode %d, Address %x)\n", Mode
, Address
);
563 Thread
= PsGetCurrentThread();
565 if (KeGetCurrentIrql() >= DISPATCH_LEVEL
)
567 DPRINT1("Page fault at high IRQL %d, address %x\n", KeGetCurrentIrql(), Address
);
568 return(STATUS_UNSUCCESSFUL
);
572 * Find the memory area for the faulting address
574 if (Address
>= (ULONG_PTR
)MmSystemRangeStart
)
579 if (Mode
!= KernelMode
)
581 DPRINT1("Address: %x:%x\n", PsGetCurrentProcess(), Address
);
582 return(STATUS_ACCESS_VIOLATION
);
584 AddressSpace
= MmGetKernelAddressSpace();
588 AddressSpace
= &PsGetCurrentProcess()->Vm
;
591 Thread
->ActiveFaultCount
++;
592 Status
= MmpSectionAccessFaultInner(Mode
, AddressSpace
, Address
, FromMdl
, Thread
);
593 Thread
->ActiveFaultCount
--;
600 MmNotPresentFaultCacheSectionInner
601 (KPROCESSOR_MODE Mode
,
602 PMMSUPPORT AddressSpace
,
607 BOOLEAN Locked
= FromMdl
;
608 PMEMORY_AREA MemoryArea
;
609 MM_REQUIRED_RESOURCES Resources
= { 0 };
610 WORK_QUEUE_WITH_CONTEXT Context
;
611 NTSTATUS Status
= STATUS_SUCCESS
;
613 RtlZeroMemory(&Context
, sizeof(WORK_QUEUE_WITH_CONTEXT
));
617 MmLockAddressSpace(AddressSpace
);
621 * Call the memory area specific fault handler
625 MemoryArea
= MmLocateMemoryAreaByAddress(AddressSpace
, (PVOID
)Address
);
626 if (MemoryArea
== NULL
|| MemoryArea
->DeleteInProgress
)
628 Status
= STATUS_ACCESS_VIOLATION
;
631 DPRINT1("Type %x DIP %x\n", MemoryArea
->Type
, MemoryArea
->DeleteInProgress
);
635 DPRINT1("No memory area\n");
637 DPRINT1("Process %x, Address %x\n", MmGetAddressSpaceOwner(AddressSpace
), Address
);
642 ("Type %x (%x -> %x -> %x) in %x\n",
644 MemoryArea
->StartingAddress
,
646 MemoryArea
->EndingAddress
,
647 PsGetCurrentThread());
649 Resources
.DoAcquisition
= NULL
;
651 // Note: fault handlers are called with address space locked
652 // We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed
654 Status
= MmNotPresentFaultCachePage
655 (AddressSpace
, MemoryArea
, (PVOID
)Address
, Locked
, &Resources
);
659 MmUnlockAddressSpace(AddressSpace
);
662 if (Status
== STATUS_SUCCESS
)
666 else if (Status
== STATUS_SUCCESS
+ 1)
669 DPRINT("Waiting for %x\n", Address
);
670 MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace
), Address
);
671 DPRINT("Done waiting for %x\n", Address
);
672 Status
= STATUS_MM_RESTART_OPERATION
;
674 else if (Status
== STATUS_MM_RESTART_OPERATION
)
677 DPRINT("Clear resource\n");
678 RtlZeroMemory(&Resources
, sizeof(Resources
));
680 else if (Status
== STATUS_MORE_PROCESSING_REQUIRED
)
682 if (Thread
->ActiveFaultCount
> 2)
684 DPRINTC("Already fault handling ... going to work item (%x)\n", Address
);
685 Context
.AddressSpace
= AddressSpace
;
686 Context
.MemoryArea
= MemoryArea
;
687 Context
.Required
= &Resources
;
688 KeInitializeEvent(&Context
.Wait
, NotificationEvent
, FALSE
);
689 ExInitializeWorkItem(&Context
.WorkItem
, (PWORKER_THREAD_ROUTINE
)MmpFaultWorker
, &Context
);
690 DPRINT("Queue work item\n");
691 ExQueueWorkItem(&Context
.WorkItem
, DelayedWorkQueue
);
693 KeWaitForSingleObject(&Context
.Wait
, 0, KernelMode
, FALSE
, NULL
);
694 Status
= Context
.Status
;
695 DPRINTC("Status %x\n", Status
);
699 DPRINT("DoAcquisition %x\n", Resources
.DoAcquisition
);
700 Status
= Resources
.DoAcquisition
701 (AddressSpace
, MemoryArea
, &Resources
);
702 DPRINT("DoAcquisition %x -> %x\n", Resources
.DoAcquisition
, Status
);
705 if (NT_SUCCESS(Status
))
707 Status
= STATUS_MM_RESTART_OPERATION
;
710 else if (NT_SUCCESS(Status
))
717 MmLockAddressSpace(AddressSpace
);
720 while (Status
== STATUS_MM_RESTART_OPERATION
);
722 DPRINTC("Completed page fault handling: %x:%x %x\n", MmGetAddressSpaceOwner(AddressSpace
), Address
, Status
);
725 MmUnlockAddressSpace(AddressSpace
);
728 MiSetPageEvent(MmGetAddressSpaceOwner(AddressSpace
), Address
);
729 DPRINT("Done %x\n", Status
);
736 MmNotPresentFaultCacheSection
737 (KPROCESSOR_MODE Mode
,
742 PMMSUPPORT AddressSpace
;
745 Address
&= ~(PAGE_SIZE
- 1);
746 DPRINT("MmNotPresentFault(Mode %d, Address %x)\n", Mode
, Address
);
748 Thread
= PsGetCurrentThread();
750 if (KeGetCurrentIrql() >= DISPATCH_LEVEL
)
752 DPRINT1("Page fault at high IRQL %d, address %x\n", KeGetCurrentIrql(), Address
);
754 return(STATUS_UNSUCCESSFUL
);
758 * Find the memory area for the faulting address
760 if (Address
>= (ULONG_PTR
)MmSystemRangeStart
)
765 if (Mode
!= KernelMode
)
767 DPRINTC("Address: %x\n", Address
);
768 return(STATUS_ACCESS_VIOLATION
);
770 AddressSpace
= MmGetKernelAddressSpace();
774 AddressSpace
= &PsGetCurrentProcess()->Vm
;
777 Thread
->ActiveFaultCount
++;
778 Status
= MmNotPresentFaultCacheSectionInner
779 (Mode
, AddressSpace
, Address
, FromMdl
, Thread
);
780 Thread
->ActiveFaultCount
--;
782 ASSERT(Status
!= STATUS_UNSUCCESSFUL
);
783 ASSERT(Status
!= STATUS_INVALID_PARAMETER
);
784 DPRINT("MmAccessFault %x:%x -> %x\n", MmGetAddressSpaceOwner(AddressSpace
), Address
, Status
);