Note: this patch only implements the code paths, they are not excercised yet.
authorSir Richard <sir_richard@svn.reactos.org>
Thu, 22 Jul 2010 20:52:23 +0000 (20:52 +0000)
committerSir Richard <sir_richard@svn.reactos.org>
Thu, 22 Jul 2010 20:52:23 +0000 (20:52 +0000)
[NTOS]: Implement handling a very special case of "prototype PTE", the one used to map the shared user data for user-mode applications.
[NTOS]: MiCheckVirtualAddress detects this (Windows behavior) and returns a prototype PTE that's marked MM_READONLY. This is our MmSharedUserDataPte from before. This gets sent to MiDispatchFault which calls MiResolveProtoPteFault to handle it. In turn, this calls MiCompleteProtoPteFault. All these code paths have heavy ASSERTions to only allow them to be hit for the shared user data page, however, in the far distant future when prototype PTEs are used for section objects, we'll at least have the right checks and code flow (many of these ASSERTions will then have to be removed).
[NTOS]: The end result is that we also now have STATUS_PAGE_FAULT_TRANSITION, not just STATUS_PAGE_FAULT_DEMAND_ZERO, and that prototype PTEs are somewhat understood and some assumptions have been removed.

svn path=/trunk/; revision=48201

reactos/ntoskrnl/mm/ARM3/pagfault.c

