Browse Source

[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.
xbzk 6 days ago
parent
commit
9328103109
  1. 92
      src/core/hle/service/nvdrv/core/container.cpp
  2. 7
      src/core/hle/service/nvdrv/core/container.h
  3. 64
      src/core/hle/service/nvdrv/core/nvmap.cpp
  4. 105
      src/core/hle/service/nvdrv/nvdrv.cpp
  5. 10
      src/core/hle/service/nvdrv/nvdrv.h
  6. 5
      src/core/hle/service/nvdrv/nvdrv_interface.cpp

92
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 yuzu Emulator Project
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors // SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <atomic> #include <atomic>
#include <deque> #include <deque>
#include <mutex> #include <mutex>
#include <vector>
#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_process.h"
#include "core/hle/service/nvdrv/core/container.h" #include "core/hle/service/nvdrv/core/container.h"
@ -40,6 +45,16 @@ Container::Container(Tegra::Host1x::Host1x& host1x_) {
Container::~Container() = default; 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) { SessionId Container::OpenSession(Kernel::KProcess* process) {
using namespace Common::Literals; using namespace Common::Literals;
@ -48,7 +63,7 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
if (!session.is_active) { if (!session.is_active) {
continue; continue;
} }
if (session.process == process) {
if (IsSameProcess(session.process, process)) {
session.ref_count++; session.ref_count++;
return session.id; return session.id;
} }
@ -116,7 +131,15 @@ SessionId Container::OpenSession(Kernel::KProcess* process) {
void Container::CloseSession(SessionId session_id) { void Container::CloseSession(SessionId session_id) {
std::scoped_lock lk(impl->session_guard); std::scoped_lock lk(impl->session_guard);
if (session_id.id >= impl->sessions.size()) {
return;
}
auto& session = impl->sessions[session_id.id]; auto& session = impl->sessions[session_id.id];
if (!session.is_active || session.ref_count <= 0) {
return;
}
if (--session.ref_count > 0) { if (--session.ref_count > 0) {
return; return;
} }
@ -134,6 +157,73 @@ void Container::CloseSession(SessionId session_id) {
impl->id_pool.emplace_front(session_id.id); impl->id_pool.emplace_front(session_id.id);
} }
size_t Container::CloseSessions(std::span<const SessionId> session_ids) {
std::vector<SessionId> 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<SessionId> Container::GetSessionIdsForProcess(Kernel::KProcess* process) {
std::vector<SessionId> 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<SessionId> Container::GetActiveSessionIds() const {
std::vector<SessionId> 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) { Session* Container::GetSession(SessionId session_id) {
std::atomic_thread_fence(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_acquire);
return &impl->sessions[session_id.id]; return &impl->sessions[session_id.id];

7
src/core/hle/service/nvdrv/core/container.h

@ -9,7 +9,10 @@
#include <deque> #include <deque>
#include <memory> #include <memory>
#include <span>
#include <cstddef>
#include <ankerl/unordered_dense.h> #include <ankerl/unordered_dense.h>
#include <vector>
#include "core/device_memory_manager.h" #include "core/device_memory_manager.h"
#include "core/hle/service/nvdrv/nvdata.h" #include "core/hle/service/nvdrv/nvdata.h"
@ -59,6 +62,10 @@ public:
SessionId OpenSession(Kernel::KProcess* process); SessionId OpenSession(Kernel::KProcess* process);
void CloseSession(SessionId id); void CloseSession(SessionId id);
size_t CloseSessions(std::span<const SessionId> session_ids);
std::vector<SessionId> GetSessionIdsForProcess(Kernel::KProcess* process);
std::vector<SessionId> GetActiveSessionIds() const;
bool IsSessionActive(SessionId id) const;
Session* GetSession(SessionId id); Session* GetSession(SessionId id);

64
src/core/hle/service/nvdrv/core/nvmap.cpp

@ -6,10 +6,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <functional> #include <functional>
#include <vector>
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/logging.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/container.h"
#include "core/hle/service/nvdrv/core/heap_mapper.h" #include "core/hle/service/nvdrv/core/heap_mapper.h"
#include "core/hle/service/nvdrv/core/nvmap.h" #include "core/hle/service/nvdrv/core/nvmap.h"
@ -326,19 +328,65 @@ std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool interna
} }
void NvMap::UnmapAllHandles(NvCore::SessionId session_id) { 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}; std::scoped_lock lk{handles_lock};
return handles;
std::vector<Handle::Id> 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);
} }
} }

105
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 yuzu Emulator Project
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors // SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <utility> #include <utility>
#include <vector>
#include <fmt/ranges.h>
#include "core/core.h" #include "core/core.h"
#include "core/hle/kernel/k_event.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/ipc_helpers.h"
#include "core/hle/service/nvdrv/core/container.h" #include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/devices/nvdevice.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; auto device = builder(fd)->second;
device->OnOpen(session_id, fd); device->OnOpen(session_id, fd);
if (container.IsSessionActive(session_id)) {
open_file_sessions.emplace(fd, session_id);
}
return fd; return fd;
} }
@ -204,6 +212,7 @@ NvResult Module::Close(DeviceFD fd) {
itr->second->OnClose(fd); itr->second->OnClose(fd);
open_files.erase(itr); open_files.erase(itr);
open_file_sessions.erase(fd);
return NvResult::Success; return NvResult::Success;
} }
@ -228,4 +237,98 @@ NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) {
return NvResult::Success; return NvResult::Success;
} }
static bool ContainsSession(std::span<const NvCore::SessionId> 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<NvCore::SessionId>& session_ids,
NvCore::SessionId session_id) {
if (!ContainsSession(session_ids, session_id)) {
session_ids.push_back(session_id);
}
}
size_t Module::CloseFilesForSessions(std::span<const NvCore::SessionId> session_ids) {
std::vector<DeviceFD> 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<NvCore::SessionId> Module::GetSessionIdsForAruid(u64 aruid) const {
std::vector<NvCore::SessionId> 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 } // namespace Service::Nvidia

10
src/core/hle/service/nvdrv/nvdrv.h

@ -12,6 +12,7 @@
#include <memory> #include <memory>
#include <span> #include <span>
#include <string> #include <string>
#include <vector>
#include <ankerl/unordered_dense.h> #include <ankerl/unordered_dense.h>
#include "common/common_types.h" #include "common/common_types.h"
@ -26,6 +27,7 @@ class System;
namespace Kernel { namespace Kernel {
class KEvent; class KEvent;
class KProcess;
} }
namespace Service::Nvidia { namespace Service::Nvidia {
@ -89,6 +91,9 @@ public:
NvResult Close(DeviceFD fd); NvResult Close(DeviceFD fd);
NvResult QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event); 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() { NvCore::Container& GetContainer() {
return container; return container;
@ -106,12 +111,17 @@ private:
using FilesContainerType = ankerl::unordered_dense::map<DeviceFD, std::shared_ptr<Devices::nvdevice>>; using FilesContainerType = ankerl::unordered_dense::map<DeviceFD, std::shared_ptr<Devices::nvdevice>>;
/// Mapping of file descriptors to the devices they reference. /// Mapping of file descriptors to the devices they reference.
FilesContainerType open_files; FilesContainerType open_files;
ankerl::unordered_dense::map<DeviceFD, NvCore::SessionId> open_file_sessions;
ankerl::unordered_dense::map<size_t, u64> session_aruids;
KernelHelpers::ServiceContext service_context; KernelHelpers::ServiceContext service_context;
EventInterface events_interface; EventInterface events_interface;
ankerl::unordered_dense::map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders; ankerl::unordered_dense::map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders;
size_t CloseFilesForSessions(std::span<const NvCore::SessionId> session_ids);
std::vector<NvCore::SessionId> GetSessionIdsForAruid(u64 aruid) const;
}; };
void LoopProcess(Core::System& system); void LoopProcess(Core::System& system);

5
src/core/hle/service/nvdrv/nvdrv_interface.cpp

@ -212,7 +212,10 @@ void NVDRV::QueryEvent(HLERequestContext& ctx) {
void NVDRV::SetAruid(HLERequestContext& ctx) { void NVDRV::SetAruid(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx}; IPC::RequestParser rp{ctx};
pid = rp.Pop<u64>(); pid = rp.Pop<u64>();
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}; IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess); rb.Push(ResultSuccess);

Loading…
Cancel
Save