Implement a block based GPU virtual memory manager

The GPU has it's own seperate address space to the CPU. It is able to
address 40 bit addresses and accesses the system memory. A sorted vector
has been used to store blocks as insertions are not very frequent.
This commit is contained in:
Billy Laws
2020-07-14 15:15:28 +01:00
committed by ◱ PixelyIon
parent 80e7b82bad
commit b23779bda1
13 changed files with 402 additions and 20 deletions

View File

@ -0,0 +1,122 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "memory_manager.h"
namespace skyline::gpu::vmm {
MemoryManager::MemoryManager() {
constexpr u64 GpuAddressSpaceSize = 1ul << 40; //!< The size of the GPU address space
constexpr u64 GpuAddressSpaceBase = 0x100000; //!< The base of the GPU address space - must be non-zero
// Create the initial chunk that will be split to create new chunks
ChunkDescriptor baseChunk(GpuAddressSpaceBase, GpuAddressSpaceSize, 0, ChunkState::Unmapped);
chunkList.push_back(baseChunk);
}
std::optional<ChunkDescriptor> MemoryManager::FindChunk(u64 size, ChunkState state) {
auto chunk = std::find_if(chunkList.begin(), chunkList.end(), [size, state] (const ChunkDescriptor& chunk) -> bool {
return chunk.size > size && chunk.state == state;
});
if (chunk != chunkList.end())
return *chunk;
return std::nullopt;
}
u64 MemoryManager::InsertChunk(const ChunkDescriptor &newChunk) {
auto chunkEnd = chunkList.end();
for (auto chunk = chunkList.begin(); chunk != chunkEnd; chunk++) {
if (chunk->CanContain(newChunk)) {
auto oldChunk = *chunk;
u64 newSize = newChunk.address - chunk->address;
u64 extension = chunk->size - newSize - newChunk.size;
if (newSize == 0) {
*chunk = newChunk;
} else {
chunk->size = newSize;
chunk = chunkList.insert(std::next(chunk), newChunk);
}
if (extension)
chunkList.insert(std::next(chunk), ChunkDescriptor(newChunk.address + newChunk.size, extension, oldChunk.cpuAddress + oldChunk.size + newChunk.size, oldChunk.state));
return newChunk.address;
} else if (chunk->address + chunk->size >= newChunk.address) {
chunk->size = (newChunk.address - chunk->address);
// Deletes all chunks that are within the chunk being inserted and split the final one
auto tailChunk = std::next(chunk);
while (tailChunk != chunkEnd) {
if (tailChunk->address + tailChunk->size >= newChunk.address + newChunk.size)
break;
tailChunk = chunkList.erase(tailChunk);
chunkEnd = chunkList.end();
}
// The given chunk is too large to fit into existing chunks
if (tailChunk == chunkEnd)
break;
u64 chunkSliceOffset = newChunk.address + newChunk.size - tailChunk->address;
tailChunk->address += chunkSliceOffset;
tailChunk->size -= chunkSliceOffset;
if (tailChunk->state == ChunkState::Mapped)
tailChunk->cpuAddress += chunkSliceOffset;
return newChunk.address;
}
}
throw exception("Failed to insert chunk into GPU address space!");
}
u64 MemoryManager::AllocateSpace(u64 size) {
size = util::AlignUp(size, constant::GpuPageSize);
auto newChunk = FindChunk(size, ChunkState::Unmapped);
if (!newChunk.has_value())
return 0;
auto chunk = newChunk.value();
chunk.size = size;
chunk.state = ChunkState::Allocated;
return InsertChunk(chunk);
}
u64 MemoryManager::AllocateFixed(u64 address, u64 size) {
if ((address & (constant::GpuPageSize - 1)) != 0)
return 0;
size = util::AlignUp(size, constant::GpuPageSize);
return InsertChunk(ChunkDescriptor(address, size, 0, ChunkState::Allocated));
}
u64 MemoryManager::MapAllocated(u64 address, u64 size) {
size = util::AlignUp(size, constant::GpuPageSize);
auto mappedChunk = FindChunk(size, ChunkState::Allocated);
if (!mappedChunk.has_value())
return 0;
auto chunk = mappedChunk.value();
chunk.cpuAddress = address;
chunk.size = size;
chunk.state = ChunkState::Mapped;
return InsertChunk(chunk);
}
u64 MemoryManager::MapFixed(u64 address, u64 cpuAddress, u64 size) {
if ((address & (constant::GpuPageSize - 1)) != 0)
return 0;
size = util::AlignUp(size, constant::GpuPageSize);
return InsertChunk(ChunkDescriptor(address, size, cpuAddress, ChunkState::Mapped));
}
}

