Ryujinx/Ryujinx.HLE/HOS/Kernel/KMemoryRegionManager.cs

428 lines
14 KiB
C#
Raw Normal View History

using Ryujinx.Common;
namespace Ryujinx.HLE.HOS.Kernel
{
class KMemoryRegionManager
{
private static readonly int[] BlockOrders = new int[] { 12, 16, 21, 22, 25, 29, 30 };
public ulong Address { get; private set; }
public ulong EndAddr { get; private set; }
public ulong Size { get; private set; }
2018-12-01 21:01:59 +01:00
private int _blockOrdersCount;
2018-12-01 21:01:59 +01:00
private KMemoryRegionBlock[] _blocks;
2018-12-01 21:01:59 +01:00
public KMemoryRegionManager(ulong address, ulong size, ulong endAddr)
{
2018-12-01 21:01:59 +01:00
_blocks = new KMemoryRegionBlock[BlockOrders.Length];
2018-12-01 21:24:37 +01:00
Address = address;
Size = size;
EndAddr = endAddr;
2018-12-01 21:01:59 +01:00
_blockOrdersCount = BlockOrders.Length;
2018-12-01 21:01:59 +01:00
for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++)
{
2018-12-01 21:01:59 +01:00
_blocks[blockIndex] = new KMemoryRegionBlock();
2018-12-01 21:01:59 +01:00
_blocks[blockIndex].Order = BlockOrders[blockIndex];
2018-12-01 21:01:59 +01:00
int nextOrder = blockIndex == _blockOrdersCount - 1 ? 0 : BlockOrders[blockIndex + 1];
2018-12-01 21:01:59 +01:00
_blocks[blockIndex].NextOrder = nextOrder;
2018-12-01 21:01:59 +01:00
int currBlockSize = 1 << BlockOrders[blockIndex];
int nextBlockSize = currBlockSize;
2018-12-01 21:01:59 +01:00
if (nextOrder != 0)
{
2018-12-01 21:01:59 +01:00
nextBlockSize = 1 << nextOrder;
}
2018-12-01 21:01:59 +01:00
ulong startAligned = BitUtils.AlignDown(address, nextBlockSize);
ulong endAddrAligned = BitUtils.AlignDown(endAddr, currBlockSize);
2018-12-01 21:01:59 +01:00
ulong sizeInBlocksTruncated = (endAddrAligned - startAligned) >> BlockOrders[blockIndex];
2018-12-01 21:01:59 +01:00
ulong endAddrRounded = BitUtils.AlignUp(address + size, nextBlockSize);
2018-12-01 21:01:59 +01:00
ulong sizeInBlocksRounded = (endAddrRounded - startAligned) >> BlockOrders[blockIndex];
2018-12-01 21:01:59 +01:00
_blocks[blockIndex].StartAligned = startAligned;
_blocks[blockIndex].SizeInBlocksTruncated = sizeInBlocksTruncated;
_blocks[blockIndex].SizeInBlocksRounded = sizeInBlocksRounded;
2018-12-01 21:01:59 +01:00
ulong currSizeInBlocks = sizeInBlocksRounded;
2018-12-01 21:01:59 +01:00
int maxLevel = 0;
do
{
2018-12-01 21:01:59 +01:00
maxLevel++;
}
2018-12-01 21:01:59 +01:00
while ((currSizeInBlocks /= 64) != 0);
2018-12-01 21:01:59 +01:00
_blocks[blockIndex].MaxLevel = maxLevel;
2018-12-01 21:01:59 +01:00
_blocks[blockIndex].Masks = new long[maxLevel][];
2018-12-01 21:01:59 +01:00
currSizeInBlocks = sizeInBlocksRounded;
2018-12-01 21:01:59 +01:00
for (int level = maxLevel - 1; level >= 0; level--)
{
2018-12-01 21:01:59 +01:00
currSizeInBlocks = (currSizeInBlocks + 63) / 64;
2018-12-01 21:01:59 +01:00
_blocks[blockIndex].Masks[level] = new long[currSizeInBlocks];
}
}
2018-12-01 21:01:59 +01:00
if (size != 0)
{
2018-12-01 21:01:59 +01:00
FreePages(address, size / KMemoryManager.PageSize);
}
}
2018-12-01 21:01:59 +01:00
public KernelResult AllocatePages(ulong pagesCount, bool backwards, out KPageList pageList)
{
2018-12-01 21:01:59 +01:00
lock (_blocks)
{
2018-12-01 21:01:59 +01:00
return AllocatePagesImpl(pagesCount, backwards, out pageList);
}
}
2018-12-01 21:01:59 +01:00
private KernelResult AllocatePagesImpl(ulong pagesCount, bool backwards, out KPageList pageList)
{
2018-12-01 21:01:59 +01:00
pageList = new KPageList();
2018-12-01 21:01:59 +01:00
if (_blockOrdersCount > 0)
{
2018-12-01 21:01:59 +01:00
if (GetFreePagesImpl() < pagesCount)
{
return KernelResult.OutOfMemory;
}
}
2018-12-01 21:01:59 +01:00
else if (pagesCount != 0)
{
return KernelResult.OutOfMemory;
}
2018-12-01 21:01:59 +01:00
for (int blockIndex = _blockOrdersCount - 1; blockIndex >= 0; blockIndex--)
{
2018-12-01 21:01:59 +01:00
KMemoryRegionBlock block = _blocks[blockIndex];
2018-12-01 21:01:59 +01:00
ulong bestFitBlockSize = 1UL << block.Order;
2018-12-01 21:01:59 +01:00
ulong blockPagesCount = bestFitBlockSize / KMemoryManager.PageSize;
//Check if this is the best fit for this page size.
//If so, try allocating as much requested pages as possible.
2018-12-01 21:01:59 +01:00
while (blockPagesCount <= pagesCount)
{
2018-12-01 21:01:59 +01:00
ulong address = 0;
2018-12-01 21:01:59 +01:00
for (int currBlockIndex = blockIndex;
currBlockIndex < _blockOrdersCount && address == 0;
currBlockIndex++)
{
2018-12-01 21:01:59 +01:00
block = _blocks[currBlockIndex];
2018-12-01 21:01:59 +01:00
int index = 0;
2018-12-01 21:01:59 +01:00
bool zeroMask = false;
2018-12-01 21:01:59 +01:00
for (int level = 0; level < block.MaxLevel; level++)
{
2018-12-01 21:01:59 +01:00
long mask = block.Masks[level][index];
2018-12-01 21:01:59 +01:00
if (mask == 0)
{
2018-12-01 21:01:59 +01:00
zeroMask = true;
break;
}
2018-12-01 21:01:59 +01:00
if (backwards)
{
2018-12-01 21:01:59 +01:00
index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask);
}
else
{
2018-12-01 21:01:59 +01:00
index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask));
}
}
2018-12-01 21:01:59 +01:00
if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
{
continue;
}
2018-12-01 21:01:59 +01:00
block.FreeCount--;
2018-12-01 21:01:59 +01:00
int tempIdx = index;
2018-12-01 21:01:59 +01:00
for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
{
2018-12-01 21:01:59 +01:00
block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
2018-12-01 21:01:59 +01:00
if (block.Masks[level][tempIdx / 64] != 0)
{
break;
}
}
2018-12-01 21:01:59 +01:00
address = block.StartAligned + ((ulong)index << block.Order);
}
2018-12-01 21:01:59 +01:00
for (int currBlockIndex = blockIndex;
currBlockIndex < _blockOrdersCount && address == 0;
currBlockIndex++)
{
2018-12-01 21:01:59 +01:00
block = _blocks[currBlockIndex];
2018-12-01 21:01:59 +01:00
int index = 0;
2018-12-01 21:01:59 +01:00
bool zeroMask = false;
2018-12-01 21:01:59 +01:00
for (int level = 0; level < block.MaxLevel; level++)
{
2018-12-01 21:01:59 +01:00
long mask = block.Masks[level][index];
2018-12-01 21:01:59 +01:00
if (mask == 0)
{
2018-12-01 21:01:59 +01:00
zeroMask = true;
break;
}
2018-12-01 21:01:59 +01:00
if (backwards)
{
2018-12-01 21:01:59 +01:00
index = index * 64 + BitUtils.CountLeadingZeros64(BitUtils.ReverseBits64(mask));
}
else
{
2018-12-01 21:01:59 +01:00
index = (index * 64 + 63) - BitUtils.CountLeadingZeros64(mask);
}
}
2018-12-01 21:01:59 +01:00
if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
{
continue;
}
2018-12-01 21:01:59 +01:00
block.FreeCount--;
2018-12-01 21:01:59 +01:00
int tempIdx = index;
2018-12-01 21:01:59 +01:00
for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
{
2018-12-01 21:01:59 +01:00
block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
2018-12-01 21:01:59 +01:00
if (block.Masks[level][tempIdx / 64] != 0)
{
break;
}
}
2018-12-01 21:01:59 +01:00
address = block.StartAligned + ((ulong)index << block.Order);
}
//The address being zero means that no free space was found on that order,
//just give up and try with the next one.
2018-12-01 21:01:59 +01:00
if (address == 0)
{
break;
}
//If we are using a larger order than best fit, then we should
//split it into smaller blocks.
2018-12-01 21:01:59 +01:00
ulong firstFreeBlockSize = 1UL << block.Order;
2018-12-01 21:01:59 +01:00
if (firstFreeBlockSize > bestFitBlockSize)
{
2018-12-01 21:01:59 +01:00
FreePages(address + bestFitBlockSize, (firstFreeBlockSize - bestFitBlockSize) / KMemoryManager.PageSize);
}
//Add new allocated page(s) to the pages list.
//If an error occurs, then free all allocated pages and fail.
2018-12-01 21:01:59 +01:00
KernelResult result = pageList.AddRange(address, blockPagesCount);
2018-12-01 21:01:59 +01:00
if (result != KernelResult.Success)
{
2018-12-01 21:01:59 +01:00
FreePages(address, blockPagesCount);
2018-12-01 21:01:59 +01:00
foreach (KPageNode pageNode in pageList)
{
2018-12-01 21:01:59 +01:00
FreePages(pageNode.Address, pageNode.PagesCount);
}
2018-12-01 21:01:59 +01:00
return result;
}
2018-12-01 21:01:59 +01:00
pagesCount -= blockPagesCount;
}
}
//Success case, all requested pages were allocated successfully.
2018-12-01 21:01:59 +01:00
if (pagesCount == 0)
{
return KernelResult.Success;
}
//Error case, free allocated pages and return out of memory.
2018-12-01 21:01:59 +01:00
foreach (KPageNode pageNode in pageList)
{
2018-12-01 21:01:59 +01:00
FreePages(pageNode.Address, pageNode.PagesCount);
}
2018-12-01 21:01:59 +01:00
pageList = null;
return KernelResult.OutOfMemory;
}
2018-12-01 21:01:59 +01:00
public void FreePages(KPageList pageList)
{
2018-12-01 21:01:59 +01:00
lock (_blocks)
{
2018-12-01 21:01:59 +01:00
foreach (KPageNode pageNode in pageList)
{
2018-12-01 21:01:59 +01:00
FreePages(pageNode.Address, pageNode.PagesCount);
}
}
}
2018-12-01 21:01:59 +01:00
private void FreePages(ulong address, ulong pagesCount)
{
2018-12-01 21:01:59 +01:00
ulong endAddr = address + pagesCount * KMemoryManager.PageSize;
2018-12-01 21:01:59 +01:00
int blockIndex = _blockOrdersCount - 1;
2018-12-01 21:01:59 +01:00
ulong addressRounded = 0;
ulong endAddrTruncated = 0;
2018-12-01 21:01:59 +01:00
for (; blockIndex >= 0; blockIndex--)
{
2018-12-01 21:01:59 +01:00
KMemoryRegionBlock allocInfo = _blocks[blockIndex];
2018-12-01 21:01:59 +01:00
int blockSize = 1 << allocInfo.Order;
2018-12-01 21:01:59 +01:00
addressRounded = BitUtils.AlignUp (address, blockSize);
endAddrTruncated = BitUtils.AlignDown(endAddr, blockSize);
2018-12-01 21:01:59 +01:00
if (addressRounded < endAddrTruncated)
{
break;
}
}
2018-12-01 21:01:59 +01:00
void FreeRegion(ulong currAddress)
{
2018-12-01 21:01:59 +01:00
for (int currBlockIndex = blockIndex;
currBlockIndex < _blockOrdersCount && currAddress != 0;
currBlockIndex++)
{
2018-12-01 21:01:59 +01:00
KMemoryRegionBlock block = _blocks[currBlockIndex];
2018-12-01 21:01:59 +01:00
block.FreeCount++;
2018-12-01 21:01:59 +01:00
ulong freedBlocks = (currAddress - block.StartAligned) >> block.Order;
2018-12-01 21:01:59 +01:00
int index = (int)freedBlocks;
2018-12-01 21:01:59 +01:00
for (int level = block.MaxLevel - 1; level >= 0; level--, index /= 64)
{
2018-12-01 21:01:59 +01:00
long mask = block.Masks[level][index / 64];
2018-12-01 21:01:59 +01:00
block.Masks[level][index / 64] = mask | (1L << (index & 63));
2018-12-01 21:01:59 +01:00
if (mask != 0)
{
break;
}
}
2018-12-01 21:01:59 +01:00
int blockSizeDelta = 1 << (block.NextOrder - block.Order);
2018-12-01 21:01:59 +01:00
int freedBlocksTruncated = BitUtils.AlignDown((int)freedBlocks, blockSizeDelta);
2018-12-01 21:01:59 +01:00
if (!block.TryCoalesce(freedBlocksTruncated, blockSizeDelta))
{
break;
}
2018-12-01 21:01:59 +01:00
currAddress = block.StartAligned + ((ulong)freedBlocksTruncated << block.Order);
}
}
//Free inside aligned region.
2018-12-01 21:01:59 +01:00
ulong baseAddress = addressRounded;
2018-12-01 21:01:59 +01:00
while (baseAddress < endAddrTruncated)
{
2018-12-01 21:01:59 +01:00
ulong blockSize = 1UL << _blocks[blockIndex].Order;
2018-12-01 21:01:59 +01:00
FreeRegion(baseAddress);
2018-12-01 21:01:59 +01:00
baseAddress += blockSize;
}
2018-12-01 21:01:59 +01:00
int nextBlockIndex = blockIndex - 1;
//Free region between Address and aligned region start.
2018-12-01 21:01:59 +01:00
baseAddress = addressRounded;
2018-12-01 21:01:59 +01:00
for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--)
{
2018-12-01 21:01:59 +01:00
ulong blockSize = 1UL << _blocks[blockIndex].Order;
2018-12-01 21:01:59 +01:00
while (baseAddress - blockSize >= address)
{
2018-12-01 21:01:59 +01:00
baseAddress -= blockSize;
2018-12-01 21:01:59 +01:00
FreeRegion(baseAddress);
}
}
//Free region between aligned region end and End Address.
2018-12-01 21:01:59 +01:00
baseAddress = endAddrTruncated;
2018-12-01 21:01:59 +01:00
for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--)
{
2018-12-01 21:01:59 +01:00
ulong blockSize = 1UL << _blocks[blockIndex].Order;
2018-12-01 21:01:59 +01:00
while (baseAddress + blockSize <= endAddr)
{
2018-12-01 21:01:59 +01:00
FreeRegion(baseAddress);
2018-12-01 21:01:59 +01:00
baseAddress += blockSize;
}
}
}
public ulong GetFreePages()
{
2018-12-01 21:01:59 +01:00
lock (_blocks)
{
return GetFreePagesImpl();
}
}
private ulong GetFreePagesImpl()
{
2018-12-01 21:01:59 +01:00
ulong availablePages = 0;
2018-12-01 21:01:59 +01:00
for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++)
{
2018-12-01 21:01:59 +01:00
KMemoryRegionBlock block = _blocks[blockIndex];
2018-12-01 21:01:59 +01:00
ulong blockPagesCount = (1UL << block.Order) / KMemoryManager.PageSize;
2018-12-01 21:01:59 +01:00
availablePages += blockPagesCount * block.FreeCount;
}
2018-12-01 21:01:59 +01:00
return availablePages;
}
}
}