using Ryujinx.Horizon.Common; using System.Diagnostics; namespace Ryujinx.HLE.HOS.Kernel.Memory { class KMemoryRegionManager { private readonly KPageHeap _pageHeap; public ulong Address { get; } public ulong Size { get; } public ulong EndAddr => Address + Size; private readonly ushort[] _pageReferenceCounts; public KMemoryRegionManager(ulong address, ulong size, ulong endAddr) { Address = address; Size = size; _pageReferenceCounts = new ushort[size / KPageTableBase.PageSize]; _pageHeap = new KPageHeap(address, size); _pageHeap.Free(address, size / KPageTableBase.PageSize); _pageHeap.UpdateUsedSize(); } public Result AllocatePages(out KPageList pageList, ulong pagesCount) { if (pagesCount == 0) { pageList = new KPageList(); return Result.Success; } lock (_pageHeap) { Result result = AllocatePagesImpl(out pageList, pagesCount, false); if (result == Result.Success) { foreach (var node in pageList) { IncrementPagesReferenceCount(node.Address, node.PagesCount); } } return result; } } public ulong AllocatePagesContiguous(KernelContext context, ulong pagesCount, bool backwards) { if (pagesCount == 0) { return 0; } lock (_pageHeap) { ulong address = AllocatePagesContiguousImpl(pagesCount, 1, backwards); if (address != 0) { IncrementPagesReferenceCount(address, pagesCount); context.CommitMemory(address - DramMemoryMap.DramBase, pagesCount * KPageTableBase.PageSize); } return address; } } private Result AllocatePagesImpl(out KPageList pageList, ulong pagesCount, bool random) { pageList = new KPageList(); int heapIndex = KPageHeap.GetBlockIndex(pagesCount); if (heapIndex < 0) { return KernelResult.OutOfMemory; } for (int index = heapIndex; index >= 0; index--) { ulong pagesPerAlloc = KPageHeap.GetBlockPagesCount(index); while (pagesCount >= pagesPerAlloc) { ulong allocatedBlock = _pageHeap.AllocateBlock(index, random); if (allocatedBlock == 0) { break; } Result result = pageList.AddRange(allocatedBlock, pagesPerAlloc); if (result != Result.Success) { FreePages(pageList); _pageHeap.Free(allocatedBlock, pagesPerAlloc); return result; } pagesCount -= pagesPerAlloc; } } if (pagesCount != 0) { FreePages(pageList); return KernelResult.OutOfMemory; } return Result.Success; } private ulong AllocatePagesContiguousImpl(ulong pagesCount, ulong alignPages, bool random) { int heapIndex = KPageHeap.GetAlignedBlockIndex(pagesCount, alignPages); ulong allocatedBlock = _pageHeap.AllocateBlock(heapIndex, random); if (allocatedBlock == 0) { return 0; } ulong allocatedPages = KPageHeap.GetBlockPagesCount(heapIndex); if (allocatedPages > pagesCount) { _pageHeap.Free(allocatedBlock + pagesCount * KPageTableBase.PageSize, allocatedPages - pagesCount); } return allocatedBlock; } public void FreePage(ulong address) { lock (_pageHeap) { _pageHeap.Free(address, 1); } } public void FreePages(KPageList pageList) { lock (_pageHeap) { foreach (KPageNode pageNode in pageList) { _pageHeap.Free(pageNode.Address, pageNode.PagesCount); } } } public void FreePages(ulong address, ulong pagesCount) { lock (_pageHeap) { _pageHeap.Free(address, pagesCount); } } public ulong GetFreePages() { lock (_pageHeap) { return _pageHeap.GetFreePagesCount(); } } public void IncrementPagesReferenceCount(ulong address, ulong pagesCount) { ulong index = GetPageOffset(address); ulong endIndex = index + pagesCount; while (index < endIndex) { ushort referenceCount = ++_pageReferenceCounts[index]; Debug.Assert(referenceCount >= 1); index++; } } public void DecrementPagesReferenceCount(ulong address, ulong pagesCount) { ulong index = GetPageOffset(address); ulong endIndex = index + pagesCount; ulong freeBaseIndex = 0; ulong freePagesCount = 0; while (index < endIndex) { Debug.Assert(_pageReferenceCounts[index] > 0); ushort referenceCount = --_pageReferenceCounts[index]; if (referenceCount == 0) { if (freePagesCount != 0) { freePagesCount++; } else { freeBaseIndex = index; freePagesCount = 1; } } else if (freePagesCount != 0) { FreePages(Address + freeBaseIndex * KPageTableBase.PageSize, freePagesCount); freePagesCount = 0; } index++; } if (freePagesCount != 0) { FreePages(Address + freeBaseIndex * KPageTableBase.PageSize, freePagesCount); } } public ulong GetPageOffset(ulong address) { return (address - Address) / KPageTableBase.PageSize; } public ulong GetPageOffsetFromEnd(ulong address) { return (EndAddr - address) / KPageTableBase.PageSize; } } }