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 *****************************************************************/
53 #define DPRINTC DPRINT
55 extern KEVENT MmWaitPageEvent
;
59 MmNotPresentFaultCachePage
60 (PMMSUPPORT AddressSpace
,
61 MEMORY_AREA
* MemoryArea
,
64 PMM_REQUIRED_RESOURCES Required
)
69 PMM_CACHE_SECTION_SEGMENT Segment
;
70 LARGE_INTEGER FileOffset
, TotalOffset
;
73 PEPROCESS Process
= MmGetAddressSpaceOwner(AddressSpace
);
75 DPRINT("Not Present: %p %p (%p-%p)\n", AddressSpace
, Address
, MemoryArea
->StartingAddress
, MemoryArea
->EndingAddress
);
78 * There is a window between taking the page fault and locking the
79 * address space when another thread could load the page so we check
82 if (MmIsPagePresent(Process
, Address
))
85 return(STATUS_SUCCESS
);
88 PAddress
= MM_ROUND_DOWN(Address
, PAGE_SIZE
);
89 TotalOffset
.QuadPart
= (ULONG_PTR
)PAddress
- (ULONG_PTR
)MemoryArea
->StartingAddress
;
91 Segment
= MemoryArea
->Data
.CacheData
.Segment
;
93 TotalOffset
.QuadPart
+= MemoryArea
->Data
.CacheData
.ViewOffset
.QuadPart
;
94 FileOffset
= TotalOffset
;
96 //Consumer = (Segment->Flags & MM_DATAFILE_SEGMENT) ? MC_CACHE : MC_USER;
99 if (Segment
->FileObject
)
101 DPRINT("FileName %wZ\n", &Segment
->FileObject
->FileName
);
104 DPRINT("Total Offset %08x%08x\n", TotalOffset
.HighPart
, TotalOffset
.LowPart
);
109 MmLockCacheSectionSegment(Segment
);
112 * Get the entry corresponding to the offset within the section
114 Entry
= MiGetPageEntryCacheSectionSegment(Segment
, &TotalOffset
);
116 Attributes
= PAGE_READONLY
;
118 if (Required
->State
&& Required
->Page
[0])
120 DPRINT("Have file and page, set page %x in section @ %x #\n", Required
->Page
[0], TotalOffset
.LowPart
);
122 if (Required
->SwapEntry
)
123 MmSetSavedSwapEntryPage(Required
->Page
[0], Required
->SwapEntry
);
125 if (Required
->State
& 2)
127 DPRINT("Set in section @ %x\n", TotalOffset
.LowPart
);
128 Status
= MiSetPageEntryCacheSectionSegment
129 (Segment
, &TotalOffset
, Entry
= MAKE_PFN_SSE(Required
->Page
[0]));
130 if (!NT_SUCCESS(Status
))
132 MmReleasePageMemoryConsumer(MC_CACHE
, Required
->Page
[0]);
134 MmUnlockCacheSectionSegment(Segment
);
135 MiSetPageEvent(Process
, Address
);
136 DPRINT("Status %x\n", Status
);
137 return STATUS_MM_RESTART_OPERATION
;
141 DPRINT("Set %x in address space @ %x\n", Required
->Page
[0], Address
);
142 Status
= MmCreateVirtualMapping(Process
, Address
, Attributes
, Required
->Page
, 1);
143 if (NT_SUCCESS(Status
))
145 MmInsertRmap(Required
->Page
[0], Process
, Address
);
149 // Drop the reference for our address space ...
150 MmReleasePageMemoryConsumer(MC_CACHE
, Required
->Page
[0]);
152 MmUnlockCacheSectionSegment(Segment
);
153 DPRINTC("XXX Set Event %x\n", Status
);
154 MiSetPageEvent(Process
, Address
);
155 DPRINT("Status %x\n", Status
);
161 PFN_NUMBER Page
= PFN_FROM_SSE(Entry
);
162 DPRINT("Take reference to page %x #\n", Page
);
164 MmReferencePage(Page
);
166 Status
= MmCreateVirtualMapping(Process
, Address
, Attributes
, &Page
, 1);
167 if (NT_SUCCESS(Status
))
169 MmInsertRmap(Page
, Process
, Address
);
171 DPRINT("XXX Set Event %x\n", Status
);
172 MiSetPageEvent(Process
, Address
);
173 MmUnlockCacheSectionSegment(Segment
);
174 DPRINT("Status %x\n", Status
);
179 DPRINT("Get page into section\n");
181 * If the entry is zero (and it can't change because we have
182 * locked the segment) then we need to load the page.
184 //DPRINT1("Read from file %08x %wZ\n", FileOffset.LowPart, &Section->FileObject->FileName);
186 Required
->Context
= Segment
->FileObject
;
187 Required
->Consumer
= Consumer
;
188 Required
->FileOffset
= FileOffset
;
189 Required
->Amount
= PAGE_SIZE
;
190 Required
->DoAcquisition
= MiReadFilePage
;
191 MiSetPageEntryCacheSectionSegment(Segment
, &TotalOffset
, MAKE_SWAP_SSE(MM_WAIT_ENTRY
));
192 MmUnlockCacheSectionSegment(Segment
);
193 return STATUS_MORE_PROCESSING_REQUIRED
;
196 return STATUS_ACCESS_VIOLATION
;
201 MiCopyPageToPage(PFN_NUMBER DestPage
, PFN_NUMBER SrcPage
)
205 PVOID TempAddress
, TempSource
;
207 Process
= PsGetCurrentProcess();
208 TempAddress
= MiMapPageInHyperSpace(Process
, DestPage
, &Irql
);
209 if (TempAddress
== NULL
)
211 return(STATUS_NO_MEMORY
);
213 TempSource
= MiMapPageInHyperSpace(Process
, SrcPage
, &Irql2
);
215 MiUnmapPageInHyperSpace(Process
, TempAddress
, Irql
);
216 return(STATUS_NO_MEMORY
);
219 memcpy(TempAddress
, TempSource
, PAGE_SIZE
);
221 MiUnmapPageInHyperSpace(Process
, TempSource
, Irql2
);
222 MiUnmapPageInHyperSpace(Process
, TempAddress
, Irql
);
223 return(STATUS_SUCCESS
);
228 MiCowCacheSectionPage
229 (PMMSUPPORT AddressSpace
,
230 PMEMORY_AREA MemoryArea
,
233 PMM_REQUIRED_RESOURCES Required
)
235 PMM_CACHE_SECTION_SEGMENT Segment
;
236 PFN_NUMBER NewPage
, OldPage
;
239 LARGE_INTEGER Offset
;
240 PEPROCESS Process
= MmGetAddressSpaceOwner(AddressSpace
);
242 DPRINT("MmAccessFaultSectionView(%x, %x, %x, %x)\n", AddressSpace
, MemoryArea
, Address
, Locked
);
244 Segment
= MemoryArea
->Data
.CacheData
.Segment
;
249 MmLockCacheSectionSegment(Segment
);
252 * Find the offset of the page
254 PAddress
= MM_ROUND_DOWN(Address
, PAGE_SIZE
);
255 Offset
.QuadPart
= (ULONG_PTR
)PAddress
- (ULONG_PTR
)MemoryArea
->StartingAddress
+
256 MemoryArea
->Data
.CacheData
.ViewOffset
.QuadPart
;
258 #if 0 // XXX Cache sections are not CoW. For now, treat all access violations this way.
259 if ((!Segment
->WriteCopy
&&
260 !MemoryArea
->Data
.CacheData
.WriteCopyView
) ||
261 Segment
->Image
.Characteristics
& IMAGE_SCN_MEM_SHARED
)
264 #if 0 // XXX Cache sections don't have regions at present, which streamlines things
265 if (Region
->Protect
== PAGE_READWRITE
||
266 Region
->Protect
== PAGE_EXECUTE_READWRITE
)
270 DPRINTC("setting non-cow page %x %x:%x offset %x (%x) to writable\n", Segment
, Process
, PAddress
, Offset
.u
.LowPart
, MmGetPfnForProcess(Process
, Address
));
271 if (Segment
->FileObject
)
273 DPRINTC("file %wZ\n", &Segment
->FileObject
->FileName
);
275 Entry
= MiGetPageEntryCacheSectionSegment(Segment
, &Offset
);
276 DPRINT("Entry %x\n", Entry
);
278 !IS_SWAP_FROM_SSE(Entry
) &&
279 PFN_FROM_SSE(Entry
) == MmGetPfnForProcess(Process
, Address
)) {
280 MiSetPageEntryCacheSectionSegment(Segment
, &Offset
, DIRTY_SSE(Entry
));
282 MmSetPageProtect(Process
, PAddress
, PAGE_READWRITE
);
283 MmUnlockCacheSectionSegment(Segment
);
285 return STATUS_SUCCESS
;
290 DPRINT("Not supposed to be writable\n");
291 MmUnlockCacheSectionSegment(Segment
);
292 return STATUS_ACCESS_VIOLATION
;
297 if (!Required
->Page
[0])
300 if (MmIsPageSwapEntry(Process
, Address
))
302 MmGetPageFileMapping(Process
, Address
, &SwapEntry
);
303 MmUnlockCacheSectionSegment(Segment
);
304 if (SwapEntry
== MM_WAIT_ENTRY
)
305 return STATUS_SUCCESS
+ 1; // Wait ... somebody else is getting it right now
307 return STATUS_SUCCESS
; // Nonwait swap entry ... handle elsewhere
309 Required
->Page
[1] = MmGetPfnForProcess(Process
, Address
);
310 Required
->Consumer
= MC_CACHE
;
311 Required
->Amount
= 1;
312 Required
->File
= __FILE__
;
313 Required
->Line
= __LINE__
;
314 Required
->DoAcquisition
= MiGetOnePage
;
315 MmCreatePageFileMapping(Process
, Address
, MM_WAIT_ENTRY
);
316 MmUnlockCacheSectionSegment(Segment
);
317 return STATUS_MORE_PROCESSING_REQUIRED
;
320 NewPage
= Required
->Page
[0];
321 OldPage
= Required
->Page
[1];
323 DPRINT("Allocated page %x\n", NewPage
);
326 * Unshare the old page.
328 MmDeleteRmap(OldPage
, Process
, PAddress
);
334 MiCopyPageToPage(NewPage
, OldPage
);
337 * Set the PTE to point to the new page
339 Status
= MmCreateVirtualMapping
340 (Process
, Address
, PAGE_READWRITE
, &NewPage
, 1);
342 if (!NT_SUCCESS(Status
))
344 DPRINT1("MmCreateVirtualMapping failed, not out of memory\n");
346 MmUnlockCacheSectionSegment(Segment
);
350 MmInsertRmap(NewPage
, Process
, PAddress
);
351 MmReleasePageMemoryConsumer(MC_CACHE
, OldPage
);
352 MmUnlockCacheSectionSegment(Segment
);
354 DPRINT("Address 0x%.8X\n", Address
);
355 return(STATUS_SUCCESS
);
358 KEVENT MmWaitPageEvent
;
360 typedef struct _WORK_QUEUE_WITH_CONTEXT
362 WORK_QUEUE_ITEM WorkItem
;
363 PMMSUPPORT AddressSpace
;
364 PMEMORY_AREA MemoryArea
;
365 PMM_REQUIRED_RESOURCES Required
;
368 AcquireResource DoAcquisition
;
369 } WORK_QUEUE_WITH_CONTEXT
, *PWORK_QUEUE_WITH_CONTEXT
;
374 (PWORK_QUEUE_WITH_CONTEXT WorkItem
)
376 DPRINT("Calling work\n");
378 WorkItem
->Required
->DoAcquisition
379 (WorkItem
->AddressSpace
,
380 WorkItem
->MemoryArea
,
382 DPRINT("Status %x\n", WorkItem
->Status
);
383 KeSetEvent(&WorkItem
->Wait
, IO_NO_INCREMENT
, FALSE
);
388 MmpSectionAccessFaultInner
389 (KPROCESSOR_MODE Mode
,
390 PMMSUPPORT AddressSpace
,
395 MEMORY_AREA
* MemoryArea
;
397 BOOLEAN Locked
= FromMdl
;
398 MM_REQUIRED_RESOURCES Resources
= { 0 };
400 DPRINT("MmAccessFault(Mode %d, Address %x)\n", Mode
, Address
);
402 if (KeGetCurrentIrql() >= DISPATCH_LEVEL
)
404 DPRINT1("Page fault at high IRQL was %d\n", KeGetCurrentIrql());
405 return(STATUS_UNSUCCESSFUL
);
409 * Find the memory area for the faulting address
411 if (Address
>= (ULONG_PTR
)MmSystemRangeStart
)
416 if (Mode
!= KernelMode
)
418 DPRINT("MmAccessFault(Mode %d, Address %x)\n", Mode
, Address
);
419 return(STATUS_ACCESS_VIOLATION
);
421 AddressSpace
= MmGetKernelAddressSpace();
425 AddressSpace
= &PsGetCurrentProcess()->Vm
;
430 MmLockAddressSpace(AddressSpace
);
435 MemoryArea
= MmLocateMemoryAreaByAddress(AddressSpace
, (PVOID
)Address
);
436 if (MemoryArea
== NULL
||
437 MemoryArea
->DeleteInProgress
)
441 MmUnlockAddressSpace(AddressSpace
);
443 DPRINT("Address: %x\n", Address
);
444 return (STATUS_ACCESS_VIOLATION
);
448 ("Type %x (%x -> %x)\n",
450 MemoryArea
->StartingAddress
,
451 MemoryArea
->EndingAddress
);
453 Resources
.DoAcquisition
= NULL
;
455 // Note: fault handlers are called with address space locked
456 // We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed
457 Status
= MiCowCacheSectionPage
458 (AddressSpace
, MemoryArea
, (PVOID
)Address
, Locked
, &Resources
);
462 MmUnlockAddressSpace(AddressSpace
);
465 if (Status
== STATUS_SUCCESS
+ 1)
468 DPRINT("Waiting for %x\n", Address
);
469 MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace
), Address
);
470 DPRINT("Restarting fault %x\n", Address
);
471 Status
= STATUS_MM_RESTART_OPERATION
;
473 else if (Status
== STATUS_MM_RESTART_OPERATION
)
476 RtlZeroMemory(&Resources
, sizeof(Resources
));
478 else if (Status
== STATUS_MORE_PROCESSING_REQUIRED
)
480 if (Thread
->ActiveFaultCount
> 0)
482 WORK_QUEUE_WITH_CONTEXT Context
= {0};
483 DPRINT("Already fault handling ... going to work item (%x)\n", Address
);
484 Context
.AddressSpace
= AddressSpace
;
485 Context
.MemoryArea
= MemoryArea
;
486 Context
.Required
= &Resources
;
487 KeInitializeEvent(&Context
.Wait
, NotificationEvent
, FALSE
);
488 ExInitializeWorkItem(&Context
.WorkItem
, (PWORKER_THREAD_ROUTINE
)MmpFaultWorker
, &Context
);
489 DPRINT("Queue work item\n");
490 ExQueueWorkItem(&Context
.WorkItem
, DelayedWorkQueue
);
492 KeWaitForSingleObject(&Context
.Wait
, 0, KernelMode
, FALSE
, NULL
);
493 Status
= Context
.Status
;
494 DPRINT("Status %x\n", Status
);
498 Status
= Resources
.DoAcquisition(AddressSpace
, MemoryArea
, &Resources
);
501 if (NT_SUCCESS(Status
))
503 Status
= STATUS_MM_RESTART_OPERATION
;
509 MmLockAddressSpace(AddressSpace
);
512 while (Status
== STATUS_MM_RESTART_OPERATION
);
514 if (!NT_SUCCESS(Status
) && MemoryArea
->Type
== 1)
516 DPRINT1("Completed page fault handling %x %x\n", Address
, Status
);
518 ("Type %x (%x -> %x)\n",
520 MemoryArea
->StartingAddress
,
521 MemoryArea
->EndingAddress
);
526 MmUnlockAddressSpace(AddressSpace
);
534 MmAccessFaultCacheSection
535 (KPROCESSOR_MODE Mode
,
540 PMMSUPPORT AddressSpace
;
543 DPRINT("MmpAccessFault(Mode %d, Address %x)\n", Mode
, Address
);
545 Thread
= PsGetCurrentThread();
547 if (KeGetCurrentIrql() >= DISPATCH_LEVEL
)
549 DPRINT1("Page fault at high IRQL %d, address %x\n", KeGetCurrentIrql(), Address
);
550 return(STATUS_UNSUCCESSFUL
);
554 * Find the memory area for the faulting address
556 if (Address
>= (ULONG_PTR
)MmSystemRangeStart
)
561 if (Mode
!= KernelMode
)
563 DPRINT1("Address: %x:%x\n", PsGetCurrentProcess(), Address
);
564 return(STATUS_ACCESS_VIOLATION
);
566 AddressSpace
= MmGetKernelAddressSpace();
570 AddressSpace
= &PsGetCurrentProcess()->Vm
;
573 Thread
->ActiveFaultCount
++;
574 Status
= MmpSectionAccessFaultInner(Mode
, AddressSpace
, Address
, FromMdl
, Thread
);
575 Thread
->ActiveFaultCount
--;
582 MmNotPresentFaultCacheSectionInner
583 (KPROCESSOR_MODE Mode
,
584 PMMSUPPORT AddressSpace
,
589 BOOLEAN Locked
= FromMdl
;
590 PMEMORY_AREA MemoryArea
;
591 MM_REQUIRED_RESOURCES Resources
= { 0 };
592 NTSTATUS Status
= STATUS_SUCCESS
;
596 MmLockAddressSpace(AddressSpace
);
600 * Call the memory area specific fault handler
604 MemoryArea
= MmLocateMemoryAreaByAddress(AddressSpace
, (PVOID
)Address
);
605 if (MemoryArea
== NULL
|| MemoryArea
->DeleteInProgress
)
607 Status
= STATUS_ACCESS_VIOLATION
;
610 DPRINT1("Type %x DIP %x\n", MemoryArea
->Type
, MemoryArea
->DeleteInProgress
);
614 DPRINT1("No memory area\n");
616 DPRINT1("Process %x, Address %x\n", MmGetAddressSpaceOwner(AddressSpace
), Address
);
621 ("Type %x (%x -> %x -> %x) in %x\n",
623 MemoryArea
->StartingAddress
,
625 MemoryArea
->EndingAddress
,
626 PsGetCurrentThread());
628 Resources
.DoAcquisition
= NULL
;
630 // Note: fault handlers are called with address space locked
631 // We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed
633 Status
= MmNotPresentFaultCachePage
634 (AddressSpace
, MemoryArea
, (PVOID
)Address
, Locked
, &Resources
);
638 MmUnlockAddressSpace(AddressSpace
);
641 if (Status
== STATUS_SUCCESS
)
645 else if (Status
== STATUS_SUCCESS
+ 1)
648 DPRINT("Waiting for %x\n", Address
);
649 MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace
), Address
);
650 DPRINT("Done waiting for %x\n", Address
);
651 Status
= STATUS_MM_RESTART_OPERATION
;
653 else if (Status
== STATUS_MM_RESTART_OPERATION
)
656 DPRINT("Clear resource\n");
657 RtlZeroMemory(&Resources
, sizeof(Resources
));
659 else if (Status
== STATUS_MORE_PROCESSING_REQUIRED
)
661 if (Thread
->ActiveFaultCount
> 1)
663 WORK_QUEUE_WITH_CONTEXT Context
= {0};
664 DPRINTC("Already fault handling ... going to work item (%x)\n", Address
);
665 Context
.AddressSpace
= AddressSpace
;
666 Context
.MemoryArea
= MemoryArea
;
667 Context
.Required
= &Resources
;
668 KeInitializeEvent(&Context
.Wait
, NotificationEvent
, FALSE
);
669 ExInitializeWorkItem(&Context
.WorkItem
, (PWORKER_THREAD_ROUTINE
)MmpFaultWorker
, &Context
);
670 DPRINT("Queue work item\n");
671 ExQueueWorkItem(&Context
.WorkItem
, DelayedWorkQueue
);
673 KeWaitForSingleObject(&Context
.Wait
, 0, KernelMode
, FALSE
, NULL
);
674 Status
= Context
.Status
;
675 DPRINTC("Status %x\n", Status
);
679 DPRINT("DoAcquisition %x\n", Resources
.DoAcquisition
);
680 Status
= Resources
.DoAcquisition
681 (AddressSpace
, MemoryArea
, &Resources
);
682 DPRINT("DoAcquisition %x -> %x\n", Resources
.DoAcquisition
, Status
);
685 if (NT_SUCCESS(Status
))
687 Status
= STATUS_MM_RESTART_OPERATION
;
690 else if (NT_SUCCESS(Status
))
697 MmLockAddressSpace(AddressSpace
);
700 while (Status
== STATUS_MM_RESTART_OPERATION
);
702 DPRINTC("Completed page fault handling: %x:%x %x\n", MmGetAddressSpaceOwner(AddressSpace
), Address
, Status
);
705 MmUnlockAddressSpace(AddressSpace
);
708 MiSetPageEvent(MmGetAddressSpaceOwner(AddressSpace
), Address
);
709 DPRINT("Done %x\n", Status
);
716 MmNotPresentFaultCacheSection
717 (KPROCESSOR_MODE Mode
,
722 PMMSUPPORT AddressSpace
;
725 Address
&= ~(PAGE_SIZE
- 1);
726 DPRINT("MmNotPresentFault(Mode %d, Address %x)\n", Mode
, Address
);
728 Thread
= PsGetCurrentThread();
730 if (KeGetCurrentIrql() >= DISPATCH_LEVEL
)
732 DPRINT1("Page fault at high IRQL %d, address %x\n", KeGetCurrentIrql(), Address
);
734 return(STATUS_UNSUCCESSFUL
);
738 * Find the memory area for the faulting address
740 if (Address
>= (ULONG_PTR
)MmSystemRangeStart
)
745 if (Mode
!= KernelMode
)
747 DPRINTC("Address: %x\n", Address
);
748 return(STATUS_ACCESS_VIOLATION
);
750 AddressSpace
= MmGetKernelAddressSpace();
754 AddressSpace
= &PsGetCurrentProcess()->Vm
;
757 Thread
->ActiveFaultCount
++;
758 Status
= MmNotPresentFaultCacheSectionInner
759 (Mode
, AddressSpace
, Address
, FromMdl
, Thread
);
760 Thread
->ActiveFaultCount
--;
762 ASSERT(Status
!= STATUS_UNSUCCESSFUL
);
763 ASSERT(Status
!= STATUS_INVALID_PARAMETER
);
764 DPRINT("MmAccessFault %x:%x -> %x\n", MmGetAddressSpaceOwner(AddressSpace
), Address
, Status
);