NVDRV: Remake ASGPU
This commit is contained in:
parent
c6ea0c650e
commit
feb49c822d
8 changed files with 882 additions and 239 deletions
|
@ -6,6 +6,7 @@
|
|||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
|
@ -21,8 +22,8 @@
|
|||
namespace Service::Nvidia::Devices {
|
||||
|
||||
nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, Module& module_, NvCore::Container& core)
|
||||
: nvdevice{system_}, module{module_}, container{core}, nvmap{core.GetNvMapFile()},
|
||||
gmmu{std::make_shared<Tegra::MemoryManager>(system)} {}
|
||||
: nvdevice{system_}, module{module_}, container{core}, nvmap{core.GetNvMapFile()}, vm{},
|
||||
gmmu{} {}
|
||||
nvhost_as_gpu::~nvhost_as_gpu() = default;
|
||||
|
||||
NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||
|
@ -89,12 +90,49 @@ NvResult nvhost_as_gpu::AllocAsEx(const std::vector<u8>& input, std::vector<u8>&
|
|||
IoctlAllocAsEx params{};
|
||||
std::memcpy(¶ms, input.data(), input.size());
|
||||
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size);
|
||||
if (params.big_page_size == 0) {
|
||||
params.big_page_size = DEFAULT_BIG_PAGE_SIZE;
|
||||
LOG_DEBUG(Service_NVDRV, "called, big_page_size=0x{:X}", params.big_page_size);
|
||||
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (vm.initialised) {
|
||||
UNREACHABLE_MSG("Cannot initialise an address space twice!");
|
||||
return NvResult::InvalidState;
|
||||
}
|
||||
|
||||
big_page_size = params.big_page_size;
|
||||
if (params.big_page_size) {
|
||||
if (!std::has_single_bit(params.big_page_size)) {
|
||||
LOG_ERROR(Service_NVDRV, "Non power-of-2 big page size: 0x{:X}!", params.big_page_size);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
if (!(params.big_page_size & VM::SUPPORTED_BIG_PAGE_SIZES)) {
|
||||
LOG_ERROR(Service_NVDRV, "Unsupported big page size: 0x{:X}!", params.big_page_size);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
vm.big_page_size = params.big_page_size;
|
||||
vm.big_page_size_bits = static_cast<u32>(std::countr_zero(params.big_page_size));
|
||||
|
||||
vm.va_range_start = params.big_page_size << VM::VA_START_SHIFT;
|
||||
}
|
||||
|
||||
// If this is unspecified then default values should be used
|
||||
if (params.va_range_start) {
|
||||
vm.va_range_start = params.va_range_start;
|
||||
vm.va_range_split = params.va_range_split;
|
||||
vm.va_range_end = params.va_range_end;
|
||||
}
|
||||
|
||||
const u64 start_pages{vm.va_range_start >> VM::PAGE_SIZE_BITS};
|
||||
const u64 end_pages{vm.va_range_split >> VM::PAGE_SIZE_BITS};
|
||||
vm.small_page_allocator = std::make_shared<VM::Allocator>(start_pages, end_pages);
|
||||
|
||||
const u64 start_big_pages{vm.va_range_split >> vm.big_page_size_bits};
|
||||
const u64 end_big_pages{(vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits};
|
||||
vm.big_page_allocator = std::make_unique<VM::Allocator>(start_big_pages, end_big_pages);
|
||||
|
||||
gmmu = std::make_shared<Tegra::MemoryManager>(system, 40, VM::PAGE_SIZE_BITS);
|
||||
vm.initialised = true;
|
||||
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
@ -106,21 +144,73 @@ NvResult nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<
|
|||
LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
|
||||
params.page_size, params.flags);
|
||||
|
||||
const auto size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
|
||||
if ((params.flags & AddressSpaceFlags::FixedOffset) != AddressSpaceFlags::None) {
|
||||
params.offset = *(gmmu->AllocateFixed(params.offset, size));
|
||||
} else {
|
||||
params.offset = gmmu->Allocate(size, params.align);
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
auto result = NvResult::Success;
|
||||
if (!params.offset) {
|
||||
LOG_CRITICAL(Service_NVDRV, "allocation failed for size {}", size);
|
||||
result = NvResult::InsufficientMemory;
|
||||
if (params.page_size != VM::YUZU_PAGESIZE && params.page_size != vm.big_page_size) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
if (params.page_size != vm.big_page_size &&
|
||||
((params.flags & MappingFlags::Sparse) != MappingFlags::None)) {
|
||||
UNIMPLEMENTED_MSG("Sparse small pages are not implemented!");
|
||||
return NvResult::NotImplemented;
|
||||
}
|
||||
|
||||
const u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
|
||||
: vm.big_page_size_bits};
|
||||
|
||||
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
|
||||
: *vm.big_page_allocator};
|
||||
|
||||
if ((params.flags & MappingFlags::Fixed) != MappingFlags::None) {
|
||||
allocator.AllocateFixed(static_cast<u32>(params.offset >> page_size_bits), params.pages);
|
||||
} else {
|
||||
params.offset = static_cast<u64>(allocator.Allocate(params.pages)) << page_size_bits;
|
||||
if (!params.offset) {
|
||||
UNREACHABLE_MSG("Failed to allocate free space in the GPU AS!");
|
||||
return NvResult::InsufficientMemory;
|
||||
}
|
||||
}
|
||||
|
||||
u64 size{static_cast<u64>(params.pages) * params.page_size};
|
||||
|
||||
if ((params.flags & MappingFlags::Sparse) != MappingFlags::None) {
|
||||
gmmu->MapSparse(params.offset, size);
|
||||
}
|
||||
|
||||
allocation_map[params.offset] = {
|
||||
.size = size,
|
||||
.page_size = params.page_size,
|
||||
.sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None,
|
||||
};
|
||||
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
return result;
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
|
||||
auto mapping{mapping_map.at(offset)};
|
||||
|
||||
if (!mapping->fixed) {
|
||||
auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
|
||||
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
|
||||
static_cast<u32>(mapping->size >> page_size_bits));
|
||||
}
|
||||
|
||||
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
|
||||
// Only FreeSpace can unmap them fully
|
||||
if (mapping->sparse_alloc)
|
||||
gmmu->MapSparse(offset, mapping->size);
|
||||
else
|
||||
gmmu->Unmap(offset, mapping->size);
|
||||
|
||||
mapping_map.erase(offset);
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
|
@ -130,7 +220,40 @@ NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>&
|
|||
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
|
||||
params.pages, params.page_size);
|
||||
|
||||
gmmu->Unmap(params.offset, static_cast<std::size_t>(params.pages) * params.page_size);
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
try {
|
||||
auto allocation{allocation_map[params.offset]};
|
||||
|
||||
if (allocation.page_size != params.page_size ||
|
||||
allocation.size != (static_cast<u64>(params.pages) * params.page_size)) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
for (const auto& mapping : allocation.mappings) {
|
||||
FreeMappingLocked(mapping->offset);
|
||||
}
|
||||
|
||||
// Unset sparse flag if required
|
||||
if (allocation.sparse) {
|
||||
gmmu->Unmap(params.offset, allocation.size);
|
||||
}
|
||||
|
||||
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
|
||||
: *vm.big_page_allocator};
|
||||
u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
|
||||
: vm.big_page_size_bits};
|
||||
|
||||
allocator.Free(static_cast<u32>(params.offset >> page_size_bits),
|
||||
static_cast<u32>(allocation.size >> page_size_bits));
|
||||
allocation_map.erase(params.offset);
|
||||
} catch ([[maybe_unused]] const std::out_of_range& e) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
return NvResult::Success;
|
||||
|
@ -141,43 +264,51 @@ NvResult nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& out
|
|||
|
||||
LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries);
|
||||
|
||||
auto result = NvResult::Success;
|
||||
std::vector<IoctlRemapEntry> entries(num_entries);
|
||||
std::memcpy(entries.data(), input.data(), input.size());
|
||||
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
for (const auto& entry : entries) {
|
||||
LOG_DEBUG(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
|
||||
entry.offset, entry.nvmap_handle, entry.pages);
|
||||
GPUVAddr virtual_address{static_cast<u64>(entry.as_offset_big_pages)
|
||||
<< vm.big_page_size_bits};
|
||||
u64 size{static_cast<u64>(entry.big_pages) << vm.big_page_size_bits};
|
||||
|
||||
if (entry.nvmap_handle == 0) {
|
||||
// If nvmap handle is null, we should unmap instead.
|
||||
const auto offset{static_cast<GPUVAddr>(entry.offset) << 0x10};
|
||||
const auto size{static_cast<u64>(entry.pages) << 0x10};
|
||||
gmmu->Unmap(offset, size);
|
||||
continue;
|
||||
auto alloc{allocation_map.upper_bound(virtual_address)};
|
||||
|
||||
if (alloc-- == allocation_map.begin() ||
|
||||
(virtual_address - alloc->first) + size > alloc->second.size) {
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap into an unallocated region!");
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
const auto object{nvmap.GetHandle(entry.nvmap_handle)};
|
||||
if (!object) {
|
||||
LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", entry.nvmap_handle);
|
||||
result = NvResult::InvalidState;
|
||||
break;
|
||||
if (!alloc->second.sparse) {
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap a non-sparse mapping!");
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
const auto offset{static_cast<GPUVAddr>(entry.offset) << 0x10};
|
||||
const auto size{static_cast<u64>(entry.pages) << 0x10};
|
||||
const auto map_offset{static_cast<u64>(entry.map_offset) << 0x10};
|
||||
const auto addr{gmmu->Map(object->address + map_offset, offset, size)};
|
||||
if (!entry.handle) {
|
||||
gmmu->MapSparse(virtual_address, size);
|
||||
} else {
|
||||
auto handle{nvmap.GetHandle(entry.handle)};
|
||||
if (!handle) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
if (!addr) {
|
||||
LOG_CRITICAL(Service_NVDRV, "map returned an invalid address!");
|
||||
result = NvResult::InvalidState;
|
||||
break;
|
||||
VAddr cpu_address{static_cast<VAddr>(
|
||||
handle->address +
|
||||
(static_cast<u64>(entry.handle_offset_big_pages) << vm.big_page_size_bits))};
|
||||
|
||||
gmmu->Map(virtual_address, cpu_address, size);
|
||||
}
|
||||
}
|
||||
|
||||
std::memcpy(output.data(), entries.data(), output.size());
|
||||
return result;
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
|
@ -187,75 +318,96 @@ NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8
|
|||
LOG_DEBUG(Service_NVDRV,
|
||||
"called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}"
|
||||
", offset={}",
|
||||
params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size,
|
||||
params.flags, params.handle, params.buffer_offset, params.mapping_size,
|
||||
params.offset);
|
||||
|
||||
if ((params.flags & AddressSpaceFlags::Remap) != AddressSpaceFlags::None) {
|
||||
if (const auto buffer_map{FindBufferMap(params.offset)}; buffer_map) {
|
||||
const auto cpu_addr{static_cast<VAddr>(buffer_map->CpuAddr() + params.buffer_offset)};
|
||||
const auto gpu_addr{static_cast<GPUVAddr>(params.offset + params.buffer_offset)};
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (!gmmu->Map(cpu_addr, gpu_addr, params.mapping_size)) {
|
||||
LOG_CRITICAL(Service_NVDRV,
|
||||
"remap failed, flags={:X}, nvmap_handle={:X}, buffer_offset={}, "
|
||||
"mapping_size = {}, offset={}",
|
||||
params.flags, params.nvmap_handle, params.buffer_offset,
|
||||
params.mapping_size, params.offset);
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
return NvResult::InvalidState;
|
||||
// Remaps a subregion of an existing mapping to a different PA
|
||||
if ((params.flags & MappingFlags::Remap) != MappingFlags::None) {
|
||||
try {
|
||||
auto mapping{mapping_map.at(params.offset)};
|
||||
|
||||
if (mapping->size < params.mapping_size) {
|
||||
LOG_WARNING(Service_NVDRV,
|
||||
"Cannot remap a partially mapped GPU address space region: 0x{:X}",
|
||||
params.offset);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
return NvResult::Success;
|
||||
} else {
|
||||
LOG_CRITICAL(Service_NVDRV, "address not mapped offset={}", params.offset);
|
||||
u64 gpu_address{static_cast<u64>(params.offset + params.buffer_offset)};
|
||||
VAddr cpu_address{mapping->ptr + params.buffer_offset};
|
||||
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
return NvResult::InvalidState;
|
||||
gmmu->Map(gpu_address, cpu_address, params.mapping_size);
|
||||
|
||||
return NvResult::Success;
|
||||
} catch ([[maybe_unused]] const std::out_of_range& e) {
|
||||
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: 0x{:X}",
|
||||
params.offset);
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
}
|
||||
|
||||
const auto object{nvmap.GetHandle(params.nvmap_handle)};
|
||||
if (!object) {
|
||||
LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", params.nvmap_handle);
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
return NvResult::InvalidState;
|
||||
auto handle{nvmap.GetHandle(params.handle)};
|
||||
if (!handle) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
// The real nvservices doesn't make a distinction between handles and ids, and
|
||||
// object can only have one handle and it will be the same as its id. Assert that this is the
|
||||
// case to prevent unexpected behavior.
|
||||
ASSERT(object->id == params.nvmap_handle);
|
||||
VAddr cpu_address{static_cast<VAddr>(handle->address + params.buffer_offset)};
|
||||
u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
|
||||
|
||||
u64 page_size{params.page_size};
|
||||
if (!page_size) {
|
||||
page_size = object->align;
|
||||
}
|
||||
if ((params.flags & MappingFlags::Fixed) != MappingFlags::None) {
|
||||
auto alloc{allocation_map.upper_bound(params.offset)};
|
||||
|
||||
const auto physical_address{object->address + params.buffer_offset};
|
||||
u64 size{params.mapping_size};
|
||||
if (!size) {
|
||||
size = object->size;
|
||||
}
|
||||
if (alloc-- == allocation_map.begin() ||
|
||||
(params.offset - alloc->first) + size > alloc->second.size) {
|
||||
UNREACHABLE_MSG("Cannot perform a fixed mapping into an unallocated region!");
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
const bool is_alloc{(params.flags & AddressSpaceFlags::FixedOffset) == AddressSpaceFlags::None};
|
||||
if (is_alloc) {
|
||||
params.offset = gmmu->MapAllocate(physical_address, size, page_size);
|
||||
gmmu->Map(params.offset, cpu_address, size);
|
||||
|
||||
auto mapping{std::make_shared<Mapping>(cpu_address, params.offset, size, true, false,
|
||||
alloc->second.sparse)};
|
||||
alloc->second.mappings.push_back(mapping);
|
||||
mapping_map[params.offset] = mapping;
|
||||
} else {
|
||||
params.offset = gmmu->Map(physical_address, params.offset, size);
|
||||
}
|
||||
bool big_page{[&]() {
|
||||
if (Common::IsAligned(handle->align, vm.big_page_size))
|
||||
return true;
|
||||
else if (Common::IsAligned(handle->align, VM::YUZU_PAGESIZE))
|
||||
return false;
|
||||
else {
|
||||
UNREACHABLE();
|
||||
return false;
|
||||
}
|
||||
}()};
|
||||
|
||||
auto result = NvResult::Success;
|
||||
if (!params.offset) {
|
||||
LOG_CRITICAL(Service_NVDRV, "failed to map size={}", size);
|
||||
result = NvResult::InvalidState;
|
||||
} else {
|
||||
AddBufferMap(params.offset, size, physical_address, is_alloc);
|
||||
auto& allocator{big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size{big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
|
||||
u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
|
||||
params.offset = static_cast<u64>(allocator.Allocate(
|
||||
static_cast<u32>(Common::AlignUp(size, page_size) >> page_size_bits)))
|
||||
<< page_size_bits;
|
||||
if (!params.offset) {
|
||||
UNREACHABLE_MSG("Failed to allocate free space in the GPU AS!");
|
||||
return NvResult::InsufficientMemory;
|
||||
}
|
||||
|
||||
gmmu->Map(params.offset, cpu_address, size);
|
||||
|
||||
auto mapping{
|
||||
std::make_shared<Mapping>(cpu_address, params.offset, size, false, big_page, false)};
|
||||
mapping_map[params.offset] = mapping;
|
||||
}
|
||||
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
return result;
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
|
@ -264,13 +416,36 @@ NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8
|
|||
|
||||
LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
|
||||
|
||||
if (const auto size{RemoveBufferMap(params.offset)}; size) {
|
||||
gmmu->Unmap(params.offset, *size);
|
||||
} else {
|
||||
LOG_ERROR(Service_NVDRV, "invalid offset=0x{:X}", params.offset);
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
try {
|
||||
auto mapping{mapping_map.at(params.offset)};
|
||||
|
||||
if (!mapping->fixed) {
|
||||
auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||
u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||
|
||||
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
|
||||
static_cast<u32>(mapping->size >> page_size_bits));
|
||||
}
|
||||
|
||||
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
|
||||
// Only FreeSpace can unmap them fully
|
||||
if (mapping->sparse_alloc) {
|
||||
gmmu->MapSparse(params.offset, mapping->size);
|
||||
} else {
|
||||
gmmu->Unmap(params.offset, mapping->size);
|
||||
}
|
||||
|
||||
mapping_map.erase(params.offset);
|
||||
} catch ([[maybe_unused]] const std::out_of_range& e) {
|
||||
LOG_WARNING(Service_NVDRV, "Couldn't find region to unmap at 0x{:X}", params.offset);
|
||||
}
|
||||
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
|
@ -284,28 +459,37 @@ NvResult nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8
|
|||
return NvResult::Success;
|
||||
}
|
||||
|
||||
void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) {
|
||||
params.buf_size = 2 * sizeof(VaRegion);
|
||||
|
||||
params.regions = std::array<VaRegion, 2>{
|
||||
VaRegion{
|
||||
.offset = vm.small_page_allocator->vaStart << VM::PAGE_SIZE_BITS,
|
||||
.page_size = VM::YUZU_PAGESIZE,
|
||||
.pages = vm.small_page_allocator->vaLimit - vm.small_page_allocator->vaStart,
|
||||
},
|
||||
VaRegion{
|
||||
.offset = vm.big_page_allocator->vaStart << vm.big_page_size_bits,
|
||||
.page_size = vm.big_page_size,
|
||||
.pages = vm.big_page_allocator->vaLimit - vm.big_page_allocator->vaStart,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlGetVaRegions params{};
|
||||
std::memcpy(¶ms, input.data(), input.size());
|
||||
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
|
||||
params.buf_size);
|
||||
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
|
||||
params.buf_size);
|
||||
|
||||
params.buf_size = 0x30;
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
params.small = IoctlVaRegion{
|
||||
.offset = 0x04000000,
|
||||
.page_size = DEFAULT_SMALL_PAGE_SIZE,
|
||||
.pages = 0x3fbfff,
|
||||
};
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
params.big = IoctlVaRegion{
|
||||
.offset = 0x04000000,
|
||||
.page_size = big_page_size,
|
||||
.pages = 0x1bffff,
|
||||
};
|
||||
|
||||
// TODO(ogniK): This probably can stay stubbed but should add support way way later
|
||||
GetVARegionsImpl(params);
|
||||
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
return NvResult::Success;
|
||||
|
@ -316,64 +500,24 @@ NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u
|
|||
IoctlGetVaRegions params{};
|
||||
std::memcpy(¶ms, input.data(), input.size());
|
||||
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
|
||||
params.buf_size);
|
||||
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
|
||||
params.buf_size);
|
||||
|
||||
params.buf_size = 0x30;
|
||||
std::scoped_lock lock(mutex);
|
||||
|
||||
params.small = IoctlVaRegion{
|
||||
.offset = 0x04000000,
|
||||
.page_size = 0x1000,
|
||||
.pages = 0x3fbfff,
|
||||
};
|
||||
if (!vm.initialised) {
|
||||
return NvResult::BadValue;
|
||||
}
|
||||
|
||||
params.big = IoctlVaRegion{
|
||||
.offset = 0x04000000,
|
||||
.page_size = big_page_size,
|
||||
.pages = 0x1bffff,
|
||||
};
|
||||
|
||||
// TODO(ogniK): This probably can stay stubbed but should add support way way later
|
||||
GetVARegionsImpl(params);
|
||||
|
||||
std::memcpy(output.data(), ¶ms, output.size());
|
||||
std::memcpy(inline_output.data(), ¶ms.small, sizeof(IoctlVaRegion));
|
||||
std::memcpy(inline_output.data() + sizeof(IoctlVaRegion), ¶ms.big, sizeof(IoctlVaRegion));
|
||||
std::memcpy(inline_output.data(), ¶ms.regions[0], sizeof(VaRegion));
|
||||
std::memcpy(inline_output.data() + sizeof(VaRegion), ¶ms.regions[1], sizeof(VaRegion));
|
||||
|
||||
return NvResult::Success;
|
||||
}
|
||||
|
||||
std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gpu_addr) const {
|
||||
const auto end{buffer_mappings.upper_bound(gpu_addr)};
|
||||
for (auto iter{buffer_mappings.begin()}; iter != end; ++iter) {
|
||||
if (gpu_addr >= iter->second.StartAddr() && gpu_addr < iter->second.EndAddr()) {
|
||||
return iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
|
||||
bool is_allocated) {
|
||||
buffer_mappings[gpu_addr] = {gpu_addr, size, cpu_addr, is_allocated};
|
||||
}
|
||||
|
||||
std::optional<std::size_t> nvhost_as_gpu::RemoveBufferMap(GPUVAddr gpu_addr) {
|
||||
if (const auto iter{buffer_mappings.find(gpu_addr)}; iter != buffer_mappings.end()) {
|
||||
std::size_t size{};
|
||||
|
||||
if (iter->second.IsAllocated()) {
|
||||
size = iter->second.Size();
|
||||
}
|
||||
|
||||
buffer_mappings.erase(iter);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Kernel::KEvent* nvhost_as_gpu::QueryEvent(u32 event_id) {
|
||||
LOG_CRITICAL(Service_NVDRV, "Unknown AS GPU Event {}", event_id);
|
||||
return nullptr;
|
||||
|
|
|
@ -5,14 +5,19 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "common/address_space.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
@ -30,17 +35,13 @@ class NvMap;
|
|||
|
||||
namespace Service::Nvidia::Devices {
|
||||
|
||||
constexpr u32 DEFAULT_BIG_PAGE_SIZE = 1 << 16;
|
||||
constexpr u32 DEFAULT_SMALL_PAGE_SIZE = 1 << 12;
|
||||
|
||||
class nvmap;
|
||||
|
||||
enum class AddressSpaceFlags : u32 {
|
||||
None = 0x0,
|
||||
FixedOffset = 0x1,
|
||||
Remap = 0x100,
|
||||
enum class MappingFlags : u32 {
|
||||
None = 0,
|
||||
Fixed = 1 << 0,
|
||||
Sparse = 1 << 1,
|
||||
Remap = 1 << 8,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AddressSpaceFlags);
|
||||
DECLARE_ENUM_FLAG_OPERATORS(MappingFlags);
|
||||
|
||||
class nvhost_as_gpu final : public nvdevice {
|
||||
public:
|
||||
|
@ -59,46 +60,15 @@ public:
|
|||
|
||||
Kernel::KEvent* QueryEvent(u32 event_id) override;
|
||||
|
||||
private:
|
||||
class BufferMap final {
|
||||
public:
|
||||
constexpr BufferMap() = default;
|
||||
|
||||
constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_)
|
||||
: start_addr{start_addr_}, end_addr{start_addr_ + size_} {}
|
||||
|
||||
constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_, VAddr cpu_addr_,
|
||||
bool is_allocated_)
|
||||
: start_addr{start_addr_}, end_addr{start_addr_ + size_}, cpu_addr{cpu_addr_},
|
||||
is_allocated{is_allocated_} {}
|
||||
|
||||
constexpr VAddr StartAddr() const {
|
||||
return start_addr;
|
||||
}
|
||||
|
||||
constexpr VAddr EndAddr() const {
|
||||
return end_addr;
|
||||
}
|
||||
|
||||
constexpr std::size_t Size() const {
|
||||
return end_addr - start_addr;
|
||||
}
|
||||
|
||||
constexpr VAddr CpuAddr() const {
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
constexpr bool IsAllocated() const {
|
||||
return is_allocated;
|
||||
}
|
||||
|
||||
private:
|
||||
GPUVAddr start_addr{};
|
||||
GPUVAddr end_addr{};
|
||||
VAddr cpu_addr{};
|
||||
bool is_allocated{};
|
||||
struct VaRegion {
|
||||
u64 offset;
|
||||
u32 page_size;
|
||||
u32 _pad0_;
|
||||
u64 pages;
|
||||
};
|
||||
static_assert(sizeof(VaRegion) == 0x18);
|
||||
|
||||
private:
|
||||
struct IoctlAllocAsEx {
|
||||
u32_le flags{}; // usually passes 1
|
||||
s32_le as_fd{}; // ignored; passes 0
|
||||
|
@ -113,7 +83,7 @@ private:
|
|||
struct IoctlAllocSpace {
|
||||
u32_le pages{};
|
||||
u32_le page_size{};
|
||||
AddressSpaceFlags flags{};
|
||||
MappingFlags flags{};
|
||||
INSERT_PADDING_WORDS(1);
|
||||
union {
|
||||
u64_le offset;
|
||||
|
@ -130,19 +100,19 @@ private:
|
|||
static_assert(sizeof(IoctlFreeSpace) == 16, "IoctlFreeSpace is incorrect size");
|
||||
|
||||
struct IoctlRemapEntry {
|
||||
u16_le flags{};
|
||||
u16_le kind{};
|
||||
u32_le nvmap_handle{};
|
||||
u32_le map_offset{};
|
||||
u32_le offset{};
|
||||
u32_le pages{};
|
||||
u16 flags;
|
||||
u16 kind;
|
||||
NvCore::NvMap::Handle::Id handle;
|
||||
u32 handle_offset_big_pages;
|
||||
u32 as_offset_big_pages;
|
||||
u32 big_pages;
|
||||
};
|
||||
static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
|
||||
|
||||
struct IoctlMapBufferEx {
|
||||
AddressSpaceFlags flags{}; // bit0: fixed_offset, bit2: cacheable
|
||||
u32_le kind{}; // -1 is default
|
||||
u32_le nvmap_handle{};
|
||||
MappingFlags flags{}; // bit0: fixed_offset, bit2: cacheable
|
||||
u32_le kind{}; // -1 is default
|
||||
NvCore::NvMap::Handle::Id handle;
|
||||
u32_le page_size{}; // 0 means don't care
|
||||
s64_le buffer_offset{};
|
||||
u64_le mapping_size{};
|
||||
|
@ -160,27 +130,15 @@ private:
|
|||
};
|
||||
static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size");
|
||||
|
||||
struct IoctlVaRegion {
|
||||
u64_le offset{};
|
||||
u32_le page_size{};
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u64_le pages{};
|
||||
};
|
||||
static_assert(sizeof(IoctlVaRegion) == 24, "IoctlVaRegion is incorrect size");
|
||||
|
||||
struct IoctlGetVaRegions {
|
||||
u64_le buf_addr{}; // (contained output user ptr on linux, ignored)
|
||||
u32_le buf_size{}; // forced to 2*sizeof(struct va_region)
|
||||
u32_le reserved{};
|
||||
IoctlVaRegion small{};
|
||||
IoctlVaRegion big{};
|
||||
std::array<VaRegion, 2> regions{};
|
||||
};
|
||||
static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2,
|
||||
static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(VaRegion) * 2,
|
||||
"IoctlGetVaRegions is incorrect size");
|
||||
|
||||
s32 channel{};
|
||||
u32 big_page_size{DEFAULT_BIG_PAGE_SIZE};
|
||||
|
||||
NvResult AllocAsEx(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
NvResult AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
NvResult Remap(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
|
@ -189,23 +147,74 @@ private:
|
|||
NvResult FreeSpace(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
NvResult BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
|
||||
void GetVARegionsImpl(IoctlGetVaRegions& params);
|
||||
NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output,
|
||||
std::vector<u8>& inline_output);
|
||||
|
||||
std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
|
||||
void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
|
||||
std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr);
|
||||
void FreeMappingLocked(u64 offset);
|
||||
|
||||
Module& module;
|
||||
|
||||
NvCore::Container& container;
|
||||
NvCore::NvMap& nvmap;
|
||||
|
||||
struct Mapping {
|
||||
VAddr ptr;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
bool fixed;
|
||||
bool big_page; // Only valid if fixed == false
|
||||
bool sparse_alloc;
|
||||
|
||||
Mapping(VAddr ptr_, u64 offset_, u64 size_, bool fixed_, bool big_page_, bool sparse_alloc_)
|
||||
: ptr(ptr_), offset(offset_), size(size_), fixed(fixed_), big_page(big_page_),
|
||||
sparse_alloc(sparse_alloc_) {}
|
||||
};
|
||||
|
||||
struct Allocation {
|
||||
u64 size;
|
||||
std::list<std::shared_ptr<Mapping>> mappings;
|
||||
u32 page_size;
|
||||
bool sparse;
|
||||
};
|
||||
|
||||
std::map<u64, std::shared_ptr<Mapping>>
|
||||
mapping_map; //!< This maps the base addresses of mapped buffers to their total sizes and
|
||||
//!< mapping type, this is needed as what was originally a single buffer may
|
||||
//!< have been split into multiple GPU side buffers with the remap flag.
|
||||
std::map<u64, Allocation> allocation_map; //!< Holds allocations created by AllocSpace from
|
||||
//!< which fixed buffers can be mapped into
|
||||
std::mutex mutex; //!< Locks all AS operations
|
||||
|
||||
struct VM {
|
||||
static constexpr u32 YUZU_PAGESIZE{0x1000};
|
||||
static constexpr u32 PAGE_SIZE_BITS{std::countr_zero(YUZU_PAGESIZE)};
|
||||
|
||||
static constexpr u32 SUPPORTED_BIG_PAGE_SIZES{0x30000};
|
||||
static constexpr u32 DEFAULT_BIG_PAGE_SIZE{0x20000};
|
||||
u32 big_page_size{DEFAULT_BIG_PAGE_SIZE};
|
||||
u32 big_page_size_bits{std::countr_zero(DEFAULT_BIG_PAGE_SIZE)};
|
||||
|
||||
static constexpr u32 VA_START_SHIFT{10};
|
||||
static constexpr u64 DEFAULT_VA_SPLIT{1ULL << 34};
|
||||
static constexpr u64 DEFAULT_VA_RANGE{1ULL << 37};
|
||||
u64 va_range_start{DEFAULT_BIG_PAGE_SIZE << VA_START_SHIFT};
|
||||
u64 va_range_split{DEFAULT_VA_SPLIT};
|
||||
u64 va_range_end{DEFAULT_VA_RANGE};
|
||||
|
||||
using Allocator = Common::FlatAllocator<u32, 0, 32>;
|
||||
|
||||
std::unique_ptr<Allocator> big_page_allocator;
|
||||
std::shared_ptr<Allocator>
|
||||
small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
|
||||
|
||||
bool initialised{};
|
||||
} vm;
|
||||
std::shared_ptr<Tegra::MemoryManager> gmmu;
|
||||
|
||||
// This is expected to be ordered, therefore we must use a map, not unordered_map
|
||||
std::map<GPUVAddr, BufferMap> buffer_mappings;
|
||||
// s32 channel{};
|
||||
// u32 big_page_size{VM::DEFAULT_BIG_PAGE_SIZE};
|
||||
};
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue