mirror of
https://github.com/FeralInteractive/gamemode.git
synced 2025-06-06 23:57:22 +02:00
Merge pull request #179 from jekstrand/igpu
Add an option for using a different governor for integrated GPUs
This commit is contained in:
commit
1576c2b39e
@ -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
165
common/common-power.c
Normal 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
45
common/common-power.h
Normal 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);
|
@ -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(
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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".
|
||||||
|
Loading…
x
Reference in New Issue
Block a user