فهرست منبع

Support setting the platform profile

The platform profile lives in /sys/firmware/acpi/platform_profile. The
desiredprof and defaultprof options correspond to the values for the
platform profile set when gamescope begins and ends, respectively.

HACK: The platform profile may restrict what values the governor can
take, so we choose to set the governor before the platform profile in
order to store the correct default governor, and restore the platform
profile before the governor. This is done to maximize correctness after
restoration, but it can cause issues if the previous platform profile
restricts the governor.

TODO: Save all the state we care about before any of it is changed. In
thsi case, we should set (and restore) the platform profile before the
governor.
MithicSpirit 1 ماه پیش
والد
کامیت
e75f2c3942

+ 78 - 0
common/common-profile.c

@@ -0,0 +1,78 @@
+/*
+
+Copyright (c) 2025, MithicSpirit
+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-profile.h"
+#include "common-logging.h"
+
+/**
+ * Path for platform profile
+ */
+const char *profile_path = "/sys/firmware/acpi/platform_profile";
+
+/**
+ * Return the current platform profile state
+ */
+const char *get_profile_state(void)
+{
+	/* Persistent profile state */
+	static char profile[64] = { 0 };
+	memset(profile, 0, sizeof(profile));
+
+	FILE *f = fopen(profile_path, "r");
+	if (!f) {
+		LOG_ERROR("Failed to open file for read %s\n", profile_path);
+		return "none";
+	}
+
+	/* Grab the file length */
+	fseek(f, 0, SEEK_END);
+	long length = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	if (length == -1) {
+		LOG_ERROR("Failed to seek file %s\n", profile_path);
+	} else {
+		char contents[length + 1];
+
+		if (fread(contents, 1, (size_t)length, f) > 0) {
+			strtok(contents, "\n");
+			strncpy(profile, contents, sizeof(profile) - 1);
+		} else {
+			LOG_ERROR("Failed to read contents of %s\n", profile_path);
+		}
+	}
+
+	fclose(f);
+
+	return profile;
+}

+ 44 - 0
common/common-profile.h

@@ -0,0 +1,44 @@
+/*
+
+Copyright (c) 2025, MithicSpirit
+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 <linux/limits.h>
+
+/**
+ * Path for platform profile
+ */
+extern const char *profile_path;
+
+/**
+ * Get the current platform profile state
+ */
+const char *get_profile_state(void);

+ 1 - 0
common/meson.build

@@ -2,6 +2,7 @@
 common_sources = [
     'common-logging.c',
     'common-governors.c',
+    'common-profile.c',
     'common-external.c',
     'common-helpers.c',
     'common-gpu.c',

+ 24 - 1
daemon/gamemode-config.c

@@ -88,6 +88,9 @@ struct GameModeConfig {
 		char defaultgov[CONFIG_VALUE_MAX];
 		char desiredgov[CONFIG_VALUE_MAX];
 
+		char defaultprof[CONFIG_VALUE_MAX];
+		char desiredprof[CONFIG_VALUE_MAX];
+
 		char igpu_desiredgov[CONFIG_VALUE_MAX];
 		float igpu_power_threshold;
 
@@ -265,6 +268,10 @@ static int inih_handler(void *user, const char *section, const char *name, const
 			valid = get_string_value(value, self->values.defaultgov);
 		} else if (strcmp(name, "desiredgov") == 0) {
 			valid = get_string_value(value, self->values.desiredgov);
+		} else if (strcmp(name, "defaultprof") == 0) {
+			valid = get_string_value(value, self->values.defaultprof);
+		} else if (strcmp(name, "desiredprof") == 0) {
+			valid = get_string_value(value, self->values.desiredprof);
 		} else if (strcmp(name, "igpu_desiredgov") == 0) {
 			valid = get_string_value(value, self->values.igpu_desiredgov);
 		} else if (strcmp(name, "igpu_power_threshold") == 0) {
@@ -691,13 +698,29 @@ void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALU
 }
 
 /*
- * Get the chosen desired governor
+ * Get the chosen desired platform profile
  */
 void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX])
 {
 	memcpy_locked_config(self, governor, self->values.desiredgov, sizeof(self->values.desiredgov));
 }
 
