From 932810310916ea661100cfb01dbf89cd8bc5b6fe Mon Sep 17 00:00:00 2001 From: xbzk Date: Mon, 27 Jul 2026 21:55:19 -0300 Subject: [PATCH] [nvdrv] Reset process resources for homebrew handoff Track NVDRV sessions by process and aruid so in-place homebrew handoffs can close process-owned device files and sessions before loading the next NRO. Also unlock nvmap device-shared pages during session cleanup to avoid stale GPU mappings leaking across repeated handoffs. --- src/core/hle/service/nvdrv/core/container.cpp | 92 ++++++++++++++- src/core/hle/service/nvdrv/core/container.h | 7 ++ src/core/hle/service/nvdrv/core/nvmap.cpp | 64 +++++++++-- src/core/hle/service/nvdrv/nvdrv.cpp | 105 +++++++++++++++++- src/core/hle/service/nvdrv/nvdrv.h | 10 ++ .../hle/service/nvdrv/nvdrv_interface.cpp | 5 +- 6 files changed, 272 insertions(+), 11 deletions(-) diff --git a/src/core/hle/service/nvdrv/core/container.cpp b/src/core/hle/service/nvdrv/core/container.cpp index 9edce03f64..f65ee2bafa 100644 --- a/src/core/hle/service/nvdrv/core/container.cpp +++ b/src/core/hle/service/nvdrv/core/container.cpp @@ -1,10 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-FileCopyrightText: 2022 Skyline Team and Contributors // SPDX-License-Identifier: GPL-3.0-or-later +#include #include #include #include +#include #include "core/hle/kernel/k_process.h" #include "core/hle/service/nvdrv/core/container.h" @@ -40,6 +45,16 @@ Container::Container(Tegra::Host1x::Host1x& host1x_) { Container::~Container() = default; +static bool IsSameProcess(Kernel::KProcess* lhs, Kernel::KProcess* rhs) { + if (lhs == rhs) { + return true; + } + if (lhs == nullptr || rhs == nullptr) { + return false; + } + return lhs->GetProcessId() == rhs->GetProcessId(); +} + SessionId Container::OpenSession(Kernel::KProcess* process) { using namespace Common::Literals; @@ -48,7 +63,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) { if (!session.is_active) { continue; } - if (session.process == process) { + if (IsSameProcess(session.process, process)) { session.ref_count++; return session.id; } @@ -116,7 +131,15 @@ SessionId Container::OpenSession(Kernel::KProcess* process) { void Container::CloseSession(SessionId session_id) { std::scoped_lock lk(impl->session_guard); + if (session_id.id >= impl->sessions.size()) { + return; + } + auto& session = impl->sessions[session_id.id]; + if (!session.is_active || session.ref_count <= 0) { + return; + } + if (--session.ref_count > 0) { return; } @@ -134,6 +157,73 @@ void Container::CloseSession(SessionId session_id) { impl->id_pool.emplace_front(session_id.id); } +size_t Container::CloseSessions(std::span session_ids) { + std::vector valid_session_ids; + valid_session_ids.reserve(session_ids.size()); + + { + std::scoped_lock lk(impl->session_guard); + for (const auto session_id : session_ids) { + if (session_id.id >= impl->sessions.size()) { + continue; + } + + auto& session = impl->sessions[session_id.id]; + if (!session.is_active) { + continue; + } + + const auto duplicate = std::ranges::any_of( + valid_session_ids, [session_id](const auto candidate) { + return candidate.id == session_id.id; + }); + if (duplicate) { + continue; + } + + session.ref_count = 1; + valid_session_ids.push_back(session_id); + } + } + + for (const auto session_id : valid_session_ids) { + CloseSession(session_id); + } + + return valid_session_ids.size(); +} + +std::vector Container::GetSessionIdsForProcess(Kernel::KProcess* process) { + std::vector session_ids; + std::scoped_lock lk(impl->session_guard); + for (const auto& session : impl->sessions) { + if (!session.is_active || !IsSameProcess(session.process, process)) { + continue; + } + + session_ids.push_back(session.id); + } + + return session_ids; +} + +std::vector Container::GetActiveSessionIds() const { + std::vector session_ids; + std::scoped_lock lk(impl->session_guard); + for (const auto& session : impl->sessions) { + if (session.is_active) { + session_ids.push_back(session.id); + } + } + + return session_ids; +} + +bool Container::IsSessionActive(SessionId session_id) const { + std::scoped_lock lk(impl->session_guard); + return session_id.id < impl->sessions.size() && impl->sessions[session_id.id].is_active; +} + Session* Container::GetSession(SessionId session_id) { std::atomic_thread_fence(std::memory_order_acquire); return &impl->sessions[session_id.id]; diff --git a/src/core/hle/service/nvdrv/core/container.h b/src/core/hle/service/nvdrv/core/container.h index 62404e14a7..e5cc164e1c 100644 --- a/src/core/hle/service/nvdrv/core/container.h +++ b/src/core/hle/service/nvdrv/core/container.h @@ -9,7 +9,10 @@ #include #include +#include +#include #include +#include #include "core/device_memory_manager.h" #include "core/hle/service/nvdrv/nvdata.h" @@ -59,6 +62,10 @@ public: SessionId OpenSession(Kernel::KProcess* process); void CloseSession(SessionId id); + size_t CloseSessions(std::span session_ids); + std::vector GetSessionIdsForProcess(Kernel::KProcess* process); + std::vector GetActiveSessionIds() const; + bool IsSessionActive(SessionId id) const; Session* GetSession(SessionId id); diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp index e2bf49da62..f7dcf566e2 100644 --- a/src/core/hle/service/nvdrv/core/nvmap.cpp +++ b/src/core/hle/service/nvdrv/core/nvmap.cpp @@ -6,10 +6,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include +#include #include "common/alignment.h" #include "common/assert.h" #include "common/logging.h" +#include "core/hle/kernel/k_process.h" #include "core/hle/service/nvdrv/core/container.h" #include "core/hle/service/nvdrv/core/heap_mapper.h" #include "core/hle/service/nvdrv/core/nvmap.h" @@ -326,19 +328,65 @@ std::optional NvMap::FreeHandle(Handle::Id handle, bool interna } void NvMap::UnmapAllHandles(NvCore::SessionId session_id) { - auto handles_copy = [&] { + auto* session = core.GetSession(session_id); + auto* process = session != nullptr ? session->process : nullptr; + auto handle_ids = [&] { std::scoped_lock lk{handles_lock}; - return handles; + std::vector ids; + ids.reserve(handles.size()); + + for (const auto& entry : handles) { + ids.push_back(entry.first); + } + + return ids; }(); - for (auto& [id, handle] : handles_copy) { - { - std::scoped_lock lk{handle->mutex}; - if (handle->session_id.id != session_id.id || handle->dupes <= 0) { - continue; + for (const auto id : handle_ids) { + bool unlocked_pages = false; + while (true) { + bool last_user_reference = false; + VAddr address = 0; + size_t size = 0; + { + const auto handle = GetHandle(id); + if (!handle) { + break; + } + + std::scoped_lock lk{handle->mutex}; + if (handle->session_id.id != session_id.id || handle->dupes <= 0) { + break; + } + + last_user_reference = handle->dupes == 1; + address = handle->address; + size = handle->size; + } + + const auto free_info = FreeHandle(id, false); + if (!free_info) { + break; + } + + if (!unlocked_pages && process != nullptr && address != 0 && size != 0 && + (free_info->can_unlock || last_user_reference)) { + const auto unlock_result = + process->GetPageTable().UnlockForDeviceAddressSpace(address, size); + if (unlock_result.IsError()) { + LOG_WARNING(Service_NVDRV, + "NextLoad: nvmap session cleanup unlock failed, " + "handle={}, session={}, address=0x{:016X}, size=0x{:X}, " + "result={:#X}", + id, session_id.id, address, size, unlock_result.raw); + } + unlocked_pages = true; + } + + if (last_user_reference) { + break; } } - FreeHandle(id, false); } } diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 8c618ca258..08d28a8784 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -1,12 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2021 yuzu Emulator Project // SPDX-FileCopyrightText: 2021 Skyline Team and Contributors // SPDX-License-Identifier: GPL-3.0-or-later +#include #include +#include -#include #include "core/core.h" #include "core/hle/kernel/k_event.h" +#include "core/hle/kernel/k_process.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/nvdrv/core/container.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" @@ -133,6 +138,9 @@ DeviceFD Module::Open(const std::string& device_name, NvCore::SessionId session_ auto device = builder(fd)->second; device->OnOpen(session_id, fd); + if (container.IsSessionActive(session_id)) { + open_file_sessions.emplace(fd, session_id); + } return fd; } @@ -204,6 +212,7 @@ NvResult Module::Close(DeviceFD fd) { itr->second->OnClose(fd); open_files.erase(itr); + open_file_sessions.erase(fd); return NvResult::Success; } @@ -228,4 +237,98 @@ NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) { return NvResult::Success; } +static bool ContainsSession(std::span session_ids, + NvCore::SessionId session_id) { + return std::ranges::any_of(session_ids, [session_id](const auto candidate) { + return candidate.id == session_id.id; + }); +} + +static void AppendUniqueSession(std::vector& session_ids, + NvCore::SessionId session_id) { + if (!ContainsSession(session_ids, session_id)) { + session_ids.push_back(session_id); + } +} + +size_t Module::CloseFilesForSessions(std::span session_ids) { + std::vector fds; + fds.reserve(open_file_sessions.size()); + + for (const auto& [fd, session_id] : open_file_sessions) { + if (ContainsSession(session_ids, session_id)) { + fds.push_back(fd); + } + } + + for (const auto fd : fds) { + Close(fd); + } + + return fds.size(); +} + +void Module::CloseSession(NvCore::SessionId session_id) { + container.CloseSession(session_id); +} + +void Module::TrackSessionAruid(NvCore::SessionId session_id, u64 aruid) { + const bool active = container.IsSessionActive(session_id); + if (active) { + session_aruids[session_id.id] = aruid; + } +} + +std::vector Module::GetSessionIdsForAruid(u64 aruid) const { + std::vector session_ids; + for (const auto& [session_id, session_aruid] : session_aruids) { + if (session_aruid == aruid) { + session_ids.push_back(NvCore::SessionId{session_id}); + } + } + + return session_ids; +} + +size_t Module::ResetForProcess(Kernel::KProcess* process) { + const auto process_id = process != nullptr ? process->GetProcessId() : 0; + auto session_ids = container.GetSessionIdsForProcess(process); + + if (process_id != 0) { + for (const auto session_id : GetSessionIdsForAruid(process_id)) { + AppendUniqueSession(session_ids, session_id); + } + } + + const auto active_session_ids = container.GetActiveSessionIds(); + const auto active_before = active_session_ids.size(); + const bool has_active_candidate = + std::ranges::any_of(session_ids, [this](const auto session_id) { + return container.IsSessionActive(session_id); + }); + bool used_active_sessions = false; + if (!has_active_candidate && !active_session_ids.empty()) { + for (const auto session_id : active_session_ids) { + AppendUniqueSession(session_ids, session_id); + } + used_active_sessions = true; + } + + const auto closed_files = CloseFilesForSessions(session_ids); + const auto closed_sessions = container.CloseSessions(session_ids); + for (const auto session_id : session_ids) { + if (!container.IsSessionActive(session_id)) { + session_aruids.erase(session_id.id); + } + } + + if (used_active_sessions) { + LOG_WARNING(Service_NVDRV, + "NextLoad: NVDRV reset used active sessions because process-owned " + "sessions were not found, process_id={}, sessions={}, files={}, active_before={}", + process_id, closed_sessions, closed_files, active_before); + } + return closed_sessions; +} + } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index 329ac91c41..5db6ec6666 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include "common/common_types.h" @@ -26,6 +27,7 @@ class System; namespace Kernel { class KEvent; +class KProcess; } namespace Service::Nvidia { @@ -89,6 +91,9 @@ public: NvResult Close(DeviceFD fd); NvResult QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event); + void CloseSession(NvCore::SessionId session_id); + void TrackSessionAruid(NvCore::SessionId session_id, u64 aruid); + size_t ResetForProcess(Kernel::KProcess* process); NvCore::Container& GetContainer() { return container; @@ -106,12 +111,17 @@ private: using FilesContainerType = ankerl::unordered_dense::map>; /// Mapping of file descriptors to the devices they reference. FilesContainerType open_files; + ankerl::unordered_dense::map open_file_sessions; + ankerl::unordered_dense::map session_aruids; KernelHelpers::ServiceContext service_context; EventInterface events_interface; ankerl::unordered_dense::map> builders; + + size_t CloseFilesForSessions(std::span session_ids); + std::vector GetSessionIdsForAruid(u64 aruid) const; }; void LoopProcess(Core::System& system); diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.cpp b/src/core/hle/service/nvdrv/nvdrv_interface.cpp index 5fae68adbb..c02ed56866 100644 --- a/src/core/hle/service/nvdrv/nvdrv_interface.cpp +++ b/src/core/hle/service/nvdrv/nvdrv_interface.cpp @@ -212,7 +212,10 @@ void NVDRV::QueryEvent(HLERequestContext& ctx) { void NVDRV::SetAruid(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; pid = rp.Pop(); - LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#x}", pid); + LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid={:#X}", pid); + if (is_initialized) { + nvdrv->TrackSessionAruid(session_id, pid); + } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess);