From ed3ff862f6b283dc9cca4bcefc759afcee7a2c7b Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Sun, 9 Aug 2020 14:26:45 +0100 Subject: [PATCH] Extend GPU VMM with unmapping/remapping support + code cleanup --- .../main/cpp/skyline/gpu/memory_manager.cpp | 66 +++++++++-- app/src/main/cpp/skyline/gpu/memory_manager.h | 45 +++++++- .../services/nvdrv/devices/nvhost_as_gpu.cpp | 103 ++++++++++++------ .../services/nvdrv/devices/nvhost_as_gpu.h | 28 +++-- 4 files changed, 192 insertions(+), 50 deletions(-) diff --git a/app/src/main/cpp/skyline/gpu/memory_manager.cpp b/app/src/main/cpp/skyline/gpu/memory_manager.cpp index 6e6f8b27..bfe663d6 100644 --- a/app/src/main/cpp/skyline/gpu/memory_manager.cpp +++ b/app/src/main/cpp/skyline/gpu/memory_manager.cpp @@ -42,11 +42,11 @@ namespace skyline::gpu::vmm { } if (extension) - chunkList.insert(std::next(chunk), ChunkDescriptor(newChunk.address + newChunk.size, extension, (oldChunk.state == ChunkState::Mapped) ? (oldChunk.cpuAddress + oldChunk.size + newChunk.size) : 0, oldChunk.state)); + chunkList.insert(std::next(chunk), ChunkDescriptor(newChunk.address + newChunk.size, extension, (oldChunk.state == ChunkState::Mapped) ? (oldChunk.cpuAddress + newSize + newChunk.size) : 0, oldChunk.state)); return newChunk.address; } else if (chunk->address + chunk->size > newChunk.address) { - chunk->size = (newChunk.address - chunk->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); @@ -68,6 +68,14 @@ namespace skyline::gpu::vmm { if (tailChunk->state == ChunkState::Mapped) tailChunk->cpuAddress += chunkSliceOffset; + + // If the size of the head chunk is zero then we can directly replace it with our new one rather than inserting it + auto headChunk = std::prev(tailChunk); + if (headChunk->size == 0) + *headChunk = newChunk; + else + chunkList.insert(std::next(headChunk), newChunk); + return newChunk.address; } } @@ -78,10 +86,10 @@ namespace skyline::gpu::vmm { u64 MemoryManager::AllocateSpace(u64 size) { size = util::AlignUp(size, constant::GpuPageSize); auto newChunk = FindChunk(size, ChunkState::Unmapped); - if (!newChunk.has_value()) + if (!newChunk) return 0; - auto chunk = newChunk.value(); + auto chunk = *newChunk; chunk.size = size; chunk.state = ChunkState::Allocated; @@ -100,10 +108,10 @@ namespace skyline::gpu::vmm { u64 MemoryManager::MapAllocated(u64 address, u64 size) { size = util::AlignUp(size, constant::GpuPageSize); auto mappedChunk = FindChunk(size, ChunkState::Allocated); - if (!mappedChunk.has_value()) + if (!mappedChunk) return 0; - auto chunk = mappedChunk.value(); + auto chunk = *mappedChunk; chunk.cpuAddress = address; chunk.size = size; chunk.state = ChunkState::Mapped; @@ -120,13 +128,30 @@ namespace skyline::gpu::vmm { return InsertChunk(ChunkDescriptor(address, size, cpuAddress, ChunkState::Mapped)); } + bool MemoryManager::Unmap(u64 address) { + if ((address & (constant::GpuPageSize - 1)) != 0) + return false; + + auto chunk = std::find_if(chunkList.begin(), chunkList.end(), [address](const ChunkDescriptor &chunk) -> bool { + return chunk.address == address; + }); + + if (chunk == chunkList.end()) + return false; + + chunk->state = ChunkState::Allocated; + chunk->cpuAddress = 0; + + return true; + } + void MemoryManager::Read(u8 *destination, u64 address, u64 size) const { auto chunk = --std::upper_bound(chunkList.begin(), chunkList.end(), address, [](const u64 address, const ChunkDescriptor &chunk) -> bool { return address < chunk.address; }); if (chunk == chunkList.end() || chunk->state != ChunkState::Mapped) - throw exception("Failed to read region in GPU address space - address: {#:X} size: {#:X}", address, size); + throw exception("Failed to read region in GPU address space - address: 0x{:X} size: 0x{:X}", address, size); u64 chunkOffset = address - chunk->address; u64 destinationOffset{}; @@ -146,4 +171,31 @@ namespace skyline::gpu::vmm { chunk++; } } + + void MemoryManager::Write(u8 *source, u64 address, u64 size) const { + auto chunk = --std::upper_bound(chunkList.begin(), chunkList.end(), address, [](const u64 address, const ChunkDescriptor &chunk) -> bool { + return address < chunk.address; + }); + + if (chunk == chunkList.end() || chunk->state != ChunkState::Mapped) + throw exception("Failed to write to region in GPU address space - address: {#:X} size: {#:X}", address, size); + + u64 chunkOffset = address - chunk->address; + u64 sourceOffset{}; + + // A continuous region in the GPU address space may be made up of several discontinuous regions in physical memory so we have to iterate over all chunks + while (size != 0) { + if (chunk == chunkList.end() || chunk->state != ChunkState::Mapped) + throw exception("Failed to write to region in GPU address space - address: {#:X} size: {#:X}", address, size); + + u64 writeSize = std::min(chunk->size - chunkOffset, size); + state.process->WriteMemory(source + sourceOffset, chunk->cpuAddress + chunkOffset, writeSize); + + // After the first read all further reads will start from the base of the chunk + chunkOffset = 0; + size -= writeSize; + sourceOffset += writeSize; + chunk++; + } + } } diff --git a/app/src/main/cpp/skyline/gpu/memory_manager.h b/app/src/main/cpp/skyline/gpu/memory_manager.h index 08df7eb1..fd31e3cd 100644 --- a/app/src/main/cpp/skyline/gpu/memory_manager.h +++ b/app/src/main/cpp/skyline/gpu/memory_manager.h @@ -3,6 +3,7 @@ #pragma once +#include #include namespace skyline { @@ -99,9 +100,51 @@ namespace skyline { u64 MapFixed(u64 address, u64 cpuAddress, u64 size); /** - * @brief Reads in a buffer from a region of the GPU virtual address space + * @brief This unmaps the chunk that starts at 'offset' from the GPU address space + * @return Whether the operation succeeded */ + bool Unmap(u64 address); + void Read(u8 *destination, u64 address, u64 size) const; + + /** + * @brief Reads in a span from a region of the GPU virtual address space + * @tparam T The type of span to read into + */ + template + void Read(std::span destination, u64 address) const { + Read(reinterpret_cast(destination.data()), address, destination.size_bytes()); + } + + /** + * @brief Reads in an object from a region of the GPU virtual address space + * @tparam T The type of object to return + */ + template + T Read(u64 address) const { + T obj; + Read(reinterpret_cast(&obj), address, sizeof(T)); + return obj; + } + + void Write(u8 *source, u64 address, u64 size) const; + + /** + * @brief Writes out a span to a region of the GPU virtual address space + */ + template + void Write(std::span source, u64 address) const { + Write(reinterpret_cast(source.data()), address, source.size_bytes()); + } + + /** + * @brief Reads in an object from a region of the GPU virtual address space + * @tparam T The type of object to return + */ + template + void Write(T source, u64 address) const { + Write(reinterpret_cast(&source), address, sizeof(T)); + } }; } } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_as_gpu.cpp b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_as_gpu.cpp index 317076eb..0a50c286 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_as_gpu.cpp +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_as_gpu.cpp @@ -10,42 +10,15 @@ namespace skyline::service::nvdrv::device { NvHostAsGpu::NvHostAsGpu(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_as_gpu, { - {0x4109, NFUNC(NvHostAsGpu::InitializeEx)}, - {0x4108, NFUNC(NvHostAsGpu::GetVaRegions)}, - {0x4102, NFUNC(NvHostAsGpu::AllocSpace)}, - {0x4106, NFUNC(NvHostAsGpu::Modify)}, {0x4101, NFUNC(NvHostAsGpu::BindChannel)}, - {0x4114, NFUNC(NvHostAsGpu::BindChannel)} + {0x4102, NFUNC(NvHostAsGpu::AllocSpace)}, + {0x4105, NFUNC(NvHostAsGpu::UnmapBuffer)}, + {0x4106, NFUNC(NvHostAsGpu::Modify)}, + {0x4108, NFUNC(NvHostAsGpu::GetVaRegions)}, + {0x4109, NFUNC(NvHostAsGpu::InitializeEx)}, + {0x4114, NFUNC(NvHostAsGpu::Remap)}, }) {} - void NvHostAsGpu::InitializeEx(IoctlData &buffer) { - struct Data { - u32 bigPageSize; - i32 asFd; - u32 flags; - u32 reserved; - u64 vaRangeStart; - u64 vaRangeEnd; - u64 vaRangeSplit; - } addressSpace = state.process->GetObject(buffer.input.at(0).address); - } - - void NvHostAsGpu::GetVaRegions(IoctlData &buffer) { - struct Data { - u64 _pad0_; - u32 bufferSize; - u32 _pad1_; - - struct { - u64 offset; - u32 page_size; - u32 pad; - u64 pages; - } regions[2]; - } regionInfo = state.process->GetReference(buffer.input.at(0).address); - state.process->WriteMemory(regionInfo, buffer.output.at(0).address); - } - void NvHostAsGpu::BindChannel(IoctlData &buffer) { struct Data { u32 fd; @@ -76,6 +49,13 @@ namespace skyline::service::nvdrv::device { state.process->WriteMemory(region, buffer.output.at(0).address); } + void NvHostAsGpu::UnmapBuffer(IoctlData &buffer) { + auto offset = state.process->GetObject(buffer.input.at(0).address); + + if (!state.gpu->memoryManager.Unmap(offset)) + state.logger->Warn("Failed to unmap chunk at 0x{:X}", offset); + } + void NvHostAsGpu::Modify(IoctlData &buffer) { struct Data { u32 flags; @@ -107,4 +87,61 @@ namespace skyline::service::nvdrv::device { state.process->WriteMemory(region, buffer.output.at(0).address); } + + void NvHostAsGpu::GetVaRegions(IoctlData &buffer) { + struct Data { + u64 _pad0_; + u32 bufferSize; + u32 _pad1_; + + struct { + u64 offset; + u32 page_size; + u32 pad; + u64 pages; + } regions[2]; + } regionInfo = state.process->GetReference(buffer.input.at(0).address); + state.process->WriteMemory(regionInfo, buffer.output.at(0).address); + } + + void NvHostAsGpu::InitializeEx(IoctlData &buffer) { + struct Data { + u32 bigPageSize; + i32 asFd; + u32 flags; + u32 reserved; + u64 vaRangeStart; + u64 vaRangeEnd; + u64 vaRangeSplit; + } addressSpace = state.process->GetObject(buffer.input.at(0).address); + } + + void NvHostAsGpu::Remap(IoctlData &buffer) { + struct Entry { + u16 flags; + u16 kind; + u32 nvmapHandle; + u32 mapOffset; + u32 gpuOffset; + u32 pages; + }; + + size_t entryCount{buffer.input.at(0).size / sizeof(Entry)}; + std::span entries(state.process->GetPointer(buffer.input.at(0).address), entryCount); + + for (auto entry : entries) { + try { + auto nvmap = state.os->serviceManager.GetService(Service::nvdrv_INvDrvServices)->GetDevice(nvdrv::device::NvDeviceType::nvmap)->handleTable.at(entry.nvmapHandle); + + u64 mapAddress = static_cast(entry.gpuOffset) << 0x10; + u64 mapPhysicalAddress = nvmap->address + (static_cast(entry.mapOffset) << 0x10); + u64 mapSize = static_cast(entry.pages) << 0x10; + + state.gpu->memoryManager.MapFixed(mapAddress, mapPhysicalAddress, mapSize); + } catch (const std::exception &e) { + buffer.status = NvStatus::BadValue; + return; + } + } + } } diff --git a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_as_gpu.h b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_as_gpu.h index 0938e7a2..5e99590e 100644 --- a/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_as_gpu.h +++ b/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost_as_gpu.h @@ -14,28 +14,38 @@ namespace skyline::service::nvdrv::device { NvHostAsGpu(const DeviceState &state); /** - * @brief This initializes the application's GPU address space (https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_INITIALIZE_EX) + * @brief This binds a channel to the address space (https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_BIND_CHANNEL) */ - void InitializeEx(IoctlData &buffer); - - /** - * @brief This returns the application's GPU address space (https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_GET_VA_REGIONS) - */ - void GetVaRegions(IoctlData &buffer); + void BindChannel(IoctlData &buffer); /** * @brief This reserves a region in the GPU address space (https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_ALLOC_SPACE) */ void AllocSpace(IoctlData &buffer); + /** + * @brief This unmaps a region in the GPU address space (https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_UNMAP_BUFFER) + */ + void UnmapBuffer(IoctlData &buffer); + /** * @brief This maps a region in the GPU address space (https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_MODIFY) */ void Modify(IoctlData &buffer); /** - * @brief This binds a channel to the address space (https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_BIND_CHANNEL) + * @brief This returns the application's GPU address space (https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_GET_VA_REGIONS) */ - void BindChannel(IoctlData &buffer); + void GetVaRegions(IoctlData &buffer); + + /** + * @brief This initializes the application's GPU address space (https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_INITIALIZE_EX) + */ + void InitializeEx(IoctlData &buffer); + + /** + * @brief Remaps a region of the GPU address space (https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_REMAP) + */ + void Remap(IoctlData &buffer); }; }