From e98e6837bb237d1e3e8b7736679a3c8b09011cd8 Mon Sep 17 00:00:00 2001 From: cjwilsontech Date: Wed, 3 Sep 2025 20:38:56 -0700 Subject: [PATCH] Support setting the X3D V-Cache Mode --- daemon/gamemode-config.c | 39 ++++ daemon/gamemode-config.h | 2 + daemon/gamemode-context.c | 94 ++++++++++ daemon/gamemode-tests.c | 117 ++++++++++++ .../com.feralinteractive.GameMode.policy.in | 12 ++ data/polkit/rules.d/gamemode.rules.in | 8 +- example/gamemode.ini | 12 ++ util/meson.build | 15 ++ util/x3dmodectl.c | 171 ++++++++++++++++++ 9 files changed, 467 insertions(+), 3 deletions(-) create mode 100644 util/x3dmodectl.c diff --git a/daemon/gamemode-config.c b/daemon/gamemode-config.c index 483de6f..7769f00 100644 --- a/daemon/gamemode-config.c +++ b/daemon/gamemode-config.c @@ -114,6 +114,8 @@ struct GameModeConfig { char cpu_park_cores[CONFIG_VALUE_MAX]; char cpu_pin_cores[CONFIG_VALUE_MAX]; + char amd_x3d_mode_desired[CONFIG_VALUE_MAX]; + char amd_x3d_mode_default[CONFIG_VALUE_MAX]; long require_supervisor; char supervisor_whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; @@ -242,6 +244,23 @@ static bool get_string_value(const char *value, char output[CONFIG_VALUE_MAX]) return true; } +/* + * Get and validate an x3d mode value + */ +static bool get_x3d_mode_value(const char *name, const char *value, char output[CONFIG_VALUE_MAX]) +{ + if (strcmp(value, "frequency") != 0 && strcmp(value, "cache") != 0) { + LOG_ERROR("Config: %s has invalid value '%s'. Valid values are 'frequency' or 'cache'\n", + name, + value); + return false; + } + + strncpy(output, value, CONFIG_VALUE_MAX - 1); + output[CONFIG_VALUE_MAX - 1] = '\0'; + return true; +} + /* Controls whether to read the protected config variables */ static bool load_protected = false; @@ -316,6 +335,10 @@ static int inih_handler(void *user, const char *section, const char *name, const valid = get_string_value(value, self->values.cpu_park_cores); } else if (strcmp(name, "pin_cores") == 0) { valid = get_string_value(value, self->values.cpu_pin_cores); + } else if (strcmp(name, "amd_x3d_mode_desired") == 0) { + valid = get_x3d_mode_value(name, value, self->values.amd_x3d_mode_desired); + } else if (strcmp(name, "amd_x3d_mode_default") == 0) { + valid = get_x3d_mode_value(name, value, self->values.amd_x3d_mode_default); } } else if (strcmp(section, "supervisor") == 0) { /* Supervisor subsection */ @@ -861,6 +884,22 @@ void config_get_cpu_pin_cores(GameModeConfig *self, char value[CONFIG_VALUE_MAX] sizeof(self->values.cpu_pin_cores)); } +void config_get_amd_x3d_mode_desired(GameModeConfig *self, char value[CONFIG_VALUE_MAX]) +{ + memcpy_locked_config(self, + value, + &self->values.amd_x3d_mode_desired, + sizeof(self->values.amd_x3d_mode_desired)); +} + +void config_get_amd_x3d_mode_default(GameModeConfig *self, char value[CONFIG_VALUE_MAX]) +{ + memcpy_locked_config(self, + value, + &self->values.amd_x3d_mode_default, + sizeof(self->values.amd_x3d_mode_default)); +} + /* * Checks if the supervisor is whitelisted */ diff --git a/daemon/gamemode-config.h b/daemon/gamemode-config.h index a134beb..b82784d 100644 --- a/daemon/gamemode-config.h +++ b/daemon/gamemode-config.h @@ -126,6 +126,8 @@ void config_get_amd_performance_level(GameModeConfig *self, char value[CONFIG_VA */ void config_get_cpu_park_cores(GameModeConfig *self, char value[CONFIG_VALUE_MAX]); void config_get_cpu_pin_cores(GameModeConfig *self, char value[CONFIG_VALUE_MAX]); +void config_get_amd_x3d_mode_desired(GameModeConfig *self, char value[CONFIG_VALUE_MAX]); +void config_get_amd_x3d_mode_default(GameModeConfig *self, char value[CONFIG_VALUE_MAX]); /** * Functions to get supervisor config permissions diff --git a/daemon/gamemode-context.c b/daemon/gamemode-context.c index 9f9668d..f4e0f23 100644 --- a/daemon/gamemode-context.c +++ b/daemon/gamemode-context.c @@ -105,6 +105,8 @@ struct GameModeContext { long initial_split_lock_mitigate; + char initial_x3d_mode[64]; /**initial_split_lock_mitigate = -1; + /* clear the initial x3d mode string */ + memset(self->initial_x3d_mode, 0, sizeof(self->initial_x3d_mode)); + pthread_rwlock_init(&self->rwlock, NULL); /* Get the reaper thread going */ @@ -256,6 +261,89 @@ static int game_mode_disable_splitlock(GameModeContext *self, bool disable) return 0; } +static void game_mode_store_x3d_mode(GameModeContext *self) +{ + char x3d_mode_desired[CONFIG_VALUE_MAX] = { 0 }; + config_get_amd_x3d_mode_desired(self->config, x3d_mode_desired); + if (x3d_mode_desired[0] == '\0') { + return; + } + + if (access(LIBEXECDIR "/x3dmodectl", X_OK) != 0) { + LOG_MSG("x3dmodectl utility not found, X3D mode control disabled\n"); + return; + } + + const char *const exec_args[] = { + LIBEXECDIR "/x3dmodectl", + "get", + NULL, + }; + + char output[EXTERNAL_BUFFER_MAX] = { 0 }; + int ret = run_external_process(exec_args, output, -1); + if (ret != 0) { + LOG_MSG("X3D mode hardware not available or failed to get current mode\n"); + return; + } + + strncpy(self->initial_x3d_mode, output, sizeof(self->initial_x3d_mode) - 1); + self->initial_x3d_mode[sizeof(self->initial_x3d_mode) - 1] = '\0'; + char *newline = strchr(self->initial_x3d_mode, '\n'); + if (newline) { + *newline = '\0'; + } + + LOG_MSG("x3d mode was initially set to [%s]\n", self->initial_x3d_mode); +} + +static int game_mode_set_x3d_mode(GameModeContext *self, bool desired) +{ + char x3d_mode_config[CONFIG_VALUE_MAX] = { 0 }; + + if (desired) { + config_get_amd_x3d_mode_desired(self->config, x3d_mode_config); + } else { + config_get_amd_x3d_mode_default(self->config, x3d_mode_config); + if (x3d_mode_config[0] == '\0') { + if (self->initial_x3d_mode[0] != '\0') { + strncpy(x3d_mode_config, self->initial_x3d_mode, CONFIG_VALUE_MAX - 1); + x3d_mode_config[CONFIG_VALUE_MAX - 1] = '\0'; + } else { + return 0; + } + } + } + + if (x3d_mode_config[0] == '\0') { + return 0; + } + + if (access(LIBEXECDIR "/x3dmodectl", X_OK) != 0) { + LOG_MSG("x3dmodectl utility not found, skipping X3D mode change\n"); + return 0; + } + + if (strcmp(x3d_mode_config, "frequency") != 0 && strcmp(x3d_mode_config, "cache") != 0) { + LOG_ERROR("Invalid X3D mode '%s'. Valid modes are 'frequency' or 'cache'\n", + x3d_mode_config); + return -1; + } + + const char *const exec_args[] = { + "pkexec", LIBEXECDIR "/x3dmodectl", "set", x3d_mode_config, NULL, + }; + + LOG_MSG("Requesting update of X3D mode to %s\n", x3d_mode_config); + int ret = run_external_process(exec_args, NULL, -1); + if (ret != 0) { + LOG_ERROR("Failed to update X3D mode\n"); + return ret; + } + + return 0; +} + static void game_mode_store_governor(GameModeContext *self) { if (self->current_govenor != GAME_MODE_GOVERNOR_DEFAULT) @@ -468,6 +556,8 @@ static void game_mode_context_store_defaults(GameModeContext *self) game_mode_store_governor(self); game_mode_store_splitlock(self); + + game_mode_store_x3d_mode(self); } /** @@ -504,6 +594,8 @@ static void game_mode_context_enter(GameModeContext *self) game_mode_disable_splitlock(self, true); + game_mode_set_x3d_mode(self, true); + /* Apply GPU optimisations by first getting the current values, and then setting the target */ game_mode_get_gpu(self->stored_gpu); game_mode_apply_gpu(self->target_gpu); @@ -548,6 +640,8 @@ static void game_mode_context_leave(GameModeContext *self) game_mode_disable_splitlock(self, false); + game_mode_set_x3d_mode(self, false); + game_mode_set_governor(self, GAME_MODE_GOVERNOR_DEFAULT); game_mode_disable_igpu_optimization(self); diff --git a/daemon/gamemode-tests.c b/daemon/gamemode-tests.c index bc4d02f..384eb0e 100644 --- a/daemon/gamemode-tests.c +++ b/daemon/gamemode-tests.c @@ -42,6 +42,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "gamemode-config.h" #include "gamemode_client.h" +#include "build-config.h" + #include #include #include @@ -837,6 +839,106 @@ int run_ioprio_tests(struct GameModeConfig *config) return ret; } +/* Check the AMD X3D mode setting works */ +static int run_x3d_mode_tests(struct GameModeConfig *config) +{ + /* Get the two config parameters we care about */ + char desired_mode[CONFIG_VALUE_MAX] = { 0 }; + config_get_amd_x3d_mode_desired(config, desired_mode); + + if (desired_mode[0] == '\0') { + /* Not configured */ + return 1; + } + + char default_mode[CONFIG_VALUE_MAX] = { 0 }; + config_get_amd_x3d_mode_default(config, default_mode); + + /* Get the initial X3D mode state */ + char initial_mode[64] = { 0 }; + const char *const get_args[] = { + LIBEXECDIR "/x3dmodectl", + "get", + NULL, + }; + + char output[EXTERNAL_BUFFER_MAX] = { 0 }; + int ret = run_external_process(get_args, output, -1); + if (ret != 0) { + return 1; + } + + /* Store the initial mode, removing any trailing newline */ + strncpy(initial_mode, output, sizeof(initial_mode) - 1); + initial_mode[sizeof(initial_mode) - 1] = '\0'; + char *newline = strchr(initial_mode, '\n'); + if (newline) { + *newline = '\0'; + } + + /* Check if hardware is available */ + if (strcmp(initial_mode, "unavailable") == 0) { + return 1; + } + + /* Start gamemode */ + gamemode_request_start(); + + /* Give gamemode time to apply settings */ + usleep(500000); + + /* Verify the mode is the desired one */ + ret = run_external_process(get_args, output, -1); + if (ret != 0) { + LOG_ERROR("Failed to get X3D mode after gamemode start\n"); + gamemode_request_end(); + return -1; + } + + /* Remove trailing newline from output */ + newline = strchr(output, '\n'); + if (newline) { + *newline = '\0'; + } + + if (strcmp(output, desired_mode) != 0) { + LOG_ERROR("X3D mode was not set to %s (was actually %s)!\n", desired_mode, output); + gamemode_request_end(); + return -1; + } + + + /* End gamemode */ + gamemode_request_end(); + + /* Give gamemode time to restore settings */ + usleep(500000); + + /* Verify the mode is restored */ + ret = run_external_process(get_args, output, -1); + if (ret != 0) { + LOG_ERROR("Failed to get X3D mode after gamemode end\n"); + return -1; + } + + /* Remove trailing newline from output */ + newline = strchr(output, '\n'); + if (newline) { + *newline = '\0'; + } + + /* Determine expected restored mode */ + const char *expected_mode = (default_mode[0] != '\0') ? default_mode : initial_mode; + + if (strcmp(output, expected_mode) != 0) { + LOG_ERROR("X3D mode was not restored to %s (was actually %s)!\n", expected_mode, output); + return -1; + } + + + return 0; +} + /** * game_mode_run_feature_tests runs a set of tests for each current feature (based on the current * config) returns 0 for success, -1 for failure @@ -947,6 +1049,21 @@ static int game_mode_run_feature_tests(struct GameModeConfig *config) } } + /* Was the AMD X3D mode changed? */ + { + LOG_MSG("::: Verifying AMD X3D mode\n"); + int x3dstatus = run_x3d_mode_tests(config); + + if (x3dstatus == 1) + LOG_MSG("::: Passed (AMD X3D mode not configured)\n"); + else if (x3dstatus == 0) + LOG_MSG("::: Passed\n"); + else { + LOG_MSG("::: Failed!\n"); + status = -1; + } + } + /* TODO */ /* Was the scheduling applied and removed? Does it get applied to a full process tree? */ /* Does the screensaver get inhibited? Unknown if this is testable, org.freedesktop.ScreenSaver diff --git a/data/polkit/actions/com.feralinteractive.GameMode.policy.in b/data/polkit/actions/com.feralinteractive.GameMode.policy.in index 1a2c505..963b788 100644 --- a/data/polkit/actions/com.feralinteractive.GameMode.policy.in +++ b/data/polkit/actions/com.feralinteractive.GameMode.policy.in @@ -70,4 +70,16 @@ @LIBEXECDIR@/platprofctl true + + + Modify the AMD X3D cache mode + Authentication is required to modify AMD X3D cache mode + + no + no + no + + @LIBEXECDIR@/x3dmodectl + true + diff --git a/data/polkit/rules.d/gamemode.rules.in b/data/polkit/rules.d/gamemode.rules.in index 21a209d..6714a8b 100644 --- a/data/polkit/rules.d/gamemode.rules.in +++ b/data/polkit/rules.d/gamemode.rules.in @@ -1,13 +1,15 @@ /* - * Allow users in privileged gamemode group to run cpugovctl & - * gpuclockctl without authentication + * Allow users in privileged gamemode group to run gamemode utilities + * (cpugovctl, gpuclockctl, cpucorectl, procsysctl, platprofctl, x3dmodectl) + * without authentication */ polkit.addRule(function (action, subject) { if ((action.id == "com.feralinteractive.GameMode.governor-helper" || action.id == "com.feralinteractive.GameMode.gpu-helper" || action.id == "com.feralinteractive.GameMode.cpu-helper" || action.id == "com.feralinteractive.GameMode.procsys-helper" || - action.id == "com.feralinteractive.GameMode.profile-helper") && + action.id == "com.feralinteractive.GameMode.profile-helper" || + action.id == "com.feralinteractive.GameMode.x3dmode-helper") && subject.isInGroup("@GAMEMODE_PRIVILEGED_GROUP@")) { return polkit.Result.YES; diff --git a/example/gamemode.ini b/example/gamemode.ini index 45365bd..f526666 100644 --- a/example/gamemode.ini +++ b/example/gamemode.ini @@ -97,6 +97,18 @@ disable_splitlock=1 ;park_cores=no ;pin_cores=yes +; AMD 3D V-Cache Performance Optimizer Driver settings +; These options control the cache mode for dual CCD X3D CPUs (7950x3d, 9950x3d, etc.) +; "frequency" mode prioritizes higher boost clocks, "cache" mode prioritizes 3D V-Cache performance +; Allows for dynamically shifting other processes onto a different CCD. E.g. amd_x3d_mode_default=cache may be +; preferred for some normal, non-game workloads that are better optimized for cache, but +; amd_x3d_mode_desired=frequency can shift everything but the game process to frequency CCD while GameMode is +; running, in conjunction with core pinning. +; Only works on systems with the AMD X3D mode driver (automatically detected) +; The desired mode is set when entering gamemode, default mode is restored when leaving +;amd_x3d_mode_desired=frequency +;amd_x3d_mode_default=cache + [supervisor] ; This section controls the new gamemode functions gamemode_request_start_for and gamemode_request_end_for ; The whilelist and blacklist control which supervisor programs are allowed to make the above requests diff --git a/util/meson.build b/util/meson.build index 4d76c05..479c6a8 100644 --- a/util/meson.build +++ b/util/meson.build @@ -73,3 +73,18 @@ platprofctl = executable( install: true, install_dir: path_libexecdir, ) + +# Small target util to get and set AMD X3D cache mode +x3dmodectl_sources = [ + 'x3dmodectl.c', +] + +x3dmodectl = executable( + 'x3dmodectl', + sources: x3dmodectl_sources, + dependencies: [ + link_daemon_common, + ], + install: true, + install_dir: path_libexecdir, +) diff --git a/util/x3dmodectl.c b/util/x3dmodectl.c new file mode 100644 index 0000000..5228e2e --- /dev/null +++ b/util/x3dmodectl.c @@ -0,0 +1,171 @@ +/* + +Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Feral Interactive nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + */ + +#define _GNU_SOURCE + +#include "common-logging.h" + +#include +#include +#include +#include +#include +#include +#include + +#define X3D_MODE_GLOB_PATTERN "/sys/bus/platform/drivers/amd_x3d_vcache/*/amd_x3d_mode" + +static char x3d_mode_path[PATH_MAX] = { 0 }; + +/** + * Find and set the x3d mode sysfs path + */ +static bool find_x3d_mode_path(void) +{ + if (x3d_mode_path[0] != '\0') { + return access(x3d_mode_path, F_OK) == 0; + } + + glob_t glob_result; + if (glob(X3D_MODE_GLOB_PATTERN, GLOB_NOSORT, NULL, &glob_result) != 0) { + return false; + } + + if (glob_result.gl_pathc > 0) { + strncpy(x3d_mode_path, glob_result.gl_pathv[0], PATH_MAX - 1); + x3d_mode_path[PATH_MAX - 1] = '\0'; + } + + globfree(&glob_result); + return x3d_mode_path[0] != '\0' && access(x3d_mode_path, F_OK) == 0; +} + +/** + * Check if x3d mode control is available + */ +static bool x3d_mode_available(void) +{ + return find_x3d_mode_path(); +} + +/** + * Return the current x3d mode + */ +static const char *get_x3d_mode(void) +{ + static char mode[64] = { 0 }; + memset(mode, 0, sizeof(mode)); + + if (!x3d_mode_available()) { + return "unavailable"; + } + + FILE *f = fopen(x3d_mode_path, "r"); + if (!f) { + LOG_ERROR("Failed to open x3d mode file for read %s: %s\n", x3d_mode_path, strerror(errno)); + return "error"; + } + + if (fgets(mode, sizeof(mode), f) != NULL) { + /* Remove trailing newline */ + char *newline = strchr(mode, '\n'); + if (newline) { + *newline = '\0'; + } + } else { + LOG_ERROR("Failed to read x3d mode from %s: %s\n", x3d_mode_path, strerror(errno)); + fclose(f); + return "error"; + } + + fclose(f); + return mode; +} + +/** + * Set the x3d mode to the specified value + */ +static int set_x3d_mode(const char *value) +{ + if (!x3d_mode_available()) { + LOG_ERROR("AMD x3D mode control is not available on this system\n"); + return EXIT_FAILURE; + } + + /* Validate the mode value */ + if (strcmp(value, "frequency") != 0 && strcmp(value, "cache") != 0) { + LOG_ERROR("Invalid x3d mode '%s'. Valid modes are 'frequency' or 'cache'\n", value); + return EXIT_FAILURE; + } + + FILE *f = fopen(x3d_mode_path, "w"); + if (!f) { + LOG_ERROR("Failed to open x3d mode file for write %s: %s\n", + x3d_mode_path, + strerror(errno)); + return EXIT_FAILURE; + } + + int res = fprintf(f, "%s\n", value); + if (res < 0) { + LOG_ERROR("Failed to set x3d mode to %s: %s\n", value, strerror(errno)); + fclose(f); + return EXIT_FAILURE; + } + + fclose(f); + return EXIT_SUCCESS; +} + +/** + * Main entry point, dispatch to the appropriate helper + */ +int main(int argc, char *argv[]) +{ + if (argc == 2 && strncmp(argv[1], "get", 3) == 0) { + printf("%s", get_x3d_mode()); + } else if (argc == 3 && strncmp(argv[1], "set", 3) == 0) { + const char *value = argv[2]; + + if (geteuid() != 0) { + LOG_ERROR("This program must be run as root\n"); + return EXIT_FAILURE; + } + + return set_x3d_mode(value); + } else { + fprintf(stderr, "usage: x3dmodectl [get] [set VALUE]\n"); + fprintf(stderr, "where VALUE can be 'frequency' or 'cache'\n"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +}