2 * PROJECT: ReactOS Kernel
3 * LICENSE: BSD - See COPYING.ARM in the top level directory
4 * FILE: ntoskrnl/mm/ARM3/pagfault.c
5 * PURPOSE: ARM Memory Manager Page Fault Handling
6 * PROGRAMMERS: ReactOS Portable Systems Group
9 /* INCLUDES *******************************************************************/
15 #line 15 "ARMĀ³::PAGFAULT"
16 #define MODULE_INVOLVED_IN_ARM3
17 #include "../ARM3/miarm.h"
19 /* GLOBALS ********************************************************************/
21 /* PRIVATE FUNCTIONS **********************************************************/
25 MiCheckVirtualAddress(IN PVOID VirtualAddress
,
26 OUT PULONG ProtectCode
,
31 /* No prototype/section support for now */
34 /* Only valid for user VADs for now */
35 ASSERT(VirtualAddress
<= MM_HIGHEST_USER_ADDRESS
);
37 /* Special case for shared data */
38 if (PAGE_ALIGN(VirtualAddress
) == (PVOID
)USER_SHARED_DATA
)
40 /* It's a read-only page */
41 *ProtectCode
= MM_READONLY
;
42 return MmSharedUserDataPte
;
45 /* Find the VAD, it must exist, since we only handle PEB/TEB */
46 Vad
= MiLocateAddress(VirtualAddress
);
49 /* This must be a TEB/PEB VAD */
50 ASSERT(Vad
->u
.VadFlags
.PrivateMemory
== TRUE
);
51 ASSERT(Vad
->u
.VadFlags
.MemCommit
== TRUE
);
52 ASSERT(Vad
->u
.VadFlags
.VadType
== VadNone
);
54 /* Return the protection on it */
55 *ProtectCode
= Vad
->u
.VadFlags
.Protection
;
61 MiCheckPdeForPagedPool(IN PVOID Address
)
64 NTSTATUS Status
= STATUS_SUCCESS
;
66 /* No session support in ReactOS yet */
67 ASSERT(MI_IS_SESSION_ADDRESS(Address
) == FALSE
);
68 ASSERT(MI_IS_SESSION_PTE(Address
) == FALSE
);
71 // Check if this is a fault while trying to access the page table itself
73 if (MI_IS_SYSTEM_PAGE_TABLE_ADDRESS(Address
))
76 // Send a hint to the page fault handler that this is only a valid fault
77 // if we already detected this was access within the page table range
79 PointerPde
= (PMMPDE
)MiAddressToPte(Address
);
80 Status
= STATUS_WAIT_1
;
82 else if (Address
< MmSystemRangeStart
)
85 // This is totally illegal
87 return STATUS_ACCESS_VIOLATION
;
92 // Get the PDE for the address
94 PointerPde
= MiAddressToPde(Address
);
98 // Check if it's not valid
100 if (PointerPde
->u
.Hard
.Valid
== 0)
103 /* This seems to be making the assumption that one PDE is one page long */
104 C_ASSERT(PAGE_SIZE
== (PD_COUNT
* (sizeof(MMPTE
) * PDE_COUNT
)));
108 // Copy it from our double-mapped system page directory
110 InterlockedExchangePte(PointerPde
,
111 MmSystemPagePtes
[((ULONG_PTR
)PointerPde
&
113 sizeof(MMPTE
)].u
.Long
);
124 MiZeroPfn(IN PFN_NUMBER PageFrameNumber
)
131 /* Get the PFN for this page */
132 Pfn1
= MiGetPfnEntry(PageFrameNumber
);
135 /* Grab a system PTE we can use to zero the page */
136 ZeroPte
= MiReserveSystemPtes(1, SystemPteSpace
);
139 /* Initialize the PTE for it */
140 TempPte
= ValidKernelPte
;
141 TempPte
.u
.Hard
.PageFrameNumber
= PageFrameNumber
;
144 if (Pfn1
->u3
.e1
.CacheAttribute
== MiWriteCombined
)
146 /* Write combining, no caching */
147 MI_PAGE_DISABLE_CACHE(&TempPte
);
148 MI_PAGE_WRITE_COMBINED(&TempPte
);
150 else if (Pfn1
->u3
.e1
.CacheAttribute
== MiNonCached
)
152 /* Write through, no caching */
153 MI_PAGE_DISABLE_CACHE(&TempPte
);
154 MI_PAGE_WRITE_THROUGH(&TempPte
);
157 /* Make the system PTE valid with our PFN */
158 MI_WRITE_VALID_PTE(ZeroPte
, TempPte
);
160 /* Get the address it maps to, and zero it out */
161 ZeroAddress
= MiPteToAddress(ZeroPte
);
162 KeZeroPages(ZeroAddress
, PAGE_SIZE
);
164 /* Now get rid of it */
165 MiReleaseSystemPtes(ZeroPte
, 1, SystemPteSpace
);
170 MiResolveDemandZeroFault(IN PVOID Address
,
171 IN PMMPTE PointerPte
,
172 IN PEPROCESS Process
,
175 PFN_NUMBER PageFrameNumber
;
177 BOOLEAN NeedZero
= FALSE
;
178 DPRINT("ARM3 Demand Zero Page Fault Handler for address: %p in process: %p\n",
182 /* Must currently only be called by paging path */
183 ASSERT(OldIrql
== MM_NOIRQL
);
187 ASSERT(MI_IS_PAGE_TABLE_ADDRESS(PointerPte
));
190 ASSERT(Process
->ForkInProgress
== NULL
);
192 /* We'll need a zero page */
197 // Lock the PFN database
199 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
200 ASSERT(PointerPte
->u
.Hard
.Valid
== 0);
203 PageFrameNumber
= MiRemoveAnyPage(0);
204 DPRINT("New pool page: %lx\n", PageFrameNumber
);
207 MiInitializePfn(PageFrameNumber
, PointerPte
, TRUE
);
212 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
215 // Increment demand zero faults
217 InterlockedIncrement(&KeGetCurrentPrcb()->MmDemandZeroCount
);
219 /* Zero the page if need be */
220 if (NeedZero
) MiZeroPfn(PageFrameNumber
);
223 if (PointerPte
<= MiHighestUserPte
)
226 MI_MAKE_HARDWARE_PTE_USER(&TempPte
,
228 PointerPte
->u
.Soft
.Protection
,
233 /* For kernel mode */
234 MI_MAKE_HARDWARE_PTE(&TempPte
,
236 PointerPte
->u
.Soft
.Protection
,
240 /* Set it dirty if it's a writable page */
241 if (TempPte
.u
.Hard
.Write
) TempPte
.u
.Hard
.Dirty
= TRUE
;
244 MI_WRITE_VALID_PTE(PointerPte
, TempPte
);
249 DPRINT("Paged pool page has now been paged in\n");
250 return STATUS_PAGE_FAULT_DEMAND_ZERO
;
255 MiCompleteProtoPteFault(IN BOOLEAN StoreInstruction
,
257 IN PMMPTE PointerPte
,
258 IN PMMPTE PointerProtoPte
,
263 PFN_NUMBER PageFrameIndex
;
265 /* Must be called with an valid prototype PTE, with the PFN lock held */
266 ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL
);
267 ASSERT(PointerProtoPte
->u
.Hard
.Valid
== 1);
270 ASSERT(PointerPte
->u
.Soft
.PageFileHigh
== 0xFFFFF);
273 PageFrameIndex
= PFN_FROM_PTE(PointerProtoPte
);
275 /* Release the PFN lock */
276 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
278 /* Build the user PTE */
279 ASSERT(Address
< MmSystemRangeStart
);
280 MI_MAKE_HARDWARE_PTE_USER(&TempPte
, PointerPte
, MM_READONLY
, PageFrameIndex
);
283 MI_WRITE_VALID_PTE(PointerPte
, TempPte
);
286 return STATUS_SUCCESS
;
291 MiResolveProtoPteFault(IN BOOLEAN StoreInstruction
,
293 IN PMMPTE PointerPte
,
294 IN PMMPTE PointerProtoPte
,
295 IN OUT PMMPFN
*OutPfn
,
296 OUT PVOID
*PageFileData
,
298 IN PEPROCESS Process
,
300 IN PVOID TrapInformation
)
304 PFN_NUMBER PageFrameIndex
;
306 /* Must be called with an invalid, prototype PTE, with the PFN lock held */
307 ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL
);
308 ASSERT(PointerPte
->u
.Hard
.Valid
== 0);
309 ASSERT(PointerPte
->u
.Soft
.Prototype
== 1);
311 /* Read the prototype PTE -- it must be valid since we only handle shared data */
312 TempPte
= *PointerProtoPte
;
313 ASSERT(TempPte
.u
.Hard
.Valid
== 1);
315 /* One more user of this mapped page */
316 PageFrameIndex
= PFN_FROM_PTE(&TempPte
);
317 Pfn1
= MiGetPfnEntry(PageFrameIndex
);
318 Pfn1
->u2
.ShareCount
++;
320 /* Call it a transition */
321 InterlockedIncrement(&KeGetCurrentPrcb()->MmTransitionCount
);
323 /* Complete the prototype PTE fault -- this will release the PFN lock */
324 return MiCompleteProtoPteFault(StoreInstruction
,
334 MiDispatchFault(IN BOOLEAN StoreInstruction
,
336 IN PMMPTE PointerPte
,
337 IN PMMPTE PointerProtoPte
,
338 IN BOOLEAN Recursive
,
339 IN PEPROCESS Process
,
340 IN PVOID TrapInformation
,
344 KIRQL OldIrql
, LockIrql
;
346 PMMPTE SuperProtoPte
;
347 DPRINT("ARM3 Page Fault Dispatcher for address: %p in process: %p\n",
352 // Make sure APCs are off and we're not at dispatch
354 OldIrql
= KeGetCurrentIrql();
355 ASSERT(OldIrql
<= APC_LEVEL
);
356 ASSERT(KeAreAllApcsDisabled() == TRUE
);
359 // Grab a copy of the PTE
361 TempPte
= *PointerPte
;
363 /* Do we have a prototype PTE? */
366 /* This should never happen */
367 ASSERT(!MI_IS_PHYSICAL_ADDRESS(PointerProtoPte
));
369 /* We currently only handle the shared user data PTE path */
370 ASSERT(Address
< MmSystemRangeStart
);
371 ASSERT(PointerPte
->u
.Soft
.Prototype
== 1);
372 ASSERT(PointerPte
->u
.Soft
.PageFileHigh
== 0xFFFFF);
375 /* Lock the PFN database */
376 LockIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
378 /* For the shared data page, this should be true */
379 SuperProtoPte
= MiAddressToPte(PointerProtoPte
);
380 ASSERT(SuperProtoPte
->u
.Hard
.Valid
== 1);
381 ASSERT(TempPte
.u
.Hard
.Valid
== 0);
383 /* Resolve the fault -- this will release the PFN lock */
384 Status
= MiResolveProtoPteFault(StoreInstruction
,
394 ASSERT(Status
== STATUS_SUCCESS
);
396 /* Complete this as a transition fault */
397 ASSERT(OldIrql
== KeGetCurrentIrql());
398 ASSERT(OldIrql
<= APC_LEVEL
);
399 ASSERT(KeAreAllApcsDisabled() == TRUE
);
400 return STATUS_PAGE_FAULT_TRANSITION
;
404 // The PTE must be invalid, but not totally blank
406 ASSERT(TempPte
.u
.Hard
.Valid
== 0);
407 ASSERT(TempPte
.u
.Long
!= 0);
410 // No prototype, transition or page file software PTEs in ARM3 yet
412 ASSERT(TempPte
.u
.Soft
.Prototype
== 0);
413 ASSERT(TempPte
.u
.Soft
.Transition
== 0);
414 ASSERT(TempPte
.u
.Soft
.PageFileHigh
== 0);
417 // If we got this far, the PTE can only be a demand zero PTE, which is what
418 // we want. Go handle it!
420 Status
= MiResolveDemandZeroFault(Address
,
424 ASSERT(KeAreAllApcsDisabled () == TRUE
);
425 if (NT_SUCCESS(Status
))
428 // Make sure we're returning in a sane state and pass the status down
430 ASSERT(OldIrql
== KeGetCurrentIrql ());
431 ASSERT(KeGetCurrentIrql() <= APC_LEVEL
);
436 // Generate an access fault
438 return STATUS_ACCESS_VIOLATION
;
443 MmArmAccessFault(IN BOOLEAN StoreInstruction
,
445 IN KPROCESSOR_MODE Mode
,
446 IN PVOID TrapInformation
)
448 KIRQL OldIrql
= KeGetCurrentIrql(), LockIrql
;
449 PMMPTE PointerPte
, ProtoPte
;
452 PETHREAD CurrentThread
;
453 PEPROCESS CurrentProcess
;
455 PMMSUPPORT WorkingSet
;
456 ULONG ProtectionCode
;
458 PFN_NUMBER PageFrameIndex
;
459 DPRINT("ARM3 FAULT AT: %p\n", Address
);
462 // Get the PTE and PDE
464 PointerPte
= MiAddressToPte(Address
);
465 PointerPde
= MiAddressToPde(Address
);
468 // Check for dispatch-level snafu
470 if (OldIrql
> APC_LEVEL
)
473 // There are some special cases where this is okay, but not in ARM3 yet
475 DbgPrint("MM:***PAGE FAULT AT IRQL > 1 Va %p, IRQL %lx\n",
478 ASSERT(OldIrql
<= APC_LEVEL
);
482 // Check for kernel fault
484 if (Address
>= MmSystemRangeStart
)
487 // What are you even DOING here?
489 if (Mode
== UserMode
) return STATUS_ACCESS_VIOLATION
;
494 if (!PointerPde
->u
.Hard
.Valid
== 0)
499 DPRINT("Invalid PDE\n");
502 // Handle mapping in "Special" PDE directoreis
504 MiCheckPdeForPagedPool(Address
);
507 // Now we SHOULD be good
509 if (PointerPde
->u
.Hard
.Valid
== 0)
512 // FIXFIX: Do the S-LIST hack
518 KeBugCheckEx(PAGE_FAULT_IN_NONPAGED_AREA
,
521 (ULONG_PTR
)TrapInformation
,
527 // The PDE is valid, so read the PTE
529 TempPte
= *PointerPte
;
530 if (TempPte
.u
.Hard
.Valid
== 1)
533 // Only two things can go wrong here:
534 // Executing NX page (we couldn't care less)
535 // Writing to a read-only page (the stuff ARM3 works with is write,
536 // so again, moot point).
538 if (StoreInstruction
)
540 DPRINT1("Should NEVER happen on ARM3!!!\n");
541 return STATUS_ACCESS_VIOLATION
;
545 // Otherwise, the PDE was probably invalid, and all is good now
547 return STATUS_SUCCESS
;
551 // Check for a fault on the page table or hyperspace itself
553 if (MI_IS_PAGE_TABLE_OR_HYPER_ADDRESS(Address
))
556 // This might happen...not sure yet
558 DPRINT1("FAULT ON PAGE TABLES: %p %lx %lx!\n", Address
, *PointerPte
, *PointerPde
);
561 // Map in the page table
563 if (MiCheckPdeForPagedPool(Address
) == STATUS_WAIT_1
)
565 DPRINT1("PAGE TABLES FAULTED IN!\n");
566 return STATUS_SUCCESS
;
570 // Otherwise the page table doesn't actually exist
572 DPRINT1("FAILING\n");
573 return STATUS_ACCESS_VIOLATION
;
576 /* In this path, we are using the system working set */
577 CurrentThread
= PsGetCurrentThread();
578 WorkingSet
= &MmSystemCacheWs
;
581 KeRaiseIrql(APC_LEVEL
, &LockIrql
);
582 MiLockWorkingSet(CurrentThread
, WorkingSet
);
585 // Re-read PTE now that the IRQL has been raised
587 TempPte
= *PointerPte
;
588 if (TempPte
.u
.Hard
.Valid
== 1)
591 // Only two things can go wrong here:
592 // Executing NX page (we couldn't care less)
593 // Writing to a read-only page (the stuff ARM3 works with is write,
594 // so again, moot point.
596 if (StoreInstruction
)
598 DPRINT1("Should NEVER happen on ARM3!!!\n");
599 return STATUS_ACCESS_VIOLATION
;
602 /* Release the working set */
603 MiUnlockWorkingSet(CurrentThread
, WorkingSet
);
604 KeLowerIrql(LockIrql
);
607 // Otherwise, the PDE was probably invalid, and all is good now
609 return STATUS_SUCCESS
;
613 // We don't implement prototype PTEs
615 ASSERT(TempPte
.u
.Soft
.Prototype
== 0);
618 // We don't implement transition PTEs
620 ASSERT(TempPte
.u
.Soft
.Transition
== 0);
623 // Now do the real fault handling
625 Status
= MiDispatchFault(StoreInstruction
,
634 /* Release the working set */
635 ASSERT(KeAreAllApcsDisabled() == TRUE
);
636 MiUnlockWorkingSet(CurrentThread
, WorkingSet
);
637 KeLowerIrql(LockIrql
);
642 DPRINT("Fault resolved with status: %lx\n", Status
);
646 /* This is a user fault */
647 CurrentThread
= PsGetCurrentThread();
648 CurrentProcess
= PsGetCurrentProcess();
650 /* Lock the working set */
651 MiLockProcessWorkingSet(CurrentProcess
, CurrentThread
);
653 /* First things first, is the PDE valid? */
654 ASSERT(PointerPde
!= MiAddressToPde(PTE_BASE
));
655 ASSERT(PointerPde
->u
.Hard
.LargePage
== 0);
656 if (PointerPde
->u
.Hard
.Valid
== 0)
658 /* Right now, we only handle scenarios where the PDE is totally empty */
659 ASSERT(PointerPde
->u
.Long
== 0);
661 /* Check if this address range belongs to a valid allocation (VAD) */
662 MiCheckVirtualAddress(Address
, &ProtectionCode
, &Vad
);
664 /* Right now, we expect a valid protection mask on the VAD */
665 ASSERT(ProtectionCode
!= MM_NOACCESS
);
667 /* Make the PDE demand-zero */
668 MI_WRITE_INVALID_PTE(PointerPde
, DemandZeroPde
);
670 /* And go dispatch the fault on the PDE. This should handle the demand-zero */
671 Status
= MiDispatchFault(TRUE
,
676 PsGetCurrentProcess(),
680 /* We should come back with APCs enabled, and with a valid PDE */
681 ASSERT(KeAreAllApcsDisabled() == TRUE
);
682 ASSERT(PointerPde
->u
.Hard
.Valid
== 1);
685 /* Now capture the PTE. We only handle cases where it's totally empty */
686 TempPte
= *PointerPte
;
687 ASSERT(TempPte
.u
.Long
== 0);
689 /* Check if this address range belongs to a valid allocation (VAD) */
690 ProtoPte
= MiCheckVirtualAddress(Address
, &ProtectionCode
, &Vad
);
691 ASSERT(ProtectionCode
!= MM_NOACCESS
);
693 /* Did we get a prototype PTE back? */
696 /* No, create a new PTE. First, write the protection */
697 PointerPte
->u
.Soft
.Protection
= ProtectionCode
;
699 /* Lock the PFN database since we're going to grab a page */
700 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
702 /* Grab a page out of there. Later we should grab a colored zero page */
703 PageFrameIndex
= MiRemoveAnyPage(0);
704 ASSERT(PageFrameIndex
);
706 /* Release the lock since we need to do some zeroing */
707 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
709 /* Zero out the page, since it's for user-mode */
710 MiZeroPfn(PageFrameIndex
);
712 /* Grab the lock again so we can initialize the PFN entry */
713 OldIrql
= KeAcquireQueuedSpinLock(LockQueuePfnLock
);
715 /* Initialize the PFN entry now */
716 MiInitializePfn(PageFrameIndex
, PointerPte
, 1);
718 /* And we're done with the lock */
719 KeReleaseQueuedSpinLock(LockQueuePfnLock
, OldIrql
);
721 /* One more demand-zero fault */
722 InterlockedIncrement(&KeGetCurrentPrcb()->MmDemandZeroCount
);
724 /* Was the fault on an actual user page, or a kernel page for the user? */
725 if (PointerPte
<= MiHighestUserPte
)
727 /* User fault, build a user PTE */
728 MI_MAKE_HARDWARE_PTE_USER(&TempPte
,
730 PointerPte
->u
.Soft
.Protection
,
735 /* Session, kernel, or user PTE, figure it out and build it */
736 MI_MAKE_HARDWARE_PTE(&TempPte
,
738 PointerPte
->u
.Soft
.Protection
,
742 /* Write the dirty bit for writeable pages */
743 if (TempPte
.u
.Hard
.Write
) TempPte
.u
.Hard
.Dirty
= TRUE
;
745 /* And now write down the PTE, making the address valid */
746 MI_WRITE_VALID_PTE(PointerPte
, TempPte
);
749 Status
= STATUS_PAGE_FAULT_DEMAND_ZERO
;
753 /* The only "prototype PTE" we support is the shared user data path */
754 ASSERT(ProtectionCode
== MM_READONLY
);
756 /* Write the prototype PTE */
757 TempPte
= PrototypePte
;
758 TempPte
.u
.Soft
.Protection
= ProtectionCode
;
759 MI_WRITE_INVALID_PTE(PointerPte
, TempPte
);
761 /* Handle the fault */
762 Status
= MiDispatchFault(StoreInstruction
,
770 ASSERT(Status
== STATUS_PAGE_FAULT_TRANSITION
);
771 ASSERT(PointerPte
->u
.Hard
.Valid
== 1);
772 ASSERT(PointerPte
->u
.Hard
.PageFrameNumber
== MmSharedUserDataPte
->u
.Hard
.PageFrameNumber
);
775 /* Release the working set */
776 MiUnlockProcessWorkingSet(CurrentProcess
, CurrentThread
);