xref: /reactos/ntoskrnl/mm/freelist.c (revision ba3f0743)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS kernel
4  * FILE:            ntoskrnl/mm/freelist.c
5  * PURPOSE:         Handle the list of free physical pages
6  *
7  * PROGRAMMERS:     David Welch (welch@cwcom.net)
8  *                  Robert Bergkvist
9  */
10 
11 /* INCLUDES ****************************************************************/
12 
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <debug.h>
16 
17 #define MODULE_INVOLVED_IN_ARM3
18 #include "ARM3/miarm.h"
19 
20 #define ASSERT_IS_ROS_PFN(x) ASSERT(MI_IS_ROS_PFN(x) == TRUE);
21 
22 /* GLOBALS ****************************************************************/
23 
24 PMMPFN MmPfnDatabase;
25 
26 PFN_NUMBER MmAvailablePages;
27 PFN_NUMBER MmResidentAvailablePages;
28 PFN_NUMBER MmResidentAvailableAtInit;
29 
30 SIZE_T MmTotalCommittedPages;
31 SIZE_T MmSharedCommit;
32 SIZE_T MmDriverCommit;
33 SIZE_T MmProcessCommit;
34 SIZE_T MmPagedPoolCommit;
35 SIZE_T MmPeakCommitment;
36 SIZE_T MmtotalCommitLimitMaximum;
37 
38 static RTL_BITMAP MiUserPfnBitMap;
39 
40 /* FUNCTIONS *************************************************************/
41 
42 VOID
43 NTAPI
44 MiInitializeUserPfnBitmap(VOID)
45 {
46     PVOID Bitmap;
47 
48     /* Allocate enough buffer for the PFN bitmap and align it on 32-bits */
49     Bitmap = ExAllocatePoolWithTag(NonPagedPool,
50                                    (((MmHighestPhysicalPage + 1) + 31) / 32) * 4,
51                                    TAG_MM);
52     ASSERT(Bitmap);
53 
54     /* Initialize it and clear all the bits to begin with */
55     RtlInitializeBitMap(&MiUserPfnBitMap,
56                         Bitmap,
57                         (ULONG)MmHighestPhysicalPage + 1);
58     RtlClearAllBits(&MiUserPfnBitMap);
59 }
60 
61 PFN_NUMBER
62 NTAPI
63 MmGetLRUFirstUserPage(VOID)
64 {
65     ULONG Position;
66     KIRQL OldIrql;
67 
68     /* Find the first user page */
69     OldIrql = MiAcquirePfnLock();
70     Position = RtlFindSetBits(&MiUserPfnBitMap, 1, 0);
71     MiReleasePfnLock(OldIrql);
72     if (Position == 0xFFFFFFFF) return 0;
73 
74     /* Return it */
75     ASSERT(Position != 0);
76     ASSERT_IS_ROS_PFN(MiGetPfnEntry(Position));
77     return Position;
78 }
79 
80 VOID
81 NTAPI
82 MmInsertLRULastUserPage(PFN_NUMBER Pfn)
83 {
84     KIRQL OldIrql;
85 
86     /* Set the page as a user page */
87     ASSERT(Pfn != 0);
88     ASSERT_IS_ROS_PFN(MiGetPfnEntry(Pfn));
89     ASSERT(!RtlCheckBit(&MiUserPfnBitMap, (ULONG)Pfn));
90     OldIrql = MiAcquirePfnLock();
91     RtlSetBit(&MiUserPfnBitMap, (ULONG)Pfn);
92     MiReleasePfnLock(OldIrql);
93 }
94 
95 PFN_NUMBER
96 NTAPI
97 MmGetLRUNextUserPage(PFN_NUMBER PreviousPfn)
98 {
99     ULONG Position;
100     KIRQL OldIrql;
101 
102     /* Find the next user page */
103     OldIrql = MiAcquirePfnLock();
104     Position = RtlFindSetBits(&MiUserPfnBitMap, 1, (ULONG)PreviousPfn + 1);
105     MiReleasePfnLock(OldIrql);
106     if (Position == 0xFFFFFFFF) return 0;
107 
108     /* Return it */
109     ASSERT(Position != 0);
110     ASSERT_IS_ROS_PFN(MiGetPfnEntry(Position));
111     return Position;
112 }
113 
114 VOID
115 NTAPI
116 MmRemoveLRUUserPage(PFN_NUMBER Page)
117 {
118     KIRQL OldIrql;
119 
120     /* Unset the page as a user page */
121     ASSERT(Page != 0);
122     ASSERT_IS_ROS_PFN(MiGetPfnEntry(Page));
123     ASSERT(RtlCheckBit(&MiUserPfnBitMap, (ULONG)Page));
124     OldIrql = MiAcquirePfnLock();
125     RtlClearBit(&MiUserPfnBitMap, (ULONG)Page);
126     MiReleasePfnLock(OldIrql);
127 }
128 
129 BOOLEAN
130 NTAPI
131 MiIsPfnFree(IN PMMPFN Pfn1)
132 {
133     /* Must be a free or zero page, with no references, linked */
134     return ((Pfn1->u3.e1.PageLocation <= StandbyPageList) &&
135             (Pfn1->u1.Flink) &&
136             (Pfn1->u2.Blink) &&
137             !(Pfn1->u3.e2.ReferenceCount));
138 }
139 
140 BOOLEAN
141 NTAPI
142 MiIsPfnInUse(IN PMMPFN Pfn1)
143 {
144     /* Standby list or higher, unlinked, and with references */
145     return !MiIsPfnFree(Pfn1);
146 }
147 
148 PMDL
149 NTAPI
150 MiAllocatePagesForMdl(IN PHYSICAL_ADDRESS LowAddress,
151                       IN PHYSICAL_ADDRESS HighAddress,
152                       IN PHYSICAL_ADDRESS SkipBytes,
153                       IN SIZE_T TotalBytes,
154                       IN MI_PFN_CACHE_ATTRIBUTE CacheAttribute,
155                       IN ULONG MdlFlags)
156 {
157     PMDL Mdl;
158     PFN_NUMBER PageCount, LowPage, HighPage, SkipPages, PagesFound = 0, Page;
159     PPFN_NUMBER MdlPage, LastMdlPage;
160     KIRQL OldIrql;
161     PMMPFN Pfn1;
162     INT LookForZeroedPages;
163     ASSERT(KeGetCurrentIrql() <= APC_LEVEL);
164     DPRINT1("ARM3-DEBUG: Being called with %I64x %I64x %I64x %lx %d %lu\n", LowAddress, HighAddress, SkipBytes, TotalBytes, CacheAttribute, MdlFlags);
165 
166     //
167     // Convert the low address into a PFN
168     //
169     LowPage = (PFN_NUMBER)(LowAddress.QuadPart >> PAGE_SHIFT);
170 
171     //
172     // Convert, and normalize, the high address into a PFN
173     //
174     HighPage = (PFN_NUMBER)(HighAddress.QuadPart >> PAGE_SHIFT);
175     if (HighPage > MmHighestPhysicalPage) HighPage = MmHighestPhysicalPage;
176 
177     //
178     // Validate skipbytes and convert them into pages
179     //
180     if (BYTE_OFFSET(SkipBytes.LowPart)) return NULL;
181     SkipPages = (PFN_NUMBER)(SkipBytes.QuadPart >> PAGE_SHIFT);
182 
183     /* This isn't supported at all */
184     if (SkipPages) DPRINT1("WARNING: Caller requesting SkipBytes, MDL might be mismatched\n");
185 
186     //
187     // Now compute the number of pages the MDL will cover
188     //
189     PageCount = (PFN_NUMBER)ADDRESS_AND_SIZE_TO_SPAN_PAGES(0, TotalBytes);
190     do
191     {
192         //
193         // Try creating an MDL for these many pages
194         //
195         Mdl = MmCreateMdl(NULL, NULL, PageCount << PAGE_SHIFT);
196         if (Mdl) break;
197 
198         //
199         // This function is not required to return the amount of pages requested
200         // In fact, it can return as little as 1 page, and callers are supposed
201         // to deal with this scenario. So re-attempt the allocation with less
202         // pages than before, and see if it worked this time.
203         //
204         PageCount -= (PageCount >> 4);
205     } while (PageCount);
206 
207     //
208     // Wow, not even a single page was around!
209     //
210     if (!Mdl) return NULL;
211 
212     //
213     // This is where the page array starts....
214     //
215     MdlPage = (PPFN_NUMBER)(Mdl + 1);
216 
217     //
218     // Lock the PFN database
219     //
220     OldIrql = MiAcquirePfnLock();
221 
222     //
223     // Are we looking for any pages, without discriminating?
224     //
225     if ((LowPage == 0) && (HighPage == MmHighestPhysicalPage))
226     {
227         //
228         // Well then, let's go shopping
229         //
230         while (PagesFound < PageCount)
231         {
232             /* Grab a page */
233             MI_SET_USAGE(MI_USAGE_MDL);
234             MI_SET_PROCESS2("Kernel");
235 
236             /* FIXME: This check should be smarter */
237             Page = 0;
238             if (MmAvailablePages != 0)
239                 Page = MiRemoveAnyPage(0);
240 
241             if (Page == 0)
242             {
243                 /* This is not good... hopefully we have at least SOME pages */
244                 ASSERT(PagesFound);
245                 break;
246             }
247 
248             /* Grab the page entry for it */
249             Pfn1 = MiGetPfnEntry(Page);
250 
251             //
252             // Make sure it's really free
253             //
254             ASSERT(Pfn1->u3.e2.ReferenceCount == 0);
255 
256             /* Now setup the page and mark it */
257             Pfn1->u3.e2.ReferenceCount = 1;
258             Pfn1->u2.ShareCount = 1;
259             MI_SET_PFN_DELETED(Pfn1);
260             Pfn1->u4.PteFrame = 0x1FFEDCB;
261             Pfn1->u3.e1.StartOfAllocation = 1;
262             Pfn1->u3.e1.EndOfAllocation = 1;
263             Pfn1->u4.VerifierAllocation = 0;
264 
265             //
266             // Save it into the MDL
267             //
268             *MdlPage++ = MiGetPfnEntryIndex(Pfn1);
269             PagesFound++;
270         }
271     }
272     else
273     {
274         //
275         // You want specific range of pages. We'll do this in two runs
276         //
277         for (LookForZeroedPages = 1; LookForZeroedPages >= 0; LookForZeroedPages--)
278         {
279             //
280             // Scan the range you specified
281             //
282             for (Page = LowPage; Page < HighPage; Page++)
283             {
284                 //
285                 // Get the PFN entry for this page
286                 //
287                 Pfn1 = MiGetPfnEntry(Page);
288                 ASSERT(Pfn1);
289 
290                 //
291                 // Make sure it's free and if this is our first pass, zeroed
292                 //
293                 if (MiIsPfnInUse(Pfn1)) continue;
294                 if ((Pfn1->u3.e1.PageLocation == ZeroedPageList) != LookForZeroedPages) continue;
295 
296                 /* Remove the page from the free or zero list */
297                 ASSERT(Pfn1->u3.e1.ReadInProgress == 0);
298                 MI_SET_USAGE(MI_USAGE_MDL);
299                 MI_SET_PROCESS2("Kernel");
300                 MiUnlinkFreeOrZeroedPage(Pfn1);
301 
302                 //
303                 // Sanity checks
304                 //
305                 ASSERT(Pfn1->u3.e2.ReferenceCount == 0);
306 
307                 //
308                 // Now setup the page and mark it
309                 //
310                 Pfn1->u3.e2.ReferenceCount = 1;
311                 Pfn1->u2.ShareCount = 1;
312                 MI_SET_PFN_DELETED(Pfn1);
313                 Pfn1->u4.PteFrame = 0x1FFEDCB;
314                 Pfn1->u3.e1.StartOfAllocation = 1;
315                 Pfn1->u3.e1.EndOfAllocation = 1;
316                 Pfn1->u4.VerifierAllocation = 0;
317 
318                 //
319                 // Save this page into the MDL
320                 //
321                 *MdlPage++ = Page;
322                 if (++PagesFound == PageCount) break;
323             }
324 
325             //
326             // If the first pass was enough, don't keep going, otherwise, go again
327             //
328             if (PagesFound == PageCount) break;
329         }
330     }
331 
332     //
333     // Now release the PFN count
334     //
335     MiReleasePfnLock(OldIrql);
336 
337     //
338     // We might've found less pages, but not more ;-)
339     //
340     if (PagesFound != PageCount) ASSERT(PagesFound < PageCount);
341     if (!PagesFound)
342     {
343         //
344         // If we didn' tfind any pages at all, fail
345         //
346         DPRINT1("NO MDL PAGES!\n");
347         ExFreePoolWithTag(Mdl, TAG_MDL);
348         return NULL;
349     }
350 
351     //
352     // Write out how many pages we found
353     //
354     Mdl->ByteCount = (ULONG)(PagesFound << PAGE_SHIFT);
355 
356     //
357     // Terminate the MDL array if there's certain missing pages
358     //
359     if (PagesFound != PageCount) *MdlPage = LIST_HEAD;
360 
361     //
362     // Now go back and loop over all the MDL pages
363     //
364     MdlPage = (PPFN_NUMBER)(Mdl + 1);
365     LastMdlPage = MdlPage + PagesFound;
366     while (MdlPage < LastMdlPage)
367     {
368         //
369         // Check if we've reached the end
370         //
371         Page = *MdlPage++;
372         if (Page == LIST_HEAD) break;
373 
374         //
375         // Get the PFN entry for the page and check if we should zero it out
376         //
377         Pfn1 = MiGetPfnEntry(Page);
378         ASSERT(Pfn1);
379         if (Pfn1->u3.e1.PageLocation != ZeroedPageList) MiZeroPhysicalPage(Page);
380         Pfn1->u3.e1.PageLocation = ActiveAndValid;
381     }
382 
383     //
384     // We're done, mark the pages as locked
385     //
386     Mdl->Process = NULL;
387     Mdl->MdlFlags |= MDL_PAGES_LOCKED;
388     return Mdl;
389 }
390 
391 VOID
392 NTAPI
393 MmSetRmapListHeadPage(PFN_NUMBER Pfn, PMM_RMAP_ENTRY ListHead)
394 {
395     KIRQL oldIrql;
396     PMMPFN Pfn1;
397 
398     oldIrql = MiAcquirePfnLock();
399     Pfn1 = MiGetPfnEntry(Pfn);
400     ASSERT(Pfn1);
401     ASSERT_IS_ROS_PFN(Pfn1);
402 
403     if (ListHead)
404     {
405         /* Should not be trying to insert an RMAP for a non-active page */
406         ASSERT(MiIsPfnInUse(Pfn1) == TRUE);
407 
408         /* Set the list head address */
409         Pfn1->RmapListHead = ListHead;
410     }
411     else
412     {
413         /* ReactOS semantics dictate the page is STILL active right now */
414         ASSERT(MiIsPfnInUse(Pfn1) == TRUE);
415 
416         /* In this case, the RMAP is actually being removed, so clear field */
417         Pfn1->RmapListHead = NULL;
418 
419         /* ReactOS semantics will now release the page, which will make it free and enter a colored list */
420     }
421 
422     MiReleasePfnLock(oldIrql);
423 }
424 
425 PMM_RMAP_ENTRY
426 NTAPI
427 MmGetRmapListHeadPage(PFN_NUMBER Pfn)
428 {
429     KIRQL oldIrql;
430     PMM_RMAP_ENTRY ListHead;
431     PMMPFN Pfn1;
432 
433     /* Lock PFN database */
434     oldIrql = MiAcquirePfnLock();
435 
436     /* Get the entry */
437     Pfn1 = MiGetPfnEntry(Pfn);
438     ASSERT(Pfn1);
439     ASSERT_IS_ROS_PFN(Pfn1);
440 
441     /* Get the list head */
442     ListHead = Pfn1->RmapListHead;
443 
444     /* Should not have an RMAP for a non-active page */
445     ASSERT(MiIsPfnInUse(Pfn1) == TRUE);
446 
447     /* Release PFN database and return rmap list head */
448     MiReleasePfnLock(oldIrql);
449     return ListHead;
450 }
451 
452 VOID
453 NTAPI
454 MmSetSavedSwapEntryPage(PFN_NUMBER Pfn,  SWAPENTRY SwapEntry)
455 {
456     KIRQL oldIrql;
457     PMMPFN Pfn1;
458 
459     Pfn1 = MiGetPfnEntry(Pfn);
460     ASSERT(Pfn1);
461     ASSERT_IS_ROS_PFN(Pfn1);
462 
463     oldIrql = MiAcquirePfnLock();
464     Pfn1->u1.SwapEntry = SwapEntry;
465     MiReleasePfnLock(oldIrql);
466 }
467 
468 SWAPENTRY
469 NTAPI
470 MmGetSavedSwapEntryPage(PFN_NUMBER Pfn)
471 {
472     SWAPENTRY SwapEntry;
473     KIRQL oldIrql;
474     PMMPFN Pfn1;
475 
476     Pfn1 = MiGetPfnEntry(Pfn);
477     ASSERT(Pfn1);
478     ASSERT_IS_ROS_PFN(Pfn1);
479 
480     oldIrql = MiAcquirePfnLock();
481     SwapEntry = Pfn1->u1.SwapEntry;
482     MiReleasePfnLock(oldIrql);
483 
484     return(SwapEntry);
485 }
486 
487 VOID
488 NTAPI
489 MmReferencePage(PFN_NUMBER Pfn)
490 {
491     PMMPFN Pfn1;
492 
493     DPRINT("MmReferencePage(PysicalAddress %x)\n", Pfn << PAGE_SHIFT);
494 
495     MI_ASSERT_PFN_LOCK_HELD();
496     ASSERT(Pfn != 0);
497     ASSERT(Pfn <= MmHighestPhysicalPage);
498 
499     Pfn1 = MiGetPfnEntry(Pfn);
500     ASSERT(Pfn1);
501     ASSERT_IS_ROS_PFN(Pfn1);
502 
503     ASSERT(Pfn1->u3.e2.ReferenceCount != 0);
504     Pfn1->u3.e2.ReferenceCount++;
505 }
506 
507 ULONG
508 NTAPI
509 MmGetReferenceCountPage(PFN_NUMBER Pfn)
510 {
511     KIRQL oldIrql;
512     ULONG RCount;
513     PMMPFN Pfn1;
514 
515     DPRINT("MmGetReferenceCountPage(PhysicalAddress %x)\n", Pfn << PAGE_SHIFT);
516 
517     oldIrql = MiAcquirePfnLock();
518     Pfn1 = MiGetPfnEntry(Pfn);
519     ASSERT(Pfn1);
520     ASSERT_IS_ROS_PFN(Pfn1);
521 
522     RCount = Pfn1->u3.e2.ReferenceCount;
523 
524     MiReleasePfnLock(oldIrql);
525     return(RCount);
526 }
527 
528 BOOLEAN
529 NTAPI
530 MmIsPageInUse(PFN_NUMBER Pfn)
531 {
532     return MiIsPfnInUse(MiGetPfnEntry(Pfn));
533 }
534 
535 VOID
536 NTAPI
537 MmDereferencePage(PFN_NUMBER Pfn)
538 {
539     PMMPFN Pfn1;
540     KIRQL OldIrql;
541     DPRINT("MmDereferencePage(PhysicalAddress %x)\n", Pfn << PAGE_SHIFT);
542 
543     OldIrql = MiAcquirePfnLock();
544 
545     Pfn1 = MiGetPfnEntry(Pfn);
546     ASSERT(Pfn1);
547     ASSERT_IS_ROS_PFN(Pfn1);
548 
549     ASSERT(Pfn1->u3.e2.ReferenceCount != 0);
550     Pfn1->u3.e2.ReferenceCount--;
551     if (Pfn1->u3.e2.ReferenceCount == 0)
552     {
553         /* Mark the page temporarily as valid, we're going to make it free soon */
554         Pfn1->u3.e1.PageLocation = ActiveAndValid;
555 
556         /* It's not a ROS PFN anymore */
557         Pfn1->u4.AweAllocation = FALSE;
558 
559         /* Bring it back into the free list */
560         DPRINT("Legacy free: %lx\n", Pfn);
561         MiInsertPageInFreeList(Pfn);
562     }
563 
564     MiReleasePfnLock(OldIrql);
565 }
566 
567 PFN_NUMBER
568 NTAPI
569 MmAllocPage(ULONG Type)
570 {
571     PFN_NUMBER PfnOffset;
572     PMMPFN Pfn1;
573     KIRQL OldIrql;
574 
575     OldIrql = MiAcquirePfnLock();
576 
577     PfnOffset = MiRemoveZeroPage(MI_GET_NEXT_COLOR());
578     if (!PfnOffset)
579     {
580         KeBugCheck(NO_PAGES_AVAILABLE);
581     }
582 
583     DPRINT("Legacy allocate: %lx\n", PfnOffset);
584     Pfn1 = MiGetPfnEntry(PfnOffset);
585     Pfn1->u3.e2.ReferenceCount = 1;
586     Pfn1->u3.e1.PageLocation = ActiveAndValid;
587 
588     /* This marks the PFN as a ReactOS PFN */
589     Pfn1->u4.AweAllocation = TRUE;
590 
591     /* Allocate the extra ReactOS Data and zero it out */
592     Pfn1->u1.SwapEntry = 0;
593     Pfn1->RmapListHead = NULL;
594 
595     MiReleasePfnLock(OldIrql);
596     return PfnOffset;
597 }
598 
599 /* EOF */
600