+/*
+ * Get the chosen default platform profile
+ */
+void config_get_default_profile(GameModeConfig *self, char profile[CONFIG_VALUE_MAX])
+{
+	memcpy_locked_config(self, profile, self->values.defaultprof, sizeof(self->values.defaultprof));
+}
+
+/*
+ * Get the chosen desired governor
+ */
+void config_get_desired_profile(GameModeConfig *self, char profile[CONFIG_VALUE_MAX])
+{
+	memcpy_locked_config(self, profile, self->values.desiredprof, sizeof(self->values.desiredprof));
+}
+
 /*
  * Get the chosen iGPU desired governor
  */

+ 2 - 0
daemon/gamemode-config.h

@@ -102,6 +102,8 @@ bool config_get_inhibit_screensaver(GameModeConfig *self);
 long config_get_script_timeout(GameModeConfig *self);
 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_default_profile(GameModeConfig *self, char profile[CONFIG_VALUE_MAX]);
+void config_get_desired_profile(GameModeConfig *self, char profile[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]);

+ 66 - 0
daemon/gamemode-context.c

@@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE.
 #include "common-helpers.h"
 #include "common-logging.h"
 #include "common-power.h"
+#include "common-profile.h"
 
 #include "gamemode.h"
 #include "gamemode-config.h"
@@ -71,6 +72,11 @@ enum GameModeGovernor {
 	GAME_MODE_GOVERNOR_IGPU_DESIRED,
 };
 
