feat(gamemode): Add support for new GPU cards via nv_per_profile_editable in gamemode.ini

- Added a new configuration variable `nv_per_profile_editable` to the `gamemode.ini` file.
  - If set to 1 (default behavior), the code will use per-profile offset behavior.
  - If set to 0, the code will use the AllPerformanceLevels API, which is compatible with newer cards like the GTX5060ti.

- Updated the `gpuclockctl` utility to accept the `nv_per_profile_editable` parameter.
  - If the parameter is not provided, it defaults to 1 and uses the previous API for backward compatibility.

This change allows `gamemode` to support a wider range of GPU cards by providing flexibility in how GPU performance levels are managed.

**Notes:**
- Ensure that the `gamemode.ini` file includes the new `nv_per_profile_editable` setting.
- Verify that the updated `gpuclockctl` utility functions as expected with both default and specified values for `nv_per_profile_editable`.

Tested on: RTX 5060 ti (driver 575.64.05) on Ubuntu 25.04
This commit is contained in:
mangobiche
2025-08-27 16:39:44 -05:00
parent 47d73cbb3f
commit e5b0a2bf3e
6 changed files with 188 additions and 77 deletions

View File

@@ -51,6 +51,7 @@ struct GameModeGPUInfo {
long nv_core; /* Nvidia core clock */ long nv_core; /* Nvidia core clock */
long nv_mem; /* Nvidia mem clock */ long nv_mem; /* Nvidia mem clock */
long nv_powermizer_mode; /* NV Powermizer Mode */ long nv_powermizer_mode; /* NV Powermizer Mode */
long nv_per_profile_editable; /* Allows per profile editable offsets */
char amd_performance_level[GPU_VALUE_MAX]; /* The AMD performance level set to */ char amd_performance_level[GPU_VALUE_MAX]; /* The AMD performance level set to */
}; };

View File

