Merge pull request #548 from cjwilsontech/x3d-vcache-optimizer-support

Support setting the X3D V-Cache Mode

Adds support for adjusting the AMD X3D V-Cache mode(https://www.phoronix.com/news/AMD-3DV-Cache-Optimizer-Linux) for systems with the latest optimizer driver support using `amd_x3d_mode` in the Linux driver for dual-CCD systems.

This allows GameMode to adjust the system's preference for which CCD to schedule tasks on, opening opportunities for optimizing a system in new ways. For example, if a system is normally in `cache` mode to optimize for cache-sensitive tasks, this setting can be used to shift those to the `frequency` CCD temporarily while GameMode is running a game process pinned on the cache CCD, and then switch it back afterwards.

Changes:
- Adds two new config items, `amd_x3d_mode_desired` and `amd_x3d_mode_default` that can be set to either `frequency` or `cache`.
- Adds a new utility, `x3dmodectl` for getting or updating the X3D mode.
- Includes the new utility in the test command.
This commit is contained in:
afayaz-feral
2025-09-04 18:20:16 +01:00
committed by GitHub
9 changed files with 465 additions and 3 deletions

View File

@@ -115,6 +115,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];
@@ -243,6 +245,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;
@@ -319,6 +338,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 */
@@ -866,6 +889,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
*/

View File

@@ -127,6 +127,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

View File

@@ -105,6 +105,8 @@ struct GameModeContext {
long initial_split_lock_mitigate;
char initial_x3d_mode[64]; /**<Initial x3d mode to restore */
/* Reaper control */
struct {
pthread_t thread;
@@ -168,6 +170,9 @@ void game_mode_context_init(GameModeContext *self)
self->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);

View File

@@ -42,6 +42,8 @@ POSSIBILITY OF SUCH DAMAGE.
#include "gamemode-config.h"
#include "gamemode_client.h"
#include "build-config.h"
#include <pthread.h>
#include <sys/syscall.h>
#include <sys/wait.h>
@@ -837,6 +839,104 @@ 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 +1047,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

View File

@@ -70,4 +70,16 @@
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/platprofctl</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
</action>
<action id="com.feralinteractive.GameMode.x3dmode-helper">
<description>Modify the AMD X3D cache mode</description>
<message>Authentication is required to modify AMD X3D cache mode</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>no</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/x3dmodectl</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
</action>
</policyconfig>

View File

@@ -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;

View File

@@ -101,6 +101,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

View File

@@ -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,
)

171
util/x3dmodectl.c Normal file
View File

@@ -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 <errno.h>
#include <glob.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#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;
}