Merge pull request #179 from jekstrand/igpu

Add an option for using a different governor for integrated GPUs
This commit is contained in:
afayaz-feral 2020-01-10 10:12:26 +00:00 committed by GitHub
commit 1576c2b39e
8 changed files with 459 additions and 42 deletions

View File

@ -40,5 +40,10 @@ set -x
sudo ninja install sudo ninja install
# Restart polkit so we don't get pop-ups whenever we pkexec
if systemctl list-unit-files |grep -q polkit.service; then
sudo systemctl try-restart polkit
fi
# Reload systemd configuration so that it picks up the new service. # Reload systemd configuration so that it picks up the new service.
systemctl --user daemon-reload systemctl --user daemon-reload

165
common/common-power.c Normal file
View File

@ -0,0 +1,165 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2019, Intel Corporation
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-power.h"
#include "common-logging.h"
#include <assert.h>
#include <ctype.h>
#include <glob.h>
#include <linux/limits.h>
#include <stdio.h>
#include <string.h>
static bool read_file_in_dir(const char *dir, const char *file, char *dest, size_t n)
{
char path[PATH_MAX];
int ret = snprintf(path, sizeof(path), "%s/%s", dir, file);
if (ret < 0 || ret >= (int)sizeof(path)) {
LOG_ERROR("Path length overrun");
return false;
}
FILE *f = fopen(path, "r");
if (!f) {
LOG_ERROR("Failed to open file for read %s\n", path);
return false;
}
size_t read = fread(dest, 1, n, f);
/* Close before we do any error checking */
fclose(f);
if (read <= 0) {
LOG_ERROR("Failed to read contents of %s: (%s)\n", path, strerror(errno));
return false;
}
if (read >= n) {
LOG_ERROR("File contained more data than expected %s\n", path);
return false;
}
/* Ensure we're null terminated */
dest[read] = '\0';
/* Trim whitespace off the end */
while (read > 0 && isspace(dest[read - 1])) {
dest[read - 1] = '\0';
read--;
}
return true;
}
static bool get_energy_uj(const char *rapl_name, uint32_t *energy_uj)
{
glob_t glo = { 0 };
static const char *path = "/sys/class/powercap/intel-rapl/intel-rapl:0/intel-rapl:0:*";
/* Assert some sanity on this glob */
if (glob(path, GLOB_NOSORT, NULL, &glo) != 0) {
LOG_ERROR("glob failed for RAPL paths: (%s)\n", strerror(errno));
return false;
}
/* If the glob doesn't find anything, this most likely means we don't
* have an Intel CPU or we have a kernel which does not support RAPL on
* our CPU.
*/
if (glo.gl_pathc < 1) {
LOG_ONCE(MSG,
"Intel RAPL interface not found in sysfs. "
"This is only problematic if you expected Intel iGPU "
"power threshold optimization.");
globfree(&glo);
return false;
}
/* Walk the glob set */
for (size_t i = 0; i < glo.gl_pathc; i++) {
char name[32];
if (!read_file_in_dir(glo.gl_pathv[i], "name", name, sizeof(name))) {
return false;
}
/* We're searching for the directory where the file named "name"
* contains the contents rapl_name. */
if (strncmp(name, rapl_name, sizeof(name)) != 0) {
continue;
}
char energy_uj_str[32];
if (!read_file_in_dir(glo.gl_pathv[i], "energy_uj", energy_uj_str, sizeof(energy_uj_str))) {
return false;
}
char *end = NULL;
long long energy_uj_ll = strtoll(energy_uj_str, &end, 10);
if (end == energy_uj_str) {
LOG_ERROR("Invalid energy_uj contents: %s\n", energy_uj_str);
return false;
}
if (energy_uj_ll < 0) {
LOG_ERROR("Value of energy_uj is out of expected bounds: %lld\n", energy_uj_ll);
return false;
}
/* Go ahead and clamp to 32 bits. We assume 32 bits later when
* taking deltas and wrapping at 32 bits is exactly what the Linux
* kernel's turbostat utility does so it's probably right.
*/
*energy_uj = (uint32_t)energy_uj_ll;
return true;
}
/* If we got here then the CPU and Kernel support RAPL and all our file
* access has succeeded but we failed to find an entry with the right
* name. This most likely means we're asking for "uncore" but are on a
* machine that doesn't have an integrated GPU.
*/
return false;
}
bool get_cpu_energy_uj(uint32_t *energy_uj)
{
return get_energy_uj("core", energy_uj);
}
bool get_igpu_energy_uj(uint32_t *energy_uj)
{
return get_energy_uj("uncore", energy_uj);
}

