xref: /reactos/ntoskrnl/mm/i386/page.c (revision d0ed4fdb)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS kernel
4  * FILE:            ntoskrnl/mm/i386/page.c
5  * PURPOSE:         Low level memory managment manipulation
6  *
7  * PROGRAMMERS:     David Welch (welch@cwcom.net)
8  */
9 
10 /* INCLUDES ***************************************************************/
11 
12 #include <ntoskrnl.h>
13 #define NDEBUG
14 #include <debug.h>
15 
16 #include <mm/ARM3/miarm.h>
17 
18 #if defined (ALLOC_PRAGMA)
19 #pragma alloc_text(INIT, MmInitGlobalKernelPageDirectory)
20 #endif
21 
22 #define ADDR_TO_PDE_OFFSET MiAddressToPdeOffset
23 #define ADDR_TO_PAGE_TABLE(v)  (((ULONG)(v)) / (1024 * PAGE_SIZE))
24 
25 /* GLOBALS *****************************************************************/
26 
27 #define PA_BIT_PRESENT   (0)
28 #define PA_BIT_READWRITE (1)
29 #define PA_BIT_USER      (2)
30 #define PA_BIT_WT        (3)
31 #define PA_BIT_CD        (4)
32 #define PA_BIT_ACCESSED  (5)
33 #define PA_BIT_DIRTY     (6)
34 #define PA_BIT_GLOBAL    (8)
35 
36 #define PA_PRESENT   (1 << PA_BIT_PRESENT)
37 #define PA_READWRITE (1 << PA_BIT_READWRITE)
38 #define PA_USER      (1 << PA_BIT_USER)
39 #define PA_DIRTY     (1 << PA_BIT_DIRTY)
40 #define PA_WT        (1 << PA_BIT_WT)
41 #define PA_CD        (1 << PA_BIT_CD)
42 #define PA_ACCESSED  (1 << PA_BIT_ACCESSED)
43 #define PA_GLOBAL    (1 << PA_BIT_GLOBAL)
44 
45 #define IS_HYPERSPACE(v)    (((ULONG)(v) >= HYPER_SPACE && (ULONG)(v) <= HYPER_SPACE_END))
46 
47 #define PTE_TO_PFN(X)  ((X) >> PAGE_SHIFT)
48 #define PFN_TO_PTE(X)  ((X) << PAGE_SHIFT)
49 
50 #define PAGE_MASK(x)		((x)&(~0xfff))
51 
52 const
53 ULONG
54 MmProtectToPteMask[32] =
55 {
56     //
57     // These are the base MM_ protection flags
58     //
59     0,
60     PTE_READONLY            | PTE_ENABLE_CACHE,
61     PTE_EXECUTE             | PTE_ENABLE_CACHE,
62     PTE_EXECUTE_READ        | PTE_ENABLE_CACHE,
63     PTE_READWRITE           | PTE_ENABLE_CACHE,
64     PTE_WRITECOPY           | PTE_ENABLE_CACHE,
65     PTE_EXECUTE_READWRITE   | PTE_ENABLE_CACHE,
66     PTE_EXECUTE_WRITECOPY   | PTE_ENABLE_CACHE,
67     //
68     // These OR in the MM_NOCACHE flag
69     //
70     0,
71     PTE_READONLY            | PTE_DISABLE_CACHE,
72     PTE_EXECUTE             | PTE_DISABLE_CACHE,
73     PTE_EXECUTE_READ        | PTE_DISABLE_CACHE,
74     PTE_READWRITE           | PTE_DISABLE_CACHE,
75     PTE_WRITECOPY           | PTE_DISABLE_CACHE,
76     PTE_EXECUTE_READWRITE   | PTE_DISABLE_CACHE,
77     PTE_EXECUTE_WRITECOPY   | PTE_DISABLE_CACHE,
78     //
79     // These OR in the MM_DECOMMIT flag, which doesn't seem supported on x86/64/ARM
80     //
81     0,
82     PTE_READONLY            | PTE_ENABLE_CACHE,
83     PTE_EXECUTE             | PTE_ENABLE_CACHE,
84     PTE_EXECUTE_READ        | PTE_ENABLE_CACHE,
85     PTE_READWRITE           | PTE_ENABLE_CACHE,
86     PTE_WRITECOPY           | PTE_ENABLE_CACHE,
87     PTE_EXECUTE_READWRITE   | PTE_ENABLE_CACHE,
88     PTE_EXECUTE_WRITECOPY   | PTE_ENABLE_CACHE,
89     //
90     // These OR in the MM_NOACCESS flag, which seems to enable WriteCombining?
91     //
92     0,
93     PTE_READONLY            | PTE_WRITECOMBINED_CACHE,
94     PTE_EXECUTE             | PTE_WRITECOMBINED_CACHE,
95     PTE_EXECUTE_READ        | PTE_WRITECOMBINED_CACHE,
96     PTE_READWRITE           | PTE_WRITECOMBINED_CACHE,
97     PTE_WRITECOPY           | PTE_WRITECOMBINED_CACHE,
98     PTE_EXECUTE_READWRITE   | PTE_WRITECOMBINED_CACHE,
99     PTE_EXECUTE_WRITECOPY   | PTE_WRITECOMBINED_CACHE,
100 };
101 
102 const
103 ULONG MmProtectToValue[32] =
104 {
105     PAGE_NOACCESS,
106     PAGE_READONLY,
107     PAGE_EXECUTE,
108     PAGE_EXECUTE_READ,
109     PAGE_READWRITE,
110     PAGE_WRITECOPY,
111     PAGE_EXECUTE_READWRITE,
112     PAGE_EXECUTE_WRITECOPY,
113     PAGE_NOACCESS,
114     PAGE_NOCACHE | PAGE_READONLY,
115     PAGE_NOCACHE | PAGE_EXECUTE,
116     PAGE_NOCACHE | PAGE_EXECUTE_READ,
117     PAGE_NOCACHE | PAGE_READWRITE,
118     PAGE_NOCACHE | PAGE_WRITECOPY,
119     PAGE_NOCACHE | PAGE_EXECUTE_READWRITE,
120     PAGE_NOCACHE | PAGE_EXECUTE_WRITECOPY,
121     PAGE_NOACCESS,
122     PAGE_GUARD | PAGE_READONLY,
123     PAGE_GUARD | PAGE_EXECUTE,
124     PAGE_GUARD | PAGE_EXECUTE_READ,
125     PAGE_GUARD | PAGE_READWRITE,
126     PAGE_GUARD | PAGE_WRITECOPY,
127     PAGE_GUARD | PAGE_EXECUTE_READWRITE,
128     PAGE_GUARD | PAGE_EXECUTE_WRITECOPY,
129     PAGE_NOACCESS,
130     PAGE_WRITECOMBINE | PAGE_READONLY,
131     PAGE_WRITECOMBINE | PAGE_EXECUTE,
132     PAGE_WRITECOMBINE | PAGE_EXECUTE_READ,
133     PAGE_WRITECOMBINE | PAGE_READWRITE,
134     PAGE_WRITECOMBINE | PAGE_WRITECOPY,
135     PAGE_WRITECOMBINE | PAGE_EXECUTE_READWRITE,
136     PAGE_WRITECOMBINE | PAGE_EXECUTE_WRITECOPY
137 };
138 
139 /* FUNCTIONS ***************************************************************/
140 
141 static BOOLEAN MmUnmapPageTable(PULONG Pt);
142 
143 VOID
144 MiFlushTlb(PULONG Pt, PVOID Address)
145 {
146     if ((Pt && MmUnmapPageTable(Pt)) || Address >= MmSystemRangeStart)
147     {
148         KeInvalidateTlbEntry(Address);
149     }
150 }
151 
152 static ULONG
153 ProtectToPTE(ULONG flProtect)
154 {
155     ULONG Attributes = 0;
156 
157     if (flProtect & (PAGE_NOACCESS|PAGE_GUARD))
158     {
159         Attributes = 0;
160     }
161     else if (flProtect & PAGE_IS_WRITABLE)
162     {
163         Attributes = PA_PRESENT | PA_READWRITE;
164     }
165     else if (flProtect & (PAGE_IS_READABLE | PAGE_IS_EXECUTABLE))
166     {
167         Attributes = PA_PRESENT;
168     }
169     else
170     {
171         DPRINT1("Unknown main protection type.\n");
172         KeBugCheck(MEMORY_MANAGEMENT);
173     }
174 
175     if (flProtect & PAGE_SYSTEM)
176     {
177     }
178     else
179     {
180         Attributes = Attributes | PA_USER;
181     }
182     if (flProtect & PAGE_NOCACHE)
183     {
184         Attributes = Attributes | PA_CD;
185     }
186     if (flProtect & PAGE_WRITETHROUGH)
187     {
188         Attributes = Attributes | PA_WT;
189     }
190     return(Attributes);
191 }
192 
193 NTSTATUS
194 NTAPI
195 MiDispatchFault(IN ULONG FaultCode,
196                 IN PVOID Address,
197                 IN PMMPTE PointerPte,
198                 IN PMMPTE PointerProtoPte,
199                 IN BOOLEAN Recursive,
200                 IN PEPROCESS Process,
201                 IN PVOID TrapInformation,
202                 IN PVOID Vad);
203 
204 NTSTATUS
205 NTAPI
206 MiFillSystemPageDirectory(IN PVOID Base,
207                           IN SIZE_T NumberOfBytes);
208 
209 static PULONG
210 MmGetPageTableForProcess(PEPROCESS Process, PVOID Address, BOOLEAN Create)
211 {
212     PFN_NUMBER Pfn;
213     PULONG Pt;
214     PMMPDE PointerPde;
215 
216     if (Address < MmSystemRangeStart)
217     {
218         /* We should have a process for user land addresses */
219         ASSERT(Process != NULL);
220 
221         if(Process != PsGetCurrentProcess())
222         {
223             PMMPDE PdeBase;
224             ULONG PdeOffset = MiGetPdeOffset(Address);
225 
226             /* Nobody but page fault should ask for creating the PDE,
227              * Which imples that Process is the current one */
228             ASSERT(Create == FALSE);
229 
230             PdeBase = MmCreateHyperspaceMapping(PTE_TO_PFN(Process->Pcb.DirectoryTableBase[0]));
231             if (PdeBase == NULL)
232             {
233                 KeBugCheck(MEMORY_MANAGEMENT);
234             }
235             PointerPde = PdeBase + PdeOffset;
236             if (PointerPde->u.Hard.Valid == 0)
237             {
238                 MmDeleteHyperspaceMapping(PdeBase);
239                 return NULL;
240             }
241             else
242             {
243                 Pfn = PointerPde->u.Hard.PageFrameNumber;
244             }
245             MmDeleteHyperspaceMapping(PdeBase);
246             Pt = MmCreateHyperspaceMapping(Pfn);
247             if (Pt == NULL)
248             {
249                 KeBugCheck(MEMORY_MANAGEMENT);
250             }
251             return Pt + MiAddressToPteOffset(Address);
252         }
253         /* This is for our process */
254         PointerPde = MiAddressToPde(Address);
255         Pt = (PULONG)MiAddressToPte(Address);
256         if (PointerPde->u.Hard.Valid == 0)
257         {
258             NTSTATUS Status;
259             if (Create == FALSE)
260             {
261                 return NULL;
262             }
263             ASSERT(PointerPde->u.Long == 0);
264 
265             MI_WRITE_INVALID_PTE(PointerPde, DemandZeroPde);
266             // Tiny HACK: Parameter 1 is the architecture specific FaultCode for an access violation (i.e. page is present)
267 
268             /* Lock the working set, as this will add this address to it */
269             MiLockProcessWorkingSetUnsafe(Process, PsGetCurrentThread());
270 
271             Status = MiDispatchFault(0x1,
272                                      Pt,
273                                      PointerPde,
274                                      NULL,
275                                      FALSE,
276                                      PsGetCurrentProcess(),
277                                      NULL,
278                                      NULL);
279             DBG_UNREFERENCED_LOCAL_VARIABLE(Status);
280             ASSERT(KeAreAllApcsDisabled() == TRUE);
281             ASSERT(PointerPde->u.Hard.Valid == 1);
282 
283             MiUnlockProcessWorkingSetUnsafe(Process, PsGetCurrentThread());
284         }
285         return (PULONG)MiAddressToPte(Address);
286     }
287 
288     /* This is for kernel land address */
289     ASSERT(Process == NULL);
290     PointerPde = MiAddressToPde(Address);
291     Pt = (PULONG)MiAddressToPte(Address);
292     if (PointerPde->u.Hard.Valid == 0)
293     {
294         /* Let ARM3 synchronize the PDE */
295         if(!MiSynchronizeSystemPde(PointerPde))
296         {
297             /* PDE (still) not valid, let ARM3 allocate one if asked */
298             if(Create == FALSE)
299                 return NULL;
300             MiFillSystemPageDirectory(Address, PAGE_SIZE);
301         }
302     }
303     return Pt;
304 }
305 
306 static BOOLEAN MmUnmapPageTable(PULONG Pt)
307 {
308     if (!IS_HYPERSPACE(Pt))
309     {
310         return TRUE;
311     }
312 
313     if (Pt)
314     {
315         MmDeleteHyperspaceMapping((PVOID)PAGE_ROUND_DOWN(Pt));
316     }
317     return FALSE;
318 }
319 
320 static ULONG MmGetPageEntryForProcess(PEPROCESS Process, PVOID Address)
321 {
322     ULONG Pte;
323     PULONG Pt;
324 
325     Pt = MmGetPageTableForProcess(Process, Address, FALSE);
326     if (Pt)
327     {
328         Pte = *Pt;
329         MmUnmapPageTable(Pt);
330         return Pte;
331     }
332     return 0;
333 }
334 
335 PFN_NUMBER
336 NTAPI
337 MmGetPfnForProcess(PEPROCESS Process,
338                    PVOID Address)
339 {
340     ULONG Entry;
341     Entry = MmGetPageEntryForProcess(Process, Address);
342     if (!(Entry & PA_PRESENT))
343     {
344         return 0;
345     }
346     return(PTE_TO_PFN(Entry));
347 }
348 
349 VOID
350 NTAPI
351 MmDeleteVirtualMapping(PEPROCESS Process, PVOID Address,
352                        BOOLEAN* WasDirty, PPFN_NUMBER Page)
353 /*
354  * FUNCTION: Delete a virtual mapping
355  */
356 {
357     BOOLEAN WasValid = FALSE;
358     PFN_NUMBER Pfn;
359     ULONG Pte;
360     PULONG Pt;
361 
362     DPRINT("MmDeleteVirtualMapping(%p, %p, %p, %p)\n",
363            Process, Address, WasDirty, Page);
364 
365     Pt = MmGetPageTableForProcess(Process, Address, FALSE);
366 
367     if (Pt == NULL)
368     {
369         if (WasDirty != NULL)
370         {
371             *WasDirty = FALSE;
372         }
373         if (Page != NULL)
374         {
375             *Page = 0;
376         }
377         return;
378     }
379 
380     /*
381      * Atomically set the entry to zero and get the old value.
382      */
383     Pte = InterlockedExchangePte(Pt, 0);
384 
385     /* We count a mapping as valid if it's a present page, or it's a nonzero pfn with
386      * the swap bit unset, indicating a valid page protected to PAGE_NOACCESS. */
387     WasValid = (Pte & PA_PRESENT) || ((Pte >> PAGE_SHIFT) && !(Pte & 0x800));
388     if (WasValid)
389     {
390         /* Flush the TLB since we transitioned this PTE
391          * from valid to invalid so any stale translations
392          * are removed from the cache */
393         MiFlushTlb(Pt, Address);
394 
395 		if (Address < MmSystemRangeStart)
396 		{
397 			/* Remove PDE reference */
398 			Process->Vm.VmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)]--;
399 			ASSERT(Process->Vm.VmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)] < PTE_PER_PAGE);
400 		}
401 
402         Pfn = PTE_TO_PFN(Pte);
403     }
404     else
405     {
406         MmUnmapPageTable(Pt);
407         Pfn = 0;
408     }
409 
410     /*
411      * Return some information to the caller
412      */
413     if (WasDirty != NULL)
414     {
415         *WasDirty = ((Pte & PA_DIRTY) && (Pte & PA_PRESENT)) ? TRUE : FALSE;
416     }
417     if (Page != NULL)
418     {
419         *Page = Pfn;
420     }
421 }
422 
423 VOID
424 NTAPI
425 MmGetPageFileMapping(PEPROCESS Process, PVOID Address,
426                      SWAPENTRY* SwapEntry)
427 /*
428  * FUNCTION: Get a page file mapping
429  */
430 {
431     ULONG Entry = MmGetPageEntryForProcess(Process, Address);
432     *SwapEntry = Entry >> 1;
433 }
434 
435 VOID
436 NTAPI
437 MmDeletePageFileMapping(PEPROCESS Process, PVOID Address,
438                         SWAPENTRY* SwapEntry)
439 /*
440  * FUNCTION: Delete a virtual mapping
441  */
442 {
443     ULONG Pte;
444     PULONG Pt;
445 
446     Pt = MmGetPageTableForProcess(Process, Address, FALSE);
447 
448     if (Pt == NULL)
449     {
450         *SwapEntry = 0;
451         return;
452     }
453 
454     /*
455      * Atomically set the entry to zero and get the old value.
456      */
457     Pte = InterlockedExchangePte(Pt, 0);
458 
459 	if (Address < MmSystemRangeStart)
460 	{
461 		/* Remove PDE reference */
462 		Process->Vm.VmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)]--;
463 		ASSERT(Process->Vm.VmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)] < PTE_PER_PAGE);
464 	}
465 
466     /* We don't need to flush here because page file entries
467      * are invalid translations, so the processor won't cache them */
468     MmUnmapPageTable(Pt);
469 
470     if ((Pte & PA_PRESENT) || !(Pte & 0x800))
471     {
472         DPRINT1("Pte %x (want not 1 and 0x800)\n", Pte);
473         KeBugCheck(MEMORY_MANAGEMENT);
474     }
475 
476     /*
477      * Return some information to the caller
478      */
479     *SwapEntry = Pte >> 1;
480 }
481 
482 BOOLEAN
483 Mmi386MakeKernelPageTableGlobal(PVOID Address)
484 {
485     PMMPDE PointerPde = MiAddressToPde(Address);
486     PMMPTE PointerPte = MiAddressToPte(Address);
487 
488     if (PointerPde->u.Hard.Valid == 0)
489     {
490         if(!MiSynchronizeSystemPde(PointerPde))
491             return FALSE;
492         return PointerPte->u.Hard.Valid != 0;
493     }
494     return FALSE;
495 }
496 
497 BOOLEAN
498 NTAPI
499 MmIsDirtyPage(PEPROCESS Process, PVOID Address)
500 {
501     return MmGetPageEntryForProcess(Process, Address) & PA_DIRTY ? TRUE : FALSE;
502 }
503 
504 VOID
505 NTAPI
506 MmSetCleanPage(PEPROCESS Process, PVOID Address)
507 {
508     PULONG Pt;
509     ULONG Pte;
510 
511     if (Address < MmSystemRangeStart && Process == NULL)
512     {
513         DPRINT1("MmSetCleanPage is called for user space without a process.\n");
514         KeBugCheck(MEMORY_MANAGEMENT);
515     }
516 
517     Pt = MmGetPageTableForProcess(Process, Address, FALSE);
518     if (Pt == NULL)
519     {
520         KeBugCheck(MEMORY_MANAGEMENT);
521     }
522 
523     do
524     {
525         Pte = *Pt;
526     } while (Pte != InterlockedCompareExchangePte(Pt, Pte & ~PA_DIRTY, Pte));
527 
528     if (!(Pte & PA_PRESENT))
529     {
530         KeBugCheck(MEMORY_MANAGEMENT);
531     }
532     else if (Pte & PA_DIRTY)
533     {
534         MiFlushTlb(Pt, Address);
535     }
536     else
537     {
538         MmUnmapPageTable(Pt);
539     }
540 }
541 
542 VOID
543 NTAPI
544 MmSetDirtyPage(PEPROCESS Process, PVOID Address)
545 {
546     PULONG Pt;
547     ULONG Pte;
548 
549     if (Address < MmSystemRangeStart && Process == NULL)
550     {
551         DPRINT1("MmSetDirtyPage is called for user space without a process.\n");
552         KeBugCheck(MEMORY_MANAGEMENT);
553     }
554 
555     Pt = MmGetPageTableForProcess(Process, Address, FALSE);
556     if (Pt == NULL)
557     {
558         KeBugCheck(MEMORY_MANAGEMENT);
559     }
560 
561     do
562     {
563         Pte = *Pt;
564     } while (Pte != InterlockedCompareExchangePte(Pt, Pte | PA_DIRTY, Pte));
565 
566     if (!(Pte & PA_PRESENT))
567     {
568         KeBugCheck(MEMORY_MANAGEMENT);
569     }
570     else
571     {
572         /* The processor will never clear this bit itself, therefore
573          * we do not need to flush the TLB here when setting it */
574         MmUnmapPageTable(Pt);
575     }
576 }
577 
578 BOOLEAN
579 NTAPI
580 MmIsPagePresent(PEPROCESS Process, PVOID Address)
581 {
582     return MmGetPageEntryForProcess(Process, Address) & PA_PRESENT;
583 }
584 
585 BOOLEAN
586 NTAPI
587 MmIsDisabledPage(PEPROCESS Process, PVOID Address)
588 {
589     ULONG_PTR Entry = MmGetPageEntryForProcess(Process, Address);
590     return !(Entry & PA_PRESENT) && !(Entry & 0x800) && (Entry >> PAGE_SHIFT);
591 }
592 
593 BOOLEAN
594 NTAPI
595 MmIsPageSwapEntry(PEPROCESS Process, PVOID Address)
596 {
597     ULONG Entry;
598     Entry = MmGetPageEntryForProcess(Process, Address);
599     return !(Entry & PA_PRESENT) && (Entry & 0x800);
600 }
601 
602 NTSTATUS
603 NTAPI
604 MmCreatePageFileMapping(PEPROCESS Process,
605                         PVOID Address,
606                         SWAPENTRY SwapEntry)
607 {
608     PULONG Pt;
609     ULONG Pte;
610 
611     if (Process == NULL && Address < MmSystemRangeStart)
612     {
613         DPRINT1("No process\n");
614         KeBugCheck(MEMORY_MANAGEMENT);
615     }
616     if (Process != NULL && Address >= MmSystemRangeStart)
617     {
618         DPRINT1("Setting kernel address with process context\n");
619         KeBugCheck(MEMORY_MANAGEMENT);
620     }
621 
622     if (SwapEntry & (1 << 31))
623     {
624         KeBugCheck(MEMORY_MANAGEMENT);
625     }
626 
627     Pt = MmGetPageTableForProcess(Process, Address, FALSE);
628     if (Pt == NULL)
629     {
630         /* Nobody should page out an address that hasn't even been mapped */
631         /* But we might place a wait entry first, requiring the page table */
632         if (SwapEntry != MM_WAIT_ENTRY)
633         {
634             KeBugCheck(MEMORY_MANAGEMENT);
635         }
636         Pt = MmGetPageTableForProcess(Process, Address, TRUE);
637     }
638     Pte = InterlockedExchangePte(Pt, SwapEntry << 1);
639     if (Pte != 0)
640     {
641         KeBugCheckEx(MEMORY_MANAGEMENT, SwapEntry, (ULONG_PTR)Process, (ULONG_PTR)Address, 0);
642     }
643 
644 	if (Address < MmSystemRangeStart)
645 	{
646 		/* Add PDE reference */
647 		Process->Vm.VmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)]++;
648 		ASSERT(Process->Vm.VmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Address)] <= PTE_PER_PAGE);
649 	}
650 
651     /* We don't need to flush the TLB here because it
652      * only caches valid translations and a zero PTE
653      * is not a valid translation */
654     MmUnmapPageTable(Pt);
655 
656     return(STATUS_SUCCESS);
657 }
658 
659 
660 NTSTATUS
661 NTAPI
662 MmCreateVirtualMappingUnsafe(PEPROCESS Process,
663                              PVOID Address,
664                              ULONG flProtect,
665                              PPFN_NUMBER Pages,
666                              ULONG PageCount)
667 {
668     ULONG Attributes;
669     PVOID Addr;
670     ULONG i;
671     ULONG oldPdeOffset, PdeOffset;
672     PULONG Pt = NULL;
673     ULONG Pte;
674     DPRINT("MmCreateVirtualMappingUnsafe(%p, %p, %lu, %p (%x), %lu)\n",
675            Process, Address, flProtect, Pages, *Pages, PageCount);
676 
677     ASSERT(((ULONG_PTR)Address % PAGE_SIZE) == 0);
678 
679     if (Process == NULL)
680     {
681         if (Address < MmSystemRangeStart)
682         {
683             DPRINT1("NULL process given for user-mode mapping at %p -- %lu pages starting at %Ix\n", Address, PageCount, *Pages);
684             KeBugCheck(MEMORY_MANAGEMENT);
685         }
686         if (PageCount > 0x10000 ||
687             (ULONG_PTR) Address / PAGE_SIZE + PageCount > 0x100000)
688         {
689             DPRINT1("Page count too large for kernel-mode mapping at %p -- %lu pages starting at %Ix\n", Address, PageCount, *Pages);
690             KeBugCheck(MEMORY_MANAGEMENT);
691         }
692     }
693     else
694     {
695         if (Address >= MmSystemRangeStart)
696         {
697             DPRINT1("Process %p given for kernel-mode mapping at %p -- %lu pages starting at %Ix\n", Process, Address, PageCount, *Pages);
698             KeBugCheck(MEMORY_MANAGEMENT);
699         }
700         if (PageCount > (ULONG_PTR)MmSystemRangeStart / PAGE_SIZE ||
701             (ULONG_PTR) Address / PAGE_SIZE + PageCount >
702             (ULONG_PTR)MmSystemRangeStart / PAGE_SIZE)
703         {
704             DPRINT1("Page count too large for process %p user-mode mapping at %p -- %lu pages starting at %Ix\n", Process, Address, PageCount, *Pages);
705             KeBugCheck(MEMORY_MANAGEMENT);
706         }
707     }
708 
709     Attributes = ProtectToPTE(flProtect);
710     Attributes &= 0xfff;
711     if (Address >= MmSystemRangeStart)
712     {
713         Attributes &= ~PA_USER;
714     }
715     else
716     {
717         Attributes |= PA_USER;
718     }
719 
720     Addr = Address;
721     /* MmGetPageTableForProcess should be called on the first run, so
722      * let this trigger it */
723     oldPdeOffset = ADDR_TO_PDE_OFFSET(Addr) + 1;
724     for (i = 0; i < PageCount; i++, Addr = (PVOID)((ULONG_PTR)Addr + PAGE_SIZE))
725     {
726         if (!(Attributes & PA_PRESENT) && Pages[i] != 0)
727         {
728             DPRINT1("Setting physical address but not allowing access at address "
729                     "0x%p with attributes %x/%x.\n",
730                     Addr, Attributes, flProtect);
731             KeBugCheck(MEMORY_MANAGEMENT);
732         }
733         PdeOffset = ADDR_TO_PDE_OFFSET(Addr);
734         if (oldPdeOffset != PdeOffset)
735         {
736             if(Pt) MmUnmapPageTable(Pt);
737             Pt = MmGetPageTableForProcess(Process, Addr, TRUE);
738             if (Pt == NULL)
739             {
740                 KeBugCheck(MEMORY_MANAGEMENT);
741             }
742         }
743         else
744         {
745             Pt++;
746         }
747         oldPdeOffset = PdeOffset;
748 
749         Pte = InterlockedExchangePte(Pt, PFN_TO_PTE(Pages[i]) | Attributes);
750 
751         /* There should not be anything valid here */
752         if (Pte != 0)
753         {
754             DPRINT1("Bad PTE %lx at %p for %p + %lu\n", Pte, Pt, Address, i);
755             KeBugCheck(MEMORY_MANAGEMENT);
756         }
757 
758         /* We don't need to flush the TLB here because it only caches valid translations
759          * and we're moving this PTE from invalid to valid so it can't be cached right now */
760 
761 		if (Addr < MmSystemRangeStart)
762 		{
763 			/* Add PDE reference */
764 			Process->Vm.VmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Addr)]++;
765 			ASSERT(Process->Vm.VmWorkingSetList->UsedPageTableEntries[MiGetPdeOffset(Addr)] <= PTE_PER_PAGE);
766 		}
767     }
768 
769     ASSERT(Addr > Address);
770     MmUnmapPageTable(Pt);
771 
772     return(STATUS_SUCCESS);
773 }
774 
775 NTSTATUS
776 NTAPI
777 MmCreateVirtualMapping(PEPROCESS Process,
778                        PVOID Address,
779                        ULONG flProtect,
780                        PPFN_NUMBER Pages,
781                        ULONG PageCount)
782 {
783     ULONG i;
784 
785     ASSERT((ULONG_PTR)Address % PAGE_SIZE == 0);
786     for (i = 0; i < PageCount; i++)
787     {
788         if (!MmIsPageInUse(Pages[i]))
789         {
790             DPRINT1("Page at address %x not in use\n", PFN_TO_PTE(Pages[i]));
791             KeBugCheck(MEMORY_MANAGEMENT);
792         }
793     }
794 
795     return(MmCreateVirtualMappingUnsafe(Process,
796                                         Address,
797                                         flProtect,
798                                         Pages,
799                                         PageCount));
800 }
801 
802 ULONG
803 NTAPI
804 MmGetPageProtect(PEPROCESS Process, PVOID Address)
805 {
806     ULONG Entry;
807     ULONG Protect;
808 
809     Entry = MmGetPageEntryForProcess(Process, Address);
810 
811 
812     if (!(Entry & PA_PRESENT))
813     {
814         Protect = PAGE_NOACCESS;
815     }
816     else
817     {
818         if (Entry & PA_READWRITE)
819         {
820             Protect = PAGE_READWRITE;
821         }
822         else
823         {
824             Protect = PAGE_EXECUTE_READ;
825         }
826         if (Entry & PA_CD)
827         {
828             Protect |= PAGE_NOCACHE;
829         }
830         if (Entry & PA_WT)
831         {
832             Protect |= PAGE_WRITETHROUGH;
833         }
834         if (!(Entry & PA_USER))
835         {
836             Protect |= PAGE_SYSTEM;
837         }
838 
839     }
840     return(Protect);
841 }
842 
843 VOID
844 NTAPI
845 MmSetPageProtect(PEPROCESS Process, PVOID Address, ULONG flProtect)
846 {
847     ULONG Attributes = 0;
848     PULONG Pt;
849     ULONG Pte;
850 
851     DPRINT("MmSetPageProtect(Process %p  Address %p  flProtect %x)\n",
852            Process, Address, flProtect);
853 
854     Attributes = ProtectToPTE(flProtect);
855 
856     Attributes &= 0xfff;
857     if (Address >= MmSystemRangeStart)
858     {
859         Attributes &= ~PA_USER;
860     }
861     else
862     {
863         Attributes |= PA_USER;
864     }
865 
866     Pt = MmGetPageTableForProcess(Process, Address, FALSE);
867     if (Pt == NULL)
868     {
869         KeBugCheck(MEMORY_MANAGEMENT);
870     }
871     Pte = InterlockedExchangePte(Pt, PAGE_MASK(*Pt) | Attributes | (*Pt & (PA_ACCESSED|PA_DIRTY)));
872 
873     // We should be able to bring a page back from PAGE_NOACCESS
874     if ((Pte & 0x800) || !(Pte >> PAGE_SHIFT))
875     {
876         DPRINT1("Invalid Pte %lx\n", Pte);
877         KeBugCheck(MEMORY_MANAGEMENT);
878     }
879 
880     if((Pte & Attributes) != Attributes)
881         MiFlushTlb(Pt, Address);
882     else
883         MmUnmapPageTable(Pt);
884 }
885 
886 INIT_FUNCTION
887 VOID
888 NTAPI
889 MmInitGlobalKernelPageDirectory(VOID)
890 {
891     /* Nothing to do here */
892 }
893 
894 /* EOF */
895