@@ -110,6 +110,7 @@ struct GameModeConfig {
long nv_core_clock_mhz_offset; long nv_core_clock_mhz_offset;
long nv_mem_clock_mhz_offset; long nv_mem_clock_mhz_offset;
long nv_powermizer_mode; long nv_powermizer_mode;
long nv_per_profile_editable;
char amd_performance_level[CONFIG_VALUE_MAX]; char amd_performance_level[CONFIG_VALUE_MAX];
char cpu_park_cores[CONFIG_VALUE_MAX]; char cpu_park_cores[CONFIG_VALUE_MAX];
@@ -308,6 +309,8 @@ static int inih_handler(void *user, const char *section, const char *name, const
valid = get_long_value(name, value, &self->values.nv_mem_clock_mhz_offset); valid = get_long_value(name, value, &self->values.nv_mem_clock_mhz_offset);
} else if (strcmp(name, "nv_powermizer_mode") == 0) { } else if (strcmp(name, "nv_powermizer_mode") == 0) {
valid = get_long_value(name, value, &self->values.nv_powermizer_mode); valid = get_long_value(name, value, &self->values.nv_powermizer_mode);
} else if (strcmp(name, "nv_per_profile_editable") == 0) {
valid = get_long_value(name, value, &self->values.nv_per_profile_editable);
} else if (strcmp(name, "amd_performance_level") == 0) { } else if (strcmp(name, "amd_performance_level") == 0) {
valid = get_string_value(value, self->values.amd_performance_level); valid = get_string_value(value, self->values.amd_performance_level);
} }
@@ -387,6 +390,7 @@ static void load_config_files(GameModeConfig *self)
self->values.reaper_frequency = DEFAULT_REAPER_FREQ; self->values.reaper_frequency = DEFAULT_REAPER_FREQ;
self->values.gpu_device = 0; self->values.gpu_device = 0;
self->values.nv_powermizer_mode = -1; self->values.nv_powermizer_mode = -1;
self->values.nv_per_profile_editable = 1; /* Defaults to editable profiles */
self->values.nv_core_clock_mhz_offset = -1; self->values.nv_core_clock_mhz_offset = -1;
self->values.nv_mem_clock_mhz_offset = -1; self->values.nv_mem_clock_mhz_offset = -1;
self->values.script_timeout = 10; /* Default to 10 seconds for scripts */ self->values.script_timeout = 10; /* Default to 10 seconds for scripts */
@@ -479,7 +483,7 @@ GameModeConfig *config_create(void)
} }
/* /*
* Initialise the config * Initialize the config
*/ */
void config_init(GameModeConfig *self) void config_init(GameModeConfig *self)
{ {
@@ -827,6 +831,7 @@ DEFINE_CONFIG_GET(gpu_device)
DEFINE_CONFIG_GET(nv_core_clock_mhz_offset) DEFINE_CONFIG_GET(nv_core_clock_mhz_offset)
DEFINE_CONFIG_GET(nv_mem_clock_mhz_offset) DEFINE_CONFIG_GET(nv_mem_clock_mhz_offset)
DEFINE_CONFIG_GET(nv_powermizer_mode) DEFINE_CONFIG_GET(nv_powermizer_mode)
DEFINE_CONFIG_GET(nv_per_profile_editable)
void config_get_amd_performance_level(GameModeConfig *self, char value[CONFIG_VALUE_MAX]) void config_get_amd_performance_level(GameModeConfig *self, char value[CONFIG_VALUE_MAX])
{ {

View File

@@ -119,6 +119,7 @@ long config_get_gpu_device(GameModeConfig *self);
long config_get_nv_core_clock_mhz_offset(GameModeConfig *self); long config_get_nv_core_clock_mhz_offset(GameModeConfig *self);
long config_get_nv_mem_clock_mhz_offset(GameModeConfig *self); long config_get_nv_mem_clock_mhz_offset(GameModeConfig *self);
long config_get_nv_powermizer_mode(GameModeConfig *self); long config_get_nv_powermizer_mode(GameModeConfig *self);
long config_get_nv_per_profile_editable(GameModeConfig *self);
void config_get_amd_performance_level(GameModeConfig *self, char value[CONFIG_VALUE_MAX]); void config_get_amd_performance_level(GameModeConfig *self, char value[CONFIG_VALUE_MAX]);
/* /*

View File

@@ -96,6 +96,7 @@ int game_mode_initialise_gpu(GameModeConfig *config, GameModeGPUInfo **info)
new_info->nv_core = config_get_nv_core_clock_mhz_offset(config); new_info->nv_core = config_get_nv_core_clock_mhz_offset(config);
new_info->nv_mem = config_get_nv_mem_clock_mhz_offset(config); new_info->nv_mem = config_get_nv_mem_clock_mhz_offset(config);
new_info->nv_powermizer_mode = config_get_nv_powermizer_mode(config); new_info->nv_powermizer_mode = config_get_nv_powermizer_mode(config);
new_info->nv_per_profile_editable = config_get_nv_per_profile_editable(config);
/* Reject values over some guessed values /* Reject values over some guessed values
* If a user wants to go into very unsafe levels they can recompile * If a user wants to go into very unsafe levels they can recompile
@@ -164,6 +165,8 @@ int game_mode_apply_gpu(const GameModeGPUInfo *info)
snprintf(nv_mem, 8, "%ld", info->nv_mem); snprintf(nv_mem, 8, "%ld", info->nv_mem);
char nv_powermizer_mode[4]; char nv_powermizer_mode[4];
snprintf(nv_powermizer_mode, 4, "%ld", info->nv_powermizer_mode); snprintf(nv_powermizer_mode, 4, "%ld", info->nv_powermizer_mode);
char nv_per_profile_editable[4];
snprintf(nv_per_profile_editable, 4, "%ld", info->nv_per_profile_editable);
// Set up our command line to pass to gpuclockctl // Set up our command line to pass to gpuclockctl
const char *const exec_args[] = { const char *const exec_args[] = {
@@ -174,6 +177,7 @@ int game_mode_apply_gpu(const GameModeGPUInfo *info)
info->vendor == Vendor_NVIDIA ? nv_core : info->amd_performance_level, info->vendor == Vendor_NVIDIA ? nv_core : info->amd_performance_level,
info->vendor == Vendor_NVIDIA ? nv_mem : NULL, /* Only use this if Nvidia */ info->vendor == Vendor_NVIDIA ? nv_mem : NULL, /* Only use this if Nvidia */
info->vendor == Vendor_NVIDIA ? nv_powermizer_mode : NULL, /* Only use this if Nvidia */ info->vendor == Vendor_NVIDIA ? nv_powermizer_mode : NULL, /* Only use this if Nvidia */
info->vendor == Vendor_NVIDIA ? nv_per_profile_editable : NULL, /* Only use this if Nvidia */
NULL, NULL,
}; };
@@ -192,7 +196,9 @@ int game_mode_get_gpu(GameModeGPUInfo *info)
/* Generate the input strings */ /* Generate the input strings */
char device[4]; char device[4];
char profile_editable[4];
snprintf(device, 4, "%ld", info->device); snprintf(device, 4, "%ld", info->device);
snprintf(profile_editable, 4, "%ld", info->nv_per_profile_editable);
// Set up our command line to pass to gpuclockctl // Set up our command line to pass to gpuclockctl
// This doesn't need pkexec as get does not need elevated perms // This doesn't need pkexec as get does not need elevated perms
@@ -200,6 +206,7 @@ int game_mode_get_gpu(GameModeGPUInfo *info)
LIBEXECDIR "/gpuclockctl", LIBEXECDIR "/gpuclockctl",
device, device,
"get", "get",
profile_editable, //TODO:refactor
NULL, NULL,
}; };
@@ -228,4 +235,4 @@ int game_mode_get_gpu(GameModeGPUInfo *info)
} }
return 0; return 0;
} }