index 34fb242..ac56738 100644 (file)
@@ -34,6 +34,14 @@ MiCheckVirtualAddress(IN PVOID VirtualAddress,
     /* Only valid for user VADs for now */
     ASSERT(VirtualAddress <= MM_HIGHEST_USER_ADDRESS);
     
+    /* Special case for shared data */
+    if (PAGE_ALIGN(VirtualAddress) == (PVOID)USER_SHARED_DATA)
+    {
+        /* It's a read-only page */
+        *ProtectCode = MM_READONLY;
+        return MmSharedUserDataPte;
+    }
+    
     /* Find the VAD, it must exist, since we only handle PEB/TEB */
     Vad = MiLocateAddress(VirtualAddress);
     ASSERT(Vad);
@@ -242,20 +250,100 @@ MiResolveDemandZeroFault(IN PVOID Address,
     return STATUS_PAGE_FAULT_DEMAND_ZERO;
 }
 
+NTSTATUS
+NTAPI
+MiCompleteProtoPteFault(IN BOOLEAN StoreInstruction,
+                        IN PVOID Address,
+                        IN PMMPTE PointerPte,
+                        IN PMMPTE PointerProtoPte,
+                        IN KIRQL OldIrql,
+                        IN PMMPFN Pfn1)
+{
+    MMPTE TempPte;
+    PFN_NUMBER PageFrameIndex;
+    
+    /* Must be called with an valid prototype PTE, with the PFN lock held */
+    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
+    ASSERT(PointerProtoPte->u.Hard.Valid == 1);
+    
+    /* Quick-n-dirty */
+    ASSERT(PointerPte->u.Soft.PageFileHigh == 0xFFFFF);
+    
+    /* Get the page */
+    PageFrameIndex = PFN_FROM_PTE(PointerProtoPte);
+
+    /* Release the PFN lock */
+    KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
+    
+    /* Build the user PTE */
+    ASSERT(Address < MmSystemRangeStart);
+    MI_MAKE_HARDWARE_PTE_USER(&TempPte, PointerPte, MM_READONLY, PageFrameIndex);
+    
+    /* Write the PTE */
+    MI_WRITE_VALID_PTE(PointerPte, TempPte);
+
+    /* Return success */
+    return STATUS_SUCCESS;
+}
+
+NTSTATUS
+NTAPI
+MiResolveProtoPteFault(IN BOOLEAN StoreInstruction,
+                       IN PVOID Address,
+                       IN PMMPTE PointerPte,
+                       IN PMMPTE PointerProtoPte,
+                       IN OUT PMMPFN *OutPfn,
+                       OUT PVOID *PageFileData,
+                       OUT PMMPTE PteValue,
+                       IN PEPROCESS Process,
+                       IN KIRQL OldIrql,
+                       IN PVOID TrapInformation)
+{
+    MMPTE TempPte;
+    PMMPFN Pfn1;
+    PFN_NUMBER PageFrameIndex;
+    
+    /* Must be called with an invalid, prototype PTE, with the PFN lock held */
+    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
+    ASSERT(PointerPte->u.Hard.Valid == 0);
+    ASSERT(PointerPte->u.Soft.Prototype == 1);
+
+    /* Read the prototype PTE -- it must be valid since we only handle shared data */
+    TempPte = *PointerProtoPte;
+    ASSERT(TempPte.u.Hard.Valid == 1);
+    
+    /* One more user of this mapped page */
+    PageFrameIndex = PFN_FROM_PTE(&TempPte);
+    Pfn1 = MiGetPfnEntry(PageFrameIndex);
+    Pfn1->u2.ShareCount++;
+    
+    /* Call it a transition */
+    InterlockedIncrement(&KeGetCurrentPrcb()->MmTransitionCount);
+    
+    /* Complete the prototype PTE fault -- this will release the PFN lock */
+    return MiCompleteProtoPteFault(StoreInstruction,
+                                   Address,
+                                   PointerPte,
+                                   PointerProtoPte,
+                                   OldIrql,
+                                   NULL);
+}
+
 NTSTATUS
 NTAPI
 MiDispatchFault(IN BOOLEAN StoreInstruction,
                 IN PVOID Address,
                 IN PMMPTE PointerPte,
-                IN PMMPTE PrototypePte,
+                IN PMMPTE PointerProtoPte,
                 IN BOOLEAN Recursive,
                 IN PEPROCESS Process,
                 IN PVOID TrapInformation,
                 IN PVOID Vad)
 {
     MMPTE TempPte;
-    KIRQL OldIrql;
+    KIRQL OldIrql, LockIrql;
     NTSTATUS Status;
+    PMMPTE SuperProtoPte;
     DPRINT("ARM3 Page Fault Dispatcher for address: %p in process: %p\n",
              Address,
              Process);
@@ -263,17 +351,54 @@ MiDispatchFault(IN BOOLEAN StoreInstruction,
     //
     // Make sure APCs are off and we're not at dispatch
     //
-    OldIrql = KeGetCurrentIrql ();
+    OldIrql = KeGetCurrentIrql();
     ASSERT(OldIrql <= APC_LEVEL);
-    ASSERT(KeAreAllApcsDisabled () == TRUE);
+    ASSERT(KeAreAllApcsDisabled() == TRUE);
     
     //
     // Grab a copy of the PTE
     //
     TempPte = *PointerPte;
     
-    /* No prototype */
-    ASSERT(PrototypePte == NULL);
+    /* Do we have a prototype PTE? */
+    if (PointerProtoPte)
+    {
+        /* This should never happen */
+        ASSERT(!MI_IS_PHYSICAL_ADDRESS(PointerProtoPte));
+            
+        /* We currently only handle the shared user data PTE path */
+        ASSERT(Address < MmSystemRangeStart);
+        ASSERT(PointerPte->u.Soft.Prototype == 1);
+        ASSERT(PointerPte->u.Soft.PageFileHigh == 0xFFFFF);
+        ASSERT(Vad == NULL);
+        
+        /* Lock the PFN database */
+        LockIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
+        
+        /* For the shared data page, this should be true */
+        SuperProtoPte = MiAddressToPte(PointerProtoPte);
+        ASSERT(SuperProtoPte->u.Hard.Valid == 1);
+        ASSERT(TempPte.u.Hard.Valid == 0);
+
+        /* Resolve the fault -- this will release the PFN lock */
+        Status = MiResolveProtoPteFault(StoreInstruction,
+                                        Address,
+                                        PointerPte,
+                                        PointerProtoPte,
+                                        NULL,
+                                        NULL,
+                                        NULL,
+                                        Process,
+                                        LockIrql,
+                                        TrapInformation);
+        ASSERT(Status == STATUS_SUCCESS);
+
+        /* Complete this as a transition fault */
+        ASSERT(OldIrql == KeGetCurrentIrql());
+        ASSERT(OldIrql <= APC_LEVEL);
+        ASSERT(KeAreAllApcsDisabled() == TRUE);
+        return STATUS_PAGE_FAULT_TRANSITION;
+    }
     
     //
     // The PTE must be invalid, but not totally blank
@@ -321,7 +446,7 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
                  IN PVOID TrapInformation)
 {
     KIRQL OldIrql = KeGetCurrentIrql(), LockIrql;
-    PMMPTE PointerPte;
+    PMMPTE PointerPte, ProtoPte;
     PMMPDE PointerPde;
     MMPTE TempPte;
     PETHREAD CurrentThread;
@@ -562,64 +687,94 @@ MmArmAccessFault(IN BOOLEAN StoreInstruction,
     ASSERT(TempPte.u.Long == 0);
 
     /* Check if this address range belongs to a valid allocation (VAD) */
-    MiCheckVirtualAddress(Address, &ProtectionCode, &Vad);
-    
-    /* Right now, we expect a valid protection mask on the VAD */
+    ProtoPte = MiCheckVirtualAddress(Address, &ProtectionCode, &Vad);
     ASSERT(ProtectionCode != MM_NOACCESS);
-    PointerPte->u.Soft.Protection = ProtectionCode;
 
-    /* Lock the PFN database since we're going to grab a page */
-    OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
+    /* Did we get a prototype PTE back? */
+    if (!ProtoPte)
+    {
+        /* No, create a new PTE. First, write the protection */
+        PointerPte->u.Soft.Protection = ProtectionCode;
 
-    /* Grab a page out of there. Later we should grab a colored zero page */
-    PageFrameIndex = MiRemoveAnyPage(0);
-    ASSERT(PageFrameIndex);
+        /* Lock the PFN database since we're going to grab a page */
+        OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
 
-    /* Release the lock since we need to do some zeroing */
-    KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
+        /* Grab a page out of there. Later we should grab a colored zero page */
+        PageFrameIndex = MiRemoveAnyPage(0);
+        ASSERT(PageFrameIndex);
 
-    /* Zero out the page, since it's for user-mode */
-    MiZeroPfn(PageFrameIndex);
+        /* Release the lock since we need to do some zeroing */
+        KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
 
-    /* Grab the lock again so we can initialize the PFN entry */
-    OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
+        /* Zero out the page, since it's for user-mode */
+        MiZeroPfn(PageFrameIndex);
 
-    /* Initialize the PFN entry now */
-    MiInitializePfn(PageFrameIndex, PointerPte, 1);
+        /* Grab the lock again so we can initialize the PFN entry */
+        OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
 
-    /* And we're done with the lock */
-    KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
+        /* Initialize the PFN entry now */
+        MiInitializePfn(PageFrameIndex, PointerPte, 1);
 
-    /* One more demand-zero fault */
-    InterlockedIncrement(&KeGetCurrentPrcb()->MmDemandZeroCount);
+        /* And we're done with the lock */
+        KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
 
-    /* Was the fault on an actual user page, or a kernel page for the user? */
-    if (PointerPte <= MiHighestUserPte)
-    {
-        /* User fault, build a user PTE */
-        MI_MAKE_HARDWARE_PTE_USER(&TempPte,
-                                  PointerPte,
-                                  PointerPte->u.Soft.Protection,
-                                  PageFrameIndex);
+        /* One more demand-zero fault */
+        InterlockedIncrement(&KeGetCurrentPrcb()->MmDemandZeroCount);
+
+        /* Was the fault on an actual user page, or a kernel page for the user? */
+        if (PointerPte <= MiHighestUserPte)
+        {
+            /* User fault, build a user PTE */
+            MI_MAKE_HARDWARE_PTE_USER(&TempPte,
+                                      PointerPte,
+                                      PointerPte->u.Soft.Protection,
+                                      PageFrameIndex);
+        }
+        else
+        {
+            /* Session, kernel, or user PTE, figure it out and build it */
+            MI_MAKE_HARDWARE_PTE(&TempPte,
+                                 PointerPte,
+                                 PointerPte->u.Soft.Protection,
+                                 PageFrameIndex);
+        }
+
+        /* Write the dirty bit for writeable pages */
+        if (TempPte.u.Hard.Write) TempPte.u.Hard.Dirty = TRUE;
+
+        /* And now write down the PTE, making the address valid */
+        MI_WRITE_VALID_PTE(PointerPte, TempPte);
+        
+        /* Demand zero */
+        Status = STATUS_PAGE_FAULT_DEMAND_ZERO;
     }
     else
     {
-        /* Session, kernel, or user PTE, figure it out and build it */
-        MI_MAKE_HARDWARE_PTE(&TempPte,
-                             PointerPte,
-                             PointerPte->u.Soft.Protection,
-                             PageFrameIndex);
+        /* The only "prototype PTE" we support is the shared user data path */
+        ASSERT(ProtectionCode == MM_READONLY);
+        
+        /* Write the prototype PTE */
+        TempPte = PrototypePte;
+        TempPte.u.Soft.Protection = ProtectionCode;
+        MI_WRITE_INVALID_PTE(PointerPte, TempPte);
+        
+        /* Handle the fault */
+        Status = MiDispatchFault(StoreInstruction,
+                                 Address,
+                                 PointerPte,
+                                 ProtoPte,
+                                 FALSE,
+                                 CurrentProcess,
+                                 TrapInformation,
+                                 Vad);
+        ASSERT(Status == STATUS_PAGE_FAULT_TRANSITION);
+        ASSERT(PointerPte->u.Hard.Valid == 1);
+        ASSERT(PointerPte->u.Hard.PageFrameNumber == MmSharedUserDataPte->u.Hard.PageFrameNumber);
     }
-
-    /* Write the dirty bit for writeable pages */
-    if (TempPte.u.Hard.Write) TempPte.u.Hard.Dirty = TRUE;
-
-    /* And now write down the PTE, making the address valid */
-    MI_WRITE_VALID_PTE(PointerPte, TempPte);
     
     /* Release the working set */
     MiUnlockProcessWorkingSet(CurrentProcess, CurrentThread);
-    return STATUS_PAGE_FAULT_DEMAND_ZERO;
+    return Status;
 }
 
 /* EOF */