[CMAKE]
[reactos.git] / ntoskrnl / mm / ARM3 / virtual.c
index 53ac250..dd0f818 100644 (file)
@@ -29,6 +29,85 @@ MiProtectVirtualMemory(IN PEPROCESS Process,
 
 /* PRIVATE FUNCTIONS **********************************************************/
 
+ULONG
+NTAPI
+MiMakeSystemAddressValid(IN PVOID PageTableVirtualAddress,
+                         IN PEPROCESS CurrentProcess)
+{
+    NTSTATUS Status;
+    BOOLEAN LockChange = FALSE;
+
+    /* Must be a non-pool page table, since those are double-mapped already */
+    ASSERT(PageTableVirtualAddress > MM_HIGHEST_USER_ADDRESS);
+    ASSERT((PageTableVirtualAddress < MmPagedPoolStart) ||
+           (PageTableVirtualAddress > MmPagedPoolEnd));
+    
+    /* Working set lock or PFN lock should be held */
+    ASSERT(KeAreAllApcsDisabled() == TRUE);
+
+    /* Check if the page table is valid */
+    while (!MmIsAddressValid(PageTableVirtualAddress))
+    {
+        /* Fault it in */
+        Status = MmAccessFault(FALSE, PageTableVirtualAddress, KernelMode, NULL);
+        if (!NT_SUCCESS(Status))
+        {
+            /* This should not fail */
+            KeBugCheckEx(KERNEL_DATA_INPAGE_ERROR,
+                         1,
+                         Status,
+                         (ULONG_PTR)CurrentProcess,
+                         (ULONG_PTR)PageTableVirtualAddress);
+        }
+
+        /* This flag will be useful later when we do better locking */
+        LockChange = TRUE;
+    }
+
+    /* Let caller know what the lock state is */
+    return LockChange;
+}
+
+ULONG
+NTAPI
+MiMakeSystemAddressValidPfn(IN PVOID VirtualAddress,
+                            IN KIRQL OldIrql)
+{
+    NTSTATUS Status;
+    BOOLEAN LockChange = FALSE;
+
+    /* Must be e kernel address */
+    ASSERT(VirtualAddress > MM_HIGHEST_USER_ADDRESS);
+
+    /* Check if the page is valid */
+    while (!MmIsAddressValid(VirtualAddress))
+    {
+        /* Release the PFN database */
+        KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
+        
+        /* Fault it in */
+        Status = MmAccessFault(FALSE, VirtualAddress, KernelMode, NULL);
+        if (!NT_SUCCESS(Status))
+        {
+            /* This should not fail */
+            KeBugCheckEx(KERNEL_DATA_INPAGE_ERROR,
+                         3,
+                         Status,
+                         0,
+                         (ULONG_PTR)VirtualAddress);
+        }
+
+        /* This flag will be useful later when we do better locking */
+        LockChange = TRUE;
+        
+        /* Lock the PFN database */
+        OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
+    }
+
+    /* Let caller know what the lock state is */
+    return LockChange;
+}
+
 PFN_NUMBER
 NTAPI
 MiDeleteSystemPageableVm(IN PMMPTE PointerPte,
@@ -126,6 +205,266 @@ MiDeleteSystemPageableVm(IN PMMPTE PointerPte,
     return ActualPages;
 }
 
+VOID
+NTAPI
+MiDeletePte(IN PMMPTE PointerPte,
+            IN PVOID VirtualAddress,
+            IN PEPROCESS CurrentProcess,
+            IN PMMPTE PrototypePte)
+{
+    PMMPFN Pfn1;
+    MMPTE TempPte;
+    PFN_NUMBER PageFrameIndex;
+    PMMPDE PointerPde;
+
+    /* PFN lock must be held */
+    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);
+
+    /* Capture the PTE */
+    TempPte = *PointerPte;
+
+    /* We only support valid PTEs for now */
+    ASSERT(TempPte.u.Hard.Valid == 1);
+    if (TempPte.u.Hard.Valid == 0)
+    {
+        /* Invalid PTEs not supported yet */
+        ASSERT(TempPte.u.Soft.Prototype == 0);
+        ASSERT(TempPte.u.Soft.Transition == 0);
+    }
+
+    /* Get the PFN entry */
+    PageFrameIndex = PFN_FROM_PTE(&TempPte);
+    Pfn1 = MiGetPfnEntry(PageFrameIndex);
+
+    /* Check if this is a valid, prototype PTE */
+    if (Pfn1->u3.e1.PrototypePte == 1)
+    {
+        /* Get the PDE and make sure it's faulted in */
+        PointerPde = MiAddressToPde(PointerPte);
+        if (PointerPde->u.Hard.Valid == 0)
+        {
+#if (_MI_PAGING_LEVELS == 2) 
+            /* Could be paged pool access from a new process -- synchronize the page directories */
+            if (!NT_SUCCESS(MiCheckPdeForPagedPool(VirtualAddress)))
+            {
+#endif
+                /* The PDE must be valid at this point */
+                KeBugCheckEx(MEMORY_MANAGEMENT,
+                             0x61940,
+                             (ULONG_PTR)PointerPte,
+                             PointerPte->u.Long,
+                             (ULONG_PTR)VirtualAddress);
+            }
+#if (_MI_PAGING_LEVELS == 2) 
+        }
+#endif
+        /* FIXME: Drop the reference on the page table. For now, leak it until RosMM is gone */
+        //MiDecrementShareCount(MiGetPfnEntry(PFN_FROM_PTE(PointerPde)), PFN_FROM_PDE(PointerPde));
+
+        /* Drop the share count */
+        MiDecrementShareCount(Pfn1, PageFrameIndex);
+        
+        /* No fork yet */
+        if (PointerPte <= MiHighestUserPte) ASSERT(PrototypePte == Pfn1->PteAddress);
+    }
+    else
+    {
+        /* Make sure the saved PTE address is valid */
+        if ((PMMPTE)((ULONG_PTR)Pfn1->PteAddress & ~0x1) != PointerPte)
+        {
+            /* The PFN entry is illegal, or invalid */
+            KeBugCheckEx(MEMORY_MANAGEMENT,
+                         0x401, 
+                         (ULONG_PTR)PointerPte,
+                         PointerPte->u.Long,
+                         (ULONG_PTR)Pfn1->PteAddress);
+        }
+
+        /* There should only be 1 shared reference count */
+        ASSERT(Pfn1->u2.ShareCount == 1);
+    
+        /* FIXME: Drop the reference on the page table. For now, leak it until RosMM is gone */
+        //MiDecrementShareCount(MiGetPfnEntry(Pfn1->u4.PteFrame), Pfn1->u4.PteFrame);
+
+        /* Mark the PFN for deletion and dereference what should be the last ref */
+        MI_SET_PFN_DELETED(Pfn1);
+        MiDecrementShareCount(Pfn1, PageFrameIndex);
+    
+        /* We should eventually do this */
+        //CurrentProcess->NumberOfPrivatePages--;
+    }
+
+    /* Destroy the PTE and flush the TLB */
+    PointerPte->u.Long = 0;
+    KeFlushCurrentTb();
+}
+
+VOID
+NTAPI
+MiDeleteVirtualAddresses(IN ULONG_PTR Va,
+                         IN ULONG_PTR EndingAddress,
+                         IN PMMVAD Vad)
+{
+    PMMPTE PointerPte, PointerPde, PrototypePte, LastPrototypePte;
+    MMPTE TempPte;
+    PEPROCESS CurrentProcess;
+    KIRQL OldIrql;
+    BOOLEAN AddressGap = FALSE;
+    PSUBSECTION Subsection;
+
+    /* Get out if this is a fake VAD, RosMm will free the marea pages */
+    if ((Vad) && (Vad->u.VadFlags.Spare == 1)) return;
+
+    /* Grab the process and PTE/PDE for the address being deleted */
+    CurrentProcess = PsGetCurrentProcess();
+    PointerPde = MiAddressToPde(Va);
+    PointerPte = MiAddressToPte(Va);
+
+    /* Check if this is a section VAD or a VM VAD */
+    if (!(Vad) || (Vad->u.VadFlags.PrivateMemory) || !(Vad->FirstPrototypePte))
+    {
+        /* Don't worry about prototypes */
+        PrototypePte = LastPrototypePte = NULL;
+    }
+    else
+    {
+        /* Get the prototype PTE */
+        PrototypePte = Vad->FirstPrototypePte;
+        LastPrototypePte = Vad->FirstPrototypePte + 1;
+    }
+
+    /* In all cases, we don't support fork() yet */
+    ASSERT(CurrentProcess->CloneRoot == NULL);
+
+    /* Loop the PTE for each VA */
+    while (TRUE)
+    {
+        /* First keep going until we find a valid PDE */
+        while (!PointerPde->u.Long)
+        {
+            /* There are gaps in the address space */
+            AddressGap = TRUE;
+
+            /* Still no valid PDE, try the next 4MB (or whatever) */
+            PointerPde++;
+            
+            /* Update the PTE on this new boundary */
+            PointerPte = MiPteToAddress(PointerPde);
+            
+            /* Check if all the PDEs are invalid, so there's nothing to free */
+            Va = (ULONG_PTR)MiPteToAddress(PointerPte);
+            if (Va > EndingAddress) return;
+        }
+        
+        /* Now check if the PDE is mapped in */
+        if (!PointerPde->u.Hard.Valid)
+        {
+            /* It isn't, so map it in */
+            PointerPte = MiPteToAddress(PointerPde);
+            MiMakeSystemAddressValid(PointerPte, CurrentProcess);
+        }
+    
+        /* Now we should have a valid PDE, mapped in, and still have some VA */
+        ASSERT(PointerPde->u.Hard.Valid == 1);
+        ASSERT(Va <= EndingAddress);
+        
+        /* Check if this is a section VAD with gaps in it */
+        if ((AddressGap) && (LastPrototypePte))
+        {
+            /* We need to skip to the next correct prototype PTE */
+            PrototypePte = MI_GET_PROTOTYPE_PTE_FOR_VPN(Vad, Va >> PAGE_SHIFT);
+            
+            /* And we need the subsection to skip to the next last prototype PTE */
+            Subsection = MiLocateSubsection(Vad, Va >> PAGE_SHIFT);
+            if (Subsection)
+            {
+                /* Found it! */
+                LastPrototypePte = &Subsection->SubsectionBase[Subsection->PtesInSubsection];
+            }
+            else
+            {
+                /* No more subsections, we are done with prototype PTEs */
+                PrototypePte = NULL;
+            }
+        }
+        
+        /* Lock the PFN Database while we delete the PTEs */
+        OldIrql = KeAcquireQueuedSpinLock(LockQueuePfnLock);
+        do
+        {
+            /* Capture the PDE and make sure it exists */
+            TempPte = *PointerPte;
+            if (TempPte.u.Long)
+            {
+                /* Check if the PTE is actually mapped in */
+                if (TempPte.u.Long & 0xFFFFFC01)
+                {
+                    /* Are we dealing with section VAD? */
+                    if ((LastPrototypePte) && (PrototypePte > LastPrototypePte))
+                    {
+                        /* We need to skip to the next correct prototype PTE */
+                        PrototypePte = MI_GET_PROTOTYPE_PTE_FOR_VPN(Vad, Va >> PAGE_SHIFT);
+        
+                        /* And we need the subsection to skip to the next last prototype PTE */
+                        Subsection = MiLocateSubsection(Vad, Va >> PAGE_SHIFT);
+                        if (Subsection)
+                        {
+                            /* Found it! */
+                            LastPrototypePte = &Subsection->SubsectionBase[Subsection->PtesInSubsection];
+                        }
+                        else
+                        {
+                            /* No more subsections, we are done with prototype PTEs */
+                            PrototypePte = NULL;
+                        }
+                    }
+                    
+                    /* Check for prototype PTE */
+                    if ((TempPte.u.Hard.Valid == 0) &&
+                        (TempPte.u.Soft.Prototype == 1))
+                    {
+                        /* Just nuke it */
+                        PointerPte->u.Long = 0;
+                    }
+                    else
+                    {
+                        /* Delete the PTE proper */
+                        MiDeletePte(PointerPte,
+                                    (PVOID)Va, 
+                                    CurrentProcess,
+                                    PrototypePte);
+                    }
+                }
+                else
+                {
+                    /* The PTE was never mapped, just nuke it here */
+                    PointerPte->u.Long = 0;
+                }
+            }
+
+            /* Update the address and PTE for it */
+            Va += PAGE_SIZE;
+            PointerPte++;
+            PrototypePte++;
+            
+            /* Making sure the PDE is still valid */
+            ASSERT(PointerPde->u.Hard.Valid == 1);    
+        }
+        while ((Va & (PDE_MAPPED_VA - 1)) && (Va <= EndingAddress));
+
+        /* The PDE should still be valid at this point */
+        ASSERT(PointerPde->u.Hard.Valid == 1);
+        
+        /* Release the lock and get out if we're done */
+        KeReleaseQueuedSpinLock(LockQueuePfnLock, OldIrql);
+        if (Va > EndingAddress) return;
+
+        /* Otherwise, we exited because we hit a new PDE boundary, so start over */
+        PointerPde = MiAddressToPde(Va);
+        AddressGap = FALSE;
+    }
+}
+
 LONG
 MiGetExceptionInfo(IN PEXCEPTION_POINTERS ExceptionInfo,
                    OUT PBOOLEAN HaveBadAddress, 
@@ -709,6 +1048,109 @@ MmFlushVirtualMemory(IN PEPROCESS Process,
     return STATUS_SUCCESS;
 }
 
+ULONG
+NTAPI
+MiQueryAddressState(IN PVOID Va,
+                    IN PMMVAD Vad,
+                    IN PEPROCESS TargetProcess,
+                    OUT PULONG ReturnedProtect,
+                    OUT PVOID *NextVa)
+{
+
+    PMMPTE PointerPte, PointerPde;
+    MMPTE TempPte;
+    BOOLEAN DemandZeroPte = TRUE, ValidPte = FALSE;
+    ULONG State = MEM_RESERVE, Protect = 0, LockChange;
+    ASSERT((Vad->StartingVpn <= ((ULONG_PTR)Va >> PAGE_SHIFT)) &&
+           (Vad->EndingVpn >= ((ULONG_PTR)Va >> PAGE_SHIFT)));
+
+    /* Only normal VADs supported */
+    ASSERT(Vad->u.VadFlags.VadType == VadNone);
+        
+    /* Get the PDE and PTE for the address */
+    PointerPde = MiAddressToPde(Va);
+    PointerPte = MiAddressToPte(Va);
+
+    /* Return the next range */
+    *NextVa = (PVOID)((ULONG_PTR)Va + PAGE_SIZE);
+
+    /* Loop to make sure the PDE is valid */
+    do
+    {
+        /* Try again */
+        LockChange = 0;
+
+        /* Is the PDE empty? */
+        if (!PointerPde->u.Long)
+        {
+            /* No address in this range used yet, move to the next PDE range */
+            *NextVa = MiPteToAddress(MiPteToAddress(PointerPde + 1));
+            break;
+        }
+
+        /* The PDE is empty, but is it faulted in? */
+        if (!PointerPde->u.Hard.Valid)
+        {
+            /* It isn't, go ahead and do the fault */
+            LockChange = MiMakeSystemAddressValid(MiPteToAddress(PointerPde),
+                                                  TargetProcess);
+        }
+
+        /* Check if the PDE was faulted in, making the PTE readable */
+        if (!LockChange) ValidPte = TRUE;
+    } while (LockChange);
+
+    /* Is it safe to try reading the PTE? */
+    if (ValidPte)
+    {
+        /* FIXME: watch out for large pages */
+        
+        /* Capture the PTE */
+        TempPte = *PointerPte;
+        if (TempPte.u.Long)
+        {
+            /* The PTE is valid, so it's not zeroed out */
+            DemandZeroPte = FALSE;
+
+            /* Check if it's valid or has a valid protection mask */
+            ASSERT(TempPte.u.Soft.Prototype == 0);
+            if ((TempPte.u.Soft.Protection != MM_DECOMMIT) ||
+                (TempPte.u.Hard.Valid == 1))
+            {
+                /* This means it's committed */
+                State = MEM_COMMIT;
+                
+                /* For now, we lie about the protection */
+                Protect = PAGE_EXECUTE_READWRITE;
+            }
+            else
+            {
+                /* Otherwise our defaults should hold */
+                ASSERT(Protect == 0);
+                ASSERT(State == MEM_RESERVE);
+            }
+        }
+    }
+
+    /* Check if this was a demand-zero PTE, since we need to find the state */
+    if (DemandZeroPte)
+    {
+        /* Check if the VAD is for committed memory */
+        if (Vad->u.VadFlags.MemCommit)
+        {
+            /* This is committed memory */
+            State = MEM_COMMIT;
+            
+            /* Convert the protection */
+            Protect = MmProtectToValue[Vad->u.VadFlags.Protection];
+        }
+    }
+
+    /* Return the protection code */
+    *ReturnedProtect = Protect;
+    return State;
+}
+
 /* PUBLIC FUNCTIONS ***********************************************************/
 
 /*
@@ -1851,4 +2293,155 @@ NtResetWriteWatch(IN HANDLE ProcessHandle,
     return STATUS_SUCCESS;
 }
 
+NTSTATUS
+NTAPI
+NtQueryVirtualMemory(IN HANDLE ProcessHandle,
+                     IN PVOID BaseAddress,
+                     IN MEMORY_INFORMATION_CLASS MemoryInformationClass,
+                     OUT PVOID MemoryInformation,
+                     IN SIZE_T MemoryInformationLength,
+                     OUT PSIZE_T ReturnLength)
+{
+    PEPROCESS TargetProcess;
+    NTSTATUS Status;
+    PMMVAD Vad = NULL;
+    PVOID Address, NextAddress;
+    BOOLEAN Found;
+    ULONG NewProtect, NewState, BaseVpn;
+    MEMORY_BASIC_INFORMATION MemoryInfo;
+    KAPC_STATE ApcState;
+    DPRINT("Querying class %d about address: %p\n", MemoryInformationClass, BaseAddress);
+
+    /* Only this class is supported for now */
+    ASSERT(MemoryInformationClass == MemoryBasicInformation);
+    
+    /* Validate the size information of the class */
+    if (MemoryInformationLength < sizeof(MEMORY_BASIC_INFORMATION))
+    {
+        /* The size is invalid */
+        return STATUS_INFO_LENGTH_MISMATCH;
+    }
+
+    /* Bail out if the address is invalid */
+    if (BaseAddress > MM_HIGHEST_USER_ADDRESS) return STATUS_INVALID_PARAMETER;
+
+    /* Check for illegal addresses in user-space, or the shared memory area */
+    if ((BaseAddress > MM_HIGHEST_VAD_ADDRESS) ||
+        (PAGE_ALIGN(BaseAddress) == (PVOID)USER_SHARED_DATA))
+    {
+        /* FIXME: We should return some bogus info structure */
+        UNIMPLEMENTED;
+        while (TRUE);
+    }
+
+    /* Check if this is for a local or remote process */
+    if (ProcessHandle == NtCurrentProcess())
+    {
+        TargetProcess = PsGetCurrentProcess();
+    }
+    else
+    {
+        /* Reference the target process */
+        Status = ObReferenceObjectByHandle(ProcessHandle,
+                                           PROCESS_QUERY_INFORMATION,
+                                           PsProcessType,
+                                           ExGetPreviousMode(),
+                                           (PVOID*)&TargetProcess,
+                                           NULL);
+        if (!NT_SUCCESS(Status)) return Status;
+        
+        /* Attach to it now */
+        KeStackAttachProcess(&TargetProcess->Pcb, &ApcState);
+    }
+
+    /* Loop the VADs */
+    ASSERT(TargetProcess->VadRoot.NumberGenericTableElements);
+    if (TargetProcess->VadRoot.NumberGenericTableElements)
+    {
+        /* Scan on the right */
+        Vad = (PMMVAD)TargetProcess->VadRoot.BalancedRoot.RightChild;
+        BaseVpn = (ULONG_PTR)BaseAddress >> PAGE_SHIFT;
+        while (Vad)
+        {
+            /* Check if this VAD covers the allocation range */
+            if ((BaseVpn >= Vad->StartingVpn) &&
+                (BaseVpn <= Vad->EndingVpn))
+            {
+                /* We're done */
+                Found = TRUE;
+                break;
+            }
+
+            /* Check if this VAD is too high */
+            if (BaseVpn < Vad->StartingVpn)
+            {
+                /* Search on the left next */
+                Vad = Vad->LeftChild;
+            }
+            else
+            {
+                /* Then this VAD is too low, keep searching on the right */
+                ASSERT(BaseVpn > Vad->EndingVpn);
+                Vad = Vad->RightChild;
+            }
+        }
+    }
+
+    /* Was a VAD found? */
+    if (!Found)
+    {
+        /* We don't handle this yet */
+        UNIMPLEMENTED;
+        while (TRUE);
+    }
+   
+    /* This must be a VM VAD */
+    ASSERT(Vad->u.VadFlags.PrivateMemory);
+   
+    /* Build the initial information block */
+    Address = PAGE_ALIGN(BaseAddress);
+    MemoryInfo.BaseAddress = Address;
+    MemoryInfo.AllocationBase = (PVOID)(Vad->StartingVpn << PAGE_SHIFT);
+    MemoryInfo.AllocationProtect = MmProtectToValue[Vad->u.VadFlags.Protection];
+    MemoryInfo.Type = MEM_PRIVATE;
+    
+    /* Find the largest chunk of memory which has the same state and protection mask */
+    MemoryInfo.State = MiQueryAddressState(Address,
+                                           Vad,
+                                           TargetProcess,
+                                           &MemoryInfo.Protect,
+                                           &NextAddress);
+    Address = NextAddress;
+    while (((ULONG_PTR)Address >> PAGE_SHIFT) <= Vad->EndingVpn)
+    {
+        /* Keep going unless the state or protection mask changed */
+        NewState = MiQueryAddressState(Address, Vad, TargetProcess, &NewProtect, &NextAddress);
+        if ((NewState != MemoryInfo.State) || (NewProtect != MemoryInfo.Protect)) break;
+        Address = NextAddress;
+    }
+
+    /* Now that we know the last VA address, calculate hte region size */
+    MemoryInfo.RegionSize = ((ULONG_PTR)Address - (ULONG_PTR)MemoryInfo.BaseAddress);
+
+    /* Check if we were attached */
+    if (ProcessHandle != NtCurrentProcess())
+    {
+        /* Detach and derefernece the process */
+        KeUnstackDetachProcess(&ApcState);
+        ObDereferenceObject(TargetProcess);
+    }
+
+    /* Return the data (FIXME: Use SEH) */
+    *(PMEMORY_BASIC_INFORMATION)MemoryInformation = MemoryInfo;
+    if (ReturnLength) *ReturnLength = sizeof(MEMORY_BASIC_INFORMATION);
+    
+    /* All went well */
+    DPRINT("Base: %p AllocBase: %p Protect: %lx AllocProtect: %lx "
+            "State: %lx Type: %lx Size: %lx\n",
+            MemoryInfo.BaseAddress, MemoryInfo.AllocationBase,
+            MemoryInfo.AllocationProtect, MemoryInfo.Protect,
+            MemoryInfo.State, MemoryInfo.Type, MemoryInfo.RegionSize);
+    return STATUS_SUCCESS;
+}
+
 /* EOF */