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
)
269 DPRINTC("setting non-cow page %x %x:%x offset %x (%x) to writable\n", Segment
, Process
, PAddress
, Offset
.u
.LowPart
, MmGetPfnForProcess(Process
, Address
));
270 if (Segment
->FileObject
)
272 DPRINTC("file %wZ\n", &Segment
->FileObject
->FileName
);
274 ULONG Entry
= MiGetPageEntryCacheSectionSegment(Segment
, &Offset
);
275 DPRINT("Entry %x\n", Entry
);
277 !IS_SWAP_FROM_SSE(Entry
) &&
278 PFN_FROM_SSE(Entry
) == MmGetPfnForProcess(Process
, Address
)) {
279 MiSetPageEntryCacheSectionSegment(Segment
, &Offset
, DIRTY_SSE(Entry
));
281 MmSetPageProtect(Process
, PAddress
, PAGE_READWRITE
);
282 MmUnlockCacheSectionSegment(Segment
);
284 return STATUS_SUCCESS
;
289 DPRINT("Not supposed to be writable\n");
290 MmUnlockCacheSectionSegment(Segment
);
291 return STATUS_ACCESS_VIOLATION
;
296 if (!Required
->Page
[0])
299 if (MmIsPageSwapEntry(Process
, Address
))
301 MmGetPageFileMapping(Process
, Address
, &SwapEntry
);
302 MmUnlockCacheSectionSegment(Segment
);
303 if (SwapEntry
== MM_WAIT_ENTRY
)
304 return STATUS_SUCCESS
+ 1; // Wait ... somebody else is getting it right now
306 return STATUS_SUCCESS
; // Nonwait swap entry ... handle elsewhere
308 Required
->Page
[1] = MmGetPfnForProcess(Process
, Address
);
309 Required
->Consumer
= MC_CACHE
;
310 Required
->Amount
= 1;
311 Required
->File
= __FILE__
;
312 Required
->Line
= __LINE__
;
313 Required
->DoAcquisition
= MiGetOnePage
;
314 MmCreatePageFileMapping(Process
, Address
, MM_WAIT_ENTRY
);
315 MmUnlockCacheSectionSegment(Segment
);
316 return STATUS_MORE_PROCESSING_REQUIRED
;
319 NewPage
= Required
->Page
[0];
320 OldPage
= Required
->Page
[1];
322 DPRINT("Allocated page %x\n", NewPage
);
325 * Unshare the old page.
327 MmDeleteRmap(OldPage
, Process
, PAddress
);
333 MiCopyPageToPage(NewPage
, OldPage
);
336 * Set the PTE to point to the new page
338 Status
= MmCreateVirtualMapping
339 (Process
, Address
, PAGE_READWRITE
, &NewPage
, 1);
341 if (!NT_SUCCESS(Status
))
343 DPRINT1("MmCreateVirtualMapping failed, not out of memory\n");
345 MmUnlockCacheSectionSegment(Segment
);
349 MmInsertRmap(NewPage
, Process
, PAddress
);
350 MmReleasePageMemoryConsumer(MC_CACHE
, OldPage
);
351 MmUnlockCacheSectionSegment(Segment
);
353 DPRINT("Address 0x%.8X\n", Address
);
354 return(STATUS_SUCCESS
);
357 KEVENT MmWaitPageEvent
;
359 typedef struct _WORK_QUEUE_WITH_CONTEXT
361 WORK_QUEUE_ITEM WorkItem
;
362 PMMSUPPORT AddressSpace
;
363 PMEMORY_AREA MemoryArea
;
364 PMM_REQUIRED_RESOURCES Required
;
367 AcquireResource DoAcquisition
;
368 } WORK_QUEUE_WITH_CONTEXT
, *PWORK_QUEUE_WITH_CONTEXT
;
373 (PWORK_QUEUE_WITH_CONTEXT WorkItem
)
375 DPRINT("Calling work\n");
377 WorkItem
->Required
->DoAcquisition
378 (WorkItem
->AddressSpace
,
379 WorkItem
->MemoryArea
,
381 DPRINT("Status %x\n", WorkItem
->Status
);
382 KeSetEvent(&WorkItem
->Wait
, IO_NO_INCREMENT
, FALSE
);
387 MmpSectionAccessFaultInner
388 (KPROCESSOR_MODE Mode
,
389 PMMSUPPORT AddressSpace
,
394 MEMORY_AREA
* MemoryArea
;
396 BOOLEAN Locked
= FromMdl
;
397 MM_REQUIRED_RESOURCES Resources
= { 0 };
399 DPRINT("MmAccessFault(Mode %d, Address %x)\n", Mode
, Address
);
401 if (KeGetCurrentIrql() >= DISPATCH_LEVEL
)
403 DPRINT1("Page fault at high IRQL was %d\n", KeGetCurrentIrql());
404 return(STATUS_UNSUCCESSFUL
);
408 * Find the memory area for the faulting address
410 if (Address
>= (ULONG_PTR
)MmSystemRangeStart
)
415 if (Mode
!= KernelMode
)
417 DPRINT("MmAccessFault(Mode %d, Address %x)\n", Mode
, Address
);
418 return(STATUS_ACCESS_VIOLATION
);
420 AddressSpace
= MmGetKernelAddressSpace();
424 AddressSpace
= &PsGetCurrentProcess()->Vm
;
429 MmLockAddressSpace(AddressSpace
);
434 MemoryArea
= MmLocateMemoryAreaByAddress(AddressSpace
, (PVOID
)Address
);
435 if (MemoryArea
== NULL
||
436 MemoryArea
->DeleteInProgress
)
440 MmUnlockAddressSpace(AddressSpace
);
442 DPRINT("Address: %x\n", Address
);
443 return (STATUS_ACCESS_VIOLATION
);
447 ("Type %x (%x -> %x)\n",
449 MemoryArea
->StartingAddress
,
450 MemoryArea
->EndingAddress
);
452 Resources
.DoAcquisition
= NULL
;
454 // Note: fault handlers are called with address space locked
455 // We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed
456 Status
= MiCowCacheSectionPage
457 (AddressSpace
, MemoryArea
, (PVOID
)Address
, Locked
, &Resources
);
461 MmUnlockAddressSpace(AddressSpace
);
464 if (Status
== STATUS_SUCCESS
+ 1)
467 DPRINT("Waiting for %x\n", Address
);
468 MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace
), Address
);
469 DPRINT("Restarting fault %x\n", Address
);
470 Status
= STATUS_MM_RESTART_OPERATION
;
472 else if (Status
== STATUS_MM_RESTART_OPERATION
)
475 RtlZeroMemory(&Resources
, sizeof(Resources
));
477 else if (Status
== STATUS_MORE_PROCESSING_REQUIRED
)
479 if (Thread
->ActiveFaultCount
> 0)
481 WORK_QUEUE_WITH_CONTEXT Context
= { };
482 DPRINT("Already fault handling ... going to work item (%x)\n", Address
);
483 Context
.AddressSpace
= AddressSpace
;
484 Context
.MemoryArea
= MemoryArea
;
485 Context
.Required
= &Resources
;
486 KeInitializeEvent(&Context
.Wait
, NotificationEvent
, FALSE
);
487 ExInitializeWorkItem(&Context
.WorkItem
, (PWORKER_THREAD_ROUTINE
)MmpFaultWorker
, &Context
);
488 DPRINT("Queue work item\n");
489 ExQueueWorkItem(&Context
.WorkItem
, DelayedWorkQueue
);
491 KeWaitForSingleObject(&Context
.Wait
, 0, KernelMode
, FALSE
, NULL
);
492 Status
= Context
.Status
;
493 DPRINT("Status %x\n", Status
);
497 Status
= Resources
.DoAcquisition(AddressSpace
, MemoryArea
, &Resources
);
500 if (NT_SUCCESS(Status
))
502 Status
= STATUS_MM_RESTART_OPERATION
;
508 MmLockAddressSpace(AddressSpace
);
511 while (Status
== STATUS_MM_RESTART_OPERATION
);
513 if (!NT_SUCCESS(Status
) && MemoryArea
->Type
== 1)
515 DPRINT1("Completed page fault handling %x %x\n", Address
, Status
);
517 ("Type %x (%x -> %x)\n",
519 MemoryArea
->StartingAddress
,
520 MemoryArea
->EndingAddress
);
525 MmUnlockAddressSpace(AddressSpace
);
533 MmAccessFaultCacheSection
534 (KPROCESSOR_MODE Mode
,
539 PMMSUPPORT AddressSpace
;
542 DPRINT("MmpAccessFault(Mode %d, Address %x)\n", Mode
, Address
);
544 Thread
= PsGetCurrentThread();
546 if (KeGetCurrentIrql() >= DISPATCH_LEVEL
)
548 DPRINT1("Page fault at high IRQL %d, address %x\n", KeGetCurrentIrql(), Address
);
549 return(STATUS_UNSUCCESSFUL
);
553 * Find the memory area for the faulting address
555 if (Address
>= (ULONG_PTR
)MmSystemRangeStart
)
560 if (Mode
!= KernelMode
)
562 DPRINT1("Address: %x:%x\n", PsGetCurrentProcess(), Address
);
563 return(STATUS_ACCESS_VIOLATION
);
565 AddressSpace
= MmGetKernelAddressSpace();
569 AddressSpace
= &PsGetCurrentProcess()->Vm
;
572 Thread
->ActiveFaultCount
++;
573 Status
= MmpSectionAccessFaultInner(Mode
, AddressSpace
, Address
, FromMdl
, Thread
);
574 Thread
->ActiveFaultCount
--;
581 MmNotPresentFaultCacheSectionInner
582 (KPROCESSOR_MODE Mode
,
583 PMMSUPPORT AddressSpace
,
588 BOOLEAN Locked
= FromMdl
;
589 PMEMORY_AREA MemoryArea
;
590 MM_REQUIRED_RESOURCES Resources
= { 0 };
591 NTSTATUS Status
= STATUS_SUCCESS
;
595 MmLockAddressSpace(AddressSpace
);
599 * Call the memory area specific fault handler
603 MemoryArea
= MmLocateMemoryAreaByAddress(AddressSpace
, (PVOID
)Address
);
604 if (MemoryArea
== NULL
|| MemoryArea
->DeleteInProgress
)
606 Status
= STATUS_ACCESS_VIOLATION
;
609 DPRINT1("Type %x DIP %x\n", MemoryArea
->Type
, MemoryArea
->DeleteInProgress
);
613 DPRINT1("No memory area\n");
615 DPRINT1("Process %x, Address %x\n", MmGetAddressSpaceOwner(AddressSpace
), Address
);
620 ("Type %x (%x -> %x -> %x) in %x\n",
622 MemoryArea
->StartingAddress
,
624 MemoryArea
->EndingAddress
,
625 PsGetCurrentThread());
627 Resources
.DoAcquisition
= NULL
;
629 // Note: fault handlers are called with address space locked
630 // We return STATUS_MORE_PROCESSING_REQUIRED if anything is needed
632 Status
= MmNotPresentFaultCachePage
633 (AddressSpace
, MemoryArea
, (PVOID
)Address
, Locked
, &Resources
);
637 MmUnlockAddressSpace(AddressSpace
);
640 if (Status
== STATUS_SUCCESS
)
644 else if (Status
== STATUS_SUCCESS
+ 1)
647 DPRINT("Waiting for %x\n", Address
);
648 MiWaitForPageEvent(MmGetAddressSpaceOwner(AddressSpace
), Address
);
649 DPRINT("Done waiting for %x\n", Address
);
650 Status
= STATUS_MM_RESTART_OPERATION
;
652 else if (Status
== STATUS_MM_RESTART_OPERATION
)
655 DPRINT("Clear resource\n");
656 RtlZeroMemory(&Resources
, sizeof(Resources
));
658 else if (Status
== STATUS_MORE_PROCESSING_REQUIRED
)
660 if (Thread
->ActiveFaultCount
> 1)
662 WORK_QUEUE_WITH_CONTEXT Context
= { };
663 DPRINTC("Already fault handling ... going to work item (%x)\n", Address
);
664 Context
.AddressSpace
= AddressSpace
;
665 Context
.MemoryArea
= MemoryArea
;
666 Context
.Required
= &Resources
;
667 KeInitializeEvent(&Context
.Wait
, NotificationEvent
, FALSE
);
668 ExInitializeWorkItem(&Context
.WorkItem
, (PWORKER_THREAD_ROUTINE
)MmpFaultWorker
, &Context
);
669 DPRINT("Queue work item\n");
670 ExQueueWorkItem(&Context
.WorkItem
, DelayedWorkQueue
);
672 KeWaitForSingleObject(&Context
.Wait
, 0, KernelMode
, FALSE
, NULL
);
673 Status
= Context
.Status
;
674 DPRINTC("Status %x\n", Status
);
678 DPRINT("DoAcquisition %x\n", Resources
.DoAcquisition
);
679 Status
= Resources
.DoAcquisition
680 (AddressSpace
, MemoryArea
, &Resources
);
681 DPRINT("DoAcquisition %x -> %x\n", Resources
.DoAcquisition
, Status
);
684 if (NT_SUCCESS(Status
))
686 Status
= STATUS_MM_RESTART_OPERATION
;
689 else if (NT_SUCCESS(Status
))
696 MmLockAddressSpace(AddressSpace
);
699 while (Status
== STATUS_MM_RESTART_OPERATION
);
701 DPRINTC("Completed page fault handling: %x:%x %x\n", MmGetAddressSpaceOwner(AddressSpace
), Address
, Status
);
704 MmUnlockAddressSpace(AddressSpace
);
707 MiSetPageEvent(MmGetAddressSpaceOwner(AddressSpace
), Address
);
708 DPRINT("Done %x\n", Status
);
715 MmNotPresentFaultCacheSection
716 (KPROCESSOR_MODE Mode
,
721 PMMSUPPORT AddressSpace
;
724 Address
&= ~(PAGE_SIZE
- 1);
725 DPRINT("MmNotPresentFault(Mode %d, Address %x)\n", Mode
, Address
);
727 Thread
= PsGetCurrentThread();
729 if (KeGetCurrentIrql() >= DISPATCH_LEVEL
)
731 DPRINT1("Page fault at high IRQL %d, address %x\n", KeGetCurrentIrql(), Address
);
733 return(STATUS_UNSUCCESSFUL
);
737 * Find the memory area for the faulting address
739 if (Address
>= (ULONG_PTR
)MmSystemRangeStart
)
744 if (Mode
!= KernelMode
)
746 DPRINTC("Address: %x\n", Address
);
747 return(STATUS_ACCESS_VIOLATION
);
749 AddressSpace
= MmGetKernelAddressSpace();
753 AddressSpace
= &PsGetCurrentProcess()->Vm
;
756 Thread
->ActiveFaultCount
++;
757 Status
= MmNotPresentFaultCacheSectionInner
758 (Mode
, AddressSpace
, Address
, FromMdl
, Thread
);
759 Thread
->ActiveFaultCount
--;
761 ASSERT(Status
!= STATUS_UNSUCCESSFUL
);
762 ASSERT(Status
!= STATUS_INVALID_PARAMETER
);
763 DPRINT("MmAccessFault %x:%x -> %x\n", MmGetAddressSpaceOwner(AddressSpace
), Address
, Status
);