45
common/common-power.h Normal file
View File

@ -0,0 +1,45 @@
/*
Copyright (c) 2017-2019, Feral Interactive
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.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
/**
* Get the amount of energy used to date by the CPU in microjoules
*/
bool get_cpu_energy_uj(uint32_t *energy_uj);
/**
* Get the amount of energy used to date by the integrated GPU in microjoules
*/
bool get_igpu_energy_uj(uint32_t *energy_uj);

View File

@ -6,6 +6,7 @@ common_sources = [
'common-helpers.c', 'common-helpers.c',
'common-gpu.c', 'common-gpu.c',
'common-pidfds.c', 'common-pidfds.c',
'common-power.c',
] ]
daemon_common = static_library( daemon_common = static_library(

View File

@ -39,6 +39,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "ini.h" #include "ini.h"
#include <dirent.h> #include <dirent.h>
#include <math.h>
#include <pthread.h> #include <pthread.h>
#include <pwd.h> #include <pwd.h>
#include <sys/inotify.h> #include <sys/inotify.h>
@ -50,6 +51,8 @@ POSSIBILITY OF SUCH DAMAGE.
/* Default value for the reaper frequency */ /* Default value for the reaper frequency */
#define DEFAULT_REAPER_FREQ 5 #define DEFAULT_REAPER_FREQ 5
#define DEFAULT_IGPU_POWER_THRESHOLD 0.3f
/* Helper macro for defining the config variable getter */ /* Helper macro for defining the config variable getter */
#define DEFINE_CONFIG_GET(name) \ #define DEFINE_CONFIG_GET(name) \
long config_get_##name(GameModeConfig *self) \ long config_get_##name(GameModeConfig *self) \
@ -82,6 +85,9 @@ struct GameModeConfig {
char defaultgov[CONFIG_VALUE_MAX]; char defaultgov[CONFIG_VALUE_MAX];
char desiredgov[CONFIG_VALUE_MAX]; char desiredgov[CONFIG_VALUE_MAX];
char igpu_desiredgov[CONFIG_VALUE_MAX];
float igpu_power_threshold;
char softrealtime[CONFIG_VALUE_MAX]; char softrealtime[CONFIG_VALUE_MAX];
long renice; long renice;
@ -179,6 +185,27 @@ __attribute__((unused)) static bool get_long_value_hex(const char *value_name, c
return true; return true;
} }
/*
* Get a long value from a string
*/
static bool get_float_value(const char *value_name, const char *value, float *output)
{
char *end = NULL;
float config_value = strtof(value, &end);
if (errno == ERANGE) {
LOG_ERROR("Config: %s overflowed, given [%s]\n", value_name, value);
return false;
} else if (!(*value != '\0' && end && *end == '\0')) {
LOG_ERROR("Config: %s was invalid, given [%s]\n", value_name, value);
return false;
} else {
*output = config_value;
}
return true;
}
/** /**
* Simple strstr scheck * Simple strstr scheck
* Could be expanded for wildcard or regex * Could be expanded for wildcard or regex
@ -230,6 +257,10 @@ static int inih_handler(void *user, const char *section, const char *name, const
valid = get_string_value(value, self->values.defaultgov); valid = get_string_value(value, self->values.defaultgov);
} else if (strcmp(name, "desiredgov") == 0) { } else if (strcmp(name, "desiredgov") == 0) {
valid = get_string_value(value, self->values.desiredgov); valid = get_string_value(value, self->values.desiredgov);
} else if (strcmp(name, "igpu_desiredgov") == 0) {
valid = get_string_value(value, self->values.igpu_desiredgov);
} else if (strcmp(name, "igpu_power_threshold") == 0) {
valid = get_float_value(name, value, &self->values.igpu_power_threshold);
} else if (strcmp(name, "softrealtime") == 0) { } else if (strcmp(name, "softrealtime") == 0) {
valid = get_string_value(value, self->values.softrealtime); valid = get_string_value(value, self->values.softrealtime);
} else if (strcmp(name, "renice") == 0) { } else if (strcmp(name, "renice") == 0) {
@ -329,6 +360,7 @@ static void load_config_files(GameModeConfig *self)
memset(&self->values, 0, sizeof(self->values)); memset(&self->values, 0, sizeof(self->values));
/* Set some non-zero defaults */ /* Set some non-zero defaults */
self->values.igpu_power_threshold = DEFAULT_IGPU_POWER_THRESHOLD;
self->values.inhibit_screensaver = 1; /* Defaults to on */ self->values.inhibit_screensaver = 1; /* Defaults to on */
self->values.reaper_frequency = DEFAULT_REAPER_FREQ; self->values.reaper_frequency = DEFAULT_REAPER_FREQ;
self->values.gpu_device = -1; /* 0 is a valid device ID so use -1 to indicate no value */ self->values.gpu_device = -1; /* 0 is a valid device ID so use -1 to indicate no value */
@ -641,6 +673,35 @@ void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALU
memcpy_locked_config(self, governor, self->values.desiredgov, sizeof(self->values.desiredgov)); memcpy_locked_config(self, governor, self->values.desiredgov, sizeof(self->values.desiredgov));
} }
/*
* Get the chosen iGPU desired governor
*/
void config_get_igpu_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX])
{
memcpy_locked_config(self,
governor,
self->values.igpu_desiredgov,
sizeof(self->values.igpu_desiredgov));
}
/*
* Get the chosen iGPU power threshold
*/
float config_get_igpu_power_threshold(GameModeConfig *self)
{
float value = 0;
memcpy_locked_config(self, &value, &self->values.igpu_power_threshold, sizeof(float));
/* Validate the threshold value */
if (isnan(value) || value < 0) {
LOG_ONCE(ERROR,
"Configured iGPU power threshold value '%f' is invalid, ignoring iGPU default "
"governor.\n",
value);
value = FP_INFINITE;
}
return value;
}
/* /*
* Get the chosen soft realtime behavior * Get the chosen soft realtime behavior
*/ */

