pax_global_header00006660000000000000000000000064146606160130014515gustar00rootroot0000000000000052 comment=715a1389b789e661a3996a90fbd2c0bf6c9cf6d2 gamemode-1.8.2/000077500000000000000000000000001466061601300133035ustar00rootroot00000000000000gamemode-1.8.2/.clang-format000066400000000000000000000044611466061601300156630ustar00rootroot00000000000000--- AccessModifierOffset: 0 AlignAfterOpenBracket: true AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: false AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false # AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: false BinPackArguments: false BinPackParameters: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Linux BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false # Too new for travis clang-format version # BreakStringLiterals: false ColumnLimit: 100 CommentPragmas: '\*\<' ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false ForEachMacros: [ ] SortIncludes: true # IncludeBlocksStyle changed to IncludeBlocks, between xenial and disco, so we can't use it for consistency # IncludeBlocks: Regroup IncludeCategories: - Regex: '^ #include #define IS_CPU_PARK 0 #define IS_CPU_PIN 1 /* Storage for CPU info*/ struct GameModeCPUInfo { size_t num_cpu; int park_or_pin; cpu_set_t *online; cpu_set_t *to_keep; }; /* parses a list of cpu cores in the format "a,b-c,d-e,f" */ char *parse_cpulist(char *cpulist, long *from, long *to); gamemode-1.8.2/common/common-external.c000066400000000000000000000122531466061601300200520ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include "common-external.h" #include "common-logging.h" #include #include static const int DEFAULT_TIMEOUT = 5; static int read_child_stdout(int pipe_fd, char buffer[EXTERNAL_BUFFER_MAX], int tsec) { fd_set fds; struct timeval timeout; int num_readable = 0; ssize_t buffer_bytes_read = 0; ssize_t just_read = 0; bool buffer_full = false; char discard_buffer[EXTERNAL_BUFFER_MAX]; /* Set up the timout */ timeout.tv_sec = tsec; timeout.tv_usec = 0; FD_ZERO(&fds); /* Wait for the child to finish up with a timout */ while (true) { FD_SET(pipe_fd, &fds); num_readable = select(pipe_fd + 1, &fds, NULL, NULL, &timeout); if (num_readable < 0) { if (errno == EINTR) { continue; } else { LOG_ERROR("sigtimedwait failed: %s\n", strerror(errno)); return -1; } } else if (num_readable == 0) { return -2; } if (!buffer_full) { just_read = read(pipe_fd, buffer + buffer_bytes_read, EXTERNAL_BUFFER_MAX - (size_t)buffer_bytes_read - 1); } else { just_read = read(pipe_fd, discard_buffer, EXTERNAL_BUFFER_MAX - 1); } if (just_read < 0) { return -1; } else if (just_read == 0) { // EOF encountered break; } if (!buffer_full) { buffer_bytes_read += just_read; if (buffer_bytes_read == EXTERNAL_BUFFER_MAX - 1) { // our buffer is exhausted, discard the rest // of the output buffer_full = true; } } } buffer[buffer_bytes_read] = 0; return 0; } /** * Call an external process */ int run_external_process(const char *const *exec_args, char buffer[EXTERNAL_BUFFER_MAX], int tsec) { pid_t p; int status = 0; int pipes[2]; int ret = 0; char internal[EXTERNAL_BUFFER_MAX] = { 0 }; if (pipe(pipes) == -1) { LOG_ERROR("Could not create pipe: %s!\n", strerror(errno)); return -1; } /* Set the default timeout */ if (tsec == -1) { tsec = DEFAULT_TIMEOUT; } if ((p = fork()) < 0) { close(pipes[0]); close(pipes[1]); LOG_ERROR("Failed to fork(): %s\n", strerror(errno)); return false; } else if (p == 0) { /* Send STDOUT to the pipe */ dup2(pipes[1], STDOUT_FILENO); close(pipes[0]); close(pipes[1]); /* Execute the command */ /* Note about cast: * The statement about argv[] and envp[] being constants is * included to make explicit to future writers of language * bindings that these objects are completely constant. * http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html */ if (execvp(exec_args[0], (char *const *)exec_args) != 0) { LOG_ERROR("Failed to execute external process: %s %s\n", exec_args[0], strerror(errno)); exit(EXIT_FAILURE); } // should never be reached abort(); } // close the write end of the pipe so we get signaled EOF once the // child exits close(pipes[1]); ret = read_child_stdout(pipes[0], internal, tsec); close(pipes[0]); if (ret != 0) { if (ret == -2) { LOG_ERROR("Child process timed out for %s, killing and returning\n", exec_args[0]); kill(p, SIGKILL); } else { LOG_ERROR("Failed to read from process %s: %s\n", exec_args[0], strerror(errno)); } if (buffer) { // make sure the buffer is a terminated empty string on error buffer[0] = 0; } } else if (buffer) { memcpy(buffer, internal, EXTERNAL_BUFFER_MAX); } if (waitpid(p, &status, 0) < 0) { LOG_ERROR("Failed to waitpid(%d): %s\n", (int)p, strerror(errno)); return -1; } /* i.e. sigsev */ if (!WIFEXITED(status)) { LOG_ERROR("Child process '%s' exited abnormally\n", exec_args[0]); } else if (WEXITSTATUS(status) != 0) { LOG_ERROR("External process failed with exit code %d\n", WEXITSTATUS(status)); LOG_ERROR("Output was: %s\n", internal); return -1; } return 0; } gamemode-1.8.2/common/common-external.h000066400000000000000000000032671466061601300200640ustar00rootroot00000000000000/* 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 #define EXTERNAL_BUFFER_MAX 1024 /* Run an external process and capture the return value */ int run_external_process(const char *const *exec_args, char buffer[EXTERNAL_BUFFER_MAX], int tsec); gamemode-1.8.2/common/common-governors.c000066400000000000000000000105371466061601300202570ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include "common-governors.h" #include "common-logging.h" #include #include /** * Discover all governers on the system. * * Located at /sys/devices/system/cpu/cpu(*)/cpufreq/scaling_governor */ int fetch_governors(char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH]) { glob_t glo = { 0 }; static const char *path = "/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor"; /* Assert some sanity on this glob */ if (glob(path, GLOB_NOSORT, NULL, &glo) != 0) { LOG_ERROR("glob failed for cpu governors: (%s)\n", strerror(errno)); return 0; } if (glo.gl_pathc < 1) { globfree(&glo); LOG_ERROR("no cpu governors found\n"); return 0; } int num_governors = 0; /* Walk the glob set */ for (size_t i = 0; i < glo.gl_pathc; i++) { if (i >= MAX_GOVERNORS) { break; } /* Get the real path to the file. * Traditionally cpufreq symlinks to a policy directory that can * be shared, so let's prevent duplicates. */ char fullpath[PATH_MAX] = { 0 }; const char *ptr = realpath(glo.gl_pathv[i], fullpath); if (fullpath != ptr) { continue; } /* Only add this governor if it is unique */ for (int j = 0; j < num_governors; j++) { if (strncmp(fullpath, governors[i], PATH_MAX) == 0) { continue; } } /* Copy this governor into the output set */ static_assert(MAX_GOVERNOR_LENGTH > PATH_MAX, "possible string truncation"); strncpy(governors[num_governors], fullpath, MAX_GOVERNOR_LENGTH); num_governors++; } globfree(&glo); return num_governors; } /** * Return the current governor state */ const char *get_gov_state(void) { /* Persistent governor state */ static char governor[64] = { 0 }; memset(governor, 0, sizeof(governor)); /* State for all governors */ char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH] = { { 0 } }; int num = fetch_governors(governors); /* Check the list */ for (int i = 0; i < num; i++) { const char *gov = governors[i]; FILE *f = fopen(gov, "r"); if (!f) { LOG_ERROR("Failed to open file for read %s\n", gov); continue; } /* 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", gov); } else { char contents[length]; if (fread(contents, 1, (size_t)length, f) > 0) { /* Files have a newline */ strtok(contents, "\n"); if (strlen(governor) > 0 && strncmp(governor, contents, 64) != 0) { /* Don't handle the mixed case, this shouldn't ever happen * But it is a clear sign we shouldn't carry on */ LOG_ERROR("Governors malformed: got \"%s\", expected \"%s\"", contents, governor); fclose(f); return "malformed"; } strncpy(governor, contents, sizeof(governor) - 1); } else { LOG_ERROR("Failed to read contents of %s\n", gov); } } fclose(f); } return governor; } gamemode-1.8.2/common/common-governors.h000066400000000000000000000034171466061601300202630ustar00rootroot00000000000000/* 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 #define MAX_GOVERNORS 128 #define MAX_GOVERNOR_LENGTH PATH_MAX + 1 /** * Grab all of the governors */ int fetch_governors(char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH]); /** * Get the current governor state */ const char *get_gov_state(void); gamemode-1.8.2/common/common-gpu.c000066400000000000000000000053141466061601300170230ustar00rootroot00000000000000/* 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. */ #include "common-gpu.h" #include "common-logging.h" /* Get the vendor for a device */ enum GPUVendor gamemode_get_gpu_vendor(long device) { enum GPUVendor vendor = Vendor_Invalid; /* Fill in GPU vendor */ char path[64] = { 0 }; if (snprintf(path, 64, "/sys/class/drm/card%ld/device/vendor", device) < 0) { LOG_ERROR("snprintf failed, will not apply gpu optimisations!\n"); return Vendor_Invalid; } FILE *file = fopen(path, "r"); if (!file) { LOG_ERROR("Couldn't open vendor file at %s, will not apply gpu optimisations!\n", path); return Vendor_Invalid; } char buff[64]; bool got_line = fgets(buff, 64, file) != NULL; fclose(file); if (got_line) { vendor = strtol(buff, NULL, 0); } else { LOG_ERROR("Couldn't read contents of file %s, will not apply optimisations!\n", path); return Vendor_Invalid; } /* verify GPU vendor */ if (!GPUVendorValid(vendor)) { LOG_ERROR("Unknown vendor value (0x%04x) found, cannot apply optimisations!\n", (unsigned int)vendor); LOG_ERROR("Known values are: 0x%04x (NVIDIA) 0x%04x (AMD) 0x%04x (Intel)\n", (unsigned int)Vendor_NVIDIA, (unsigned int)Vendor_AMD, (unsigned int)Vendor_Intel); return Vendor_Invalid; } return vendor; } gamemode-1.8.2/common/common-gpu.h000066400000000000000000000044241466061601300170310ustar00rootroot00000000000000/* 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 #define GPU_VALUE_MAX 256 /* Enums for GPU vendors */ enum GPUVendor { Vendor_Invalid = 0, Vendor_NVIDIA = 0x10de, Vendor_AMD = 0x1002, Vendor_Intel = 0x8086 }; #define GPUVendorValid(vendor) \ (vendor == Vendor_NVIDIA || vendor == Vendor_AMD || vendor == Vendor_Intel) /* Storage for GPU info*/ struct GameModeGPUInfo { long vendor; long device; /* path to device, ie. /sys/class/drm/card#/ */ long nv_core; /* Nvidia core clock */ long nv_mem; /* Nvidia mem clock */ long nv_powermizer_mode; /* NV Powermizer Mode */ char amd_performance_level[GPU_VALUE_MAX]; /* The AMD performance level set to */ }; /* Get the vendor for a device */ enum GPUVendor gamemode_get_gpu_vendor(long device); gamemode-1.8.2/common/common-helpers.c000066400000000000000000000036611466061601300176750ustar00rootroot00000000000000/* Copyright (c) 2017-2019, Feral Interactive Copyright (c) 2019, Red Hat 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-helpers.h" /* Starting with C99 we can use "inline" without "static" and thus avoid * having multiple (local) definitions of the same inline function. One * consequence of that is that if the compiler decides to *not* inline * a specific call to the function the linker will expect an definition. */ extern inline void cleanup_close(int *fd); extern inline void cleanup_free(void *ptr); gamemode-1.8.2/common/common-helpers.h000066400000000000000000000071431466061601300177010ustar00rootroot00000000000000/* 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 #include #include #include /** * Value clamping helper, works like MIN/MAX but constraints a value within the range. */ #define CLAMP(l, u, value) MAX(MIN(l, u), MIN(MAX(l, u), value)) /** * Little helper to safely print into a buffer, returns a pointer into the buffer */ #define buffered_snprintf(b, s, ...) \ (snprintf(b, sizeof(b), s, __VA_ARGS__) < (ssize_t)sizeof(b) ? b : NULL) /** * Little helper to safely print into a buffer, returns a newly allocated string */ #define safe_snprintf(b, s, ...) \ (snprintf(b, sizeof(b), s, __VA_ARGS__) < (ssize_t)sizeof(b) ? strndup(b, sizeof(b)) : NULL) /** * Helper function: Test, if haystack ends with needle. */ static inline const char *strtail(const char *haystack, const char *needle) { char *pos = strstr(haystack, needle); if (pos && (strlen(pos) == strlen(needle))) return pos; return NULL; } /** * Helper function for autoclosing file-descriptors. Does nothing if the argument * is NULL or the referenced integer < 0. */ inline void cleanup_close(int *fd_ptr) { if (fd_ptr == NULL || *fd_ptr < 0) return; (void)close(*fd_ptr); } /** * Helper macro for autoclosing file-descriptors: use by prefixing the variable, * like "autoclose_fd int fd = -1;". */ #define autoclose_fd __attribute__((cleanup(cleanup_close))) /** * Helper function for auto-freeing dynamically allocated memory. Does nothing * if *ptr is NULL (ptr must not be NULL). */ inline void cleanup_free(void *ptr) { /* The function is defined to work with 'void *' because * that will make sure it compiles without warning also * for all types; what we are getting passed into is a * pointer to a pointer though, so we need to cast */ void *target = *(void **)ptr; free(target); /* free can deal with NULL */ } /** * Helper macro for auto-freeing dynamically allocated memory: use by * prefixing the variable, like "autofree char *data = NULL;". */ #define autofree __attribute__((cleanup(cleanup_free))) gamemode-1.8.2/common/common-logging.c000066400000000000000000000035141466061601300176560ustar00rootroot00000000000000/* 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. */ #include "common-logging.h" #include "syslog.h" static bool use_syslog = false; /** * Control if we want to use the system logger */ void set_use_syslog(const char *name) { /* Open the syslog */ openlog(name, LOG_PID, LOG_DAEMON); use_syslog = true; } /** * Simple getter for the syslog var */ bool get_use_syslog(void) { return use_syslog; } gamemode-1.8.2/common/common-logging.h000066400000000000000000000136271466061601300176710ustar00rootroot00000000000000/* 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 #include #include #include #include #include /* Macros to help with basic logging */ #define PLOG_MSG(...) printf(__VA_ARGS__) #define SYSLOG_MSG(...) syslog(LOG_INFO, __VA_ARGS__) #define LOG_MSG(...) \ do { \ if (get_use_syslog()) { \ SYSLOG_MSG(__VA_ARGS__); \ } else { \ PLOG_MSG(__VA_ARGS__); \ } \ } while (0) #define PLOG_ERROR(...) fprintf(stderr, "ERROR: " __VA_ARGS__) #define SYSLOG_ERROR(...) syslog(LOG_ERR, __VA_ARGS__) #define LOG_ERROR(...) \ do { \ if (get_use_syslog()) { \ SYSLOG_ERROR(__VA_ARGS__); \ } else { \ PLOG_ERROR(__VA_ARGS__); \ } \ } while (0) #define LOG_ONCE(type, ...) \ do { \ static int __once = 0; \ if (!__once++) \ LOG_##type(__VA_ARGS__); \ } while (0) /* Fatal warnings trigger an exit */ #define FATAL_ERRORNO(msg) \ do { \ LOG_ERROR(msg " (%s)\n", strerror(errno)); \ exit(EXIT_FAILURE); \ } while (0) #define FATAL_ERROR(...) \ do { \ LOG_ERROR(__VA_ARGS__); \ exit(EXIT_FAILURE); \ } while (0) /* Hinting helpers */ #define HINT_ONCE(name, hint) \ do { \ static int __once = 0; \ name = (!__once++ ? hint : ""); \ } while (0) #define HINT_ONCE_ON(cond, ...) \ do { \ if (cond) \ HINT_ONCE(__VA_ARGS__); \ } while (0); #define LOG_HINTED(type, msg, hint, ...) \ do { \ const char *__arg; \ HINT_ONCE(__arg, hint); \ LOG_##type(msg "%s", __VA_ARGS__, __arg); \ } while (0) /** * Control if and how how we use syslog */ void set_use_syslog(const char *name); bool get_use_syslog(void); gamemode-1.8.2/common/common-pidfds.c000066400000000000000000000101301466061601300174710ustar00rootroot00000000000000/* Copyright (c) 2017-2019, Feral Interactive Copyright (c) 2019, Red Hat 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 #include "common-helpers.h" #include "common-pidfds.h" #include #include #include #include #include #include #include #include #include #include #include #if !HAVE_FN_PIDFD_OPEN #include #ifndef __NR_pidfd_open #define __NR_pidfd_open 434 #endif static int pidfd_open(pid_t pid, unsigned int flags) { return (int)syscall(__NR_pidfd_open, pid, flags); } #else #include #endif /* pidfd functions */ int open_pidfds(pid_t *pids, int *fds, int count) { int i = 0; for (i = 0; i < count; i++) { int pid = pids[i]; int fd = pidfd_open(pid, 0); if (fd < 0) break; fds[i] = fd; } return i; } static int parse_pid(const char *str, pid_t *pid) { unsigned long long int v; char *end; pid_t p; errno = 0; v = strtoull(str, &end, 0); if (end == str) return -ENOENT; else if (errno != 0) return -errno; p = (pid_t)v; if (p < 1 || (unsigned long long int)p != v) return -ERANGE; if (pid) *pid = p; return 0; } static int parse_status_field_pid(const char *val, pid_t *pid) { const char *t; t = strrchr(val, '\t'); if (t == NULL) return -ENOENT; return parse_pid(t, pid); } static int pidfd_to_pid(int fdinfo, int pidfd, pid_t *pid) { autofree char *key = NULL; autofree char *val = NULL; char name[256] = { 0, }; bool found = false; FILE *f = NULL; size_t keylen = 0; size_t vallen = 0; ssize_t n; int fd; int r = 0; *pid = 0; buffered_snprintf(name, "%d", pidfd); fd = openat(fdinfo, name, O_RDONLY | O_CLOEXEC | O_NOCTTY); if (fd != -1) f = fdopen(fd, "r"); if (f == NULL) return -errno; do { n = getdelim(&key, &keylen, ':', f); if (n == -1) { r = errno; break; } n = getdelim(&val, &vallen, '\n', f); if (n == -1) { r = errno; break; } // TODO: strstrip (key); if (!strncmp(key, "Pid", 3)) { r = parse_status_field_pid(val, pid); found = r > -1; } } while (r == 0 && !found); fclose(f); if (r < 0) return r; else if (!found) return -ENOENT; return 0; } int pidfds_to_pids(int *fds, pid_t *pids, int count) { int fdinfo = -1; int r = 0; int i; fdinfo = open_fdinfo_dir(); if (fdinfo == -1) return -1; for (i = 0; i < count && r == 0; i++) r = pidfd_to_pid(fdinfo, fds[i], &pids[i]); (void)close(fdinfo); if (r != 0) errno = -r; return i; } /* misc directory helpers */ int open_fdinfo_dir(void) { return open("/proc/self/fdinfo", O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); } gamemode-1.8.2/common/common-pidfds.h000066400000000000000000000047171466061601300175140ustar00rootroot00000000000000/* Copyright (c) 2017-2019, Feral Interactive Copyright (c) 2019, Red Hat 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. */ #include /* Open pidfds for up to count process ids specified in pids. The * pointer fds needs to point to an array with at least count * entries. Will stop when it encounters an error (and sets errno). * Returns the number of successfully opened pidfds (or -1 in case * of other errors. */ int open_pidfds(pid_t *pids, int *fds, int count); /* Translate up to count process ids to the corresponding process ids. * The pointer pids needs to point to an array with at least count * entries. Will stop when it encounters an error (and sets errno). * Returns the number of successfully translated pidfds (or -1 in * case of other errors. */ int pidfds_to_pids(int *fds, pid_t *pids, int count); /* Helper to open the fdinfo directory for the current process, i.e. * does open("/proc/self/fdinfo", ...). Returns the file descriptor * for the directory, ownership is transferred and caller needs to * call close on it. */ int open_fdinfo_dir(void); gamemode-1.8.2/common/common-power.c000066400000000000000000000115621466061601300173660ustar00rootroot00000000000000/* 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 #include #include #include #include #include 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); } gamemode-1.8.2/common/common-power.h000066400000000000000000000034351466061601300173730ustar00rootroot00000000000000/* 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 #include /** * 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); gamemode-1.8.2/common/meson.build000066400000000000000000000015151466061601300167370ustar00rootroot00000000000000# Convenience library for the duplicated logging functionality common_sources = [ 'common-logging.c', 'common-governors.c', 'common-external.c', 'common-helpers.c', 'common-gpu.c', 'common-cpu.c', 'common-pidfds.c', 'common-power.c', ] daemon_common = static_library( 'daemon-common', sources: common_sources, install: false, include_directories: [config_h_dir] ) link_daemon_common = declare_dependency( link_with: daemon_common, include_directories: [include_directories('.')] ) lib_common = static_library( 'lib-common', sources: [ 'common-helpers.c', 'common-pidfds.c' ], install: false, include_directories: [config_h_dir] ) link_lib_common = declare_dependency( link_with: lib_common, include_directories: [include_directories('.')] ) gamemode-1.8.2/daemon/000077500000000000000000000000001466061601300145465ustar00rootroot00000000000000gamemode-1.8.2/daemon/README.md000066400000000000000000000104461466061601300160320ustar00rootroot00000000000000### gamemoded **gamemoded** is a daemon that runs in the background, activates system and program optimisations on request, refcounts and also checks caller lifetime. **gamemoded** currently supports the current arguments: ``` Usage: gamemoded [-d] [-l] [-r] [-t] [-h] [-v] -r[PID], --request=[PID] Toggle gamemode for process When no PID given, requests gamemode and pauses -s[PID], --status=[PID] Query the status of gamemode for process When no PID given, queries the status globally -d, --daemonize Daemonize self after launch -l, --log-to-syslog Log to syslog -t, --test Run tests -h, --help Print this help -v, --version Print version ``` Run `man gamemoded` for information and options. --- ## Daemon Features ### Scheduling GameMode can leverage support for soft real time mode if the running kernel supports `SCHED_ISO` (not currently supported in upstream kernels), controlled by the `softrealtime` option. This adjusts the scheduling of the game to real time without sacrificing system stability by starving other processes. GameMode can adjust the nice priority of games to give them a slight IO and CPU priority over other background processes, controlled by the `renice` option. This only works if your user is permitted to adjust priorities within the limits configured by PAM. GameMode can be configured to take care of it by passing `with-pam-group=group` to the build options where `group` is a group your user needs to be part of. For more information, see `/etc/security/limits.conf`. Please take note that some games may actually run seemingly slower with `SCHED_ISO` if the game makes use of busy looping while interacting with the graphic driver. The same may happen if you apply too strong nice values. This effect is called priority inversion: Due to the high priority given to busy loops, there may be too few resources left for the graphics driver. Thus, sane defaults were chosen to not expose this effect on most systems. Part of this default is a heuristic which automatically turns off `SCHED_ISO` if GameMode detects three or less CPU cores. Your experience may change based on using GL threaded optimizations, CPU core binding (taskset), the graphic driver, or different CPU architectures. If you experience bad input latency or inconsistent FPS, try switching these configurations on or off first and report back. `SCHED_ISO` comes with a protection against this effect by falling back to normal scheduling as soon as the `SCHED_ISO` process uses more than 70% avarage across all CPU cores. This default value can be adjusted outside of the scope of GameMode (it's in `/proc/sys/kernel/iso_cpu`). This value also protects against compromising system stability, do not set it to 100% as this would turn the game into a full real time process, thus potentially starving all other OS components from CPU resources. ### IO priority GameMode can adjust the I/O priority of games to benefit from reduced lag and latency when a game has to load assets on demand. This is done by default. ### For those with overclocked CPUs If you have an AMD CPU and have disabled Cool'n'Quiet, or you have an Intel CPU and have disabled SpeedStep, then GameMode's governor settings will not work, as your CPU is not running with a governor. You are already getting maximum performance. If you are unsure, `bootstrap.sh` will warn you if your system lacks CPU governor control. Scripts and other features will still work. ### GPU optimisations GameMode is able to automatically apply GPU performance mode changes on AMD and NVIDIA, and overclocking on NVIDIA, when activated. AMD support currently requires the `amdgpu` kernel module, and NVIDIA requires the `coolbits` extension to be enabled in the NVIDIA settings. It is very much encouraged for users to find out their own overclocking limits manually before venturing into configuring them in GameMode, and activating this feature in GameMode assumes you take responsibility for the effects of said overclocks. More information can be found in the `example/gamemode.ini` file. Note that both NVIDIA (GPUBoost) and AMD (Overdrive) devices and drivers already attempt to internally overclock if possible, but it is still common for enthusiasts to want to manually push the upper threshold.gamemode-1.8.2/daemon/gamemode-config.c000066400000000000000000000631751466061601300177470ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include "gamemode-config.h" #include "common-helpers.h" #include "common-logging.h" #include "build-config.h" /* Ben Hoyt's inih library */ #include #include #include #include #include #include #include #include /* Name and possible location of the config file */ #define CONFIG_NAME "gamemode.ini" /* Default value for the reaper frequency */ #define DEFAULT_REAPER_FREQ 5 #define DEFAULT_IGPU_POWER_THRESHOLD 0.3f /* Helper macro for defining the config variable getter */ #define DEFINE_CONFIG_GET(name) \ long config_get_##name(GameModeConfig *self) \ { \ long value = 0; \ memcpy_locked_config(self, &value, &self->values.name, sizeof(long)); \ return value; \ } /* The number of current locations for config files */ #define CONFIG_NUM_LOCATIONS 4 /** * The config holds various details as needed * and a rwlock to allow config_reload to be called */ struct GameModeConfig { pthread_rwlock_t rwlock; int inotfd; int inotwd[CONFIG_NUM_LOCATIONS]; struct { char whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; char blacklist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; long script_timeout; char startscripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; char endscripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; char defaultgov[CONFIG_VALUE_MAX]; char desiredgov[CONFIG_VALUE_MAX]; char igpu_desiredgov[CONFIG_VALUE_MAX]; float igpu_power_threshold; char softrealtime[CONFIG_VALUE_MAX]; long renice; char ioprio[CONFIG_VALUE_MAX]; long inhibit_screensaver; long disable_splitlock; long reaper_frequency; char apply_gpu_optimisations[CONFIG_VALUE_MAX]; long gpu_device; long nv_core_clock_mhz_offset; long nv_mem_clock_mhz_offset; long nv_powermizer_mode; char amd_performance_level[CONFIG_VALUE_MAX]; char cpu_park_cores[CONFIG_VALUE_MAX]; char cpu_pin_cores[CONFIG_VALUE_MAX]; long require_supervisor; char supervisor_whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; char supervisor_blacklist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; } values; }; /* * Add values to a char list */ static bool append_value_to_list(const char *list_name, const char *value, char list[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]) { unsigned int i = 0; while (*list[i] && ++i < CONFIG_LIST_MAX) ; if (i < CONFIG_LIST_MAX) { strncpy(list[i], value, CONFIG_VALUE_MAX); if (list[i][CONFIG_VALUE_MAX - 1] != '\0') { LOG_ERROR("Config: Could not add [%s] to [%s], exceeds length limit of %d\n", value, list_name, CONFIG_VALUE_MAX); memset(list[i], 0, sizeof(list[i])); return false; } } else { LOG_ERROR("Config: Could not add [%s] to [%s], exceeds number of %d\n", value, list_name, CONFIG_LIST_MAX); return false; } return true; } /* * Get a long value from a string */ static bool get_long_value(const char *value_name, const char *value, long *output) { char *end = NULL; long config_value = strtol(value, &end, 10); 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; } /* * Get a long value from a hex string */ __attribute__((unused)) static bool get_long_value_hex(const char *value_name, const char *value, long *output) { char *end = NULL; long config_value = strtol(value, &end, 16); 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; } /* * 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 * Could be expanded for wildcard or regex */ static bool config_string_list_contains(const char *needle, char haystack[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]) { for (unsigned int i = 0; i < CONFIG_LIST_MAX && haystack[i][0]; i++) { if (strstr(needle, haystack[i])) { return true; } } return false; } /* * Get a string value */ static bool get_string_value(const char *value, char output[CONFIG_VALUE_MAX]) { 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; /* * Handler for the inih callback */ static int inih_handler(void *user, const char *section, const char *name, const char *value) { GameModeConfig *self = (GameModeConfig *)user; bool valid = false; if (strcmp(section, "filter") == 0) { /* Filter subsection */ if (strcmp(name, "whitelist") == 0) { valid = append_value_to_list(name, value, self->values.whitelist); } else if (strcmp(name, "blacklist") == 0) { valid = append_value_to_list(name, value, self->values.blacklist); } } else if (strcmp(section, "general") == 0) { /* General subsection */ if (strcmp(name, "reaper_freq") == 0) { valid = get_long_value(name, value, &self->values.reaper_frequency); } else if (strcmp(name, "defaultgov") == 0) { 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, "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) { valid = get_string_value(value, self->values.softrealtime); } else if (strcmp(name, "renice") == 0) { valid = get_long_value(name, value, &self->values.renice); } else if (strcmp(name, "ioprio") == 0) { valid = get_string_value(value, self->values.ioprio); } else if (strcmp(name, "inhibit_screensaver") == 0) { valid = get_long_value(name, value, &self->values.inhibit_screensaver); } else if (strcmp(name, "disable_splitlock") == 0) { valid = get_long_value(name, value, &self->values.disable_splitlock); } } else if (strcmp(section, "gpu") == 0) { /* Protect the user - don't allow these config options from unsafe config locations */ if (!load_protected) { LOG_ERROR( "The [gpu] config section is not configurable from unsafe config files! Option %s " "will be ignored!\n", name); LOG_ERROR("Consider moving this option to /etc/gamemode.ini\n"); } /* GPU subsection */ if (strcmp(name, "apply_gpu_optimisations") == 0) { valid = get_string_value(value, self->values.apply_gpu_optimisations); } else if (strcmp(name, "gpu_device") == 0) { valid = get_long_value(name, value, &self->values.gpu_device); } else if (strcmp(name, "nv_core_clock_mhz_offset") == 0) { valid = get_long_value(name, value, &self->values.nv_core_clock_mhz_offset); } else if (strcmp(name, "nv_mem_clock_mhz_offset") == 0) { valid = get_long_value(name, value, &self->values.nv_mem_clock_mhz_offset); } else if (strcmp(name, "nv_powermizer_mode") == 0) { valid = get_long_value(name, value, &self->values.nv_powermizer_mode); } else if (strcmp(name, "amd_performance_level") == 0) { valid = get_string_value(value, self->values.amd_performance_level); } } else if (strcmp(section, "cpu") == 0) { if (strcmp(name, "park_cores") == 0) { 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(section, "supervisor") == 0) { /* Supervisor subsection */ if (strcmp(name, "supervisor_whitelist") == 0) { valid = append_value_to_list(name, value, self->values.supervisor_whitelist); } else if (strcmp(name, "supervisor_blacklist") == 0) { valid = append_value_to_list(name, value, self->values.supervisor_blacklist); } else if (strcmp(name, "require_supervisor") == 0) { valid = get_long_value(name, value, &self->values.require_supervisor); } } else if (strcmp(section, "custom") == 0) { /* Custom subsection */ if (strcmp(name, "start") == 0) { valid = append_value_to_list(name, value, self->values.startscripts); } else if (strcmp(name, "end") == 0) { valid = append_value_to_list(name, value, self->values.endscripts); } else if (strcmp(name, "script_timeout") == 0) { valid = get_long_value(name, value, &self->values.script_timeout); } } if (!valid) { /* Simply ignore the value, but with a log */ LOG_MSG("Config: Value ignored [%s] %s=%s\n", section, name, value); } return 1; } /* * Load the config file */ static void load_config_files(GameModeConfig *self) { /* grab the current dir */ char *config_location_local = get_current_dir_name(); /* Get home config location */ char *config_location_home = NULL; const char *cfg = getenv("XDG_CONFIG_HOME"); if (cfg) { config_location_home = realpath(cfg, NULL); } else { cfg = getenv("HOME"); if (cfg) { char *cfg_full = NULL; if (asprintf(&cfg_full, "%s/.config", cfg) > 0) { config_location_home = realpath(cfg_full, NULL); free(cfg_full); } } else { struct passwd *p = getpwuid(getuid()); if (p) { config_location_home = realpath(p->pw_dir, NULL); } } } /* Take the write lock for the internal data */ pthread_rwlock_wrlock(&self->rwlock); /* Clear our config values */ memset(&self->values, 0, sizeof(self->values)); /* Set some non-zero defaults */ self->values.igpu_power_threshold = DEFAULT_IGPU_POWER_THRESHOLD; self->values.inhibit_screensaver = 1; /* Defaults to on */ self->values.disable_splitlock = 1; /* Defaults to on */ self->values.reaper_frequency = DEFAULT_REAPER_FREQ; self->values.gpu_device = 0; self->values.nv_powermizer_mode = -1; self->values.nv_core_clock_mhz_offset = -1; self->values.nv_mem_clock_mhz_offset = -1; self->values.script_timeout = 10; /* Default to 10 seconds for scripts */ /* * Locations to load, in order * Arrays merge and values overwrite */ struct ConfigLocation { const char *path; bool protected; }; struct ConfigLocation locations[CONFIG_NUM_LOCATIONS] = { { SYSCONFDIR, true }, /* shipped default config */ { "/etc", true }, /* administrator config */ { config_location_home, false }, /* $XDG_CONFIG_HOME or $HOME/.config/ */ { config_location_local, false } /* local data eg. $PWD */ }; /* Load each file in order and overwrite values */ for (unsigned int i = 0; i < CONFIG_NUM_LOCATIONS; i++) { char *path = NULL; if (locations[i].path && asprintf(&path, "%s/" CONFIG_NAME, locations[i].path) > 0) { FILE *f = NULL; DIR *d = NULL; if ((f = fopen(path, "r"))) { LOG_MSG("Loading config file [%s]\n", path); load_protected = locations[i].protected; int error = ini_parse_file(f, inih_handler, (void *)self); /* Failure here isn't fatal */ if (error) { LOG_MSG("Failed to parse config file - error on line %d!\n", error); } fclose(f); /* Register for inotify */ /* Watch for modification, deletion, moves, or attribute changes */ uint32_t fileflags = IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF; if ((self->inotwd[i] = inotify_add_watch(self->inotfd, path, fileflags)) == -1) { LOG_ERROR("Failed to watch %s, error: %s", path, strerror(errno)); } } else if ((d = opendir(locations[i].path))) { /* We didn't find a file, so we'll wait on the directory */ /* Notify if a file is created, or move to the directory, or if the directory itself * is removed or moved away */ uint32_t dirflags = IN_CREATE | IN_MOVED_TO | IN_DELETE_SELF | IN_MOVE_SELF; if ((self->inotwd[i] = inotify_add_watch(self->inotfd, locations[i].path, dirflags)) == -1) { LOG_ERROR("Failed to watch %s, error: %s", path, strerror(errno)); } closedir(d); } free(path); } } /* clean up memory */ free(config_location_home); free(config_location_local); /* Release the lock */ pthread_rwlock_unlock(&self->rwlock); } /* * Copy a config parameter with a lock */ static void memcpy_locked_config(GameModeConfig *self, void *dst, void *src, size_t n) { /* Take the read lock */ pthread_rwlock_rdlock(&self->rwlock); /* copy the data */ memcpy(dst, src, n); /* release the lock */ pthread_rwlock_unlock(&self->rwlock); } /* * Create a context object */ GameModeConfig *config_create(void) { GameModeConfig *newconfig = (GameModeConfig *)malloc(sizeof(GameModeConfig)); return newconfig; } /* * Initialise the config */ void config_init(GameModeConfig *self) { pthread_rwlock_init(&self->rwlock, NULL); self->inotfd = inotify_init1(IN_NONBLOCK); if (self->inotfd == -1) LOG_ERROR( "inotify_init failed: %s, gamemode will not be able to watch config files for edits!\n", strerror(errno)); for (unsigned int i = 0; i < CONFIG_NUM_LOCATIONS; i++) { self->inotwd[i] = -1; } /* load the initial config */ load_config_files(self); } /* * Destroy internal parts of config */ static void internal_destroy(GameModeConfig *self) { pthread_rwlock_destroy(&self->rwlock); for (unsigned int i = 0; i < CONFIG_NUM_LOCATIONS; i++) { if (self->inotwd[i] != -1) { /* TODO: Error handle */ inotify_rm_watch(self->inotfd, self->inotwd[i]); } } if (self->inotfd != -1) close(self->inotfd); } /* * Re-load the config file */ void config_reload(GameModeConfig *self) { internal_destroy(self); config_init(self); } /* * Check if the config needs to be reloaded */ bool config_needs_reload(GameModeConfig *self) { bool need = false; /* Take a read lock while we use the inotify fd */ pthread_rwlock_rdlock(&self->rwlock); const size_t buflen = sizeof(struct inotify_event) + NAME_MAX + 1; char buffer[buflen] __attribute__((aligned(__alignof__(struct inotify_event)))); ssize_t len = read(self->inotfd, buffer, buflen); if (len == -1) { /* EAGAIN is returned when there's nothing to read on a non-blocking fd */ if (errno != EAGAIN) LOG_ERROR("Could not read inotify fd: %s\n", strerror(errno)); } else if (len > 0) { /* Iterate over each event we've been given */ size_t i = 0; while (i < (size_t)len) { struct inotify_event *event = (struct inotify_event *)&buffer[i]; /* We have picked up an event and need to handle it */ if (event->mask & IN_ISDIR) { /* If the event is a dir event we need to take a look */ if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF) { /* The directory itself changed, trigger a reload */ need = true; break; } } else { /* When the event has a filename (ie. is from a dir watch), check the name */ if (event->len > 0) { if (strncmp(basename(event->name), CONFIG_NAME, strlen(CONFIG_NAME)) == 0) { /* This is a gamemode config file, trigger a reload */ need = true; break; } } else { /* Otherwise this is for one of our watches on a specific config file, so * trigger the reload regardless */ need = true; break; } } i += sizeof(struct inotify_event) + event->len; } } /* Return the read lock */ pthread_rwlock_unlock(&self->rwlock); return need; } /* * Destroy the config */ void config_destroy(GameModeConfig *self) { internal_destroy(self); /* Finally, free the memory */ free(self); } /* * Checks if the client is whitelisted */ bool config_get_client_whitelisted(GameModeConfig *self, const char *client) { /* Take the read lock for the internal data */ pthread_rwlock_rdlock(&self->rwlock); /* If the whitelist is empty then everything passes */ bool found = true; if (self->values.whitelist[0][0]) { /* * Check if the value is found in our whitelist * Currently is a simple strstr check, but could be modified for wildcards etc. */ found = config_string_list_contains(client, self->values.whitelist); } /* release the lock */ pthread_rwlock_unlock(&self->rwlock); return found; } /* * Checks if the client is blacklisted */ bool config_get_client_blacklisted(GameModeConfig *self, const char *client) { /* Take the read lock for the internal data */ pthread_rwlock_rdlock(&self->rwlock); /* * Check if the value is found in our whitelist * Currently is a simple strstr check, but could be modified for wildcards etc. */ bool found = config_string_list_contains(client, self->values.blacklist); /* release the lock */ pthread_rwlock_unlock(&self->rwlock); return found; } /* * Gets the reaper frequency */ DEFINE_CONFIG_GET(reaper_frequency) /* * Gets the screensaver inhibit setting */ bool config_get_inhibit_screensaver(GameModeConfig *self) { long val; memcpy_locked_config(self, &val, &self->values.inhibit_screensaver, sizeof(long)); return val == 1; } /* * Gets the disable splitlock setting */ bool config_get_disable_splitlock(GameModeConfig *self) { long val; memcpy_locked_config(self, &val, &self->values.disable_splitlock, sizeof(long)); return val == 1; } /* * Get a set of scripts to call when gamemode starts */ void config_get_gamemode_start_scripts(GameModeConfig *self, char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]) { memcpy_locked_config(self, scripts, self->values.startscripts, sizeof(self->values.startscripts)); } /* * Get a set of scripts to call when gamemode ends */ void config_get_gamemode_end_scripts(GameModeConfig *self, char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]) { memcpy_locked_config(self, scripts, self->values.endscripts, sizeof(self->values.startscripts)); } /* * Get the script timemout value */ DEFINE_CONFIG_GET(script_timeout) /* * Get the chosen default governor */ void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]) { memcpy_locked_config(self, governor, self->values.defaultgov, sizeof(self->values.defaultgov)); } /* * Get the chosen desired governor */ 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 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 */ void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX]) { memcpy_locked_config(self, softrealtime, self->values.softrealtime, sizeof(self->values.softrealtime)); } /* * Get the renice value */ long config_get_renice_value(GameModeConfig *self) { long value = 0; memcpy_locked_config(self, &value, &self->values.renice, sizeof(long)); /* Validate the renice value */ if ((value < 1 || value > 20) && value != 0) { LOG_ONCE(ERROR, "Configured renice value '%ld' is invalid, will not renice.\n", value); value = 0; } return value; } /* * Get the ioprio value */ long config_get_ioprio_value(GameModeConfig *self) { long value = 0; char ioprio_value[CONFIG_VALUE_MAX] = { 0 }; memcpy_locked_config(self, ioprio_value, &self->values.ioprio, sizeof(self->values.ioprio)); /* account for special string values */ if (0 == strncmp(ioprio_value, "off", sizeof(self->values.ioprio))) value = IOPRIO_DONT_SET; else if (0 == strncmp(ioprio_value, "default", sizeof(self->values.ioprio))) value = IOPRIO_RESET_DEFAULT; else value = atoi(ioprio_value); /* Validate values */ if (IOPRIO_RESET_DEFAULT == value) { LOG_ONCE(MSG, "IO priority will be reset to default behavior (based on CPU priority).\n"); value = 0; } else { /* maybe clamp the value */ long invalid_ioprio = value; value = CLAMP(0, 7, value); if (value != invalid_ioprio) LOG_ONCE(ERROR, "IO priority value %ld invalid, clamping to %ld\n", invalid_ioprio, value); } return value; } /* * Get various config info for gpu optimisations */ void config_get_apply_gpu_optimisations(GameModeConfig *self, char value[CONFIG_VALUE_MAX]) { memcpy_locked_config(self, value, &self->values.apply_gpu_optimisations, sizeof(self->values.apply_gpu_optimisations)); } /* Define the getters for GPU values */ DEFINE_CONFIG_GET(gpu_device) DEFINE_CONFIG_GET(nv_core_clock_mhz_offset) DEFINE_CONFIG_GET(nv_mem_clock_mhz_offset) DEFINE_CONFIG_GET(nv_powermizer_mode) void config_get_amd_performance_level(GameModeConfig *self, char value[CONFIG_VALUE_MAX]) { memcpy_locked_config(self, value, &self->values.amd_performance_level, sizeof(self->values.amd_performance_level)); } /* char supervisor_whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; char supervisor_blacklist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; */ DEFINE_CONFIG_GET(require_supervisor) /* * Get various config info for cpu optimisations */ void config_get_cpu_park_cores(GameModeConfig *self, char value[CONFIG_VALUE_MAX]) { memcpy_locked_config(self, value, &self->values.cpu_park_cores, sizeof(self->values.cpu_park_cores)); } void config_get_cpu_pin_cores(GameModeConfig *self, char value[CONFIG_VALUE_MAX]) { memcpy_locked_config(self, value, &self->values.cpu_pin_cores, sizeof(self->values.cpu_pin_cores)); } /* * Checks if the supervisor is whitelisted */ bool config_get_supervisor_whitelisted(GameModeConfig *self, const char *supervisor) { /* Take the read lock for the internal data */ pthread_rwlock_rdlock(&self->rwlock); /* If the whitelist is empty then everything passes */ bool found = true; if (self->values.supervisor_whitelist[0][0]) { /* * Check if the value is found in our whitelist * Currently is a simple strstr check, but could be modified for wildcards etc. */ found = config_string_list_contains(supervisor, self->values.supervisor_whitelist); } /* release the lock */ pthread_rwlock_unlock(&self->rwlock); return found; } /* * Checks if the supervisor is blacklisted */ bool config_get_supervisor_blacklisted(GameModeConfig *self, const char *supervisor) { /* Take the read lock for the internal data */ pthread_rwlock_rdlock(&self->rwlock); /* * Check if the value is found in our whitelist * Currently is a simple strstr check, but could be modified for wildcards etc. */ bool found = config_string_list_contains(supervisor, self->values.supervisor_blacklist); /* release the lock */ pthread_rwlock_unlock(&self->rwlock); return found; } gamemode-1.8.2/daemon/gamemode-config.h000066400000000000000000000114301466061601300177370ustar00rootroot00000000000000/* 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 /* * Maximum sizes values in a config list * In practice inih has a INI_MAX_LINE value of 200 so the length is just a safeguard */ #define CONFIG_LIST_MAX 32 #define CONFIG_VALUE_MAX 256 /* * Special ioprio values */ #define IOPRIO_RESET_DEFAULT -1 #define IOPRIO_DONT_SET -2 #define IOPRIO_DEFAULT 4 /* * Opaque config context type */ typedef struct GameModeConfig GameModeConfig; /* * Initialise a config */ GameModeConfig *config_create(void); /* * Initialise a config * Must be called before using any config later config functions */ void config_init(GameModeConfig *self); /* * Reload a config from disk * Thread safe to call */ void config_reload(GameModeConfig *self); /* * Check if the config has changed and will need a reload */ bool config_needs_reload(GameModeConfig *self); /* * Destroy a config * Invalidates the config */ void config_destroy(GameModeConfig *self); /* * Get if the client is in the whitelist or blacklist * config_get_client_whitelisted returns false for an empty whitelist */ bool config_get_client_whitelisted(GameModeConfig *self, const char *client); bool config_get_client_blacklisted(GameModeConfig *self, const char *client); /* * Get the script sets to run at the start or end */ void config_get_gamemode_start_scripts(GameModeConfig *self, char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]); void config_get_gamemode_end_scripts(GameModeConfig *self, char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]); /* * Various get methods for config values */ long config_get_reaper_frequency(GameModeConfig *self); 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_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]); long config_get_renice_value(GameModeConfig *self); long config_get_ioprio_value(GameModeConfig *self); bool config_get_disable_splitlock(GameModeConfig *self); /* * Get various config info for gpu optimisations */ void config_get_apply_gpu_optimisations(GameModeConfig *self, char value[CONFIG_VALUE_MAX]); long config_get_gpu_device(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_powermizer_mode(GameModeConfig *self); void config_get_amd_performance_level(GameModeConfig *self, char value[CONFIG_VALUE_MAX]); /* * Get various config info for cpu optimisations */ 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]); /** * Functions to get supervisor config permissions */ long config_get_require_supervisor(GameModeConfig *self); bool config_get_supervisor_whitelisted(GameModeConfig *self, const char *supervisor); bool config_get_supervisor_blacklisted(GameModeConfig *self, const char *supervisor); gamemode-1.8.2/daemon/gamemode-context.c000066400000000000000000000770371466061601300201700ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include "common-external.h" #include "common-governors.h" #include "common-helpers.h" #include "common-logging.h" #include "common-power.h" #include "gamemode.h" #include "gamemode-config.h" #include "build-config.h" #include #include #include #include #include #include #include #include /* TODO: Move usage to gamemode-dbus.c */ #include /** * The GameModeClient encapsulates the remote connection, providing a list * form to contain the pid and credentials. */ struct GameModeClient { _Atomic int refcount; /**reaper.mutex, NULL); pthread_cond_init(&self->reaper.condition, NULL); self->reaper.running = true; if (pthread_create(&self->reaper.thread, NULL, game_mode_context_reaper, self) != 0) { FATAL_ERROR("Couldn't construct a new thread"); } } void game_mode_context_init(GameModeContext *self) { if (had_context_init) { LOG_ERROR("Context already initialised\n"); return; } had_context_init = true; self->refcount = ATOMIC_VAR_INIT(0); /* clear the initial string */ memset(self->initial_cpu_mode, 0, sizeof(self->initial_cpu_mode)); /* Initialise the config */ self->config = config_create(); config_init(self->config); self->current_govenor = GAME_MODE_GOVERNOR_DEFAULT; /* Initialise the current GPU info */ game_mode_initialise_gpu(self->config, &self->stored_gpu); game_mode_initialise_gpu(self->config, &self->target_gpu); /* Initialise the current CPU info */ game_mode_initialise_cpu(self->config, &self->cpu); self->initial_split_lock_mitigate = -1; pthread_rwlock_init(&self->rwlock, NULL); /* Get the reaper thread going */ start_reaper_thread(self); } static void end_reaper_thread(GameModeContext *self) { self->reaper.running = false; /* We might be stuck waiting, so wake it up again */ pthread_mutex_lock(&self->reaper.mutex); pthread_cond_signal(&self->reaper.condition); pthread_mutex_unlock(&self->reaper.mutex); /* Join the thread as soon as possible */ pthread_join(self->reaper.thread, NULL); pthread_cond_destroy(&self->reaper.condition); pthread_mutex_destroy(&self->reaper.mutex); } void game_mode_context_destroy(GameModeContext *self) { if (!had_context_init) { return; } /* Leave game mode now */ if (game_mode_context_num_clients(self) > 0) { game_mode_context_leave(self); } had_context_init = false; game_mode_client_unref(self->client); end_reaper_thread(self); /* Destroy the gpu object */ game_mode_free_gpu(&self->stored_gpu); game_mode_free_gpu(&self->target_gpu); /* Destroy the cpu object */ game_mode_free_cpu(&self->cpu); /* Destroy the config object */ config_destroy(self->config); pthread_rwlock_destroy(&self->rwlock); } static int game_mode_disable_splitlock(GameModeContext *self, bool disable) { if (!config_get_disable_splitlock(self->config)) return 0; long value_num = self->initial_split_lock_mitigate; char value_str[40]; if (disable) { FILE *f = fopen("/proc/sys/kernel/split_lock_mitigate", "r"); if (f == NULL) { if (errno == ENOENT) return 0; LOG_ERROR("Couldn't open /proc/sys/kernel/split_lock_mitigate : %s\n", strerror(errno)); return 1; } if (fgets(value_str, sizeof value_str, f) == NULL) { LOG_ERROR("Couldn't read from /proc/sys/kernel/split_lock_mitigate : %s\n", strerror(errno)); fclose(f); return 1; } self->initial_split_lock_mitigate = strtol(value_str, NULL, 10); fclose(f); value_num = 0; if (self->initial_split_lock_mitigate == value_num) return 0; } if (value_num == -1) return 0; sprintf(value_str, "%ld", value_num); const char *const exec_args[] = { "pkexec", LIBEXECDIR "/procsysctl", "split_lock_mitigate", value_str, NULL, }; LOG_MSG("Requesting update of split_lock_mitigate to %s\n", value_str); int ret = run_external_process(exec_args, NULL, -1); if (ret != 0) { LOG_ERROR("Failed to update split_lock_mitigate\n"); return ret; } return 0; } 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[] = { "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. * * This is only possible after game_mode_context_init has made a GameModeContext * usable, and should always be followed by a game_mode_context_leave. */ static void game_mode_context_enter(GameModeContext *self) { LOG_MSG("Entering Game Mode...\n"); sd_notifyf(0, "STATUS=%sGameMode is now active.%s\n", "\x1B[1;32m", "\x1B[0m"); if (game_mode_set_governor(self, GAME_MODE_GOVERNOR_DESIRED) == 0) { /* We just switched to a non-default governor. Enable the iGPU * optimization. */ game_mode_enable_igpu_optimization(self); } /* Inhibit the screensaver */ if (config_get_inhibit_screensaver(self->config)) { game_mode_destroy_idle_inhibitor(self->idle_inhibitor); self->idle_inhibitor = game_mode_create_idle_inhibitor(); } game_mode_disable_splitlock(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); game_mode_park_cpu(self->cpu); /* Run custom scripts last - ensures the above are applied first and these scripts can react to * them if needed */ char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; memset(scripts, 0, sizeof(scripts)); config_get_gamemode_start_scripts(self->config, scripts); long timeout = config_get_script_timeout(self->config); game_mode_execute_scripts(scripts, (int)timeout); } /** * Pivot out of game mode. * * Should only be called after both init and game_mode_context_enter have * been performed. */ static void game_mode_context_leave(GameModeContext *self) { LOG_MSG("Leaving Game Mode...\n"); sd_notifyf(0, "STATUS=%sGameMode is currently deactivated.%s\n", "\x1B[1;36m", "\x1B[0m"); /* Remove GPU optimisations */ game_mode_apply_gpu(self->stored_gpu); game_mode_unpark_cpu(self->cpu); /* UnInhibit the screensaver */ if (config_get_inhibit_screensaver(self->config)) { game_mode_destroy_idle_inhibitor(self->idle_inhibitor); self->idle_inhibitor = NULL; } game_mode_disable_splitlock(self, false); game_mode_set_governor(self, GAME_MODE_GOVERNOR_DEFAULT); game_mode_disable_igpu_optimization(self); char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; memset(scripts, 0, sizeof(scripts)); config_get_gamemode_end_scripts(self->config, scripts); long timeout = config_get_script_timeout(self->config); game_mode_execute_scripts(scripts, (int)timeout); } /** * Automatically expire all dead processes * * This has to take special care to ensure thread safety and ensuring that our * pointer is never cached incorrectly. */ static void game_mode_context_auto_expire(GameModeContext *self) { bool removing = true; while (removing) { pthread_rwlock_rdlock(&self->rwlock); removing = false; /* Each time we hit an expired game, start the loop back */ for (GameModeClient *client = self->client; client; client = client->next) { if (kill(client->pid, 0) != 0) { LOG_MSG("Removing expired game [%i]...\n", client->pid); pthread_rwlock_unlock(&self->rwlock); game_mode_context_unregister(self, client->pid, client->pid); removing = true; break; } } if (!removing) { pthread_rwlock_unlock(&self->rwlock); break; } if (game_mode_context_num_clients(self) == 0) LOG_MSG("Properly cleaned up all expired games.\n"); } } /** * Determine if the client is already known to the context */ static const GameModeClient *game_mode_context_has_client(GameModeContext *self, pid_t client) { const GameModeClient *found = NULL; pthread_rwlock_rdlock(&self->rwlock); /* Walk all clients and find a matching pid */ for (GameModeClient *cl = self->client; cl; cl = cl->next) { if (cl->pid == client) { found = cl; break; } } pthread_rwlock_unlock(&self->rwlock); return found; } int game_mode_context_num_clients(GameModeContext *self) { return atomic_load(&self->refcount); } pid_t *game_mode_context_list_clients(GameModeContext *self, unsigned int *count) { pid_t *res = NULL; unsigned int i = 0; unsigned int n; pthread_rwlock_rdlock(&self->rwlock); n = (unsigned int)atomic_load(&self->refcount); if (n > 0) res = (pid_t *)malloc(n * sizeof(pid_t)); for (GameModeClient *cl = self->client; cl; cl = cl->next) { assert(n > i); res[i] = cl->pid; i++; } *count = i; pthread_rwlock_unlock(&self->rwlock); return res; } GameModeClient *game_mode_context_lookup_client(GameModeContext *self, pid_t client) { GameModeClient *found = NULL; pthread_rwlock_rdlock(&self->rwlock); /* Walk all clients and find a matching pid */ for (GameModeClient *cl = self->client; cl; cl = cl->next) { if (cl->pid == client) { found = cl; break; } } if (found) { game_mode_client_ref(found); } pthread_rwlock_unlock(&self->rwlock); return found; } static int game_mode_apply_client_optimisations(GameModeContext *self, pid_t client) { /* Store current renice and apply */ game_mode_apply_renice(self, client, 0 /* expect zero value to start with */); /* Store current ioprio value and apply */ game_mode_apply_ioprio(self, client, IOPRIO_DEFAULT); /* Apply scheduler policies */ game_mode_apply_scheduling(self, client); /* Apply core pinning */ game_mode_apply_core_pinning(self->cpu, client, false); return 0; } int game_mode_context_register(GameModeContext *self, pid_t client, pid_t requester) { errno = 0; /* Construct a new client if we can */ char *executable = NULL; int err = -1; /* Check our requester config first */ if (requester != client) { /* Lookup the executable first */ executable = game_mode_context_find_exe(requester); if (!executable) { goto error_cleanup; } /* Check our blacklist and whitelist */ if (!config_get_supervisor_whitelisted(self->config, executable)) { LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable); err = -2; goto error_cleanup; } else if (config_get_supervisor_blacklisted(self->config, executable)) { LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable); err = -2; goto error_cleanup; } /* We're done with the requestor */ free(executable); executable = NULL; } else if (config_get_require_supervisor(self->config)) { LOG_ERROR("Direct request made but require_supervisor was set, rejecting request!\n"); err = -2; goto error_cleanup; } /* Check the PID first to spare a potentially expensive lookup for the exe */ pthread_rwlock_rdlock(&self->rwlock); // ensure our pointer is sane const GameModeClient *existing = game_mode_context_has_client(self, client); if (existing) { LOG_HINTED(ERROR, "Addition requested for already known client %d [%s].\n", " -- This may happen due to using exec or shell wrappers. You may want to\n" " -- blacklist this client so GameMode can see its final name here.\n", existing->pid, existing->executable); pthread_rwlock_unlock(&self->rwlock); goto error_cleanup; } pthread_rwlock_unlock(&self->rwlock); /* Lookup the executable first */ executable = game_mode_context_find_exe(client); if (!executable) goto error_cleanup; /* Check our blacklist and whitelist */ if (!config_get_client_whitelisted(self->config, executable)) { LOG_MSG("Client [%s] was rejected (not in whitelist)\n", executable); goto error_cleanup; } else if (config_get_client_blacklisted(self->config, executable)) { LOG_MSG("Client [%s] was rejected (in blacklist)\n", executable); goto error_cleanup; } /* From now on we depend on the client, initialize it */ GameModeClient *cl = game_mode_client_new(client, executable, requester); if (!cl) goto error_cleanup; free(executable); /* we're now done with memory */ /* Begin a write lock now to insert our new client at list start */ pthread_rwlock_wrlock(&self->rwlock); LOG_MSG("Adding game: %d [%s]\n", client, cl->executable); /* Update the list */ cl->next = self->client; self->client = cl; /* First add, init */ if (atomic_fetch_add_explicit(&self->refcount, 1, memory_order_seq_cst) == 0) { game_mode_context_enter(self); } game_mode_apply_client_optimisations(self, client); /* Unlock now we're done applying optimisations */ pthread_rwlock_unlock(&self->rwlock); game_mode_client_registered(client); return 0; error_cleanup: if (errno != 0) LOG_ERROR("Failed to register client [%d]: %s\n", client, strerror(errno)); free(executable); return err; } static int game_mode_remove_client_optimisations(GameModeContext *self, pid_t client) { /* Restore the ioprio value for the process, expecting it to be the config value */ game_mode_apply_ioprio(self, client, (int)config_get_ioprio_value(self->config)); /* Restore the renice value for the process, expecting it to be our config value */ game_mode_apply_renice(self, client, (int)config_get_renice_value(self->config)); /* Restore the process affinity to all online cores */ game_mode_undo_core_pinning(self->cpu, client); return 0; } int game_mode_context_unregister(GameModeContext *self, pid_t client, pid_t requester) { GameModeClient *cl = NULL; GameModeClient *prev = NULL; bool found = false; /* Check our requester config first */ if (requester != client) { /* Lookup the executable first */ char *executable = game_mode_context_find_exe(requester); if (!executable) { return -1; } /* Check our blacklist and whitelist */ if (!config_get_supervisor_whitelisted(self->config, executable)) { LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable); free(executable); return -2; } else if (config_get_supervisor_blacklisted(self->config, executable)) { LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable); free(executable); return -2; } free(executable); } else if (config_get_require_supervisor(self->config)) { LOG_ERROR("Direct request made but require_supervisor was set, rejecting request!\n"); return -2; } /* Requires locking. */ pthread_rwlock_wrlock(&self->rwlock); for (prev = cl = self->client; cl; cl = cl->next) { if (cl->pid != client) { prev = cl; continue; } LOG_MSG("Removing game: %d [%s]\n", client, cl->executable); /* Found it */ found = true; prev->next = cl->next; if (cl == self->client) { self->client = cl->next; } cl->next = NULL; game_mode_client_unref(cl); break; } if (!found) { LOG_HINTED( ERROR, "Removal requested for unknown process [%d].\n", " -- The parent process probably forked and tries to unregister from the wrong\n" " -- process now. We cannot work around this. This message will likely be paired\n" " -- with a nearby 'Removing expired game' which means we cleaned up properly\n" " -- (we will log this event). This hint will be displayed only once.\n", client); pthread_rwlock_unlock(&self->rwlock); return -1; } /* When we hit bottom then end the game mode */ if (atomic_fetch_sub_explicit(&self->refcount, 1, memory_order_seq_cst) == 1) { game_mode_context_leave(self); } game_mode_remove_client_optimisations(self, client); /* Unlock now we're done applying optimisations */ pthread_rwlock_unlock(&self->rwlock); game_mode_client_unregistered(client); return 0; } int game_mode_context_query_status(GameModeContext *self, pid_t client, pid_t requester) { GameModeClient *cl = NULL; int ret = 0; /* First check the requester settings if appropriate */ if (client != requester) { char *executable = game_mode_context_find_exe(requester); if (!executable) { return -1; } /* Check our blacklist and whitelist */ if (!config_get_supervisor_whitelisted(self->config, executable)) { LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable); free(executable); return -2; } else if (config_get_supervisor_blacklisted(self->config, executable)) { LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable); free(executable); return -2; } free(executable); } /* * Check the current refcount on gamemode, this equates to whether gamemode is active or not, * see game_mode_context_register and game_mode_context_unregister */ if (atomic_load_explicit(&self->refcount, memory_order_seq_cst)) { ret++; /* Check if the current client is registered */ /* Requires locking. */ pthread_rwlock_rdlock(&self->rwlock); for (cl = self->client; cl; cl = cl->next) { if (cl->pid != client) { continue; } /* Found it */ ret++; break; } /* Unlock here, potentially yielding */ pthread_rwlock_unlock(&self->rwlock); } return ret; } /** * Construct a new GameModeClient for the given process ID * * This is deliberately OOM safe */ static GameModeClient *game_mode_client_new(pid_t pid, char *executable, pid_t requester) { /* This bit seems to be formatted differently by different clang-format versions */ /* clang-format off */ GameModeClient c = { .next = NULL, .pid = pid, .requester = requester, .timestamp = 0, }; /* clang-format on */ GameModeClient *ret = NULL; struct timeval now = { 0, }; int r; r = gettimeofday(&now, NULL); if (r == 0) c.timestamp = now.tv_sec; ret = calloc(1, sizeof(struct GameModeClient)); if (!ret) { return NULL; } *ret = c; ret->refcount = ATOMIC_VAR_INIT(1); strncpy(ret->executable, executable, PATH_MAX - 1); return ret; } /** * Unref a client and the next element in the list, if non-null. */ void game_mode_client_unref(GameModeClient *client) { if (!client) { return; } if (atomic_fetch_sub_explicit(&client->refcount, 1, memory_order_seq_cst) > 1) { return; /* object is still alive */ } if (client->next) { game_mode_client_unref(client->next); } free(client); } void game_mode_client_ref(GameModeClient *client) { if (!client) { return; } atomic_fetch_add_explicit(&client->refcount, 1, memory_order_seq_cst); } /** * The process identifier of the client. */ pid_t game_mode_client_get_pid(GameModeClient *client) { assert(client != NULL); return client->pid; } /** * The path to the executable of client. */ const char *game_mode_client_get_executable(GameModeClient *client) { assert(client != NULL); return client->executable; } /** * The process identifier of the requester. */ pid_t game_mode_client_get_requester(GameModeClient *client) { assert(client != NULL); return client->requester; } /** * The time that game mode was requested for the client. */ uint64_t game_mode_client_get_timestamp(GameModeClient *client) { assert(client != NULL); return (uint64_t)client->timestamp; } static void game_mode_reapply_core_pinning_internal(GameModeContext *self) { pthread_rwlock_wrlock(&self->rwlock); if (game_mode_context_num_clients(self)) { for (GameModeClient *cl = self->client; cl; cl = cl->next) game_mode_apply_core_pinning(self->cpu, cl->pid, true); } pthread_rwlock_unlock(&self->rwlock); } /* Internal refresh config function (assumes no contention with reaper thread) */ static void game_mode_reload_config_internal(GameModeContext *self) { LOG_MSG("Reloading config...\n"); /* Make sure we have a readwrite lock on ourselves */ pthread_rwlock_wrlock(&self->rwlock); /* Remove current optimisations when we're already active */ if (game_mode_context_num_clients(self)) { for (GameModeClient *cl = self->client; cl; cl = cl->next) game_mode_remove_client_optimisations(self, cl->pid); game_mode_context_leave(self); } /* Reload the config */ config_reload(self->config); game_mode_reconfig_cpu(self->config, &self->cpu); /* Re-apply all current optimisations */ if (game_mode_context_num_clients(self)) { /* Start the global context back up */ game_mode_context_enter(self); for (GameModeClient *cl = self->client; cl; cl = cl->next) game_mode_apply_client_optimisations(self, cl->pid); } pthread_rwlock_unlock(&self->rwlock); LOG_MSG("Config reload complete\n"); } /** * We continuously run until told otherwise. */ static void *game_mode_context_reaper(void *userdata) { /* Stack, not allocated, won't disappear. */ GameModeContext *self = userdata; long reaper_interval = config_get_reaper_frequency(self->config); struct timespec ts = { 0, 0 }; ts.tv_sec = time(NULL) + reaper_interval; while (self->reaper.running) { /* Wait for condition */ pthread_mutex_lock(&self->reaper.mutex); pthread_cond_timedwait(&self->reaper.condition, &self->reaper.mutex, &ts); pthread_mutex_unlock(&self->reaper.mutex); /* Highly possible the main thread woke us up to exit */ if (!self->reaper.running) { return NULL; } /* Check on the CPU/iGPU energy balance */ game_mode_check_igpu_energy(self); /* Expire remaining entries */ game_mode_context_auto_expire(self); /* Re apply the thread affinity mask (aka core pinning) */ game_mode_reapply_core_pinning_internal(self); /* Check if we should be reloading the config, and do so if needed */ if (config_needs_reload(self->config)) { LOG_MSG("Detected config file changes\n"); game_mode_reload_config_internal(self); } ts.tv_sec = time(NULL) + reaper_interval; } return NULL; } GameModeContext *game_mode_context_instance(void) { return &instance; } GameModeConfig *game_mode_config_from_context(const GameModeContext *context) { return context ? context->config : NULL; } /** * Attempt to locate the exe for the process. * We might run into issues if the process is running under an odd umask. */ static char *game_mode_context_find_exe(pid_t pid) { char buffer[PATH_MAX]; char *proc_path = NULL, *wine_exe = NULL; autoclose_fd int pidfd = -1; ssize_t r; if (!(proc_path = buffered_snprintf(buffer, "/proc/%d", pid))) goto fail; /* Translate /proc//exe to the application binary */ pidfd = openat(AT_FDCWD, proc_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); if (pidfd == -1) goto fail; r = readlinkat(pidfd, "exe", buffer, sizeof(buffer)); if (r == sizeof(buffer)) { errno = ENAMETOOLONG; r = -1; } if (r == -1) goto fail; buffer[r] = '\0'; char *exe = strdup(buffer); /* Resolve for wine if appropriate */ if ((wine_exe = game_mode_resolve_wine_preloader(exe, pid))) { free(exe); exe = wine_exe; } return exe; fail: if (errno != 0) // otherwise a proper message was logged before LOG_ERROR("Unable to find executable for PID %d: %s\n", pid, strerror(errno)); return NULL; } /* Executes a set of scripts */ static void game_mode_execute_scripts(char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX], int timeout) { unsigned int i = 0; while (i < CONFIG_LIST_MAX && *scripts[i] != '\0') { LOG_MSG("Executing script [%s]\n", scripts[i]); int err; const char *args[] = { "/bin/sh", "-c", scripts[i], NULL }; if ((err = run_external_process(args, NULL, timeout)) != 0) { /* Log the failure, but this is not fatal */ LOG_ERROR("Script [%s] failed with error %d\n", scripts[i], err); } i++; } } /* * Reload the current configuration * * Reloading the configuration completely live would be problematic for various optimisation values, * to ensure we have a fully clean state, we tear down the whole gamemode state and regrow it with a * new config, remembering the registered games */ int game_mode_reload_config(GameModeContext *self) { /* Stop the reaper thread first */ end_reaper_thread(self); game_mode_reload_config_internal(self); /* Restart the reaper thread back up again */ start_reaper_thread(self); return 0; } gamemode-1.8.2/daemon/gamemode-cpu.c000066400000000000000000000331711466061601300172620ustar00rootroot00000000000000 /* 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. */ #define _GNU_SOURCE #include #include #include #include "common-cpu.h" #include "common-external.h" #include "common-helpers.h" #include "common-logging.h" #include "gamemode.h" #include "gamemode-config.h" #include "build-config.h" static int read_small_file(char *path, char **buf, size_t *buflen) { FILE *f = fopen(path, "r"); if (!f) { LOG_ERROR("Couldn't open file at %s : %s\n", path, strerror(errno)); return 0; } ssize_t nread = getline(buf, buflen, f); if (nread == -1) { LOG_ERROR("Couldn't read file at %s : %s\n", path, strerror(errno)); fclose(f); return 0; } fclose(f); while (nread > 0 && ((*buf)[nread - 1] == '\n' || (*buf)[nread - 1] == '\r')) nread--; (*buf)[nread] = '\0'; return 1; } static int walk_sysfs(char *cpulist, char **buf, size_t *buflen, GameModeCPUInfo *info) { char path[PATH_MAX]; unsigned long long max_cache = 0, max_freq = 0; long from, to; cpu_set_t *freq_cores = CPU_ALLOC(info->num_cpu); char *list = cpulist; while ((list = parse_cpulist(list, &from, &to))) { for (long cpu = from; cpu < to + 1; cpu++) { CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online); /* check for L3 cache non-uniformity among the cores */ int ret = snprintf(path, PATH_MAX, "/sys/devices/system/cpu/cpu%ld/cache/index3/size", cpu); if (ret > 0 && ret < PATH_MAX) { if (read_small_file(path, buf, buflen)) { char *endp; unsigned long long cache_size = strtoull(*buf, &endp, 10); if (*endp == 'K') { cache_size *= 1024; } else if (*endp == 'M') { cache_size *= 1024 * 1024; } else if (*endp == 'G') { cache_size *= 1024 * 1024 * 1024; } else if (*endp != '\0') { LOG_MSG("cpu L3 cache size (%s) on core #%ld is silly\n", *buf, cpu); cache_size = 0; } if (cache_size > max_cache) { max_cache = cache_size; CPU_ZERO_S(CPU_ALLOC_SIZE(info->num_cpu), info->to_keep); } if (cache_size == max_cache) CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep); } } /* check for frequency non-uniformity among the cores */ ret = snprintf(path, PATH_MAX, "/sys/devices/system/cpu/cpu%ld/cpufreq/cpuinfo_max_freq", cpu); if (ret > 0 && ret < PATH_MAX) { if (read_small_file(path, buf, buflen)) { unsigned long long freq = strtoull(*buf, NULL, 10); unsigned long long cutoff = (freq * 5) / 100; if (freq > max_freq) { if (max_freq < freq - cutoff) CPU_ZERO_S(CPU_ALLOC_SIZE(info->num_cpu), freq_cores); max_freq = freq; } if (freq + cutoff >= max_freq) CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), freq_cores); } } } } if (CPU_EQUAL_S(CPU_ALLOC_SIZE(info->num_cpu), info->online, info->to_keep) || CPU_COUNT_S(CPU_ALLOC_SIZE(info->num_cpu), info->to_keep) == 0) { LOG_MSG("cpu L3 cache was uniform, this is not a x3D with multiple chiplets\n"); CPU_FREE(info->to_keep); info->to_keep = freq_cores; if (CPU_EQUAL_S(CPU_ALLOC_SIZE(info->num_cpu), info->online, info->to_keep) || CPU_COUNT_S(CPU_ALLOC_SIZE(info->num_cpu), info->to_keep) == 0) LOG_MSG("cpu frequency was uniform, this is not a big.LITTLE type of system\n"); } else { CPU_FREE(freq_cores); } return 1; } static int walk_string(char *cpulist, char *config_cpulist, GameModeCPUInfo *info) { long from, to; char *list = cpulist; while ((list = parse_cpulist(list, &from, &to))) { for (long cpu = from; cpu < to + 1; cpu++) { CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online); if (info->park_or_pin == IS_CPU_PARK) CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep); } } list = config_cpulist; while ((list = parse_cpulist(list, &from, &to))) { for (long cpu = from; cpu < to + 1; cpu++) { if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online)) { if (info->park_or_pin == IS_CPU_PARK) CPU_CLR_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep); else CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep); } } } return 1; } void game_mode_reconfig_cpu(GameModeConfig *config, GameModeCPUInfo **info) { game_mode_unpark_cpu(*info); game_mode_free_cpu(info); game_mode_initialise_cpu(config, info); } int game_mode_initialise_cpu(GameModeConfig *config, GameModeCPUInfo **info) { /* Verify input, this is programmer error */ if (!info || *info) FATAL_ERROR("Invalid GameModeCPUInfo passed to %s", __func__); /* Early out if we have this feature turned off */ char park_cores[CONFIG_VALUE_MAX]; char pin_cores[CONFIG_VALUE_MAX]; config_get_cpu_park_cores(config, park_cores); config_get_cpu_pin_cores(config, pin_cores); int park_or_pin = -1; if (pin_cores[0] != '\0') { if (strcasecmp(pin_cores, "no") == 0 || strcasecmp(pin_cores, "false") == 0 || strcmp(pin_cores, "0") == 0) { park_or_pin = -2; } else if (strcasecmp(pin_cores, "yes") == 0 || strcasecmp(pin_cores, "true") == 0 || strcmp(pin_cores, "1") == 0) { pin_cores[0] = '\0'; park_or_pin = IS_CPU_PIN; } else { park_or_pin = IS_CPU_PIN; } } if (park_or_pin != IS_CPU_PIN && park_cores[0] != '\0') { if (strcasecmp(park_cores, "no") == 0 || strcasecmp(park_cores, "false") == 0 || strcmp(park_cores, "0") == 0) { if (park_or_pin == -2) return 0; park_or_pin = -1; } else if (strcasecmp(park_cores, "yes") == 0 || strcasecmp(park_cores, "true") == 0 || strcmp(park_cores, "1") == 0) { park_cores[0] = '\0'; park_or_pin = IS_CPU_PARK; } else { park_or_pin = IS_CPU_PARK; } } /* always default to pin */ if (park_or_pin != IS_CPU_PARK) park_or_pin = IS_CPU_PIN; char *buf = NULL, *buf2 = NULL; size_t buflen = 0, buf2len = 0; /* first we find which cores are online, this also helps us to determine the max * cpu core number that we need to allocate the cpulist later */ if (!read_small_file("/sys/devices/system/cpu/online", &buf, &buflen)) goto error_exit; long from, to, max = 0; char *s = buf; while ((s = parse_cpulist(s, &from, &to))) { if (to > max) max = to; } /* either parsing failed or we have only a single core, in either case * we cannot optimize anyway */ if (max == 0) goto early_exit; GameModeCPUInfo *new_info = malloc(sizeof(GameModeCPUInfo)); memset(new_info, 0, sizeof(GameModeCPUInfo)); new_info->num_cpu = (size_t)(max + 1); new_info->park_or_pin = park_or_pin; new_info->online = CPU_ALLOC(new_info->num_cpu); new_info->to_keep = CPU_ALLOC(new_info->num_cpu); CPU_ZERO_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->online); CPU_ZERO_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->to_keep); if (park_or_pin == IS_CPU_PARK && park_cores[0] != '\0') { if (!walk_string(buf, park_cores, new_info)) goto error_exit; } else if (park_or_pin == IS_CPU_PIN && pin_cores[0] != '\0') { if (!walk_string(buf, pin_cores, new_info)) goto error_exit; } else if (!walk_sysfs(buf, &buf2, &buf2len, new_info)) { goto error_exit; } if (park_or_pin == IS_CPU_PARK && CPU_EQUAL_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->online, new_info->to_keep)) { game_mode_free_cpu(&new_info); LOG_MSG("I can find no reason to perform core parking on this system!\n"); goto error_exit; } if (CPU_COUNT_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->to_keep) == 0) { game_mode_free_cpu(&new_info); LOG_MSG("I can find no reason to perform core pinning on this system!\n"); goto error_exit; } if (CPU_COUNT_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->to_keep) < 4) { game_mode_free_cpu(&new_info); LOG_MSG( "logic or config would result in less than 4 active cores, will not apply cpu core " "parking/pinning!\n"); goto error_exit; } *info = new_info; early_exit: free(buf); free(buf2); return 0; error_exit: free(buf); free(buf2); return -1; } static int log_state(char *cpulist, int *pos, const long first, const long last) { int ret; if (*pos != 0) { ret = snprintf(cpulist + *pos, ARG_MAX - (size_t)*pos, ","); if (ret < 0 || (size_t)ret >= (ARG_MAX - (size_t)*pos)) { LOG_ERROR("snprintf failed, will not apply cpu core parking!\n"); return 0; } *pos += ret; } if (first == last) ret = snprintf(cpulist + *pos, ARG_MAX - (size_t)*pos, "%ld", first); else ret = snprintf(cpulist + *pos, ARG_MAX - (size_t)*pos, "%ld-%ld", first, last); if (ret < 0 || (size_t)ret >= (ARG_MAX - (size_t)*pos)) { LOG_ERROR("snprintf failed, will not apply cpu core parking!\n"); return 0; } *pos += ret; return 1; } int game_mode_park_cpu(const GameModeCPUInfo *info) { if (!info || info->park_or_pin == IS_CPU_PIN) return 0; long first = -1, last = -1; char cpulist[ARG_MAX]; int pos = 0; for (long cpu = 0; cpu < (long)(info->num_cpu); cpu++) { if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online) && !CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep)) { if (first == -1) { first = cpu; last = cpu; } else if (last + 1 == cpu) { last = cpu; } else { if (!log_state(cpulist, &pos, first, last)) return 0; first = cpu; last = cpu; } } } if (first != -1) log_state(cpulist, &pos, first, last); const char *const exec_args[] = { "pkexec", LIBEXECDIR "/cpucorectl", "offline", cpulist, NULL, }; LOG_MSG("Requesting parking of cores %s\n", cpulist); int ret = run_external_process(exec_args, NULL, -1); if (ret != 0) { LOG_ERROR("Failed to park cpu cores\n"); return ret; } return 0; } int game_mode_unpark_cpu(const GameModeCPUInfo *info) { if (!info || info->park_or_pin == IS_CPU_PIN) return 0; long first = -1, last = -1; char cpulist[ARG_MAX]; int pos = 0; for (long cpu = 0; cpu < (long)(info->num_cpu); cpu++) { if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online) && !CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep)) { if (first == -1) { first = cpu; last = cpu; } else if (last + 1 == cpu) { last = cpu; } else { if (!log_state(cpulist, &pos, first, last)) return 0; first = cpu; last = cpu; } } } if (first != -1) log_state(cpulist, &pos, first, last); const char *const exec_args[] = { "pkexec", LIBEXECDIR "/cpucorectl", "online", cpulist, NULL, }; LOG_MSG("Requesting unparking of cores %s\n", cpulist); int ret = run_external_process(exec_args, NULL, -1); if (ret != 0) { LOG_ERROR("Failed to unpark cpu cores\n"); return ret; } return 0; } static void apply_affinity_mask(pid_t pid, size_t cpusetsize, const cpu_set_t *mask, const bool be_silent) { char buffer[PATH_MAX]; char *proc_path = NULL; DIR *proc_dir = NULL; if (!(proc_path = buffered_snprintf(buffer, "/proc/%d/task", pid))) { if (!be_silent) { LOG_ERROR("Unable to find executable for PID %d: %s\n", pid, strerror(errno)); } return; } if (!(proc_dir = opendir(proc_path))) { if (!be_silent) { LOG_ERROR("Unable to find executable for PID %d: %s\n", pid, strerror(errno)); } return; } struct dirent *entry; while ((entry = readdir(proc_dir))) { if (entry->d_name[0] == '.') continue; int tid = atoi(entry->d_name); if (sched_setaffinity(tid, cpusetsize, mask) != 0 && !be_silent) LOG_ERROR("Failed to pin thread %d: %s\n", tid, strerror(errno)); } closedir(proc_dir); } void game_mode_apply_core_pinning(const GameModeCPUInfo *info, const pid_t client, const bool be_silent) { if (!info || info->park_or_pin == IS_CPU_PARK) return; if (!be_silent) LOG_MSG("Pinning process...\n"); apply_affinity_mask(client, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep, be_silent); } void game_mode_undo_core_pinning(const GameModeCPUInfo *info, const pid_t client) { if (!info || info->park_or_pin == IS_CPU_PARK) return; LOG_MSG("Pinning process back to all online cores...\n"); apply_affinity_mask(client, CPU_ALLOC_SIZE(info->num_cpu), info->online, false); } void game_mode_free_cpu(GameModeCPUInfo **info) { if ((*info)) { CPU_FREE((*info)->online); (*info)->online = NULL; CPU_FREE((*info)->to_keep); (*info)->to_keep = NULL; free(*info); *info = NULL; } } gamemode-1.8.2/daemon/gamemode-dbus.c000066400000000000000000000544771466061601300174440ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include "gamemode.h" #include "common-helpers.h" #include "common-logging.h" #include "common-pidfds.h" #ifdef USE_ELOGIND #include #include #else #include #include #endif #include #include #include #include #define GAME_PATH_PREFIX "/com/feralinteractive/GameMode/Games" /* maximum length of a valid game object path string: * The path prefix including \0 (sizeof), another '/', and 10 digits for uint32_t ('%u')*/ #define GAME_PATH_MAX (sizeof(GAME_PATH_PREFIX) + 11) /* systemd dbus components */ static sd_bus *bus = NULL; static sd_bus_slot *slot = NULL; /** * Clean up our private dbus state */ static void clean_up(void) { if (slot) { sd_bus_slot_unref(slot); } slot = NULL; if (bus) { sd_bus_unref(bus); } bus = NULL; } /** * Handles the RegisterGame D-BUS Method */ static int method_register_game(sd_bus_message *m, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { int pid = 0; GameModeContext *context = userdata; int ret = sd_bus_message_read(m, "i", &pid); if (ret < 0) { LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret)); return ret; } int status = game_mode_context_register(context, (pid_t)pid, (pid_t)pid); return sd_bus_reply_method_return(m, "i", status); } /** * Handles the UnregisterGame D-BUS Method */ static int method_unregister_game(sd_bus_message *m, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { int pid = 0; GameModeContext *context = userdata; int ret = sd_bus_message_read(m, "i", &pid); if (ret < 0) { LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret)); return ret; } int status = game_mode_context_unregister(context, (pid_t)pid, (pid_t)pid); return sd_bus_reply_method_return(m, "i", status); } /** * Handles the QueryStatus D-BUS Method */ static int method_query_status(sd_bus_message *m, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { int pid = 0; GameModeContext *context = userdata; int ret = sd_bus_message_read(m, "i", &pid); if (ret < 0) { LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret)); return ret; } int status = game_mode_context_query_status(context, (pid_t)pid, (pid_t)pid); return sd_bus_reply_method_return(m, "i", status); } /** * Handles the RegisterGameByPID D-BUS Method */ static int method_register_game_by_pid(sd_bus_message *m, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { int callerpid = 0; int gamepid = 0; GameModeContext *context = userdata; int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid); if (ret < 0) { LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret)); return ret; } int reply = game_mode_context_register(context, (pid_t)gamepid, (pid_t)callerpid); return sd_bus_reply_method_return(m, "i", reply); } /** * Handles the UnregisterGameByPID D-BUS Method */ static int method_unregister_game_by_pid(sd_bus_message *m, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { int callerpid = 0; int gamepid = 0; GameModeContext *context = userdata; int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid); if (ret < 0) { LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret)); return ret; } int reply = game_mode_context_unregister(context, (pid_t)gamepid, (pid_t)callerpid); return sd_bus_reply_method_return(m, "i", reply); } /** * Handles the QueryStatusByPID D-BUS Method */ static int method_query_status_by_pid(sd_bus_message *m, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { int callerpid = 0; int gamepid = 0; GameModeContext *context = userdata; int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid); if (ret < 0) { LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret)); return ret; } int status = game_mode_context_query_status(context, (pid_t)gamepid, (pid_t)callerpid); return sd_bus_reply_method_return(m, "i", status); } /** * Handles the RegisterGameByPIDFd D-BUS Method */ static int method_register_game_by_pidfd(sd_bus_message *m, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { int fds[2] = { -1, -1 }; pid_t pids[2] = { 0, 0 }; GameModeContext *context = userdata; int ret = sd_bus_message_read(m, "hh", &fds[0], &fds[1]); if (ret < 0) { LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret)); return ret; } int reply = pidfds_to_pids(fds, pids, 2); if (reply == 2) reply = game_mode_context_register(context, pids[0], pids[1]); else reply = -1; return sd_bus_reply_method_return(m, "i", reply); } /** * Handles the UnregisterGameByPIDFd D-BUS Method */ static int method_unregister_game_by_pidfd(sd_bus_message *m, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { int fds[2] = { -1, -1 }; pid_t pids[2] = { 0, 0 }; GameModeContext *context = userdata; int ret = sd_bus_message_read(m, "hh", &fds[0], &fds[1]); if (ret < 0) { LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret)); return ret; } int reply = pidfds_to_pids(fds, pids, 2); if (reply == 2) reply = game_mode_context_unregister(context, pids[0], pids[1]); else reply = -1; return sd_bus_reply_method_return(m, "i", reply); } /** * Handles the QueryStatusByPIDFd D-BUS Method */ static int method_query_status_by_pidfd(sd_bus_message *m, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { int fds[2] = { -1, -1 }; pid_t pids[2] = { 0, 0 }; GameModeContext *context = userdata; int ret = sd_bus_message_read(m, "hh", &fds[0], &fds[1]); if (ret < 0) { LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret)); return ret; } int reply = pidfds_to_pids(fds, pids, 2); if (reply == 2) reply = game_mode_context_query_status(context, pids[0], pids[1]); else reply = -1; return sd_bus_reply_method_return(m, "i", reply); } /** * Handles the ClientCount D-BUS Property */ static int property_get_client_count(sd_bus *local_bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { GameModeContext *context = userdata; int count; count = game_mode_context_num_clients(context); return sd_bus_message_append_basic(reply, 'i', &count); } /** * Handles the Refresh Config request */ static int method_refresh_config(sd_bus_message *m, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { GameModeContext *context = userdata; int status = game_mode_reload_config(context); return sd_bus_reply_method_return(m, "i", status); } static inline void game_object_bus_path(pid_t pid, char path[static GAME_PATH_MAX]) { snprintf(path, GAME_PATH_MAX, GAME_PATH_PREFIX "/%u", (uint32_t)pid); } /** * Handles the List Games */ static int method_list_games(sd_bus_message *m, void *userdata, __attribute__((unused)) sd_bus_error *ret_error) { GameModeContext *context = userdata; sd_bus_message *reply = NULL; unsigned int count; pid_t *clients; int r; r = sd_bus_message_new_method_return(m, &reply); if (r < 0) return r; r = sd_bus_message_open_container(reply, 'a', "(io)"); if (r < 0) return r; clients = game_mode_context_list_clients(context, &count); for (unsigned int i = 0; i < count; i++) { char path[GAME_PATH_MAX] = { 0, }; pid_t pid = clients[i]; game_object_bus_path(pid, path); r = sd_bus_message_append(reply, "(io)", (int32_t)pid, path); if (r < 0) break; } free(clients); if (r < 0) return r; r = sd_bus_message_close_container(reply); if (r < 0) return r; return sd_bus_send(NULL, reply, NULL); } /* Signal emission helper */ static void game_mode_client_send_game_signal(pid_t pid, bool new_game) { char path[GAME_PATH_MAX] = { 0, }; int ret; game_object_bus_path(pid, path); ret = sd_bus_emit_signal(bus, "/com/feralinteractive/GameMode", "com.feralinteractive.GameMode", new_game ? "GameRegistered" : "GameUnregistered", "io", (int32_t)pid, path); if (ret < 0) fprintf(stderr, "failed to emit signal: %s", strerror(-ret)); (void)sd_bus_emit_properties_changed(bus, "/com/feralinteractive/GameMode", "com.feralinteractive.GameMode", "ClientCount", NULL); } /* Emit GameRegistered signal */ void game_mode_client_registered(pid_t pid) { game_mode_client_send_game_signal(pid, true); } /* Emit GameUnregistered signal */ void game_mode_client_unregistered(pid_t pid) { game_mode_client_send_game_signal(pid, false); } /** * D-BUS vtable to dispatch virtual methods */ /* This bit seems to be formatted differently by different clang-format versions */ /* clang-format off */ static const sd_bus_vtable gamemode_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("ClientCount", "i", property_get_client_count, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_METHOD("RegisterGame", "i", "i", method_register_game, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("UnregisterGame", "i", "i", method_unregister_game, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("QueryStatus", "i", "i", method_query_status, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("RegisterGameByPID", "ii", "i", method_register_game_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("UnregisterGameByPID", "ii", "i", method_unregister_game_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("QueryStatusByPID", "ii", "i", method_query_status_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("RegisterGameByPIDFd", "hh", "i", method_register_game_by_pidfd, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("UnregisterGameByPIDFd", "hh", "i", method_unregister_game_by_pidfd, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("QueryStatusByPIDFd", "hh", "i", method_query_status_by_pidfd, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("RefreshConfig", "", "i", method_refresh_config, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ListGames", "", "a(io)", method_list_games, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL("GameRegistered", "io", 0), SD_BUS_SIGNAL("GameUnregistered", "io", 0), SD_BUS_VTABLE_END }; /** * Game Objects */ static inline void pid_to_pointer(pid_t pid, void **pointer) { _Static_assert(sizeof (void *) >= sizeof (pid_t), "pointer type not large enough to store pid_t"); *pointer = (void *) (intptr_t) pid; } static inline pid_t pid_from_pointer(const void *pointer) { return (pid_t) (intptr_t) pointer; } static int game_object_find(sd_bus *local_bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *ret_error) { static const char prefix[] = GAME_PATH_PREFIX "/"; const char *start; unsigned long int n; char *end; if (strncmp(path, prefix, strlen(prefix)) != 0) return 0; start = path + strlen(prefix); errno = 0; n = strtoul(start, &end, 10); if (start == end || errno != 0) return 0; pid_to_pointer((pid_t) n, found); return 1; } static int game_node_enumerator(sd_bus *local_bus, const char *path, void *userdata, char ***nodes, __attribute__((unused)) sd_bus_error *ret_error) { GameModeContext *context = userdata; unsigned int count; pid_t *clients; char **strv = NULL; clients = game_mode_context_list_clients(context, &count); strv = malloc (sizeof (char *) * (count + 1)); for (unsigned int i = 0; i < count; i++) { char bus_path[GAME_PATH_MAX] = {0, }; game_object_bus_path(clients[i], bus_path); strv[i] = strdup (bus_path); } strv[count] = NULL; *nodes = strv; free(clients); return 1; } /** * Handles the ProcessId property for Game objects */ static int game_object_get_process_id(sd_bus *local_bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { GameModeClient *client; GameModeContext *context; pid_t pid; int pv; int ret; pid = pid_from_pointer(userdata); context = game_mode_context_instance(); client = game_mode_context_lookup_client(context, pid); pv = (int) pid; if (client == NULL) { return sd_bus_error_setf(ret_error, SD_BUS_ERROR_UNKNOWN_OBJECT, "No client registered with id '%d'", pv); } ret = sd_bus_message_append_basic(reply, 'i', &pv); game_mode_client_unref(client); return ret; } /** * Handles the Exectuable property for Game objects */ static int game_object_get_executable(sd_bus *local_bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { GameModeClient *client; GameModeContext *context; const char *exec; pid_t pid; int ret; pid = pid_from_pointer(userdata); context = game_mode_context_instance(); client = game_mode_context_lookup_client(context, pid); if (client == NULL) { return sd_bus_error_setf(ret_error, SD_BUS_ERROR_UNKNOWN_OBJECT, "No client registered with id '%d'", (int) pid); } exec = game_mode_client_get_executable(client); ret = sd_bus_message_append_basic(reply, 's', exec); game_mode_client_unref(client); return ret; } /** * Handles the Requester property for Game objects */ static int game_object_get_requester(sd_bus *local_bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { GameModeClient *client; GameModeContext *context; pid_t requester; pid_t pid; int ret; int pv; pid = pid_from_pointer(userdata); context = game_mode_context_instance(); client = game_mode_context_lookup_client(context, pid); if (client == NULL) { return sd_bus_error_setf(ret_error, SD_BUS_ERROR_UNKNOWN_OBJECT, "No client registered with id '%d'", (int) pid); } requester = game_mode_client_get_requester(client); pv = (int) requester; ret = sd_bus_message_append_basic(reply, 'i', &pv); game_mode_client_unref(client); return ret; } /** * Handles the Timestamp property for Game objects */ static int game_object_get_timestamp(sd_bus *local_bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { GameModeClient *client; GameModeContext *context; uint64_t timestamp; pid_t pid; int ret; pid = pid_from_pointer(userdata); context = game_mode_context_instance(); client = game_mode_context_lookup_client(context, pid); if (client == NULL) { return sd_bus_error_setf(ret_error, SD_BUS_ERROR_UNKNOWN_OBJECT, "No client registered with id '%d'", (int) pid); } timestamp = game_mode_client_get_timestamp(client); ret = sd_bus_message_append_basic(reply, 't', ×tamp); game_mode_client_unref(client); return ret; } /* Same as above: this bit seems to be formatted differently by different clang-format versions */ /* clang-format off */ static const sd_bus_vtable game_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("ProcessId", "i", game_object_get_process_id, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Executable", "s", game_object_get_executable, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Requester", "i", game_object_get_requester, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Timestamp", "t", game_object_get_timestamp, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_VTABLE_END }; /* clang-format on */ /** * Main process loop for the daemon. Run until quitting has been requested. */ void game_mode_context_loop(GameModeContext *context) { /* Set up function to handle clean up of resources */ atexit(clean_up); int ret = 0; /* Connect to the session bus */ ret = sd_bus_open_user(&bus); if (ret < 0) { FATAL_ERROR("Failed to connect to the bus: %s\n", strerror(-ret)); } /* Create the object to allow connections */ ret = sd_bus_add_object_vtable(bus, &slot, "/com/feralinteractive/GameMode", "com.feralinteractive.GameMode", gamemode_vtable, context); if (ret < 0) { FATAL_ERROR("Failed to install GameMode object: %s\n", strerror(-ret)); } ret = sd_bus_add_fallback_vtable(bus, &slot, GAME_PATH_PREFIX, "com.feralinteractive.GameMode.Game", game_vtable, game_object_find, context); if (ret < 0) { FATAL_ERROR("Failed to install Game object: %s\n", strerror(-ret)); } ret = sd_bus_add_node_enumerator(bus, &slot, GAME_PATH_PREFIX, game_node_enumerator, context); if (ret < 0) { FATAL_ERROR("Failed to install Game object enumerator: %s\n", strerror(-ret)); } /* Request our name */ ret = sd_bus_request_name(bus, "com.feralinteractive.GameMode", 0); if (ret < 0) { FATAL_ERROR("Failed to acquire service name: %s\n", strerror(-ret)); } LOG_MSG("Successfully initialised bus with name [%s]...\n", "com.feralinteractive.GameMode"); sd_notifyf(0, "STATUS=%sGameMode is ready to be activated.%s\n", "\x1B[1;36m", "\x1B[0m"); /* Now loop, waiting for callbacks */ for (;;) { ret = sd_bus_process(bus, NULL); if (ret < 0) { FATAL_ERROR("Failure when processing the bus: %s\n", strerror(-ret)); } /* We're done processing */ if (ret > 0) { continue; } /* Wait for more */ ret = sd_bus_wait(bus, (uint64_t)-1); if (ret < 0 && -ret != EINTR) { FATAL_ERROR("Failure when waiting on bus: %s\n", strerror(-ret)); } } } struct GameModeIdleInhibitor { sd_bus *bus; unsigned int cookie; }; /** * Attempts to inhibit the screensaver * Uses the "org.freedesktop.ScreenSaver" interface */ GameModeIdleInhibitor *game_mode_create_idle_inhibitor(void) { sd_bus_message *msg = NULL; sd_bus *bus_local = NULL; sd_bus_error err = SD_BUS_ERROR_NULL; // Open the user bus int ret = sd_bus_open_user(&bus_local); if (ret < 0) { LOG_ERROR("Could not connect to user bus: %s\n", strerror(-ret)); return NULL; } ret = sd_bus_call_method(bus_local, "org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", "Inhibit", &err, &msg, "ss", "com.feralinteractive.GameMode", "GameMode Activated"); if (ret < 0) { LOG_ERROR( "Failed to call Inhibit on org.freedesktop.ScreenSaver: %s\n" "\t%s\n" "\t%s\n", strerror(-ret), err.name, err.message); sd_bus_close(bus_local); sd_bus_unrefp(&bus_local); return NULL; } // Read the reply unsigned int cookie = 0; ret = sd_bus_message_read(msg, "u", &cookie); if (ret < 0) { LOG_ERROR("Invalid response from Inhibit on org.freedesktop.ScreenSaver: %s\n", strerror(-ret)); sd_bus_close(bus_local); sd_bus_unrefp(&bus_local); return NULL; } GameModeIdleInhibitor *inhibitor = malloc(sizeof(GameModeIdleInhibitor)); if (inhibitor == NULL) { sd_bus_close(bus_local); sd_bus_unrefp(&bus_local); return NULL; } inhibitor->bus = bus_local; inhibitor->cookie = cookie; return inhibitor; } void game_mode_destroy_idle_inhibitor(GameModeIdleInhibitor *inhibitor) { sd_bus_message *msg = NULL; sd_bus_error err = SD_BUS_ERROR_NULL; if (inhibitor == NULL) { return; } int ret = sd_bus_call_method(inhibitor->bus, "org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", "UnInhibit", &err, &msg, "u", inhibitor->cookie); if (ret < 0) { LOG_ERROR( "Failed to call UnInhibit on org.freedesktop.ScreenSaver: %s\n" "\t%s\n" "\t%s\n", strerror(-ret), err.name, err.message); } sd_bus_close(inhibitor->bus); sd_bus_unrefp(&inhibitor->bus); free(inhibitor); } gamemode-1.8.2/daemon/gamemode-gpu.c000066400000000000000000000155611466061601300172710ustar00rootroot00000000000000 /* 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. */ #define _GNU_SOURCE #include "common-external.h" #include "common-gpu.h" #include "common-helpers.h" #include "common-logging.h" #include "gamemode.h" #include "gamemode-config.h" #include "build-config.h" _Static_assert(CONFIG_VALUE_MAX == GPU_VALUE_MAX, "Config max value and GPU value out of sync!"); /** * Attempts to identify the current in use GPU information */ int game_mode_initialise_gpu(GameModeConfig *config, GameModeGPUInfo **info) { /* Verify input, this is programmer error */ if (!info || *info) FATAL_ERROR("Invalid GameModeGPUInfo passed to %s", __func__); /* Early out if we have this feature turned off */ char apply[CONFIG_VALUE_MAX]; config_get_apply_gpu_optimisations(config, apply); if (strlen(apply) == 0) { return 0; } else if (strncmp(apply, "accept-responsibility", CONFIG_VALUE_MAX) != 0) { LOG_ERROR( "apply_gpu_optimisations set to value other than \"accept-responsibility\" (%s), will " "not apply GPU optimisations!\n", apply); return -1; } /* Create the context */ GameModeGPUInfo *new_info = malloc(sizeof(GameModeGPUInfo)); memset(new_info, 0, sizeof(GameModeGPUInfo)); /* Get the config parameters */ new_info->device = config_get_gpu_device(config); /* verify device ID */ if (new_info->device == -1) { LOG_ERROR( "Invalid gpu_device value set in configuration, will not apply " "optimisations!\n"); free(new_info); return -1; } /* Fill in GPU vendor */ new_info->vendor = gamemode_get_gpu_vendor(new_info->device); if (!GPUVendorValid(new_info->vendor)) { LOG_ERROR("Found invalid vendor, will not apply optimisations!\n"); free(new_info); return -1; } /* Load the config based on GPU and also verify the values are sane */ switch (new_info->vendor) { case Vendor_NVIDIA: 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_powermizer_mode = config_get_nv_powermizer_mode(config); /* Reject values over some guessed values * If a user wants to go into very unsafe levels they can recompile */ const int nv_core_hard_limit = 200; const int nv_mem_hard_limit = 2000; if (new_info->nv_core > nv_core_hard_limit || new_info->nv_mem > nv_mem_hard_limit) { LOG_ERROR( "NVIDIA Overclock value above safety levels of +%d (core) +%d (mem), will " "not overclock!\n", nv_core_hard_limit, nv_mem_hard_limit); LOG_ERROR("nv_core_clock_mhz_offset:%ld nv_mem_clock_mhz_offset:%ld\n", new_info->nv_core, new_info->nv_mem); free(new_info); return -1; } break; case Vendor_AMD: config_get_amd_performance_level(config, new_info->amd_performance_level); /* Error about unsupported "manual" option, for now */ if (strcmp(new_info->amd_performance_level, "manual") == 0) { LOG_ERROR("AMD Performance level set to \"manual\", this is currently unsupported"); free(new_info); return -1; } break; default: break; } /* Give back the new gpu info */ *info = new_info; return 0; } /* Simply used to free the GPU info object */ void game_mode_free_gpu(GameModeGPUInfo **info) { /* Simply free the object */ free(*info); *info = NULL; } /** * Applies GPU optimisations when gamemode is active and removes them after */ int game_mode_apply_gpu(const GameModeGPUInfo *info) { // Null info means don't apply anything if (!info) return 0; LOG_MSG("Requesting GPU optimisations on device:%ld\n", info->device); /* Generate the input strings */ char device[4]; snprintf(device, 4, "%ld", info->device); char nv_core[8]; snprintf(nv_core, 8, "%ld", info->nv_core); char nv_mem[8]; snprintf(nv_mem, 8, "%ld", info->nv_mem); char nv_powermizer_mode[4]; snprintf(nv_powermizer_mode, 4, "%ld", info->nv_powermizer_mode); // Set up our command line to pass to gpuclockctl const char *const exec_args[] = { "pkexec", LIBEXECDIR "/gpuclockctl", device, "set", 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_powermizer_mode : NULL, /* Only use this if Nvidia */ NULL, }; if (run_external_process(exec_args, NULL, -1) != 0) { LOG_ERROR("Failed to call gpuclockctl, could not apply optimisations!\n"); return -1; } return 0; } int game_mode_get_gpu(GameModeGPUInfo *info) { if (!info) return 0; /* Generate the input strings */ char device[4]; snprintf(device, 4, "%ld", info->device); // Set up our command line to pass to gpuclockctl // This doesn't need pkexec as get does not need elevated perms const char *const exec_args[] = { LIBEXECDIR "/gpuclockctl", device, "get", NULL, }; char buffer[EXTERNAL_BUFFER_MAX] = { 0 }; if (run_external_process(exec_args, buffer, -1) != 0) { LOG_ERROR("Failed to call gpuclockctl, could not get values!\n"); return -1; } strtok(buffer, "\n"); switch (info->vendor) { case Vendor_NVIDIA: if (sscanf(buffer, "%ld %ld %ld", &info->nv_core, &info->nv_mem, &info->nv_powermizer_mode) != 3) { LOG_ERROR("Failed to parse gpuclockctl output: %s\n", buffer); return -1; } break; case Vendor_AMD: strncpy(info->amd_performance_level, buffer, sizeof(info->amd_performance_level) - 1); info->amd_performance_level[sizeof(info->amd_performance_level) - 1] = '\0'; break; } return 0; } gamemode-1.8.2/daemon/gamemode-ioprio.c000066400000000000000000000127051466061601300177740ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include "gamemode.h" #include "common-helpers.h" #include "common-logging.h" #include "gamemode-config.h" #include #include /** * Define the syscall interface in Linux because it is missing from glibc */ #ifndef IOPRIO_BITS #define IOPRIO_BITS (16) #endif #ifndef IOPRIO_CLASS_SHIFT #define IOPRIO_CLASS_SHIFT (13) #endif #ifndef IOPRIO_PRIO_MASK #define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) #endif #ifndef IOPRIO_PRIO_CLASS #define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) #endif #ifndef IOPRIO_PRIO_DATA #define IOPRIO_PRIO_DATA(mask) ((mask)&IOPRIO_PRIO_MASK) #endif #ifndef IOPRIO_PRIO_VALUE #define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) #endif enum { IOPRIO_CLASS_NONE, IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE, }; enum { IOPRIO_WHO_PROCESS = 1, IOPRIO_WHO_PGRP, IOPRIO_WHO_USER, }; static inline int ioprio_set(int which, int who, int ioprio) { return (int)syscall(SYS_ioprio_set, which, who, ioprio); } static inline int ioprio_get(int which, int who) { return (int)syscall(SYS_ioprio_get, which, who); } /** * Get the i/o priorities */ int game_mode_get_ioprio(const pid_t client) { int ret = ioprio_get(IOPRIO_WHO_PROCESS, client); if (ret == -1) { LOG_ERROR("Failed to get ioprio value for [%d] with error %s\n", client, strerror(errno)); ret = IOPRIO_DONT_SET; } /* We support only IOPRIO_CLASS_BE as IOPRIO_CLASS_RT required CAP_SYS_ADMIN */ return IOPRIO_PRIO_DATA(ret); } /** * Apply io priorities * * This tries to change the io priority of the client to a value specified * and can possibly reduce lags or latency when a game has to load assets * on demand. */ void game_mode_apply_ioprio(const GameModeContext *self, const pid_t client, int expected) { if (expected == IOPRIO_DONT_SET) /* Silently bail if fed a don't set (invalid) */ return; GameModeConfig *config = game_mode_config_from_context(self); /* read configuration "ioprio" (0..7) */ int ioprio = (int)config_get_ioprio_value(config); /* Special value to simply not set the value */ if (ioprio == IOPRIO_DONT_SET) return; LOG_MSG("Setting ioprio value...\n"); /* If fed the default, we'll try and reset the value back */ if (expected != IOPRIO_DEFAULT) { expected = (int)ioprio; ioprio = IOPRIO_DEFAULT; } /* Open the tasks dir for the client */ char tasks[128]; snprintf(tasks, sizeof(tasks), "/proc/%d/task", client); DIR *client_task_dir = opendir(tasks); if (client_task_dir == NULL) { LOG_ERROR("Could not inspect tasks for client [%d]! Skipping ioprio optimisation.\n", client); return; } /* Iterate for all tasks of client process */ struct dirent *tid_entry; while ((tid_entry = readdir(client_task_dir)) != NULL) { /* Skip . and .. */ if (tid_entry->d_name[0] == '.') continue; /* task name is the name of the file */ int tid = atoi(tid_entry->d_name); int current = game_mode_get_ioprio(tid); if (current == IOPRIO_DONT_SET) { /* Couldn't get the ioprio value * This could simply mean that the thread exited before fetching the ioprio * So we should continue */ } else if (current != expected) { /* Don't try and adjust the ioprio value if the value we got doesn't match default */ LOG_ERROR("Skipping ioprio on client [%d,%d]: ioprio was (%d) but we expected (%d)\n", client, tid, current, expected); } else { /* * For now we only support IOPRIO_CLASS_BE * IOPRIO_CLASS_RT requires CAP_SYS_ADMIN but should be possible with a polkit process */ int p = ioprio; ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, ioprio); if (ioprio_set(IOPRIO_WHO_PROCESS, tid, ioprio) != 0) { /* This could simply mean the thread is gone now, as above */ LOG_ERROR( "Setting client [%d,%d] IO priority to (%d) failed with error %d, ignoring.\n", client, tid, p, errno); } } } closedir(client_task_dir); } gamemode-1.8.2/daemon/gamemode-sched.c000066400000000000000000000156741466061601300175710ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include "gamemode.h" #include "common-logging.h" #include "gamemode-config.h" #include #include #include #include /* SCHED_ISO may not be defined as it is a reserved value not yet * implemented in official kernel sources, see linux/sched.h. */ #ifndef SCHED_ISO #define SCHED_ISO 4 #endif /** * Apply scheduling policies * * This tries to change the scheduler of the client to soft realtime mode * available in some kernels as SCHED_ISO. It also tries to adjust the nice * level. If some of each fail, ignore this and log a warning. */ #define RENICE_INVALID -128 /* Special value to store invalid value */ int game_mode_get_renice(const pid_t client) { /* Clear errno as -1 is a regitimate return */ errno = 0; int priority = getpriority(PRIO_PROCESS, (id_t)client); if (priority == -1 && errno) { LOG_ERROR("getprority(PRIO_PROCESS, %d) failed : %s\n", client, strerror(errno)); return RENICE_INVALID; } return -priority; } /* If expected is 0 then we try to apply our renice, otherwise, we try to remove it */ void game_mode_apply_renice(const GameModeContext *self, const pid_t client, int expected) { if (expected == RENICE_INVALID) /* Silently bail if fed an invalid value */ return; GameModeConfig *config = game_mode_config_from_context(self); /* * read configuration "renice" (1..20) */ long int renice = config_get_renice_value(config); if (renice == 0) { return; } /* Invert the renice value */ renice = -renice; /* When expected is non-zero, we should try and remove the renice only if it doesn't match the * expected value */ if (expected != 0) { expected = (int)renice; renice = 0; } /* Open the tasks dir for the client */ char tasks[128]; snprintf(tasks, sizeof(tasks), "/proc/%d/task", client); DIR *client_task_dir = opendir(tasks); if (client_task_dir == NULL) { LOG_ERROR("Could not inspect tasks for client [%d]! Skipping ioprio optimisation.\n", client); return; } /* Iterate for all tasks of client process */ struct dirent *tid_entry; while ((tid_entry = readdir(client_task_dir)) != NULL) { /* Skip . and .. */ if (tid_entry->d_name[0] == '.') continue; /* task name is the name of the file */ int tid = atoi(tid_entry->d_name); /* Clear errno as -1 is a regitimate return */ errno = 0; int prio = getpriority(PRIO_PROCESS, (id_t)tid); if (prio == -1 && errno) { /* Process may well have ended */ LOG_ERROR("getpriority failed for client [%d,%d] with error: %s\n", client, tid, strerror(errno)); } else if (prio != expected) { /* * Don't adjust priority if it does not match the expected value * ie. Another process has changed it, or it began non-standard */ LOG_ERROR("Refused to renice client [%d,%d]: prio was (%d) but we expected (%d)\n", client, tid, prio, expected); } else if (setpriority(PRIO_PROCESS, (id_t)tid, (int)renice)) { LOG_HINTED(ERROR, "Failed to renice client [%d,%d], ignoring error condition: %s\n", " -- Your user may not have permission to do this. Please read the docs\n" " -- to learn how to adjust the pam limits.\n", client, tid, strerror(errno)); } } closedir(client_task_dir); } void game_mode_apply_scheduling(const GameModeContext *self, const pid_t client) { GameModeConfig *config = game_mode_config_from_context(self); /* * read configuration "softrealtime" (on, off, auto) */ char softrealtime[CONFIG_VALUE_MAX] = { 0 }; config_get_soft_realtime(config, softrealtime); /* * Enable unconditionally or auto-detect soft realtime usage, * auto detection is based on observations where dual-core CPU suffered * priority inversion problems with the graphics driver thus running * slower as a result, so enable only with more than 3 cores. */ bool enable_softrealtime = (strcmp(softrealtime, "on") == 0) || ((strcmp(softrealtime, "auto") == 0) && (get_nprocs() > 3)); /* * Actually apply the scheduler policy if not explicitly turned off */ if (enable_softrealtime) { const struct sched_param p = { .sched_priority = 0 }; if (sched_setscheduler(client, SCHED_ISO | SCHED_RESET_ON_FORK, &p)) { const char *hint = ""; HINT_ONCE_ON( errno == EPERM, hint, " -- The error indicates that you may be running a resource management\n" " -- daemon managing your game launcher and it leaks lower scheduling\n" " -- classes into the games. This is likely a bug in the management daemon\n" " -- and not a bug in GameMode, it should be reported upstream.\n" " -- If unsure, please also look here:\n" " -- https://github.com/FeralInteractive/gamemode/issues/68\n"); HINT_ONCE_ON( errno == EINVAL, hint, " -- The error indicates that your kernel may not support this. If you\n" " -- don't know what SCHED_ISO means, you can safely ignore this. If you\n" " -- expected it to work, ensure you're running a kernel with MuQSS or\n" " -- PDS scheduler.\n" " -- For further technical reading on the topic start here:\n" " -- https://lwn.net/Articles/720227/\n"); LOG_ERROR( "Failed setting client [%d] into SCHED_ISO mode, ignoring error condition: %s\n" "%s", client, strerror(errno), hint); } } } gamemode-1.8.2/daemon/gamemode-tests.c000066400000000000000000000703431466061601300176370ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include "common-external.h" #include "common-governors.h" #include "common-gpu.h" #include "common-helpers.h" #include "common-logging.h" #include "gamemode.h" #include "gamemode-config.h" #include "gamemode_client.h" #include #include #include struct GameModeConfig; /* Initial verify step to ensure gamemode isn't already active */ static int verify_gamemode_initial(struct GameModeConfig *config) { int status = 0; if ((status = gamemode_query_status()) != 0 && status != -1) { long reaper = config_get_reaper_frequency(config); LOG_MSG("GameMode was active, waiting for the reaper thread (%ld seconds)!\n", reaper); sleep(1); /* Try again after waiting */ for (int i = 0; i < reaper; i++) { if ((status = gamemode_query_status()) == 0) { status = 0; break; } else if (status == -1) { goto status_error; } LOG_MSG("Waiting...\n"); sleep(1); } if (status == 1) LOG_ERROR("GameMode still active, cannot run tests!\n"); } else if (status == -1) { goto status_error; } else { status = 0; } return status; status_error: LOG_ERROR("gamemode_query_status failed: %s!\n", gamemode_error_string()); LOG_ERROR("is gamemode installed correctly?\n"); return -1; } /* Check if gamemode is active and this client is registered */ static int verify_active_and_registered(void) { int status = gamemode_query_status(); if (status != 2) { if (status == -1) { LOG_ERROR("gamemode_query_status failed: %s\n", gamemode_error_string()); } else if (status == 1) { LOG_ERROR("gamemode was active but did not have this process registered\n"); } LOG_ERROR("gamemode failed to activate correctly when requested (expected 2)!\n"); status = -1; } else { status = 0; } return status; } /* Ensure gamemode is deactivated when it should be */ static int verify_deactivated(void) { int status = gamemode_query_status(); if (status != 0) { if (status == -1) { LOG_ERROR("gamemode_query_status failed: %s\n", gamemode_error_string()); } LOG_ERROR("gamemode failed to deactivate when requested (expected 0)!\n"); status = -1; } else { status = 0; } return status; } /* Ensure another client is connected */ static int verify_other_client_connected(void) { int status = gamemode_query_status(); if (status != 1) { if (status == -1) { LOG_ERROR("gamemode_query_status failed: %s\n", gamemode_error_string()); } LOG_ERROR("gamemode_query_status failed to return other client connected (expected 1)!\n"); status = -1; } else { status = 0; } return status; } /* Run basic client tests * Tests a simple request_start and request_end works */ static int run_basic_client_tests(void) { LOG_MSG(":: Basic client tests\n"); /* Verify that gamemode_request_start correctly start gamemode */ if (gamemode_request_start() != 0) { LOG_ERROR("gamemode_request_start failed: %s\n", gamemode_error_string()); return -1; } /* Verify that gamemode is now active and this client is registered*/ if (verify_active_and_registered() != 0) return -1; /* Verify that gamemode_request_end corrently de-registers gamemode */ if (gamemode_request_end() != 0) { LOG_ERROR("gamemode_request_end failed: %s!\n", gamemode_error_string()); return -1; } /* Verify that gamemode is now inactive */ if (verify_deactivated() != 0) return -1; LOG_MSG(":: Passed\n\n"); return 0; } /* Run some dual client tests * This also tests that the "-r" argument works correctly and cleans up correctly */ static int run_dual_client_tests(void) { int status = 0; /* Try running some process interop tests */ LOG_MSG(":: Dual client tests\n"); /* Get the current path to this binary */ char mypath[PATH_MAX]; memset(mypath, 0, sizeof(mypath)); if (readlink("/proc/self/exe", mypath, PATH_MAX) == -1) { LOG_ERROR("could not read current exe path: %s\n", strerror(errno)); return -1; } /* Fork so that the child can request gamemode */ int child = fork(); if (child == 0) { /* Relaunch self with -r (request and wait for signal) */ if (execl(mypath, mypath, "-r", (char *)NULL) == -1) { LOG_ERROR("failed to re-launch self (%s) with execl: %s\n", mypath, strerror(errno)); return -1; } } /* Parent process */ /* None of these should early-out as we need to clean up the child */ /* Give the child a chance to request gamemode */ usleep(10000); /* Check that when we request gamemode, it replies that the other client is connected */ if (verify_other_client_connected() != 0) status = -1; /* Verify that gamemode_request_start correctly start gamemode */ if (gamemode_request_start() != 0) { LOG_ERROR("gamemode_request_start failed: %s\n", gamemode_error_string()); status = -1; } /* Verify that gamemode is now active and this client is registered*/ if (verify_active_and_registered() != 0) status = -1; /* Request end of gamemode (de-register ourselves) */ if (gamemode_request_end() != 0) { LOG_ERROR("gamemode_request_end failed: %s!\n", gamemode_error_string()); status = -1; } /* Check that when we request gamemode, it replies that the other client is connected */ if (verify_other_client_connected() != 0) status = -1; /* Send SIGINT to child to wake it up*/ if (kill(child, SIGINT) == -1) { LOG_ERROR("failed to send continue signal to other client: %s\n", strerror(errno)); status = -1; } /* Give the child a chance to finish */ usleep(100000); // Wait for the child to finish up int wstatus; while (waitpid(child, &wstatus, WNOHANG) == 0) { LOG_MSG("...Waiting for child to quit...\n"); usleep(100000); } /* Verify that gamemode is now inactive */ if (verify_deactivated() != 0) return -1; if (status == 0) LOG_MSG(":: Passed\n\n"); return status; } /* Check gamemoderun works */ static int run_gamemoderun_and_reaper_tests(struct GameModeConfig *config) { int status = 0; LOG_MSG(":: Gamemoderun and reaper thread tests\n"); /* Fork so that the child can request gamemode */ int child = fork(); if (child == 0) { /* Close stdout, we don't care if sh prints anything */ fclose(stdout); /* Preload into sh and then kill it */ if (execlp("gamemoderun", "gamemoderun", "sleep", "5", (char *)NULL) == -1) { LOG_ERROR("failed to launch gamemoderun with execl: %s\n", strerror(errno)); return -1; } } /* Give the child a chance to reqeust gamemode */ usleep(100000); /* Check that when we request gamemode, it replies that the other client is connected */ if (verify_other_client_connected() != 0) status = -1; /* Send SIGTERM to the child to stop it*/ if (kill(child, SIGTERM) == -1) { LOG_ERROR("failed to send continue signal to other client: %s\n", strerror(errno)); status = -1; } /* Wait for the child to clean up */ int wstatus; while (waitpid(child, &wstatus, WNOHANG) == 0) { LOG_MSG("...Waiting for child to quit...\n"); usleep(100000); } /* And give gamemode a chance to reap the process */ long freq = config_get_reaper_frequency(config); LOG_MSG("...Waiting for reaper thread (reaper_frequency set to %ld seconds)...\n", freq); sleep((unsigned int)freq); /* Verify that gamemode is now inactive */ if (verify_deactivated() != 0) return -1; if (status == 0) LOG_MSG(":: Passed\n\n"); return status; } /* Check the cpu governor setting works */ static int run_cpu_governor_tests(struct GameModeConfig *config) { /* get the two config parameters we care about */ char desiredgov[CONFIG_VALUE_MAX] = { 0 }; config_get_desired_governor(config, desiredgov); if (desiredgov[0] == '\0') strcpy(desiredgov, "performance"); char defaultgov[CONFIG_VALUE_MAX] = { 0 }; if (defaultgov[0] == '\0') { const char *currentgov = get_gov_state(); if (currentgov) { strncpy(defaultgov, currentgov, CONFIG_VALUE_MAX - 1); } else { LOG_ERROR( "Could not get current CPU governor state, this indicates an error! See rest " "of log.\n"); return -1; } } /* Start gamemode */ gamemode_request_start(); /* Verify the governor is the desired one */ const char *currentgov = get_gov_state(); if (strncmp(currentgov, desiredgov, CONFIG_VALUE_MAX) != 0) { LOG_ERROR("Governor was not set to %s (was actually %s)!\n", desiredgov, currentgov); gamemode_request_end(); return -1; } /* End gamemode */ gamemode_request_end(); /* Verify the governor has been set back */ currentgov = get_gov_state(); if (strncmp(currentgov, defaultgov, CONFIG_VALUE_MAX) != 0) { LOG_ERROR("Governor was not set back to %s (was actually %s)!\n", defaultgov, currentgov); return -1; } return 0; } static int run_custom_scripts_tests(struct GameModeConfig *config) { int scriptstatus = 0; long timeout = config_get_script_timeout(config); /* Grab and test the start scripts */ char startscripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; memset(startscripts, 0, sizeof(startscripts)); config_get_gamemode_start_scripts(config, startscripts); if (startscripts[0][0] != '\0') { int i = 0; while (i < CONFIG_LIST_MAX && *startscripts[i] != '\0') { LOG_MSG(":::: Running start script [%s]\n", startscripts[i]); const char *args[] = { "/bin/sh", "-c", startscripts[i], NULL }; int ret = run_external_process(args, NULL, (int)timeout); if (ret == 0) LOG_MSG(":::: Passed\n"); else { LOG_MSG(":::: Failed!\n"); scriptstatus = -1; } i++; } } /* Grab and test the end scripts */ char endscripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]; memset(endscripts, 0, sizeof(endscripts)); config_get_gamemode_end_scripts(config, endscripts); if (endscripts[0][0] != '\0') { int i = 0; while (i < CONFIG_LIST_MAX && *endscripts[i] != '\0') { LOG_MSG(":::: Running end script [%s]\n", endscripts[i]); const char *args[] = { "/bin/sh", "-c", endscripts[i], NULL }; int ret = run_external_process(args, NULL, (int)timeout); if (ret == 0) LOG_MSG(":::: Passed\n"); else { LOG_MSG(":::: Failed!\n"); scriptstatus = -1; } i++; } } /* Specal value for no scripts */ if (endscripts[0][0] == '\0' && startscripts[0][0] == '\0') return 1; return scriptstatus; } int run_gpu_optimisation_tests(struct GameModeConfig *config) { int gpustatus = 0; /* First check if these are turned on */ char apply[CONFIG_VALUE_MAX]; config_get_apply_gpu_optimisations(config, apply); if (strlen(apply) == 0) { /* Special value for disabled */ return 1; } else if (strncmp(apply, "accept-responsibility", CONFIG_VALUE_MAX) != 0) { LOG_ERROR( "apply_gpu_optimisations set to value other than \"accept-responsibility\" (%s), will " "not apply GPU optimisations!\n", apply); return -1; } /* Get current GPU values */ GameModeGPUInfo *gpuinfo; game_mode_initialise_gpu(config, &gpuinfo); if (!gpuinfo) { LOG_ERROR("Failed to initialise gpuinfo!\n"); return -1; } /* Grab the expected values */ long expected_core = gpuinfo->nv_core; long expected_mem = gpuinfo->nv_mem; long expected_nv_powermizer_mode = gpuinfo->nv_powermizer_mode; char expected_amd_performance_level[CONFIG_VALUE_MAX]; strncpy(expected_amd_performance_level, gpuinfo->amd_performance_level, CONFIG_VALUE_MAX - 1); expected_amd_performance_level[CONFIG_VALUE_MAX - 1] = '\0'; /* Get current stats */ game_mode_get_gpu(gpuinfo); long original_nv_core = gpuinfo->nv_core; long original_nv_mem = gpuinfo->nv_mem; long original_nv_powermizer_mode = gpuinfo->nv_powermizer_mode; char original_amd_performance_level[CONFIG_VALUE_MAX]; strncpy(original_amd_performance_level, gpuinfo->amd_performance_level, CONFIG_VALUE_MAX - 1); original_amd_performance_level[CONFIG_VALUE_MAX - 1] = '\0'; /* account for when settings are not set */ if (expected_nv_powermizer_mode == -1) expected_nv_powermizer_mode = original_nv_powermizer_mode; if (expected_core == -1) expected_core = original_nv_core; if (expected_mem == -1) expected_mem = original_nv_mem; /* Start gamemode and check the new values */ gamemode_request_start(); if (game_mode_get_gpu(gpuinfo) != 0) { LOG_ERROR("Could not get current GPU info, see above!\n"); gamemode_request_end(); game_mode_free_gpu(&gpuinfo); return -1; } if (gpuinfo->vendor == Vendor_NVIDIA && (gpuinfo->nv_core != expected_core || gpuinfo->nv_mem != expected_mem || gpuinfo->nv_powermizer_mode != expected_nv_powermizer_mode)) { LOG_ERROR( "Current Nvidia GPU clocks during gamemode do not match requested values!\n" "\tnv_core - expected:%ld was:%ld | nv_mem - expected:%ld was:%ld | nv_powermizer_mode " "- expected:%ld was:%ld\n", expected_core, gpuinfo->nv_core, expected_mem, gpuinfo->nv_mem, expected_nv_powermizer_mode, gpuinfo->nv_powermizer_mode); gpustatus = -1; } else if (gpuinfo->vendor == Vendor_AMD && strcmp(expected_amd_performance_level, gpuinfo->amd_performance_level) != 0) { LOG_ERROR( "Current AMD GPU performance level during gamemode does not match requested value!\n" "\texpected:%s was:%s\n", expected_amd_performance_level, gpuinfo->amd_performance_level); gpustatus = -1; } /* End gamemode and check the values have returned */ gamemode_request_end(); if (game_mode_get_gpu(gpuinfo) != 0) { LOG_ERROR("Could not get current GPU info, see above!\n"); game_mode_free_gpu(&gpuinfo); return -1; } if (gpuinfo->vendor == Vendor_NVIDIA && (gpuinfo->nv_core != original_nv_core || gpuinfo->nv_mem != original_nv_mem || gpuinfo->nv_powermizer_mode != original_nv_powermizer_mode)) { LOG_ERROR( "Current Nvidia GPU clocks after gamemode do not matcch original values!\n" "\tnv_core - original:%ld was:%ld | nv_mem - original:%ld was:%ld | nv_powermizer_mode " "- original:%ld was:%ld\n", original_nv_core, gpuinfo->nv_core, original_nv_mem, gpuinfo->nv_mem, original_nv_powermizer_mode, gpuinfo->nv_powermizer_mode); gpustatus = -1; } else if (gpuinfo->vendor == Vendor_AMD && strcmp(original_amd_performance_level, gpuinfo->amd_performance_level) != 0) { LOG_ERROR( "Current AMD GPU performance level after gamemode does not match requested value!\n" "\texpected:%s was:%s\n", original_amd_performance_level, gpuinfo->amd_performance_level); gpustatus = -1; } return gpustatus; } /** * Multithreaded process simulation * * Some of the optimisations that gamemode implements needs to be tested against a full process * tree, otherwise we may only be applying them to only the main thread */ typedef struct { pthread_barrier_t *barrier; pid_t this; } ThreadInfo; static void *fake_thread_wait(void *arg) { ThreadInfo *info = (ThreadInfo *)arg; /* Store the thread ID */ info->this = (pid_t)syscall(SYS_gettid); /** * Wait twice * First to sync that all threads have started * Second to sync all threads exiting */ int ret = 0; ret = pthread_barrier_wait(info->barrier); if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) FATAL_ERROR("pthread_barrier_wait failed in child with error %d!\n", ret); ret = pthread_barrier_wait(info->barrier); if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) FATAL_ERROR("pthread_barrier_wait failed in child with error %d!\n", ret); return NULL; } /* Runs a process tree in a child and tests each thread */ static pid_t run_tests_on_process_tree(int inactive, int active, int (*func)(pid_t)) { /* Create a fake game-like multithreaded fork */ pid_t child = fork(); if (child == 0) { /* Some stetup */ bool fail = false; const unsigned int numthreads = 3; pthread_barrier_t barrier; pthread_barrier_init(&barrier, NULL, numthreads + 1); /* First, request gamemode for this child process before it created the threads */ gamemode_request_start(); /* Spawn a few child threads */ pthread_t threads[numthreads]; ThreadInfo info[numthreads]; for (unsigned int i = 0; i < numthreads; i++) { info[i].barrier = &barrier; int err = pthread_create(&threads[i], NULL, fake_thread_wait, &info[i]); if (err != 0) { LOG_ERROR("Failed to spawn thread! Error: %d\n", err); exit(EXIT_FAILURE); } } /* Wait for threads to be created */ pthread_barrier_wait(&barrier); /* Test each spawned thread */ for (unsigned int i = 0; i < numthreads; i++) fail |= (active != func(info[i].this)); if (fail) { LOG_ERROR("Initial values for new threads were incorrect!\n"); gamemode_request_end(); exit(-1); } /* Request gamemode end */ gamemode_request_end(); /* Test each spawned thread */ for (unsigned int i = 0; i < numthreads; i++) fail |= (inactive != func(info[i].this)); if (fail) { LOG_ERROR("values for threads were not reset after gamemode_request_end!\n"); exit(-1); } /* Request gamemode again - this time after threads were created */ gamemode_request_start(); /* Test each spawned thread */ for (unsigned int i = 0; i < numthreads; i++) fail |= (active != func(info[i].this)); if (fail) { LOG_ERROR("values for threads were not set correctly!\n"); gamemode_request_end(); exit(-1); } /* Request gamemode end */ gamemode_request_end(); /* Test each spawned thread */ for (unsigned int i = 0; i < numthreads; i++) fail |= (inactive != func(info[i].this)); if (fail) { LOG_ERROR("values for threads were not reset after gamemode_request_end!\n"); exit(-1); } /* Tell the threads to continue */ pthread_barrier_wait(&barrier); /* Wait for threads to join */ int ret = 0; for (unsigned int i = 0; i < numthreads; i++) ret &= pthread_join(threads[i], NULL); if (ret != 0) LOG_ERROR("Thread cleanup in multithreaded tests failed!\n"); /* We're done, so return the error code generated */ exit(ret); } /* Wait for the child */ int wstatus = 0; waitpid(child, &wstatus, 0); int status = 0; if (WIFEXITED(wstatus)) status = WEXITSTATUS(wstatus); else { LOG_ERROR("Multithreaded child exited abnormally!\n"); status = -1; } return status; } int run_renice_tests(struct GameModeConfig *config) { /* read configuration "renice" (1..20) */ long int renice = config_get_renice_value(config); if (renice == 0) { return 1; /* not configured */ } /* Verify renice starts at 0 */ int val = game_mode_get_renice(getpid()); if (val != 0) { LOG_ERROR("Initial renice value is non-zero: %d\n", val); return -1; } int ret = 0; /* Ask for gamemode for ourselves */ gamemode_request_start(); /* Check renice is now requested value */ val = game_mode_get_renice(getpid()); if (val != renice) { LOG_ERROR( "renice value not set correctly after gamemode_request_start\nExpected: %ld, Was: %d\n", renice, val); ret = -1; } /* End gamemode for ourselves */ gamemode_request_end(); /* Check renice is returned to correct value */ val = game_mode_get_renice(getpid()); if (val != 0) { LOG_ERROR("renice value non-zero after gamemode_request_end\nExpected: 0, Was: %d\n", val); ret = -1; } /* Check multiprocess nice works as well */ val = run_tests_on_process_tree(0, (int)renice, game_mode_get_renice); if (val != 0) { LOG_ERROR("Multithreaded renice tests failed!\n"); ret = -1; } return ret; } int run_ioprio_tests(struct GameModeConfig *config) { /* read configuration "ioprio" */ long int ioprio = config_get_ioprio_value(config); if (ioprio == IOPRIO_DONT_SET) { return 1; /* not configured */ } /* Verify ioprio starts at 0 */ int val = game_mode_get_ioprio(getpid()); if (val != IOPRIO_DEFAULT) { LOG_ERROR("Initial ioprio value is non-default\nExpected: %d, Was: %d\n", IOPRIO_DEFAULT, val); return -1; } int ret = 0; /* Ask for gamemode for ourselves */ gamemode_request_start(); /* Check renice is now requested value */ val = game_mode_get_ioprio(getpid()); if (val != ioprio) { LOG_ERROR( "ioprio value not set correctly after gamemode_request_start\nExpected: %ld, Was: %d\n", ioprio, val); ret = -1; } /* End gamemode for ourselves */ gamemode_request_end(); /* Check ioprio is returned to correct value */ val = game_mode_get_ioprio(getpid()); if (val != IOPRIO_DEFAULT) { LOG_ERROR("ioprio value non-default after gamemode_request_end\nExpected: %d, Was: %d\n", IOPRIO_DEFAULT, val); ret = -1; } /* Check multiprocess nice works as well */ val = run_tests_on_process_tree(IOPRIO_DEFAULT, (int)ioprio, game_mode_get_ioprio); if (val != 0) { LOG_ERROR("Multithreaded ioprio tests failed!\n"); ret = -1; } return ret; } /** * 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 */ static int game_mode_run_feature_tests(struct GameModeConfig *config) { int status = 0; LOG_MSG(":: Feature tests\n"); /* If we reach here, we should assume the basic requests and register functions are working */ /* Does the CPU governor get set properly? */ { LOG_MSG("::: Verifying CPU governor setting\n"); int cpustatus = run_cpu_governor_tests(config); if (cpustatus == 0) LOG_MSG("::: Passed\n"); else { LOG_MSG("::: Failed!\n"); // Consider the CPU governor feature required status = -1; } } /* Do custom scripts run? */ { LOG_MSG("::: Verifying Scripts\n"); int scriptstatus = run_custom_scripts_tests(config); if (scriptstatus == 1) LOG_MSG("::: Passed (no scripts configured to run)\n"); else if (scriptstatus == 0) LOG_MSG("::: Passed\n"); else { LOG_MSG("::: Failed!\n"); // Any custom scripts should be expected to work status = -1; } } /* Do GPU optimisations get applied? */ { LOG_MSG("::: Verifying GPU Optimisations\n"); int gpustatus = run_gpu_optimisation_tests(config); if (gpustatus == 1) LOG_MSG("::: Passed (gpu optimisations not configured to run)\n"); else if (gpustatus == 0) LOG_MSG("::: Passed\n"); else { LOG_MSG("::: Failed!\n"); // Any custom scripts should be expected to work status = -1; } } /* Was the process reniced? */ { LOG_MSG("::: Verifying renice\n"); int renicestatus = run_renice_tests(config); if (renicestatus == 1) LOG_MSG("::: Passed (no renice configured)\n"); else if (renicestatus == 0) LOG_MSG("::: Passed\n"); else { LOG_MSG("::: Failed!\n"); // Renice should be expected to work, if set status = -1; } } /* Was the process ioprio set? */ { LOG_MSG("::: Verifying ioprio\n"); int iopriostatus = run_ioprio_tests(config); if (iopriostatus == 1) LOG_MSG("::: Passed (no ioprio configured)\n"); else if (iopriostatus == 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 * has no query method */ if (status != -1) LOG_MSG(":: Passed%s\n\n", status > 0 ? " (with optional failures)" : ""); else LOG_ERROR(":: Failed!\n"); return status; } /* Run a set of tests on the supervisor code */ static int run_supervisor_tests(void) { int supervisortests = 0; int ret = 0; LOG_MSG(":: Supervisor tests\n"); /* Launch an external dummy process we can leave running and request gamemode for it */ pid_t pid = fork(); if (pid == 0) { /* Child simply pauses and exits */ pause(); exit(EXIT_SUCCESS); } /* Request gamemode for our dummy process */ ret = gamemode_request_start_for(pid); if (ret != 0) { LOG_ERROR("gamemode_request_start_for gave unexpected value %d, (expected 0)!\n", ret); if (ret == -1) LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string()); supervisortests = -1; } /* Check it's active */ ret = gamemode_query_status(); if (ret != 1) { LOG_ERROR( "gamemode_query_status after start request gave unexpected value %d, (expected 1)!\n", ret); if (ret == -1) LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string()); supervisortests = -1; } /* Check it's active for the dummy */ ret = gamemode_query_status_for(pid); if (ret != 2) { LOG_ERROR( "gamemode_query_status_for after start request gave unexpected value %d, (expected " "2)!\n", ret); if (ret == -1) LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string()); supervisortests = -1; } /* request gamemode end for the client */ ret = gamemode_request_end_for(pid); if (ret != 0) { LOG_ERROR("gamemode_request_end_for gave unexpected value %d, (expected 0)!\n", ret); if (ret == -1) LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string()); supervisortests = -1; } /* Verify it's not active */ ret = gamemode_query_status(); if (ret != 0) { LOG_ERROR( "gamemode_query_status after end request gave unexpected value %d, (expected 0)!\n", ret); if (ret == -1) LOG_ERROR("GameMode error string: %s!\n", gamemode_error_string()); supervisortests = -1; } /* Wake up the child process */ if (kill(pid, SIGUSR1) == -1) { LOG_ERROR("failed to send continue signal to other child process: %s\n", strerror(errno)); supervisortests = -1; } // Wait for the child to finish up int wstatus; usleep(100000); while (waitpid(pid, &wstatus, WNOHANG) == 0) { LOG_MSG("...Waiting for child to quit...\n"); usleep(100000); } if (supervisortests == 0) LOG_MSG(":: Passed\n\n"); else LOG_ERROR(":: Failed!\n"); return supervisortests; } /** * game_mode_run_client_tests runs a set of tests of the client code * we simply verify that the client can request the status and recieves the correct results * * returns 0 for success, -1 for failure */ int game_mode_run_client_tests(void) { int status = 0; LOG_MSG(": Loading config\n"); /* Grab the config */ /* Note: this config may pick up a local gamemode.ini, or the daemon may have one, we may need * to cope with that */ GameModeConfig *config = config_create(); config_init(config); LOG_MSG(": Running tests\n\n"); /* First verify that gamemode is not currently active on the system * As well as it being currently installed and queryable */ if (verify_gamemode_initial(config) != 0) return -1; /* Controls whether we require a supervisor to actually make requests */ if (config_get_require_supervisor(config) != 0) { LOG_ERROR("Tests currently unsupported when require_supervisor is set\n"); return -1; } /* TODO: Also check blacklist/whitelist values as these may mess up the tests below */ /* Run the basic tests */ if (run_basic_client_tests() != 0) status = -1; /* Run the dual client tests */ if (run_dual_client_tests() != 0) status = -1; /* Check gamemoderun and the reaper thread work */ if (run_gamemoderun_and_reaper_tests(config) != 0) status = -1; /* Run the supervisor tests */ if (run_supervisor_tests() != 0) status = -1; if (status != 0) { LOG_MSG(": Client tests failed, skipping feature tests\n"); } else { /* Run the feature tests */ status = game_mode_run_feature_tests(config); } if (status >= 0) LOG_MSG(": All Tests Passed%s!\n", status > 0 ? " (with optional failures)" : ""); else LOG_MSG(": Tests Failed!\n"); return status; } gamemode-1.8.2/daemon/gamemode-wine.c000066400000000000000000000165271466061601300174430ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include "gamemode.h" #include "common-helpers.h" #include "common-logging.h" #include #include #include /** * Detect if the process is a wine preloader process */ static bool game_mode_detect_wine_preloader(const char *exe) { return (strtail(exe, "/wine-preloader") || strtail(exe, "/wine64-preloader")); } /** * Detect if the process is a wine loader process */ static bool game_mode_detect_wine_loader(const char *exe) { return (strtail(exe, "/wine") || strtail(exe, "/wine64")); } /** * Opens the process environment for a specific PID and returns * a file descriptor to the directory /proc/PID. Doing it that way prevents * the directory going MIA when a process exits while we are looking at it * and allows us to handle fewer error cases. */ static procfd_t game_mode_open_proc(const pid_t pid) { char buffer[PATH_MAX]; const char *proc_path = buffered_snprintf(buffer, "/proc/%d", pid); return proc_path ? open(proc_path, O_RDONLY | O_CLOEXEC) : INVALID_PROCFD; } /** * Closes the process environment. */ static int game_mode_close_proc(const procfd_t procfd) { return close(procfd); } /** * Lookup the process environment for a specific variable or return NULL. * Requires an open directory FD from /proc/PID. */ static char *game_mode_lookup_proc_env(const procfd_t proc_fd, const char *var) { char *environ = NULL; int fd = openat(proc_fd, "environ", O_RDONLY | O_CLOEXEC); if (fd != -1) { FILE *stream = fdopen(fd, "r"); if (stream) { /* Read every \0 terminated line from the environment */ char *line = NULL; size_t len = 0; size_t pos = strlen(var) + 1; while (!environ && (getdelim(&line, &len, 0, stream) != -1)) { /* Find a match including the "=" suffix */ if ((len > pos) && (strncmp(line, var, strlen(var)) == 0) && (line[pos - 1] == '=')) environ = strndup(line + pos, len - pos); } free(line); fclose(stream); } else close(fd); } /* If found variable is empty, skip it */ if (environ && !strlen(environ)) { free(environ); environ = NULL; } return environ; } /** * Lookup the home directory of the user in a safe way. */ static char *game_mode_lookup_user_home(void) { /* Try loading env HOME first */ const char *home = secure_getenv("HOME"); if (!home) { /* If HOME is not defined (or out of context), fall back to passwd */ struct passwd *pw = getpwuid(getuid()); if (!pw) return NULL; home = pw->pw_dir; } /* Try to allocate into our heap */ return home ? strdup(home) : NULL; } /** * Attempt to resolve the exe for wine-preloader. * This function is used if game_mode_context_find_exe() identified the * process as wine-preloader. Returns NULL when resolve fails. */ char *game_mode_resolve_wine_preloader(const char *exe, const pid_t pid) { /* Detect if the process is a wine loader process */ if (game_mode_detect_wine_preloader(exe) || game_mode_detect_wine_loader(exe)) { LOG_MSG("Detected wine for client %d [%s].\n", pid, exe); } else { return NULL; } char buffer[PATH_MAX]; char *wine_exe = NULL, *wineprefix = NULL; /* Open the directory, we are potentially reading multiple files from it */ procfd_t proc_fd = game_mode_open_proc(pid); if (proc_fd == INVALID_PROCFD) goto fail_proc; /* Open the command line */ int fd = openat(proc_fd, "cmdline", O_RDONLY | O_CLOEXEC); if (fd != -1) { FILE *stream = fdopen(fd, "r"); if (stream) { char *argv = NULL; size_t args = 0; int argc = 0; while (!wine_exe && (argc++ < 2) && (getdelim(&argv, &args, 0, stream) != -1)) { /* If we see the wine loader here, we have to use the next argument */ if (strtail(argv, "/wine") || strtail(argv, "/wine64")) continue; free(wine_exe); // just in case /* Check presence of the drive letter, we assume that below */ wine_exe = args > 2 && argv[1] == ':' ? strndup(argv, args) : NULL; } free(argv); fclose(stream); } else close(fd); } /* Did we get wine exe from cmdline? */ if (wine_exe) LOG_MSG("Detected wine exe for client %d [%s].\n", pid, wine_exe); else goto fail_cmdline; /* Open the process environment and find the WINEPREFIX */ errno = 0; if (!(wineprefix = game_mode_lookup_proc_env(proc_fd, "WINEPREFIX"))) { /* Lookup user home instead only if there was no error */ char *home = NULL; if (errno == 0) home = game_mode_lookup_user_home(); /* Append "/.wine" if we found the user home */ if (home) wineprefix = safe_snprintf(buffer, "%s/.wine", home); /* Cleanup and check result */ free(home); if (!wineprefix) goto fail_env; } /* Wine prefix was detected, log this for diagnostics */ LOG_MSG("Detected wine prefix for client %d: '%s'\n", pid, wineprefix); /* Convert Windows to Unix path separators */ char *ix = wine_exe; while (ix != NULL) (ix = strchr(ix, '\\')) && (*ix++ = '/'); /* Convert the drive letter to lcase because wine handles it this way in the prefix */ wine_exe[0] = (char)tolower(wine_exe[0]); /* Convert relative wine exe path to full unix path */ char *wine_path = buffered_snprintf(buffer, "%s/dosdevices/%s", wineprefix, wine_exe); free(wine_exe); wine_exe = wine_path ? realpath(wine_path, NULL) : NULL; /* Fine? Successo? Fortuna! */ if (wine_exe) LOG_MSG("Successfully mapped wine client %d [%s].\n", pid, wine_exe); else goto fail; error_cleanup: if (proc_fd != INVALID_PROCFD) game_mode_close_proc(proc_fd); free(wineprefix); return wine_exe; fail: LOG_ERROR("Unable to find wine executable for client %d: %s\n", pid, strerror(errno)); goto error_cleanup; fail_cmdline: LOG_ERROR("Wine loader has no accepted cmdline for client %d yet, deferring.\n", pid); goto error_cleanup; fail_env: LOG_ERROR("Failed to access process environment for client %d: %s\n", pid, strerror(errno)); goto error_cleanup; fail_proc: LOG_ERROR("Failed to access process data for client %d: %s\n", pid, strerror(errno)); goto error_cleanup; } gamemode-1.8.2/daemon/gamemode.h000066400000000000000000000170071466061601300165020ustar00rootroot00000000000000/* 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 #include #include #define INVALID_PROCFD -1 typedef int procfd_t; /** * Opaque types */ typedef struct GameModeContext GameModeContext; typedef struct GameModeConfig GameModeConfig; typedef struct GameModeClient GameModeClient; /** * GameModeClient related functions */ /** * Decrement the usage count of client. */ void game_mode_client_unref(GameModeClient *client); /** * Increment the usage count of client. */ void game_mode_client_ref(GameModeClient *client); /** * The process identifier of the client. */ pid_t game_mode_client_get_pid(GameModeClient *client); /** * The path to the executable of client. */ const char *game_mode_client_get_executable(GameModeClient *client); /** * The process identifier of the requester. */ pid_t game_mode_client_get_requester(GameModeClient *client); /** * The time that game mode was requested for the client. */ u_int64_t game_mode_client_get_timestamp(GameModeClient *client); /** * Return the singleton instance */ GameModeContext *game_mode_context_instance(void); /** * Initialise the GameModeContext * * This is performed in a thread-safe fashion. */ void game_mode_context_init(GameModeContext *self); /** * Destroy the previously initialised GameModeContext. * * This is performed in a thread safe fashion. */ void game_mode_context_destroy(GameModeContext *self); /** * Query the number of currently registered clients. * * @returns The number of clients. A number > 0 means that gamemode is active. */ int game_mode_context_num_clients(GameModeContext *self); /** * List the currently active clients. * @param out holds the number of active clients. * * @returns A array of pid_t or NULL if there are no active clients. */ pid_t *game_mode_context_list_clients(GameModeContext *self, unsigned int *count); /** * Lookup up information about a client via the pid; * * @returns A pointer to a GameModeClient struct or NULL in case no client * with the corresponding id could be found. Adds a reference to * GameModeClient that needs to be released. */ GameModeClient *game_mode_context_lookup_client(GameModeContext *self, pid_t client); /** * Register a new game client with the context * * @param pid Process ID for the remote client * @param requester Process ID for the remote requestor * @returns 0 if the request was accepted and the client could be registered * -1 if the request was accepted but the client could not be registered * -2 if the request was rejected */ int game_mode_context_register(GameModeContext *self, pid_t pid, pid_t requester); /** * Unregister an existing remote game client from the context * * @param pid Process ID for the remote client * @param requester Process ID for the remote requestor * @returns 0 if the request was accepted and the client existed * -1 if the request was accepted but the client did not exist * -2 if the request was rejected */ int game_mode_context_unregister(GameModeContext *self, pid_t pid, pid_t requester); /** * Query the current status of gamemode * * @param pid Process ID for the remote client * @returns Positive if gamemode is active * 1 if gamemode is active but the client is not registered * 2 if gamemode is active and the client is registered * -2 if this requester was rejected */ int game_mode_context_query_status(GameModeContext *self, pid_t pid, pid_t requester); /** * Query the config of a gamemode context * * @param context A gamemode context * @returns Configuration from the gamemode context */ GameModeConfig *game_mode_config_from_context(const GameModeContext *context); /* * Reload the current configuration */ int game_mode_reload_config(GameModeContext *context); /** gamemode-ioprio.c * Provides internal API functions specific to adjusting process * IO priorities. */ int game_mode_get_ioprio(const pid_t client); void game_mode_apply_ioprio(const GameModeContext *self, const pid_t client, int expected); /** gamemode-sched.c * Provides internal API functions specific to adjusting process * scheduling. */ int game_mode_get_renice(const pid_t client); void game_mode_apply_renice(const GameModeContext *self, const pid_t client, int expected); void game_mode_apply_scheduling(const GameModeContext *self, const pid_t client); /** gamemode-wine.c * Provides internal API functions specific to handling wine * prefixes. */ char *game_mode_resolve_wine_preloader(const char *exe, const pid_t pid); /** gamemode-tests.c * Provides a test suite to verify gamemode behaviour */ int game_mode_run_client_tests(void); /** gamemode-gpu.c * Provides internal APU functions to apply optimisations to gpus */ typedef struct GameModeGPUInfo GameModeGPUInfo; int game_mode_initialise_gpu(GameModeConfig *config, GameModeGPUInfo **info); void game_mode_free_gpu(GameModeGPUInfo **info); int game_mode_apply_gpu(const GameModeGPUInfo *info); int game_mode_get_gpu(GameModeGPUInfo *info); /** gamemode-cpu.c * Provides internal functions to apply optimisations to cpus */ typedef struct GameModeCPUInfo GameModeCPUInfo; int game_mode_initialise_cpu(GameModeConfig *config, GameModeCPUInfo **info); void game_mode_free_cpu(GameModeCPUInfo **info); void game_mode_reconfig_cpu(GameModeConfig *config, GameModeCPUInfo **info); int game_mode_park_cpu(const GameModeCPUInfo *info); int game_mode_unpark_cpu(const GameModeCPUInfo *info); void game_mode_apply_core_pinning(const GameModeCPUInfo *info, const pid_t client, const bool be_silent); void game_mode_undo_core_pinning(const GameModeCPUInfo *info, const pid_t client); /** gamemode-dbus.c * Provides an API interface for using dbus */ typedef struct GameModeIdleInhibitor GameModeIdleInhibitor; void game_mode_context_loop(GameModeContext *context) __attribute__((noreturn)); GameModeIdleInhibitor *game_mode_create_idle_inhibitor(void); void game_mode_destroy_idle_inhibitor(GameModeIdleInhibitor *inhibitor); void game_mode_client_registered(pid_t); void game_mode_client_unregistered(pid_t); gamemode-1.8.2/daemon/gamemoded.c000066400000000000000000000252101466061601300166340ustar00rootroot00000000000000/* 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. */ /** * Simple daemon to allow user space programs to control the CPU governors * * The main process is responsible for bootstrapping the D-BUS daemon, caching * the initial governor settings, and then responding to requests over D-BUS. * * Clients register their pid(s) with the service, which are routinely checked * to see if they've expired. Once we reach our first actively registered client * we put the system into "game mode", i.e. move the CPU governor into a performance * mode. * * Upon exit, or when all clients have stopped running, we put the system back * into the default governor policy, which is invariably powersave or similar * on laptops. This ensures that the system is obtaining the maximum performance * whilst gaming, and allowed to sanely return to idle once the workload is * complete. */ #define _GNU_SOURCE #include "gamemode.h" #include "common-logging.h" #include "gamemode-config.h" #include "gamemode_client.h" #include "build-config.h" #include #include #include #include #include /* TODO: Move usage to gamemode-dbus.c */ #include #define USAGE_TEXT \ "Usage: %s [-d] [-l] [-r] [-t] [-h] [-v]\n\n" \ " -r[PID], --request=[PID] Toggle gamemode for process\n" \ " When no PID given, requests gamemode and pauses\n" \ " -s[PID], --status=[PID] Query the status of gamemode for process\n" \ " When no PID given, queries the status globally\n" \ " -d, --daemonize Daemonize self after launch\n" \ " -l, --log-to-syslog Log to syslog\n" \ " -t, --test Run tests\n" \ " -h, --help Print this help\n" \ " -v, --version Print version\n" \ "\n" \ "See man page for more information.\n" #define VERSION_TEXT "gamemode version: v" GAMEMODE_VERSION "\n" static void sigint_handler(__attribute__((unused)) int signo) { LOG_MSG("Quitting by request...\n"); sd_notify(0, "STATUS=GameMode is quitting by request...\n"); /* Clean up nicely */ game_mode_context_destroy(game_mode_context_instance()); _Exit(EXIT_SUCCESS); } static void sigint_handler_noexit(__attribute__((unused)) int signo) { LOG_MSG("Quitting by request...\n"); } /** * Helper to perform standard UNIX daemonization */ static void daemonize(const char *name) { /* Initial fork */ pid_t pid = fork(); if (pid < 0) { FATAL_ERRORNO("Failed to fork"); } if (pid != 0) { LOG_MSG("Daemon launched as %s...\n", name); exit(EXIT_SUCCESS); } /* Fork a second time */ pid = fork(); if (pid < 0) { FATAL_ERRORNO("Failed to fork"); } else if (pid > 0) { exit(EXIT_SUCCESS); } /* Now continue execution */ umask(0022); if (setsid() < 0) { FATAL_ERRORNO("Failed to create process group\n"); } if (chdir("/") < 0) { FATAL_ERRORNO("Failed to change to root directory\n"); } /* replace standard file descriptors by /dev/null */ int devnull_r = open("/dev/null", O_RDONLY); int devnull_w = open("/dev/null", O_WRONLY); if (devnull_r == -1 || devnull_w == -1) { LOG_ERROR("Failed to redirect standard input and output to /dev/null\n"); } else { dup2(devnull_r, STDIN_FILENO); dup2(devnull_w, STDOUT_FILENO); dup2(devnull_w, STDERR_FILENO); close(devnull_r); close(devnull_w); } } /** * Main bootstrap entry into gamemoded */ int main(int argc, char *argv[]) { GameModeContext *context = NULL; /* Gather command line options */ bool daemon = false; bool use_syslog = false; int opt = 0; /* Options struct for getopt_long */ static struct option long_options[] = { { "daemonize", no_argument, 0, 'd' }, { "log-to-syslog", no_argument, 0, 'l' }, { "request", optional_argument, 0, 'r' }, { "test", no_argument, 0, 't' }, { "status", optional_argument, 0, 's' }, { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 'v' }, { NULL, 0, NULL, 0 }, }; static const char *short_options = "dls::r::tvh"; while ((opt = getopt_long(argc, argv, short_options, long_options, 0)) != -1) { switch (opt) { case 'd': daemon = true; break; case 'l': use_syslog = true; break; case 's': if (optarg != NULL) { pid_t pid = atoi(optarg); switch (gamemode_query_status_for(pid)) { case 0: /* inactive */ LOG_MSG("gamemode is inactive\n"); break; case 1: /* active not not registered */ LOG_MSG("gamemode is active but [%d] not registered\n", pid); break; case 2: /* active for client */ LOG_MSG("gamemode is active and [%d] registered\n", pid); break; case -1: LOG_ERROR("gamemode_query_status_for(%d) failed: %s\n", pid, gamemode_error_string()); exit(EXIT_FAILURE); default: LOG_ERROR("gamemode_query_status returned unexpected value 2\n"); exit(EXIT_FAILURE); } } else { int ret = 0; switch ((ret = gamemode_query_status())) { case 0: /* inactive */ LOG_MSG("gamemode is inactive\n"); break; case 1: /* active */ LOG_MSG("gamemode is active\n"); break; case -1: /* error */ LOG_ERROR("gamemode status request failed: %s\n", gamemode_error_string()); exit(EXIT_FAILURE); default: /* unexpected value eg. 2 */ LOG_ERROR("gamemode_query_status returned unexpected value %d\n", ret); exit(EXIT_FAILURE); } } exit(EXIT_SUCCESS); case 'r': if (optarg != NULL) { pid_t pid = atoi(optarg); /* toggle gamemode for the process */ switch (gamemode_query_status_for(pid)) { case 0: /* inactive */ case 1: /* active not not registered */ LOG_MSG("gamemode not active for client, requesting start for %d...\n", pid); if (gamemode_request_start_for(pid) < 0) { LOG_ERROR("gamemode_request_start_for(%d) failed: %s\n", pid, gamemode_error_string()); exit(EXIT_FAILURE); } LOG_MSG("request succeeded\n"); break; case 2: /* active for client */ LOG_MSG("gamemode active for client, requesting end for %d...\n", pid); if (gamemode_request_end_for(pid) < 0) { LOG_ERROR("gamemode_request_end_for(%d) failed: %s\n", pid, gamemode_error_string()); exit(EXIT_FAILURE); } LOG_MSG("request succeeded\n"); break; case -1: /* error */ LOG_ERROR("gamemode_query_status_for(%d) failed: %s\n", pid, gamemode_error_string()); exit(EXIT_FAILURE); } } else { /* Request gamemode for this process */ if (gamemode_request_start() < 0) { LOG_ERROR("gamemode request failed: %s\n", gamemode_error_string()); exit(EXIT_FAILURE); } /* Request and report on the status */ switch (gamemode_query_status()) { case 2: /* active for this client */ LOG_MSG("gamemode request succeeded and is active\n"); break; case 1: /* active */ LOG_ERROR("gamemode request succeeded and is active but registration failed\n"); exit(EXIT_FAILURE); case 0: /* inactive */ LOG_ERROR("gamemode request succeeded but is not active\n"); exit(EXIT_FAILURE); case -1: /* error */ LOG_ERROR("gamemode_query_status failed: %s\n", gamemode_error_string()); exit(EXIT_FAILURE); } /* Simply pause and wait a SIGINT */ if (signal(SIGINT, sigint_handler_noexit) == SIG_ERR) { FATAL_ERRORNO("Could not catch SIGINT"); } pause(); /* Explicitly clean up */ if (gamemode_request_end() < 0) { LOG_ERROR("gamemode request failed: %s\n", gamemode_error_string()); exit(EXIT_FAILURE); } } exit(EXIT_SUCCESS); case 't': { int status = game_mode_run_client_tests(); exit(status); } case 'v': LOG_MSG(VERSION_TEXT); exit(EXIT_SUCCESS); case 'h': LOG_MSG(USAGE_TEXT, argv[0]); exit(EXIT_SUCCESS); default: fprintf(stderr, USAGE_TEXT, argv[0]); exit(EXIT_FAILURE); } } /* If syslog is requested, set it up with our process name */ if (use_syslog) { set_use_syslog(argv[0]); } /* Daemonize ourselves first if asked */ if (daemon) { daemonize(argv[0]); } /* Log a version message on startup */ LOG_MSG("v%s\n", GAMEMODE_VERSION); /* Set up the game mode context */ context = game_mode_context_instance(); game_mode_context_init(context); /* Handle quits cleanly */ if (signal(SIGINT, sigint_handler) == SIG_ERR) { FATAL_ERRORNO("Could not catch SIGINT"); } if (signal(SIGTERM, sigint_handler) == SIG_ERR) { FATAL_ERRORNO("Could not catch SIGTERM"); } /* Run the main dbus message loop */ game_mode_context_loop(context); game_mode_context_destroy(context); /* Log we're finished */ LOG_MSG("Quitting naturally...\n"); sd_notify(0, "STATUS=GameMode is quitting naturally...\n"); } gamemode-1.8.2/daemon/meson.build000066400000000000000000000014251466061601300167120ustar00rootroot00000000000000# Main daemon daemon_sources = [ 'gamemoded.c', 'gamemode-context.c', 'gamemode-ioprio.c', 'gamemode-sched.c', 'gamemode-wine.c', 'gamemode-tests.c', 'gamemode-gpu.c', 'gamemode-cpu.c', 'gamemode-dbus.c', 'gamemode-config.c', ] gamemoded_includes = gamemode_headers_includes gamemoded_includes += config_h_dir gamemoded = executable( 'gamemoded', sources: daemon_sources, c_args: sd_bus_args, dependencies: [ link_daemon_common, dep_threads, sd_bus_dep, inih_dependency, libdl, ], include_directories: [ gamemoded_includes, ], install: true, ) # verify gamemoded compiled properly test( 'validate gamemoded compiled properly', gamemoded, args: ['-v'], ) gamemode-1.8.2/data/000077500000000000000000000000001466061601300142145ustar00rootroot00000000000000gamemode-1.8.2/data/dbus/000077500000000000000000000000001466061601300151515ustar00rootroot00000000000000gamemode-1.8.2/data/dbus/com.feralinteractive.GameMode.service.in000066400000000000000000000001571466061601300247240ustar00rootroot00000000000000[D-BUS Service] Name=com.feralinteractive.GameMode Exec=@BINDIR@/gamemoded -d SystemdService=gamemoded.service gamemode-1.8.2/data/gamemode-simulate-game.1.in000066400000000000000000000035011466061601300212100ustar00rootroot00000000000000.\" Manpage for gamemode-simulate-game. .\" Contact linux-contact@feralinteractive.com to correct errors or typos. .TH gamemode-simulate-game 1 "26 May 2020" "@GAMEMODE_VERSION@" "gamemode-simulate-game man page" .SH NAME gamemode-simulate-game \- simulate a game using gamemode .SH SYNOPSIS \fBgamemode-simulate-game\fR .SH DESCRIPTION \fBGameMode\fR is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS. The design has a clear cut abstraction between the host daemon and library (\fBgamemoded\fR and \fBlibgamemode\fR), and the client loaders (\fBlibgamemodeauto\fR and \fBgamemode_client.h\fR) that allows for safe usage without worrying about whether the daemon is installed or running. This design also means that while the host library currently relies on systemd for exchanging messages with the daemon, it's entirely possible to implement other internals that still work with the same clients. \fBGameMode\fR was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is intended to be expanded beyond just CPU governor states, as there are a wealth of automation tasks one might want to apply. .SH USAGE The executable starts gamemode, sleeps for 10 seconds and stops it. It will exit with zero if everything works fine, else it will print an error and exit with one. To use this with a CI you might need to start a dbus session by hand. This can be done with: .RS 4 dbus-run-session -- gamemode-simulate-game .RE Note that this might output to stderr, even if it exits with zero. .SH SEE ALSO gamemoded(8), gamemoderun(1), dbus-run-session(1) .SH ABOUT GameMode source can be found at \fIhttps://github.com/FeralInteractive/gamemode.git\fR .SH AUTHOR Feral Interactive (linux-contact@feralinteractive.com) gamemode-1.8.2/data/gamemoded.8.in000066400000000000000000000061401466061601300166350ustar00rootroot00000000000000.\" Manpage for gamemoded. .\" Contact linux-contact@feralinteractive.com to correct errors or typos. .TH gamemoded 8 "4 May 2020" "@GAMEMODE_VERSION@" "gamemoded man page" .SH NAME gamemoded \- daemon that optimises system performance on demand .SH SYNOPSIS \fBgamemoded\fR [OPTIONS...] .SH DESCRIPTION \fBGameMode\fR is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS. The design has a clear cut abstraction between the host daemon and library (\fBgamemoded\fR and \fBlibgamemode\fR), and the client loaders (\fBlibgamemodeauto\fR and \fBgamemode_client.h\fR) that allows for safe usage without worrying about whether the daemon is installed or running. This design also means that while the host library currently relies on systemd for exchanging messages with the daemon, it's entirely possible to implement other internals that still work with the same clients. \fBGameMode\fR was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is intended to be expanded beyond just CPU governor states, as there are a wealth of automation tasks one might want to apply. .SH OPTIONS .TP 8 .B \-r[PID], \-\-request=[PID] Toggle gamemode for process. When no PID given, requests gamemode and pauses .TP 8 .B \-s[PID], \-\-status=[PID] Query the status of gamemode for process When no PID given, queries the status globally .TP 8 .B \-d, \-\-daemonize Run the daemon as a separate process (daemonize it) .TP 8 .B \-l, \-\-log-to-syslog Log to syslog .TP 8 .B \-h, \-\-help Print help text .TP 8 .B \-t, \-\-test Run diagnostic tests on the current installation .TP 8 .B \-v, \-\-version Print the version .SH USAGE \fBlibgamemodeauto.so.0\fR can be pre-loaded into any program to request \fBgamemoded\fR begin or end the mode. See gamemoderun(1) for details. The \fBgamemode_client.h\fR header can be used by developers to build the requests into a program: .RS 4 .nf #include "gamemode_client.h" if( gamemode_request_start() < 0 ) { fprintf( stderr, "gamemode request failed: %s\\n", gamemode_error_string() ) } /* run the process */ /* Not required, gamemoded can clean up after game exits */ gamemode_request_end(); .fi .RE Atlernatively developers can define \fBGAMEMODE_AUTO\fR to mimic the behaviour of \fBlibgamemodeauto.so.0\fR: .RS 4 .nf #define GAMEMODE_AUTO #include "gamemode_client.h" .fi .RE Or, distribute \fBlibgamemodeauto.so.0\fR and either link with \fB\-lgamemodeauto\fR or inject it as above with \fBLD\_PRELOAD\fR. .SH CONFIG \fBgamemoded\fR will load and merge \fBgamemode.ini\fR config files from these directories in the following order: .RS 4 @SYSCONFDIR@/ .RE .RS 4 /etc/ .RE .RS 4 $XDG_CONFIG_HOME or $HOME/.config/ .RE .RS 4 $PWD .RE Behaviour of the config file can be explained by presenting a commented example: .RS 4 .nf @GAMEMODE_EXAMPLE_CONFIG@ .fi .RE .SH SEE ALSO gamemoderun(1), systemd(1) .SH ABOUT GameMode source can be found at \fIhttps://github.com/FeralInteractive/gamemode.git\fR .SH AUTHOR Feral Interactive (linux-contact@feralinteractive.com) gamemode-1.8.2/data/gamemodelist000077500000000000000000000022061466061601300166140ustar00rootroot00000000000000#!/bin/bash # Created by Sam Gleske # Created Sat Jan 1 16:56:54 EST 2022 # MIT License - https://github.com/samrocketman/home # DESCRIPTION # Find all running processes which have loaded Feral Interactive gamemode # via libgamemodeauto.so. This script will not detect processes which load # gamemode without libgamemodeauto.so. # DEVELOPMENT ENVIRONMENT # Ubuntu 18.04.6 LTS # Linux 5.4.0-91-generic x86_64 # GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu) # find (GNU findutils) 4.7.0-git # GNU Awk 4.1.4, API: 1.1 (GNU MPFR 4.0.1, GNU MP 6.1.2) # xargs (GNU findutils) 4.7.0-git # ps from procps-ng 3.3.12 if [ -z "${USER:-}" ]; then echo '$USER variable not defined.' >&2 exit 1 fi if [ ! -d /proc ]; then echo 'ERROR: /proc filesystem missing. We do not appear to be running on Linux.' >&2 exit 1 fi find /proc -maxdepth 2 -type f -user "${USER}" -readable -name maps -exec \ awk -- 'BEGINFILE { if (ERRNO) nextfile } $0 ~ /libgamemodeauto\.so\.0/ {pid=FILENAME; gsub("[^0-9]", "", pid); print pid;nextfile}' {} + \ | xargs | xargs -I{} -- ps -o pid,ppid,user,ni,psr,comm --pid '{}' gamemode-1.8.2/data/gamemodelist.1.in000066400000000000000000000037021466061601300173570ustar00rootroot00000000000000.\" Manpage for gamemodelist. .\" Contact linux-contact@feralinteractive.com to correct errors or typos. .TH gamemodelist 1 "4 May 2020" "@GAMEMODE_VERSION@" "gamemodelist man page" .SH NAME gamemodelist \- search for processes running with gamemode .SH SYNOPSIS \fBgamemodelist\fR .SH DESCRIPTION \fBgamemodelist\fR will search the runtime of all processes running which started \fBGameMode\fR via \fBlibgamemodeauto.so\fR and print them with .BR ps (1) command output. This helper script makes it easy to find which processes are utilizing \fBGameMode\fR via \fBlibgamemodeauto.so\fR when troubleshooting potential game issues. .SH USAGE \fBlibgamemodeauto.so.0\fR will be found in the shared object maps of running processes utilizing \fBGameMode\fR. Run the following command to print processes loaded with \fBlibgamemodeauto.so.0\fR. \fBGameMode\fR can be started other ways but this script will only detect processes utilizing \fBGameMode\fR via \fBlibgamemodeauto.so\fR. .RS 4 gamemodelist .RE .SH OUTPUT The output is a process table from .BR ps (1) command. .RS 4 PID PPID USER NI PSR COMMAND .RE Where each of these fields are defined in .BR ps (1) manual. For your convenience here's a definition for each field. .RS 4 .TS l lw(3i). \fBCOLUMN DESCRIPTION\fR PID Process ID PPID Parent process ID USER User name owning the process. NI T{ Nice value. This ranges from 19 (nicest) to \-20 (not nice to others), See .IR nice (1). T} PSR T{ Processor that process is currently assigned to. Useful when setting process affinity using .IR taskset (1) utility. T} COMMAND Command name (only the executable name). .TE .RE .SH SEE ALSO .BR gamemodrun (1), .BR nice (1), .BR ps (1), .BR taskset (1). .SH ABOUT GameMode source can be found at \fIhttps://github.com/FeralInteractive/gamemode.git\fR .SH AUTHOR .BR gamemodelist was authored by Sam Gleske (https://github.com/samrocketman/) .BR GameMode was authored by Feral Interactive (linux-contact@feralinteractive.com) gamemode-1.8.2/data/gamemoderun000077500000000000000000000004531466061601300164470ustar00rootroot00000000000000#!/bin/bash # Helper script to launch games with gamemode GAMEMODEAUTO_NAME="libgamemodeauto.so.0" # ld will find the right path to load the library, including for 32-bit apps. LD_PRELOAD="${GAMEMODEAUTO_NAME}${LD_PRELOAD:+:$LD_PRELOAD}" exec env LD_PRELOAD="${LD_PRELOAD}" $GAMEMODERUNEXEC "$@" gamemode-1.8.2/data/gamemoderun.1.in000066400000000000000000000041571466061601300172150ustar00rootroot00000000000000.\" Manpage for gamemoderun. .\" Contact linux-contact@feralinteractive.com to correct errors or typos. .TH gamemoderun 1 "4 May 2020" "@GAMEMODE_VERSION@" "gamemoderun man page" .SH NAME gamemoderun \- invoke gamemode into any program .SH SYNOPSIS \fBgamemoderun\fR PROGRAM .SH DESCRIPTION \fBGameMode\fR is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS. The design has a clear cut abstraction between the host daemon and library (\fBgamemoded\fR and \fBlibgamemode\fR), and the client loaders (\fBlibgamemodeauto\fR and \fBgamemode_client.h\fR) that allows for safe usage without worrying about whether the daemon is installed or running. This design also means that while the host library currently relies on systemd for exchanging messages with the daemon, it's entirely possible to implement other internals that still work with the same clients. \fBGameMode\fR was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is intended to be expanded beyond just CPU governor states, as there are a wealth of automation tasks one might want to apply. .SH USAGE \fBlibgamemodeauto.so.0\fR can be pre-loaded into any program to request \fBgamemoded\fR begin or end the mode, like so: .RS 4 gamemoderun \./game .RE Or by setting the Steam launch options for a game: .RS 4 gamemoderun %command% .RE The library can be manually preloaded if needed: .RS 4 LD_PRELOAD=$LD_PRELOAD:/usr/\e$LIB/libgamemodeauto.so.0 ./game .RE .SH CONFIG It is possible to set additional start commands to gamemoderun by setting the environment variable: .RS 4 GAMEMODERUNEXEC="command" .RE When this is set, gamemoderun will execute the command given by that environment variable, and the command line passed to gamemoderun will be passed as arguments to that command. GameMode will not be applied to the wrapper command, just the game itself. .SH SEE ALSO gamemoded(8) .SH ABOUT GameMode source can be found at \fIhttps://github.com/FeralInteractive/gamemode.git\fR .SH AUTHOR Feral Interactive (linux-contact@feralinteractive.com) gamemode-1.8.2/data/io.github.feralinteractive.gamemode.metainfo.xml000066400000000000000000000027221466061601300255350ustar00rootroot00000000000000 io.github.feralinteractive.gamemode gamemode daemon that allows games to request a set of optimizations be temporarily applied Feral Interactive FSFAP BSD-3-Clause

