/** * PROJECT: ExectOS * COPYRIGHT: See COPYING.md in the top level directory * FILE: xtoskrnl/mm/pfn.cc * DESCRIPTION: Physical Frame Number (PFN) support * DEVELOPERS: Aiken Harris * Rafal Kupiec */ #include /** * Allocates a block of physical pages for early kernel initialization. * * @param NumberOfPages * The number of physical pages to allocate. * * @return This routine returns the base page frame number (PFN) of the allocated block. * * @since XT 1.0 */ XTAPI PFN_NUMBER MM::Pfn::AllocateBootstrapPages(IN PFN_NUMBER NumberOfPages) { PFN_NUMBER Pfn; /* Check if the largest free memory block has enough pages */ if(NumberOfPages > FreeDescriptor->PageCount) { /* Not enough physical memory available, kernel panic */ DebugPrint(L"Insufficient physical pages! Install additional memory\n"); KE::Crash::Panic(0); } /* Allocate pages from the beginning of the free descriptor */ Pfn = FreeDescriptor->BasePage; FreeDescriptor->BasePage += NumberOfPages; FreeDescriptor->PageCount -= NumberOfPages; /* Return the base page frame number of the allocated block */ return Pfn; } /** * Allocates a physical page frame (PFN) from one of the system's free page lists. * * @param Color * The preferred page color, used to optimize CPU cache alignment and reduce cache contention. * * @return This routine returns the Page Frame Number (PFN) of the allocated page. * * @since XT 1.0 */ XTAPI PFN_NUMBER MM::Pfn::AllocatePhysicalPage(IN ULONG Color) { PFN_NUMBER PageNumber; ULONG PagingColorsMask; /* Check if any physical pages are available in the system */ if(!AvailablePages) { /* No physical pages are available in the system, return 0 */ return 0; } /* Retrieve the bitmask used for calculating a page's color */ PagingColorsMask = MM::Colors::GetPagingColorsMask(); /* Attempt to retrieve a page from the colored free page list */ PageNumber = MM::Colors::GetFreePages(FreePageList, Color)->Flink; if(PageNumber == MAXULONG_PTR) { /* No page was found in the colored free page list, check the colored zero page list */ PageNumber = MM::Colors::GetFreePages(ZeroedPageList, Color)->Flink; } /* Attempt to retrieve a page from the colored zero list */ if(PageNumber == MAXULONG_PTR) { /* No page was found in the colored zero page list, check the global free page list */ PageNumber = FreePagesList.Flink; } /* Attempt to retrieve a page from the global free page list */ if(PageNumber == MAXULONG_PTR) { /* No page was found in the global free page list, check the global zero page list */ PageNumber = ZeroedPagesList.Flink; } /* Remove the page from its list and return its PFN */ return UnlinkFreePage(PageNumber, PageNumber & PagingColorsMask); } /** * Calculates the total number of pages required for the PFN database and its associated color tables. * * @return This routine does not return any value. * * @since XT 1.0 */ XTAPI VOID MM::Pfn::ComputePfnDatabaseSize(VOID) { /* Calculate the total number of pages required for the PFN database */ PfnDatabaseSize = (HighestPhysicalPage + 1) * sizeof(MMPFN); PfnDatabaseSize += (MM::Colors::GetPagingColors() * sizeof(MMCOLOR_TABLES) * 2); PfnDatabaseSize = ROUND_UP(PfnDatabaseSize, MM_PAGE_SIZE); PfnDatabaseSize >>= MM_PAGE_SHIFT; } /** * Decrements the global count of available pages. * * @return This routine does not return any value. * * @since XT 1.0 */ XTAPI VOID MM::Pfn::DecrementAvailablePages(VOID) { /* Decrement the global count of available pages */ AvailablePages--; } /** * Retrieves the highest physical page number (PFN) detected in the system. * * @return This routine returns the highest physical page number. * * @since XT 1.0 */ XTAPI ULONG_PTR MM::Pfn::GetHighestPhysicalPage(VOID) { /* Return the highest physical page number */ return HighestPhysicalPage; } /** * Retrieves the total number of physical pages managed by the system. * * @return Returns the total count of physical memory pages. * * @since XT 1.0 */ XTAPI ULONGLONG MM::Pfn::GetNumberOfPhysicalPages(VOID) { /* Return the number of physical pages */ return NumberOfPhysicalPages; } /** * Gets the size of the PFN database and its associated structures, in pages. * * @return This routine returns the total number of pages required for the PFN database and its associated structures. * * @since XT 1.0 */ XTAPI PFN_NUMBER MM::Pfn::GetPfnDatabaseSize(VOID) { /* Return the pre-calculated size of the PFN database in pages */ return PfnDatabaseSize; } /** * Retrieves a pointer to the PFN database entry for a given physical page. * * @param Pfn * The Page Frame Number (PFN) to look up. * * @return This routine returns a pointer to the MMPFN structure for the given PFN, or NULLPTR if the PFN is invalid. * * @since XT 1.0 */ XTAPI PMMPFN MM::Pfn::GetPfnEntry(IN PFN_NUMBER Pfn) { PMMMEMORY_LAYOUT MemoryLayout; /* Validate that the PFN is within the range of managed physical memory */ if(Pfn > HighestPhysicalPage) { /* The requested page number is outside the bounds, return NULLPTR */ return NULLPTR; } /* Get the memory layout */ MemoryLayout = MM::Manager::GetMemoryLayout(); /* Calculate the address of the PFN entry by indexing into the PFN database array and return it */ return &((PMMPFN)MemoryLayout->PfnDatabaseAddress)[Pfn]; } /** * Increments the global count of available pages. * * @return This routine does not return any value. * * @since XT 1.0 */ XTAPI VOID MM::Pfn::IncrementAvailablePages(VOID) { /* Increment the global count of available pages */ AvailablePages++; } /** * Links a physical page to the appropriate free lists. * * @param PageFrameIndex * The Page Frame Number (PFN) of the page to link. * * @return This routine does not return any value. * * @since XT 1.0 */ XTAPI VOID MM::Pfn::LinkFreePage(IN PFN_NUMBER PageFrameIndex) { ULONG Color; PMMPFNLIST ListHead; PFN_NUMBER LastPage; PMMPFN ColoredPfn, PfnEntry; PMMCOLOR_TABLES ColorTable; PMMMEMORY_LAYOUT MemoryLayout; /* Get the PFN database entry for the page */ PfnEntry = GetPfnEntry(PageFrameIndex); /* Check if the page is part of a ROM image */ if(PfnEntry->u3.e1.Rom == 1) { /* Mark the page as inactive and clear its links */ PfnEntry->u1.Flink = 0; PfnEntry->u3.e1.PageLocation = 0; /* Do not free ROM pages */ return; } /* Check if the page is marked for removal */ if(PfnEntry->u3.e1.RemovalRequested == 1) { /* Update cache attribute to not mapped */ PfnEntry->u3.e1.CacheAttribute = PfnNotMapped; /* Do not add it to the free list */ return; } /* Insert the page into the global free list */ ListHead = &FreePagesList; ListHead->Total++; /* Get the current last page on the list */ LastPage = ListHead->Blink; /* Check if the list is not empty */ if(LastPage != MAXULONG_PTR) { /* Link with the previous last page */ GetPfnEntry(LastPage)->u1.Flink = PageFrameIndex; } else { /* Put the page as the first entry */ ListHead->Flink = PageFrameIndex; } /* Set the page as the new tail of the list */ ListHead->Blink = PageFrameIndex; PfnEntry->u1.Flink = MAXULONG_PTR; PfnEntry->u2.Blink = LastPage; PfnEntry->u3.e1.CacheAttribute = PfnNotMapped; PfnEntry->u3.e1.PageLocation = FreePageList; PfnEntry->u4.AweAllocation = 0; PfnEntry->u4.InPageError = 0; PfnEntry->u4.Priority = 3; /* Insert the page into the colored free list */ Color = PageFrameIndex & MM::Colors::GetPagingColorsMask(); ColorTable = MM::Colors::GetFreePages(FreePageList, Color); /* Get the memory layout */ MemoryLayout = MM::Manager::GetMemoryLayout(); /* Check if the colored list is empty */ if(ColorTable->Flink == MAXULONG_PTR) { /* Put the page as the first entry */ ColorTable->Flink = PageFrameIndex; PfnEntry->u4.PteFrame = MM_PFN_PTE_FRAME; } else { /* Get the current last page on the colored list */ ColoredPfn = (PMMPFN)ColorTable->Blink; /* Link with the previous last page */ MM::Paging::SetPte(&ColoredPfn->OriginalPte, PageFrameIndex); PfnEntry->u4.PteFrame = ColoredPfn - (PMMPFN)MemoryLayout->PfnDatabaseAddress; } /* Set the page as the new tail of the colored list */ ColorTable->Blink = PfnEntry; ColorTable->Count++; MM::Paging::SetPte(&PfnEntry->OriginalPte, MAXULONG_PTR); /* Increment number of available pages */ IncrementAvailablePages(); } /** * Initializes the PFN database entry for a physical page that is used as a page table. * * @param PageFrameIndex * The page frame number of the physical page being used as a page table. * * @param PointerPte * A pointer to the higher-level PTE that maps this page table page. * * @return This routine does not return any value. * * @since XT 1.0 */ XTAPI VOID MM::Pfn::LinkPfnForPageTable(IN PFN_NUMBER PageFrameIndex, IN PMMPTE PointerPte) { PMMPFN Pfn; PMMPDE PointerPde; PVOID EndAddress; /* Retrieve the PFN database entry for the physical page of the page table */ Pfn = GetPfnEntry(PageFrameIndex); /* Calculate the end address of the PFN entry to ensure it's mapped */ EndAddress = (PUCHAR)(Pfn + 1) - 1; /* Validate that the PFN entry corresponds to a valid, active physical page */ if((PageFrameIndex <= HighestPhysicalPage) && (MM::Pte::AddressValid(Pfn)) && (MM::Pte::AddressValid(EndAddress)) && (Pfn->u3.e1.PageLocation == ActiveAndValid)) { /* Initialize the PFN entry for this page table page */ Pfn->OriginalPte = *PointerPte; Pfn->PteAddress = PointerPte; Pfn->u1.WsIndex = 0; Pfn->u2.ShareCount++; Pfn->u3.e1.CacheAttribute = PfnNonCached; Pfn->u3.e1.PageLocation = ActiveAndValid; Pfn->u3.e2.ReferenceCount = 1; Pfn->u4.PteFrame = MM::Paging::GetPageFrameNumber(MM::Paging::GetPteAddress(PointerPte)); } /* Increment the share count of the parent page table that contains the mapping */ PointerPde = MM::Paging::GetPdeAddress(MM::Paging::GetPteVirtualAddress(PointerPte)); Pfn = GetPfnEntry(MM::Paging::GetPageFrameNumber(PointerPde)); Pfn->u2.ShareCount++; } /** * Links a PFN entry to its corresponding PTE and ensures the page table that contains the PTE is resident in memory. * * @param PageFrameIndex * Supplies the index into the PFN database for the page being initialized. * * @param PointerPte * Supplies the pointer to the PTE which maps the physical page. * * @param Modified * Supplies a flag indicating if the page's initial state is modified. * * @return This routine does not return any value. * * @since XT 1.0 */ XTAPI VOID MM::Pfn::LinkPfnToPte(IN PFN_NUMBER PageFrameIndex, IN PMMPTE PointerPte, IN BOOLEAN Modified) { PMMMEMORY_LAYOUT MemoryLayout; XTSTATUS Status; PMMPFN Pfn; PMMPTE Pte; /* Get the memory layout */ MemoryLayout = MM::Manager::GetMemoryLayout(); /* Point the PFN to its PTE */ Pfn = &((PMMPFN)MemoryLayout->PfnDatabaseAddress)[PageFrameIndex]; Pfn->PteAddress = PointerPte; /* Check if the page is already mapped and in use */ if(MM::Paging::PteValid(PointerPte)) { /* Clear the original PTE information */ MM::Paging::SetPte(&Pfn->OriginalPte, 0, MM_PTE_READWRITE | MM_PTE_CACHE_ENABLE); } else { /* Page is not resident, so save the PTE contents for later use */ Pfn->OriginalPte = *PointerPte; } /* Initialize the PFN database entry for this page */ Pfn->u2.ShareCount = 1; Pfn->u3.e1.Modified = Modified; Pfn->u3.e1.PageLocation = ActiveAndValid; Pfn->u3.e2.ReferenceCount = 1; /* Get the PDE that maps the page table containing this PTE */ Pte = MM::Paging::GetPteAddress(PointerPte); if(!MM::Paging::PteValid(Pte)) { /* Check if page table is resident */ Status = MM::PageFault::CheckPdeForPagedPool(PointerPte); if(Status != STATUS_SUCCESS) { /* Could not make the page table resident, crash system */ KE::Crash::PanicEx(0x1, (ULONG_PTR)0x61940, (ULONG_PTR)PointerPte, MM::Paging::GetPageFrameNumber(PointerPte), (ULONG_PTR)MM::Paging::GetPteVirtualAddress(PointerPte)); } } /* Record the page frame of the page table itself */ PageFrameIndex = MM::Paging::GetPageFrameNumber(Pte); Pfn->u4.PteFrame = PageFrameIndex; /* Pin the page table in memory by incrementing its PFN share count */ Pfn = &((PMMPFN)MemoryLayout->PfnDatabaseAddress)[PageFrameIndex]; Pfn->u2.ShareCount++; } /** * Processes a memory descriptor and initializes the corresponding PFN database entries * * @param BasePage * The starting physical page number of the memory run * * @param PageCount * The number of pages in the memory run * * @param MemoryType * The type of memory as reported by the bootloader (e.g., free, ROM, in-use) * * @return This routine does not return any value. * * @since XT 1.0 */ XTAPI VOID MM::Pfn::ProcessMemoryDescriptor(IN PFN_NUMBER BasePage, IN PFN_NUMBER PageCount, IN LOADER_MEMORY_TYPE MemoryType) { PFN_NUMBER CurrentPage, PageNumber; PMMPFN Pfn; /* Check if the memory descriptor describes a free memory region */ if(MM::Manager::VerifyMemoryTypeFree(MemoryType)) { /* Iterate over each page in this free memory run */ for(PageNumber = 0; PageNumber < PageCount; PageNumber++) { /* Get the PFN entry for the current page and set its initial cache attribute */ CurrentPage = BasePage + PageNumber; Pfn = GetPfnEntry(CurrentPage); Pfn->u3.e1.CacheAttribute = PfnNonCached; /* Add the page to the free list to make it available for allocation */ LinkFreePage(CurrentPage); } } else if(MM::Manager::VerifyMemoryTypeInvisible(MemoryType)) { /* This memory range should never be used, skip it */ } else { /* Handle all other (non-free) memory types */ switch(MemoryType) { case LoaderBad: /* This memory is marked as bad and should not be used */ UNIMPLEMENTED; break; case LoaderXIPRom: /* This memory range contains Read-Only Memory (ROM) */ for(PageNumber = 0; PageNumber < PageCount; PageNumber++) { /* Get the PFN entry for the current ROM page */ Pfn = GetPfnEntry(BasePage + PageNumber); /* Initialize the PFN entry to represent a ROM page */ Pfn->PteAddress = 0; Pfn->u1.Flink = 0; Pfn->u2.ShareCount = 0; Pfn->u3.e1.CacheAttribute = PfnNonCached; Pfn->u3.e1.PageLocation = 0; Pfn->u3.e1.PrototypePte = 1; Pfn->u3.e1.Rom = 1; Pfn->u3.e2.ReferenceCount = 0; Pfn->u4.InPageError = 0; Pfn->u4.PteFrame = 0; } break; default: /* All other types are considered in-use (ie, by the kernel, ACPI, etc) */ for(PageNumber = 0; PageNumber < PageCount; PageNumber++) { /* Get the PFN entry for the current in-use page */ Pfn = GetPfnEntry(BasePage + PageNumber); /* Mark the PFN as active and valid to prevent it from being allocated */ Pfn->u3.e1.PageLocation = ActiveAndValid; } break; } } } /** * Scans memory descriptors provided by the boot loader. * * @return This routine does not return any value. * * @since XT 1.0 */ XTAPI VOID MM::Pfn::ScanMemoryDescriptors(VOID) { PLIST_ENTRY LoaderMemoryDescriptors, MemoryMappings; PLOADER_MEMORY_DESCRIPTOR MemoryDescriptor; PFN_NUMBER FreePages; /* Initially, set number of free pages to 0 */ FreePages = 0; /* Get the list head of memory descriptors */ LoaderMemoryDescriptors = KE::BootInformation::GetMemoryDescriptors(); /* Iterate through the memory descriptors */ MemoryMappings = LoaderMemoryDescriptors->Flink; while(MemoryMappings != LoaderMemoryDescriptors) { /* Get the memory descriptor */ MemoryDescriptor = CONTAIN_RECORD(MemoryMappings, LOADER_MEMORY_DESCRIPTOR, ListEntry); /* Skip invisible or hardware cached memory regions */ if(MM::Manager::VerifyMemoryTypeInvisible(MemoryDescriptor->MemoryType) || (MemoryDescriptor->MemoryType == LoaderHardwareCachedMemory)) { /* Move to the next descriptor and skip further processing */ MemoryMappings = MemoryMappings->Flink; continue; } /* Count the number of physical pages, excluding bad memory */ if(MemoryDescriptor->MemoryType != LoaderBad) { /* Add the pages from this descriptor to the total count */ NumberOfPhysicalPages += MemoryDescriptor->PageCount; } /* Check if this physical page is the lowest one yet */ if(MemoryDescriptor->BasePage < LowestPhysicalPage) { /* Update the lowest physical page number found so far */ LowestPhysicalPage = MemoryDescriptor->BasePage; } /* Check if this physical page is the highest one yet */ if((MemoryDescriptor->BasePage + MemoryDescriptor->PageCount) > HighestPhysicalPage) { /* Update the highest physical page number found so far */ HighestPhysicalPage = (MemoryDescriptor->BasePage + MemoryDescriptor->PageCount) - 1; } /* Identify the largest block of free memory */ if(MM::Manager::VerifyMemoryTypeFree(MemoryDescriptor->MemoryType)) { /* Check if this free memory block is the largest one yet */ if(MemoryDescriptor->PageCount >= FreePages) { /* Update the largest free block size and save the descriptor */ FreePages = MemoryDescriptor->PageCount; FreeDescriptor = MemoryDescriptor; } } /* Get next memory descriptor */ MemoryMappings = MemoryMappings->Flink; } /* Ensure a free memory descriptor was found */ if(!FreeDescriptor) { /* No free memory available to bootstrap the system */ KE::Crash::Panic(0); } /* Save a copy of the original free descriptor before it gets modified */ RTL::Memory::CopyMemory(&OriginalFreeDescriptor, FreeDescriptor, sizeof(LOADER_MEMORY_DESCRIPTOR)); } /** * Unlinks a physical page from its corresponding list. * * @param PageIndex * The Page Frame Number (PFN) of the page to unlink. * * @param Color * The color of the page, used to find the correct colored list. * * @return This routine returns the PFN of the page that was unlinked. */ XTAPI PFN_NUMBER MM::Pfn::UnlinkFreePage(IN PFN_NUMBER PageFrameIndex, IN ULONG Color) { PMMPFN Pfn; PMMPFNLIST PfnList; MMPAGELISTS PageList; ULONG NodeColor; PMMCOLOR_TABLES ColorTable; PFN_NUMBER NextPage, PrevPage; /* Get the PFN database entry for the target page */ Pfn = GetPfnEntry(PageFrameIndex); /* Identify which list the page belongs to (FreePageList or ZeroedPageList) */ PfnList = PageLocationList[Pfn->u3.e1.PageLocation]; PageList = PfnList->ListName; /* Update the forward link of the previous page */ if(Pfn->u2.Blink != MAXULONG_PTR) { /* The page is not the head of the list; update the previous page's Flink */ GetPfnEntry(Pfn->u2.Blink)->u1.Flink = Pfn->u1.Flink; } else { /* This is the first page in the list; update the list head's Flink */ PfnList->Flink = Pfn->u1.Flink; } /* Update the backward link of the next page */ if(Pfn->u1.Flink != MAXULONG_PTR) { /* The page is not the tail of the list; update the next page's Blink */ GetPfnEntry(Pfn->u1.Flink)->u2.Blink = Pfn->u2.Blink; } else { /* This is the last page in the list; update the list head's Blink */ PfnList->Blink = Pfn->u2.Blink; } /* Get the first page on the color list */ ColorTable = MM::Colors::GetFreePages(PageList, Color); NodeColor = Pfn->u3.e1.PageColor; PrevPage = Pfn->u4.PteFrame; NextPage = MM::Paging::GetPte(&Pfn->OriginalPte); /* Decrement the count of pages for this specific color and total page count for this list */ ColorTable->Count--; PfnList->Total--; /* Update the forward link of the previous colored page */ if(PrevPage != MM_PFN_PTE_FRAME) { /* This is not the first page; update the previous page's Flink */ MM::Paging::SetPte(&GetPfnEntry(PrevPage)->OriginalPte, NextPage); } else { /* This was the first page; update the color table's Flink */ ColorTable->Flink = NextPage; } /* Update the backward link of the next colored page */ if (NextPage != MAXULONG_PTR) { /* This is not the last page; update the next page's Blink */ GetPfnEntry(NextPage)->u4.PteFrame = PrevPage; } else { /* This was the last page; update the color table's Blink */ ColorTable->Blink = (PVOID)PrevPage; } /* Clear the list pointers and flags, but preserve the color and cache attributes */ Pfn->u1.Flink = 0; Pfn->u2.Blink = 0; Pfn->u3.e1.CacheAttribute = PfnNotMapped; Pfn->u3.e1.PageColor = NodeColor; Pfn->u3.e2.ShortFlags = 0; /* Decrement the global count of available pages */ DecrementAvailablePages(); /* Return the page that was just unlinked */ return PageFrameIndex; }