+enum GameModeProfile {
+	GAME_MODE_PROFILE_DEFAULT,
+	GAME_MODE_PROFILE_DESIRED,
+};
+
 struct GameModeContext {
 	pthread_rwlock_t rwlock; /**<Guard access to the client list */
 	_Atomic int refcount;    /**<Allow cycling the game mode */
@@ -82,6 +88,9 @@ struct GameModeContext {
 
 	enum GameModeGovernor current_govenor;
 
+	char initial_profile[64];
+	enum GameModeProfile current_profile;
+
 	struct GameModeGPUInfo *stored_gpu; /**<Stored GPU info for the current GPU */
 	struct GameModeGPUInfo *target_gpu; /**<Target GPU info for the current GPU */
 
@@ -319,6 +328,59 @@ static int game_mode_set_governor(GameModeContext *self, enum GameModeGovernor g
 	return 0;
 }
 
+static int game_mode_set_profile(GameModeContext *self, enum GameModeProfile prof)
+{
+	if (self->current_profile == prof) {
+		return 0;
+	}
+
+	if (self->current_profile == GAME_MODE_PROFILE_DEFAULT) {
+		/* Read the initial platform profile so we can revert it correctly */
+		const char *initial_state = get_profile_state();
+		if (initial_state == NULL) {
+			return 0;
+		}
+
+		/* store the initial platform profile */
+		strncpy(self->initial_profile, initial_state, sizeof(self->initial_profile) - 1);
+		self->initial_profile[sizeof(self->initial_profile) - 1] = '\0';
+		LOG_MSG("platform profile was initially set to [%s]\n", initial_state);
+	}
+
+	const char *prof_str = NULL;
+	char prof_config_str[CONFIG_VALUE_MAX] = { 0 };
+	switch (prof) {
+	case GAME_MODE_PROFILE_DEFAULT:
+		config_get_default_profile(self->config, prof_config_str);
+		prof_str = prof_config_str[0] != '\0' ? prof_config_str : self->initial_profile;
+		break;
+
+	case GAME_MODE_PROFILE_DESIRED:
+		config_get_desired_profile(self->config, prof_config_str);
+		prof_str = prof_config_str[0] != '\0' ? prof_config_str : "performance";
+		break;
+
+	default:
+		assert(!"Invalid platform profile requested");
+	}
+
+	const char *const exec_args[] = {
+		"pkexec", LIBEXECDIR "/platprofctl", "set", prof_str, NULL,
+	};
+
+	LOG_MSG("Requesting update of platform profile to %s\n", prof_str);
+	int ret = run_external_process(exec_args, NULL, -1);
+	if (ret != 0) {
+		LOG_ERROR("Failed to update platform profile\n");
+		return ret;
+	}
+
+	/* Update the current govenor only if we succeed at setting govenors. */
+	self->current_profile = prof;
+
+	return 0;
+}
+
 static void game_mode_enable_igpu_optimization(GameModeContext *self)
 {
 	float threshold = config_get_igpu_power_threshold(self->config);
@@ -424,6 +486,8 @@ static void game_mode_context_enter(GameModeContext *self)
 		game_mode_enable_igpu_optimization(self);
 	}
 
+	game_mode_set_profile(self, GAME_MODE_PROFILE_DESIRED);
+
 	/* Inhibit the screensaver */
 	if (config_get_inhibit_screensaver(self->config)) {
 		game_mode_destroy_idle_inhibitor(self->idle_inhibitor);
@@ -471,6 +535,8 @@ static void game_mode_context_leave(GameModeContext *self)
 
 	game_mode_disable_splitlock(self, false);
 
+	game_mode_set_profile(self, GAME_MODE_PROFILE_DEFAULT);
+
 	game_mode_set_governor(self, GAME_MODE_GOVERNOR_DEFAULT);
 
 	game_mode_disable_igpu_optimization(self);

+ 12 - 0
data/polkit/actions/com.feralinteractive.GameMode.policy.in

@@ -58,4 +58,16 @@
     <annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/procsysctl</annotate>
     <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
   </action>
+
+  <action id="com.feralinteractive.GameMode.profile-helper">
+    <description>Modify the platform profile</description>
+    <message>Authentication is required to modify platform profile</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@/platprofctl</annotate>
+    <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
+  </action>
 </policyconfig>

+ 2 - 1
data/polkit/rules.d/gamemode.rules.in

@@ -6,7 +6,8 @@ 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.procsys-helper" ||
+         action.id == "com.feralinteractive.GameMode.profile-helper") &&
         subject.isInGroup("@GAMEMODE_PRIVILEGED_GROUP@"))
     {
         return polkit.Result.YES;

+ 5 - 0
example/gamemode.ini

@@ -7,6 +7,11 @@ desiredgov=performance
 ; The default governor is used when leaving GameMode instead of restoring the original value
 ;defaultgov=powersave
 
+; The desired platform profile is used when entering GameMode instead of "performance"
+desiredprof=performance
+; The default platform profile is used when leaving GameMode instead of restoring the original value
+;defaultgov=low-power
+
 ; 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.

+ 15 - 0
util/meson.build

@@ -58,3 +58,18 @@ procsysctl = executable(
     install: true,
     install_dir: path_libexecdir,
 )
+
+# Small target util to get and set platform profile
+platprofctl_sources = [
+    'platprofctl.c',
+]
+
+platprofctl = executable(
+    'platprofctl',
+    sources: platprofctl_sources,
+    dependencies: [
+        link_daemon_common,
+    ],
+    install: true,
+    install_dir: path_libexecdir,
+)

+ 84 - 0
util/platprofctl.c

@@ -0,0 +1,84 @@
+/*
+
+Copyright (c) 2025, MithicSpirit
+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 "common-profile.h"
+
+#include <unistd.h>
+
+/**
+ * Sets platform profile to a value
+ */
+static int set_profile_state(const char *value)
+{
+	int retval = EXIT_SUCCESS;
+
+	FILE *f = fopen(profile_path, "w");
+	if (!f) {
+		LOG_ERROR("Failed to open file for write %s\n", profile_path);
+		retval = EXIT_FAILURE;
+	}
+
+	if (fprintf(f, "%s\n", value) < 0) {
+		LOG_ERROR("Failed to set platform profile to %s: %s", value, strerror(errno));
+		retval = EXIT_FAILURE;
+	}
+	fclose(f);
+
+	return retval;
+}
+
+/**
+ * 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_profile_state());
+	} else if (argc == 3 && strncmp(argv[1], "set", 3) == 0) {
+		const char *value = argv[2];
+
+		/* Must be root to set the state */
+		if (geteuid() != 0) {
+			LOG_ERROR("This program must be run as root\n");
+			return EXIT_FAILURE;
+		}
+
+		return set_profile_state(value);
+	} else {
+		fprintf(stderr, "usage: platprofctl [get] [set VALUE]\n");
+		return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}