GameMode is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS and/or a game process.

It was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is now host to a range of optimisation features and configurations.

Currently GameMode includes support for optimisations including:

  • CPU governor
  • I/O priority
  • Process niceness
  • Kernel scheduler (SCHED_ISO)
  • Screensaver inhibiting
  • GPU performance mode (NVIDIA and AMD)
  • GPU overclocking (NVIDIA)
  • Custom scripts
Utility Game https://feralinteractive.github.io/gamemode
gamemode-1.8.2/data/meson.build000066400000000000000000000100351466061601300163550ustar00rootroot00000000000000data_conf = configuration_data() data_conf.set('BINDIR', path_bindir) data_conf.set('LIBEXECDIR', path_libexecdir) data_conf.set('SYSCONFDIR', path_sysconfdir) data_conf.set('GAMEMODE_PREFIX', path_prefix) data_conf.set('GAMEMODE_VERSION', meson.project_version()) data_conf.set('GAMEMODE_PRIVILEGED_GROUP', with_privileged_group) # Pull in the example config config_example = run_command( 'cat', join_paths(meson.source_root(), 'example', 'gamemode.ini'), check: true, ).stdout().strip() data_conf.set('GAMEMODE_EXAMPLE_CONFIG', config_example) if sd_bus_provider == 'systemd' if with_systemd_unit # Install systemd user unit configure_file( input: 'systemd/user/gamemoded.service.in', output: 'gamemoded.service', configuration: data_conf, install_dir: path_systemd_unit_dir, ) endif if with_systemd_group # Install the sysusers.d file configure_file( input: 'systemd/sysusers.d/gamemode.conf.in', output: 'gamemode.conf', configuration: data_conf, install_dir: path_systemd_group_dir, ) endif endif if with_pam_renicing # Install the limits.d configuration file configure_file( input: 'pam_limits/10-gamemode.conf.in', output: '10-gamemode.conf', configuration: data_conf, install_dir: path_pam_limits_dir, ) endif # Install the D-BUS service file configure_file( input: 'dbus/com.feralinteractive.GameMode.service.in', output: 'com.feralinteractive.GameMode.service', configuration: data_conf, install_dir: path_dbus_service_dir, ) # Install the Polkit action & rule files for the privileged gamemode group if with_privileged_group != '' configure_file( input: 'polkit/actions/com.feralinteractive.GameMode.policy.in', output: 'com.feralinteractive.GameMode.policy', configuration: data_conf, install_dir: path_polkit_action_dir, ) configure_file( input: 'polkit/rules.d/gamemode.rules.in', output: 'gamemode.rules', configuration: data_conf, install_dir: path_polkit_rule_dir, ) endif # Install the helper run script and man page if get_option('default_library') == 'static' warning('gamemoderun will not be installed as a shared libgamemodeauto library is required') else install_data( files('gamemoderun'), install_dir: path_bindir, install_mode: 'rwxr-xr-x', ) gamemoderun_manpage = configure_file( input: files('gamemoderun.1.in'), output: 'gamemoderun.1', configuration: data_conf, ) install_man( gamemoderun_manpage, install_dir: join_paths(path_mandir, 'man1') ) endif # Install script to find processes with gamemode lib in runtime install_data( files('gamemodelist'), install_dir: path_bindir, install_mode: 'rwxr-xr-x', ) # Configure and install man pages gamemoded_manpage = configure_file( input: files('gamemoded.8.in'), output: 'gamemoded.8', configuration: data_conf, ) install_man( gamemoded_manpage, install_dir: join_paths(path_mandir, 'man8') ) gamemodelist_manpage = configure_file( input: files('gamemodelist.1.in'), output: 'gamemodelist.1', configuration: data_conf, ) install_man( gamemodelist_manpage, install_dir: join_paths(path_mandir, 'man1') ) if with_examples example_manpage = configure_file( input: files('gamemode-simulate-game.1.in'), output: 'gamemode-simulate-game.1', configuration: data_conf, ) install_man( example_manpage, install_dir: join_paths(path_mandir, 'man1') ) endif # Install metainfo metainfo_file = files('io.github.feralinteractive.gamemode.metainfo.xml') install_data( metainfo_file, install_dir: path_metainfo, ) # Validate metainfo appstreamcli = find_program( 'appstreamcli', required: false ) if appstreamcli.found() test( 'validate metainfo file', appstreamcli, args: ['validate', '--no-net', '--pedantic', metainfo_file], ) endif gamemode-1.8.2/data/pam_limits/000077500000000000000000000000001466061601300163525ustar00rootroot00000000000000gamemode-1.8.2/data/pam_limits/10-gamemode.conf.in000066400000000000000000000000501466061601300216150ustar00rootroot00000000000000@@GAMEMODE_PRIVILEGED_GROUP@ - nice -10 gamemode-1.8.2/data/polkit/000077500000000000000000000000001466061601300155165ustar00rootroot00000000000000gamemode-1.8.2/data/polkit/actions/000077500000000000000000000000001466061601300171565ustar00rootroot00000000000000gamemode-1.8.2/data/polkit/actions/com.feralinteractive.GameMode.policy.in000066400000000000000000000046001466061601300265650ustar00rootroot00000000000000 Feral GameMode Activation http://www.feralinteractive.com Modify the CPU governor Authentication is required to modify the CPU governor no no no @LIBEXECDIR@/cpugovctl Modify the GPU clock states Authentication is required to modify the GPU clock states no no no @LIBEXECDIR@/gpuclockctl true Modify the CPU core states Authentication is required to modify the CPU core states no no no @LIBEXECDIR@/cpucorectl true Modify the /proc/sys values Authentication is required to modify the /proc/sys/ values no no no @LIBEXECDIR@/procsysctl true gamemode-1.8.2/data/polkit/rules.d/000077500000000000000000000000001466061601300170725ustar00rootroot00000000000000gamemode-1.8.2/data/polkit/rules.d/gamemode.rules.in000066400000000000000000000010361466061601300223310ustar00rootroot00000000000000/* * Allow users in privileged gamemode group to run cpugovctl & * gpuclockctl 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") && subject.isInGroup("@GAMEMODE_PRIVILEGED_GROUP@")) { return polkit.Result.YES; } }); gamemode-1.8.2/data/systemd/000077500000000000000000000000001466061601300157045ustar00rootroot00000000000000gamemode-1.8.2/data/systemd/sysusers.d/000077500000000000000000000000001466061601300200265ustar00rootroot00000000000000gamemode-1.8.2/data/systemd/sysusers.d/gamemode.conf.in000066400000000000000000000000421466061601300230540ustar00rootroot00000000000000g @GAMEMODE_PRIVILEGED_GROUP@ - - gamemode-1.8.2/data/systemd/user/000077500000000000000000000000001466061601300166625ustar00rootroot00000000000000gamemode-1.8.2/data/systemd/user/gamemoded.service.in000066400000000000000000000002521466061601300225720ustar00rootroot00000000000000[Unit] Description=gamemoded [Service] Type=dbus BusName=com.feralinteractive.GameMode NotifyAccess=main ExecStart=@BINDIR@/gamemoded [Install] WantedBy=default.target gamemode-1.8.2/example/000077500000000000000000000000001466061601300147365ustar00rootroot00000000000000gamemode-1.8.2/example/gamemode.ini000066400000000000000000000122271466061601300172210ustar00rootroot00000000000000[general] ; 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 ; The desired governor is used when entering GameMode instead of "performance" desiredgov=performance ; The default governor is used when leaving GameMode instead of restoring the original value ;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 ; 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". softrealtime=off ; GameMode can renice game processes. You can put any value between 0 and 20 here, the value ; will be negated and applied as a nice value (0 means no change). Defaults to 0. ; To use this feature, the user must be added to the gamemode group (and then rebooted): ; sudo usermod -aG gamemode $(whoami) renice=0 ; By default, GameMode adjusts the iopriority of clients to BE/0, you can put any value ; between 0 and 7 here (with 0 being highest priority), or one of the special values ; "off" (to disable) or "reset" (to restore Linux default behavior based on CPU priority), ; currently, only the best-effort class is supported thus you cannot set it here ioprio=0 ; Sets whether gamemode will inhibit the screensaver when active ; Defaults to 1 inhibit_screensaver=1 ; Sets whether gamemode will disable split lock mitigation when active ; Defaults to 1 disable_splitlock=1 [filter] ; If "whitelist" entry has a value(s) ; gamemode will reject anything not in the whitelist ;whitelist=RiseOfTheTombRaider ; Gamemode will always reject anything in the blacklist ;blacklist=HalfLife3 ; glxgears [gpu] ; Here Be Dragons! ; Warning: Use these settings at your own risk ; Any damage to hardware incurred due to this feature is your responsibility and yours alone ; It is also highly recommended you try these settings out first manually to find the sweet spots ; Setting this to the keyphrase "accept-responsibility" will allow gamemode to apply GPU optimisations such as overclocks ;apply_gpu_optimisations=0 ; The DRM device number on the system (usually 0), ie. the number in /sys/class/drm/card0/ ;gpu_device=0 ; Nvidia specific settings ; Requires the coolbits extension activated in nvidia-xconfig ; This corresponds to the desired GPUPowerMizerMode ; "Adaptive"=0 "Prefer Maximum Performance"=1 and "Auto"=2 ; See NV_CTRL_GPU_POWER_MIZER_MODE and friends in https://github.com/NVIDIA/nvidia-settings/blob/master/src/libXNVCtrl/NVCtrl.h ;nv_powermizer_mode=1 ; These will modify the core and mem clocks of the highest perf state in the Nvidia PowerMizer ; They are measured as Mhz offsets from the baseline, 0 will reset values to default, -1 or unset will not modify values ;nv_core_clock_mhz_offset=0 ;nv_mem_clock_mhz_offset=0 ; AMD specific settings ; 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 ; It is also highly recommended you use lm-sensors (or other available tools) to verify card temperatures ; This corresponds to power_dpm_force_performance_level, "manual" is not supported for now ;amd_performance_level=high [cpu] ; Parking or Pinning can be enabled with either "yes", "true" or "1" and disabled with "no", "false" or "0". ; Either can also be set to a specific list of cores to park or pin, comma separated list where "-" denotes ; a range. E.g "park_cores=1,8-15" would park cores 1 and 8 to 15. ; The default is uncommented is to disable parking but enable pinning. If either is enabled the code will ; currently only properly autodetect Ryzen 7900x3d, 7950x3d and Intel CPU:s with E- and P-cores. ; For Core Parking, user must be added to the gamemode group (not required for Core Pinning): ; sudo usermod -aG gamemode $(whoami) ;park_cores=no ;pin_cores=yes [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 ;supervisor_whitelist= ;supervisor_blacklist= ; In case you want to allow a supervisor to take full control of gamemode, this option can be set ; This will only allow gamemode clients to be registered by using the above functions by a supervisor client ;require_supervisor=0 [custom] ; Custom scripts (executed using the shell) when gamemode starts and ends ;start=notify-send "GameMode started" ; /home/me/bin/stop_foldingathome.sh ;end=notify-send "GameMode ended" ; /home/me/bin/start_foldingathome.sh ; Timeout for scripts (seconds). Scripts will be killed if they do not complete within this time. ;script_timeout=10 gamemode-1.8.2/example/main.c000066400000000000000000000040241466061601300160260ustar00rootroot00000000000000/* 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. */ #include "gamemode_client.h" #include #include #include int main(void) { /* Request we start game mode */ if (gamemode_request_start() != 0) { fprintf(stderr, "Failed to request gamemode start: %s...\n", gamemode_error_string()); return EXIT_FAILURE; } /* Simulate running a game */ sleep(10); /* Request we end game mode (optional) */ if (gamemode_request_end() != 0) { fprintf(stderr, "Failed to request gamemode end: %s...\n", gamemode_error_string()); return EXIT_FAILURE; } return EXIT_SUCCESS; } gamemode-1.8.2/example/meson.build000066400000000000000000000004611466061601300171010ustar00rootroot00000000000000# An example game executable( 'gamemode-simulate-game', sources: [ 'main.c', ], dependencies: [ gamemode_dep, ], install: true, install_dir: path_bindir, ) # An example configuration install_data( files('gamemode.ini'), install_dir: path_sysconfdir, ) gamemode-1.8.2/lib/000077500000000000000000000000001466061601300140515ustar00rootroot00000000000000gamemode-1.8.2/lib/README.md000066400000000000000000000027161466061601300153360ustar00rootroot00000000000000## libgamemode **libgamemode** is an internal library used to dispatch requests to the daemon. Note: `libgamemode` should never be linked with directly. **libgamemodeauto** is a simple dynamic library that automatically requests game mode when loaded. Useful to `LD_PRELOAD` into any game as needed. **gamemode\_client.h** is as single header lib that lets a game request game mode and handle errors. ### Integration Developers can integrate the request directly into an app. Note that none of these client methods force your users to have the daemon installed or running - they will safely no-op if the host is missing. ```C // Manually with error checking #include "gamemode_client.h" if( gamemode_request_start() < 0 ) { fprintf( stderr, "gamemode request failed: %s\n", gamemode_error_string() ); } /* run game... */ gamemode_request_end(); // Not required, gamemoded can clean up after game exits ``` ```C // Automatically on program start and finish #define GAMEMODE_AUTO #include "gamemode_client.h" ``` Or, distribute `libgamemodeauto.so` and either add `-lgamemodeauto` to your linker arguments, or add it to an LD\_PRELOAD in a launch script. ### Supervisor support Developers can also create apps that manage GameMode on the system, for other processes: ```C #include "gamemode_client.h" gamemode_request_start_for(gamePID); gamemode_request_end_for(gamePID); ``` This functionality can also be controlled in the config file in the `supervisor` section.gamemode-1.8.2/lib/client_impl.c000066400000000000000000000231461466061601300165220ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include // For developmental purposes #define DO_TRACE 0 // D-Bus name, path, iface #define DAEMON_DBUS_NAME "com.feralinteractive.GameMode" #define DAEMON_DBUS_PATH "/com/feralinteractive/GameMode" #define DAEMON_DBUS_IFACE "com.feralinteractive.GameMode" #define PORTAL_DBUS_NAME "org.freedesktop.portal.Desktop" #define PORTAL_DBUS_PATH "/org/freedesktop/portal/desktop" #define PORTAL_DBUS_IFACE "org.freedesktop.portal.GameMode" // Cleanup macros #define _cleanup_(x) __attribute__((cleanup(x))) #define _cleanup_bus_ _cleanup_(hop_off_the_bus) #define _cleanup_msg_ _cleanup_(cleanup_msg) #define _cleanup_dpc_ _cleanup_(cleanup_pending_call) #define _cleanup_fds_ _cleanup_(cleanup_fd_array) #ifdef NDEBUG #define DEBUG(...) #else #define DEBUG(...) fprintf(stderr, __VA_ARGS__) #endif #if DO_TRACE #define TRACE(...) fprintf(stderr, __VA_ARGS__) #else #define TRACE(...) #endif // Prototypes static int log_error(const char *fmt, ...) __attribute__((format(printf, 1, 2))); // Storage for error strings static char error_string[512] = { 0 }; // memory helpers static void cleanup_fd_array(int **fdlist) { if (fdlist == NULL || *fdlist == NULL) return; int errsave = errno; for (int *fd = *fdlist; *fd != -1; fd++) { TRACE("GM Closing fd %d\n", *fd); (void)close(*fd); } errno = errsave; free(*fdlist); } // Allocate a -1 termianted array of ints static inline int *alloc_fd_array(int n) { int *fds; size_t count = (size_t)n + 1; /* -1, terminated */ fds = (int *)malloc(sizeof(int) * count); for (size_t i = 0; i < count; i++) fds[i] = -1; return fds; } // Helper to check if we are running inside a sandboxed framework like Flatpak or Snap static int in_sandbox(void) { static int status = -1; if (status == -1) { struct stat sb; int r; r = lstat("/.flatpak-info", &sb); status = r == 0 && sb.st_size > 0; if (getenv("SNAP") != NULL) { status = 1; } } return status; } static int log_error(const char *fmt, ...) { va_list args; int n; va_start(args, fmt); n = vsnprintf(error_string, sizeof(error_string), fmt, args); va_end(args); if (n < 0) DEBUG("Failed to format error string"); else if ((size_t)n >= sizeof(error_string)) DEBUG("Error log overflow"); fprintf(stderr, "GameMode ERROR: %s\n", error_string); return -1; } static void hop_off_the_bus(DBusConnection **bus) { if (bus == NULL || *bus == NULL) return; dbus_connection_unref(*bus); } static DBusConnection *hop_on_the_bus(void) { DBusConnection *bus; DBusError err; dbus_error_init(&err); bus = dbus_bus_get(DBUS_BUS_SESSION, &err); if (bus == NULL) { log_error("Could not connect to bus: %s", err.message); dbus_error_free(&err); } return bus; } /* cleanup functions */ static void cleanup_msg(DBusMessage **msg) { if (msg == NULL || *msg == NULL) return; dbus_message_unref(*msg); } static void cleanup_pending_call(DBusPendingCall **call) { if (call == NULL || *call == NULL) return; dbus_pending_call_unref(*call); } /* internal API */ static int make_request(DBusConnection *bus, int native, int use_pidfds, const char *method, pid_t *pids, int npids, DBusError *error) { _cleanup_msg_ DBusMessage *msg = NULL; _cleanup_dpc_ DBusPendingCall *call = NULL; _cleanup_fds_ int *fds = NULL; char action[256] = { 0, }; DBusError err; DBusMessageIter iter; int res = -1; TRACE("GM: Incoming request: %s, npids: %d, native: %d pifds: %d\n", method, npids, native, use_pidfds); if (use_pidfds) { fds = alloc_fd_array(npids); res = open_pidfds(pids, fds, npids); if (res != npids) { dbus_set_error(error, DBUS_ERROR_FAILED, "Could not open pidfd for %d", (int)pids[res]); return -1; } if (strstr(method, "ByPID")) snprintf(action, sizeof(action), "%sFd", method); else snprintf(action, sizeof(action), "%sByPIDFd", method); method = action; } TRACE("GM: Making request: %s, npids: %d, native: %d pifds: %d\n", method, npids, native, use_pidfds); // If we are inside a Flatpak or Snap we need to talk to the portal instead const char *dest = native ? DAEMON_DBUS_NAME : PORTAL_DBUS_NAME; const char *path = native ? DAEMON_DBUS_PATH : PORTAL_DBUS_PATH; const char *iface = native ? DAEMON_DBUS_IFACE : PORTAL_DBUS_IFACE; msg = dbus_message_new_method_call(dest, path, iface, method); if (!msg) { dbus_set_error_const(error, DBUS_ERROR_FAILED, "Could not create dbus message"); return -1; } dbus_message_iter_init_append(msg, &iter); for (int i = 0; i < npids; i++) { dbus_int32_t p; int type; if (use_pidfds) { type = DBUS_TYPE_UNIX_FD; p = (dbus_int32_t)fds[i]; } else { type = DBUS_TYPE_INT32; p = (dbus_int32_t)pids[i]; } dbus_message_iter_append_basic(&iter, type, &p); } dbus_connection_send_with_reply(bus, msg, &call, -1); dbus_connection_flush(bus); dbus_message_unref(msg); msg = NULL; dbus_pending_call_block(call); msg = dbus_pending_call_steal_reply(call); if (msg == NULL) { dbus_set_error_const(error, DBUS_ERROR_FAILED, "Did not receive a reply"); return -1; } dbus_error_init(&err); res = -1; if (dbus_set_error_from_message(&err, msg)) { dbus_set_error(error, err.name, "Could not call method '%s' on '%s': %s", method, dest, err.message); } else if (!dbus_message_iter_init(msg, &iter) || dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) { dbus_set_error(error, DBUS_ERROR_INVALID_SIGNATURE, "Failed to parse response"); } else { dbus_message_iter_get_basic(&iter, &res); } /* free the local error */ if (dbus_error_is_set(&err)) dbus_error_free(&err); return res; } static int gamemode_request(const char *method, pid_t for_pid) { _cleanup_bus_ DBusConnection *bus = NULL; static int use_pidfs = 1; DBusError err; pid_t pids[2]; int npids; int native; int res = -1; native = !in_sandbox(); /* pid[0] is the client, i.e. the game * pid[1] is the requestor, i.e. this process * * we setup the array such that pids[1] will always be a valid * pid, because if we are going to use the pidfd based API, * both pids are being sent, even if they are the same */ pids[1] = getpid(); pids[0] = for_pid != 0 ? for_pid : pids[1]; TRACE("GM: [%d] request '%s' received (by: %d) [portal: %s]\n", (int)pids[0], method, (int)pids[1], (native ? "n" : "y")); bus = hop_on_the_bus(); if (bus == NULL) return -1; dbus_error_init(&err); retry: if (for_pid != 0 || use_pidfs) npids = 2; else npids = 1; res = make_request(bus, native, use_pidfs, method, pids, npids, &err); if (res == -1 && use_pidfs && dbus_error_is_set(&err)) { TRACE("GM: Request with pidfds failed (%s). Retrying.\n", err.message); use_pidfs = 0; dbus_error_free(&err); goto retry; } if (res == -1 && dbus_error_is_set(&err)) log_error("D-Bus error: %s", err.message); TRACE("GM: [%d] request '%s' done: %d\n", (int)pids[0], method, res); if (dbus_error_is_set(&err)) dbus_error_free(&err); return res; } // Get the error string extern const char *real_gamemode_error_string(void) { return error_string; } // Wrapper to call RegisterGame extern int real_gamemode_request_start(void) { return gamemode_request("RegisterGame", 0); } // Wrapper to call UnregisterGame extern int real_gamemode_request_end(void) { return gamemode_request("UnregisterGame", 0); } // Wrapper to call QueryStatus extern int real_gamemode_query_status(void) { return gamemode_request("QueryStatus", 0); } // Wrapper to call RegisterGameByPID extern int real_gamemode_request_start_for(pid_t pid) { return gamemode_request("RegisterGameByPID", pid); } // Wrapper to call UnregisterGameByPID extern int real_gamemode_request_end_for(pid_t pid) { return gamemode_request("UnregisterGameByPID", pid); } // Wrapper to call QueryStatusByPID extern int real_gamemode_query_status_for(pid_t pid) { return gamemode_request("QueryStatusByPID", pid); } gamemode-1.8.2/lib/client_loader.c000066400000000000000000000032131466061601300170200ustar00rootroot00000000000000/* 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. */ // Simply include the header with GAMEMODE_AUTO set // This will ensure it calls the functions when it's loaded #define GAMEMODE_AUTO #include "gamemode_client.h" gamemode-1.8.2/lib/gamemode_client.h000066400000000000000000000275101466061601300173430ustar00rootroot00000000000000/* 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. */ #ifndef CLIENT_GAMEMODE_H #define CLIENT_GAMEMODE_H /* * GameMode supports the following client functions * Requests are refcounted in the daemon * * int gamemode_request_start() - Request gamemode starts * 0 if the request was sent successfully * -1 if the request failed * * int gamemode_request_end() - Request gamemode ends * 0 if the request was sent successfully * -1 if the request failed * * GAMEMODE_AUTO can be defined to make the above two functions apply during static init and * destruction, as appropriate. In this configuration, errors will be printed to stderr * * int gamemode_query_status() - Query the current status of gamemode * 0 if gamemode is inactive * 1 if gamemode is active * 2 if gamemode is active and this client is registered * -1 if the query failed * * int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process * 0 if the request was sent successfully * -1 if the request failed * -2 if the request was rejected * * int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process * 0 if the request was sent successfully * -1 if the request failed * -2 if the request was rejected * * int gamemode_query_status_for(pid_t pid) - Query status of gamemode for another process * 0 if gamemode is inactive * 1 if gamemode is active * 2 if gamemode is active and this client is registered * -1 if the query failed * * const char* gamemode_error_string() - Get an error string * returns a string describing any of the above errors * * Note: All the above requests can be blocking - dbus requests can and will block while the daemon * handles the request. It is not recommended to make these calls in performance critical code */ #include #include #include #include #include #include static char internal_gamemode_client_error_string[512] = { 0 }; /** * Load libgamemode dynamically to dislodge us from most dependencies. * This allows clients to link and/or use this regardless of runtime. * See SDL2 for an example of the reasoning behind this in terms of * dynamic versioning as well. */ static volatile int internal_libgamemode_loaded = 1; /* Typedefs for the functions to load */ typedef int (*api_call_return_int)(void); typedef const char *(*api_call_return_cstring)(void); typedef int (*api_call_pid_return_int)(pid_t); /* Storage for functors */ static api_call_return_int REAL_internal_gamemode_request_start = NULL; static api_call_return_int REAL_internal_gamemode_request_end = NULL; static api_call_return_int REAL_internal_gamemode_query_status = NULL; static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL; /** * Internal helper to perform the symbol binding safely. * * Returns 0 on success and -1 on failure */ __attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol( void *handle, const char *name, void **out_func, size_t func_size, bool required) { void *symbol_lookup = NULL; char *dl_error = NULL; /* Safely look up the symbol */ symbol_lookup = dlsym(handle, name); dl_error = dlerror(); if (required && (dl_error || !symbol_lookup)) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "dlsym failed - %s", dl_error); return -1; } /* Have the symbol correctly, copy it to make it usable */ memcpy(out_func, &symbol_lookup, func_size); return 0; } /** * Loads libgamemode and needed functions * * Returns 0 on success and -1 on failure */ __attribute__((always_inline)) static inline int internal_load_libgamemode(void) { /* We start at 1, 0 is a success and -1 is a fail */ if (internal_libgamemode_loaded != 1) { return internal_libgamemode_loaded; } /* Anonymous struct type to define our bindings */ struct binding { const char *name; void **functor; size_t func_size; bool required; } bindings[] = { { "real_gamemode_request_start", (void **)&REAL_internal_gamemode_request_start, sizeof(REAL_internal_gamemode_request_start), true }, { "real_gamemode_request_end", (void **)&REAL_internal_gamemode_request_end, sizeof(REAL_internal_gamemode_request_end), true }, { "real_gamemode_query_status", (void **)&REAL_internal_gamemode_query_status, sizeof(REAL_internal_gamemode_query_status), false }, { "real_gamemode_error_string", (void **)&REAL_internal_gamemode_error_string, sizeof(REAL_internal_gamemode_error_string), true }, { "real_gamemode_request_start_for", (void **)&REAL_internal_gamemode_request_start_for, sizeof(REAL_internal_gamemode_request_start_for), false }, { "real_gamemode_request_end_for", (void **)&REAL_internal_gamemode_request_end_for, sizeof(REAL_internal_gamemode_request_end_for), false }, { "real_gamemode_query_status_for", (void **)&REAL_internal_gamemode_query_status_for, sizeof(REAL_internal_gamemode_query_status_for), false }, }; void *libgamemode = NULL; /* Try and load libgamemode */ libgamemode = dlopen("libgamemode.so.0", RTLD_NOW); if (!libgamemode) { /* Attempt to load unversioned library for compatibility with older * versions (as of writing, there are no ABI changes between the two - * this may need to change if ever ABI-breaking changes are made) */ libgamemode = dlopen("libgamemode.so", RTLD_NOW); if (!libgamemode) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "dlopen failed - %s", dlerror()); internal_libgamemode_loaded = -1; return -1; } } /* Attempt to bind all symbols */ for (size_t i = 0; i < sizeof(bindings) / sizeof(bindings[0]); i++) { struct binding *binder = &bindings[i]; if (internal_bind_libgamemode_symbol(libgamemode, binder->name, binder->functor, binder->func_size, binder->required)) { internal_libgamemode_loaded = -1; return -1; }; } /* Success */ internal_libgamemode_loaded = 0; return 0; } /** * Redirect to the real libgamemode */ __attribute__((always_inline)) static inline const char *gamemode_error_string(void) { /* If we fail to load the system gamemode, or we have an error string already, return our error * string instead of diverting to the system version */ if (internal_load_libgamemode() < 0 || internal_gamemode_client_error_string[0] != '\0') { return internal_gamemode_client_error_string; } /* Assert for static analyser that the function is not NULL */ assert(REAL_internal_gamemode_error_string != NULL); return REAL_internal_gamemode_error_string(); } /** * Redirect to the real libgamemode * Allow automatically requesting game mode * Also prints errors as they happen. */ #ifdef GAMEMODE_AUTO __attribute__((constructor)) #else __attribute__((always_inline)) static inline #endif int gamemode_request_start(void) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { #ifdef GAMEMODE_AUTO fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); #endif return -1; } /* Assert for static analyser that the function is not NULL */ assert(REAL_internal_gamemode_request_start != NULL); if (REAL_internal_gamemode_request_start() < 0) { #ifdef GAMEMODE_AUTO fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); #endif return -1; } return 0; } /* Redirect to the real libgamemode */ #ifdef GAMEMODE_AUTO __attribute__((destructor)) #else __attribute__((always_inline)) static inline #endif int gamemode_request_end(void) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { #ifdef GAMEMODE_AUTO fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); #endif return -1; } /* Assert for static analyser that the function is not NULL */ assert(REAL_internal_gamemode_request_end != NULL); if (REAL_internal_gamemode_request_end() < 0) { #ifdef GAMEMODE_AUTO fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); #endif return -1; } return 0; } /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_query_status(void) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { return -1; } if (REAL_internal_gamemode_query_status == NULL) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "gamemode_query_status missing (older host?)"); return -1; } return REAL_internal_gamemode_query_status(); } /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { return -1; } if (REAL_internal_gamemode_request_start_for == NULL) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "gamemode_request_start_for missing (older host?)"); return -1; } return REAL_internal_gamemode_request_start_for(pid); } /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { return -1; } if (REAL_internal_gamemode_request_end_for == NULL) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "gamemode_request_end_for missing (older host?)"); return -1; } return REAL_internal_gamemode_request_end_for(pid); } /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid) { /* Need to load gamemode */ if (internal_load_libgamemode() < 0) { return -1; } if (REAL_internal_gamemode_query_status_for == NULL) { snprintf(internal_gamemode_client_error_string, sizeof(internal_gamemode_client_error_string), "gamemode_query_status_for missing (older host?)"); return -1; } return REAL_internal_gamemode_query_status_for(pid); } #endif // CLIENT_GAMEMODE_H gamemode-1.8.2/lib/meson.build000066400000000000000000000032271466061601300162170ustar00rootroot00000000000000# Libtool like versioning (current.revision.age) for the libraries # See https://www.sourceware.org/autobook/autobook/autobook_61.html#Library-Versioning lt_current = '0' lt_revision = '0' lt_age = '0' lt_version = '@0@.@1@.@2@'.format(lt_current, lt_age, lt_revision) # Main client library to message the daemon libgamemode = shared_library( 'gamemode', sources: [ 'client_impl.c', ], dependencies: [ link_lib_common, dep_dbus, ], install: true, soversion: lt_current, version: lt_version, ) gamemode_headers_includes = [ include_directories('.'), ] # Small library to automatically use gamemode libgamemodeauto = library( 'gamemodeauto', sources: [ 'client_loader.c', ], dependencies: [ libdl, ], install: true, soversion: lt_current, version: lt_version, ) # Install the gamemode_client header gamemode_headers = [ 'gamemode_client.h', ] install_headers( gamemode_headers, install_dir: path_includedir, ) # Generate a pkg-config files pkg = import('pkgconfig') desc = 'GameMode temporarily applies game specific optimisations to the host OS.' pkg.generate( name: 'gamemode', description: desc, filebase: 'gamemode', version: meson.project_version(), libraries: [ libdl ], ) pkg.generate( name: 'libgamemodeauto', description: desc, filebase: 'libgamemodeauto', libraries: libgamemodeauto, version: meson.project_version(), ) # Dependency objects gamemode_dep = declare_dependency( include_directories: gamemode_headers_includes, dependencies: libdl, ) libgamemodeauto_dep = declare_dependency( link_with: libgamemodeauto, ) gamemode-1.8.2/meson.build000066400000000000000000000160461466061601300154540ustar00rootroot00000000000000project( 'gamemode', 'c', default_options : ['c_std=c11', 'warning_level=3'], version: '1.8.1', license: 'BSD', ) am_cflags = [ '-fstack-protector', '-Wstrict-prototypes', '-Wundef', '-fno-common', '-Werror-implicit-function-declaration', '-Wformat-security', '-Werror=format-security', '-Wconversion', '-Wunreachable-code', ] # Add our main flags add_global_arguments(am_cflags, language: 'c') cc = meson.get_compiler('c') # additional compiler warnings, if supported test_args = [ '-Waggregate-return', '-Wunused', '-Warray-bounds', '-Wcast-align', '-Wclobbered', '-Wempty-body', '-Wformat=2', '-Wformat-nonliteral', '-Wformat-signedness', '-Wignored-qualifiers', '-Wimplicit-function-declaration', '-Winit-self', '-Wmissing-format-attribute', '-Wmissing-include-dirs', '-Wmissing-noreturn', '-Wmissing-parameter-type', '-Wnested-externs', '-Wno-discarded-qualifiers', '-Wno-missing-field-initializers', '-Wno-suggest-attribute=format', '-Wno-unused-parameter', '-Wold-style-definition', '-Woverride-init', '-Wpointer-arith', '-Wredundant-decls', '-Wreturn-type', '-Wshadow', '-Wsign-compare', '-Wstrict-aliasing=3', '-Wstrict-prototypes', '-Wstringop-overflow', '-Wstringop-truncation', '-Wtype-limits', '-Wundef', '-Wuninitialized', '-Wunused-but-set-variable', '-Wwrite-strings', ] foreach arg: test_args if cc.has_argument(arg) add_global_arguments(arg, language : 'c') endif endforeach path_prefix = get_option('prefix') path_bindir = join_paths(path_prefix, get_option('bindir')) path_datadir = join_paths(path_prefix, get_option('datadir')) path_includedir = join_paths(path_prefix, get_option('includedir')) path_libdir = join_paths(path_prefix, get_option('libdir')) path_libexecdir = join_paths(path_prefix, get_option('libexecdir')) path_mandir = join_paths(path_prefix, get_option('mandir')) path_metainfo = join_paths(path_datadir, 'metainfo') path_sysconfdir = join_paths(path_datadir, 'gamemode') # Find systemd / elogind via pkgconfig sd_bus_provider = get_option('with-sd-bus-provider') sd_bus_args = [] sd_bus_dep = [] if sd_bus_provider == 'systemd' sd_bus_dep = dependency('libsystemd') elif sd_bus_provider == 'elogind' sd_bus_args += ['-DUSE_ELOGIND'] sd_bus_dep = dependency('libelogind') endif # For the client, libdbus is used dep_dbus = dependency('dbus-1') # Allow meson to figure out how the compiler sets up threading dep_threads = dependency('threads') # On non glibc systems this might be a stub, i.e. for musl libdl = cc.find_library('dl', required: false) with_privileged_group = get_option('with-privileged-group') # Determine the location for the systemd unit if sd_bus_provider == 'systemd' with_systemd_unit = get_option('with-systemd-user-unit') if with_systemd_unit path_systemd_unit_dir = get_option('with-systemd-user-unit-dir') if path_systemd_unit_dir == '' message('Asking pkg-config for systemd\'s \'systemduserunitdir\' directory') pkgconfig_systemd = dependency('systemd') path_systemd_unit_dir = pkgconfig_systemd.get_pkgconfig_variable('systemduserunitdir') endif endif if with_privileged_group != '' with_systemd_group = get_option('with-systemd-group') if with_systemd_group path_systemd_group_dir = get_option('with-systemd-group-dir') if path_systemd_group_dir == '' message('Asking pkg-config for systemd\'s \'sysusersdir\' directory') pkgconfig_systemd = dependency('systemd') path_systemd_group_dir = pkgconfig_systemd.get_pkgconfig_variable('sysusersdir') endif endif else with_systemd_group = false endif endif if with_privileged_group != '' with_pam_renicing = get_option('with-pam-renicing') if with_pam_renicing path_pam_limits_dir = get_option('with-pam-limits-dir') endif else with_pam_renicing = false endif # Set the dbus path as appropriate. path_dbus_service_dir = get_option('with-dbus-service-dir') if path_dbus_service_dir == '' path_dbus_service_dir = join_paths(path_datadir, 'dbus-1', 'services') endif path_polkit_dir = join_paths(path_datadir, 'polkit-1') path_polkit_action_dir = join_paths(path_polkit_dir, 'actions') path_polkit_rule_dir = join_paths(path_polkit_dir, 'rules.d') with_examples = get_option('with-examples') with_util = get_option('with-util') # Provide a config.h pidfd_open = cc.has_function('pidfd_open', args: '-D_GNU_SOURCE') cdata = configuration_data() cdata.set_quoted('LIBEXECDIR', path_libexecdir) cdata.set_quoted('SYSCONFDIR', path_sysconfdir) cdata.set_quoted('GAMEMODE_VERSION', meson.project_version()) cdata.set10('HAVE_FN_PIDFD_OPEN', pidfd_open) config_h = configure_file( configuration: cdata, output: 'build-config.h', ) config_h_dir = include_directories('.') # common lib is always required subdir('common') # Library is always required subdir('lib') # Utilities are always required except when having both 64 and 32 bit versions # of libgamemode installed if with_util == true subdir('util') endif # The daemon can be disabled if necessary, allowing multilib builds of the # main library if sd_bus_provider != 'no-daemon' # inih currently only needed by the daemon inih_dependency = dependency( 'inih', fallback : ['inih', 'inih_dep'] ) subdir('daemon') # All installed data is currently daemon specific subdir('data') endif # Optionally allow building of examples if with_examples == true subdir('example') endif report = [ ' Build configuration:', ' ====================', '', ' prefix: @0@'.format(path_prefix), ' bindir: @0@'.format(path_bindir), ' datadir: @0@'.format(path_datadir), ' libdir: @0@'.format(path_libdir), ' libexecdir: @0@'.format(path_libexecdir), ' includedir: @0@'.format(path_includedir), ] if with_pam_renicing report += [ ' PAM limits.d directory: @0@'.format(path_pam_limits_dir), ] endif if sd_bus_provider == 'systemd' if with_systemd_unit report += [ ' systemd user unit directory: @0@'.format(path_systemd_unit_dir), ] endif if with_systemd_group report += [ ' systemd group directory: @0@'.format(path_systemd_group_dir), ] endif endif report += [ ' D-BUS service directory: @0@'.format(path_dbus_service_dir), ] report += [ ' PolKit Action Directory: @0@'.format(path_polkit_action_dir), '', ' Options:', ' ========', '', ' sd-bus provier: @0@'.format(sd_bus_provider), ' examples: @0@'.format(with_examples), ' util: @0@'.format(with_util), ] # Output some stuff to validate the build config message('\n\n\n' + '\n'.join(report) + '\n\n') gamemode-1.8.2/meson_options.txt000066400000000000000000000025761466061601300167520ustar00rootroot00000000000000# limits.d option('with-pam-renicing', type: 'boolean', description: 'Install the limits.d configuration file to allow renicing as a user being part of the privileged gamemode group', value: 'true') option('with-pam-limits-dir', type: 'string', description: 'Explicitly set the PAM limits.d directory', value: '/etc/security/limits.d') # sd-bus provider option('with-sd-bus-provider', type: 'combo', choices: ['systemd', 'elogind', 'no-daemon'], value: 'systemd') # systemd specific option('with-systemd-user-unit', type: 'boolean', description: 'Install systemd user unit', value: 'true') option('with-systemd-user-unit-dir', type: 'string', description: 'Explicitly set the systemd user unit directory') option('with-systemd-group', type: 'boolean', description: 'Install privileged gamemode group with systemd', value: 'true') option('with-systemd-group-dir', type: 'string', description: 'Explicitly set the systemd group directory') # Not using systemd option('with-dbus-service-dir', type: 'string', description: 'Explicitly set the D-BUS session directory') # General options option('with-examples', type: 'boolean', description: 'Build sample programs', value: 'true') option('with-util', type: 'boolean', description: 'Build the utilities', value: 'true') option('with-privileged-group', type: 'string', description: 'Group that has access to privileged gamemode features', value: 'gamemode') gamemode-1.8.2/scripts/000077500000000000000000000000001466061601300147725ustar00rootroot00000000000000gamemode-1.8.2/scripts/format-check.sh000077500000000000000000000016611466061601300177000ustar00rootroot00000000000000#!/bin/bash # Simple script to check for clang-format compliance # Ensure we are at the project root cd "$(dirname $0)"/.. if [[ "$1" == "--pre-commit" ]]; then # used via .git/hooks/pre-commit: # exec "$(dirname $0)"/../../scripts/format-check.sh --pre-commit git-clang-format exit fi if [[ "$CI" == "true" ]]; then # used in ci, assumes clean repo clang-format -i $(find . -name '*.[ch]' -not -path "*subprojects/*") GIT_DIFF_OUTPUT=$(git diff) if [[ ! -z ${GIT_DIFF_OUTPUT} ]]; then echo "Failed clang format check:" echo "${GIT_DIFF_OUTPUT}" exit 1 else echo "Passed clang format check" exit 0 fi fi CLANG_FORMAT_OUTPUT=$(git-clang-format HEAD^ HEAD --diff) if [[ ! ${CLANG_FORMAT_OUTPUT} == "no modified files to format" ]] && [[ ! -z ${CLANG_FORMAT_OUTPUT} ]]; then echo "Failed clang format check:" echo "${CLANG_FORMAT_OUTPUT}" exit 1 else echo "Passed clang format check" exit 0 fi gamemode-1.8.2/scripts/mkrelease.sh000077500000000000000000000016361466061601300173070ustar00rootroot00000000000000#!/bin/bash set -e # Simple script to construct a redistributable and complete tarball of the # gamemode tree, including the subprojects, so that it can be trivially # packaged by distributions banning networking during build. NAME="gamemode" VERSION=$(git describe --tags --dirty) # get code in this repo git archive HEAD --format=tar --prefix=${NAME}-${VERSION}/ --output=${NAME}-${VERSION}.tar # get code from subprojects meson subprojects download meson subprojects update --reset tar -rf ${NAME}-${VERSION}.tar --exclude-vcs --transform="s,^subprojects,${NAME}-$VERSION/subprojects," subprojects/inih-r54/ # compress archive xz -9 "${NAME}-${VERSION}.tar" # Automatically sign the tarball with GPG key of user running this script gpg --armor --detach-sign "${NAME}-${VERSION}.tar.xz" gpg --verify "${NAME}-${VERSION}.tar.xz.asc" sha256sum "${NAME}-${VERSION}.tar.xz" "${NAME}-${VERSION}.tar.xz.asc" > sha256sums.txt gamemode-1.8.2/scripts/static-analyser-check.sh000077500000000000000000000004711466061601300215110ustar00rootroot00000000000000#!/bin/bash set -exo pipefail # Ensure we are at the project root cd "$(dirname $0)"/.. # Collect scan-build output ninja scan-build -C builddir | tee builddir/meson-logs/scan-build.txt # Invert the output - if this string exists it's a fail ! grep -E '[0-9]+ bugs? found.' builddir/meson-logs/scan-build.txt gamemode-1.8.2/subprojects/000077500000000000000000000000001466061601300156465ustar00rootroot00000000000000gamemode-1.8.2/subprojects/inih.wrap000066400000000000000000000004101466061601300174630ustar00rootroot00000000000000[wrap-file] directory = inih-r54 source_url = https://github.com/benhoyt/inih/archive/r54.tar.gz source_filename = inih-r54.tar.gz source_hash = b5566af5203f8a49fda27f1b864c0c157987678ffbd183280e16124012869869 [provide] inih = inih_dep inireader = INIReader_dep gamemode-1.8.2/util/000077500000000000000000000000001466061601300142605ustar00rootroot00000000000000gamemode-1.8.2/util/cpucorectl.c000066400000000000000000000074321466061601300165750ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include #include #include #include "common-cpu.h" #include "common-logging.h" static int write_state(char *path, int state) { FILE *f = fopen(path, "w"); if (!f) { LOG_ERROR("Couldn't open file at %s (%s)\n", path, strerror(errno)); return 0; } if (putc(state, f) == EOF) { LOG_ERROR("Couldn't write to file at %s (%s)\n", path, strerror(errno)); fclose(f); return 0; } fclose(f); return 1; } static void log_state(const int state, const long first, const long last) { if (state == '0') { if (first == last) LOG_MSG("parked core %ld\n", first); else LOG_MSG("parked cores %ld - %ld\n", first, last); } else { if (first == last) LOG_MSG("unparked core %ld\n", first); else LOG_MSG("unparked cores %ld - %ld\n", first, last); } } static int set_state(char *cpulist, int state) { char path[PATH_MAX]; long from, to; char *list = cpulist; long first = -1, last = -1; while ((list = parse_cpulist(list, &from, &to))) { for (long cpu = from; cpu < to + 1; cpu++) { if (snprintf(path, PATH_MAX, "/sys/devices/system/cpu/cpu%ld/online", cpu) < 0) { LOG_ERROR("snprintf failed, will not apply cpu core parking!\n"); return 0; } if (!write_state(path, state)) { /* on some systems one cannot park core #0 */ if (cpu != 0) { if (state == '0') { LOG_ERROR("unable to park core #%ld, will not apply cpu core parking!\n", cpu); return -1; } LOG_ERROR("unable to unpark core #%ld\n", cpu); } } else { if (first == -1) { first = cpu; last = cpu; } else if (last + 1 == cpu) { last = cpu; } else { log_state(state, first, last); first = cpu; last = cpu; } } } } if (first != -1) log_state(state, first, last); return 1; } int main(int argc, char *argv[]) { if (geteuid() != 0) { LOG_ERROR("This program must be run as root\n"); return EXIT_FAILURE; } if (argc == 3 && strncmp(argv[1], "online", 6) == 0) { if (!set_state(argv[2], '1')) return EXIT_FAILURE; } else if (argc == 3 && strncmp(argv[1], "offline", 7) == 0) { if (!set_state(argv[2], '0')) return EXIT_FAILURE; } else { fprintf(stderr, "usage: cpucorectl [online]|[offline] VALUE]\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } gamemode-1.8.2/util/cpugovctl.c000066400000000000000000000053611466061601300164370ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include "common-governors.h" #include "common-logging.h" #include /** * Sets all governors to a value */ static int set_gov_state(const char *value) { char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH] = { { 0 } }; int num = fetch_governors(governors); int retval = EXIT_SUCCESS; int res = 0; for (int i = 0; i < num; i++) { const char *gov = governors[i]; FILE *f = fopen(gov, "w"); if (!f) { LOG_ERROR("Failed to open file for write %s\n", gov); continue; } res = fprintf(f, "%s\n", value); if (res < 0) { LOG_ERROR("Failed to set governor %s to %s: %s", gov, 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_gov_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_gov_state(value); } else { fprintf(stderr, "usage: cpugovctl [get] [set VALUE]\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } gamemode-1.8.2/util/gpuclockctl.c000066400000000000000000000317661466061601300167530ustar00rootroot00000000000000/* 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. */ #define _GNU_SOURCE #include "common-external.h" #include "common-gpu.h" #include "common-logging.h" #include #include /* NV constants */ #define NV_CORE_OFFSET_ATTRIBUTE "GPUGraphicsClockOffset" #define NV_MEM_OFFSET_ATTRIBUTE "GPUMemoryTransferRateOffset" #define NV_POWERMIZER_MODE_ATTRIBUTE "GPUPowerMizerMode" #define NV_PERFMODES_ATTRIBUTE "GPUPerfModes" #define NV_PCIDEVICE_ATTRIBUTE "PCIDevice" #define NV_ATTRIBUTE_FORMAT "[gpu:%ld]/%s" #define NV_PERF_LEVEL_FORMAT "[%ld]" #define NV_ARG_MAX 128 /* AMD constants */ #define AMD_DRM_PATH "/sys/class/drm/card%ld/device/%s" /* Plausible extras to add: * Intel support - https://blog.ffwll.ch/2013/03/overclocking-your-intel-gpu-on-linux.html * AMD - Allow setting fan speed as well * Store baseline values with get_gpu_state to apply when leaving gamemode */ /* Helper to quit with usage */ static const char *usage_text = "usage: gpuclockctl DEVICE {arg}\n\t\tget - return current values\n\t\tset [NV_CORE NV_MEM " "NV_POWERMIZER_MODE | AMD_PERFORMANCE_LEVEL] - set current values"; static void print_usage_and_exit(void) { fprintf(stderr, "%s\n", usage_text); exit(EXIT_FAILURE); } static const char *get_nv_attr(const char *attr) { static char out[EXTERNAL_BUFFER_MAX]; const char *exec_args[] = { "nvidia-settings", "-q", attr, "-t", NULL }; if (run_external_process(exec_args, out, -1) != 0) { LOG_ERROR("Failed to get %s!\n", attr); out[0] = 0; return NULL; } return &out[0]; } static int set_nv_attr(const char *attr) { const char *exec_args_core[] = { "nvidia-settings", "-a", attr, NULL }; if (run_external_process(exec_args_core, NULL, -1) != 0) { LOG_ERROR("Failed to set %s!\n", attr); return -1; } return 0; } /* Get the nvidia driver index for the current GPU */ static long get_gpu_index_id_nv(struct GameModeGPUInfo *info) { if (info->vendor != Vendor_NVIDIA) return -1; /* NOTE: This is currently based off of a best guess of how the NVidia gpu index works * ie. that the index is simply the index into available NV gpus in the same order as drm * If that is not the case then this may fail to discern the correct GPU */ int device = 0; int nv_device = -1; while (device <= info->device) { /* Get the vendor for each gpu sequentially */ enum GPUVendor vendor = gamemode_get_gpu_vendor(device++); switch (vendor) { case Vendor_NVIDIA: /* If we've found an nvidia device, increment our counter */ nv_device++; break; case Vendor_Invalid: /* Bail out, we've gone too far */ LOG_ERROR("Failed to find Nvidia GPU with expected index!\n"); break; default: /* Non-NV gpu, continue */ break; } }; return nv_device; } /* Get the max nvidia perf level */ static long get_max_perf_level_nv(struct GameModeGPUInfo *info) { if (info->vendor != Vendor_NVIDIA) return -1; if (!getenv("DISPLAY")) LOG_ERROR("Getting Nvidia parameters requires DISPLAY to be set - will likely fail!\n"); char arg[NV_ARG_MAX] = { 0 }; const char *attr; snprintf(arg, NV_ARG_MAX, NV_ATTRIBUTE_FORMAT, info->device, NV_PERFMODES_ATTRIBUTE); if ((attr = get_nv_attr(arg)) == NULL) { return -1; } char *ptr = strrchr(attr, ';'); long level = -1; if (!ptr || sscanf(ptr, "; perf=%ld", &level) != 1) { LOG_ERROR( "Output didn't match expected format, couldn't discern highest perf level from " "nvidia-settings!\n"); LOG_ERROR("Output:%s\n", attr); return -1; } return level; } /* Get the nvidia gpu state */ static int get_gpu_state_nv(struct GameModeGPUInfo *info) { if (info->vendor != Vendor_NVIDIA) return -1; if (!getenv("DISPLAY")) LOG_ERROR("Getting Nvidia parameters requires DISPLAY to be set - will likely fail!\n"); long perf_level = get_max_perf_level_nv(info); char arg[NV_ARG_MAX] = { 0 }; const char *attr; char *end; /* Get the GPUGraphicsClockOffset parameter */ snprintf(arg, NV_ARG_MAX, NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT, info->device, NV_CORE_OFFSET_ATTRIBUTE, perf_level); if ((attr = get_nv_attr(arg)) == NULL) { 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); 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 0; } /** * Set the gpu state based on input parameters on Nvidia */ static int set_gpu_state_nv(struct GameModeGPUInfo *info) { int status = 0; if (info->vendor != Vendor_NVIDIA) return -1; if (!getenv("DISPLAY") || !getenv("XAUTHORITY")) LOG_ERROR( "Setting Nvidia parameters requires DISPLAY and XAUTHORITY to be set - will likely " "fail!\n"); long perf_level = get_max_perf_level_nv(info); char arg[NV_ARG_MAX] = { 0 }; /* Set the GPUGraphicsClockOffset parameter */ if (info->nv_core != -1) { snprintf(arg, NV_ARG_MAX, NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT "=%ld", info->device, NV_CORE_OFFSET_ATTRIBUTE, perf_level, info->nv_core); 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; } } return status; } /** * Get the gpu state * Populates the struct with the GPU info on the system */ static int get_gpu_state_amd(struct GameModeGPUInfo *info) { if (info->vendor != Vendor_AMD) return -1; /* Get the contents of power_dpm_force_performance_level */ char path[PATH_MAX]; snprintf(path, PATH_MAX, AMD_DRM_PATH, info->device, "power_dpm_force_performance_level"); FILE *file = fopen(path, "r"); if (!file) { LOG_ERROR("Could not open %s for read (%s)!\n", path, strerror(errno)); return -1; } int ret = 0; char buff[GPU_VALUE_MAX] = { 0 }; if (!fgets(buff, GPU_VALUE_MAX, file)) { LOG_ERROR("Could not read file %s (%s)!\n", path, strerror(errno)); ret = -1; } if (fclose(file) != 0) { LOG_ERROR("Could not close %s after reading (%s)!\n", path, strerror(errno)); ret = -1; } if (ret == 0) { /* Copy in the value from the file */ strncpy(info->amd_performance_level, buff, GPU_VALUE_MAX - 1); info->amd_performance_level[GPU_VALUE_MAX - 1] = '\0'; } return ret; } /* * Simply set an amd drm file to a value */ static int set_gpu_state_amd_file(const char *filename, long device, const char *value) { char path[PATH_MAX]; snprintf(path, PATH_MAX, AMD_DRM_PATH, device, filename); FILE *file = fopen(path, "w"); if (!file) { LOG_ERROR("Could not open %s for write (%s)!\n", path, strerror(errno)); return -1; } int ret = 0; if (fprintf(file, "%s", value) < 0) { LOG_ERROR("Could not write to %s (%s)!\n", path, strerror(errno)); ret = -1; } if (fclose(file) != 0) { LOG_ERROR("Could not close %s after writing (%s)!\n", path, strerror(errno)); ret = -1; } return ret; } /** * Set the gpu state based on input parameters on amd */ static int set_gpu_state_amd(struct GameModeGPUInfo *info) { if (info->vendor != Vendor_AMD) return -1; /* Must be root to set the state */ if (geteuid() != 0) { fprintf(stderr, "gpuclockctl must be run as root to set AMD values\n"); print_usage_and_exit(); } /* First set the amd_performance_level to the chosen setting */ if (set_gpu_state_amd_file("power_dpm_force_performance_level", info->device, info->amd_performance_level) != 0) return -1; /* TODO: If amd_performance_level is set to "manual" we need to adjust pp_table and/or pp_od_clk_voltage see https://dri.freedesktop.org/docs/drm/gpu/amdgpu.html#gpu-power-thermal-controls-and-monitoring */ return 0; } /* Helper to get and verify device value */ static long get_device(const char *val) { char *end; long ret = strtol(val, &end, 10); if (ret < 0 || end == val) { LOG_ERROR("Invalid GPU device passed (%ld)!\n", ret); print_usage_and_exit(); } return ret; } /* Helper to get and verify nv_core and nv_mem value */ static long get_generic_value(const char *val) { char *end; long ret = strtol(val, &end, 10); if (end == val) { LOG_ERROR("Invalid value passed (%ld)!\n", ret); print_usage_and_exit(); } return ret; } /** * Main entry point, dispatch to the appropriate helper */ int main(int argc, char *argv[]) { struct GameModeGPUInfo info; memset(&info, 0, sizeof(info)); if (argc == 3 && strncmp(argv[2], "get", 3) == 0) { /* Get and verify the vendor and device */ info.device = get_device(argv[1]); info.vendor = gamemode_get_gpu_vendor(info.device); /* Fetch the state and print it out */ switch (info.vendor) { case Vendor_NVIDIA: /* Adjust the device number to the gpu index for Nvidia */ info.device = get_gpu_index_id_nv(&info); if (get_gpu_state_nv(&info) != 0) exit(EXIT_FAILURE); printf("%ld %ld %ld\n", info.nv_core, info.nv_mem, info.nv_powermizer_mode); break; case Vendor_AMD: if (get_gpu_state_amd(&info) != 0) exit(EXIT_FAILURE); printf("%s\n", info.amd_performance_level); break; default: LOG_ERROR("Currently unsupported GPU vendor 0x%04x, doing nothing!\n", (unsigned short)info.vendor); break; } } else if (argc >= 4 && argc <= 7 && strncmp(argv[2], "set", 3) == 0) { /* Get and verify the vendor and device */ info.device = get_device(argv[1]); info.vendor = gamemode_get_gpu_vendor(info.device); switch (info.vendor) { case Vendor_NVIDIA: if (argc < 4) { LOG_ERROR("Must pass at least 4 arguments for nvidia gpu!\n"); print_usage_and_exit(); } info.nv_core = get_generic_value(argv[3]); info.nv_mem = get_generic_value(argv[4]); /* Adjust the device number to the gpu index for Nvidia */ info.device = get_gpu_index_id_nv(&info); /* Optional */ info.nv_powermizer_mode = -1; if (argc >= 6) info.nv_powermizer_mode = get_generic_value(argv[5]); return set_gpu_state_nv(&info); break; case Vendor_AMD: if (argc < 3) { LOG_ERROR("Must pass performance level for AMD gpu!\n"); print_usage_and_exit(); } strncpy(info.amd_performance_level, argv[3], GPU_VALUE_MAX - 1); return set_gpu_state_amd(&info); break; default: LOG_ERROR("Currently unsupported GPU vendor 0x%04x, doing nothing!\n", (unsigned short)info.vendor); print_usage_and_exit(); break; } } else { print_usage_and_exit(); } return EXIT_SUCCESS; } gamemode-1.8.2/util/meson.build000066400000000000000000000021471466061601300164260ustar00rootroot00000000000000 # Small target util to get and set cpu governors cpugovctl_sources = [ 'cpugovctl.c', ] cpugovctl = executable( 'cpugovctl', sources: cpugovctl_sources, dependencies: [ link_daemon_common, ], install: true, install_dir: path_libexecdir, ) # Small target util to get and set gpu clocks values gpuclockctl_sources = [ 'gpuclockctl.c', ] gpuclockctl = executable( 'gpuclockctl', sources: gpuclockctl_sources, dependencies: [ link_daemon_common, ], install: true, install_dir: path_libexecdir, ) # Small target util to park and unpark cores cpucorectl_sources = [ 'cpucorectl.c', ] cpucorectl = executable( 'cpucorectl', sources: cpucorectl_sources, dependencies: [ link_daemon_common, ], install: true, install_dir: path_libexecdir, ) # Small target util to set values in /proc/sys/ procsysctl_sources = [ 'procsysctl.c', ] procsysctl = executable( 'procsysctl', sources: procsysctl_sources, dependencies: [ link_daemon_common, ], install: true, install_dir: path_libexecdir, ) gamemode-1.8.2/util/procsysctl.c000066400000000000000000000047261466061601300166420ustar00rootroot00000000000000/* Copyright (c) 2017-2023, 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. */ #define _GNU_SOURCE #include #include "common-logging.h" static bool write_value(char *key, char *value) { FILE *f = fopen(key, "w"); if (!f) { if (errno != ENOENT) LOG_ERROR("Couldn't open file at %s (%s)\n", key, strerror(errno)); return false; } if (fputs(value, f) == EOF) { LOG_ERROR("Couldn't write to file at %s (%s)\n", key, strerror(errno)); fclose(f); return false; } fclose(f); return true; } int main(int argc, char *argv[]) { if (geteuid() != 0) { LOG_ERROR("This program must be run as root\n"); return EXIT_FAILURE; } if (argc == 3) { if (strcmp(argv[1], "split_lock_mitigate") == 0) { if (!write_value("/proc/sys/kernel/split_lock_mitigate", argv[2])) return EXIT_FAILURE; return EXIT_SUCCESS; } else { fprintf(stderr, "unsupported key: '%s'\n", argv[1]); return EXIT_FAILURE; } } fprintf(stderr, "usage: procsysctl KEY VALUE\n"); fprintf(stderr, "where KEY can by any of 'split_lock_mitigate'\n"); return EXIT_FAILURE; }