mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-30 02:16:06 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| edc38b8230 | |||
| ffecb4af7b |
+27
-11
@@ -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:
|
Firstly add your desired toggle:
|
||||||
|
|
||||||
Example: `src/common/setting.h`
|
Example: `src/common/setting.h`
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
SwitchableSetting<bool> your_setting_name{linkage, false, "your_setting_name", Category::RendererExtensions};
|
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.
|
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`
|
Example: `src/qt_common/config/shared_translation.cpp`
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
INSERT(Settings,
|
INSERT(Settings,
|
||||||
your_setting_name,
|
your_setting_name,
|
||||||
@@ -91,6 +93,7 @@ INSERT(Settings,
|
|||||||
Add where it should be in the 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`
|
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt`
|
||||||
|
|
||||||
```kts
|
```kts
|
||||||
RENDERER_YOUR_SETTING_NAME("your_setting_name"),
|
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
|
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`
|
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt`
|
||||||
|
|
||||||
```kts
|
```kts
|
||||||
put(
|
put(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
@@ -123,6 +127,7 @@ put(
|
|||||||
Add your setting within the right category.
|
Add your setting within the right category.
|
||||||
|
|
||||||
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt`
|
Example: `src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt`
|
||||||
|
|
||||||
```kts
|
```kts
|
||||||
add(BooleanSetting.RENDERER_YOUR_SETTING_NAME.key)
|
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.
|
Add your setting and description in the appropriate place.
|
||||||
|
|
||||||
Example: `src/android/app/src/main/res/values/strings.xml`
|
Example: `src/android/app/src/main/res/values/strings.xml`
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<string name="your_setting_name">Your Setting Display Name</string>
|
<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>
|
<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!
|
And use it to your heart's desire!
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
const bool your_value = Settings::values.your_setting_name.GetValue();
|
const bool your_value = Settings::values.your_setting_name.GetValue();
|
||||||
|
|
||||||
@@ -196,25 +203,31 @@ Common advantages recap:
|
|||||||
|
|
||||||
#### Accessing Debug Knobs (dev side)
|
#### 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
|
||||||
//cpp side
|
//cpp side
|
||||||
#include "common/settings.h"
|
#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
|
// Check if bit 0 is set
|
||||||
bool feature_enabled = Settings::getDebugKnobAt(0);
|
bool feature_enabled = Settings::GetDebugKnobAt(0);
|
||||||
|
|
||||||
// Check if bit 15 is set
|
// Check if bit 15 is set
|
||||||
bool another_feature = Settings::getDebugKnobAt(15);
|
bool another_feature = Settings::GetDebugKnobAt(15);
|
||||||
```
|
```
|
||||||
|
|
||||||
```kts
|
```kts
|
||||||
//kotlin side
|
//kotlin side
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
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
|
// 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.
|
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).
|
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:
|
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)
|
* 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.
|
* 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:
|
ALWAYS use the word in PLURAL (knobs), without mentioning which one, to refer to the setting, aka multiple knobs at once:
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- **knobs=0**: no knobs enabled
|
- **knobs=0**: no knobs enabled
|
||||||
- **knobs=1**: knob0 enabled, others disabled
|
- **knobs=1**: knob0 enabled, others disabled
|
||||||
- **knobs=2**: knob1 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:
|
Use the word in SINGULAR (knob), or in plural but referring which ones, when meaning multiple knobs at once:
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- **knob0**: knob 0 enabled, others disabled
|
- **knob0**: knob 0 enabled, others disabled
|
||||||
- **knob1**: knob 1 enabled, others disabled
|
- **knob1**: knob 1 enabled, others disabled
|
||||||
- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled
|
- **knobs 0 and 1**: knobs 0 and 1 enabled, others disabled
|
||||||
@@ -282,12 +298,12 @@ Examples:
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
void SomeFunction() {
|
void SomeFunction() {
|
||||||
if (Settings::getDebugKnobAt(0)) {
|
if (Settings::GetDebugKnobAt(0)) {
|
||||||
LOG_DEBUG(Common, "Debug feature 0 is enabled");
|
LOG_DEBUG(Common, "Debug feature 0 is enabled");
|
||||||
// Additional debug code here
|
// Additional debug code here
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings::getDebugKnobAt(1)) {
|
if (Settings::GetDebugKnobAt(1)) {
|
||||||
LOG_DEBUG(Common, "Debug feature 1 is enabled");
|
LOG_DEBUG(Common, "Debug feature 1 is enabled");
|
||||||
// Different debug behavior
|
// Different debug behavior
|
||||||
}
|
}
|
||||||
@@ -299,7 +315,7 @@ void SomeFunction() {
|
|||||||
```cpp
|
```cpp
|
||||||
bool UseOptimizedPath() {
|
bool UseOptimizedPath() {
|
||||||
// Skip optimization if debug bit 2 is set for testing
|
// 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
|
```cpp
|
||||||
void ExperimentalFeature() {
|
void ExperimentalFeature() {
|
||||||
static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3;
|
static constexpr u8 EXPERIMENTAL_FEATURE_BIT = 3;
|
||||||
|
|
||||||
if (!Settings::getDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
|
if (!Settings::GetDebugKnobAt(EXPERIMENTAL_FEATURE_BIT)) {
|
||||||
// Fallback to stable implementation
|
// Fallback to stable implementation
|
||||||
StableImplementation();
|
StableImplementation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Experimental implementation
|
// Experimental implementation
|
||||||
ExperimentalImplementation();
|
ExperimentalImplementation();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ object NativeLibrary {
|
|||||||
|
|
||||||
external fun refreshThreadPolicies()
|
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.
|
* Set the current speed limit to the configured turbo speed.
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ object Settings {
|
|||||||
fun getPlayerString(player: Int): String =
|
fun getPlayerString(player: Int): String =
|
||||||
YuzuApplication.appContext.getString(R.string.preferences_player, player)
|
YuzuApplication.appContext.getString(R.string.preferences_player, player)
|
||||||
|
|
||||||
fun getDebugKnobAt(index: Int): Boolean {
|
fun GetDebugKnobAt(index: Int): Boolean {
|
||||||
return org.yuzu.yuzu_emu.NativeLibrary.getDebugKnobAt(index)
|
return org.yuzu.yuzu_emu.NativeLibrary.GetDebugKnobAt(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"
|
||||||
|
|||||||
+2
-3
@@ -11,8 +11,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
|||||||
enum class ShortSetting(override val key: String) : AbstractShortSetting {
|
enum class ShortSetting(override val key: String) : AbstractShortSetting {
|
||||||
RENDERER_SPEED_LIMIT("speed_limit"),
|
RENDERER_SPEED_LIMIT("speed_limit"),
|
||||||
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
|
RENDERER_TURBO_SPEED_LIMIT("turbo_speed_limit"),
|
||||||
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit"),
|
RENDERER_SLOW_SPEED_LIMIT("slow_speed_limit")
|
||||||
DEBUG_KNOBS("debug_knobs")
|
|
||||||
;
|
;
|
||||||
|
|
||||||
override fun getShort(needsGlobal: Boolean): Short = NativeConfig.getShort(key, needsGlobal)
|
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 getValueAsString(needsGlobal: Boolean): String = getShort(needsGlobal).toString()
|
||||||
|
|
||||||
override fun reset() = NativeConfig.setShort(key, defaultValue)
|
override fun reset() = NativeConfig.setShort(key, defaultValue)
|
||||||
}
|
}
|
||||||
|
|||||||
+27
@@ -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)
|
||||||
|
}
|
||||||
+2
-1
@@ -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.LongSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
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.StringSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
|
||||||
import org.yuzu.yuzu_emu.network.NetDataValidators
|
import org.yuzu.yuzu_emu.network.NetDataValidators
|
||||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
@@ -1034,7 +1035,7 @@ abstract class SettingsItem(
|
|||||||
)
|
)
|
||||||
put(
|
put(
|
||||||
SpinBoxSetting(
|
SpinBoxSetting(
|
||||||
ShortSetting.DEBUG_KNOBS,
|
UShortSetting.DEBUG_KNOBS,
|
||||||
titleId = R.string.debug_knobs,
|
titleId = R.string.debug_knobs,
|
||||||
descriptionId = R.string.debug_knobs_description,
|
descriptionId = R.string.debug_knobs_description,
|
||||||
valueHint = R.string.debug_knobs_hint,
|
valueHint = R.string.debug_knobs_hint,
|
||||||
|
|||||||
+2
-1
@@ -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.Settings.MenuTag
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
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.StringSetting
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.UShortSetting
|
||||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
||||||
@@ -1326,7 +1327,7 @@ class SettingsFragmentPresenter(
|
|||||||
|
|
||||||
add(HeaderSetting(R.string.general))
|
add(HeaderSetting(R.string.general))
|
||||||
|
|
||||||
add(ShortSetting.DEBUG_KNOBS.key)
|
add(UShortSetting.DEBUG_KNOBS.key)
|
||||||
add(StringSetting.PROGRAM_ARGS.key)
|
add(StringSetting.PROGRAM_ARGS.key)
|
||||||
|
|
||||||
if (!NativeConfig.isPerGameConfigLoaded()) {
|
if (!NativeConfig.isPerGameConfigLoaded()) {
|
||||||
|
|||||||
@@ -80,6 +80,12 @@ object NativeConfig {
|
|||||||
@Synchronized
|
@Synchronized
|
||||||
external fun setShort(key: String, value: Short)
|
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
|
@Synchronized
|
||||||
external fun getInt(key: String, needsGlobal: Boolean): Int
|
external fun getInt(key: String, needsGlobal: Boolean): Int
|
||||||
|
|
||||||
|
|||||||
@@ -1300,8 +1300,8 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jo
|
|||||||
Common::RefreshThreadPolicies();
|
Common::RefreshThreadPolicies();
|
||||||
}
|
}
|
||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint 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)));
|
return static_cast<jboolean>(Settings::GetDebugKnobAt(static_cast<u8>(index)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_setTurboSpeedLimit(JNIEnv *env, jobject jobj, jboolean enabled) {
|
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);
|
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,
|
jint Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getInt(JNIEnv* env, jobject obj, jstring jkey,
|
||||||
jboolean needGlobal) {
|
jboolean needGlobal) {
|
||||||
auto setting = getSetting<int>(env, jkey);
|
auto setting = getSetting<int>(env, jkey);
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ void LogSettings() {
|
|||||||
#undef LOG_PATH
|
#undef LOG_PATH
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getDebugKnobAt(u8 i) {
|
bool GetDebugKnobAt(u8 i) {
|
||||||
return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0;
|
return (values.debug_knobs.GetValue() & (1 << (i & 0xF))) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -904,7 +904,7 @@ struct Values {
|
|||||||
0,
|
0,
|
||||||
65535,
|
65535,
|
||||||
"debug_knobs",
|
"debug_knobs",
|
||||||
Category::Debugging,
|
Category::System,
|
||||||
Specialization::Countable,
|
Specialization::Countable,
|
||||||
true,
|
true,
|
||||||
true};
|
true};
|
||||||
@@ -947,7 +947,7 @@ constexpr u32 MAX_FRAME_GEN_MULTIPLIER = 4;
|
|||||||
|
|
||||||
[[nodiscard]] size_t FrameGenMaxGenerations();
|
[[nodiscard]] size_t FrameGenMaxGenerations();
|
||||||
|
|
||||||
bool getDebugKnobAt(u8 i);
|
bool GetDebugKnobAt(u8 i);
|
||||||
|
|
||||||
void UpdateGPUAccuracy();
|
void UpdateGPUAccuracy();
|
||||||
bool IsGPULevelHigh();
|
bool IsGPULevelHigh();
|
||||||
|
|||||||
@@ -25,33 +25,42 @@ NvMap::Handle::Handle(u64 size_, Id id_)
|
|||||||
flags.raw = 0;
|
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
|
// Handles cannot be allocated twice
|
||||||
if (allocated) {
|
if (allocated) {
|
||||||
return NvResult::AccessDenied;
|
return NvResult::AccessDenied;
|
||||||
}
|
}
|
||||||
|
|
||||||
flags = pFlags;
|
flags = pFlags;
|
||||||
kind = pKind;
|
kind = pKind;
|
||||||
align = pAlign < YUZU_PAGESIZE ? YUZU_PAGESIZE : pAlign;
|
align = pAlign < YUZU_PAGESIZE ? YUZU_PAGESIZE : pAlign;
|
||||||
session_id = pSessionId;
|
session_id = pSessionId;
|
||||||
|
|
||||||
// This flag is only applicable for handles with an address passed
|
// This flag is only applicable for handles with an address passed
|
||||||
if (pAddress) {
|
if (pAddress) {
|
||||||
flags.keep_uncached_after_free.Assign(0);
|
flags.keep_uncached_after_free.Assign(0);
|
||||||
} else {
|
} 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);
|
size = Common::AlignUp(size, YUZU_PAGESIZE);
|
||||||
aligned_size = Common::AlignUp(size, align);
|
aligned_size = Common::AlignUp(size, align);
|
||||||
address = pAddress;
|
address = pAddress;
|
||||||
allocated = true;
|
allocated = true;
|
||||||
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult NvMap::Handle::Duplicate(bool internal_session) {
|
NvResult NvMap::Handle::Duplicate(bool internal_session) {
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
// Unallocated handles cannot be duplicated as duplication requires memory accounting (in HOS)
|
// Unallocated handles cannot be duplicated as duplication requires memory accounting (in HOS)
|
||||||
if (!allocated) [[unlikely]] {
|
if (!allocated) [[unlikely]] {
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we internally use FromId the duplication tracking of handles won't work accurately due to
|
// If we internally use FromId the duplication tracking of handles won't work accurately due to
|
||||||
// us not implementing per-process handle refs.
|
// us not implementing per-process handle refs.
|
||||||
if (internal_session) {
|
if (internal_session) {
|
||||||
@@ -59,14 +68,16 @@ NvResult NvMap::Handle::Duplicate(bool internal_session) {
|
|||||||
} else {
|
} else {
|
||||||
dupes++;
|
dupes++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
NvMap::NvMap(Container& core_, Tegra::Host1x::Host1x& host1x_) : host1x{host1x_}, core{core_} {}
|
NvMap::NvMap(Container& core_, Tegra::Host1x::Host1x& host1x_) : host1x{host1x_}, core{core_} {}
|
||||||
|
|
||||||
void NvMap::AddHandle(Handle&& handle_description) {
|
void NvMap::AddHandle(std::shared_ptr<Handle> handle_description) {
|
||||||
std::scoped_lock l(handles_lock);
|
std::scoped_lock lock(handles_lock);
|
||||||
handles.insert_or_assign(handle_description.id, std::move(handle_description));
|
|
||||||
|
handles.emplace(handle_description->id, std::move(handle_description));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NvMap::UnmapHandle(Handle& 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) {
|
bool NvMap::TryRemoveHandle(const Handle& handle_description) {
|
||||||
// No dupes left, we can remove from handle map
|
// No dupes left, we can remove from handle map
|
||||||
if (handle_description.dupes == 0 && handle_description.internal_dupes == 0) {
|
if (handle_description.dupes == 0 && handle_description.internal_dupes == 0) {
|
||||||
std::scoped_lock l(handles_lock);
|
std::scoped_lock lock(handles_lock);
|
||||||
auto it = handles.find(handle_description.id);
|
|
||||||
|
auto it{handles.find(handle_description.id)};
|
||||||
if (it != handles.end()) {
|
if (it != handles.end()) {
|
||||||
handles.erase(it);
|
handles.erase(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult NvMap::CreateHandle(u64 size, Handle::Id& out_handle) {
|
NvResult NvMap::CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out) {
|
||||||
if (!Common::AlignUp(size, YUZU_PAGESIZE)) {
|
if (!size) [[unlikely]] {
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
u32 id = next_handle_id.fetch_add(HandleIdIncrement, std::memory_order_relaxed);
|
|
||||||
AddHandle(Handle(size, id));
|
u32 id{next_handle_id.fetch_add(HandleIdIncrement, std::memory_order_relaxed)};
|
||||||
out_handle = id;
|
auto handle_description{std::make_shared<Handle>(size, id)};
|
||||||
|
AddHandle(handle_description);
|
||||||
|
|
||||||
|
result_out = handle_description;
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::reference_wrapper<NvMap::Handle>> NvMap::GetHandle(Handle::Id handle) {
|
std::shared_ptr<NvMap::Handle> NvMap::GetHandle(Handle::Id handle) {
|
||||||
if (auto const it = handles.find(handle); it != handles.end())
|
std::scoped_lock lock(handles_lock);
|
||||||
return {it->second};
|
try {
|
||||||
return std::nullopt;
|
return handles.at(handle);
|
||||||
|
} catch (std::out_of_range&) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DAddr NvMap::GetHandleAddress(Handle::Id handle) {
|
DAddr NvMap::GetHandleAddress(Handle::Id handle) {
|
||||||
if (auto const it = handles.find(handle); it != handles.end())
|
std::scoped_lock lock(handles_lock);
|
||||||
return it->second.d_address;
|
try {
|
||||||
return 0;
|
return handles.at(handle)->d_address;
|
||||||
|
} catch (std::out_of_range&) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DAddr NvMap::PinHandle(NvMap::Handle::Id handle, bool low_area_pin) {
|
DAddr NvMap::PinHandle(NvMap::Handle::Id handle, bool low_area_pin) {
|
||||||
std::scoped_lock lock(handles_lock);
|
auto handle_description{GetHandle(handle)};
|
||||||
auto o = GetHandle(handle);
|
if (!handle_description) [[unlikely]] {
|
||||||
if (!o) [[unlikely]] {
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto handle_description = &o->get();
|
std::scoped_lock lock(handle_description->mutex);
|
||||||
|
|
||||||
const auto map_low_area = [&] {
|
const auto map_low_area = [&] {
|
||||||
if (handle_description->pin_virt_address == 0) {
|
if (handle_description->pin_virt_address == 0) {
|
||||||
u32 address = host1x.Allocator().Allocate(u32(handle_description->aligned_size));
|
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
|
// Lock now to prevent our queue entry from being removed for allocation in-between the
|
||||||
// following check and erase
|
// following check and erase
|
||||||
std::scoped_lock ql(unmap_queue_lock);
|
std::scoped_lock queueLock(unmap_queue_lock);
|
||||||
if (handle_description->unmap_queue_entry) {
|
if (handle_description->unmap_queue_entry) {
|
||||||
unmap_queue.erase(*handle_description->unmap_queue_entry);
|
unmap_queue.erase(*handle_description->unmap_queue_entry);
|
||||||
handle_description->unmap_queue_entry.reset();
|
handle_description->unmap_queue_entry.reset();
|
||||||
|
|
||||||
if (low_area_pin) {
|
if (low_area_pin) {
|
||||||
map_low_area();
|
map_low_area();
|
||||||
handle_description->pins++;
|
handle_description->pins++;
|
||||||
return DAddr(handle_description->pin_virt_address);
|
return static_cast<DAddr>(handle_description->pin_virt_address);
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_description->pins++;
|
handle_description->pins++;
|
||||||
return handle_description->d_address;
|
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) {
|
while ((address = smmu.Allocate(aligned_up)) == 0) {
|
||||||
// Free handles until the allocation succeeds
|
// Free handles until the allocation succeeds
|
||||||
std::scoped_lock queueLock(unmap_queue_lock);
|
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
|
// Handles in the unmap queue are guaranteed not to be pinned so don't bother
|
||||||
// checking if they are before unmapping
|
// checking if they are before unmapping
|
||||||
|
std::scoped_lock freeLock(freeHandleDesc->mutex);
|
||||||
if (handle_description->d_address)
|
if (handle_description->d_address)
|
||||||
UnmapHandle(free_handle->second);
|
UnmapHandle(*freeHandleDesc);
|
||||||
} else {
|
} else {
|
||||||
LOG_CRITICAL(Service_NVDRV, "Ran out of SMMU address space!");
|
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++;
|
handle_description->pins++;
|
||||||
if (low_area_pin) {
|
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;
|
return handle_description->d_address;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NvMap::UnpinHandle(Handle::Id handle) {
|
void NvMap::UnpinHandle(Handle::Id handle) {
|
||||||
std::scoped_lock lock(handles_lock);
|
auto handle_description{GetHandle(handle)};
|
||||||
if (auto o = GetHandle(handle); o) {
|
if (!handle_description) {
|
||||||
auto handle_description = &o->get();
|
return;
|
||||||
if (--handle_description->pins < 0) {
|
}
|
||||||
LOG_WARNING(Service_NVDRV, "Pin count imbalance detected!");
|
|
||||||
} else if (!handle_description->pins) {
|
std::scoped_lock lock(handle_description->mutex);
|
||||||
std::scoped_lock ql(unmap_queue_lock);
|
if (--handle_description->pins < 0) {
|
||||||
// Add to the unmap queue allowing this handle's memory to be freed if needed
|
LOG_WARNING(Service_NVDRV, "Pin count imbalance detected!");
|
||||||
unmap_queue.push_back(handle);
|
} else if (!handle_description->pins) {
|
||||||
handle_description->unmap_queue_entry = std::prev(unmap_queue.end());
|
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) {
|
void NvMap::DuplicateHandle(Handle::Id handle, bool internal_session) {
|
||||||
std::scoped_lock lock(handles_lock);
|
auto handle_description{GetHandle(handle)};
|
||||||
auto o = GetHandle(handle);
|
if (!handle_description) {
|
||||||
if (!o) {
|
|
||||||
LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
|
LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto result = o->get().Duplicate(internal_session);
|
|
||||||
|
auto result = handle_description->Duplicate(internal_session);
|
||||||
if (result != NvResult::Success) {
|
if (result != NvResult::Success) {
|
||||||
LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
|
LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool internal_session) {
|
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::weak_ptr<Handle> hWeak{GetHandle(handle)};
|
||||||
std::scoped_lock lock(handles_lock);
|
FreeInfo freeInfo;
|
||||||
if (auto o = GetHandle(handle); o) {
|
|
||||||
auto handle_description = &o->get();
|
// 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 (internal_session) {
|
||||||
if (--handle_description->internal_dupes < 0)
|
if (--handle_description->internal_dupes < 0)
|
||||||
LOG_WARNING(Service_NVDRV, "Internal duplicate count imbalance detected!");
|
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) {
|
} else if (handle_description->dupes == 0) {
|
||||||
// Force unmap the handle
|
// Force unmap the handle
|
||||||
if (handle_description->d_address) {
|
if (handle_description->d_address) {
|
||||||
std::scoped_lock ql(unmap_queue_lock);
|
std::scoped_lock queueLock(unmap_queue_lock);
|
||||||
UnmapHandle(*handle_description);
|
UnmapHandle(*handle_description);
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_description->pins = 0;
|
handle_description->pins = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to remove the shared ptr to the handle from the map, if nothing else is using the
|
// 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
|
// handle then it will now be freed when `handle_description` goes out of scope
|
||||||
if (TryRemoveHandle(*handle_description)) {
|
if (TryRemoveHandle(*handle_description)) {
|
||||||
LOG_DEBUG(Service_NVDRV, "Removed nvmap handle: {}", handle);
|
LOG_DEBUG(Service_NVDRV, "Removed nvmap handle: {}", handle);
|
||||||
} else {
|
} 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()) {
|
freeInfo = {
|
||||||
// LOG_DEBUG(Service_NVDRV, "nvmap handle: {} wasn't freed as it is still in use", handle);
|
|
||||||
// freeInfo.can_unlock = false;
|
|
||||||
// }
|
|
||||||
return FreeInfo{
|
|
||||||
.address = handle_description->address,
|
.address = handle_description->address,
|
||||||
.size = handle_description->size,
|
.size = handle_description->size,
|
||||||
.was_uncached = handle_description->flags.map_uncached.Value() != 0,
|
.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 {
|
} else {
|
||||||
return std::nullopt;
|
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) {
|
void NvMap::UnmapAllHandles(NvCore::SessionId session_id) {
|
||||||
std::scoped_lock lk{handles_lock};
|
auto handles_copy = [&] {
|
||||||
for (auto it = handles.begin(); it != handles.end(); ++it) {
|
std::scoped_lock lk{handles_lock};
|
||||||
if (it->second.session_id.id != session_id.id || it->second.dupes <= 0) {
|
return handles;
|
||||||
continue;
|
}();
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,6 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#if BOOST_VERSION >= 109000
|
|
||||||
#include <boost/unordered/unordered_node_map.hpp>
|
|
||||||
#else
|
|
||||||
#include <unordered_map>
|
|
||||||
#endif
|
|
||||||
#include <ankerl/unordered_dense.h>
|
#include <ankerl/unordered_dense.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
@@ -36,36 +31,54 @@ class Host1x;
|
|||||||
namespace Service::Nvidia::NvCore {
|
namespace Service::Nvidia::NvCore {
|
||||||
|
|
||||||
class Container;
|
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 {
|
class NvMap {
|
||||||
public:
|
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 {
|
struct Handle {
|
||||||
using Id = u32;
|
std::mutex mutex;
|
||||||
std::optional<typename std::list<Handle::Id>::iterator> unmap_queue_entry{};
|
|
||||||
u64 align{}; //!< The alignment to use when pinning the handle onto the SMMU
|
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 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 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
|
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 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
|
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
|
Id id; //!< A globally unique identifier for this handle
|
||||||
|
|
||||||
|
s64 pins{};
|
||||||
u32 pin_virt_address{};
|
u32 pin_virt_address{};
|
||||||
|
std::optional<typename std::list<std::shared_ptr<Handle>>::iterator> unmap_queue_entry{};
|
||||||
|
|
||||||
union Flags {
|
union Flags {
|
||||||
u32 raw;
|
u32 raw;
|
||||||
BitField<0, 1, u32> map_uncached; //!< If the handle should be mapped as uncached
|
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<2, 1, u32> keep_uncached_after_free; //!< Only applicable when the handle was
|
||||||
BitField<4, 1, u32> _unk0_; //!< Passed to IOVMM for pins
|
//!< allocated with a fixed address
|
||||||
|
BitField<4, 1, u32> _unk0_; //!< Passed to IOVMM for pins
|
||||||
} flags{};
|
} flags{};
|
||||||
static_assert(sizeof(Flags) == sizeof(u32));
|
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
|
u8 kind{}; //!< Used for memory compression
|
||||||
bool allocated : 1 = false; //!< If the handle has been allocated with `Alloc`
|
bool allocated{}; //!< If the handle has been allocated with `Alloc`
|
||||||
bool in_heap : 1 = false;
|
bool in_heap{};
|
||||||
bool is_shared_mem_mapped : 1 = false; //!< If this nvmap has been mapped with the MapSharedMem IPC < call
|
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);
|
Handle(u64 size, Id id);
|
||||||
|
|
||||||
@@ -110,9 +123,9 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Creates an unallocated handle of the given size
|
* @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);
|
DAddr GetHandleAddress(Handle::Id handle);
|
||||||
|
|
||||||
@@ -144,21 +157,20 @@ public:
|
|||||||
|
|
||||||
void UnmapAllHandles(NvCore::SessionId session_id);
|
void UnmapAllHandles(NvCore::SessionId session_id);
|
||||||
|
|
||||||
std::list<Handle::Id> unmap_queue{};
|
private:
|
||||||
/// Main owning map of handles
|
std::list<std::shared_ptr<Handle>> unmap_queue{};
|
||||||
#if BOOST_VERSION >= 109000
|
|
||||||
boost::unordered_node_map<Handle::Id, Handle> handles{};
|
|
||||||
#else
|
|
||||||
std::unordered_map<Handle::Id, Handle> handles{};
|
|
||||||
#endif
|
|
||||||
std::mutex unmap_queue_lock{}; //!< Protects access to `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`
|
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};
|
std::atomic<u32> next_handle_id{HandleIdIncrement};
|
||||||
Tegra::Host1x::Host1x& host1x;
|
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
|
* @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
|
* @return If the handle was removed from the map
|
||||||
*/
|
*/
|
||||||
bool TryRemoveHandle(const Handle& handle_description);
|
bool TryRemoveHandle(const Handle& handle_description);
|
||||||
|
|
||||||
|
Container& core;
|
||||||
};
|
};
|
||||||
} // namespace Service::Nvidia::NvCore
|
} // namespace Service::Nvidia::NvCore
|
||||||
|
|||||||
@@ -328,11 +328,10 @@ NvResult nvhost_as_gpu::MapBufferEx(IoctlMapBufferEx& params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto o = nvmap.GetHandle(params.handle);
|
auto handle{nvmap.GetHandle(params.handle)};
|
||||||
if (!o) {
|
if (!handle) {
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
auto handle = &o->get();
|
|
||||||
|
|
||||||
DAddr device_address = DAddr(nvmap.PinHandle(params.handle, false) + params.buffer_offset);
|
DAddr device_address = DAddr(nvmap.PinHandle(params.handle, false) + params.buffer_offset);
|
||||||
u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
|
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++) {
|
for (std::size_t i = 0; i < syncpt_increments.size(); i++) {
|
||||||
const SyncptIncr& syncpt_incr = syncpt_increments[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) {
|
for (const auto& cmd_buffer : command_buffers) {
|
||||||
const auto object = nvmap.GetHandle(cmd_buffer.memory_id);
|
const auto object = nvmap.GetHandle(cmd_buffer.memory_id);
|
||||||
ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;);
|
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));
|
host1x.PushEntries(fd, std::move(cmdlist));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,14 +83,17 @@ void nvmap::OnClose(DeviceFD fd) {
|
|||||||
NvResult nvmap::IocCreate(IocCreateParams& params) {
|
NvResult nvmap::IocCreate(IocCreateParams& params) {
|
||||||
LOG_DEBUG(Service_NVDRV, "called, size={:#08x}", params.size);
|
LOG_DEBUG(Service_NVDRV, "called, size={:#08x}", params.size);
|
||||||
|
|
||||||
NvCore::NvMap::Handle handle_description(0, 0);
|
std::shared_ptr<NvCore::NvMap::Handle> handle_description{};
|
||||||
// Orig size is the unaligned size, set the handle to that
|
auto result =
|
||||||
auto result = file.CreateHandle(params.size, params.handle);
|
file.CreateHandle(Common::AlignUp(params.size, YUZU_PAGESIZE), handle_description);
|
||||||
if (result != NvResult::Success) {
|
if (result != NvResult::Success) {
|
||||||
LOG_CRITICAL(Service_NVDRV, "Failed to create Object");
|
LOG_CRITICAL(Service_NVDRV, "Failed to create Object");
|
||||||
return result;
|
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;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,27 +115,30 @@ NvResult nvmap::IocAlloc(IocAllocParams& params, DeviceFD fd) {
|
|||||||
params.align = YUZU_PAGESIZE;
|
params.align = YUZU_PAGESIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::scoped_lock lock(file.handles_lock);
|
auto handle_description{file.GetHandle(params.handle)};
|
||||||
auto o = file.GetHandle(params.handle);
|
if (!handle_description) {
|
||||||
if (!o) {
|
|
||||||
LOG_CRITICAL(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
|
LOG_CRITICAL(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto handle_description = &o->get();
|
|
||||||
if (handle_description->allocated) {
|
if (handle_description->allocated) {
|
||||||
LOG_CRITICAL(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
|
LOG_CRITICAL(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
|
||||||
return NvResult::InsufficientMemory;
|
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) {
|
if (result != NvResult::Success) {
|
||||||
LOG_CRITICAL(Service_NVDRV, "Object failed to allocate, handle={:08X}", params.handle);
|
LOG_CRITICAL(Service_NVDRV, "Object failed to allocate, handle={:08X}", params.handle);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
bool is_out_io{};
|
bool is_out_io{};
|
||||||
auto process = container.GetSession(sessions[fd])->process;
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,13 +151,13 @@ NvResult nvmap::IocGetId(IocGetIdParams& params) {
|
|||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::scoped_lock lock(file.handles_lock);
|
auto handle_description{file.GetHandle(params.handle)};
|
||||||
auto o = file.GetHandle(params.handle);
|
if (!handle_description) {
|
||||||
if (!o) {
|
|
||||||
LOG_CRITICAL(Service_NVDRV, "Error!");
|
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;
|
params.id = handle_description->id;
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
@@ -168,14 +174,12 @@ NvResult nvmap::IocFromId(IocFromIdParams& params) {
|
|||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::scoped_lock lock(file.handles_lock);
|
auto handle_description{file.GetHandle(params.id)};
|
||||||
auto o = file.GetHandle(params.id);
|
if (!handle_description) {
|
||||||
if (!o) {
|
|
||||||
LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
|
LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto handle_description = &o->get();
|
|
||||||
auto result = handle_description->Duplicate(false);
|
auto result = handle_description->Duplicate(false);
|
||||||
if (result != NvResult::Success) {
|
if (result != NvResult::Success) {
|
||||||
LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
|
LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
|
||||||
@@ -195,14 +199,12 @@ NvResult nvmap::IocParam(IocParamParams& params) {
|
|||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::scoped_lock lock(file.handles_lock);
|
auto handle_description{file.GetHandle(params.handle)};
|
||||||
auto o = file.GetHandle(params.handle);
|
if (!handle_description) {
|
||||||
if (!o) {
|
|
||||||
LOG_CRITICAL(Service_NVDRV, "Not registered handle!");
|
LOG_CRITICAL(Service_NVDRV, "Not registered handle!");
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto handle_description = &o->get();
|
|
||||||
switch (params.param) {
|
switch (params.param) {
|
||||||
case HandleParameterType::Size:
|
case HandleParameterType::Size:
|
||||||
params.result = static_cast<u32_le>(handle_description->orig_size);
|
params.result = static_cast<u32_le>(handle_description->orig_size);
|
||||||
|
|||||||
Reference in New Issue
Block a user