View File

@ -0,0 +1,101 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
namespace skyline {
namespace constant {
constexpr u64 GpuPageSize = 1 << 16; //!< The page size of the GPU address space
}
namespace gpu::vmm {
/**
* @brief This enumerates the possible states of a memory chunk
*/
enum ChunkState {
Unmapped, //!< The chunk is unmapped
Allocated, //!< The chunk is allocated but unmapped
Mapped //!< The chunk is mapped and a CPU side address is present
};
/**
* @brief This describes a chunk of memory and all of it's individual attributes
*/
struct ChunkDescriptor {
u64 address; //!< The address of the chunk in the GPU address space
u64 size; //!< The size of the chunk in bytes
u64 cpuAddress; //!< The address of the chunk in the CPU address space (if mapped)
ChunkState state; //!< The state of the chunk
ChunkDescriptor(u64 address, u64 size, u64 cpuAddress, ChunkState state) : address(address), size(size), cpuAddress(cpuAddress), state(state) {}
/**
* @param chunk The chunk to check
* @return If the given chunk can be contained wholly within this chunk
*/
inline bool CanContain(const ChunkDescriptor& chunk) {
return (chunk.address >= this->address) && ((this->size + this->address) >= (chunk.size + chunk.address));
}
};
/**
* @brief The MemoryManager class handles the mapping of the GPU address space
*/
class MemoryManager {
private:
std::vector<ChunkDescriptor> chunkList; //!< This vector holds all the chunk descriptors
/**
* @brief This finds a chunk of the specified type in the GPU address space that is larger than the given size
* @param size The minimum size of the chunk to find
* @param state The state desired state of the chunk to find
* @return The first unmapped chunk in the GPU address space that fits the requested size
*/
std::optional<ChunkDescriptor> FindChunk(u64 size, ChunkState state);
/**
* @brief This inserts a chunk into the chunk list, resizing and splitting as necessary
* @param newChunk The chunk to insert
* @return The base virtual GPU address of the inserted chunk
*/
u64 InsertChunk(const ChunkDescriptor &newChunk);
public:
MemoryManager();
/**
* @brief This reserves a region of the GPU address space so it can be automatically used when mapping
* @param size The size of the region to reserve
* @return The virtual GPU base address of the region base
*/
u64 AllocateSpace(u64 size);
/**
* @brief This reserves a fixed region of the GPU address space so it can be automatically used when mapping
* @param address The virtual base address of the region to allocate
* @param size The size of the region to allocate
* @return The virtual address of the region base
*/
u64 AllocateFixed(u64 address, u64 size);
/**
* @brief This maps a physical CPU memory region to an automatically chosen virtual memory region
* @param address The physical CPU address of the region to be mapped into the GPU's address space
* @param size The size of the region to map
* @return The virtual address of the region base
*/
u64 MapAllocated(u64 address, u64 size);
/**
* @brief This maps a physical CPU memory region to a fixed virtual memory region
* @param address The target virtual address of the region
* @param cpuAddress The physical CPU address of the region to be mapped into the GPU's address space
* @param size The size of the region to map
* @return The virtual address of the region base
*/
u64 MapFixed(u64 address, u64 cpuAddress, u64 size);
};
}
}