mirror of
https://github.com/FeralInteractive/gamemode.git
synced 2025-06-06 07:37:21 +02:00
Compare commits
380 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
af07e169d5 | ||
![]() |
6e5c950141 | ||
![]() |
499af4c7bb | ||
![]() |
5f691c3171 | ||
![]() |
f5fbdcf014 | ||
![]() |
7c49d6e1db | ||
![]() |
d84c5ded1c | ||
![]() |
2d71be6a94 | ||
![]() |
1e3e55c5d8 | ||
![]() |
e75f2c3942 | ||
![]() |
81d26c79b4 | ||
![]() |
5b3b4ae638 | ||
![]() |
1d9c1e6a72 | ||
![]() |
2eecd513ac | ||
![]() |
086b3fe5b4 | ||
![]() |
2f69f7c023 | ||
![]() |
c54d6d4243 | ||
![]() |
715a1389b7 | ||
![]() |
c0516059ed | ||
![]() |
a875008d8e | ||
![]() |
ec10c591ff | ||
![]() |
fef538ba92 | ||
![]() |
a2fe0108b5 | ||
![]() |
c854772369 | ||
![]() |
7d55ef856b | ||
![]() |
1c293d7818 | ||
![]() |
9646f2bd93 | ||
![]() |
8e0a71a0bc | ||
![]() |
3fa41891cf | ||
![]() |
8cea4c2418 | ||
![]() |
4a82094c98 | ||
![]() |
5180d89e66 | ||
![]() |
e82e9dafd0 | ||
![]() |
bb40374067 | ||
![]() |
c3b9d4ce8b | ||
![]() |
070216ad81 | ||
![]() |
6b03bc9354 | ||
![]() |
5867058d19 | ||
![]() |
49dc7ee49b | ||
![]() |
41191dc607 | ||
![]() |
22001c56b1 | ||
![]() |
088639e8b2 | ||
![]() |
9e21ac3924 | ||
![]() |
e2e8e981a2 | ||
![]() |
7389d5ed1d | ||
![]() |
162374c026 | ||
![]() |
4700089325 | ||
![]() |
c7a4572d73 | ||
![]() |
7381d93c13 | ||
![]() |
97e588a5ad | ||
![]() |
9a7ee00bbf | ||
![]() |
b2bd7e5919 | ||
![]() |
97cee92d94 | ||
![]() |
b9e8448afe | ||
![]() |
6c197f9b70 | ||
![]() |
485a7f920a | ||
![]() |
f48c58f34c | ||
![]() |
fad889db45 | ||
![]() |
775c93001c | ||
![]() |
160bf91665 | ||
![]() |
138ad384e3 | ||
![]() |
9614ceca9b | ||
![]() |
865ffb2588 | ||
![]() |
b83fb8f83e | ||
![]() |
e882505881 | ||
![]() |
88a15aba86 | ||
![]() |
9cb119be62 | ||
![]() |
fd226e46ba | ||
![]() |
91eb57574c | ||
![]() |
25a99af4a1 | ||
![]() |
740a82a761 | ||
![]() |
173a8594b4 | ||
![]() |
81dc3f95e5 | ||
![]() |
912d4bdc19 | ||
![]() |
85b9abd654 | ||
![]() |
2f7075b9c6 | ||
![]() |
2e26331d97 | ||
![]() |
303a5a9734 | ||
![]() |
a9042199c6 | ||
![]() |
3cabf9859d | ||
![]() |
51ee251efb | ||
![]() |
ba1c593d0e | ||
![]() |
7d00c1f0a4 | ||
![]() |
c070604a22 | ||
![]() |
dc2f7bbcd0 | ||
![]() |
437a129900 | ||
![]() |
4997adef8d | ||
![]() |
1b10b679cd | ||
![]() |
495a659895 | ||
![]() |
2dbd565340 | ||
![]() |
01024927d2 | ||
![]() |
32a0b5b636 | ||
![]() |
520dc85ac3 | ||
![]() |
82206534dc | ||
![]() |
1f28880509 | ||
![]() |
8cfc345312 | ||
![]() |
7cf59587ce | ||
![]() |
f5882d5158 | ||
![]() |
06f01938a9 | ||
![]() |
71f4b875ce | ||
![]() |
b103bfdd60 | ||
![]() |
4cffdef805 | ||
![]() |
3f969bbf1f | ||
![]() |
1ca2daf47f | ||
![]() |
179d5432e4 | ||
![]() |
4934191b19 | ||
![]() |
55b799e3df | ||
![]() |
337f1b8a8e | ||
![]() |
898ab01924 | ||
![]() |
1e24067430 | ||
![]() |
e34e9c5a43 | ||
![]() |
aee9703872 | ||
![]() |
4dc99dff76 | ||
![]() |
5fff74598a | ||
![]() |
c3d638d7ce | ||
![]() |
86591acdf2 | ||
![]() |
618466005e | ||
![]() |
f5a715e124 | ||
![]() |
f498f2a20a | ||
![]() |
9b44fc6def | ||
![]() |
b58b32072a | ||
![]() |
f50e7fffe7 | ||
![]() |
fac33baa09 | ||
![]() |
e7dcf8792d | ||
![]() |
db1a0e3ace | ||
![]() |
01b849a518 | ||
![]() |
b8fa857b35 | ||
![]() |
23493e5cc3 | ||
![]() |
c96f7379b4 | ||
![]() |
898feb9c52 | ||
![]() |
ab06ba419e | ||
![]() |
6c60565f33 | ||
![]() |
5e366bd55d | ||
![]() |
126e67fb6b | ||
![]() |
4000a32584 | ||
![]() |
b11d2912e2 | ||
![]() |
0344e7c311 | ||
![]() |
993857d0e8 | ||
![]() |
5fd0065df6 | ||
![]() |
6592c2229c | ||
![]() |
05a4c0c152 | ||
![]() |
fb7062bd9c | ||
![]() |
49a0ef8c0b | ||
![]() |
cfa8b9a2c4 | ||
![]() |
62659edd2b | ||
![]() |
9037b730f6 | ||
![]() |
5f71f57db1 | ||
![]() |
17b2b6201b | ||
![]() |
832f9ab7e0 | ||
![]() |
5163c01d24 | ||
![]() |
3fcff0d75f | ||
![]() |
510a0a6ae2 | ||
![]() |
f6c68cd6de | ||
![]() |
7a68a178ac | ||
![]() |
8b408694b0 | ||
![]() |
d8337aeb05 | ||
![]() |
d4536c62af | ||
![]() |
785df22274 | ||
![]() |
8810e4f158 | ||
![]() |
db7d52dbac | ||
![]() |
3033867fc9 | ||
![]() |
a48272ae61 | ||
![]() |
39f93e4000 | ||
![]() |
d27f8caecc | ||
![]() |
3d49f87308 | ||
![]() |
d47dea92de | ||
![]() |
16ebc794c5 | ||
![]() |
082b1c6619 | ||
![]() |
937bcf3c73 | ||
![]() |
e6355d91f4 | ||
![]() |
9ecff8d5d3 | ||
![]() |
9f676632c3 | ||
![]() |
115f1ecdbd | ||
![]() |
ce6485ef97 | ||
![]() |
953792b4a5 | ||
![]() |
1e8312f7e3 | ||
![]() |
1709810707 | ||
![]() |
c36019a9aa | ||
![]() |
6453a123ab | ||
![]() |
f95470c94a | ||
![]() |
a1660fc37a | ||
![]() |
43911a8919 | ||
![]() |
e1ae78e346 | ||
![]() |
f7a4a6ccfe | ||
![]() |
90d05eef42 | ||
![]() |
2fb62e34fa | ||
![]() |
c755f7e539 | ||
![]() |
1ca9b727c3 | ||
![]() |
329f7b4cee | ||
![]() |
695f7e565f | ||
![]() |
e3c24f34f1 | ||
![]() |
29b4148a00 | ||
![]() |
94444cb76f | ||
![]() |
57bc3e26ba | ||
![]() |
0ba1551293 | ||
![]() |
094905f7f8 | ||
![]() |
c8492ca28f | ||
![]() |
faea358023 | ||
![]() |
bfa20975ca | ||
![]() |
40702d3fed | ||
![]() |
616dca659a | ||
![]() |
d3e309b23b | ||
![]() |
1a598f53d2 | ||
![]() |
f0943ff431 | ||
![]() |
065454bb4e | ||
![]() |
d58c59c183 | ||
![]() |
9d34caa1fa | ||
![]() |
c2846f4a77 | ||
![]() |
78b326adb6 | ||
![]() |
1576c2b39e | ||
![]() |
79d0c64ef1 | ||
![]() |
688373a260 | ||
![]() |
c1646ecdd9 | ||
![]() |
2023a2a1ef | ||
![]() |
8b8113fb80 | ||
![]() |
57efe440c3 | ||
![]() |
b2b09fbb83 | ||
![]() |
d25379e001 | ||
![]() |
514ab58be3 | ||
![]() |
6f7df91b60 | ||
![]() |
b84d673aae | ||
![]() |
cd6c2ee612 | ||
![]() |
a6552044cd | ||
![]() |
5398dd1d19 | ||
![]() |
4b59818fd4 | ||
![]() |
35fb7f5baf | ||
![]() |
b7dc1dc10c | ||
![]() |
b513bc65ae | ||
![]() |
f6220a2d6e | ||
![]() |
24f054659c | ||
![]() |
0668e3b4da | ||
![]() |
30ae8d71f0 | ||
![]() |
509ae3593a | ||
![]() |
5add7f41b3 | ||
![]() |
8182edc048 | ||
![]() |
86473bea0a | ||
![]() |
711e5e9995 | ||
![]() |
33f8be9557 | ||
![]() |
24687f960b | ||
![]() |
acb57735fc | ||
![]() |
5c1b2d0c74 | ||
![]() |
cfe0fb4f17 | ||
![]() |
288b3a005e | ||
![]() |
52367772c8 | ||
![]() |
6f39ecbc9b | ||
![]() |
ffea085396 | ||
![]() |
47db83e509 | ||
![]() |
c7c464bea6 | ||
![]() |
c6d1b45bfb | ||
![]() |
9075829526 | ||
![]() |
02ad53584d | ||
![]() |
3881b8b2c8 | ||
![]() |
60c68feea6 | ||
![]() |
f9827edfb6 | ||
![]() |
d2bab2962d | ||
![]() |
5949a988ea | ||
![]() |
208f37b7d1 | ||
![]() |
455ea0c72e | ||
![]() |
d99af40795 | ||
![]() |
7e10cc3a0b | ||
![]() |
1a863f32a1 | ||
![]() |
2027e981e6 | ||
![]() |
b3cec8d901 | ||
![]() |
ebbb9a3511 | ||
![]() |
15ff22c745 | ||
![]() |
e5286e1495 | ||
![]() |
b0c36c0eaa | ||
![]() |
2a124ce8c7 | ||
![]() |
fc46ffc463 | ||
![]() |
e9ab20be60 | ||
![]() |
702407595a | ||
![]() |
a5e00bc94e | ||
![]() |
c5c966ad54 | ||
![]() |
e537caf170 | ||
![]() |
2e67906402 | ||
![]() |
28243afde9 | ||
![]() |
98e656f9ec | ||
![]() |
1b78d0dcf7 | ||
![]() |
41988b7f1c | ||
![]() |
2bca1fb04e | ||
![]() |
81e38d02e6 | ||
![]() |
9d484061ad | ||
![]() |
717d6cc003 | ||
![]() |
1df1852c76 | ||
![]() |
75dc083616 | ||
![]() |
a837b8630c | ||
![]() |
37108d7c6b | ||
![]() |
7ebe633026 | ||
![]() |
71d4fab15e | ||
![]() |
e87a8f19f3 | ||
![]() |
3a2ebd1cdf | ||
![]() |
a0b850474d | ||
![]() |
a9e3f866a0 | ||
![]() |
128d9b1364 | ||
![]() |
c502e00b7c | ||
![]() |
83c4d38858 | ||
![]() |
0c36f3a6b0 | ||
![]() |
536d687c9a | ||
![]() |
44ab695246 | ||
![]() |
9db7442a31 | ||
![]() |
23dd471f6b | ||
![]() |
89904602e9 | ||
![]() |
c276f760c7 | ||
![]() |
5d0a413035 | ||
![]() |
ec55bda3b2 | ||
![]() |
ceb1808c95 | ||
![]() |
16e7d06083 | ||
![]() |
b04e39df43 | ||
![]() |
09e475e092 | ||
![]() |
139b644d6d | ||
![]() |
f41ecda047 | ||
![]() |
7ede50af39 | ||
![]() |
ddecc89f10 | ||
![]() |
ddc802573a | ||
![]() |
a27e741beb | ||
![]() |
f401b49b1a | ||
![]() |
41d35fa12a | ||
![]() |
0d018d91a8 | ||
![]() |
ef29b35258 | ||
![]() |
139b90e0b7 | ||
![]() |
4091a0c532 | ||
![]() |
f00a89bc56 | ||
![]() |
2d19b61a38 | ||
![]() |
16c932f5d0 | ||
![]() |
f152ea9338 | ||
![]() |
3ac49385dc | ||
![]() |
87cfd9c5a6 | ||
![]() |
38e48a2d8e | ||
![]() |
bf2b057915 | ||
![]() |
6639b17500 | ||
![]() |
6869470f9b | ||
![]() |
934b497603 | ||
![]() |
5ffe832faf | ||
![]() |
7f196cdd1a | ||
![]() |
598969a538 | ||
![]() |
2249a71355 | ||
![]() |
6d14149658 | ||
![]() |
09d63ae4f5 | ||
![]() |
7e5216c4a0 | ||
![]() |
a482b72d37 | ||
![]() |
717777e6c2 | ||
![]() |
baff9c0363 | ||
![]() |
99c7d04e69 | ||
![]() |
4a49a1c2a5 | ||
![]() |
67c7aa04d6 | ||
![]() |
70e601267b | ||
![]() |
0c778200ae | ||
![]() |
001a69f565 | ||
![]() |
393a5e8f41 | ||
![]() |
6291c13cf4 | ||
![]() |
dbf9974fed | ||
![]() |
0aced6f14e | ||
![]() |
05909d1cfa | ||
![]() |
6d921617f9 | ||
![]() |
e86580c18c | ||
![]() |
dbc5c453d5 | ||
![]() |
a91156ef93 | ||
![]() |
78c2ced7d7 | ||
![]() |
b411cfff6b | ||
![]() |
d7394cbeb2 | ||
![]() |
618997f0b3 | ||
![]() |
85d75c303c | ||
![]() |
959c48978b | ||
![]() |
e5ccb4b68d | ||
![]() |
1a51a2ec00 | ||
![]() |
9465c5f6bd | ||
![]() |
efb8fbc3af | ||
![]() |
41b85c245d | ||
![]() |
34d86d30b1 | ||
![]() |
4a577b8c7c | ||
![]() |
c34186be07 | ||
![]() |
d12ab6830f | ||
![]() |
45ba5bc4c4 | ||
![]() |
0eb59fc848 | ||
![]() |
bbde1d0357 | ||
![]() |
db4dd87e22 | ||
![]() |
c0032efc4f | ||
![]() |
5516b547ca | ||
![]() |
c0ffecae43 | ||
![]() |
95c365076f |
@ -2,8 +2,7 @@
|
||||
AccessModifierOffset: 0
|
||||
AlignAfterOpenBracket: true
|
||||
AlignConsecutiveAssignments: false
|
||||
#uncomment for clang 3.9
|
||||
#AlignConsecutiveDeclarations: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlinesLeft: false
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
@ -14,21 +13,17 @@ AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
# AlwaysBreakAfterDefinitionReturnType: None
|
||||
#uncomment for clang 3.9
|
||||
#AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
BinPackArguments: false
|
||||
BinPackParameters: true
|
||||
# BraceWrapping: (not set since BreakBeforeBraces is not Custom)
|
||||
BreakBeforeBinaryOperators: None
|
||||
# BreakAfterJavaFieldAnnotations: (not java)
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Linux
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
#uncomment for clang 3.9
|
||||
#BreakStringLiterals: false
|
||||
# Too new for travis clang-format version
|
||||
# BreakStringLiterals: false
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: '\*\<'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
@ -39,35 +34,35 @@ DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
ForEachMacros: [ ]
|
||||
#Uncomment for clang 3.9
|
||||
#IncludeCategories:
|
||||
# - Regex: '^"'
|
||||
# Priority: 1
|
||||
SortIncludes: true
|
||||
# IncludeBlocksStyle changed to IncludeBlocks, between xenial and disco, so we can't use it for consistency
|
||||
# IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<linux\/'
|
||||
Priority: 0
|
||||
- Regex: '^<'
|
||||
Priority: 1
|
||||
- Regex: '^"gamemode.h"'
|
||||
Priority: 2
|
||||
- Regex: '^"'
|
||||
Priority: 3
|
||||
# IncludeIsMainRegex: (project doesn't use a main includes that can add other includes via regex)
|
||||
IndentCaseLabels: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
# JavaScriptQuotes: (not javascript)
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
Language: Cpp
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
# ObjCBlockIndentWidth: (not objc)
|
||||
# ObjCSpaceAfterProperty: (not objc)
|
||||
# ObjCSpaceBeforeProtocolList: (not objc)
|
||||
PenaltyBreakBeforeFirstCallParameter: 400
|
||||
PenaltyBreakComment: 0
|
||||
# PenaltyBreakFirstLessLess: (not cpp)
|
||||
PenaltyBreakString: 500
|
||||
PenaltyExcessCharacter: 10000
|
||||
PenaltyReturnTypeOnItsOwnLine: 600
|
||||
PointerAlignment: Right
|
||||
#uncomment for clang 3.9
|
||||
#ReflowComments: true
|
||||
#uncomment for clang 3.9
|
||||
#SortIncludes: true
|
||||
ReflowComments: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
@ -75,7 +70,6 @@ SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
# SpacesInContainerLiterals: (not objc or javascript)
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
|
41
.github/workflows/build-and-test.yml
vendored
Normal file
41
.github/workflows/build-and-test.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: Build and test
|
||||
on: [push, pull_request]
|
||||
permissions:
|
||||
contents: read
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential meson appstream clang clang-format clang-tools libdbus-1-dev libinih-dev libsystemd-dev systemd-dev
|
||||
- name: Check format
|
||||
env:
|
||||
CI: "true"
|
||||
run: |
|
||||
./scripts/format-check.sh
|
||||
- name: Build and install
|
||||
env:
|
||||
CI: "true"
|
||||
run: |
|
||||
./bootstrap.sh -Dwith-examples=true
|
||||
- name: Tests
|
||||
run: |
|
||||
meson test -C builddir
|
||||
- name: Simulate game
|
||||
run: |
|
||||
dbus-run-session -- gamemode-simulate-game
|
||||
- name: Static analyser check
|
||||
run: |
|
||||
./scripts/static-analyser-check.sh
|
||||
- name: Upload logs
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: logs
|
||||
path: |
|
||||
builddir/meson-logs/
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
build/
|
||||
*.swp
|
||||
/builddir
|
||||
/subprojects/packagecache/
|
||||
/subprojects/inih-r*
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "subprojects/inih"]
|
||||
path = subprojects/inih
|
||||
url = https://github.com/FeralInteractive/inih.git
|
23
.travis.yml
23
.travis.yml
@ -1,23 +0,0 @@
|
||||
dist: xenial
|
||||
language: c
|
||||
compiler: gcc
|
||||
sudo: false
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- clang-format
|
||||
- python3-pip
|
||||
- python3-setuptools
|
||||
- libsystemd-dev
|
||||
- ninja-build
|
||||
|
||||
before_script:
|
||||
- pip3 install wheel
|
||||
- pip3 install meson
|
||||
- meson --version
|
||||
|
||||
script:
|
||||
- ./scripts/format-check.sh
|
||||
- ./bootstrap.sh
|
||||
- gamemoded -v
|
165
CHANGELOG.md
165
CHANGELOG.md
@ -1,3 +1,168 @@
|
||||
## 1.8.2
|
||||
|
||||
### Changes
|
||||
|
||||
* Fix idle inhibitor closing bus connection too early (#466)
|
||||
* Fix hybrid CPU core pinning (#455)
|
||||
* Fix unreadable process maps in gamemodelist (#463)
|
||||
* Fixed crash if dbus is not accesible (#458)
|
||||
* Various bugfixes and improvements to documentation
|
||||
|
||||
### Contributors
|
||||
|
||||
* @notpeelz
|
||||
* @patatahooligan
|
||||
* Reilly Brogan @ReillyBrogan
|
||||
* Alexandru Ionut Tripon @Trial97
|
||||
* Kostadin @kostadinsh
|
||||
* Daniel Martinez @Calandracas606
|
||||
|
||||
[View the full list of contributors](https://github.com/FeralInteractive/gamemode/graphs/contributors?from=2023-12-13&to=2024-08-19&type=c)
|
||||
|
||||
## 1.8.1
|
||||
|
||||
### Changes
|
||||
|
||||
* Fix polkit parse error (#449)
|
||||
|
||||
### Contributors
|
||||
|
||||
* @Vam-Jam
|
||||
|
||||
[View the full list of contributors](https://github.com/FeralInteractive/gamemode/graphs/contributors?from=2022-07-22&to=2023-12-12&type=c)
|
||||
|
||||
## 1.8
|
||||
|
||||
### Changes
|
||||
|
||||
* Add CPU core pinning and parking capability (#416)
|
||||
* Allow disabling the Linux kernel split lock mitigation (#446)
|
||||
* Fix building when pidfd_open is available (Fixes build with glibc 2.36) (#379)
|
||||
* Unify privileged group configuration between pam, systemd, & polkit (#375)
|
||||
* Various other bugfixes and improved default configuration
|
||||
|
||||
### Contributors
|
||||
|
||||
* Henrik Holst @HenrikHolst
|
||||
* Kira Bruneau @kira-bruneau
|
||||
* James Le Cuirot @chewi
|
||||
* Hugo Locurcio @Calinou
|
||||
* Zoltán Nyikos @nyz93
|
||||
* @ashuntu
|
||||
* @szymon-gniado
|
||||
|
||||
[View the full list of contributors](https://github.com/FeralInteractive/gamemode/graphs/contributors?from=2022-07-22&to=2023-12-06&type=c)
|
||||
|
||||
## 1.7
|
||||
|
||||
### Changes
|
||||
|
||||
* Added new utility: `gamemodelist` (#346)
|
||||
* Run executables from `PATH` instead of `/usr/bin` (#323)
|
||||
* Add a trivial `gamemode.conf` file, which creates the gamemode group (#339)
|
||||
* Various minor bugfixes and updates to documentation
|
||||
|
||||
### Contributors
|
||||
|
||||
* Sam Gleske @samrocketman
|
||||
* Kira Bruneau @kira-bruneau
|
||||
* Stephan Lachnit @stephanlachnit
|
||||
* Emil Velikov @evelikov-work
|
||||
|
||||
[View the full list of contributors](https://github.com/FeralInteractive/gamemode/graphs/contributors?from=2021-02-19&to=2022-07-21&type=c)
|
||||
|
||||
## 1.6.1
|
||||
|
||||
### Changes
|
||||
|
||||
* Use inih r53
|
||||
* Packaging changes for Arch
|
||||
* Minor metainfo improvements
|
||||
|
||||
### Contributors
|
||||
|
||||
* Stephan Lachnit @stephanlachnit
|
||||
* Alberto Oporto Ames @otreblan
|
||||
|
||||
## 1.6
|
||||
|
||||
### Changes
|
||||
|
||||
* Created new manpages for `gamemoderun` and the example, now called `gamemode-simulate-game`
|
||||
* Add ability to change lib directory of `gamemoderun`
|
||||
* Add option to use `elogind`
|
||||
* Copy default config file to the correct location
|
||||
* Allow `LD_PRELOAD` to be overridden in `$GAMEMODERUNEXEC`
|
||||
* Various minor bugfixes
|
||||
* Improvements to dependency management
|
||||
|
||||
### Contributors
|
||||
|
||||
* Stephan Lachnit @stephanlachnit
|
||||
* Rafał Mikrut @qarmin
|
||||
* Niels Thykier @nthykier
|
||||
* Stéphane Gleizes @sgleizes
|
||||
|
||||
## 1.5.1
|
||||
|
||||
### Changes
|
||||
|
||||
Minor changes for Debian and Ubuntu packaging:
|
||||
* Use the preferred logging system rather than defaulting to syslog.
|
||||
* Prefer the system installation of inih.
|
||||
|
||||
### Contributors
|
||||
|
||||
* Sebastien Bacher @seb128
|
||||
* Stephan Lachnit @stephanlachnit
|
||||
|
||||
## 1.5
|
||||
|
||||
### Changes
|
||||
|
||||
* Introduce a new pidfd based set of D-Bus APIs (#173)
|
||||
* Dynamically change governor on integrated GPUs for improved performance (#179)
|
||||
* Various other fixes and improvements.
|
||||
|
||||
### Contributors
|
||||
|
||||
* Alex Smith @aejsmith
|
||||
* Christian Kellner @gicmo
|
||||
* Faith Ekstrand @gfxstrand
|
||||
|
||||
## 1.4
|
||||
|
||||
### Changes
|
||||
|
||||
* Add new D-Bus methods/properties for use by external tools such as the [GameMode GNOME Shell extension](https://github.com/gicmo/gamemode-extension/) (#129, #155, #161).
|
||||
* Fix I/O priority and niceness optimisations to apply to the whole process rather than just the thread that requests GameMode (#142).
|
||||
* `gamemoded` will now automatically reload the configuration file when it is changed and update optimisations on current clients (#144).
|
||||
* Add support for using the client library inside Flatpak by communicating with the daemon via a portal (#146).
|
||||
* Client library now uses libdbus rather than sd-bus (#147).
|
||||
* Fix `gamemoderun` to use the correct library path depending on whether the app is 32-bit or 64-bit.
|
||||
* Support the `GAMEMODERUNEXEC` environment variable to specify an extra wrapper command for games launched with `gamemoderun` (e.g. a hybrid GPU wrapper such as `optirun`) (#159).
|
||||
* Various other fixes and improvements.
|
||||
|
||||
### Contributors
|
||||
|
||||
* Christian Kellner @gicmo
|
||||
* Marc Di Luzio @mdiluz
|
||||
* Matthias Gerstner @mgerstner
|
||||
* Minze Zwerver @ysblokje
|
||||
* Stephan Lachnit @stephanlachnit
|
||||
* Timo Gurr @tgurr
|
||||
|
||||
## 1.3.1
|
||||
|
||||
### Changes
|
||||
|
||||
* Change permission of `gamemoderun` in source tree so that it is correctly installed with execute permissions on older Meson versions (such as that included with Ubuntu 18.04) (#115).
|
||||
* Enable more compiler warnings and fix issues highlighted by these.
|
||||
|
||||
### Contributors
|
||||
|
||||
* Christian Kellner @gicmo
|
||||
|
||||
## 1.3
|
||||
|
||||
### Changes
|
||||
|
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
224
README.md
224
README.md
@ -1,69 +1,28 @@
|
||||
# GameMode
|
||||
**GameMode** is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS.
|
||||
**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.
|
||||
|
||||
GameMode 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)
|
||||
* CPU core pinning or parking
|
||||
* Custom scripts
|
||||
|
||||
GameMode packages are available for Ubuntu, Debian, Solus, Arch, Gentoo, Fedora, OpenSUSE, Mageia and possibly more.
|
||||
|
||||
Issues with GameMode should be reported here in the issues section, and not reported to Feral directly.
|
||||
|
||||
---
|
||||
## Building and installing [](https://travis-ci.org/FeralInteractive/gamemode)
|
||||
|
||||
*It is preferable to install GameMode with your package manager of choice, if available*. There are Ubuntu (Cosmic), Solus, AUR, Gentoo and Fedora packages available at the time of writing.
|
||||
|
||||
### Install Dependencies
|
||||
GameMode depends on `meson` for building and `systemd` for internal communication. This repo contains a `bootstrap.sh` script to allow for quick install to the user bus, but check `meson_options.txt` for custom settings.
|
||||
|
||||
#### Ubuntu/Debian (you may also need `dbus-user-session`)
|
||||
```bash
|
||||
apt install meson libsystemd-dev pkg-config ninja-build git
|
||||
```
|
||||
#### Arch
|
||||
```bash
|
||||
pacman -S meson systemd git
|
||||
```
|
||||
#### Fedora
|
||||
```bash
|
||||
dnf install meson systemd-devel pkg-config git
|
||||
```
|
||||
#### Gentoo
|
||||
Gentoo has an ebuild which builds a stable release from sources. It will also pull in all the dependencies so you can work on the source code.
|
||||
```bash
|
||||
emerge --ask games-util/gamemode
|
||||
```
|
||||
You can also install using the latest sources from git:
|
||||
```bash
|
||||
ACCEPT_KEYWORDS="**" emerge --ask ~games-util/gamemode-9999
|
||||
```
|
||||
|
||||
### Build and Install GameMode
|
||||
Then clone, build and install a release version of GameMode at 1.3:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/FeralInteractive/gamemode.git
|
||||
cd gamemode
|
||||
git checkout 1.3 # omit to build the master branch
|
||||
./bootstrap.sh
|
||||
```
|
||||
|
||||
To uninstall:
|
||||
```bash
|
||||
systemctl --user stop gamemoded.service
|
||||
cd build/
|
||||
ninja uninstall
|
||||
```
|
||||
|
||||
---
|
||||
## Requesting GameMode
|
||||
|
||||
After installing `libgamemodeauto.so.0` simply preload it into the game:
|
||||
For games/launchers which integrate GameMode support, simply running the game will automatically activate GameMode.
|
||||
|
||||
For others, you must manually request GameMode when running the game. This can be done by launching the game through `gamemoderun`:
|
||||
```bash
|
||||
gamemoderun ./game
|
||||
```
|
||||
@ -76,103 +35,120 @@ Note: for older versions of GameMode (before 1.3) use this string in place of `g
|
||||
```
|
||||
LD_PRELOAD="$LD_PRELOAD:/usr/\$LIB/libgamemodeauto.so.0"
|
||||
```
|
||||
Please note the backslash here in `\$LIB` is required.
|
||||
**Please note the backslash here in `\$LIB` is required.**
|
||||
|
||||
---
|
||||
## Configuration
|
||||
|
||||
The daemon can currently be configured using a `gamemode.ini` file. [gamemode.ini](https://github.com/FeralInteractive/gamemode/blob/master/example/gamemode.ini) is an example of what this file would look like, with explanations for all the variables.
|
||||
The daemon is configured with a `gamemode.ini` file. [example/gamemode.ini](https://github.com/FeralInteractive/gamemode/blob/master/example/gamemode.ini) is an example of what this file would look like, with explanations for all the variables.
|
||||
|
||||
Config files are loaded and merged from the following directories, in order:
|
||||
1. `/usr/share/gamemode/`
|
||||
2. `/etc/`
|
||||
3. `$XDG_CONFIG_HOME` or `$HOME/.config/`
|
||||
4. `$PWD`
|
||||
Configuration files are loaded and merged from the following directories, from highest to lowest priority:
|
||||
|
||||
The file parsing uses [inih](https://github.com/benhoyt/inih).
|
||||
1. `$PWD` ("unsafe" - **`[gpu]` settings take no effect in this file**)
|
||||
2. `$XDG_CONFIG_HOME` or `$HOME/.config/` ("unsafe" - **`[gpu]` settings take no effect in this file**)
|
||||
3. `/etc/`
|
||||
4. `/usr/share/gamemode/`
|
||||
|
||||
---
|
||||
## Features
|
||||
## Note for Hybrid GPU users
|
||||
|
||||
### 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.
|
||||
It's not possible to integrate commands like optirun automatically inside GameMode, since the GameMode request is made once the game has already started. However it is possible to use a hybrid GPU wrapper like optirun by starting the game with `gamemoderun`.
|
||||
|
||||
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`.
|
||||
You can do this by setting the environment variable `GAMEMODERUNEXEC` to your wrapper's launch command, so for example `GAMEMODERUNEXEC=optirun`, `GAMEMODERUNEXEC="env DRI_PRIME=1"`, or `GAMEMODERUNEXEC="env __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia __VK_LAYER_NV_optimus=NVIDIA_only"`. This environment variable can be set globally (e.g. in /etc/environment), so that the same prefix command does not have to be duplicated everywhere you want to use `gamemoderun`.
|
||||
|
||||
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 will not be injected to the wrapper.
|
||||
|
||||
---
|
||||
## Developers
|
||||
## Development [](https://github.com/FeralInteractive/gamemode/actions/workflows/build-and-test.yml)
|
||||
|
||||
The design of GameMode has a clear-cut abstraction between the host daemon and library (`gamemoded` and `libgamemode`), and the client loaders (`libgamemodeauto` and `gamemode_client.h`) that allows for safe use 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.
|
||||
|
||||
### Components
|
||||
**gamemoded** runs in the background, activates game mode on request, refcounts and also checks caller PID lifetime. Run `man gamemoded` for command line options.
|
||||
See repository subdirectories for information on each component.
|
||||
|
||||
**libgamemode** is an internal library used to dispatch requests to the daemon. Note: `libgamemode` should never be linked with directly.
|
||||
### Install Dependencies
|
||||
GameMode depends on `meson` for building and `systemd` for internal communication. This repo contains a `bootstrap.sh` script to allow for quick install to the user bus, but check `meson_options.txt` for custom settings. These instructions all assume that you
|
||||
already have a C development environment (gcc or clang, libc-devel, etc) installed.
|
||||
|
||||
**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
|
||||
#### Ubuntu/Debian
|
||||
Note: Debian 13 and Ubuntu 25.04 (and later) need to install `systemd-dev` in addition to the dependencies below.
|
||||
```bash
|
||||
apt update && apt install meson libsystemd-dev pkg-config ninja-build git dbus-user-session libdbus-1-dev libinih-dev build-essential
|
||||
```
|
||||
|
||||
```C
|
||||
// Automatically on program start and finish
|
||||
#define GAMEMODE_AUTO
|
||||
#include "gamemode_client.h"
|
||||
On Debian 12 and Ubuntu 22 (and earlier), you'll need to install `python3` and `python3-venv` packages to install the latest meson version from `pip`.
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install meson
|
||||
```
|
||||
|
||||
Or, distribute `libgamemodeauto.so` and either add `-lgamemodeauto` to your linker arguments, or add it to an LD\_PRELOAD in a launch script.
|
||||
Later you can deactivate the virtual environment and remove it.
|
||||
|
||||
### 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);
|
||||
```bash
|
||||
deactivate
|
||||
rm -rf .venv
|
||||
```
|
||||
|
||||
This functionality can also be controlled in the config file in the `supervisor` section.
|
||||
#### Arch
|
||||
```bash
|
||||
pacman -S meson systemd git dbus libinih gcc pkgconf
|
||||
```
|
||||
|
||||
---
|
||||
## Contributions
|
||||
#### RHEL 10 and variants
|
||||
Note: Older versions of RHEL (and variants) cannot build gamemode due to not exposing libdbus-1 to pkg-config.
|
||||
(also - don't try and play games on RHEL, come on)
|
||||
|
||||
You must have [EPEL](https://docs.fedoraproject.org/en-US/epel/getting-started/) enabled to install all dependencies.
|
||||
```bash
|
||||
dnf install meson systemd-devel pkg-config git dbus-devel inih-devel
|
||||
```
|
||||
|
||||
#### Fedora
|
||||
```bash
|
||||
dnf install meson systemd-devel pkg-config git dbus-devel inih-devel
|
||||
```
|
||||
|
||||
#### OpenSUSE Leap/Tumbleweed
|
||||
```bash
|
||||
zypper install meson systemd-devel git dbus-1-devel libgcc_s1 libstdc++-devel libinih-devel
|
||||
```
|
||||
|
||||
#### Gentoo
|
||||
Gentoo has an ebuild which builds a stable release from sources. It will also pull in all the dependencies so you can work on the source code.
|
||||
```bash
|
||||
emerge --ask games-util/gamemode
|
||||
```
|
||||
You can also install using the latest sources from git:
|
||||
```bash
|
||||
ACCEPT_KEYWORDS="**" emerge --ask ~games-util/gamemode-9999
|
||||
```
|
||||
|
||||
#### Nix
|
||||
Similar to Gentoo, nixOS already has a package for gamemode, so we can use that to setup an environment:
|
||||
```bash
|
||||
nix-shell -p pkgs.gamemode.buildInputs pkgs.gamemode.nativeBuildInputs
|
||||
```
|
||||
|
||||
### Build and Install GameMode
|
||||
Then clone, build and install a release version of GameMode at 1.8.2:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/FeralInteractive/gamemode.git
|
||||
cd gamemode
|
||||
git checkout 1.8.2 # omit to build the master branch
|
||||
./bootstrap.sh
|
||||
```
|
||||
To test GameMode installed and will run correctly:
|
||||
```bash
|
||||
gamemoded -t
|
||||
```
|
||||
|
||||
To uninstall:
|
||||
```bash
|
||||
systemctl --user stop gamemoded.service
|
||||
ninja uninstall -C builddir
|
||||
```
|
||||
|
||||
### Pull Requests
|
||||
Pull requests must match with the coding style found in the `.clang-format` file, please run this before committing:
|
||||
@ -180,10 +156,6 @@ Pull requests must match with the coding style found in the `.clang-format` file
|
||||
clang-format -i $(find . -name '*.[ch]' -not -path "*subprojects/*")
|
||||
```
|
||||
|
||||
### Planned Features
|
||||
* Additional mode-switch plugins
|
||||
* Improved client state tracking (PID is unreliable)
|
||||
|
||||
### Maintained by
|
||||
Feral Interactive
|
||||
|
||||
@ -192,6 +164,8 @@ See the [contributors](https://github.com/FeralInteractive/gamemode/graphs/contr
|
||||
---
|
||||
## License
|
||||
|
||||
Copyright © 2017-2019 Feral Interactive
|
||||
Copyright © 2017-2025 Feral Interactive and the GameMode contributors
|
||||
|
||||
GameMode is available under the terms of the BSD 3-Clause License (Revised)
|
||||
|
||||
The "inih" library is distributed under the New BSD license
|
||||
|
26
bootstrap.sh
26
bootstrap.sh
@ -14,9 +14,9 @@ if [ ! -f "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor" ]; then
|
||||
echo "This probably means that you have disabled processor scheduling features in your BIOS. See README.md (or GitHub issue #44) for more information."
|
||||
echo "This means GameMode's CPU governor control feature will not work (other features will still work)."
|
||||
|
||||
if [ "$TRAVIS" != "true" ]; then
|
||||
if [ "$CI" != "true" ]; then
|
||||
# Allow to continue the install, as gamemode has other useful features
|
||||
read -p "Would you like to continue anyway [Y/N]? " -r
|
||||
read -p "Would you like to continue anyway [y/N]? " -r
|
||||
[[ $REPLY =~ ^[Yy]$ ]]
|
||||
fi
|
||||
fi
|
||||
@ -26,19 +26,25 @@ fi
|
||||
|
||||
# Echo the rest so it's obvious
|
||||
set -x
|
||||
meson --prefix=$prefix build -Dwith-systemd-user-unit-dir=/etc/systemd/user
|
||||
cd build
|
||||
ninja
|
||||
meson setup builddir --prefix=$prefix --buildtype debugoptimized -Dwith-systemd-user-unit-dir=/etc/systemd/user "$@"
|
||||
ninja -C builddir
|
||||
|
||||
# Verify user wants to install
|
||||
set +x
|
||||
if [ "$TRAVIS" != "true" ]; then
|
||||
read -p "Install to $prefix? [Yy] " -r
|
||||
if [ "$CI" != "true" ]; then
|
||||
read -p "Install to $prefix? [y/N] " -r
|
||||
[[ $REPLY =~ ^[Yy]$ ]]
|
||||
fi
|
||||
set -x
|
||||
|
||||
sudo ninja install
|
||||
sudo ninja install -C builddir
|
||||
|
||||
# Reload systemd configuration so that it picks up the new service.
|
||||
systemctl --user daemon-reload
|
||||
if [ "$CI" != "true" ]; then
|
||||
# Restart polkit so we don't get pop-ups whenever we pkexec
|
||||
if systemctl list-unit-files | grep -q polkit.service; then
|
||||
sudo systemctl try-restart polkit
|
||||
fi
|
||||
|
||||
# Reload systemd configuration so that it picks up the new service.
|
||||
systemctl --user daemon-reload
|
||||
fi
|
||||
|
71
common/common-cpu.c
Normal file
71
common/common-cpu.c
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
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-cpu.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
char *parse_cpulist(char *cpulist, long *from, long *to)
|
||||
{
|
||||
if (!cpulist || *cpulist == '\0')
|
||||
return NULL;
|
||||
|
||||
char *endp;
|
||||
*from = strtol(cpulist, &endp, 10);
|
||||
|
||||
if (endp == cpulist)
|
||||
return NULL;
|
||||
|
||||
if (*endp == '\0' || *endp == ',') {
|
||||
*to = *from;
|
||||
|
||||
if (*endp == '\0')
|
||||
return endp;
|
||||
|
||||
return endp + 1;
|
||||
}
|
||||
|
||||
if (*endp != '-')
|
||||
return NULL;
|
||||
|
||||
cpulist = endp + 1;
|
||||
*to = strtol(cpulist, &endp, 10);
|
||||
|
||||
if (endp == cpulist)
|
||||
return NULL;
|
||||
|
||||
if (*to < *from)
|
||||
return NULL;
|
||||
|
||||
if (*endp == '\0')
|
||||
return endp;
|
||||
|
||||
return endp + 1;
|
||||
}
|
51
common/common-cpu.h
Normal file
51
common/common-cpu.h
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
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 _GNU_SOURCE
|
||||
|
||||
#include <sched.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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);
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -31,16 +31,76 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "external-helper.h"
|
||||
#include "logging.h"
|
||||
#include "common-external.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
#include <linux/limits.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static const int default_timeout = 5;
|
||||
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
|
||||
@ -50,6 +110,7 @@ int run_external_process(const char *const *exec_args, char buffer[EXTERNAL_BUFF
|
||||
pid_t p;
|
||||
int status = 0;
|
||||
int pipes[2];
|
||||
int ret = 0;
|
||||
char internal[EXTERNAL_BUFFER_MAX] = { 0 };
|
||||
|
||||
if (pipe(pipes) == -1) {
|
||||
@ -59,20 +120,12 @@ int run_external_process(const char *const *exec_args, char buffer[EXTERNAL_BUFF
|
||||
|
||||
/* Set the default timeout */
|
||||
if (tsec == -1) {
|
||||
tsec = default_timeout;
|
||||
}
|
||||
|
||||
/* set up our signaling for the child and the timout */
|
||||
sigset_t mask;
|
||||
sigset_t omask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGCHLD);
|
||||
if (sigprocmask(SIG_BLOCK, &mask, &omask) < 0) {
|
||||
LOG_ERROR("sigprocmask failed: %s\n", strerror(errno));
|
||||
return -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) {
|
||||
@ -87,42 +140,34 @@ int run_external_process(const char *const *exec_args, char buffer[EXTERNAL_BUFF
|
||||
* bindings that these objects are completely constant.
|
||||
* http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
|
||||
*/
|
||||
if (execv(exec_args[0], (char *const *)exec_args) != 0) {
|
||||
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);
|
||||
}
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Set up the timout */
|
||||
struct timespec timeout;
|
||||
timeout.tv_sec = tsec;
|
||||
timeout.tv_nsec = 0;
|
||||
|
||||
/* Wait for the child to finish up with a timout */
|
||||
while (true) {
|
||||
if (sigtimedwait(&mask, NULL, &timeout) < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} else if (errno == EAGAIN) {
|
||||
LOG_ERROR("Child process timed out for %s, killing and returning\n", exec_args[0]);
|
||||
kill(p, SIGKILL);
|
||||
} else {
|
||||
LOG_ERROR("sigtimedwait failed: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// should never be reached
|
||||
abort();
|
||||
}
|
||||
|
||||
// close the write end of the pipe so we get signaled EOF once the
|
||||
// child exits
|
||||
close(pipes[1]);
|
||||
ssize_t output_size = read(pipes[0], internal, EXTERNAL_BUFFER_MAX - 1);
|
||||
if (output_size < 0) {
|
||||
LOG_ERROR("Failed to read from process %s: %s\n", exec_args[0], strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
ret = read_child_stdout(pipes[0], internal, tsec);
|
||||
close(pipes[0]);
|
||||
|
||||
internal[output_size] = 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));
|
||||
@ -133,13 +178,10 @@ int run_external_process(const char *const *exec_args, char buffer[EXTERNAL_BUFF
|
||||
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 %u\n", WEXITSTATUS(status));
|
||||
LOG_ERROR("Output was: %s\n", buffer ? buffer : internal);
|
||||
LOG_ERROR("External process failed with exit code %d\n", WEXITSTATUS(status));
|
||||
LOG_ERROR("Output was: %s\n", internal);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (buffer)
|
||||
memcpy(buffer, internal, EXTERNAL_BUFFER_MAX);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -31,12 +31,11 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "governors-query.h"
|
||||
#include "logging.h"
|
||||
#include "common-governors.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <glob.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Discover all governers on the system.
|
||||
@ -80,12 +79,13 @@ int fetch_governors(char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH])
|
||||
|
||||
/* Only add this governor if it is unique */
|
||||
for (int j = 0; j < num_governors; j++) {
|
||||
if (strncmp(fullpath, governors[i], MAX_GOVERNOR_LENGTH) == 0) {
|
||||
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++;
|
||||
}
|
||||
@ -122,21 +122,28 @@ const char *get_gov_state(void)
|
||||
long length = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
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);
|
||||
return "malformed";
|
||||
}
|
||||
|
||||
strncpy(governor, contents, sizeof(governor));
|
||||
if (length == -1) {
|
||||
LOG_ERROR("Failed to seek file %s\n", gov);
|
||||
} else {
|
||||
LOG_ERROR("Failed to read contents of %s\n", gov);
|
||||
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);
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -28,10 +28,8 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
#include "gpu-control.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include "common-gpu.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
/* Get the vendor for a device */
|
||||
enum GPUVendor gamemode_get_gpu_vendor(long device)
|
||||
@ -50,10 +48,13 @@ enum GPUVendor gamemode_get_gpu_vendor(long device)
|
||||
return Vendor_Invalid;
|
||||
}
|
||||
char buff[64];
|
||||
if (fgets(buff, 64, file) != NULL) {
|
||||
bool got_line = fgets(buff, 64, file) != NULL;
|
||||
fclose(file);
|
||||
|
||||
if (got_line) {
|
||||
vendor = strtol(buff, NULL, 0);
|
||||
} else {
|
||||
LOG_ERROR("Coudn't read contents of file %s, will not apply optimisations!\n", path);
|
||||
LOG_ERROR("Couldn't read contents of file %s, will not apply optimisations!\n", path);
|
||||
return Vendor_Invalid;
|
||||
}
|
||||
|
||||
@ -62,9 +63,9 @@ enum GPUVendor gamemode_get_gpu_vendor(long device)
|
||||
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",
|
||||
Vendor_NVIDIA,
|
||||
Vendor_AMD,
|
||||
Vendor_Intel);
|
||||
(unsigned int)Vendor_NVIDIA,
|
||||
(unsigned int)Vendor_AMD,
|
||||
(unsigned int)Vendor_Intel);
|
||||
return Vendor_Invalid;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -30,7 +30,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "daemon_config.h"
|
||||
#define GPU_VALUE_MAX 256
|
||||
|
||||
/* Enums for GPU vendors */
|
||||
enum GPUVendor {
|
||||
@ -52,7 +52,7 @@ struct GameModeGPUInfo {
|
||||
long nv_mem; /* Nvidia mem clock */
|
||||
long nv_powermizer_mode; /* NV Powermizer Mode */
|
||||
|
||||
char amd_performance_level[CONFIG_VALUE_MAX]; /* The AMD performance level set to */
|
||||
char amd_performance_level[GPU_VALUE_MAX]; /* The AMD performance level set to */
|
||||
};
|
||||
|
||||
/* Get the vendor for a device */
|
42
common/common-helpers.c
Normal file
42
common/common-helpers.c
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
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);
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -31,14 +31,15 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Value clamping helper, works like MIN/MAX but constraints a value within the range.
|
||||
*/
|
||||
#define CLAMP(lbound, ubound, value) MIN(MIN(lbound, ubound), MAX(MAX(lbound, ubound), value))
|
||||
#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
|
||||
@ -62,3 +63,41 @@ static inline const char *strtail(const char *haystack, const char *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)))
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -29,7 +29,8 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#include "logging.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
#include "syslog.h"
|
||||
|
||||
static bool use_syslog = false;
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -37,7 +37,6 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Macros to help with basic logging */
|
||||
#define PLOG_MSG(...) printf(__VA_ARGS__)
|
202
common/common-pidfds.c
Normal file
202
common/common-pidfds.c
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
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 <build-config.h>
|
||||
|
||||
#include "common-helpers.h"
|
||||
#include "common-pidfds.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if !HAVE_FN_PIDFD_OPEN
|
||||
#include <sys/syscall.h>
|
||||
|
||||
#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 <sys/pidfd.h>
|
||||
#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);
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
Copyright (c) 2019, Red Hat
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -27,55 +28,26 @@ 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 "daemonize.h"
|
||||
#include "logging.h"
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Helper to perform standard UNIX daemonization
|
||||
*/
|
||||
void daemonize(const char *name)
|
||||
{
|
||||
/* Initial fork */
|
||||
pid_t pid = fork();
|
||||
if (pid < 0) {
|
||||
FATAL_ERRORNO("Failed to fork");
|
||||
}
|
||||
/* 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);
|
||||
|
||||
if (pid != 0) {
|
||||
LOG_MSG("Daemon launched as %s...\n", name);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
/* 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);
|
||||
|
||||
/* 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);
|
||||
dup2(devnull_r, STDIN_FILENO);
|
||||
dup2(devnull_w, STDOUT_FILENO);
|
||||
dup2(devnull_w, STDERR_FILENO);
|
||||
close(devnull_r);
|
||||
close(devnull_w);
|
||||
}
|
||||
/* 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);
|
165
common/common-power.c
Normal file
165
common/common-power.c
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
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 <linux/limits.h>
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <glob.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool read_file_in_dir(const char *dir, const char *file, char *dest, size_t n)
|
||||
{
|
||||
char path[PATH_MAX];
|
||||
int ret = snprintf(path, sizeof(path), "%s/%s", dir, file);
|
||||
if (ret < 0 || ret >= (int)sizeof(path)) {
|
||||
LOG_ERROR("Path length overrun");
|
||||
return false;
|
||||
}
|
||||
|
||||
FILE *f = fopen(path, "r");
|
||||
if (!f) {
|
||||
LOG_ERROR("Failed to open file for read %s\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t read = fread(dest, 1, n, f);
|
||||
|
||||
/* Close before we do any error checking */
|
||||
fclose(f);
|
||||
|
||||
if (read <= 0) {
|
||||
LOG_ERROR("Failed to read contents of %s: (%s)\n", path, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (read >= n) {
|
||||
LOG_ERROR("File contained more data than expected %s\n", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure we're null terminated */
|
||||
dest[read] = '\0';
|
||||
|
||||
/* Trim whitespace off the end */
|
||||
while (read > 0 && isspace(dest[read - 1])) {
|
||||
dest[read - 1] = '\0';
|
||||
read--;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_energy_uj(const char *rapl_name, uint32_t *energy_uj)
|
||||
{
|
||||
glob_t glo = { 0 };
|
||||
static const char *path = "/sys/class/powercap/intel-rapl/intel-rapl:0/intel-rapl:0:*";
|
||||
|
||||
/* Assert some sanity on this glob */
|
||||
if (glob(path, GLOB_NOSORT, NULL, &glo) != 0) {
|
||||
LOG_ERROR("glob failed for RAPL paths: (%s)\n", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the glob doesn't find anything, this most likely means we don't
|
||||
* have an Intel CPU or we have a kernel which does not support RAPL on
|
||||
* our CPU.
|
||||
*/
|
||||
if (glo.gl_pathc < 1) {
|
||||
LOG_ONCE(MSG,
|
||||
"Intel RAPL interface not found in sysfs. "
|
||||
"This is only problematic if you expected Intel iGPU "
|
||||
"power threshold optimization.");
|
||||
globfree(&glo);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Walk the glob set */
|
||||
for (size_t i = 0; i < glo.gl_pathc; i++) {
|
||||
char name[32];
|
||||
if (!read_file_in_dir(glo.gl_pathv[i], "name", name, sizeof(name))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* We're searching for the directory where the file named "name"
|
||||
* contains the contents rapl_name. */
|
||||
if (strncmp(name, rapl_name, sizeof(name)) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char energy_uj_str[32];
|
||||
if (!read_file_in_dir(glo.gl_pathv[i], "energy_uj", energy_uj_str, sizeof(energy_uj_str))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char *end = NULL;
|
||||
long long energy_uj_ll = strtoll(energy_uj_str, &end, 10);
|
||||
if (end == energy_uj_str) {
|
||||
LOG_ERROR("Invalid energy_uj contents: %s\n", energy_uj_str);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (energy_uj_ll < 0) {
|
||||
LOG_ERROR("Value of energy_uj is out of expected bounds: %lld\n", energy_uj_ll);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Go ahead and clamp to 32 bits. We assume 32 bits later when
|
||||
* taking deltas and wrapping at 32 bits is exactly what the Linux
|
||||
* kernel's turbostat utility does so it's probably right.
|
||||
*/
|
||||
*energy_uj = (uint32_t)energy_uj_ll;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If we got here then the CPU and Kernel support RAPL and all our file
|
||||
* access has succeeded but we failed to find an entry with the right
|
||||
* name. This most likely means we're asking for "uncore" but are on a
|
||||
* machine that doesn't have an integrated GPU.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
bool get_cpu_energy_uj(uint32_t *energy_uj)
|
||||
{
|
||||
return get_energy_uj("core", energy_uj);
|
||||
}
|
||||
|
||||
bool get_igpu_energy_uj(uint32_t *energy_uj)
|
||||
{
|
||||
return get_energy_uj("uncore", energy_uj);
|
||||
}
|
45
common/common-power.h
Normal file
45
common/common-power.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Feral Interactive nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Get the amount of energy used to date by the CPU in microjoules
|
||||
*/
|
||||
bool get_cpu_energy_uj(uint32_t *energy_uj);
|
||||
|
||||
/**
|
||||
* Get the amount of energy used to date by the integrated GPU in microjoules
|
||||
*/
|
||||
bool get_igpu_energy_uj(uint32_t *energy_uj);
|
86
common/common-profile.c
Normal file
86
common/common-profile.c
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2025, the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Feral Interactive nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "common-profile.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
/**
|
||||
* Path for platform profile
|
||||
*/
|
||||
const char *profile_path = "/sys/firmware/acpi/platform_profile";
|
||||
|
||||
/**
|
||||
* Check if platform profile file exists
|
||||
*/
|
||||
int profile_exists(void)
|
||||
{
|
||||
return !access(profile_path, F_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current platform profile state
|
||||
*/
|
||||
const char *get_profile_state(void)
|
||||
{
|
||||
/* Persistent profile state */
|
||||
static char profile[64] = { 0 };
|
||||
memset(profile, 0, sizeof(profile));
|
||||
|
||||
FILE *f = fopen(profile_path, "r");
|
||||
if (!f) {
|
||||
LOG_ERROR("Failed to open file for read %s\n", profile_path);
|
||||
return "none";
|
||||
}
|
||||
|
||||
/* Grab the file length */
|
||||
fseek(f, 0, SEEK_END);
|
||||
long length = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
if (length == -1) {
|
||||
LOG_ERROR("Failed to seek file %s\n", profile_path);
|
||||
} else {
|
||||
char contents[length + 1];
|
||||
|
||||
if (fread(contents, 1, (size_t)length, f) > 0) {
|
||||
strtok(contents, "\n");
|
||||
strncpy(profile, contents, sizeof(profile) - 1);
|
||||
} else {
|
||||
LOG_ERROR("Failed to read contents of %s\n", profile_path);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
return profile;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2025, the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -31,16 +31,20 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "gamemode.h"
|
||||
#include <linux/limits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Run the main D-BUS loop "forever"
|
||||
* Path for platform profile
|
||||
*/
|
||||
void game_mode_context_loop(GameModeContext *context);
|
||||
extern const char *profile_path;
|
||||
|
||||
/**
|
||||
* Inhibit the screensaver
|
||||
* Check if platform profile file exists
|
||||
*/
|
||||
int game_mode_inhibit_screensaver(bool inhibit);
|
||||
int profile_exists(void);
|
||||
|
||||
/**
|
||||
* Get the current platform profile state
|
||||
*/
|
||||
const char *get_profile_state(void);
|
64
common/common-splitlock.c
Normal file
64
common/common-splitlock.c
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2025, the GameMode contributors
|
||||
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-splitlock.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
/**
|
||||
* Path for the split lock mitigation state
|
||||
*/
|
||||
const char *splitlock_path = "/proc/sys/kernel/split_lock_mitigate";
|
||||
|
||||
/**
|
||||
* Return the current split lock mitigation state
|
||||
*/
|
||||
long get_splitlock_state(void)
|
||||
{
|
||||
FILE *f = fopen(splitlock_path, "r");
|
||||
if (!f) {
|
||||
LOG_ERROR("Failed to open file for read %s\n", splitlock_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char contents[41] = { 0 };
|
||||
long value = -1;
|
||||
|
||||
if (fread(contents, 1, sizeof contents - 1, f) > 0) {
|
||||
value = strtol(contents, NULL, 10);
|
||||
} else {
|
||||
LOG_ERROR("Failed to read contents of %s\n", splitlock_path);
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
return value;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2025, the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -31,8 +31,14 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <linux/limits.h>
|
||||
|
||||
/**
|
||||
* Attempt daemonization of the process.
|
||||
* If this fails, the process will exit
|
||||
* Path for the split lock mitagation state
|
||||
*/
|
||||
void daemonize(const char *name);
|
||||
extern const char *splitlock_path;
|
||||
|
||||
/**
|
||||
* Get the current split lock mitigation state
|
||||
*/
|
||||
long get_splitlock_state(void);
|
40
common/meson.build
Normal file
40
common/meson.build
Normal file
@ -0,0 +1,40 @@
|
||||
# Convenience library for the duplicated logging functionality
|
||||
common_sources = [
|
||||
'common-logging.c',
|
||||
'common-governors.c',
|
||||
'common-profile.c',
|
||||
'common-splitlock.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('.')]
|
||||
)
|
49
daemon/README.md
Normal file
49
daemon/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
### 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.
|
@ -1,334 +0,0 @@
|
||||
/*
|
||||
|
||||
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 "dbus_messaging.h"
|
||||
#include "daemonize.h"
|
||||
#include "gamemode.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <systemd/sd-daemon.h>
|
||||
|
||||
/* 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 QueryStatus 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* D-BUS vtable to dispatch virtual methods
|
||||
*/
|
||||
static const sd_bus_vtable gamemode_vtable[] =
|
||||
{ SD_BUS_VTABLE_START(0),
|
||||
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_VTABLE_END };
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
||||
/* 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to inhibit the screensaver
|
||||
* Uses the "org.freedesktop.ScreenSaver" interface
|
||||
*/
|
||||
static unsigned int screensaver_inhibit_cookie = 0;
|
||||
int game_mode_inhibit_screensaver(bool inhibit)
|
||||
{
|
||||
const char *service = "org.freedesktop.ScreenSaver";
|
||||
const char *object_path = "/org/freedesktop/ScreenSaver";
|
||||
const char *interface = "org.freedesktop.ScreenSaver";
|
||||
const char *function = inhibit ? "Inhibit" : "UnInhibit";
|
||||
|
||||
sd_bus_message *msg = NULL;
|
||||
sd_bus *bus = NULL;
|
||||
sd_bus_error err;
|
||||
memset(&err, 0, sizeof(sd_bus_error));
|
||||
|
||||
int result = -1;
|
||||
|
||||
// Open the user bus
|
||||
int ret = sd_bus_open_user(&bus);
|
||||
if (ret < 0) {
|
||||
LOG_ERROR("Could not connect to user bus: %s\n", strerror(-ret));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inhibit) {
|
||||
ret = sd_bus_call_method(bus,
|
||||
service,
|
||||
object_path,
|
||||
interface,
|
||||
function,
|
||||
&err,
|
||||
&msg,
|
||||
"ss",
|
||||
"com.feralinteractive.GameMode",
|
||||
"GameMode Activated");
|
||||
} else {
|
||||
ret = sd_bus_call_method(bus,
|
||||
service,
|
||||
object_path,
|
||||
interface,
|
||||
function,
|
||||
&err,
|
||||
&msg,
|
||||
"u",
|
||||
screensaver_inhibit_cookie);
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
LOG_ERROR(
|
||||
"Could not call %s on %s: %s\n"
|
||||
"\t%s\n"
|
||||
"\t%s\n",
|
||||
function,
|
||||
service,
|
||||
strerror(-ret),
|
||||
err.name,
|
||||
err.message);
|
||||
} else if (inhibit) {
|
||||
// Read the reply
|
||||
ret = sd_bus_message_read(msg, "u", &screensaver_inhibit_cookie);
|
||||
if (ret < 0) {
|
||||
LOG_ERROR("Failure to parse response from %s on %s: %s\n",
|
||||
function,
|
||||
service,
|
||||
strerror(-ret));
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -30,18 +30,23 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "daemon_config.h"
|
||||
#include "logging.h"
|
||||
#include "gamemode-config.h"
|
||||
|
||||
#include "common-helpers.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
#include "build-config.h"
|
||||
|
||||
/* Ben Hoyt's inih library */
|
||||
#include "ini.h"
|
||||
#include <ini.h>
|
||||
|
||||
#include <linux/limits.h>
|
||||
#include <dirent.h>
|
||||
#include <libgen.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* Name and possible location of the config file */
|
||||
#define CONFIG_NAME "gamemode.ini"
|
||||
@ -49,6 +54,8 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
/* 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) \
|
||||
@ -58,6 +65,9 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
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
|
||||
@ -65,7 +75,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
struct GameModeConfig {
|
||||
pthread_rwlock_t rwlock;
|
||||
int inotfd;
|
||||
int inotwd;
|
||||
int inotwd[CONFIG_NUM_LOCATIONS];
|
||||
|
||||
struct {
|
||||
char whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
|
||||
@ -78,6 +88,12 @@ struct GameModeConfig {
|
||||
char defaultgov[CONFIG_VALUE_MAX];
|
||||
char desiredgov[CONFIG_VALUE_MAX];
|
||||
|
||||
char defaultprof[CONFIG_VALUE_MAX];
|
||||
char desiredprof[CONFIG_VALUE_MAX];
|
||||
|
||||
char igpu_desiredgov[CONFIG_VALUE_MAX];
|
||||
float igpu_power_threshold;
|
||||
|
||||
char softrealtime[CONFIG_VALUE_MAX];
|
||||
long renice;
|
||||
|
||||
@ -85,6 +101,8 @@ struct GameModeConfig {
|
||||
|
||||
long inhibit_screensaver;
|
||||
|
||||
long disable_splitlock;
|
||||
|
||||
long reaper_frequency;
|
||||
|
||||
char apply_gpu_optimisations[CONFIG_VALUE_MAX];
|
||||
@ -94,6 +112,9 @@ struct GameModeConfig {
|
||||
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];
|
||||
@ -175,6 +196,27 @@ __attribute__((unused)) static bool get_long_value_hex(const char *value_name, c
|
||||
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
|
||||
@ -226,6 +268,14 @@ static int inih_handler(void *user, const char *section, const char *name, const
|
||||
valid = get_string_value(value, self->values.defaultgov);
|
||||
} else if (strcmp(name, "desiredgov") == 0) {
|
||||
valid = get_string_value(value, self->values.desiredgov);
|
||||
} else if (strcmp(name, "defaultprof") == 0) {
|
||||
valid = get_string_value(value, self->values.defaultprof);
|
||||
} else if (strcmp(name, "desiredprof") == 0) {
|
||||
valid = get_string_value(value, self->values.desiredprof);
|
||||
} else if (strcmp(name, "igpu_desiredgov") == 0) {
|
||||
valid = get_string_value(value, self->values.igpu_desiredgov);
|
||||
} else if (strcmp(name, "igpu_power_threshold") == 0) {
|
||||
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) {
|
||||
@ -234,6 +284,8 @@ static int inih_handler(void *user, const char *section, const char *name, const
|
||||
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 */
|
||||
@ -242,9 +294,7 @@ static int inih_handler(void *user, const char *section, const char *name, const
|
||||
"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 or "
|
||||
"/usr/share/gamemode/gamemode.ini\n");
|
||||
LOG_ERROR("Consider moving this option to /etc/gamemode.ini\n");
|
||||
}
|
||||
|
||||
/* GPU subsection */
|
||||
@ -261,6 +311,12 @@ static int inih_handler(void *user, const char *section, const char *name, const
|
||||
} 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) {
|
||||
@ -325,9 +381,11 @@ static void load_config_files(GameModeConfig *self)
|
||||
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 = -1; /* 0 is a valid device ID so use -1 to indicate no value */
|
||||
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;
|
||||
@ -341,19 +399,20 @@ static void load_config_files(GameModeConfig *self)
|
||||
const char *path;
|
||||
bool protected;
|
||||
};
|
||||
struct ConfigLocation locations[] = {
|
||||
{ "/usr/share/gamemode", true }, /* shipped default config */
|
||||
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 < sizeof(locations) / sizeof(locations[0]); i++) {
|
||||
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 = fopen(path, "r");
|
||||
if (f) {
|
||||
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);
|
||||
@ -362,6 +421,25 @@ static void load_config_files(GameModeConfig *self)
|
||||
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);
|
||||
}
|
||||
@ -407,16 +485,104 @@ 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)
|
||||
{
|
||||
load_config_files(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;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -424,7 +590,7 @@ void config_reload(GameModeConfig *self)
|
||||
*/
|
||||
void config_destroy(GameModeConfig *self)
|
||||
{
|
||||
pthread_rwlock_destroy(&self->rwlock);
|
||||
internal_destroy(self);
|
||||
|
||||
/* Finally, free the memory */
|
||||
free(self);
|
||||
@ -487,6 +653,16 @@ bool config_get_inhibit_screensaver(GameModeConfig *self)
|
||||
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
|
||||
*/
|
||||
@ -522,13 +698,58 @@ void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALU
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the chosen desired governor
|
||||
* Get the chosen desired platform profile
|
||||
*/
|
||||
void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX])
|
||||
{
|
||||
memcpy_locked_config(self, governor, self->values.desiredgov, sizeof(self->values.desiredgov));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the chosen default platform profile
|
||||
*/
|
||||
void config_get_default_profile(GameModeConfig *self, char profile[CONFIG_VALUE_MAX])
|
||||
{
|
||||
memcpy_locked_config(self, profile, self->values.defaultprof, sizeof(self->values.defaultprof));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the chosen desired governor
|
||||
*/
|
||||
void config_get_desired_profile(GameModeConfig *self, char profile[CONFIG_VALUE_MAX])
|
||||
{
|
||||
memcpy_locked_config(self, profile, self->values.desiredprof, sizeof(self->values.desiredprof));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the chosen iGPU desired governor
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@ -547,6 +768,11 @@ 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;
|
||||
}
|
||||
|
||||
@ -558,12 +784,30 @@ 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;
|
||||
}
|
||||
|
||||
@ -598,6 +842,25 @@ void config_get_amd_performance_level(GameModeConfig *self, char value[CONFIG_VA
|
||||
*/
|
||||
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
|
||||
*/
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -44,6 +44,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#define IOPRIO_RESET_DEFAULT -1
|
||||
#define IOPRIO_DONT_SET -2
|
||||
#define IOPRIO_DEFAULT 4
|
||||
|
||||
/*
|
||||
* Opaque config context type
|
||||
@ -67,6 +68,11 @@ void config_init(GameModeConfig *self);
|
||||
*/
|
||||
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
|
||||
@ -74,66 +80,36 @@ void config_reload(GameModeConfig *self);
|
||||
void config_destroy(GameModeConfig *self);
|
||||
|
||||
/*
|
||||
* Get if the client is in the whitelist
|
||||
* returns false for an empty whitelist
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* Get if the client is in the blacklist
|
||||
*/
|
||||
bool config_get_client_blacklisted(GameModeConfig *self, const char *client);
|
||||
|
||||
/*
|
||||
* Get the frequency (in seconds) for the reaper thread
|
||||
*/
|
||||
long config_get_reaper_frequency(GameModeConfig *self);
|
||||
|
||||
/*
|
||||
* Get whether we want to inhibit the screensaver (defaults to true)
|
||||
*/
|
||||
bool config_get_inhibit_screensaver(GameModeConfig *self);
|
||||
|
||||
/*
|
||||
* Get a set of scripts to call when gamemode starts
|
||||
* 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]);
|
||||
/*
|
||||
* 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]);
|
||||
|
||||
/*
|
||||
* Get the script timout value
|
||||
* 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);
|
||||
|
||||
/*
|
||||
* Get the chosen default governor
|
||||
*/
|
||||
void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
|
||||
|
||||
/*
|
||||
* Get the chosen desired governor
|
||||
*/
|
||||
void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
|
||||
|
||||
/*
|
||||
* Get the chosen soft realtime behavior
|
||||
*/
|
||||
void config_get_default_profile(GameModeConfig *self, char profile[CONFIG_VALUE_MAX]);
|
||||
void config_get_desired_profile(GameModeConfig *self, char profile[CONFIG_VALUE_MAX]);
|
||||
void config_get_igpu_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
|
||||
float config_get_igpu_power_threshold(GameModeConfig *self);
|
||||
void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX]);
|
||||
|
||||
/*
|
||||
* Get the renice value
|
||||
*/
|
||||
long config_get_renice_value(GameModeConfig *self);
|
||||
|
||||
/*
|
||||
* Get the ioprio value
|
||||
*/
|
||||
long config_get_ioprio_value(GameModeConfig *self);
|
||||
bool config_get_disable_splitlock(GameModeConfig *self);
|
||||
|
||||
/*
|
||||
* Get various config info for gpu optimisations
|
||||
@ -145,6 +121,12 @@ 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
|
||||
*/
|
1215
daemon/gamemode-context.c
Normal file
1215
daemon/gamemode-context.c
Normal file
File diff suppressed because it is too large
Load Diff
540
daemon/gamemode-cpu.c
Normal file
540
daemon/gamemode-cpu.c
Normal file
@ -0,0 +1,540 @@
|
||||
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
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 <linux/limits.h>
|
||||
#include <dirent.h>
|
||||
#include <sched.h>
|
||||
|
||||
#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 void set_online_from_list(char *cpulist, GameModeCPUInfo *info)
|
||||
{
|
||||
long from, to;
|
||||
while ((cpulist = parse_cpulist(cpulist, &from, &to))) {
|
||||
for (long cpu = from; cpu < to + 1; cpu++) {
|
||||
CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int check_pe_cores(char **buf, size_t *buflen, GameModeCPUInfo *info)
|
||||
{
|
||||
if (!read_small_file("/sys/devices/cpu_core/cpus", buf, buflen))
|
||||
return 0;
|
||||
|
||||
LOG_MSG("found kernel support for checking P/E-cores\n");
|
||||
|
||||
long from, to;
|
||||
char *list = *buf;
|
||||
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->to_keep);
|
||||
}
|
||||
}
|
||||
|
||||
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("kernel did not indicate that this was an P/E-cores system\n");
|
||||
|
||||
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++) {
|
||||
/* 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 * 10) / 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 {
|
||||
set_online_from_list(buf, new_info);
|
||||
|
||||
if (!check_pe_cores(&buf2, &buf2len, new_info)) {
|
||||
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;
|
||||
}
|
||||
}
|
813
daemon/gamemode-dbus.c
Normal file
813
daemon/gamemode-dbus.c
Normal file
@ -0,0 +1,813 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
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 <elogind/sd-bus.h>
|
||||
#include <elogind/sd-daemon.h>
|
||||
#else
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <systemd/sd-daemon.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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);
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
/*
|
||||
|
||||
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 <fcntl.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Lookup the process environment for a specific variable or return NULL.
|
||||
* Requires an open directory FD from /proc/PID.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -32,15 +32,17 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "config.h"
|
||||
#include "external-helper.h"
|
||||
#include "helpers.h"
|
||||
#include "logging.h"
|
||||
#include "common-external.h"
|
||||
#include "common-gpu.h"
|
||||
#include "common-helpers.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
#include "gamemode.h"
|
||||
#include "gamemode-config.h"
|
||||
|
||||
#include "daemon_config.h"
|
||||
#include "gpu-control.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
|
||||
@ -84,6 +86,7 @@ int game_mode_initialise_gpu(GameModeConfig *config, GameModeGPUInfo **info)
|
||||
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;
|
||||
}
|
||||
|
||||
@ -140,11 +143,6 @@ void game_mode_free_gpu(GameModeGPUInfo **info)
|
||||
*info = NULL;
|
||||
}
|
||||
|
||||
//#include <linux/limits.h>
|
||||
//#include <stdio.h>
|
||||
//#include <sys/wait.h>
|
||||
//#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Applies GPU optimisations when gamemode is active and removes them after
|
||||
*/
|
||||
@ -169,7 +167,7 @@ int game_mode_apply_gpu(const GameModeGPUInfo *info)
|
||||
|
||||
// Set up our command line to pass to gpuclockctl
|
||||
const char *const exec_args[] = {
|
||||
"/usr/bin/pkexec",
|
||||
"pkexec",
|
||||
LIBEXECDIR "/gpuclockctl",
|
||||
device,
|
||||
"set",
|
||||
@ -224,7 +222,8 @@ int game_mode_get_gpu(GameModeGPUInfo *info)
|
||||
}
|
||||
break;
|
||||
case Vendor_AMD:
|
||||
strncpy(info->amd_performance_level, buffer, CONFIG_VALUE_MAX);
|
||||
strncpy(info->amd_performance_level, buffer, sizeof(info->amd_performance_level) - 1);
|
||||
info->amd_performance_level[sizeof(info->amd_performance_level) - 1] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -31,14 +31,13 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "daemon_config.h"
|
||||
#include "gamemode.h"
|
||||
#include "helpers.h"
|
||||
#include "logging.h"
|
||||
#include "common-helpers.h"
|
||||
#include "common-logging.h"
|
||||
#include "gamemode-config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Define the syscall interface in Linux because it is missing from glibc
|
||||
@ -61,7 +60,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_PRIO_DATA
|
||||
#define IOPRIO_PRIO_DATA(mask) ((mask)&IOPRIO_PRIO_MASK)
|
||||
#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK)
|
||||
#endif
|
||||
|
||||
#ifndef IOPRIO_PRIO_VALUE
|
||||
@ -86,6 +85,25 @@ 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
|
||||
*
|
||||
@ -93,49 +111,80 @@ static inline int ioprio_set(int which, int who, int ioprio)
|
||||
* 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)
|
||||
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);
|
||||
|
||||
LOG_MSG("Setting scheduling policies...\n");
|
||||
|
||||
/*
|
||||
* read configuration "ioprio" (0..7)
|
||||
*/
|
||||
/* read configuration "ioprio" (0..7) */
|
||||
int ioprio = (int)config_get_ioprio_value(config);
|
||||
if (IOPRIO_RESET_DEFAULT == ioprio) {
|
||||
LOG_MSG("IO priority will be reset to default behavior (based on CPU priority).\n");
|
||||
ioprio = 0;
|
||||
} else if (IOPRIO_DONT_SET == ioprio) {
|
||||
|
||||
/* Special value to simply not set the value */
|
||||
if (ioprio == IOPRIO_DONT_SET)
|
||||
return;
|
||||
} else {
|
||||
/* maybe clamp the value */
|
||||
int invalid_ioprio = ioprio;
|
||||
ioprio = CLAMP(0, 7, ioprio);
|
||||
if (ioprio != invalid_ioprio)
|
||||
LOG_ONCE(ERROR,
|
||||
"IO priority value %d invalid, clamping to %d\n",
|
||||
invalid_ioprio,
|
||||
ioprio);
|
||||
|
||||
/* We support only IOPRIO_CLASS_BE as IOPRIO_CLASS_RT required CAP_SYS_ADMIN */
|
||||
ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, ioprio);
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Actually apply the io priority
|
||||
*/
|
||||
int c = IOPRIO_PRIO_CLASS(ioprio), p = IOPRIO_PRIO_DATA(ioprio);
|
||||
if (ioprio_set(IOPRIO_WHO_PROCESS, client, ioprio) == 0) {
|
||||
if (0 == ioprio)
|
||||
LOG_MSG("Resetting client [%d] IO priority.\n", client);
|
||||
else
|
||||
LOG_MSG("Setting client [%d] IO priority to (%d,%d).\n", client, c, p);
|
||||
} else {
|
||||
LOG_ERROR("Setting client [%d] IO priority to (%d,%d) failed with error %d, ignoring\n",
|
||||
client,
|
||||
c,
|
||||
p,
|
||||
errno);
|
||||
/* 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);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -31,13 +31,12 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "daemon_config.h"
|
||||
#include "gamemode.h"
|
||||
#include "logging.h"
|
||||
#include "common-logging.h"
|
||||
#include "gamemode-config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <dirent.h>
|
||||
#include <sched.h>
|
||||
#include <string.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/sysinfo.h>
|
||||
|
||||
@ -54,13 +53,28 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
* 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.
|
||||
*
|
||||
* We don't need to store the current values because when the client exits,
|
||||
* everything will be good: Scheduling is only applied to the client and
|
||||
* its children.
|
||||
*/
|
||||
void game_mode_apply_renice(const GameModeContext *self, const pid_t client)
|
||||
|
||||
#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);
|
||||
|
||||
/*
|
||||
@ -69,26 +83,70 @@ void game_mode_apply_renice(const GameModeContext *self, const pid_t client)
|
||||
long int renice = config_get_renice_value(config);
|
||||
if (renice == 0) {
|
||||
return;
|
||||
} else if ((renice < 1) || (renice > 20)) {
|
||||
LOG_ONCE(ERROR, "Configured renice value '%ld' is invalid, will not renice.\n", renice);
|
||||
return;
|
||||
} else {
|
||||
renice = -renice;
|
||||
}
|
||||
|
||||
/*
|
||||
* don't adjust priority if it was already adjusted
|
||||
*/
|
||||
if (getpriority(PRIO_PROCESS, (id_t)client) != 0) {
|
||||
LOG_ERROR("Refused to renice client [%d]: already reniced\n", client);
|
||||
} else if (setpriority(PRIO_PROCESS, (id_t)client, (int)renice)) {
|
||||
LOG_HINTED(ERROR,
|
||||
"Failed to renice client [%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,
|
||||
strerror(errno));
|
||||
/* 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)
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -31,20 +31,22 @@ 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 "common-profile.h"
|
||||
|
||||
#include "gamemode.h"
|
||||
#include "helpers.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <libgen.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "daemon_config.h"
|
||||
#include "external-helper.h"
|
||||
#include "gamemode-config.h"
|
||||
#include "gamemode_client.h"
|
||||
#include "governors-query.h"
|
||||
#include "gpu-control.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
struct GameModeConfig;
|
||||
|
||||
/* Initial verify step to ensure gamemode isn't already active */
|
||||
static int verify_gamemode_initial(struct GameModeConfig *config)
|
||||
@ -161,7 +163,7 @@ static int run_basic_client_tests(void)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Verify that gamemode is now innactive */
|
||||
/* Verify that gamemode is now inactive */
|
||||
if (verify_deactivated() != 0)
|
||||
return -1;
|
||||
|
||||
@ -244,7 +246,7 @@ static int run_dual_client_tests(void)
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
/* Verify that gamemode is now innactive */
|
||||
/* Verify that gamemode is now inactive */
|
||||
if (verify_deactivated() != 0)
|
||||
return -1;
|
||||
|
||||
@ -267,14 +269,14 @@ static int run_gamemoderun_and_reaper_tests(struct GameModeConfig *config)
|
||||
/* Close stdout, we don't care if sh prints anything */
|
||||
fclose(stdout);
|
||||
/* Preload into sh and then kill it */
|
||||
if (execl("/usr/bin/gamemoderun", "/usr/bin/gamemoderun", "sh", (char *)NULL) == -1) {
|
||||
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(10000);
|
||||
usleep(100000);
|
||||
|
||||
/* Check that when we request gamemode, it replies that the other client is connected */
|
||||
if (verify_other_client_connected() != 0)
|
||||
@ -298,7 +300,7 @@ static int run_gamemoderun_and_reaper_tests(struct GameModeConfig *config)
|
||||
LOG_MSG("...Waiting for reaper thread (reaper_frequency set to %ld seconds)...\n", freq);
|
||||
sleep((unsigned int)freq);
|
||||
|
||||
/* Verify that gamemode is now innactive */
|
||||
/* Verify that gamemode is now inactive */
|
||||
if (verify_deactivated() != 0)
|
||||
return -1;
|
||||
|
||||
@ -323,7 +325,7 @@ static int run_cpu_governor_tests(struct GameModeConfig *config)
|
||||
if (defaultgov[0] == '\0') {
|
||||
const char *currentgov = get_gov_state();
|
||||
if (currentgov) {
|
||||
strncpy(defaultgov, currentgov, CONFIG_VALUE_MAX);
|
||||
strncpy(defaultgov, currentgov, CONFIG_VALUE_MAX - 1);
|
||||
} else {
|
||||
LOG_ERROR(
|
||||
"Could not get current CPU governor state, this indicates an error! See rest "
|
||||
@ -356,6 +358,62 @@ static int run_cpu_governor_tests(struct GameModeConfig *config)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check the platform profile setting works */
|
||||
static int run_platform_profile_tests(struct GameModeConfig *config)
|
||||
{
|
||||
if (!profile_exists())
|
||||
return 1;
|
||||
|
||||
/* get the two config parameters we care about */
|
||||
char desiredprof[CONFIG_VALUE_MAX] = { 0 };
|
||||
config_get_desired_profile(config, desiredprof);
|
||||
|
||||
if (desiredprof[0] == '\0')
|
||||
strcpy(desiredprof, "performance");
|
||||
|
||||
char defaultprof[CONFIG_VALUE_MAX] = { 0 };
|
||||
config_get_default_profile(config, defaultprof);
|
||||
|
||||
if (defaultprof[0] == '\0') {
|
||||
const char *currentprof = get_profile_state();
|
||||
if (currentprof) {
|
||||
strncpy(defaultprof, currentprof, CONFIG_VALUE_MAX - 1);
|
||||
} else {
|
||||
LOG_ERROR(
|
||||
"Could not get current platform profile state, this indicates an error! See rest "
|
||||
"of log.\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Start gamemode */
|
||||
gamemode_request_start();
|
||||
|
||||
/* Verify the platform profile is the desired one */
|
||||
const char *currentprof = get_profile_state();
|
||||
if (strncmp(currentprof, desiredprof, CONFIG_VALUE_MAX) != 0) {
|
||||
LOG_ERROR("Platform profile was not set to %s (was actually %s)!\n",
|
||||
desiredprof,
|
||||
currentprof);
|
||||
gamemode_request_end();
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* End gamemode */
|
||||
gamemode_request_end();
|
||||
|
||||
/* Verify the platform profile has been set back */
|
||||
currentprof = get_profile_state();
|
||||
if (strncmp(currentprof, defaultprof, CONFIG_VALUE_MAX) != 0) {
|
||||
LOG_ERROR("Platform profile was not set back to %s (was actually %s)!\n",
|
||||
defaultprof,
|
||||
currentprof);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run_custom_scripts_tests(struct GameModeConfig *config)
|
||||
{
|
||||
int scriptstatus = 0;
|
||||
@ -368,7 +426,7 @@ static int run_custom_scripts_tests(struct GameModeConfig *config)
|
||||
|
||||
if (startscripts[0][0] != '\0') {
|
||||
int i = 0;
|
||||
while (*startscripts[i] != '\0' && i < CONFIG_LIST_MAX) {
|
||||
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 };
|
||||
@ -391,7 +449,7 @@ static int run_custom_scripts_tests(struct GameModeConfig *config)
|
||||
|
||||
if (endscripts[0][0] != '\0') {
|
||||
int i = 0;
|
||||
while (*endscripts[i] != '\0' && i < CONFIG_LIST_MAX) {
|
||||
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 };
|
||||
@ -446,7 +504,8 @@ int run_gpu_optimisation_tests(struct GameModeConfig *config)
|
||||
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);
|
||||
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);
|
||||
@ -454,7 +513,8 @@ int run_gpu_optimisation_tests(struct GameModeConfig *config)
|
||||
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);
|
||||
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)
|
||||
@ -534,6 +594,249 @@ int run_gpu_optimisation_tests(struct GameModeConfig *config)
|
||||
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
|
||||
@ -555,8 +858,29 @@ static int game_mode_run_feature_tests(struct GameModeConfig *config)
|
||||
LOG_MSG("::: Passed\n");
|
||||
else {
|
||||
LOG_MSG("::: Failed!\n");
|
||||
LOG_MSG(" -- You may need to add your user to the gamemode group:");
|
||||
LOG_MSG(" -- $ sudo usermod -aG gamemode $(whoami)");
|
||||
// Consider the CPU governor feature required
|
||||
status = 1;
|
||||
status = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Does the platform profile get set properly? */
|
||||
{
|
||||
LOG_MSG("::: Verifying platform profile setting\n");
|
||||
|
||||
int profstatus = run_platform_profile_tests(config);
|
||||
|
||||
if (profstatus == 1)
|
||||
LOG_MSG("::: Passed (platform profile not supported)\n");
|
||||
else if (profstatus == 0)
|
||||
LOG_MSG("::: Passed\n");
|
||||
else {
|
||||
LOG_MSG("::: Failed!\n");
|
||||
LOG_MSG(" -- You may need to add your user to the gamemode group:");
|
||||
LOG_MSG(" -- $ sudo usermod -aG gamemode $(whoami)");
|
||||
// If available, setting the platform profile should work
|
||||
status = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -572,7 +896,7 @@ static int game_mode_run_feature_tests(struct GameModeConfig *config)
|
||||
else {
|
||||
LOG_MSG("::: Failed!\n");
|
||||
// Any custom scripts should be expected to work
|
||||
status = 1;
|
||||
status = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -588,18 +912,45 @@ static int game_mode_run_feature_tests(struct GameModeConfig *config)
|
||||
else {
|
||||
LOG_MSG("::: Failed!\n");
|
||||
// Any custom scripts should be expected to work
|
||||
status = 1;
|
||||
status = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Does the screensaver get inhibited? */
|
||||
/* TODO: Unknown if this is testable, org.freedesktop.ScreenSaver has no query method */
|
||||
|
||||
/* Was the process reniced? */
|
||||
/* Was the scheduling applied? */
|
||||
/* Were io priorities changed? */
|
||||
/* Note: These don't get cleared up on un-register, so will have already been applied */
|
||||
{
|
||||
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)" : "");
|
||||
@ -705,7 +1056,7 @@ static int run_supervisor_tests(void)
|
||||
*
|
||||
* returns 0 for success, -1 for failure
|
||||
*/
|
||||
int game_mode_run_client_tests()
|
||||
int game_mode_run_client_tests(void)
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
@ -725,7 +1076,6 @@ int game_mode_run_client_tests()
|
||||
return -1;
|
||||
|
||||
/* Controls whether we require a supervisor to actually make requests */
|
||||
/* TODO: This effects all tests below */
|
||||
if (config_get_require_supervisor(config) != 0) {
|
||||
LOG_ERROR("Tests currently unsupported when require_supervisor is set\n");
|
||||
return -1;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -32,18 +32,17 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "gamemode.h"
|
||||
#include "helpers.h"
|
||||
#include "logging.h"
|
||||
#include "common-helpers.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
|
||||
/**
|
||||
* Detect if the process is a wine preloader process
|
||||
*/
|
||||
bool game_mode_detect_wine_preloader(const char *exe)
|
||||
static bool game_mode_detect_wine_preloader(const char *exe)
|
||||
{
|
||||
return (strtail(exe, "/wine-preloader") || strtail(exe, "/wine64-preloader"));
|
||||
}
|
||||
@ -51,20 +50,104 @@ bool game_mode_detect_wine_preloader(const char *exe)
|
||||
/**
|
||||
* Detect if the process is a wine loader process
|
||||
*/
|
||||
bool game_mode_detect_wine_loader(const char *exe)
|
||||
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 pid_t pid)
|
||||
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 *proc_path = NULL, *wine_exe = NULL, *wineprefix = NULL;
|
||||
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);
|
||||
@ -141,9 +224,9 @@ char *game_mode_resolve_wine_preloader(const pid_t pid)
|
||||
goto fail;
|
||||
|
||||
error_cleanup:
|
||||
game_mode_close_proc(proc_fd);
|
||||
if (proc_fd != INVALID_PROCFD)
|
||||
game_mode_close_proc(proc_fd);
|
||||
free(wineprefix);
|
||||
free(proc_path);
|
||||
return wine_exe;
|
||||
|
||||
fail:
|
||||
@ -155,10 +238,10 @@ fail_cmdline:
|
||||
goto error_cleanup;
|
||||
|
||||
fail_env:
|
||||
LOG_ERROR("Failed to access process environment in '%s': %s\n", proc_path, strerror(errno));
|
||||
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 in '%s': %s\n", proc_path, strerror(errno));
|
||||
LOG_ERROR("Failed to access process data for client %d: %s\n", pid, strerror(errno));
|
||||
goto error_cleanup;
|
||||
}
|
||||
|
@ -1,708 +0,0 @@
|
||||
/*
|
||||
|
||||
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 "config.h"
|
||||
#include "daemon_config.h"
|
||||
#include "dbus_messaging.h"
|
||||
#include "external-helper.h"
|
||||
#include "governors-query.h"
|
||||
#include "helpers.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdatomic.h>
|
||||
#include <systemd/sd-daemon.h>
|
||||
|
||||
/**
|
||||
* The GameModeClient encapsulates the remote connection, providing a list
|
||||
* form to contain the pid and credentials.
|
||||
*/
|
||||
typedef struct GameModeClient {
|
||||
pid_t pid; /**< Process ID */
|
||||
struct GameModeClient *next; /**<Next client in the list */
|
||||
char *executable; /**<Process executable */
|
||||
} GameModeClient;
|
||||
|
||||
struct GameModeContext {
|
||||
pthread_rwlock_t rwlock; /**<Guard access to the client list */
|
||||
_Atomic int refcount; /**<Allow cycling the game mode */
|
||||
GameModeClient *client; /**<Pointer to first client */
|
||||
|
||||
GameModeConfig *config; /**<Pointer to config object */
|
||||
|
||||
char initial_cpu_mode[64]; /**<Only updates when we can */
|
||||
|
||||
struct GameModeGPUInfo *stored_gpu; /**<Stored GPU info for the current GPU */
|
||||
struct GameModeGPUInfo *target_gpu; /**<Target GPU info for the current GPU */
|
||||
|
||||
/* Reaper control */
|
||||
struct {
|
||||
pthread_t thread;
|
||||
bool running;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t condition;
|
||||
} reaper;
|
||||
};
|
||||
|
||||
static GameModeContext instance = { 0 };
|
||||
|
||||
/* Maximum number of concurrent processes we'll sanely support */
|
||||
#define MAX_GAMES 256
|
||||
|
||||
/**
|
||||
* Protect against signals
|
||||
*/
|
||||
static volatile bool had_context_init = false;
|
||||
|
||||
static GameModeClient *game_mode_client_new(pid_t pid, char *exe);
|
||||
static void game_mode_client_free(GameModeClient *client);
|
||||
static const GameModeClient *game_mode_context_has_client(GameModeContext *self, pid_t client);
|
||||
static int game_mode_context_num_clients(GameModeContext *self);
|
||||
static void *game_mode_context_reaper(void *userdata);
|
||||
static void game_mode_context_enter(GameModeContext *self);
|
||||
static void game_mode_context_leave(GameModeContext *self);
|
||||
static char *game_mode_context_find_exe(pid_t pid);
|
||||
static void game_mode_execute_scripts(char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX], int timeout);
|
||||
|
||||
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);
|
||||
|
||||
/* Initialise the current GPU info */
|
||||
game_mode_initialise_gpu(self->config, &self->stored_gpu);
|
||||
game_mode_initialise_gpu(self->config, &self->target_gpu);
|
||||
|
||||
pthread_rwlock_init(&self->rwlock, NULL);
|
||||
pthread_mutex_init(&self->reaper.mutex, NULL);
|
||||
pthread_cond_init(&self->reaper.condition, NULL);
|
||||
|
||||
/* Get the reaper thread going */
|
||||
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_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_free(self->client);
|
||||
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);
|
||||
|
||||
/* Destroy the gpu object */
|
||||
game_mode_free_gpu(&self->stored_gpu);
|
||||
game_mode_free_gpu(&self->target_gpu);
|
||||
|
||||
/* Destroy the config object */
|
||||
config_destroy(self->config);
|
||||
|
||||
pthread_rwlock_destroy(&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");
|
||||
|
||||
/* Read the initial governor state so we can revert it correctly */
|
||||
const char *initial_state = get_gov_state();
|
||||
if (initial_state) {
|
||||
/* 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);
|
||||
|
||||
/* Choose the desired governor */
|
||||
char desired[CONFIG_VALUE_MAX] = { 0 };
|
||||
config_get_desired_governor(self->config, desired);
|
||||
const char *desiredGov = desired[0] != '\0' ? desired : "performance";
|
||||
|
||||
const char *const exec_args[] = {
|
||||
"/usr/bin/pkexec", LIBEXECDIR "/cpugovctl", "set", desiredGov, NULL,
|
||||
};
|
||||
|
||||
LOG_MSG("Requesting update of governor policy to %s\n", desiredGov);
|
||||
if (run_external_process(exec_args, NULL, -1) != 0) {
|
||||
LOG_ERROR("Failed to update cpu governor policy\n");
|
||||
/* if the set fails, clear the initial mode so we don't try and reset it back and fail
|
||||
* again, presumably */
|
||||
memset(self->initial_cpu_mode, 0, sizeof(self->initial_cpu_mode));
|
||||
}
|
||||
}
|
||||
|
||||
/* Inhibit the screensaver */
|
||||
if (config_get_inhibit_screensaver(self->config))
|
||||
game_mode_inhibit_screensaver(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);
|
||||
|
||||
/* 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);
|
||||
|
||||
/* UnInhibit the screensaver */
|
||||
if (config_get_inhibit_screensaver(self->config))
|
||||
game_mode_inhibit_screensaver(false);
|
||||
|
||||
/* Reset the governer state back to initial */
|
||||
if (self->initial_cpu_mode[0] != '\0') {
|
||||
/* Choose the governor to reset to, using the config to override */
|
||||
char defaultgov[CONFIG_VALUE_MAX] = { 0 };
|
||||
config_get_default_governor(self->config, defaultgov);
|
||||
const char *gov_mode = defaultgov[0] != '\0' ? defaultgov : self->initial_cpu_mode;
|
||||
|
||||
const char *const exec_args[] = {
|
||||
"/usr/bin/pkexec", LIBEXECDIR "/cpugovctl", "set", gov_mode, NULL,
|
||||
};
|
||||
|
||||
LOG_MSG("Requesting update of governor policy to %s\n", gov_mode);
|
||||
if (run_external_process(exec_args, NULL, -1) != 0) {
|
||||
LOG_ERROR("Failed to update cpu governor policy\n");
|
||||
}
|
||||
|
||||
memset(self->initial_cpu_mode, 0, sizeof(self->initial_cpu_mode));
|
||||
}
|
||||
|
||||
char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to grab the current number of clients we know about
|
||||
*/
|
||||
static int game_mode_context_num_clients(GameModeContext *self)
|
||||
{
|
||||
return atomic_load(&self->refcount);
|
||||
}
|
||||
|
||||
int game_mode_context_register(GameModeContext *self, pid_t client, pid_t requester)
|
||||
{
|
||||
errno = 0;
|
||||
|
||||
/* Construct a new client if we can */
|
||||
GameModeClient *cl = NULL;
|
||||
char *executable = NULL;
|
||||
|
||||
/* Check our requester config first */
|
||||
if (requester != client) {
|
||||
/* Lookup the executable first */
|
||||
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;
|
||||
}
|
||||
} else if (config_get_require_supervisor(self->config)) {
|
||||
LOG_ERROR("Direct request made but require_supervisor was set, rejecting request!\n");
|
||||
return -2;
|
||||
}
|
||||
|
||||
/* Cap the total number of active clients */
|
||||
if (game_mode_context_num_clients(self) + 1 > MAX_GAMES) {
|
||||
LOG_ERROR("Max games (%d) reached, not registering %d\n", MAX_GAMES, client);
|
||||
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 */
|
||||
cl = game_mode_client_new(client, executable);
|
||||
if (cl)
|
||||
executable = NULL; // ownership has been delegated
|
||||
else
|
||||
goto error_cleanup;
|
||||
|
||||
/* 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;
|
||||
pthread_rwlock_unlock(&self->rwlock);
|
||||
|
||||
/* First add, init */
|
||||
if (atomic_fetch_add_explicit(&self->refcount, 1, memory_order_seq_cst) == 0) {
|
||||
game_mode_context_enter(self);
|
||||
}
|
||||
|
||||
/* Apply scheduler policies */
|
||||
game_mode_apply_renice(self, client);
|
||||
game_mode_apply_scheduling(self, client);
|
||||
|
||||
/* Apply io priorities */
|
||||
game_mode_apply_ioprio(self, client);
|
||||
|
||||
return 0;
|
||||
|
||||
error_cleanup:
|
||||
if (errno != 0)
|
||||
LOG_ERROR("Failed to register client [%d]: %s\n", client, strerror(errno));
|
||||
free(executable);
|
||||
game_mode_client_free(cl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
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_free(cl);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Unlock here, potentially yielding */
|
||||
pthread_rwlock_unlock(&self->rwlock);
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
GameModeClient c = {
|
||||
.executable = executable,
|
||||
.next = NULL,
|
||||
.pid = pid,
|
||||
};
|
||||
GameModeClient *ret = NULL;
|
||||
|
||||
ret = calloc(1, sizeof(struct GameModeClient));
|
||||
if (!ret) {
|
||||
return NULL;
|
||||
}
|
||||
*ret = c;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Free a client and the next element in the list.
|
||||
*/
|
||||
static void game_mode_client_free(GameModeClient *client)
|
||||
{
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
if (client->next) {
|
||||
game_mode_client_free(client->next);
|
||||
}
|
||||
if (client->executable) {
|
||||
free(client->executable);
|
||||
}
|
||||
free(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/* Expire remaining entries */
|
||||
game_mode_context_auto_expire(self);
|
||||
|
||||
ts.tv_sec = time(NULL) + reaper_interval;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GameModeContext *game_mode_context_instance()
|
||||
{
|
||||
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;
|
||||
|
||||
if (!(proc_path = buffered_snprintf(buffer, "/proc/%d/exe", pid)))
|
||||
goto fail;
|
||||
|
||||
/* Allocate the realpath if possible */
|
||||
char *exe = realpath(proc_path, NULL);
|
||||
if (!exe)
|
||||
goto fail;
|
||||
|
||||
/* Detect if the process is a wine loader process */
|
||||
if (game_mode_detect_wine_preloader(exe)) {
|
||||
LOG_MSG("Detected wine preloader for client %d [%s].\n", pid, exe);
|
||||
goto wine_preloader;
|
||||
}
|
||||
if (game_mode_detect_wine_loader(exe)) {
|
||||
LOG_MSG("Detected wine loader for client %d [%s].\n", pid, exe);
|
||||
goto wine_preloader;
|
||||
}
|
||||
|
||||
return exe;
|
||||
|
||||
wine_preloader:
|
||||
|
||||
wine_exe = game_mode_resolve_wine_preloader(pid);
|
||||
if (wine_exe) {
|
||||
free(exe);
|
||||
exe = wine_exe;
|
||||
return exe;
|
||||
}
|
||||
|
||||
/* We have to ignore this because the wine process is in some sort
|
||||
* of respawn mode
|
||||
*/
|
||||
free(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 (*scripts[i] != '\0' && i < CONFIG_LIST_MAX) {
|
||||
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++;
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -32,6 +32,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#define INVALID_PROCFD -1
|
||||
@ -43,6 +44,41 @@ typedef int procfd_t;
|
||||
*/
|
||||
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
|
||||
@ -63,6 +99,29 @@ void game_mode_context_init(GameModeContext *self);
|
||||
*/
|
||||
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
|
||||
*
|
||||
@ -104,40 +163,31 @@ int game_mode_context_query_status(GameModeContext *self, pid_t pid, pid_t reque
|
||||
*/
|
||||
GameModeConfig *game_mode_config_from_context(const GameModeContext *context);
|
||||
|
||||
/** gamemode-env.c
|
||||
* Provides internal API functions specific to working environment
|
||||
* variables.
|
||||
/*
|
||||
* Reload the current configuration
|
||||
*/
|
||||
char *game_mode_lookup_proc_env(const procfd_t proc_fd, const char *var);
|
||||
char *game_mode_lookup_user_home(void);
|
||||
int game_mode_reload_config(GameModeContext *context);
|
||||
|
||||
/** gamemode-ioprio.c
|
||||
* Provides internal API functions specific to adjusting process
|
||||
* IO priorities.
|
||||
*/
|
||||
void game_mode_apply_ioprio(const GameModeContext *self, const pid_t client);
|
||||
|
||||
/** gamemode-proc.c
|
||||
* Provides internal API functions specific to working with process
|
||||
* environments.
|
||||
*/
|
||||
procfd_t game_mode_open_proc(const pid_t pid);
|
||||
int game_mode_close_proc(const procfd_t procfd);
|
||||
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.
|
||||
*/
|
||||
void game_mode_apply_renice(const GameModeContext *self, const pid_t client);
|
||||
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.
|
||||
*/
|
||||
bool game_mode_detect_wine_loader(const char *exe);
|
||||
bool game_mode_detect_wine_preloader(const char *exe);
|
||||
char *game_mode_resolve_wine_preloader(const pid_t pid);
|
||||
char *game_mode_resolve_wine_preloader(const char *exe, const pid_t pid);
|
||||
|
||||
/** gamemode-tests.c
|
||||
* Provides a test suite to verify gamemode behaviour
|
||||
@ -152,3 +202,26 @@ 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);
|
||||
|
341
daemon/gamemoded.c
Normal file
341
daemon/gamemoded.c
Normal file
@ -0,0 +1,341 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
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 <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <sys/stat.h>
|
||||
#include <systemd/sd-daemon.h> /* TODO: Move usage to gamemode-dbus.c */
|
||||
#include <unistd.h>
|
||||
|
||||
#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");
|
||||
}
|
209
daemon/main.c
209
daemon/main.c
@ -1,209 +0,0 @@
|
||||
/*
|
||||
|
||||
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 "config.h"
|
||||
#include "daemonize.h"
|
||||
#include "dbus_messaging.h"
|
||||
#include "gamemode.h"
|
||||
#include "gamemode_client.h"
|
||||
#include "logging.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <systemd/sd-daemon.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define USAGE_TEXT \
|
||||
"Usage: %s [-d] [-l] [-r] [-t] [-h] [-v]\n\n" \
|
||||
" -d daemonize self after launch\n" \
|
||||
" -l log to syslog\n" \
|
||||
" -r request gamemode and pause\n" \
|
||||
" -t run tests\n" \
|
||||
" -h print this help\n" \
|
||||
" -v 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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
int status;
|
||||
while ((opt = getopt(argc, argv, "dlsrtvh")) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
daemon = true;
|
||||
break;
|
||||
case 'l':
|
||||
use_syslog = true;
|
||||
break;
|
||||
case 's': {
|
||||
if ((status = gamemode_query_status()) < 0) {
|
||||
LOG_ERROR("gamemode status request failed: %s\n", gamemode_error_string());
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (status > 0) {
|
||||
LOG_MSG("gamemode is active\n");
|
||||
} else {
|
||||
LOG_MSG("gamemode is inactive\n");
|
||||
}
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
}
|
||||
case 'r':
|
||||
if (gamemode_request_start() < 0) {
|
||||
LOG_ERROR("gamemode request failed: %s\n", gamemode_error_string());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if ((status = gamemode_query_status()) == 2) {
|
||||
LOG_MSG("gamemode request succeeded and is active\n");
|
||||
} else if (status == 1) {
|
||||
LOG_ERROR("gamemode request succeeded and is active but registration failed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
LOG_ERROR("gamemode request succeeded but is not active\n");
|
||||
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);
|
||||
break;
|
||||
case 't':
|
||||
status = game_mode_run_client_tests();
|
||||
exit(status);
|
||||
break;
|
||||
case 'v':
|
||||
LOG_MSG(VERSION_TEXT);
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
case 'h':
|
||||
LOG_MSG(USAGE_TEXT, argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, USAGE_TEXT, argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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");
|
||||
}
|
@ -1,80 +1,40 @@
|
||||
# Convenience library for the duplicated logging functionality
|
||||
common_sources = [
|
||||
'logging.c',
|
||||
'governors-query.c',
|
||||
'external-helper.c',
|
||||
'gpu-control.c',
|
||||
]
|
||||
|
||||
daemon_common = static_library(
|
||||
'daemon-common',
|
||||
sources: common_sources,
|
||||
install: false,
|
||||
)
|
||||
|
||||
link_daemon_common = declare_dependency(
|
||||
link_with: daemon_common,
|
||||
)
|
||||
|
||||
# Main daemon
|
||||
daemon_sources = [
|
||||
'main.c',
|
||||
'gamemode.c',
|
||||
'gamemode-env.c',
|
||||
'gamemoded.c',
|
||||
'gamemode-context.c',
|
||||
'gamemode-ioprio.c',
|
||||
'gamemode-proc.c',
|
||||
'gamemode-sched.c',
|
||||
'gamemode-wine.c',
|
||||
'gamemode-tests.c',
|
||||
'gamemode-gpu.c',
|
||||
'daemonize.c',
|
||||
'dbus_messaging.c',
|
||||
'daemon_config.c',
|
||||
'gamemode-cpu.c',
|
||||
'gamemode-dbus.c',
|
||||
'gamemode-config.c',
|
||||
]
|
||||
|
||||
gamemoded_includes = libgamemode_includes
|
||||
gamemoded_includes = gamemode_headers_includes
|
||||
gamemoded_includes += config_h_dir
|
||||
|
||||
executable(
|
||||
gamemoded = executable(
|
||||
'gamemoded',
|
||||
sources: daemon_sources,
|
||||
c_args: sd_bus_args,
|
||||
dependencies: [
|
||||
link_daemon_common,
|
||||
dep_threads,
|
||||
dep_systemd,
|
||||
sd_bus_dep,
|
||||
inih_dependency,
|
||||
libdl,
|
||||
],
|
||||
include_directories: gamemoded_includes,
|
||||
install: true,
|
||||
)
|
||||
|
||||
# Small target util to get and set cpu governors
|
||||
cpugovctl_sources = [
|
||||
'cpugovctl.c',
|
||||
]
|
||||
|
||||
cpugovctl = executable(
|
||||
'cpugovctl',
|
||||
sources: cpugovctl_sources,
|
||||
dependencies: [
|
||||
link_daemon_common,
|
||||
include_directories: [
|
||||
gamemoded_includes,
|
||||
],
|
||||
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,
|
||||
# verify gamemoded compiled properly
|
||||
test(
|
||||
'validate gamemoded compiled properly',
|
||||
gamemoded,
|
||||
args: ['-v'],
|
||||
)
|
||||
|
@ -1 +0,0 @@
|
||||
@@LIMITSGROUP@ - nice -10
|
@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC
|
||||
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
|
||||
<!--
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
All rights reserved.
|
||||
-->
|
||||
|
||||
<vendor>Feral GameMode Activation</vendor>
|
||||
<vendor_url>http://www.feralinteractive.com</vendor_url>
|
||||
|
||||
<action id="com.feralinteractive.GameMode.governor-helper">
|
||||
<description>Modify the CPU governor</description>
|
||||
<message>Authentication is required to modify the CPU governor</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/cpugovctl</annotate>
|
||||
</action>
|
||||
|
||||
<action id="com.feralinteractive.GameMode.gpu-helper">
|
||||
<description>Modify the GPU clock states</description>
|
||||
<message>Authentication is required to modify the GPU clock states</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/gpuclockctl</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||
</action>
|
||||
|
||||
</policyconfig>
|
33
data/gamemode-simulate-game.1.in
Normal file
33
data/gamemode-simulate-game.1.in
Normal file
@ -0,0 +1,33 @@
|
||||
.\" 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)
|
@ -1,10 +1,10 @@
|
||||
.\" Manpage for gamemoded.
|
||||
.\" Contact linux-contact@feralinteractive.com to correct errors or typos.
|
||||
.TH gamemoded 8 "15 March 2019" "1.3" "gamemoded man page"
|
||||
.TH gamemoded 8 "4 May 2020" "@GAMEMODE_VERSION@" "gamemoded man page"
|
||||
.SH NAME
|
||||
gamemoded \- optimises system performance on demand
|
||||
gamemoded \- daemon that optimises system performance on demand
|
||||
.SH SYNOPSIS
|
||||
\fBgamemoded\fR [\fB\-d\fR] [\fB\-l\fR] [\fB\-h\fR] [\fB\-v\fR]
|
||||
\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.
|
||||
|
||||
@ -12,46 +12,33 @@ The design has a clear cut abstraction between the host daemon and library (\fBg
|
||||
|
||||
\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 \-d
|
||||
.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
|
||||
.B \-l, \-\-log-to-syslog
|
||||
Log to syslog
|
||||
.TP 8
|
||||
.B \-r
|
||||
Request gamemode and wait for any signal
|
||||
.TP 8
|
||||
.B \-s
|
||||
Query the current status of gamemode
|
||||
.TP 8
|
||||
.B \-h
|
||||
.TP 8
|
||||
.B \-h, \-\-help
|
||||
Print help text
|
||||
.TP 8
|
||||
.B \-t
|
||||
.B \-t, \-\-test
|
||||
Run diagnostic tests on the current installation
|
||||
.TP 8
|
||||
.B \-v
|
||||
.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, 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
|
||||
\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:
|
||||
|
||||
@ -86,7 +73,7 @@ Or, distribute \fBlibgamemodeauto.so.0\fR and either link with \fB\-lgamemodeaut
|
||||
\fBgamemoded\fR will load and merge \fBgamemode.ini\fR config files from these directories in the following order:
|
||||
|
||||
.RS 4
|
||||
/usr/share/gamemode/
|
||||
@SYSCONFDIR@/
|
||||
.RE
|
||||
.RS 4
|
||||
/etc/
|
||||
@ -107,7 +94,7 @@ Behaviour of the config file can be explained by presenting a commented example:
|
||||
.RE
|
||||
|
||||
.SH SEE ALSO
|
||||
systemd(1)
|
||||
gamemoderun(1), systemd(1)
|
||||
|
||||
.SH ABOUT
|
||||
GameMode source can be found at \fIhttps://github.com/FeralInteractive/gamemode.git\fR
|
||||
|
32
data/gamemodelist
Executable file
32
data/gamemodelist
Executable file
@ -0,0 +1,32 @@
|
||||
#!/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 '{}'
|
68
data/gamemodelist.1.in
Normal file
68
data/gamemodelist.1.in
Normal file
@ -0,0 +1,68 @@
|
||||
.\" 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)
|
9
data/gamemoderun
Executable file
9
data/gamemoderun
Executable file
@ -0,0 +1,9 @@
|
||||
#!/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 "$@"
|
50
data/gamemoderun.1.in
Normal file
50
data/gamemoderun.1.in
Normal file
@ -0,0 +1,50 @@
|
||||
.\" 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)
|
@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Helper script to launch games with gamemode
|
||||
|
||||
# Path to install gamemoded auto script
|
||||
CONFIG_LIB_DIR="@GAMEMODE_LIB_DIR@/libgamemodeauto.so.0"
|
||||
|
||||
# Set the ld preload path prefixed libgamemodeauto
|
||||
export LD_PRELOAD="${CONFIG_LIB_DIR}${LD_PRELOAD:+:$LD_PRELOAD}"
|
||||
|
||||
# Launch
|
||||
exec "$@"
|
41
data/io.github.feralinteractive.gamemode.metainfo.xml
Normal file
41
data/io.github.feralinteractive.gamemode.metainfo.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component>
|
||||
<id>io.github.feralinteractive.gamemode</id>
|
||||
|
||||
<name>gamemode</name>
|
||||
<summary>daemon that allows games to request a set of optimizations be temporarily applied</summary>
|
||||
<developer_name>Feral Interactive</developer_name>
|
||||
|
||||
<!-- <icon type="stock">io.github.feralinteractive.gamemode</icon> -->
|
||||
|
||||
<metadata_license>FSFAP</metadata_license>
|
||||
<project_license>BSD-3-Clause</project_license>
|
||||
|
||||
<description>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>Currently GameMode includes support for optimisations including:</p>
|
||||
<ul>
|
||||
<li>CPU governor</li>
|
||||
<li>I/O priority</li>
|
||||
<li>Process niceness</li>
|
||||
<li>Kernel scheduler (SCHED_ISO)</li>
|
||||
<li>Screensaver inhibiting</li>
|
||||
<li>GPU performance mode (NVIDIA and AMD)</li>
|
||||
<li>GPU overclocking (NVIDIA)</li>
|
||||
<li>Custom scripts</li>
|
||||
</ul>
|
||||
</description>
|
||||
|
||||
<content_rating type="oars-1.1" />
|
||||
<categories>
|
||||
<category>Utility</category>
|
||||
<category>Game</category>
|
||||
</categories>
|
||||
|
||||
<url type="homepage">https://feralinteractive.github.io/gamemode</url>
|
||||
</component>
|
156
data/meson.build
156
data/meson.build
@ -1,55 +1,157 @@
|
||||
data_conf = configuration_data()
|
||||
data_conf.set('BINDIR', path_bindir)
|
||||
data_conf.set('LIBEXECDIR', path_libexecdir)
|
||||
data_conf.set('GAMEMODE_LIB_DIR', path_libdir)
|
||||
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')
|
||||
join_paths(meson.project_source_root(), 'example', 'gamemode.ini'),
|
||||
check: true,
|
||||
).stdout().strip()
|
||||
data_conf.set('GAMEMODE_EXAMPLE_CONFIG', config_example)
|
||||
|
||||
if with_systemd == true
|
||||
# Install systemd user unit
|
||||
configure_file(
|
||||
input: 'gamemoded.service.in',
|
||||
output: 'gamemoded.service',
|
||||
configuration: data_conf,
|
||||
install_dir: path_systemd_unit_dir,
|
||||
)
|
||||
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: 'com.feralinteractive.GameMode.service.in',
|
||||
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 file in all cases
|
||||
configure_file(
|
||||
input: 'com.feralinteractive.GameMode.policy.in',
|
||||
output: 'com.feralinteractive.GameMode.policy',
|
||||
configuration: data_conf,
|
||||
install_dir: path_polkit_action_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
|
||||
configure_file(
|
||||
input: 'gamemoderun.in',
|
||||
output: 'gamemoderun',
|
||||
configuration: data_conf,
|
||||
install_dir: 'bin',
|
||||
# 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 the man page
|
||||
manpage = configure_file(
|
||||
# Configure and install man pages
|
||||
gamemoded_manpage = configure_file(
|
||||
input: files('gamemoded.8.in'),
|
||||
output: 'gamemoded.8',
|
||||
configuration: data_conf,
|
||||
)
|
||||
install_man(manpage)
|
||||
|
||||
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
|
||||
|
1
data/pam_limits/10-gamemode.conf.in
Normal file
1
data/pam_limits/10-gamemode.conf.in
Normal file
@ -0,0 +1 @@
|
||||
@@GAMEMODE_PRIVILEGED_GROUP@ - nice -10
|
73
data/polkit/actions/com.feralinteractive.GameMode.policy.in
Normal file
73
data/polkit/actions/com.feralinteractive.GameMode.policy.in
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC
|
||||
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
|
||||
<!--
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
-->
|
||||
|
||||
<vendor>Feral GameMode Activation</vendor>
|
||||
<vendor_url>http://www.feralinteractive.com</vendor_url>
|
||||
|
||||
<action id="com.feralinteractive.GameMode.governor-helper">
|
||||
<description>Modify the CPU governor</description>
|
||||
<message>Authentication is required to modify the CPU governor</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>no</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/cpugovctl</annotate>
|
||||
</action>
|
||||
|
||||
<action id="com.feralinteractive.GameMode.gpu-helper">
|
||||
<description>Modify the GPU clock states</description>
|
||||
<message>Authentication is required to modify the GPU clock states</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>no</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/gpuclockctl</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||
</action>
|
||||
|
||||
<action id="com.feralinteractive.GameMode.cpu-helper">
|
||||
<description>Modify the CPU core states</description>
|
||||
<message>Authentication is required to modify the CPU core states</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>no</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/cpucorectl</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||
</action>
|
||||
|
||||
<action id="com.feralinteractive.GameMode.procsys-helper">
|
||||
<description>Modify the /proc/sys values</description>
|
||||
<message>Authentication is required to modify the /proc/sys/ values</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>no</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/procsysctl</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||
</action>
|
||||
|
||||
<action id="com.feralinteractive.GameMode.profile-helper">
|
||||
<description>Modify the platform profile</description>
|
||||
<message>Authentication is required to modify platform profile</message>
|
||||
<defaults>
|
||||
<allow_any>no</allow_any>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>no</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/platprofctl</annotate>
|
||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||
</action>
|
||||
</policyconfig>
|
15
data/polkit/rules.d/gamemode.rules.in
Normal file
15
data/polkit/rules.d/gamemode.rules.in
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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" ||
|
||||
action.id == "com.feralinteractive.GameMode.profile-helper") &&
|
||||
subject.isInGroup("@GAMEMODE_PRIVILEGED_GROUP@"))
|
||||
{
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
});
|
1
data/systemd/sysusers.d/gamemode.conf.in
Normal file
1
data/systemd/sysusers.d/gamemode.conf.in
Normal file
@ -0,0 +1 @@
|
||||
g @GAMEMODE_PRIVILEGED_GROUP@ - -
|
@ -5,7 +5,7 @@ Description=gamemoded
|
||||
Type=dbus
|
||||
BusName=com.feralinteractive.GameMode
|
||||
NotifyAccess=main
|
||||
ExecStart=@BINDIR@/gamemoded -l
|
||||
ExecStart=@BINDIR@/gamemoded
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
@ -1,30 +0,0 @@
|
||||
# Maintainer: Ysblokje <ysblokje at gmail dot com>
|
||||
pkgname=('gamemode-git')
|
||||
pkgver='1.3-dev'
|
||||
pkgrel=1
|
||||
pkgdesc="GameMode is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS."
|
||||
arch=('x86_64')
|
||||
url="https://github.com/FeralInteractive/gamemode.git"
|
||||
license=('MIT')
|
||||
depends=('systemd' 'polkit')
|
||||
makedepends=('meson' 'pkg-config')
|
||||
provides=('gamemode')
|
||||
source=("git+https://github.com/FeralInteractive/gamemode.git")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
pkgver() {
|
||||
cd gamemode
|
||||
echo $(git rev-parse --short HEAD)
|
||||
}
|
||||
|
||||
build() {
|
||||
cd gamemode
|
||||
arch-meson build
|
||||
cd build
|
||||
ninja
|
||||
}
|
||||
|
||||
package() {
|
||||
cd gamemode/build
|
||||
DESTDIR=$pkgdir ninja install
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
The following folders contain PKGBUILD file for arch(like) distro's. You can use those as starting point for your own packages.
|
||||
|
||||
Regards,
|
||||
Minze Zwerver
|
@ -1,11 +1,25 @@
|
||||
[general]
|
||||
; The reaper thread will check every 5 seconds for exited clients
|
||||
; 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 governer is used when leaving GameMode instead of restoring the original value
|
||||
defaultgov=powersave
|
||||
; The default governor is used when leaving GameMode instead of restoring the original value
|
||||
;defaultgov=powersave
|
||||
|
||||
; The desired platform profile is used when entering GameMode instead of "performance"
|
||||
desiredprof=performance
|
||||
; The default platform profile is used when leaving GameMode instead of restoring the original value
|
||||
;defaultgov=low-power
|
||||
|
||||
; The iGPU desired governor is used when the integrated GPU is under heavy load
|
||||
igpu_desiredgov=powersave
|
||||
; Threshold to use to decide when the integrated GPU is under heavy load.
|
||||
; 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
|
||||
@ -14,6 +28,8 @@ 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
|
||||
@ -26,6 +42,10 @@ ioprio=0
|
||||
; 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
|
||||
@ -66,6 +86,17 @@ inhibit_screensaver=1
|
||||
; 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
|
||||
@ -79,10 +110,10 @@ inhibit_screensaver=1
|
||||
[custom]
|
||||
; Custom scripts (executed using the shell) when gamemode starts and ends
|
||||
;start=notify-send "GameMode started"
|
||||
; /home/me/bin/stop_ethmining.sh
|
||||
; /home/me/bin/stop_foldingathome.sh
|
||||
|
||||
;end=notify-send "GameMode ended"
|
||||
; /home/me/bin/start_ethmining.sh
|
||||
; /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
|
||||
|
@ -1,5 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Name=gamemoded
|
||||
Exec=systemctl --user restart gamemoded.service
|
||||
Type=Application
|
||||
Terminal=false
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -32,6 +32,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#include "gamemode_client.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(void)
|
||||
@ -39,6 +40,7 @@ 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 */
|
||||
@ -47,5 +49,8 @@ int main(void)
|
||||
/* 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;
|
||||
}
|
||||
|
@ -1,11 +1,18 @@
|
||||
# An example game
|
||||
executable(
|
||||
'example',
|
||||
'gamemode-simulate-game',
|
||||
sources: [
|
||||
'main.c',
|
||||
],
|
||||
include_directories: libgamemode_includes,
|
||||
dependencies: [
|
||||
libdl,
|
||||
gamemode_dep,
|
||||
],
|
||||
install: true,
|
||||
install_dir: path_bindir,
|
||||
)
|
||||
|
||||
# An example configuration
|
||||
install_data(
|
||||
files('gamemode.ini'),
|
||||
install_dir: path_sysconfdir,
|
||||
)
|
||||
|
43
lib/README.md
Normal file
43
lib/README.md
Normal file
@ -0,0 +1,43 @@
|
||||
## 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.
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -31,70 +31,329 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <common-helpers.h>
|
||||
#include <common-pidfds.h>
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <systemd/sd-bus.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// 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 };
|
||||
|
||||
// Simple requestor function for a gamemode
|
||||
static int gamemode_request(const char *function, int arg)
|
||||
// memory helpers
|
||||
static void cleanup_fd_array(int **fdlist)
|
||||
{
|
||||
sd_bus_message *msg = NULL;
|
||||
sd_bus *bus = NULL;
|
||||
sd_bus_error err;
|
||||
memset(&err, 0, sizeof(err));
|
||||
if (fdlist == NULL || *fdlist == NULL)
|
||||
return;
|
||||
|
||||
int result = -1;
|
||||
int errsave = errno;
|
||||
for (int *fd = *fdlist; *fd != -1; fd++) {
|
||||
TRACE("GM Closing fd %d\n", *fd);
|
||||
(void)close(*fd);
|
||||
}
|
||||
|
||||
// Open the user bus
|
||||
int ret = sd_bus_open_user(&bus);
|
||||
if (ret < 0) {
|
||||
snprintf(error_string,
|
||||
sizeof(error_string),
|
||||
"Could not connect to bus: %s",
|
||||
strerror(-ret));
|
||||
} else {
|
||||
// Attempt to send the requested function
|
||||
ret = sd_bus_call_method(bus,
|
||||
"com.feralinteractive.GameMode",
|
||||
"/com/feralinteractive/GameMode",
|
||||
"com.feralinteractive.GameMode",
|
||||
function,
|
||||
&err,
|
||||
&msg,
|
||||
arg ? "ii" : "i",
|
||||
getpid(),
|
||||
arg);
|
||||
if (ret < 0) {
|
||||
snprintf(error_string,
|
||||
sizeof(error_string),
|
||||
"Could not call method %s on com.feralinteractive.GameMode\n"
|
||||
"\t%s\n"
|
||||
"\t%s\n"
|
||||
"\t%s\n",
|
||||
function,
|
||||
err.name,
|
||||
err.message,
|
||||
strerror(-ret));
|
||||
} else {
|
||||
// Read the reply
|
||||
ret = sd_bus_message_read(msg, "i", &result);
|
||||
if (ret < 0) {
|
||||
snprintf(error_string,
|
||||
sizeof(error_string),
|
||||
"Failure to parse response: %s",
|
||||
strerror(-ret));
|
||||
}
|
||||
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 result;
|
||||
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
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -42,6 +42,9 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
* 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
|
||||
@ -58,23 +61,27 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
* -1 if the request failed
|
||||
* -2 if the request was rejected
|
||||
*
|
||||
* int gamemode_query_status_for(pid_t pid) - Query the current 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
|
||||
* 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 <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
static char internal_gamemode_client_error_string[512] = { 0 };
|
||||
@ -226,11 +233,14 @@ __attribute__((always_inline)) static inline const char *gamemode_error_string(v
|
||||
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 libgamemod
|
||||
* Redirect to the real libgamemode
|
||||
* Allow automatically requesting game mode
|
||||
* Also prints errors as they happen.
|
||||
*/
|
||||
@ -249,6 +259,9 @@ int gamemode_request_start(void)
|
||||
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());
|
||||
@ -275,6 +288,9 @@ int gamemode_request_end(void)
|
||||
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());
|
||||
|
@ -6,25 +6,26 @@ lt_age = '0'
|
||||
lt_version = '@0@.@1@.@2@'.format(lt_current, lt_age, lt_revision)
|
||||
|
||||
# Main client library to message the daemon
|
||||
gamemode = shared_library(
|
||||
libgamemode = shared_library(
|
||||
'gamemode',
|
||||
sources: [
|
||||
'client_impl.c',
|
||||
],
|
||||
dependencies: [
|
||||
dep_systemd,
|
||||
link_lib_common,
|
||||
dep_dbus,
|
||||
],
|
||||
install: true,
|
||||
soversion: lt_current,
|
||||
version: lt_version,
|
||||
)
|
||||
|
||||
libgamemode_includes = [
|
||||
gamemode_headers_includes = [
|
||||
include_directories('.'),
|
||||
]
|
||||
|
||||
# Small library to automatically use gamemode
|
||||
gamemodeauto = shared_library(
|
||||
libgamemodeauto = library(
|
||||
'gamemodeauto',
|
||||
sources: [
|
||||
'client_loader.c',
|
||||
@ -42,7 +43,10 @@ gamemode_headers = [
|
||||
'gamemode_client.h',
|
||||
]
|
||||
|
||||
install_headers(gamemode_headers)
|
||||
install_headers(
|
||||
gamemode_headers,
|
||||
install_dir: path_includedir,
|
||||
)
|
||||
|
||||
# Generate a pkg-config files
|
||||
pkg = import('pkgconfig')
|
||||
@ -58,13 +62,18 @@ pkg.generate(
|
||||
)
|
||||
|
||||
pkg.generate(
|
||||
name: 'gamemode',
|
||||
name: 'libgamemodeauto',
|
||||
description: desc,
|
||||
filebase: 'gamemode-auto',
|
||||
libraries: gamemodeauto,
|
||||
filebase: 'libgamemodeauto',
|
||||
libraries: libgamemodeauto,
|
||||
version: meson.project_version(),
|
||||
libraries_private: [
|
||||
libdl
|
||||
],
|
||||
)
|
||||
|
||||
# Dependency objects
|
||||
gamemode_dep = declare_dependency(
|
||||
include_directories: gamemode_headers_includes,
|
||||
dependencies: libdl,
|
||||
)
|
||||
libgamemodeauto_dep = declare_dependency(
|
||||
link_with: libgamemodeauto,
|
||||
)
|
||||
|
169
meson.build
169
meson.build
@ -2,8 +2,9 @@ project(
|
||||
'gamemode',
|
||||
'c',
|
||||
default_options : ['c_std=c11', 'warning_level=3'],
|
||||
version: '1.3',
|
||||
version: '1.8.2',
|
||||
license: 'BSD',
|
||||
meson_version: '>= 1.3.1',
|
||||
)
|
||||
|
||||
am_cflags = [
|
||||
@ -23,15 +24,78 @@ 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 via pkgconfig
|
||||
dep_systemd = dependency('libsystemd')
|
||||
# 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')
|
||||
@ -39,28 +103,41 @@ 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_systemd = get_option('with-systemd')
|
||||
if with_systemd == true
|
||||
# If the path isn't explicitly set, ask systemd for the systemd user unit directory
|
||||
path_systemd_unit_dir = get_option('with-systemd-user-unit-dir')
|
||||
if path_systemd_unit_dir == ''
|
||||
message('Asking pkg-config for systemd\'s directories')
|
||||
pkgconfig_systemd = dependency('systemd')
|
||||
path_systemd_unit_dir = pkgconfig_systemd.get_pkgconfig_variable('systemduserunitdir')
|
||||
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_variable(pkgconfig: '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_variable(pkgconfig: 'sysusersdir')
|
||||
endif
|
||||
endif
|
||||
else
|
||||
with_systemd_group = false
|
||||
endif
|
||||
endif
|
||||
|
||||
with_limits_conf = get_option('with-pam-group')
|
||||
if with_limits_conf != ''
|
||||
ldata = configuration_data()
|
||||
ldata.set('LIMITSGROUP', with_limits_conf)
|
||||
# Install the limits.d configuration file
|
||||
configure_file(
|
||||
input: 'data/10-gamemode.conf.in',
|
||||
output: '10-gamemode.conf',
|
||||
configuration: ldata,
|
||||
install_dir: '/etc/security/limits.d',
|
||||
)
|
||||
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.
|
||||
@ -69,30 +146,48 @@ if path_dbus_service_dir == ''
|
||||
path_dbus_service_dir = join_paths(path_datadir, 'dbus-1', 'services')
|
||||
endif
|
||||
|
||||
path_polkit_action_dir = join_paths(path_datadir, 'polkit-1', 'actions')
|
||||
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_daemon = get_option('with-daemon')
|
||||
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: 'config.h',
|
||||
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 with_daemon == true
|
||||
if sd_bus_provider != 'no-daemon'
|
||||
# inih currently only needed by the daemon
|
||||
inih = subproject('inih')
|
||||
inih_dependency = inih.get_variable('inih_dependency')
|
||||
inih_dependency = dependency(
|
||||
'inih',
|
||||
fallback : ['inih', 'inih_dep']
|
||||
)
|
||||
|
||||
subdir('daemon')
|
||||
|
||||
@ -118,11 +213,23 @@ report = [
|
||||
' includedir: @0@'.format(path_includedir),
|
||||
]
|
||||
|
||||
if with_systemd == true
|
||||
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),
|
||||
]
|
||||
@ -134,9 +241,9 @@ report += [
|
||||
' Options:',
|
||||
' ========',
|
||||
'',
|
||||
' daemon: @0@'.format(with_daemon),
|
||||
' sd-bus provier: @0@'.format(sd_bus_provider),
|
||||
' examples: @0@'.format(with_examples),
|
||||
' systemd: @0@'.format(with_systemd),
|
||||
' util: @0@'.format(with_util),
|
||||
]
|
||||
|
||||
# Output some stuff to validate the build config
|
||||
|
@ -1,14 +1,20 @@
|
||||
option('with-systemd', type: 'boolean', description: 'Use systemd support (unit, etc)', value: 'true')
|
||||
|
||||
# limits.d
|
||||
option('with-pam-group', type: 'string', description: 'Install the limits.d configuration file to allow renicing as an unpriviledged user being part of the specified group')
|
||||
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-daemon', type: 'boolean', description: 'Build the daemon', value: 'true')
|
||||
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')
|
||||
|
@ -4,24 +4,33 @@
|
||||
# Ensure we are at the project root
|
||||
cd "$(dirname $0)"/..
|
||||
|
||||
wget -Nq -T3 -t1 https://llvm.org/svn/llvm-project/cfe/trunk/tools/clang-format/git-clang-format
|
||||
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 chmod +x git-clang-format; then
|
||||
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
|
||||
CLANG_FORMAT_OUTPUT=$(./git-clang-format HEAD^ HEAD --diff)
|
||||
if [[ ! ${CLANG_FORMAT_OUTPUT} == "no modified files to format" ]] && [[ ! -z ${CLANG_FORMAT_OUTPUT} ]]; then
|
||||
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 "${CLANG_FORMAT_OUTPUT}"
|
||||
echo "${GIT_DIFF_OUTPUT}"
|
||||
exit 1
|
||||
else
|
||||
echo "Passed clang format check"
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "git-clang-format not downloaded"
|
||||
exit 1
|
||||
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
|
||||
|
@ -1,313 +0,0 @@
|
||||
#!/bin/bash -
|
||||
#
|
||||
# File: git-archive-all.sh
|
||||
#
|
||||
# Description: A utility script that builds an archive file(s) of all
|
||||
# git repositories and submodules in the current path.
|
||||
# Useful for creating a single tarfile of a git super-
|
||||
# project that contains other submodules.
|
||||
#
|
||||
# Examples: Use git-archive-all.sh to create archive distributions
|
||||
# from git repositories. To use, simply do:
|
||||
#
|
||||
# cd $GIT_DIR; git-archive-all.sh
|
||||
#
|
||||
# where $GIT_DIR is the root of your git superproject.
|
||||
#
|
||||
# License: GPL3+
|
||||
#
|
||||
###############################################################################
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
# DEBUGGING
|
||||
set -e
|
||||
set -C # noclobber
|
||||
|
||||
# TRAP SIGNALS
|
||||
trap 'cleanup' QUIT EXIT
|
||||
|
||||
# For security reasons, explicitly set the internal field separator
|
||||
# to newline, space, tab
|
||||
OLD_IFS=$IFS
|
||||
IFS="$(printf '\n \t')"
|
||||
|
||||
function cleanup () {
|
||||
rm -f $TMPFILE
|
||||
rm -f $TMPLIST
|
||||
rm -f $TOARCHIVE
|
||||
IFS="$OLD_IFS"
|
||||
}
|
||||
|
||||
function usage () {
|
||||
echo "Usage is as follows:"
|
||||
echo
|
||||
echo "$PROGRAM <--version>"
|
||||
echo " Prints the program version number on a line by itself and exits."
|
||||
echo
|
||||
echo "$PROGRAM <--usage|--help|-?>"
|
||||
echo " Prints this usage output and exits."
|
||||
echo
|
||||
echo "$PROGRAM [--format <fmt>] [--prefix <path>] [--verbose|-v] [--separate|-s]"
|
||||
echo " [--worktree-attributes] [--tree-ish|-t <tree-ish>] [output_file]"
|
||||
echo " Creates an archive for the entire git superproject, and its submodules"
|
||||
echo " using the passed parameters, described below."
|
||||
echo
|
||||
echo " If '--format' is specified, the archive is created with the named"
|
||||
echo " git archiver backend. Obviously, this must be a backend that git archive"
|
||||
echo " understands. The format defaults to 'tar' if not specified."
|
||||
echo
|
||||
echo " If '--prefix' is specified, the archive's superproject and all submodules"
|
||||
echo " are created with the <path> prefix named. The default is to not use one."
|
||||
echo
|
||||
echo " If '--worktree-attributes' is specified, the invidual archive commands will"
|
||||
echo " look for attributes in .gitattributes in the working directory too."
|
||||
echo
|
||||
echo " If '--separate' or '-s' is specified, individual archives will be created"
|
||||
echo " for each of the superproject itself and its submodules. The default is to"
|
||||
echo " concatenate individual archives into one larger archive."
|
||||
echo
|
||||
echo " If '--tree-ish' is specified, the archive will be created based on whatever"
|
||||
echo " you define the tree-ish to be. Branch names, commit hash, etc. are acceptable."
|
||||
echo " Defaults to HEAD if not specified. See git archive's documentation for more"
|
||||
echo " information on what a tree-ish is."
|
||||
echo
|
||||
echo " If 'output_file' is specified, the resulting archive is created as the"
|
||||
echo " file named. This parameter is essentially a path that must be writeable."
|
||||
echo " When combined with '--separate' ('-s') this path must refer to a directory."
|
||||
echo " Without this parameter or when combined with '--separate' the resulting"
|
||||
echo " archive(s) are named with a dot-separated path of the archived directory and"
|
||||
echo " a file extension equal to their format (e.g., 'superdir.submodule1dir.tar')."
|
||||
echo
|
||||
echo " The special value '-' (single dash) is treated as STDOUT and, when used, the"
|
||||
echo " --separate option is ignored. Use a double-dash to separate the outfile from"
|
||||
echo " the value of previous options. For example, to write a .zip file to STDOUT:"
|
||||
echo
|
||||
echo " ./$PROGRAM --format zip -- -"
|
||||
echo
|
||||
echo " If '--verbose' or '-v' is specified, progress will be printed."
|
||||
}
|
||||
|
||||
function version () {
|
||||
echo "$PROGRAM version $VERSION"
|
||||
}
|
||||
|
||||
# Internal variables and initializations.
|
||||
readonly PROGRAM=`basename "$0"`
|
||||
readonly VERSION=0.3
|
||||
|
||||
SEPARATE=0
|
||||
VERBOSE=0
|
||||
|
||||
TARCMD=`command -v gtar || command -v gnutar || command -v tar`
|
||||
FORMAT=tar
|
||||
PREFIX=
|
||||
TREEISH=HEAD
|
||||
ARCHIVE_OPTS=
|
||||
|
||||
# RETURN VALUES/EXIT STATUS CODES
|
||||
readonly E_BAD_OPTION=254
|
||||
readonly E_UNKNOWN=255
|
||||
|
||||
# Process command-line arguments.
|
||||
while test $# -gt 0; do
|
||||
if [ x"$1" == x"--" ]; then
|
||||
# detect argument termination
|
||||
shift
|
||||
break
|
||||
fi
|
||||
case $1 in
|
||||
--format )
|
||||
shift
|
||||
FORMAT="$1"
|
||||
shift
|
||||
;;
|
||||
|
||||
--prefix )
|
||||
shift
|
||||
PREFIX="$1"
|
||||
shift
|
||||
;;
|
||||
|
||||
--worktree-attributes )
|
||||
ARCHIVE_OPTS+=" $1"
|
||||
shift
|
||||
;;
|
||||
|
||||
--separate | -s )
|
||||
shift
|
||||
SEPARATE=1
|
||||
;;
|
||||
|
||||
--tree-ish | -t )
|
||||
shift
|
||||
TREEISH="$1"
|
||||
shift
|
||||
;;
|
||||
|
||||
--version )
|
||||
version
|
||||
exit
|
||||
;;
|
||||
|
||||
--verbose | -v )
|
||||
shift
|
||||
VERBOSE=1
|
||||
;;
|
||||
|
||||
-? | --usage | --help )
|
||||
usage
|
||||
exit
|
||||
;;
|
||||
|
||||
-* )
|
||||
echo "Unrecognized option: $1" >&2
|
||||
usage
|
||||
exit $E_BAD_OPTION
|
||||
;;
|
||||
|
||||
* )
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
OLD_PWD="`pwd`"
|
||||
TMPDIR=${TMPDIR:-/tmp}
|
||||
TMPFILE=`mktemp "$TMPDIR/$PROGRAM.XXXXXX"` # Create a place to store our work's progress
|
||||
TMPLIST=`mktemp "$TMPDIR/$PROGRAM.submodules.XXXXXX"`
|
||||
TOARCHIVE=`mktemp "$TMPDIR/$PROGRAM.toarchive.XXXXXX"`
|
||||
OUT_FILE=$OLD_PWD # assume "this directory" without a name change by default
|
||||
|
||||
if [ ! -z "$1" ]; then
|
||||
OUT_FILE="$1"
|
||||
if [ "-" == "$OUT_FILE" ]; then
|
||||
SEPARATE=0
|
||||
fi
|
||||
shift
|
||||
fi
|
||||
|
||||
# Validate parameters; error early, error often.
|
||||
if [ "-" == "$OUT_FILE" -o $SEPARATE -ne 1 ] && [ "$FORMAT" == "tar" -a `$TARCMD --help | grep -q -- "--concatenate"; echo $?` -ne 0 ]; then
|
||||
echo "Your 'tar' does not support the '--concatenate' option, which we need"
|
||||
echo "to produce a single tarfile. Either install a compatible tar (such as"
|
||||
echo "gnutar), or invoke $PROGRAM with the '--separate' option."
|
||||
exit
|
||||
elif [ $SEPARATE -eq 1 -a ! -d "$OUT_FILE" ]; then
|
||||
echo "When creating multiple archives, your destination must be a directory."
|
||||
echo "If it's not, you risk being surprised when your files are overwritten."
|
||||
exit
|
||||
elif [ `git config -l | grep -q '^core\.bare=true'; echo $?` -eq 0 ]; then
|
||||
echo "$PROGRAM must be run from a git working copy (i.e., not a bare repository)."
|
||||
exit
|
||||
fi
|
||||
|
||||
# Create the superproject's git-archive
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo -n "creating superproject archive..."
|
||||
fi
|
||||
git archive --format=$FORMAT --prefix="$PREFIX" $ARCHIVE_OPTS $TREEISH > $TMPDIR/$(basename "$(pwd)").$FORMAT
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "done"
|
||||
fi
|
||||
echo $TMPDIR/$(basename "$(pwd)").$FORMAT >| $TMPFILE # clobber on purpose
|
||||
superfile=`head -n 1 $TMPFILE`
|
||||
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo -n "looking for subprojects..."
|
||||
fi
|
||||
# find all '.git' dirs, these show us the remaining to-be-archived dirs
|
||||
# we only want directories that are below the current directory
|
||||
find . -mindepth 2 -name '.git' -type d -print | sed -e 's/^\.\///' -e 's/\.git$//' >> $TOARCHIVE
|
||||
# as of version 1.7.8, git places the submodule .git directories under the superprojects .git dir
|
||||
# the submodules get a .git file that points to their .git dir. we need to find all of these too
|
||||
find . -mindepth 2 -name '.git' -type f -print | xargs grep -l "gitdir" | sed -e 's/^\.\///' -e 's/\.git$//' >> $TOARCHIVE
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "done"
|
||||
echo " found:"
|
||||
cat $TOARCHIVE | while read arch
|
||||
do
|
||||
echo " $arch"
|
||||
done
|
||||
fi
|
||||
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo -n "archiving submodules..."
|
||||
fi
|
||||
git submodule >>"$TMPLIST"
|
||||
while read path; do
|
||||
TREEISH=$(grep "^ .*${path%/} " "$TMPLIST" | cut -d ' ' -f 2) # git submodule does not list trailing slashes in $path
|
||||
cd "$path"
|
||||
git archive --format=$FORMAT --prefix="${PREFIX}$path" $ARCHIVE_OPTS ${TREEISH:-HEAD} > "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT
|
||||
if [ $FORMAT == 'zip' ]; then
|
||||
# delete the empty directory entry; zipped submodules won't unzip if we don't do this
|
||||
zip -d "$(tail -n 1 $TMPFILE)" "${PREFIX}${path%/}" >/dev/null # remove trailing '/'
|
||||
fi
|
||||
echo "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT >> $TMPFILE
|
||||
cd "$OLD_PWD"
|
||||
done < $TOARCHIVE
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "done"
|
||||
fi
|
||||
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo -n "concatenating archives into single archive..."
|
||||
fi
|
||||
# Concatenate archives into a super-archive.
|
||||
if [ $SEPARATE -eq 0 -o "-" == "$OUT_FILE" ]; then
|
||||
if [ $FORMAT == 'tar.gz' ]; then
|
||||
gunzip $superfile
|
||||
superfile=${superfile:0: -3} # Remove '.gz'
|
||||
sed -e '1d' $TMPFILE | while read file; do
|
||||
gunzip $file
|
||||
file=${file:0: -3}
|
||||
$TARCMD --concatenate -f "$superfile" "$file" && rm -f "$file"
|
||||
done
|
||||
gzip $superfile
|
||||
superfile=$superfile.gz
|
||||
elif [ $FORMAT == 'tar' ]; then
|
||||
sed -e '1d' $TMPFILE | while read file; do
|
||||
$TARCMD --concatenate -f "$superfile" "$file" && rm -f "$file"
|
||||
done
|
||||
elif [ $FORMAT == 'zip' ]; then
|
||||
sed -e '1d' $TMPFILE | while read file; do
|
||||
# zip incorrectly stores the full path, so cd and then grow
|
||||
cd `dirname "$file"`
|
||||
zip -g "$superfile" `basename "$file"` && rm -f "$file"
|
||||
done
|
||||
cd "$OLD_PWD"
|
||||
fi
|
||||
|
||||
echo "$superfile" >| $TMPFILE # clobber on purpose
|
||||
fi
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "done"
|
||||
fi
|
||||
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo -n "moving archive to $OUT_FILE..."
|
||||
fi
|
||||
while read file; do
|
||||
if [ "-" == "$OUT_FILE" ]; then
|
||||
cat "$file" && rm -f "$file"
|
||||
else
|
||||
mv "$file" "$OUT_FILE"
|
||||
fi
|
||||
done < $TMPFILE
|
||||
if [ $VERBOSE -eq 1 ]; then
|
||||
echo "done"
|
||||
fi
|
@ -2,21 +2,23 @@
|
||||
set -e
|
||||
|
||||
# Simple script to construct a redistributable and complete tarball of the
|
||||
# gamemode tree, including the git submodules, so that it can be trivially
|
||||
# gamemode tree, including the subprojects, so that it can be trivially
|
||||
# packaged by distributions banning networking during build.
|
||||
#
|
||||
# Modified from Ikey Doherty's release scripts for use within
|
||||
# Feral Interactive's gamemode project.
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
# Bump in tandem with meson.build, run script once new tag is up.
|
||||
VERSION="1.3"
|
||||
|
||||
NAME="gamemode"
|
||||
./scripts/git-archive-all.sh --format tar --prefix ${NAME}-${VERSION}/ --verbose -t HEAD ${NAME}-${VERSION}.tar
|
||||
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
|
||||
|
12
scripts/static-analyser-check.sh
Executable file
12
scripts/static-analyser-check.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/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
|
@ -1 +0,0 @@
|
||||
Subproject commit 745ada6724038cde32ff6390b32426cbdd5e532b
|
9
subprojects/inih.wrap
Normal file
9
subprojects/inih.wrap
Normal file
@ -0,0 +1,9 @@
|
||||
[wrap-file]
|
||||
directory = inih-r60
|
||||
source_url = https://github.com/benhoyt/inih/archive/r60.tar.gz
|
||||
source_filename = inih-r60.tar.gz
|
||||
source_hash = 706aa05c888b53bd170e5d8aa8f8a9d9ccf5449dfed262d5103d1f292af26774
|
||||
|
||||
[provide]
|
||||
inih = inih_dep
|
||||
inireader = INIReader_dep
|
141
util/cpucorectl.c
Normal file
141
util/cpucorectl.c
Normal file
@ -0,0 +1,141 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
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 <linux/limits.h>
|
||||
#include <sched.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -31,12 +31,10 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "governors-query.h"
|
||||
#include "logging.h"
|
||||
#include "common-governors.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Sets all governors to a value
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@ -31,10 +31,12 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "logging.h"
|
||||
#include "common-external.h"
|
||||
#include "common-gpu.h"
|
||||
#include "common-logging.h"
|
||||
|
||||
#include "external-helper.h"
|
||||
#include "gpu-control.h"
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* NV constants */
|
||||
#define NV_CORE_OFFSET_ATTRIBUTE "GPUGraphicsClockOffset"
|
||||
@ -44,6 +46,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
#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"
|
||||
@ -64,6 +67,30 @@ static void print_usage_and_exit(void)
|
||||
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)
|
||||
{
|
||||
@ -108,23 +135,21 @@ static long get_max_perf_level_nv(struct GameModeGPUInfo *info)
|
||||
if (!getenv("DISPLAY"))
|
||||
LOG_ERROR("Getting Nvidia parameters requires DISPLAY to be set - will likely fail!\n");
|
||||
|
||||
char arg[128] = { 0 };
|
||||
char buf[EXTERNAL_BUFFER_MAX] = { 0 };
|
||||
char arg[NV_ARG_MAX] = { 0 };
|
||||
const char *attr;
|
||||
|
||||
snprintf(arg, 128, NV_ATTRIBUTE_FORMAT, info->device, NV_PERFMODES_ATTRIBUTE);
|
||||
const char *exec_args[] = { "/usr/bin/nvidia-settings", "-q", arg, "-t", NULL };
|
||||
if (run_external_process(exec_args, buf, -1) != 0) {
|
||||
LOG_ERROR("Failed to get %s!\n", arg);
|
||||
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(buf, ';');
|
||||
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", buf);
|
||||
LOG_ERROR("Output:%s\n", attr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -142,59 +167,53 @@ static int get_gpu_state_nv(struct GameModeGPUInfo *info)
|
||||
|
||||
long perf_level = get_max_perf_level_nv(info);
|
||||
|
||||
char arg[128] = { 0 };
|
||||
char buf[EXTERNAL_BUFFER_MAX] = { 0 };
|
||||
char arg[NV_ARG_MAX] = { 0 };
|
||||
const char *attr;
|
||||
char *end;
|
||||
|
||||
/* Get the GPUGraphicsClockOffset parameter */
|
||||
snprintf(arg,
|
||||
128,
|
||||
NV_ARG_MAX,
|
||||
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT,
|
||||
info->device,
|
||||
NV_CORE_OFFSET_ATTRIBUTE,
|
||||
perf_level);
|
||||
const char *exec_args_core[] = { "/usr/bin/nvidia-settings", "-q", arg, "-t", NULL };
|
||||
if (run_external_process(exec_args_core, buf, -1) != 0) {
|
||||
LOG_ERROR("Failed to get %s!\n", arg);
|
||||
if ((attr = get_nv_attr(arg)) == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
info->nv_core = strtol(buf, &end, 10);
|
||||
if (end == buf) {
|
||||
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, buf);
|
||||
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,
|
||||
128,
|
||||
NV_ARG_MAX,
|
||||
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT,
|
||||
info->device,
|
||||
NV_MEM_OFFSET_ATTRIBUTE,
|
||||
perf_level);
|
||||
const char *exec_args_mem[] = { "/usr/bin/nvidia-settings", "-q", arg, "-t", NULL };
|
||||
if (run_external_process(exec_args_mem, buf, -1) != 0) {
|
||||
LOG_ERROR("Failed to get %s!\n", arg);
|
||||
if ((attr = get_nv_attr(arg)) == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
info->nv_mem = strtol(buf, &end, 10);
|
||||
if (end == buf) {
|
||||
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, buf);
|
||||
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, 128, NV_ATTRIBUTE_FORMAT, info->device, NV_POWERMIZER_MODE_ATTRIBUTE);
|
||||
const char *exec_args_pm[] = { "/usr/bin/nvidia-settings", "-q", arg, "-t", NULL };
|
||||
if (run_external_process(exec_args_pm, buf, -1) != 0) {
|
||||
LOG_ERROR("Failed to get %s!\n", arg);
|
||||
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(buf, &end, 10);
|
||||
if (end == buf) {
|
||||
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, buf);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -218,20 +237,18 @@ static int set_gpu_state_nv(struct GameModeGPUInfo *info)
|
||||
|
||||
long perf_level = get_max_perf_level_nv(info);
|
||||
|
||||
char arg[128] = { 0 };
|
||||
char arg[NV_ARG_MAX] = { 0 };
|
||||
|
||||
/* Set the GPUGraphicsClockOffset parameter */
|
||||
if (info->nv_core != -1) {
|
||||
snprintf(arg,
|
||||
128,
|
||||
NV_ARG_MAX,
|
||||
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT "=%ld",
|
||||
info->device,
|
||||
NV_CORE_OFFSET_ATTRIBUTE,
|
||||
perf_level,
|
||||
info->nv_core);
|
||||
const char *exec_args_core[] = { "/usr/bin/nvidia-settings", "-a", arg, NULL };
|
||||
if (run_external_process(exec_args_core, NULL, -1) != 0) {
|
||||
LOG_ERROR("Failed to set %s!\n", arg);
|
||||
if (set_nv_attr(arg) != 0) {
|
||||
status = -1;
|
||||
}
|
||||
}
|
||||
@ -239,15 +256,13 @@ static int set_gpu_state_nv(struct GameModeGPUInfo *info)
|
||||
/* Set the GPUMemoryTransferRateOffset parameter */
|
||||
if (info->nv_mem != -1) {
|
||||
snprintf(arg,
|
||||
128,
|
||||
NV_ARG_MAX,
|
||||
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT "=%ld",
|
||||
info->device,
|
||||
NV_MEM_OFFSET_ATTRIBUTE,
|
||||
perf_level,
|
||||
info->nv_mem);
|
||||
const char *exec_args_mem[] = { "/usr/bin/nvidia-settings", "-a", arg, NULL };
|
||||
if (run_external_process(exec_args_mem, NULL, -1) != 0) {
|
||||
LOG_ERROR("Failed to set %s!\n", arg);
|
||||
if (set_nv_attr(arg) != 0) {
|
||||
status = -1;
|
||||
}
|
||||
}
|
||||
@ -255,14 +270,12 @@ static int set_gpu_state_nv(struct GameModeGPUInfo *info)
|
||||
/* Set the GPUPowerMizerMode parameter if requested */
|
||||
if (info->nv_powermizer_mode != -1) {
|
||||
snprintf(arg,
|
||||
128,
|
||||
NV_ARG_MAX,
|
||||
NV_ATTRIBUTE_FORMAT "=%ld",
|
||||
info->device,
|
||||
NV_POWERMIZER_MODE_ATTRIBUTE,
|
||||
info->nv_powermizer_mode);
|
||||
const char *exec_args_pm[] = { "/usr/bin/nvidia-settings", "-a", arg, NULL };
|
||||
if (run_external_process(exec_args_pm, NULL, -1) != 0) {
|
||||
LOG_ERROR("Failed to set %s!\n", arg);
|
||||
if (set_nv_attr(arg) != 0) {
|
||||
status = -1;
|
||||
}
|
||||
}
|
||||
@ -280,8 +293,8 @@ static int get_gpu_state_amd(struct GameModeGPUInfo *info)
|
||||
return -1;
|
||||
|
||||
/* Get the contents of power_dpm_force_performance_level */
|
||||
char path[64];
|
||||
snprintf(path, 64, AMD_DRM_PATH, info->device, "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) {
|
||||
@ -289,21 +302,26 @@ static int get_gpu_state_amd(struct GameModeGPUInfo *info)
|
||||
return -1;
|
||||
}
|
||||
|
||||
char buff[CONFIG_VALUE_MAX];
|
||||
if (!fgets(buff, CONFIG_VALUE_MAX, file)) {
|
||||
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));
|
||||
return -1;
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
if (fclose(file) != 0) {
|
||||
LOG_ERROR("Could not close %s after reading (%s)!\n", path, strerror(errno));
|
||||
return -1;
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
/* Copy in the value from the file */
|
||||
strncpy(info->amd_performance_level, buff, CONFIG_VALUE_MAX);
|
||||
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 info == NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -311,8 +329,8 @@ static int get_gpu_state_amd(struct GameModeGPUInfo *info)
|
||||
*/
|
||||
static int set_gpu_state_amd_file(const char *filename, long device, const char *value)
|
||||
{
|
||||
char path[64];
|
||||
snprintf(path, 64, AMD_DRM_PATH, device, filename);
|
||||
char path[PATH_MAX];
|
||||
snprintf(path, PATH_MAX, AMD_DRM_PATH, device, filename);
|
||||
|
||||
FILE *file = fopen(path, "w");
|
||||
if (!file) {
|
||||
@ -320,17 +338,19 @@ static int set_gpu_state_amd_file(const char *filename, long device, const char
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = 0;
|
||||
|
||||
if (fprintf(file, "%s", value) < 0) {
|
||||
LOG_ERROR("Could not write to %s (%s)!\n", path, strerror(errno));
|
||||
return -1;
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
if (fclose(file) != 0) {
|
||||
LOG_ERROR("Could not close %s after writing (%s)!\n", path, strerror(errno));
|
||||
return -1;
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -390,10 +410,11 @@ static long get_generic_value(const char *val)
|
||||
*/
|
||||
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 */
|
||||
struct GameModeGPUInfo info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.device = get_device(argv[1]);
|
||||
info.vendor = gamemode_get_gpu_vendor(info.device);
|
||||
|
||||
@ -414,14 +435,12 @@ int main(int argc, char *argv[])
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("Currently unsupported GPU vendor 0x%04x, doing nothing!\n",
|
||||
(short)info.vendor);
|
||||
(unsigned short)info.vendor);
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (argc >= 4 && argc <= 7 && strncmp(argv[2], "set", 3) == 0) {
|
||||
/* Get and verify the vendor and device */
|
||||
struct GameModeGPUInfo info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.device = get_device(argv[1]);
|
||||
info.vendor = gamemode_get_gpu_vendor(info.device);
|
||||
|
||||
@ -449,12 +468,12 @@ int main(int argc, char *argv[])
|
||||
LOG_ERROR("Must pass performance level for AMD gpu!\n");
|
||||
print_usage_and_exit();
|
||||
}
|
||||
strncpy(info.amd_performance_level, argv[3], CONFIG_VALUE_MAX);
|
||||
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",
|
||||
(short)info.vendor);
|
||||
(unsigned short)info.vendor);
|
||||
print_usage_and_exit();
|
||||
break;
|
||||
}
|
75
util/meson.build
Normal file
75
util/meson.build
Normal file
@ -0,0 +1,75 @@
|
||||
|
||||
# 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,
|
||||
)
|
||||
|
||||
# Small target util to get and set platform profile
|
||||
platprofctl_sources = [
|
||||
'platprofctl.c',
|
||||
]
|
||||
|
||||
platprofctl = executable(
|
||||
'platprofctl',
|
||||
sources: platprofctl_sources,
|
||||
dependencies: [
|
||||
link_daemon_common,
|
||||
],
|
||||
install: true,
|
||||
install_dir: path_libexecdir,
|
||||
)
|
84
util/platprofctl.c
Normal file
84
util/platprofctl.c
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2025, the GameMode contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Feral Interactive nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "common-logging.h"
|
||||
#include "common-profile.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* Sets platform profile to a value
|
||||
*/
|
||||
static int set_profile_state(const char *value)
|
||||
{
|
||||
int retval = EXIT_SUCCESS;
|
||||
|
||||
FILE *f = fopen(profile_path, "w");
|
||||
if (!f) {
|
||||
LOG_ERROR("Failed to open file for write %s\n", profile_path);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (fprintf(f, "%s\n", value) < 0) {
|
||||
LOG_ERROR("Failed to set platform profile to %s: %s", value, strerror(errno));
|
||||
retval = EXIT_FAILURE;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point, dispatch to the appropriate helper
|
||||
*/
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc == 2 && strncmp(argv[1], "get", 3) == 0) {
|
||||
printf("%s", get_profile_state());
|
||||
} else if (argc == 3 && strncmp(argv[1], "set", 3) == 0) {
|
||||
const char *value = argv[2];
|
||||
|
||||
/* Must be root to set the state */
|
||||
if (geteuid() != 0) {
|
||||
LOG_ERROR("This program must be run as root\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return set_profile_state(value);
|
||||
} else {
|
||||
fprintf(stderr, "usage: platprofctl [get] [set VALUE]\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -1,11 +1,8 @@
|
||||
/*
|
||||
|
||||
Copyright (c) 2017-2019, Feral Interactive
|
||||
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
|
||||
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
|
||||
@ -14,7 +11,6 @@ modification, are permitted provided that the following conditions are met:
|
||||
* 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
|
||||
@ -26,36 +22,54 @@ 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 "helpers.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
#include <unistd.h>
|
||||
#include "common-logging.h"
|
||||
#include "common-splitlock.h"
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
procfd_t game_mode_open_proc(const pid_t pid)
|
||||
static bool write_value(const char *key, const char *value)
|
||||
{
|
||||
char buffer[PATH_MAX];
|
||||
const char *proc_path = buffered_snprintf(buffer, "/proc/%d", pid);
|
||||
FILE *f = fopen(key, "w");
|
||||
|
||||
return proc_path ? open(proc_path, O_RDONLY | O_CLOEXEC) : INVALID_PROCFD;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the process environment.
|
||||
*/
|
||||
int game_mode_close_proc(const procfd_t procfd)
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
return close(procfd);
|
||||
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(splitlock_path, 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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user