View File

@ -102,6 +102,8 @@ bool config_get_inhibit_screensaver(GameModeConfig *self);
long config_get_script_timeout(GameModeConfig *self); long config_get_script_timeout(GameModeConfig *self);
void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]); void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]); void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
void config_get_igpu_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
float config_get_igpu_power_threshold(GameModeConfig *self);
void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX]); void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX]);
long config_get_renice_value(GameModeConfig *self); long config_get_renice_value(GameModeConfig *self);
long config_get_ioprio_value(GameModeConfig *self); long config_get_ioprio_value(GameModeConfig *self);

View File

@ -35,6 +35,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "common-governors.h" #include "common-governors.h"
#include "common-helpers.h" #include "common-helpers.h"
#include "common-logging.h" #include "common-logging.h"
#include "common-power.h"
#include "gamemode.h" #include "gamemode.h"
#include "gamemode-config.h" #include "gamemode-config.h"
@ -63,6 +64,12 @@ struct GameModeClient {
time_t timestamp; /**<When was the client registered */ time_t timestamp; /**<When was the client registered */
}; };
enum GameModeGovernor {
GAME_MODE_GOVERNOR_DEFAULT,
GAME_MODE_GOVERNOR_DESIRED,
GAME_MODE_GOVERNOR_IGPU_DESIRED,
};
struct GameModeContext { struct GameModeContext {
pthread_rwlock_t rwlock; /**<Guard access to the client list */ pthread_rwlock_t rwlock; /**<Guard access to the client list */
_Atomic int refcount; /**<Allow cycling the game mode */ _Atomic int refcount; /**<Allow cycling the game mode */
@ -72,9 +79,15 @@ struct GameModeContext {
char initial_cpu_mode[64]; /**<Only updates when we can */ char initial_cpu_mode[64]; /**<Only updates when we can */
enum GameModeGovernor current_govenor;
struct GameModeGPUInfo *stored_gpu; /**<Stored GPU info for the current GPU */ struct GameModeGPUInfo *stored_gpu; /**<Stored GPU info for the current GPU */
struct GameModeGPUInfo *target_gpu; /**<Target GPU info for the current GPU */ struct GameModeGPUInfo *target_gpu; /**<Target GPU info for the current GPU */
bool igpu_optimization_enabled;
uint32_t last_cpu_energy_uj;
uint32_t last_igpu_energy_uj;
/* Reaper control */ /* Reaper control */
struct { struct {
pthread_t thread; pthread_t thread;
@ -126,6 +139,8 @@ void game_mode_context_init(GameModeContext *self)
self->config = config_create(); self->config = config_create();
config_init(self->config); config_init(self->config);
self->current_govenor = GAME_MODE_GOVERNOR_DEFAULT;
/* Initialise the current GPU info */ /* Initialise the current GPU info */
game_mode_initialise_gpu(self->config, &self->stored_gpu); game_mode_initialise_gpu(self->config, &self->stored_gpu);
game_mode_initialise_gpu(self->config, &self->target_gpu); game_mode_initialise_gpu(self->config, &self->target_gpu);
@ -178,6 +193,151 @@ void game_mode_context_destroy(GameModeContext *self)
pthread_rwlock_destroy(&self->rwlock); pthread_rwlock_destroy(&self->rwlock);
} }
static int game_mode_set_governor(GameModeContext *self, enum GameModeGovernor gov)
{
if (self->current_govenor == gov) {
return 0;
}
if (self->current_govenor == GAME_MODE_GOVERNOR_DEFAULT) {
/* Read the initial governor state so we can revert it correctly */
const char *initial_state = get_gov_state();
if (initial_state == NULL) {
return 0;
}
/* store the initial cpu governor mode */
strncpy(self->initial_cpu_mode, initial_state, sizeof(self->initial_cpu_mode) - 1);
self->initial_cpu_mode[sizeof(self->initial_cpu_mode) - 1] = '\0';
LOG_MSG("governor was initially set to [%s]\n", initial_state);
}
char *gov_str = NULL;
char gov_config_str[CONFIG_VALUE_MAX] = { 0 };
switch (gov) {
case GAME_MODE_GOVERNOR_DEFAULT:
config_get_default_governor(self->config, gov_config_str);
gov_str = gov_config_str[0] != '\0' ? gov_config_str : self->initial_cpu_mode;
break;
case GAME_MODE_GOVERNOR_DESIRED:
config_get_desired_governor(self->config, gov_config_str);
gov_str = gov_config_str[0] != '\0' ? gov_config_str : "performance";
break;
case GAME_MODE_GOVERNOR_IGPU_DESIRED:
config_get_igpu_desired_governor(self->config, gov_config_str);
gov_str = gov_config_str[0] != '\0' ? gov_config_str : "powersave";
break;
default:
assert(!"Invalid governor requested");
}
const char *const exec_args[] = {
"/usr/bin/pkexec", LIBEXECDIR "/cpugovctl", "set", gov_str, NULL,
};
LOG_MSG("Requesting update of governor policy to %s\n", gov_str);
int ret = run_external_process(exec_args, NULL, -1);
if (ret != 0) {
LOG_ERROR("Failed to update cpu governor policy\n");
return ret;
}
/* Update the current govenor only if we succeed at setting govenors. */
self->current_govenor = gov;
return 0;
}
static void game_mode_enable_igpu_optimization(GameModeContext *self)
{
float threshold = config_get_igpu_power_threshold(self->config);
/* There's no way the GPU is using 10000x the power. This lets us
* short-circuit if the config file specifies an invalid threshold
* and we want to disable the iGPU heuristic.
*/
if (threshold < 10000 && get_cpu_energy_uj(&self->last_cpu_energy_uj) &&
get_igpu_energy_uj(&self->last_igpu_energy_uj)) {
LOG_MSG(
"Successfully queried power data for the CPU and iGPU. "
"Enabling the integrated GPU optimization");
self->igpu_optimization_enabled = true;
}
}
static void game_mode_disable_igpu_optimization(GameModeContext *self)
{
self->igpu_optimization_enabled = false;
}
static void game_mode_check_igpu_energy(GameModeContext *self)
{
pthread_rwlock_wrlock(&self->rwlock);
/* We only care if we're not in the default governor */
if (self->current_govenor == GAME_MODE_GOVERNOR_DEFAULT)
goto unlock;
if (!self->igpu_optimization_enabled)
goto unlock;
uint32_t cpu_energy_uj, igpu_energy_uj;
if (!get_cpu_energy_uj(&cpu_energy_uj) || !get_igpu_energy_uj(&igpu_energy_uj)) {
/* We've already succeeded at getting power information once so
* failing here is possible but very unexpected. */
self->igpu_optimization_enabled = false;
LOG_ERROR("Failed to get CPU and iGPU power data\n");
goto unlock;
}
/* The values we query from RAPL are in units of microjoules of energy
* used since boot or since the last time the counter rolled over. You
* can get average power over some time window T by sampling before and
* after and doing the following calculation
*
* power_uw = (energy_uj_after - energy_uj_before) / seconds
*
* To get the power in Watts (rather than microwatts), you can simply
* divide by 1000000.
*
* Because we're only concerned with the ratio between the GPU and CPU
* power, we never bother dividing by 1000000 the length of time of the
* sampling window because that would just algebraically cancel out.
* Instead, we divide the GPU energy used in the window (difference of
* before and after) by the CPU energy used. It nicely provides the
* ratio of the averages and there are no instantaneous sampling
* problems.
*
* Overflow is possible here. However, that would simply mean that
* the HW counter has overflowed and us wrapping around is probably
* the right thing to do. Wrapping at 32 bits is exactly what the
* Linux kernel's turbostat utility does so it's probably right.
*/
uint32_t cpu_energy_delta_uj = cpu_energy_uj - self->last_cpu_energy_uj;
uint32_t igpu_energy_delta_uj = igpu_energy_uj - self->last_igpu_energy_uj;
self->last_cpu_energy_uj = cpu_energy_uj;
self->last_igpu_energy_uj = igpu_energy_uj;
if (cpu_energy_delta_uj == 0) {
LOG_ERROR("CPU reported no energy used\n");
goto unlock;
}
float threshold = config_get_igpu_power_threshold(self->config);
double ratio = (double)igpu_energy_delta_uj / (double)cpu_energy_delta_uj;
if (ratio > threshold) {
game_mode_set_governor(self, GAME_MODE_GOVERNOR_IGPU_DESIRED);
} else {
game_mode_set_governor(self, GAME_MODE_GOVERNOR_DESIRED);
}
unlock:
pthread_rwlock_unlock(&self->rwlock);
}
/** /**
* Pivot into game mode. * Pivot into game mode.
* *
@ -189,30 +349,11 @@ static void game_mode_context_enter(GameModeContext *self)
LOG_MSG("Entering Game Mode...\n"); LOG_MSG("Entering Game Mode...\n");
sd_notifyf(0, "STATUS=%sGameMode is now active.%s\n", "\x1B[1;32m", "\x1B[0m"); sd_notifyf(0, "STATUS=%sGameMode is now active.%s\n", "\x1B[1;32m", "\x1B[0m");
/* Read the initial governor state so we can revert it correctly */ if (game_mode_set_governor(self, GAME_MODE_GOVERNOR_DESIRED) == 0) {
const char *initial_state = get_gov_state(); /* We just switched to a non-default governor. Enable the iGPU
if (initial_state) { * optimization.
/* store the initial cpu governor mode */ */
strncpy(self->initial_cpu_mode, initial_state, sizeof(self->initial_cpu_mode) - 1); game_mode_enable_igpu_optimization(self);
self->initial_cpu_mode[sizeof(self->initial_cpu_mode) - 1] = '\0';
LOG_MSG("governor was initially set to [%s]\n", initial_state);
/* Choose the desired governor */
char desired[CONFIG_VALUE_MAX] = { 0 };
config_get_desired_governor(self->config, desired);
const char *desiredGov = desired[0] != '\0' ? desired : "performance";
const char *const exec_args[] = {
"/usr/bin/pkexec", LIBEXECDIR "/cpugovctl", "set", desiredGov, NULL,
};
LOG_MSG("Requesting update of governor policy to %s\n", desiredGov);
if (run_external_process(exec_args, NULL, -1) != 0) {
LOG_ERROR("Failed to update cpu governor policy\n");
/* if the set fails, clear the initial mode so we don't try and reset it back and fail
* again, presumably */
memset(self->initial_cpu_mode, 0, sizeof(self->initial_cpu_mode));
}
} }
/* Inhibit the screensaver */ /* Inhibit the screensaver */
@ -250,24 +391,9 @@ static void game_mode_context_leave(GameModeContext *self)
if (config_get_inhibit_screensaver(self->config)) if (config_get_inhibit_screensaver(self->config))
game_mode_inhibit_screensaver(false); game_mode_inhibit_screensaver(false);
/* Reset the governer state back to initial */ game_mode_set_governor(self, GAME_MODE_GOVERNOR_DEFAULT);
if (self->initial_cpu_mode[0] != '\0') {
/* Choose the governor to reset to, using the config to override */
char defaultgov[CONFIG_VALUE_MAX] = { 0 };
config_get_default_governor(self->config, defaultgov);
const char *gov_mode = defaultgov[0] != '\0' ? defaultgov : self->initial_cpu_mode;
const char *const exec_args[] = { game_mode_disable_igpu_optimization(self);
"/usr/bin/pkexec", LIBEXECDIR "/cpugovctl", "set", gov_mode, NULL,
};
LOG_MSG("Requesting update of governor policy to %s\n", gov_mode);
if (run_external_process(exec_args, NULL, -1) != 0) {
LOG_ERROR("Failed to update cpu governor policy\n");
}
memset(self->initial_cpu_mode, 0, sizeof(self->initial_cpu_mode));
}
char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
memset(scripts, 0, sizeof(scripts)); memset(scripts, 0, sizeof(scripts));
@ -801,6 +927,9 @@ static void *game_mode_context_reaper(void *userdata)
return NULL; return NULL;
} }
/* Check on the CPU/iGPU energy balance */
game_mode_check_igpu_energy(self);
/* Expire remaining entries */ /* Expire remaining entries */
game_mode_context_auto_expire(self); game_mode_context_auto_expire(self);

