/* * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS kernel * FILE: ntoskrnl/mm/i386/page.c * PURPOSE: Low level memory managment manipulation * * PROGRAMMERS: David Welch (welch@cwcom.net) */ /* INCLUDES ***************************************************************/ #include #define NDEBUG #include #include /* GLOBALS *****************************************************************/ #define PA_BIT_PRESENT (0) #define PA_BIT_READWRITE (1) #define PA_BIT_USER (2) #define PA_BIT_WT (3) #define PA_BIT_CD (4) #define PA_BIT_ACCESSED (5) #define PA_BIT_DIRTY (6) #define PA_BIT_GLOBAL (8) #define PA_PRESENT (1 << PA_BIT_PRESENT) #define PA_READWRITE (1 << PA_BIT_READWRITE) #define PA_USER (1 << PA_BIT_USER) #define PA_DIRTY (1 << PA_BIT_DIRTY) #define PA_WT (1 << PA_BIT_WT) #define PA_CD (1 << PA_BIT_CD) #define PA_ACCESSED (1 << PA_BIT_ACCESSED) #define PA_GLOBAL (1 << PA_BIT_GLOBAL) #define IS_HYPERSPACE(v) (((ULONG)(v) >= HYPER_SPACE && (ULONG)(v) <= HYPER_SPACE_END)) #define PTE_TO_PFN(X) ((X) >> PAGE_SHIFT) #define PFN_TO_PTE(X) ((X) << PAGE_SHIFT) #define PAGE_MASK(x) ((x)&(~0xfff)) const ULONG MmProtectToPteMask[32] = { // // These are the base MM_ protection flags // 0, PTE_READONLY | PTE_ENABLE_CACHE, PTE_EXECUTE | PTE_ENABLE_CACHE, PTE_EXECUTE_READ | PTE_ENABLE_CACHE, PTE_READWRITE | PTE_ENABLE_CACHE, PTE_WRITECOPY | PTE_ENABLE_CACHE, PTE_EXECUTE_READWRITE | PTE_ENABLE_CACHE, PTE_EXECUTE_WRITECOPY | PTE_ENABLE_CACHE, // // These OR in the MM_NOCACHE flag // 0, PTE_READONLY | PTE_DISABLE_CACHE, PTE_EXECUTE | PTE_DISABLE_CACHE, PTE_EXECUTE_READ | PTE_DISABLE_CACHE, PTE_READWRITE | PTE_DISABLE_CACHE, PTE_WRITECOPY | PTE_DISABLE_CACHE, PTE_EXECUTE_READWRITE | PTE_DISABLE_CACHE, PTE_EXECUTE_WRITECOPY | PTE_DISABLE_CACHE, // // These OR in the MM_DECOMMIT flag, which doesn't seem supported on x86/64/ARM // 0, PTE_READONLY | PTE_ENABLE_CACHE, PTE_EXECUTE | PTE_ENABLE_CACHE, PTE_EXECUTE_READ | PTE_ENABLE_CACHE, PTE_READWRITE | PTE_ENABLE_CACHE, PTE_WRITECOPY | PTE_ENABLE_CACHE, PTE_EXECUTE_READWRITE | PTE_ENABLE_CACHE, PTE_EXECUTE_WRITECOPY | PTE_ENABLE_CACHE, // // These OR in the MM_NOACCESS flag, which seems to enable WriteCombining? // 0, PTE_READONLY | PTE_WRITECOMBINED_CACHE, PTE_EXECUTE | PTE_WRITECOMBINED_CACHE, PTE_EXECUTE_READ | PTE_WRITECOMBINED_CACHE, PTE_READWRITE | PTE_WRITECOMBINED_CACHE, PTE_WRITECOPY | PTE_WRITECOMBINED_CACHE, PTE_EXECUTE_READWRITE | PTE_WRITECOMBINED_CACHE, PTE_EXECUTE_WRITECOPY | PTE_WRITECOMBINED_CACHE, }; const ULONG MmProtectToValue[32] = { PAGE_NOACCESS, PAGE_READONLY, PAGE_EXECUTE, PAGE_EXECUTE_READ, PAGE_READWRITE, PAGE_WRITECOPY, PAGE_EXECUTE_READWRITE, PAGE_EXECUTE_WRITECOPY, PAGE_NOACCESS, PAGE_NOCACHE | PAGE_READONLY, PAGE_NOCACHE | PAGE_EXECUTE, PAGE_NOCACHE | PAGE_EXECUTE_READ, PAGE_NOCACHE | PAGE_READWRITE, PAGE_NOCACHE | PAGE_WRITECOPY, PAGE_NOCACHE | PAGE_EXECUTE_READWRITE, PAGE_NOCACHE | PAGE_EXECUTE_WRITECOPY, PAGE_NOACCESS, PAGE_GUARD | PAGE_READONLY, PAGE_GUARD | PAGE_EXECUTE, PAGE_GUARD | PAGE_EXECUTE_READ, PAGE_GUARD | PAGE_READWRITE, PAGE_GUARD | PAGE_WRITECOPY, PAGE_GUARD | PAGE_EXECUTE_READWRITE, PAGE_GUARD | PAGE_EXECUTE_WRITECOPY, PAGE_NOACCESS, PAGE_WRITECOMBINE | PAGE_READONLY, PAGE_WRITECOMBINE | PAGE_EXECUTE, PAGE_WRITECOMBINE | PAGE_EXECUTE_READ, PAGE_WRITECOMBINE | PAGE_READWRITE, PAGE_WRITECOMBINE | PAGE_WRITECOPY, PAGE_WRITECOMBINE | PAGE_EXECUTE_READWRITE, PAGE_WRITECOMBINE | PAGE_EXECUTE_WRITECOPY }; /* FUNCTIONS ***************************************************************/ NTSTATUS NTAPI MiFillSystemPageDirectory(IN PVOID Base, IN SIZE_T NumberOfBytes); PFN_NUMBER NTAPI MmGetPfnForProcess(PEPROCESS Process, PVOID Address) { PMMPTE PointerPte; PFN_NUMBER Page; /* Must be called for user mode only */ ASSERT(Process != NULL); ASSERT(Address < MmSystemRangeStart); /* And for our process */ ASSERT(Process == PsGetCurrentProcess()); /* Lock for reading */ MiLockProcessWorkingSetShared(Process, PsGetCurrentThread()); if (MiQueryPageTableReferences(Address) == 0) { MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); return 0; } /* Make sure we can read the PTE */ MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); PointerPte = MiAddressToPte(Address); Page = PointerPte->u.Hard.Valid ? PFN_FROM_PTE(PointerPte) : 0; MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); return Page; } VOID NTAPI MmDeleteVirtualMapping(PEPROCESS Process, PVOID Address, BOOLEAN* WasDirty, PPFN_NUMBER Page) /* * FUNCTION: Delete a virtual mapping */ { PMMPTE PointerPte; ULONG Pte; DPRINT("MmDeleteVirtualMapping(%p, %p, %p, %p)\n", Process, Address, WasDirty, Page); ASSERT(((ULONG_PTR)Address % PAGE_SIZE) == 0); /* And we should be at low IRQL */ ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); /* Make sure our PDE is valid, and that everything is going fine */ if (Process == NULL) { if (Address < MmSystemRangeStart) { DPRINT1("NULL process given for user-mode mapping at %p\n", Address); KeBugCheck(MEMORY_MANAGEMENT); } if (!MiSynchronizeSystemPde(MiAddressToPde(Address))) { /* There can't be a page if there is no PDE */ if (WasDirty) *WasDirty = FALSE; if (Page) *Page = 0; return; } } else { if ((Address >= MmSystemRangeStart) || Add2Ptr(Address, PAGE_SIZE) >= MmSystemRangeStart) { DPRINT1("Process %p given for kernel-mode mapping at %p -- %lu pages starting at %Ix\n", Process, Address); KeBugCheck(MEMORY_MANAGEMENT); } /* Only for current process !!! */ ASSERT(Process = PsGetCurrentProcess()); MiLockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); /* No PDE --> No page */ if (MiQueryPageTableReferences(Address) == 0) { MiUnlockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); if (WasDirty) *WasDirty = 0; if (Page) *Page = 0; return; } MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); } PointerPte = MiAddressToPte(Address); Pte = InterlockedExchangePte(PointerPte, 0); if (Pte == 0) { /* There was nothing here */ if (Address < MmSystemRangeStart) MiUnlockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); if (WasDirty) *WasDirty = 0; if (Page) *Page = 0; return; } /* It must have been present, or not a swap entry */ ASSERT(FlagOn(Pte, PA_PRESENT) || !FlagOn(Pte, 0x800)); if (FlagOn(Pte, PA_PRESENT)) KeInvalidateTlbEntry(Address); if (Address < MmSystemRangeStart) { /* Remove PDE reference */ MiDecrementPageTableReferences(Address); if (MiQueryPageTableReferences(Address) == 0) { KIRQL OldIrql = MiAcquirePfnLock(); MiDeletePte(MiAddressToPte(PointerPte), PointerPte, Process, NULL); MiReleasePfnLock(OldIrql); } MiUnlockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); } if (WasDirty) *WasDirty = FlagOn(Pte, PA_DIRTY); if (Page) *Page = PTE_TO_PFN(Pte); } VOID NTAPI MmDeletePageFileMapping( PEPROCESS Process, PVOID Address, SWAPENTRY* SwapEntry) { PMMPTE PointerPte; ULONG Pte; /* This should not be called for kernel space anymore */ ASSERT(Process != NULL); ASSERT(Address < MmSystemRangeStart); /* And we don't support deleting for other process */ ASSERT(Process == PsGetCurrentProcess()); /* And we should be at low IRQL */ ASSERT(KeGetCurrentIrql() < DISPATCH_LEVEL); /* We are tinkering with the PDE here. Ensure it will be there */ MiLockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); /* Callers must ensure there is actually something there */ ASSERT(MiAddressToPde(Address)->u.Long != 0); MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); PointerPte = MiAddressToPte(Address); Pte = InterlockedExchangePte(PointerPte, 0); if (!FlagOn(Pte, 0x800) || FlagOn(Pte, PA_PRESENT)) { KeBugCheckEx(MEMORY_MANAGEMENT, Pte, (ULONG_PTR)Process, (ULONG_PTR)Address, 0); } /* This used to be a non-zero PTE, now we can let the PDE go. */ MiDecrementPageTableReferences(Address); if (MiQueryPageTableReferences(Address) == 0) { /* We can let it go */ KIRQL OldIrql = MiAcquirePfnLock(); MiDeletePte(MiAddressToPte(PointerPte), PointerPte, Process, NULL); MiReleasePfnLock(OldIrql); } MiUnlockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); *SwapEntry = Pte >> 1; } BOOLEAN Mmi386MakeKernelPageTableGlobal(PVOID Address) { PMMPDE PointerPde = MiAddressToPde(Address); PMMPTE PointerPte = MiAddressToPte(Address); if (PointerPde->u.Hard.Valid == 0) { if(!MiSynchronizeSystemPde(PointerPde)) return FALSE; return PointerPte->u.Hard.Valid != 0; } return FALSE; } BOOLEAN NTAPI MmIsPagePresent(PEPROCESS Process, PVOID Address) { BOOLEAN Ret; if (Address >= MmSystemRangeStart) { ASSERT(Process == NULL); if (!MiSynchronizeSystemPde(MiAddressToPde(Address))) { /* It can't be present if there is no PDE */ return FALSE; } return MiAddressToPte(Address)->u.Hard.Valid; } ASSERT(Process != NULL); ASSERT(Process == PsGetCurrentProcess()); MiLockProcessWorkingSetShared(Process, PsGetCurrentThread()); if (MiQueryPageTableReferences(Address) == 0) { /* It can't be present if there is no PDE */ MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); return FALSE; } MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); Ret = MiAddressToPte(Address)->u.Hard.Valid; MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); return Ret; } BOOLEAN NTAPI MmIsDisabledPage(PEPROCESS Process, PVOID Address) { BOOLEAN Ret; ULONG Pte; if (Address >= MmSystemRangeStart) { ASSERT(Process == NULL); if (!MiSynchronizeSystemPde(MiAddressToPde(Address))) { /* It's not disabled if it's not present */ return FALSE; } Pte = MiAddressToPte(Address)->u.Long; } else { ASSERT(Process != NULL); ASSERT(Process == PsGetCurrentProcess()); MiLockProcessWorkingSetShared(Process, PsGetCurrentThread()); if (MiQueryPageTableReferences(Address) == 0) { /* It can't be disabled if there is no PDE */ MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); return FALSE; } MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); Pte = MiAddressToPte(Address)->u.Long; } Ret = !FlagOn(Pte, PA_PRESENT) && !FlagOn(Pte, 0x800) && (PAGE_MASK(Pte) != 0); if (Address < MmSystemRangeStart) MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); return Ret; } BOOLEAN NTAPI MmIsPageSwapEntry(PEPROCESS Process, PVOID Address) { BOOLEAN Ret; ULONG Pte; /* We never set swap entries for kernel addresses */ if (Address >= MmSystemRangeStart) { ASSERT(Process == NULL); return FALSE; } ASSERT(Process != NULL); ASSERT(Process == PsGetCurrentProcess()); MiLockProcessWorkingSetShared(Process, PsGetCurrentThread()); if (MiQueryPageTableReferences(Address) == 0) { /* There can't be a swap entry if there is no PDE */ MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); return FALSE; } MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); Pte = MiAddressToPte(Address)->u.Long; Ret = !FlagOn(Pte, PA_PRESENT) && FlagOn(Pte, 0x800); MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); return Ret; } VOID NTAPI MmGetPageFileMapping(PEPROCESS Process, PVOID Address, SWAPENTRY* SwapEntry) { ULONG Pte; /* We never set swap entries for kernel addresses */ if (Address >= MmSystemRangeStart) { ASSERT(Process == NULL); *SwapEntry = 0; return; } ASSERT(Process != NULL); ASSERT(Process == PsGetCurrentProcess()); MiLockProcessWorkingSetShared(Process, PsGetCurrentThread()); if (MiQueryPageTableReferences(Address) == 0) { /* There can't be a swap entry if there is no PDE */ MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); *SwapEntry = 0; return; } MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); Pte = MiAddressToPte(Address)->u.Long; if (!FlagOn(Pte, PA_PRESENT) && FlagOn(Pte, 0x800)) *SwapEntry = Pte >> 1; else *SwapEntry = 0; MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); } NTSTATUS NTAPI MmCreatePageFileMapping(PEPROCESS Process, PVOID Address, SWAPENTRY SwapEntry) { PMMPTE PointerPte; ULONG Pte; /* This should not be called for kernel space anymore */ ASSERT(Process != NULL); ASSERT(Address < MmSystemRangeStart); /* And we don't support creating for other process */ ASSERT(Process == PsGetCurrentProcess()); if (SwapEntry & (1 << 31)) { KeBugCheck(MEMORY_MANAGEMENT); } /* We are tinkering with the PDE here. Ensure it will be there */ ASSERT(Process == PsGetCurrentProcess()); MiLockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); PointerPte = MiAddressToPte(Address); Pte = InterlockedExchangePte(PointerPte, SwapEntry << 1); if (Pte != 0) { KeBugCheckEx(MEMORY_MANAGEMENT, SwapEntry, (ULONG_PTR)Process, (ULONG_PTR)Address, 0); } /* This used to be a 0 PTE, now we need a valid PDE to keep it around */ MiIncrementPageTableReferences(Address); MiUnlockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); return STATUS_SUCCESS; } NTSTATUS NTAPI MmCreateVirtualMappingUnsafe(PEPROCESS Process, PVOID Address, ULONG flProtect, PFN_NUMBER Page) { ULONG ProtectionMask; PMMPTE PointerPte; MMPTE TempPte; ULONG_PTR Pte; DPRINT("MmCreateVirtualMappingUnsafe(%p, %p, %lu, %x)\n", Process, Address, flProtect, Page); ASSERT(((ULONG_PTR)Address % PAGE_SIZE) == 0); ProtectionMask = MiMakeProtectionMask(flProtect); /* Caller must have checked ! */ ASSERT(ProtectionMask != MM_INVALID_PROTECTION); ASSERT(ProtectionMask != MM_NOACCESS); ASSERT(ProtectionMask != MM_ZERO_ACCESS); /* Make sure our PDE is valid, and that everything is going fine */ if (Process == NULL) { if (Address < MmSystemRangeStart) { DPRINT1("NULL process given for user-mode mapping at %p\n", Address); KeBugCheck(MEMORY_MANAGEMENT); } if (!MiSynchronizeSystemPde(MiAddressToPde(Address))) MiFillSystemPageDirectory(Address, PAGE_SIZE); } else { if ((Address >= MmSystemRangeStart) || Add2Ptr(Address, PAGE_SIZE) >= MmSystemRangeStart) { DPRINT1("Process %p given for kernel-mode mapping at %p -- %lu pages starting at %Ix\n", Process, Address); KeBugCheck(MEMORY_MANAGEMENT); } /* Only for current process !!! */ ASSERT(Process = PsGetCurrentProcess()); MiLockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); } PointerPte = MiAddressToPte(Address); if (Address >= MmSystemRangeStart) { MI_MAKE_HARDWARE_PTE_KERNEL(&TempPte, PointerPte, ProtectionMask, Page); } else { MI_MAKE_HARDWARE_PTE_USER(&TempPte, PointerPte, ProtectionMask, Page); } Pte = InterlockedExchangePte(PointerPte, TempPte.u.Long); /* There should not have been anything valid here */ if (Pte != 0) { DPRINT1("Bad PTE %lx at %p for %p\n", Pte, PointerPte, Address); KeBugCheck(MEMORY_MANAGEMENT); } /* We don't need to flush the TLB here because it only caches valid translations * and we're moving this PTE from invalid to valid so it can't be cached right now */ if (Address < MmSystemRangeStart) { /* Add PDE reference */ MiIncrementPageTableReferences(Address); MiUnlockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); } return(STATUS_SUCCESS); } NTSTATUS NTAPI MmCreateVirtualMapping(PEPROCESS Process, PVOID Address, ULONG flProtect, PFN_NUMBER Page) { ASSERT((ULONG_PTR)Address % PAGE_SIZE == 0); if (!MmIsPageInUse(Page)) { DPRINT1("Page at address %x not in use\n", PFN_TO_PTE(Page)); KeBugCheck(MEMORY_MANAGEMENT); } return MmCreateVirtualMappingUnsafe(Process, Address, flProtect, Page); } ULONG NTAPI MmGetPageProtect(PEPROCESS Process, PVOID Address) { ULONG_PTR Pte; ULONG Protect; if (Address >= MmSystemRangeStart) { ASSERT(Process == NULL); if (!MiSynchronizeSystemPde(MiAddressToPde(Address))) return PAGE_NOACCESS; } else { ASSERT(Address < MmSystemRangeStart); ASSERT(Process != NULL); ASSERT(Process == PsGetCurrentProcess()); MiLockProcessWorkingSetShared(Process, PsGetCurrentThread()); if (MiQueryPageTableReferences(Address) == 0) { /* It can't be present if there is no PDE */ MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); return PAGE_NOACCESS; } MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); } Pte = MiAddressToPte(Address)->u.Long; if (!(Pte & PA_PRESENT)) { Protect = PAGE_NOACCESS; } else { if (Pte & PA_READWRITE) { Protect = PAGE_READWRITE; } else { Protect = PAGE_EXECUTE_READ; } if (Pte & PA_CD) { Protect |= PAGE_NOCACHE; } if (Pte & PA_WT) { Protect |= PAGE_WRITETHROUGH; } if (!(Pte & PA_USER)) { Protect |= PAGE_SYSTEM; } } if (Address < MmSystemRangeStart) MiUnlockProcessWorkingSetShared(Process, PsGetCurrentThread()); return(Protect); } VOID NTAPI MmSetPageProtect(PEPROCESS Process, PVOID Address, ULONG flProtect) { ULONG ProtectionMask; PMMPTE PointerPte; MMPTE TempPte; ULONG_PTR Pte; DPRINT("MmSetPageProtect(Process %p Address %p flProtect %x)\n", Process, Address, flProtect); ASSERT(Process != NULL); ASSERT(Address < MmSystemRangeStart); ASSERT(Process == PsGetCurrentProcess()); ProtectionMask = MiMakeProtectionMask(flProtect); /* Caller must have checked ! */ ASSERT(ProtectionMask != MM_INVALID_PROTECTION); MiLockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); PointerPte = MiAddressToPte(Address); MI_MAKE_HARDWARE_PTE_USER(&TempPte, PointerPte, ProtectionMask, PFN_FROM_PTE(PointerPte)); /* Keep dirty & accessed bits */ TempPte.u.Hard.Accessed = PointerPte->u.Hard.Accessed; TempPte.u.Hard.Dirty = PointerPte->u.Hard.Dirty; Pte = InterlockedExchangePte(PointerPte, TempPte.u.Long); // We should be able to bring a page back from PAGE_NOACCESS if ((Pte & 0x800) || !(Pte >> PAGE_SHIFT)) { DPRINT1("Invalid Pte %lx\n", Pte); KeBugCheck(MEMORY_MANAGEMENT); } if (Pte != TempPte.u.Long) KeInvalidateTlbEntry(Address); MiUnlockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); } VOID NTAPI MmSetDirtyBit(PEPROCESS Process, PVOID Address, BOOLEAN Bit) { PMMPTE PointerPte; ULONG Pte; DPRINT("MmSetDirtyBit(Process %p Address %p Bit %x)\n", Process, Address, Bit); ASSERT(Process != NULL); ASSERT(Address < MmSystemRangeStart); ASSERT(Process == PsGetCurrentProcess()); MiLockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL); PointerPte = MiAddressToPte(Address); Pte = PointerPte->u.Long; if (Bit) Pte |= PA_DIRTY; else Pte &= ~PA_DIRTY; Pte = InterlockedExchangePte(PointerPte, Pte); // We shouldnl't set dirty bit on non-mapped adresses if ((Pte & 0x800) || !(Pte >> PAGE_SHIFT)) { DPRINT1("Invalid Pte %lx\n", Pte); KeBugCheck(MEMORY_MANAGEMENT); } if (!Bit) KeInvalidateTlbEntry(Address); MiUnlockProcessWorkingSetUnsafe(Process, PsGetCurrentThread()); } CODE_SEG("INIT") VOID NTAPI MmInitGlobalKernelPageDirectory(VOID) { /* Nothing to do here */ } /* EOF */