diff options
| author | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 04:56:16 -0400 |
|---|---|---|
| committer | Takoroka <137927095+Takoroka@users.noreply.github.com> | 2026-10-28 04:56:16 -0400 |
| commit | 2c6d8f03aadb3c9d900909bd4af64bbb18614d6d (patch) | |
| tree | f7ae707fd278434de8ced86a266d43a6752dc827 /src | |
| parent | 64c91974f4455a63a025149a4ba8e2c46894bdc7 (diff) | |
nx2: unmap GPU memory the game frees
nx2:gpu 213 (NormalUnmapGpuMemory4K) and 102 (destroy GPU-accessible
memory) were no-ops, so Eden kept freed memory mapped for the GPU. Title 4
frees its customization-room memory right after saving and reuses it as
ordinary heap; pending GPU->CPU write-backs for the old buffers then landed on
the new objects, and two threads crashed on garbage pointers ~5 s later
(the post-customization freeze).
Unmap the range from every address space (the GMMU unmap drops cached
buffers/images and their pending downloads), keep the registry's mapping
list in step, re-map 211 requests even at the memory's own address, and
drop freed regions from the semaphore mirror table, which every 8 KiB
allocation used to fill for good after 32 entries.
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp | 16 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h | 1 | ||||
| -rw-r--r-- | src/core/hle/service/nvdrv/devices/nx2_stub_device.h | 45 | ||||
| -rw-r--r-- | src/core/hle/service/nx2_gsv.h | 49 | ||||
| -rw-r--r-- | src/video_core/nx2.h | 17 |
5 files changed, 115 insertions, 13 deletions
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 6de442fcdf..4dc667fa18 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -58,9 +58,11 @@ NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> i const auto result = WrapFixed(this, &nvhost_as_gpu::AllocAsEx, input, output); if (result == NvResult::Success && g_nx2_driver) { Nx2GpuMemoryRegistry::Get().AddListener( - this, [this](u64 gpu_address, u64 cpu_address, u64 size) { + this, + [this](u64 gpu_address, u64 cpu_address, u64 size) { Nx2Map(gpu_address, cpu_address, size); - }); + }, + [this](u64 gpu_address, u64 size) { Nx2Unmap(gpu_address, size); }); } return result; } @@ -143,6 +145,16 @@ void nvhost_as_gpu::Nx2Map(u64 gpu_address, u64 cpu_address, u64 size) { LOG_DEBUG(Service_NVDRV, "Switch 2 GPU mapping {:#x}+{:#x} -> CPU {:#x}", gpu_address, size, cpu_address); } + +void nvhost_as_gpu::Nx2Unmap(u64 gpu_address, u64 size) { + std::scoped_lock lock(mutex); + if (!gmmu) { + return; + } + gmmu->Unmap(gpu_address, size); + LOG_DEBUG(Service_NVDRV, "Switch 2 GPU unmapping {:#x}+{:#x}", gpu_address, size); +} + void nvhost_as_gpu::OnClose(DeviceFD fd) {} // Switch 2 NvRm (with characteristics flag 26) asks for the read-only syncpoint shim: a GPU page diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index ca94dc2632..b094ab9936 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -233,6 +233,7 @@ private: // Switch 2: maps GPU-accessible process memory (CPU range) at a GPU address. void Nx2Map(u64 gpu_address, u64 cpu_address, u64 size); + void Nx2Unmap(u64 gpu_address, u64 size); NvCore::SessionId nx2_session{}; std::map<std::pair<u64, u64>, DAddr> nx2_device_ranges; }; diff --git a/src/core/hle/service/nvdrv/devices/nx2_stub_device.h b/src/core/hle/service/nvdrv/devices/nx2_stub_device.h index b8556af894..e5220cd26f 100644 --- a/src/core/hle/service/nvdrv/devices/nx2_stub_device.h +++ b/src/core/hle/service/nvdrv/devices/nx2_stub_device.h @@ -38,6 +38,7 @@ inline NvResult Nx2UnknownIoctl(const char* device, Ioctl command, std::span<con class Nx2GpuMemoryRegistry { public: using Listener = std::function<void(u64 gpu_address, u64 cpu_address, u64 size)>; + using UnmapListener = std::function<void(u64 gpu_address, u64 size)>; static Nx2GpuMemoryRegistry& Get() { static Nx2GpuMemoryRegistry registry; @@ -50,18 +51,28 @@ public: void MapAt(u64 gpu_address, u64 cpu_address, u64 size) { std::scoped_lock lk{mutex}; + TrimMappings(gpu_address, size); mappings.push_back({gpu_address, cpu_address, size}); for (auto& [owner, listener] : listeners) { - listener(gpu_address, cpu_address, size); + listener.map(gpu_address, cpu_address, size); } } - void AddListener(const void* owner, Listener listener) { + // NormalUnmapGpuMemory4K, or destroying the memory: the GPU range stops reaching it. + void Unmap(u64 gpu_address, u64 size) { + std::scoped_lock lk{mutex}; + TrimMappings(gpu_address, size); + for (auto& [owner, listener] : listeners) { + listener.unmap(gpu_address, size); + } + } + + void AddListener(const void* owner, Listener listener, UnmapListener unmap_listener) { std::scoped_lock lk{mutex}; for (const auto& m : mappings) { listener(m.gpu_address, m.cpu_address, m.size); } - listeners.insert_or_assign(owner, std::move(listener)); + listeners.insert_or_assign(owner, Listeners{std::move(listener), std::move(unmap_listener)}); } void RemoveListener(const void* owner) { @@ -100,9 +111,35 @@ private: u64 size; }; + struct Listeners { + Listener map; + UnmapListener unmap; + }; + + // Drops [gpu_address, +size) from the recorded mappings, keeping the parts outside it. + void TrimMappings(u64 gpu_address, u64 size) { + const u64 end = gpu_address + size; + std::vector<Mapping> kept; + kept.reserve(mappings.size() + 1); + for (const auto& m : mappings) { + const u64 m_end = m.gpu_address + m.size; + if (m_end <= gpu_address || m.gpu_address >= end) { + kept.push_back(m); + continue; + } + if (m.gpu_address < gpu_address) { + kept.push_back({m.gpu_address, m.cpu_address, gpu_address - m.gpu_address}); + } + if (m_end > end) { + kept.push_back({end, m.cpu_address + (end - m.gpu_address), m_end - end}); + } + } + mappings = std::move(kept); + } + std::mutex mutex; std::vector<Mapping> mappings; - std::map<const void*, Listener> listeners; + std::map<const void*, Listeners> listeners; }; // Device node that Switch 2 nvgpu drivers open but Switch 1 never had (e.g. /dev/nvhost-tsg-gpu). diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h index 302c5a41a8..b8156b1921 100644 --- a/src/core/hle/service/nx2_gsv.h +++ b/src/core/hle/service/nx2_gsv.h @@ -254,9 +254,9 @@ public: {203, &INx2GpuSession::GetVaRange, "GetVaRange (guessed)"}, // Sub-session commands (gpu 22 hands out a second session to this same object). {101, &INx2GpuSession::SubRegisterMemory, "CreateGpuAccessibleMemory"}, - {102, &INx2GpuSession::SubSlotJob<3>, "Sub102 (job, slot in word 3)"}, + {102, &INx2GpuSession::SubDestroyMemory, "DestroyGpuAccessibleMemory (guessed)"}, {211, &INx2GpuSession::SubSubmitJob, "NormalMapGpuMemory4K"}, - {213, &INx2GpuSession::SubSlotJob<8>, "NormalUnmapGpuMemory4K"}, + {213, &INx2GpuSession::SubUnmapJob, "NormalUnmapGpuMemory4K"}, {214, &INx2GpuSession::SubCreateGpuOnlyRegion, "CreateGpuOnlyRegion (guessed)"}, }); } @@ -391,10 +391,10 @@ private: LOG_INFO(Service, "nx2:gpu 211 map [{:#x}, {:#x}] handle={} offset={:#x} slot={}", a, b, handle, c, slot); if (const auto it = m_memory.find(handle); it != m_memory.end() && b > a) { + // Also at the memory's own address: an earlier 213 may have unmapped the mapping 101 + // made there. const u64 cpu = it->second.address + c; - if (cpu != a) { - Nvidia::Devices::Nx2GpuMemoryRegistry::Get().MapAt(a, cpu, b - a + 1); - } + Nvidia::Devices::Nx2GpuMemoryRegistry::Get().MapAt(a, cpu, b - a + 1); } else { LOG_WARNING(Service, "nx2:gpu 211 map of unknown handle {}", handle); } @@ -403,6 +403,45 @@ private: rb.Push(ResultSuccess); } + // Sub 213: words 3-4 first GPU address, 5-6 last, job slot in word 8. + void SubUnmapJob(HLERequestContext& ctx) { + const auto* w = ctx.CommandBuffer(); + const u64 first = u64{w[3]} | (u64{w[4]} << 32); + const u64 last = u64{w[5]} | (u64{w[6]} << 32); + LOG_DEBUG(Service, "nx2:gpu 213 unmap [{:#x}, {:#x}] slot={}", first, last, w[8]); + if (last > first) { + Nvidia::Devices::Nx2GpuMemoryRegistry::Get().Unmap(first, last - first + 1); + } + EventFor(w[8])->Signal(system.Kernel()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + // Sub 102: word 2 is the memory handle 101 returned, word 3 the job slot. The game frees the + // memory (svc 0x80 clears GPU access) and reuses it as ordinary heap right after, so nothing + // on the GPU side may keep it: drop the mapping 101 made at its own address. + void SubDestroyMemory(HLERequestContext& ctx) { + const auto* w = ctx.CommandBuffer(); + const u32 handle = w[2]; + if (const auto it = m_memory.find(handle); it != m_memory.end()) { + const auto [address, size] = it->second; + LOG_DEBUG(Service, "nx2:gpu 102 destroy handle={} [{:#x}, +{:#x}] slot={}", handle, + address, size, w[3]); + if (size != 0) { + Nvidia::Devices::Nx2GpuMemoryRegistry::Get().Unmap(address, size); + } + if (size == 0x2000) { + Tegra::Nx2RemoveSemaphoreRegion(address); + } + m_memory.erase(it); + } else { + LOG_WARNING(Service, "nx2:gpu 102 destroy of unknown handle {}", handle); + } + EventFor(w[3])->Signal(system.Kernel()); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + // Sub 214: GPU-only memory [start, last] (words 3-6, job slot in word 11). The graphics // server backs it itself: the client never maps anything there but renders into it (title 4 // keeps its render targets in six 300 MB regions at 0x81_0000_0000). Back each region with diff --git a/src/video_core/nx2.h b/src/video_core/nx2.h index ef6619df3f..7bbbf14785 100644 --- a/src/video_core/nx2.h +++ b/src/video_core/nx2.h @@ -23,19 +23,32 @@ inline std::array<std::atomic<u64>, Nx2MaxSemaphoreRegions> g_nx2_semaphore_regi inline void Nx2AddSemaphoreRegion(u64 start) { for (auto& region : g_nx2_semaphore_regions) { + if (region.load() == start) { + return; + } + } + for (auto& region : g_nx2_semaphore_regions) { u64 expected = 0; - if (region.load() == start || region.compare_exchange_strong(expected, start)) { + if (region.compare_exchange_strong(expected, start)) { return; } } } +/// The region's memory was destroyed: stop mirroring into it. +inline void Nx2RemoveSemaphoreRegion(u64 start) { + for (auto& region : g_nx2_semaphore_regions) { + u64 expected = start; + region.compare_exchange_strong(expected, 0); + } +} + /// GPU VA in the first page that mirrors a release at `address`, or 0. inline u64 Nx2SemaphoreMirror(u64 address) { for (const auto& region : g_nx2_semaphore_regions) { const u64 start = region.load(std::memory_order_relaxed); if (start == 0) { - break; + continue; } if (address >= start + 0x1000 && address < start + 0x2000) { return address - 0x1000; |