View File

@ -1,5 +1,5 @@
[general] [general]
; The reaper thread will check every 5 seconds for exited clients and for config file changes ; The reaper thread will check every 5 seconds for exited clients, for config file changes, and for the CPU/iGPU power balance
reaper_freq=5 reaper_freq=5
; The desired governor is used when entering GameMode instead of "performance" ; The desired governor is used when entering GameMode instead of "performance"
@ -7,6 +7,15 @@ desiredgov=performance
; The default governer is used when leaving GameMode instead of restoring the original value ; The default governer is used when leaving GameMode instead of restoring the original value
defaultgov=powersave defaultgov=powersave
; The iGPU desired governor is used when the integrated GPU is under heavy load
igpu_desiredgov=powersave
; Threshold to use to decide when the integrated GPU is under heavy load.
; This is a ratio of iGPU Watts / CPU Watts which is used to determine when the
; integraged GPU is under heavy enough load to justify switching to
; igpu_desiredgov. Set this to -1 to disable all iGPU checking and always
; use desiredgov for games.
igpu_power_threshold=0.3
; GameMode can change the scheduler policy to SCHED_ISO on kernels which support it (currently ; GameMode can change the scheduler policy to SCHED_ISO on kernels which support it (currently
; not supported by upstream kernels). Can be set to "auto", "on" or "off". "auto" will enable ; not supported by upstream kernels). Can be set to "auto", "on" or "off". "auto" will enable
; with 4 or more CPU cores. "on" will always enable. Defaults to "off". ; with 4 or more CPU cores. "on" will always enable. Defaults to "off".