Compare commits

..

2 Commits

Author SHA1 Message Date
xbzk edc38b8230 Update src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/UShortSetting.kt
removed acidental copy paste
2026-08-29 21:27:59 +02:00
xbzk ffecb4af7b [debug] debug knobs adjustments 2026-08-29 07:43:28 -03:00
17 changed files with 276 additions and 143 deletions
+27 -11
View File
@@ -43,6 +43,7 @@ This guide will walk you through adding a new boolean toggle setting to Eden's c
Firstly add your desired toggle:
Example: `src/common/setting.h`
```cpp
SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions};
```
@@ -67,6 +68,7 @@ Common Categories:
Add the toggle to the Qt UI, where you wish for it to appear and place it there.
Example: `src/qt_common/config/shared_translation.cpp`
```cpp
INSERT(Settings,
your_setting_name,
@@ -91,6 +93,7 @@ INSERT(Settings,
Add where it should be in the settings.
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt`
```kts
RENDERER_YOUR_SETTING_NAME("your_setting_name"),
```
@@ -106,6 +109,7 @@ RENDERER_YOUR_SETTING_NAME("your_setting_name"),
Add the toggle to the Kotlin (Android) UI
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt`
```kts
put(
SwitchSetting(
@@ -123,6 +127,7 @@ put(
Add your setting within the right category.
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt`
```kts
add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
```
@@ -137,6 +142,7 @@ add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
Add your setting and description in the appropriate place.
Example: `src/android/app/src/main/res/values/strings.xml`
```xml
<string name="your_setting_name">Your Setting Display Name</string>
<string name="your_setting_name_description">Detailed description of what this setting does. Explain any caveats, requirements, or warnings here.</string>
@@ -150,6 +156,7 @@ Now the UI part is done find a place in the code for the toggle,
And use it to your heart's desire!
Example:
```cpp
const bool your_value = Settings::values.your_setting_name.GetValue();
@@ -196,25 +203,31 @@ Common advantages recap:
#### Accessing Debug Knobs (dev side)
Use the `Settings::getDebugKnobAt(u8 i)` function to check if a specific bit is set:
Use the `Settings::GetDebugKnobAt(u8 i)` function to check if a specific bit is set:
```cpp
//cpp side
#include "common/settings.h"
//To use it as a general purpose uint var:
unsigned int debug_knobs = Settings::values.debug_knobs.GetValue();
// Check if bit 0 is set
bool feature_enabled = Settings::getDebugKnobAt(0);
bool feature_enabled = Settings::GetDebugKnobAt(0);
// Check if bit 15 is set
bool another_feature = Settings::getDebugKnobAt(15);
bool another_feature = Settings::GetDebugKnobAt(15);
```
```kts
//kotlin side
import org.yuzu.yuzu_emu.features.settings.model.Settings
//To use it as a general purpose uint var
val debug_knobs: Int = UShortSetting.DEBUG_KNOBS.getInt()
// Check if bit x is set
bool feature_enabled = Settings.getDebugKnobAt(x); //x as integer from 0 to 15
bool feature_enabled = Settings.GetDebugKnobAt(x); //x as integer from 0 to 15
```
The function returns `true` if the specified bit (0-15) is set in the `debug_knobs` value, `false` otherwise.
@@ -247,6 +260,7 @@ There are two main confusions when talking about knobs:
Sometimes when an user reports: knobs 1 and 2 gets better performance, dev may get confuse whether he means the knobs 1 and 2 literally, or the 1st and 2nd knobs (knobs 0 and 1).
Debug knobs are **zero-based**, which means:
* The first knob is the knob(0) (or knob0 henceforth), and the last one is the 15 (knob15, likewise)
* You can talk: "knob0 is enabled/disabled", "In this video i was using only knobs 0 and 2", etc.
@@ -259,6 +273,7 @@ Whenever you're instructing tests or reporting results, be precise about whether
ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once:
Examples:
- **knobs=0**: no knobs enabled
- **knobs=1**: knob0 enabled, others disabled
- **knobs=2**: knob1 enabled, others disabled
@@ -270,6 +285,7 @@ Examples:
Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once:
Examples:
- **knob0**: knob 0 enabled, others disabled
- **knob1**: knob 1 enabled, others disabled
- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled
@@ -282,12 +298,12 @@ Examples:
```cpp
void SomeFunction() {
if (Settings::getDebugKnobAt(0)) {
if (Settings::GetDebugKnobAt(0)) {
LOG_DEBUG(Common, "Debug feature 0 is enabled");
// Additional debug code here
}
if (Settings::getDebugKnobAt(1)) {
if (Settings::GetDebugKnobAt(1)) {
LOG_DEBUG(Common, "Debug feature 1 is enabled");
// Different debug behavior
}
@@ -299,7 +315,7 @@ void SomeFunction() {
```cpp
bool UseOptimizedPath() {
// Skip optimization if debug bit 2 is set for testing
return !Settings::getDebugKnobAt(2);
return !Settings::GetDebugKnobAt(2);
}
```
@@ -308,13 +324,13 @@ bool UseOptimizedPath() {
```cpp
void ExperimentalFeature() {
static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3;
if (!Settings::getDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
if (!Settings::GetDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
// Fallback to stable implementation
StableImplementation();
return;
}
// Experimental implementation
ExperimentalImplementation();
}
@@ -220,7 +220,7 @@ object NativeLibrary {
external fun refreshThreadPolicies()
external fun getDebugKnobAt(index: Int): Boolean
external fun GetDebugKnobAt(index: Int): Boolean
/**
* Set the current speed limit to the configured turbo speed.
@@ -35,8 +35,8 @@ object Settings {
fun getPlayerString(player: Int): String =
YuzuApplication.appContext.getString(R.string.preferences_player, player)
fun getDebugKnobAt(index: Int): Boolean {
return org.yuzu.yuzu_emu.NativeLibrary.getDebugKnobAt(index)
fun GetDebugKnobAt(index: Int): Boolean {
return org.yuzu.yuzu_emu.NativeLibrary.GetDebugKnobAt(index)
}
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
@@ -11,8 +11,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
enum class ShortSetting(override val key: String) : AbstractShortSetting {
RENDERER_SPEED_LIMIT("speed_limit"),
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"),
DEBUG_KNOBS("debug_knobs")
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit")
;
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
@@ -29,4 +28,4 @@ enum class ShortSetting(override val key: String) : AbstractShortSetting {
override fun getValueAsString(needsGlobal: Boolean): String = getShort(needsGlobal).toString()
override fun reset() = NativeConfig.setShort(key, defaultValue)
}
}
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
package org.yuzu.yuzu_emu.features.settings.model
import org.yuzu.yuzu_emu.utils.NativeConfig
enum class UShortSetting(override val key: String) : AbstractIntSetting {
DEBUG_KNOBS("debug_knobs")
;
override fun getInt(needsGlobal: Boolean): Int =
NativeConfig.getUnsignedShort(key, needsGlobal)
override fun setInt(value: Int) {
if (NativeConfig.isPerGameConfigLoaded()) {
global = false
}
NativeConfig.setUnsignedShort(key, value)
}
override val defaultValue: Int by lazy { NativeConfig.getDefaultToString(key).toInt() }
override fun getValueAsString(needsGlobal: Boolean): String = getInt(needsGlobal).toString()
override fun reset() = NativeConfig.setUnsignedShort(key, defaultValue)
}
@@ -20,6 +20,7 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
import org.yuzu.yuzu_emu.features.settings.model.LongSetting
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
import org.yuzu.yuzu_emu.network.NetDataValidators
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
import org.yuzu.yuzu_emu.utils.NativeConfig
@@ -1034,7 +1035,7 @@ abstract class SettingsItem(
)
put(
SpinBoxSetting(
ShortSetting.DEBUG_KNOBS,
UShortSetting.DEBUG_KNOBS,
titleId = R.string.debug_knobs,
descriptionId = R.string.debug_knobs_description,
valueHint = R.string.debug_knobs_hint,
@@ -25,6 +25,7 @@ import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.features.settings.model.Settings.MenuTag
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
import org.yuzu.yuzu_emu.features.settings.model.StringSetting
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
import org.yuzu.yuzu_emu.features.settings.model.view.*
import org.yuzu.yuzu_emu.utils.InputHandler
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
@@ -1326,7 +1327,7 @@ class SettingsFragmentPresenter(
add(HeaderSetting(R.string.general))
add(ShortSetting.DEBUG_KNOBS.key)
add(UShortSetting.DEBUG_KNOBS.key)
add(StringSetting.PROGRAM_ARGS.key)
if (!NativeConfig.isPerGameConfigLoaded()) {
@@ -80,6 +80,12 @@ object NativeConfig {
@Synchronized
external fun setShort(key: String, value: Short)
@Synchronized
external fun getUnsignedShort(key: String, needsGlobal: Boolean): Int
@Synchronized
external fun setUnsignedShort(key: String, value: Int)
@Synchronized
external fun getInt(key: String, needsGlobal: Boolean): Int
+2 -2
View File
@@ -1300,8 +1300,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jo
Common::RefreshThreadPolicies();
}
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
return static_cast<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_GetDebugKnobAt(JNIEnv* env, jobject jobj, jint index) {
return static_cast<jboolean>(Settings::GetDebugKnobAt(static_cast<u8>(index)));
}
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
@@ -130,6 +130,25 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setShort(JNIEnv* env, jobject ob
setting->SetValue(value);
}
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getUnsignedShort(JNIEnv* env, jobject obj,
jstring jkey,
jboolean needGlobal) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return -1;
}
return static_cast<jint>(setting->GetValue(static_cast<bool>(needGlobal)));
}
void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setUnsignedShort(JNIEnv* env, jobject obj,
jstring jkey, jint value) {
auto setting = getSetting<u16>(env, jkey);
if (setting == nullptr) {
return;
}
setting->SetValue(static_cast<u16>(value));
}
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
jboolean needGlobal) {
auto setting = getSetting<int>(env, jkey);
+1 -1
View File
@@ -146,7 +146,7 @@ void LogSettings() {
#undef LOG_PATH
}
bool getDebugKnobAt(u8 i) {
bool GetDebugKnobAt(u8 i) {
return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0;
}
+2 -2
View File
@@ -904,7 +904,7 @@ struct Values {
0,
65535,
"debug_knobs",
Category::Debugging,
Category::System,
Specialization::Countable,
true,
true};
@@ -947,7 +947,7 @@ constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4;
[[nodiscard]] size_t FrameGenMaxGenerations();
bool getDebugKnobAt(u8 i);
bool GetDebugKnobAt(u8 i);
void UpdateGPUAccuracy();
bool IsGPULevelHigh();
+106 -61
View File
@@ -25,33 +25,42 @@ NvMap::Handle::Handle(u64 size_, Id id_)
flags.raw = 0;
}
NvResult NvMap::Handle::Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress, NvCore::SessionId pSessionId) {
NvResult NvMap::Handle::Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress,
NvCore::SessionId pSessionId) {
std::scoped_lock lock(mutex);
// Handles cannot be allocated twice
if (allocated) {
return NvResult::AccessDenied;
}
flags = pFlags;
kind = pKind;
align = pAlign < YUZU_PAGESIZE ? YUZU_PAGESIZE : pAlign;
session_id = pSessionId;
// This flag is only applicable for handles with an address passed
if (pAddress) {
flags.keep_uncached_after_free.Assign(0);
} else {
LOG_CRITICAL(Service_NVDRV, "Mapping nvmap handles without a CPU side address is unimplemented!");
LOG_CRITICAL(Service_NVDRV,
"Mapping nvmap handles without a CPU side address is unimplemented!");
}
size = Common::AlignUp(size, YUZU_PAGESIZE);
aligned_size = Common::AlignUp(size, align);
address = pAddress;
allocated = true;
return NvResult::Success;
}
NvResult NvMap::Handle::Duplicate(bool internal_session) {
std::scoped_lock lock(mutex);
// Unallocated handles cannot be duplicated as duplication requires memory accounting (in HOS)
if (!allocated) [[unlikely]] {
return NvResult::BadValue;
}
// If we internally use FromId the duplication tracking of handles won't work accurately due to
// us not implementing per-process handle refs.
if (internal_session) {
@@ -59,14 +68,16 @@ NvResult NvMap::Handle::Duplicate(bool internal_session) {
} else {
dupes++;
}
return NvResult::Success;
}
NvMap::NvMap(Container& core_, Tegra::Host1x::Host1x& host1x_) : host1x{host1x_}, core{core_} {}
void NvMap::AddHandle(Handle&& handle_description) {
std::scoped_lock l(handles_lock);
handles.insert_or_assign(handle_description.id, std::move(handle_description));
void NvMap::AddHandle(std::shared_ptr<Handle> handle_description) {
std::scoped_lock lock(handles_lock);
handles.emplace(handle_description->id, std::move(handle_description));
}
void NvMap::UnmapHandle(Handle& handle_description) {
@@ -105,48 +116,57 @@ void NvMap::UnmapHandle(Handle& handle_description) {
bool NvMap::TryRemoveHandle(const Handle& handle_description) {
// No dupes left, we can remove from handle map
if (handle_description.dupes == 0 && handle_description.internal_dupes == 0) {
std::scoped_lock l(handles_lock);
auto it = handles.find(handle_description.id);
std::scoped_lock lock(handles_lock);
auto it{handles.find(handle_description.id)};
if (it != handles.end()) {
handles.erase(it);
}
return true;
} else {
return false;
}
}
NvResult NvMap::CreateHandle(u64 size, Handle::Id& out_handle) {
if (!Common::AlignUp(size, YUZU_PAGESIZE)) {
NvResult NvMap::CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out) {
if (!size) [[unlikely]] {
return NvResult::BadValue;
}
u32 id = next_handle_id.fetch_add(HandleIdIncrement, std::memory_order_relaxed);
AddHandle(Handle(size, id));
out_handle = id;
u32 id{next_handle_id.fetch_add(HandleIdIncrement, std::memory_order_relaxed)};
auto handle_description{std::make_shared<Handle>(size, id)};
AddHandle(handle_description);
result_out = handle_description;
return NvResult::Success;
}
std::optional<std::reference_wrapper<NvMap::Handle>> NvMap::GetHandle(Handle::Id handle) {
if (auto const it = handles.find(handle); it != handles.end())
return {it->second};
return std::nullopt;
std::shared_ptr<NvMap::Handle> NvMap::GetHandle(Handle::Id handle) {
std::scoped_lock lock(handles_lock);
try {
return handles.at(handle);
} catch (std::out_of_range&) {
return nullptr;
}
}
DAddr NvMap::GetHandleAddress(Handle::Id handle) {
if (auto const it = handles.find(handle); it != handles.end())
return it->second.d_address;
return 0;
std::scoped_lock lock(handles_lock);
try {
return handles.at(handle)->d_address;
} catch (std::out_of_range&) {
return 0;
}
}
DAddr NvMap::PinHandle(NvMap::Handle::Id handle, bool low_area_pin) {
std::scoped_lock lock(handles_lock);
auto o = GetHandle(handle);
if (!o) [[unlikely]] {
auto handle_description{GetHandle(handle)};
if (!handle_description) [[unlikely]] {
return 0;
}
auto handle_description = &o->get();
std::scoped_lock lock(handle_description->mutex);
const auto map_low_area = [&] {
if (handle_description->pin_virt_address == 0) {
u32 address = host1x.Allocator().Allocate(u32(handle_description->aligned_size));
@@ -160,15 +180,17 @@ DAddr NvMap::PinHandle(NvMap::Handle::Id handle, bool low_area_pin) {
{
// Lock now to prevent our queue entry from being removed for allocation in-between the
// following check and erase
std::scoped_lock ql(unmap_queue_lock);
std::scoped_lock queueLock(unmap_queue_lock);
if (handle_description->unmap_queue_entry) {
unmap_queue.erase(*handle_description->unmap_queue_entry);
handle_description->unmap_queue_entry.reset();
if (low_area_pin) {
map_low_area();
handle_description->pins++;
return DAddr(handle_description->pin_virt_address);
return static_cast<DAddr>(handle_description->pin_virt_address);
}
handle_description->pins++;
return handle_description->d_address;
}
@@ -189,11 +211,12 @@ DAddr NvMap::PinHandle(NvMap::Handle::Id handle, bool low_area_pin) {
while ((address = smmu.Allocate(aligned_up)) == 0) {
// Free handles until the allocation succeeds
std::scoped_lock queueLock(unmap_queue_lock);
if (auto free_handle = handles.find(unmap_queue.front()); free_handle != handles.end()) {
if (auto freeHandleDesc{unmap_queue.front()}) {
// Handles in the unmap queue are guaranteed not to be pinned so don't bother
// checking if they are before unmapping
std::scoped_lock freeLock(freeHandleDesc->mutex);
if (handle_description->d_address)
UnmapHandle(free_handle->second);
UnmapHandle(*freeHandleDesc);
} else {
LOG_CRITICAL(Service_NVDRV, "Ran out of SMMU address space!");
}
@@ -211,44 +234,51 @@ DAddr NvMap::PinHandle(NvMap::Handle::Id handle, bool low_area_pin) {
handle_description->pins++;
if (low_area_pin) {
return DAddr(handle_description->pin_virt_address);
return static_cast<DAddr>(handle_description->pin_virt_address);
}
return handle_description->d_address;
}
void NvMap::UnpinHandle(Handle::Id handle) {
std::scoped_lock lock(handles_lock);
if (auto o = GetHandle(handle); o) {
auto handle_description = &o->get();
if (--handle_description->pins < 0) {
LOG_WARNING(Service_NVDRV, "Pin count imbalance detected!");
} else if (!handle_description->pins) {
std::scoped_lock ql(unmap_queue_lock);
// Add to the unmap queue allowing this handle's memory to be freed if needed
unmap_queue.push_back(handle);
handle_description->unmap_queue_entry = std::prev(unmap_queue.end());
}
auto handle_description{GetHandle(handle)};
if (!handle_description) {
return;
}
std::scoped_lock lock(handle_description->mutex);
if (--handle_description->pins < 0) {
LOG_WARNING(Service_NVDRV, "Pin count imbalance detected!");
} else if (!handle_description->pins) {
std::scoped_lock queueLock(unmap_queue_lock);
// Add to the unmap queue allowing this handle's memory to be freed if needed
unmap_queue.push_back(handle_description);
handle_description->unmap_queue_entry = std::prev(unmap_queue.end());
}
}
void NvMap::DuplicateHandle(Handle::Id handle, bool internal_session) {
std::scoped_lock lock(handles_lock);
auto o = GetHandle(handle);
if (!o) {
auto handle_description{GetHandle(handle)};
if (!handle_description) {
LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
return;
}
auto result = o->get().Duplicate(internal_session);
auto result = handle_description->Duplicate(internal_session);
if (result != NvResult::Success) {
LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
}
}
std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool internal_session) {
// We use a weak ptr here so we can tell when the handle has been freed and report that back to guest
std::scoped_lock lock(handles_lock);
if (auto o = GetHandle(handle); o) {
auto handle_description = &o->get();
std::weak_ptr<Handle> hWeak{GetHandle(handle)};
FreeInfo freeInfo;
// We use a weak ptr here so we can tell when the handle has been freed and report that back to
// guest
if (auto handle_description = hWeak.lock()) {
std::scoped_lock lock(handle_description->mutex);
if (internal_session) {
if (--handle_description->internal_dupes < 0)
LOG_WARNING(Service_NVDRV, "Internal duplicate count imbalance detected!");
@@ -258,25 +288,25 @@ std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool interna
} else if (handle_description->dupes == 0) {
// Force unmap the handle
if (handle_description->d_address) {
std::scoped_lock ql(unmap_queue_lock);
std::scoped_lock queueLock(unmap_queue_lock);
UnmapHandle(*handle_description);
}
handle_description->pins = 0;
}
}
// Try to remove the shared ptr to the handle from the map, if nothing else is using the
// handle then it will now be freed when `handle_description` goes out of scope
if (TryRemoveHandle(*handle_description)) {
LOG_DEBUG(Service_NVDRV, "Removed nvmap handle: {}", handle);
} else {
LOG_DEBUG(Service_NVDRV, "Tried to free nvmap handle: {} but didn't as it still has duplicates", handle);
LOG_DEBUG(Service_NVDRV,
"Tried to free nvmap handle: {} but didn't as it still has duplicates",
handle);
}
// // If the handle hasn't been freed from memory, mark that
// if (!hWeak.expired()) {
// LOG_DEBUG(Service_NVDRV, "nvmap handle: {} wasn't freed as it is still in use", handle);
// freeInfo.can_unlock = false;
// }
return FreeInfo{
freeInfo = {
.address = handle_description->address,
.size = handle_description->size,
.was_uncached = handle_description->flags.map_uncached.Value() != 0,
@@ -285,15 +315,30 @@ std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool interna
} else {
return std::nullopt;
}
// If the handle hasn't been freed from memory, mark that
if (!hWeak.expired()) {
LOG_DEBUG(Service_NVDRV, "nvmap handle: {} wasn't freed as it is still in use", handle);
freeInfo.can_unlock = false;
}
return freeInfo;
}
void NvMap::UnmapAllHandles(NvCore::SessionId session_id) {
std::scoped_lock lk{handles_lock};
for (auto it = handles.begin(); it != handles.end(); ++it) {
if (it->second.session_id.id != session_id.id || it->second.dupes <= 0) {
continue;
auto handles_copy = [&] {
std::scoped_lock lk{handles_lock};
return handles;
}();
for (auto& [id, handle] : handles_copy) {
{
std::scoped_lock lk{handle->mutex};
if (handle->session_id.id != session_id.id || handle->dupes <= 0) {
continue;
}
}
FreeHandle(it->first, false);
FreeHandle(id, false);
}
}
+44 -30
View File
@@ -12,11 +12,6 @@
#include <memory>
#include <mutex>
#include <optional>
#if BOOST_VERSION >= 109000
#include <boost/unordered/unordered_node_map.hpp>
#else
#include <unordered_map>
#endif
#include <ankerl/unordered_dense.h>
#include <assert.h>
@@ -36,36 +31,54 @@ class Host1x;
namespace Service::Nvidia::NvCore {
class Container;
/// @brief The nvmap core class holds the global state for nvmap and provides methods to manage handles
/**
* @brief The nvmap core class holds the global state for nvmap and provides methods to manage
* handles
*/
class NvMap {
public:
/// @brief A handle to a contiguous block of memory in an application's address space
/**
* @brief A handle to a contiguous block of memory in an application's address space
*/
struct Handle {
using Id = u32;
std::optional<typename std::list<Handle::Id>::iterator> unmap_queue_entry{};
std::mutex mutex;
u64 align{}; //!< The alignment to use when pinning the handle onto the SMMU
u64 size; //!< Page-aligned size of the memory the handle refers to
u64 aligned_size; //!< `align`-aligned size of the memory the handle refers to
u64 orig_size; //!< Original unaligned size of the memory this handle refers to
DAddr d_address{}; //!< The memory location in the device's AS that this handle corresponds to, this can also be in the nvdrv tmem
VAddr address{}; //!< The memory location in the guest's AS that this handle corresponds to, this can also be in the nvdrv tmem
s64 pins{};
s32 dupes{1}; //!< How many guest references there are to this handle
s32 internal_dupes{0}; //!< How many emulator-internal references there are to this handle
using Id = u32;
Id id; //!< A globally unique identifier for this handle
s64 pins{};
u32 pin_virt_address{};
std::optional<typename std::list<std::shared_ptr<Handle>>::iterator> unmap_queue_entry{};
union Flags {
u32 raw;
BitField<0, 1, u32> map_uncached; //!< If the handle should be mapped as uncached
BitField<2, 1, u32> keep_uncached_after_free; //!< Only applicable when the handle was allocated with a fixed address
BitField<4, 1, u32> _unk0_; //!< Passed to IOVMM for pins
BitField<2, 1, u32> keep_uncached_after_free; //!< Only applicable when the handle was
//!< allocated with a fixed address
BitField<4, 1, u32> _unk0_; //!< Passed to IOVMM for pins
} flags{};
static_assert(sizeof(Flags) == sizeof(u32));
NvCore::SessionId session_id{};
VAddr address{}; //!< The memory location in the guest's AS that this handle corresponds to,
//!< this can also be in the nvdrv tmem
bool is_shared_mem_mapped{}; //!< If this nvmap has been mapped with the MapSharedMem IPC
//!< call
u8 kind{}; //!< Used for memory compression
bool allocated : 1 = false; //!< If the handle has been allocated with `Alloc`
bool in_heap : 1 = false;
bool is_shared_mem_mapped : 1 = false; //!< If this nvmap has been mapped with the MapSharedMem IPC < call
bool allocated{}; //!< If the handle has been allocated with `Alloc`
bool in_heap{};
NvCore::SessionId session_id{};
DAddr d_address{}; //!< The memory location in the device's AS that this handle corresponds
//!< to, this can also be in the nvdrv tmem
Handle(u64 size, Id id);
@@ -110,9 +123,9 @@ public:
/**
* @brief Creates an unallocated handle of the given size
*/
[[nodiscard]] NvResult CreateHandle(u64 size, Handle::Id& out_handle);
[[nodiscard]] NvResult CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out);
std::optional<std::reference_wrapper<Handle>> GetHandle(Handle::Id handle);
std::shared_ptr<Handle> GetHandle(Handle::Id handle);
DAddr GetHandleAddress(Handle::Id handle);
@@ -144,21 +157,20 @@ public:
void UnmapAllHandles(NvCore::SessionId session_id);
std::list<Handle::Id> unmap_queue{};
/// Main owning map of handles
#if BOOST_VERSION >= 109000
boost::unordered_node_map<Handle::Id, Handle> handles{};
#else
std::unordered_map<Handle::Id, Handle> handles{};
#endif
private:
std::list<std::shared_ptr<Handle>> unmap_queue{};
std::mutex unmap_queue_lock{}; //!< Protects access to `unmap_queue`
ankerl::unordered_dense::map<Handle::Id, std::shared_ptr<Handle>>
handles{}; //!< Main owning map of handles
std::mutex handles_lock; //!< Protects access to `handles`
static constexpr u32 HandleIdIncrement{4}; //!< Each new handle ID is an increment of 4 from the previous
static constexpr u32 HandleIdIncrement{
4}; //!< Each new handle ID is an increment of 4 from the previous
std::atomic<u32> next_handle_id{HandleIdIncrement};
Tegra::Host1x::Host1x& host1x;
Container& core;
void AddHandle(Handle&& handle);
void AddHandle(std::shared_ptr<Handle> handle);
/**
* @brief Unmaps and frees the SMMU memory region a handle is mapped to
@@ -172,5 +184,7 @@ public:
* @return If the handle was removed from the map
*/
bool TryRemoveHandle(const Handle& handle_description);
Container& core;
};
} // namespace Service::Nvidia::NvCore
@@ -328,11 +328,10 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
}
}
auto o = nvmap.GetHandle(params.handle);
if (!o) {
auto handle{nvmap.GetHandle(params.handle)};
if (!handle) {
return NvResult::BadValue;
}
auto handle = &o->get();
DAddr device_address = DAddr(nvmap.PinHandle(params.handle, false) + params.buffer_offset);
u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
@@ -103,13 +103,17 @@ NvResult nvhost_nvdec_common::Submit(IoctlSubmit& params, std::span<u8> data, De
for (std::size_t i = 0; i < syncpt_increments.size(); i++) {
const SyncptIncr& syncpt_incr = syncpt_increments[i];
fence_thresholds[i] = syncpoint_manager.IncrementSyncpointMaxExt(syncpt_incr.id, syncpt_incr.increments);
fence_thresholds[i] =
syncpoint_manager.IncrementSyncpointMaxExt(syncpt_incr.id, syncpt_incr.increments);
}
for (const auto& cmd_buffer : command_buffers) {
const auto object = nvmap.GetHandle(cmd_buffer.memory_id);
ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;);
Core::Memory::CpuGuestMemory<Tegra::ChCommandHeader, Core::Memory::GuestMemoryFlags::SafeRead> cmdlist(session->process->GetMemory(), object->get().address + cmd_buffer.offset, cmd_buffer.word_count);
Core::Memory::CpuGuestMemory<Tegra::ChCommandHeader,
Core::Memory::GuestMemoryFlags::SafeRead>
cmdlist(session->process->GetMemory(), object->address + cmd_buffer.offset,
cmd_buffer.word_count);
host1x.PushEntries(fd, std::move(cmdlist));
}
+25 -23
View File
@@ -83,14 +83,17 @@ void nvmap::OnClose(DeviceFD fd) {
NvResult nvmap::IocCreate(IocCreateParams& params) {
LOG_DEBUG(Service_NVDRV, "called, size={:#08x}", params.size);
NvCore::NvMap::Handle handle_description(0, 0);
// Orig size is the unaligned size, set the handle to that
auto result = file.CreateHandle(params.size, params.handle);
std::shared_ptr<NvCore::NvMap::Handle> handle_description{};
auto result =
file.CreateHandle(Common::AlignUp(params.size, YUZU_PAGESIZE), handle_description);
if (result != NvResult::Success) {
LOG_CRITICAL(Service_NVDRV, "Failed to create Object");
return result;
}
LOG_DEBUG(Service_NVDRV, "handle: {}, size: {:#x}", params.handle, params.size);
handle_description->orig_size = params.size; // Orig size is the unaligned size
params.handle = handle_description->id;
LOG_DEBUG(Service_NVDRV, "handle: {}, size: {:#x}", handle_description->id, params.size);
return NvResult::Success;
}
@@ -112,27 +115,30 @@ NvResult nvmap::IocAlloc(IocAllocParams& params, DeviceFD fd) {
params.align = YUZU_PAGESIZE;
}
std::scoped_lock lock(file.handles_lock);
auto o = file.GetHandle(params.handle);
if (!o) {
auto handle_description{file.GetHandle(params.handle)};
if (!handle_description) {
LOG_CRITICAL(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
return NvResult::BadValue;
}
auto handle_description = &o->get();
if (handle_description->allocated) {
LOG_CRITICAL(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
return NvResult::InsufficientMemory;
}
const auto result = handle_description->Alloc(params.flags, params.align, params.kind, params.address, sessions[fd]);
const auto result = handle_description->Alloc(params.flags, params.align, params.kind,
params.address, sessions[fd]);
if (result != NvResult::Success) {
LOG_CRITICAL(Service_NVDRV, "Object failed to allocate, handle={:08X}", params.handle);
return result;
}
bool is_out_io{};
auto process = container.GetSession(sessions[fd])->process;
ASSERT(process->GetPageTable().LockForMapDeviceAddressSpace(&is_out_io, handle_description->address, handle_description->size, Kernel::KMemoryPermission::None, true, false).IsSuccess());
ASSERT(process->GetPageTable()
.LockForMapDeviceAddressSpace(&is_out_io, handle_description->address,
handle_description->size,
Kernel::KMemoryPermission::None, true, false)
.IsSuccess());
return result;
}
@@ -145,13 +151,13 @@ NvResult nvmap::IocGetId(IocGetIdParams& params) {
return NvResult::BadValue;
}
std::scoped_lock lock(file.handles_lock);
auto o = file.GetHandle(params.handle);
if (!o) {
auto handle_description{file.GetHandle(params.handle)};
if (!handle_description) {
LOG_CRITICAL(Service_NVDRV, "Error!");
return NvResult::AccessDenied; // This will always return EPERM irrespective of if the handle exists or not
return NvResult::AccessDenied; // This will always return EPERM irrespective of if the
// handle exists or not
}
auto handle_description = &o->get();
params.id = handle_description->id;
return NvResult::Success;
}
@@ -168,14 +174,12 @@ NvResult nvmap::IocFromId(IocFromIdParams& params) {
return NvResult::BadValue;
}
std::scoped_lock lock(file.handles_lock);
auto o = file.GetHandle(params.id);
if (!o) {
auto handle_description{file.GetHandle(params.id)};
if (!handle_description) {
LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
return NvResult::BadValue;
}
auto handle_description = &o->get();
auto result = handle_description->Duplicate(false);
if (result != NvResult::Success) {
LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
@@ -195,14 +199,12 @@ NvResult nvmap::IocParam(IocParamParams& params) {
return NvResult::BadValue;
}
std::scoped_lock lock(file.handles_lock);
auto o = file.GetHandle(params.handle);
if (!o) {
auto handle_description{file.GetHandle(params.handle)};
if (!handle_description) {
LOG_CRITICAL(Service_NVDRV, "Not registered handle!");
return NvResult::BadValue;
}
auto handle_description = &o->get();
switch (params.param) {
case HandleParameterType::Size:
params.result = static_cast<u32_le>(handle_description->orig_size);