View File

@@ -79,6 +79,10 @@ disable_splitlock=1
;nv_core_clock_mhz_offset=0 ;nv_core_clock_mhz_offset=0
;nv_mem_clock_mhz_offset=0 ;nv_mem_clock_mhz_offset=0
; Whether the GPU supports per-profile editable options for core and memory clock offsets.
; NOTE: if thi is set to 0 (AllPerformanceLevels) then nv_powermizer_mode must be set to 0
;nv_per_profile_editable = 1
; AMD specific settings ; AMD specific settings
; Requires a relatively up to date AMDGPU kernel module ; Requires a relatively up to date AMDGPU kernel module
; See: https://dri.freedesktop.org/docs/drm/gpu/amdgpu.html#gpu-power-thermal-controls-and-monitoring ; See: https://dri.freedesktop.org/docs/drm/gpu/amdgpu.html#gpu-power-thermal-controls-and-monitoring

View File

@@ -46,6 +46,7 @@ POSSIBILITY OF SUCH DAMAGE.
#define NV_PCIDEVICE_ATTRIBUTE "PCIDevice" #define NV_PCIDEVICE_ATTRIBUTE "PCIDevice"
#define NV_ATTRIBUTE_FORMAT "[gpu:%ld]/%s" #define NV_ATTRIBUTE_FORMAT "[gpu:%ld]/%s"
#define NV_PERF_LEVEL_FORMAT "[%ld]" #define NV_PERF_LEVEL_FORMAT "[%ld]"
#define NV_ALL_PERF_LEVELS "AllPerformanceLevels"
#define NV_ARG_MAX 128 #define NV_ARG_MAX 128
/* AMD constants */ /* AMD constants */
@@ -183,49 +184,98 @@ static int get_gpu_state_nv(struct GameModeGPUInfo *info)
const char *attr; const char *attr;
char *end; char *end;
/* Get the GPUGraphicsClockOffset parameter */ if (info->nv_per_profile_editable == 1) {
snprintf(arg, /* Get the GPUGraphicsClockOffset parameter */
NV_ARG_MAX, snprintf(arg,
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT, NV_ARG_MAX,
info->device, NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT,
NV_CORE_OFFSET_ATTRIBUTE, info->device,
perf_level); NV_CORE_OFFSET_ATTRIBUTE,
if ((attr = get_nv_attr(arg)) == NULL) { perf_level);
return -1;
attr = get_nv_attr(arg); // Should declaration be joined with assigment? :S
if (attr == NULL || attr[0] == '\0') {
return -1;
}
info->nv_core = strtol(attr, &end, 10);
if (end == attr) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, attr);
return -1;
}
/* Get the GPUMemoryTransferRateOffset parameter */
snprintf(arg,
NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT,
info->device,
NV_MEM_OFFSET_ATTRIBUTE,
perf_level);
attr = get_nv_attr(arg); // Should declaration be joined with assigment? :S
if (attr == NULL || attr[0] == '\0') {
return -1;
}
info->nv_mem = strtol(attr, &end, 10);
if (end == attr) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, attr);
return -1;
}
/* Get the GPUPowerMizerMode parameter */
snprintf(arg, NV_ARG_MAX, NV_ATTRIBUTE_FORMAT, info->device, NV_POWERMIZER_MODE_ATTRIBUTE);
if ((attr = get_nv_attr(arg)) == NULL) {
return -1;
}
info->nv_powermizer_mode = strtol(attr, &end, 10);
if (end == attr) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, attr);
return -1;
}
} }
info->nv_core = strtol(attr, &end, 10); else if (info->nv_per_profile_editable == 0) {
if (end == attr) { /* Get the GPUGraphicsClockOffset parameter */
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, attr); snprintf(arg,
return -1; NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT NV_ALL_PERF_LEVELS,
info->device,
NV_CORE_OFFSET_ATTRIBUTE);
attr = get_nv_attr(arg); // Should declaration be joined with assigment? :S
if (attr == NULL || attr[0] == '\0') {
return -1;
}
info->nv_core = strtol(attr, &end, 10);
if (end == attr) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, attr);
return -1;
}
/* Get the GPUMemoryTransferRateOffset parameter */
snprintf(arg,
NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT NV_ALL_PERF_LEVELS,
info->device,
NV_MEM_OFFSET_ATTRIBUTE);
attr = get_nv_attr(arg); // Should declaration be joined with assigment? :S
if (attr == NULL || attr[0] == '\0') {
return -1;
}
info->nv_mem = strtol(attr, &end, 10);
if (end == attr) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, attr);
return -1;
}
} }
/* Get the GPUMemoryTransferRateOffset parameter */ else {
snprintf(arg, LOG_ERROR("nv_per_profile_editable should be 0 or 1!");
NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT,
info->device,
NV_MEM_OFFSET_ATTRIBUTE,
perf_level);
if ((attr = get_nv_attr(arg)) == NULL) {
return -1;
}
info->nv_mem = strtol(attr, &end, 10);
if (end == attr) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, attr);
return -1;
}
/* Get the GPUPowerMizerMode parameter */
snprintf(arg, NV_ARG_MAX, NV_ATTRIBUTE_FORMAT, info->device, NV_POWERMIZER_MODE_ATTRIBUTE);
if ((attr = get_nv_attr(arg)) == NULL) {
return -1;
}
info->nv_powermizer_mode = strtol(attr, &end, 10);
if (end == attr) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, attr);
return -1; return -1;
} }
@@ -251,44 +301,74 @@ static int set_gpu_state_nv(struct GameModeGPUInfo *info)
char arg[NV_ARG_MAX] = { 0 }; char arg[NV_ARG_MAX] = { 0 };
/* Set the GPUGraphicsClockOffset parameter */ if (info->nv_per_profile_editable == 1) {
if (info->nv_core != -1) { /* Set the GPUGraphicsClockOffset parameter */
snprintf(arg, if (info->nv_core != -1) {
NV_ARG_MAX, snprintf(arg,
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT "=%ld", NV_ARG_MAX,
info->device, NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT "=%ld",
NV_CORE_OFFSET_ATTRIBUTE, info->device,
perf_level, NV_CORE_OFFSET_ATTRIBUTE,
info->nv_core); perf_level,
if (set_nv_attr(arg) != 0) { info->nv_core);
status = -1; if (set_nv_attr(arg) != 0) {
status = -1;
}
}
/* Set the GPUMemoryTransferRateOffset parameter */
if (info->nv_mem != -1) {
snprintf(arg,
NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT "=%ld",
info->device,
NV_MEM_OFFSET_ATTRIBUTE,
perf_level,
info->nv_mem);
if (set_nv_attr(arg) != 0) {
status = -1;
}
}
/* Set the GPUPowerMizerMode parameter if requested */
if (info->nv_powermizer_mode != -1) {
snprintf(arg,
NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT "=%ld",
info->device,
NV_POWERMIZER_MODE_ATTRIBUTE,
info->nv_powermizer_mode);
if (set_nv_attr(arg) != 0) {
status = -1;
}
} }
} }
/* Set the GPUMemoryTransferRateOffset parameter */ else if (info->nv_per_profile_editable == 0) {
if (info->nv_mem != -1) { /* Set the GPUGraphicsClockOffset parameter */
snprintf(arg, if (info->nv_core != -1) {
NV_ARG_MAX, snprintf(arg,
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT "=%ld", NV_ARG_MAX,
info->device, NV_ATTRIBUTE_FORMAT NV_ALL_PERF_LEVELS "=%ld",
NV_MEM_OFFSET_ATTRIBUTE, info->device,
perf_level, NV_CORE_OFFSET_ATTRIBUTE,
info->nv_mem); info->nv_core);
if (set_nv_attr(arg) != 0) { if (set_nv_attr(arg) != 0) {
status = -1; status = -1;
}
} }
}
/* Set the GPUPowerMizerMode parameter if requested */ /* Set the GPUMemoryTransferRateOffset parameter */
if (info->nv_powermizer_mode != -1) { if (info->nv_mem != -1) {
snprintf(arg, snprintf(arg,
NV_ARG_MAX, NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT "=%ld", NV_ATTRIBUTE_FORMAT NV_ALL_PERF_LEVELS "=%ld",
info->device, info->device,
NV_POWERMIZER_MODE_ATTRIBUTE, NV_MEM_OFFSET_ATTRIBUTE,
info->nv_powermizer_mode); info->nv_mem);
if (set_nv_attr(arg) != 0) { if (set_nv_attr(arg) != 0) {
status = -1; status = -1;
}
} }
} }
@@ -425,11 +505,18 @@ int main(int argc, char *argv[])
struct GameModeGPUInfo info; struct GameModeGPUInfo info;
memset(&info, 0, sizeof(info)); memset(&info, 0, sizeof(info));
if (argc == 3 && strncmp(argv[2], "get", 3) == 0) { if (argc >= 3 && strncmp(argv[2], "get", 3) == 0) {
/* Get and verify the vendor and device */ /* Get and verify the vendor and device */
info.device = get_device(argv[1]); info.device = get_device(argv[1]);
info.vendor = gamemode_get_gpu_vendor(info.device); info.vendor = gamemode_get_gpu_vendor(info.device);
/* Check for editable profiles argument */
if (argc == 3) {
info.nv_per_profile_editable = 1; /* If not specified, default to editable profiles */
} else if (argc == 4) {
info.nv_per_profile_editable = get_generic_value(argv[3]);
}
/* Fetch the state and print it out */ /* Fetch the state and print it out */
switch (info.vendor) { switch (info.vendor) {
case Vendor_NVIDIA: case Vendor_NVIDIA:
@@ -469,9 +556,15 @@ int main(int argc, char *argv[])
info.device = get_gpu_index_id_nv(&info); info.device = get_gpu_index_id_nv(&info);
/* Optional */ /* Optional */
/* If no per profile editable is defined, default to 1 */
info.nv_powermizer_mode = -1; info.nv_powermizer_mode = -1;
if (argc >= 6) info.nv_per_profile_editable = 1;
if (argc >= 6) {
info.nv_powermizer_mode = get_generic_value(argv[5]); info.nv_powermizer_mode = get_generic_value(argv[5]);
if (argc == 7) {
info.nv_per_profile_editable = get_generic_value(argv[6]);
}
}
return set_gpu_state_nv(&info); return set_gpu_state_nv(&info);
break; break;
@@ -495,4 +588,4 @@ int main(int argc, char *argv[])
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }