adjustor 3.3.1__tar.gz → 3.4.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. {adjustor-3.3.1/src/adjustor.egg-info → adjustor-3.4.0}/PKG-INFO +51 -8
  2. {adjustor-3.3.1 → adjustor-3.4.0}/pyproject.toml +1 -1
  3. {adjustor-3.3.1 → adjustor-3.4.0}/readme.md +50 -7
  4. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/amd/__init__.py +18 -3
  5. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/amd/settings.yml +5 -0
  6. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/asus/__init__.py +179 -79
  7. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/asus/settings.yml +47 -33
  8. adjustor-3.4.0/src/adjustor/drivers/general/__init__.py +146 -0
  9. adjustor-3.4.0/src/adjustor/drivers/general/settings.yml +27 -0
  10. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/lenovo/__init__.py +1 -0
  11. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/lenovo/settings.yml +14 -4
  12. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/hhd.py +7 -4
  13. {adjustor-3.3.1 → adjustor-3.4.0/src/adjustor.egg-info}/PKG-INFO +51 -8
  14. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor.egg-info/SOURCES.txt +2 -0
  15. {adjustor-3.3.1 → adjustor-3.4.0}/LICENSE +0 -0
  16. {adjustor-3.3.1 → adjustor-3.4.0}/MANIFEST.in +0 -0
  17. {adjustor-3.3.1 → adjustor-3.4.0}/setup.cfg +0 -0
  18. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/__init__.py +0 -0
  19. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/__main__.py +0 -0
  20. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/core/__init__.py +0 -0
  21. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/core/acpi.py +0 -0
  22. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/core/alib.py +0 -0
  23. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/core/const.py +0 -0
  24. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/core/lenovo.py +0 -0
  25. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/core/platform.py +0 -0
  26. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/__init__.py +0 -0
  27. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/amd/power-profiles-daemon.dbus.xml.in +0 -0
  28. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/amd/ppd.py +0 -0
  29. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/smu/__init__.py +0 -0
  30. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/smu/qam.yml +0 -0
  31. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/drivers/smu/smu.yml +0 -0
  32. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/events.py +0 -0
  33. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/fuse/__init__.py +0 -0
  34. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/fuse/driver.py +0 -0
  35. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/fuse/gpu.py +0 -0
  36. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/fuse/utils.py +0 -0
  37. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/i18n.py +0 -0
  38. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor/settings.yml +0 -0
  39. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor.egg-info/dependency_links.txt +0 -0
  40. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor.egg-info/entry_points.txt +0 -0
  41. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor.egg-info/requires.txt +0 -0
  42. {adjustor-3.3.1 → adjustor-3.4.0}/src/adjustor.egg-info/top_level.txt +0 -0
  43. {adjustor-3.3.1 → adjustor-3.4.0}/usr/share/dbus-1/system.d/hhd-net.hadess.PowerProfiles.conf +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: adjustor
3
- Version: 3.3.1
3
+ Version: 3.4.0
4
4
  Summary: Adjustor, a userspace program for managing the TDP of handheld devices.
5
5
  Author-email: Kapenekakis Antheas <pypi@antheas.dev>
6
6
  Project-URL: Homepage, https://github.com/hhd-dev/adjustor
@@ -38,7 +38,7 @@ except intel handhelds and older prior to 6XXX AMD handhelds.
38
38
  ## TDP Control
39
39
  For the ROG Ally, Ally X and Legion Go that have an ACPI/EC implementation for
40
40
  bios and fan curves,
41
- Adjustor uses the manufactuer functions for setting TDP.
41
+ Adjustor uses the manufacturer functions for setting TDP.
42
42
  For the Allys, the asus-wmi kernel driver is used to set the tdp and manage the
43
43
  fan curves.
44
44
  For the Go, Lenovo's WMI methods are called through `acpi_call`, which will hopefully
@@ -53,14 +53,14 @@ For more, see [AMD TDP Control Details](#amd-tdp).
53
53
  In all cases, there are checks to ensure that the TDP is within the safe range
54
54
  of the processors.
55
55
 
56
- ## Energy Management
57
- Adjustor can also manage the energy profile of the processor, by setting EPP
56
+ ## Energy Management in Handhelds
57
+ Adjustor can also manage the energy profile of the processor in handhelds, by setting EPP
58
58
  and proper frequency values.
59
59
  After we transitioned people away from Decky plugins (which had some governor controls)
60
60
  to using Handheld Daemon for TDP, we found that Power Profiles Daemon (PPD)
61
61
  would use aggressive CPU values.
62
62
  These values are optimized for devices that have a dedicated power budget for the CPU
63
- (e.g., laptops, desktops), which caused issues with handhelds.
63
+ (e.g., laptops, desktops), which causes undesirable behavior handhelds.
64
64
 
65
65
  For example, the balanced PPD profile would set EPP to balance_performance and
66
66
  enable CPU boost, which would increase the draw of the CPU during gaming by 2W
@@ -91,6 +91,48 @@ TDP range instead of CPU values (which is the user's expectation).
91
91
  Of course, depending on TDP and user preference, the CPU governor values will be set
92
92
  accordingly.
93
93
 
94
+ ## Energy Management in other computers
95
+ As we design Handheld Daemon to be enabled in more Deck style devices (e.g., HTPCs),
96
+ these devices have different power requirements and processors (e.g., Intel), which
97
+ are better managed with Power Profiles Daemon.
98
+ It is the aim of the Handheld Daemon project to become a general Deck style session
99
+ manager for anything gamescope related, with useful features for all devices.
100
+
101
+ In these cases, starting with 3.4, for devices that are not in the CPU/device
102
+ whitelist (includes only AMD U series APUs and handhelds), Adjustor contains a
103
+ general energy management plugin that allows for switching the PPD power profile
104
+ from game mode.
105
+ In addition, it supports [sched_ext schedulers](#sched_ext).
106
+
107
+ This means that for general devices, Handheld Daemon uses PPD, and for handhelds,
108
+ Handheld Daemon becomes PPD.
109
+ This can be confusing for distribution maintainers and users, as they can and
110
+ should install both (e.g., Adjustor uses the Power Profile Daemon polkits).
111
+
112
+ In any case, Adjustor will never break/conflict with PPD and contains helpful
113
+ messages about disabling PPD in case the optimized handheld plugin is loaded.
114
+ For distribution maintainers that ship both and want Handheld Daemon to work
115
+ without user intervention, the environment variable `HHD_PPD_MASK` is provided.
116
+ If and only if it is set e.g., by using a systemd service extension, Handheld Daemon
117
+ will mask and disable PPD if optimized energy management is supported and enabled (e.g., for handhelds).
118
+ If the general plugin is loaded, it will unmask PPD during startup.
119
+ This means that Power Management will work properly for all devices without manual
120
+ intervention or whitelisting by distribution maintainers.
121
+
122
+ ## Sched_ext<a name="sched-ext"></a>
123
+ Starting with version 3.3, Adjustor can attach sched_ext schedulers to the
124
+ kernel if those are supported and installed.
125
+ Adjustor manages the lifetime of the scheduler, including launching and attaching
126
+ it, without using a systemd service, and is fully responsive to quirk scheduler
127
+ switches.
128
+
129
+ Schedulers are whitelisted in a case by case basis, with LAVD, rusty, and bpfland
130
+ being supported in the current version (the `scx_` namespace is crowded with e.g.,
131
+ test schedulers).
132
+ Of course, only installed schedulers are shown if and only if the kernel supports
133
+ them.
134
+ Get in touch to add your favorite scheduler, as it is a single line change.
135
+
94
136
  ## AMD TDP Control Details<a name="amd-tdp"></a>
95
137
  Adjustor controls TDP through the Dynamic Power and Thermal Configuration Interface
96
138
  of AMD, which exposes a superset of the parameters that can be currently found in
@@ -99,7 +141,7 @@ This vendor interface is part of the ACPI ASL library, and provided through the
99
141
  ALIB method 0x0C.
100
142
  The underlying implementation of the interface is SMU calls.
101
143
  This means that as long as the kernel module `acpi_call` is loaded, Adjustor
102
- can control TDP in an equivalent way to [RyzenAdj](https://github.dev/FlyGoat/RyzenAdj/).
144
+ can control TDP in a similar way to [RyzenAdj](https://github.dev/FlyGoat/RyzenAdj/).
103
145
 
104
146
  The ABI of this vendor function (as it is provided to manufacturers) can be
105
147
  considered mostly stable, so little work is needed between subsequent
@@ -109,8 +151,9 @@ Of course, support for processors is only added after the ACPI bindings have
109
151
  been reviewed, to avoid surprises.
110
152
  Both the Ally and Legion Go use this function, in the exact same way, so setting
111
153
  TDP with it is very stable, and we have had no reported crashes.
112
- It should not be used (and is not used) with those devices, however, as the
113
- manufacturer functions will interfere.
154
+ It can not be used and is not used with those devices, however, as the
155
+ manufacturer functions would interfere and provide a better user experience,
156
+ such as setting appropriate fan curves and changing the power light color on the Legion Go.
114
157
 
115
158
  Unfortunately for devices that do have an ACPI/EC implementation for TDP, there
116
159
  is no official way of setting TDP on demand, either on Linux or Windows, with
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "adjustor"
3
- version = "3.3.1"
3
+ version = "3.4.0"
4
4
  authors = [
5
5
  { name="Kapenekakis Antheas", email="pypi@antheas.dev" },
6
6
  ]
@@ -17,7 +17,7 @@ except intel handhelds and older prior to 6XXX AMD handhelds.
17
17
  ## TDP Control
18
18
  For the ROG Ally, Ally X and Legion Go that have an ACPI/EC implementation for
19
19
  bios and fan curves,
20
- Adjustor uses the manufactuer functions for setting TDP.
20
+ Adjustor uses the manufacturer functions for setting TDP.
21
21
  For the Allys, the asus-wmi kernel driver is used to set the tdp and manage the
22
22
  fan curves.
23
23
  For the Go, Lenovo's WMI methods are called through `acpi_call`, which will hopefully
@@ -32,14 +32,14 @@ For more, see [AMD TDP Control Details](#amd-tdp).
32
32
  In all cases, there are checks to ensure that the TDP is within the safe range
33
33
  of the processors.
34
34
 
35
- ## Energy Management
36
- Adjustor can also manage the energy profile of the processor, by setting EPP
35
+ ## Energy Management in Handhelds
36
+ Adjustor can also manage the energy profile of the processor in handhelds, by setting EPP
37
37
  and proper frequency values.
38
38
  After we transitioned people away from Decky plugins (which had some governor controls)
39
39
  to using Handheld Daemon for TDP, we found that Power Profiles Daemon (PPD)
40
40
  would use aggressive CPU values.
41
41
  These values are optimized for devices that have a dedicated power budget for the CPU
42
- (e.g., laptops, desktops), which caused issues with handhelds.
42
+ (e.g., laptops, desktops), which causes undesirable behavior handhelds.
43
43
 
44
44
  For example, the balanced PPD profile would set EPP to balance_performance and
45
45
  enable CPU boost, which would increase the draw of the CPU during gaming by 2W
@@ -70,6 +70,48 @@ TDP range instead of CPU values (which is the user's expectation).
70
70
  Of course, depending on TDP and user preference, the CPU governor values will be set
71
71
  accordingly.
72
72
 
73
+ ## Energy Management in other computers
74
+ As we design Handheld Daemon to be enabled in more Deck style devices (e.g., HTPCs),
75
+ these devices have different power requirements and processors (e.g., Intel), which
76
+ are better managed with Power Profiles Daemon.
77
+ It is the aim of the Handheld Daemon project to become a general Deck style session
78
+ manager for anything gamescope related, with useful features for all devices.
79
+
80
+ In these cases, starting with 3.4, for devices that are not in the CPU/device
81
+ whitelist (includes only AMD U series APUs and handhelds), Adjustor contains a
82
+ general energy management plugin that allows for switching the PPD power profile
83
+ from game mode.
84
+ In addition, it supports [sched_ext schedulers](#sched_ext).
85
+
86
+ This means that for general devices, Handheld Daemon uses PPD, and for handhelds,
87
+ Handheld Daemon becomes PPD.
88
+ This can be confusing for distribution maintainers and users, as they can and
89
+ should install both (e.g., Adjustor uses the Power Profile Daemon polkits).
90
+
91
+ In any case, Adjustor will never break/conflict with PPD and contains helpful
92
+ messages about disabling PPD in case the optimized handheld plugin is loaded.
93
+ For distribution maintainers that ship both and want Handheld Daemon to work
94
+ without user intervention, the environment variable `HHD_PPD_MASK` is provided.
95
+ If and only if it is set e.g., by using a systemd service extension, Handheld Daemon
96
+ will mask and disable PPD if optimized energy management is supported and enabled (e.g., for handhelds).
97
+ If the general plugin is loaded, it will unmask PPD during startup.
98
+ This means that Power Management will work properly for all devices without manual
99
+ intervention or whitelisting by distribution maintainers.
100
+
101
+ ## Sched_ext<a name="sched-ext"></a>
102
+ Starting with version 3.3, Adjustor can attach sched_ext schedulers to the
103
+ kernel if those are supported and installed.
104
+ Adjustor manages the lifetime of the scheduler, including launching and attaching
105
+ it, without using a systemd service, and is fully responsive to quirk scheduler
106
+ switches.
107
+
108
+ Schedulers are whitelisted in a case by case basis, with LAVD, rusty, and bpfland
109
+ being supported in the current version (the `scx_` namespace is crowded with e.g.,
110
+ test schedulers).
111
+ Of course, only installed schedulers are shown if and only if the kernel supports
112
+ them.
113
+ Get in touch to add your favorite scheduler, as it is a single line change.
114
+
73
115
  ## AMD TDP Control Details<a name="amd-tdp"></a>
74
116
  Adjustor controls TDP through the Dynamic Power and Thermal Configuration Interface
75
117
  of AMD, which exposes a superset of the parameters that can be currently found in
@@ -78,7 +120,7 @@ This vendor interface is part of the ACPI ASL library, and provided through the
78
120
  ALIB method 0x0C.
79
121
  The underlying implementation of the interface is SMU calls.
80
122
  This means that as long as the kernel module `acpi_call` is loaded, Adjustor
81
- can control TDP in an equivalent way to [RyzenAdj](https://github.dev/FlyGoat/RyzenAdj/).
123
+ can control TDP in a similar way to [RyzenAdj](https://github.dev/FlyGoat/RyzenAdj/).
82
124
 
83
125
  The ABI of this vendor function (as it is provided to manufacturers) can be
84
126
  considered mostly stable, so little work is needed between subsequent
@@ -88,8 +130,9 @@ Of course, support for processors is only added after the ACPI bindings have
88
130
  been reviewed, to avoid surprises.
89
131
  Both the Ally and Legion Go use this function, in the exact same way, so setting
90
132
  TDP with it is very stable, and we have had no reported crashes.
91
- It should not be used (and is not used) with those devices, however, as the
92
- manufacturer functions will interfere.
133
+ It can not be used and is not used with those devices, however, as the
134
+ manufacturer functions would interfere and provide a better user experience,
135
+ such as setting appropriate fan curves and changing the power light color on the Legion Go.
93
136
 
94
137
  Unfortunately for devices that do have an ACPI/EC implementation for TDP, there
95
138
  is no official way of setting TDP on demand, either on Linux or Windows, with
@@ -24,7 +24,7 @@ from adjustor.fuse.gpu import (
24
24
 
25
25
  logger = logging.getLogger(__name__)
26
26
 
27
- APPLY_DELAY = 0.5
27
+ APPLY_DELAY = 0.2
28
28
 
29
29
 
30
30
  def _ppd_client(emit, proc):
@@ -126,14 +126,29 @@ class AmdGPUPlugin(HHDPlugin):
126
126
  ]
127
127
  )
128
128
  for line in out.decode().splitlines():
129
- if "not-found" in line.lower():
129
+ line = line.lower()
130
+ if "masked" in line:
130
131
  continue
131
- if "power-profiles-daemon" in line or "tuned" in line.lower():
132
+ if "not-found" in line:
133
+ continue
134
+ if "inactive" in line:
135
+ continue
136
+ if "power-profiles-daemon" in line or "tuned" in line:
132
137
  self.ppd_conflict = True
133
138
  break
134
139
  except Exception as e:
135
140
  logger.error(f"Failed to check for PPD conflict:\n{e}")
136
141
 
142
+ if self.ppd_conflict and os.environ.get("HHD_PPD_MASK", None):
143
+ logger.warning(
144
+ "PPD conflict detected but HHD_PPD_MASK is set. Masking PPD."
145
+ )
146
+ # Mask and disable
147
+ os.system("systemctl mask power-profiles-daemon.service")
148
+ os.system("systemctl disable --now power-profiles-daemon.service")
149
+ # Keep going without check to avoid obscure errors
150
+ self.ppd_conflict = False
151
+
137
152
  if self.ppd_conflict:
138
153
  self.initialized = False
139
154
  return {
@@ -46,6 +46,11 @@ enabled:
46
46
  options:
47
47
  min: 400MHz
48
48
  nonlinear: 1GHz
49
+ hint: >-
50
+ Sets the minimum frequency for the CPU.
51
+ Using 400MHz will save battery in light games.
52
+ However, the delay of increasing the frequency
53
+ may cause minor stutters, especially in VRR displays.
49
54
  cpu_boost:
50
55
  type: multiple
51
56
  title: CPU Boost
@@ -9,8 +9,8 @@ from adjustor.core.platform import set_platform_profile
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
- APPLY_DELAY = 1.5
13
- TDP_DELAY = 0.2
12
+ APPLY_DELAY = 0.7
13
+ TDP_DELAY = 0.1
14
14
  MIN_TDP_START = 7
15
15
  MAX_TDP_START = 30
16
16
  # FIXME: add AC/DC values
@@ -123,7 +123,7 @@ def disable_fan_curve():
123
123
 
124
124
 
125
125
  class AsusDriverPlugin(HHDPlugin):
126
- def __init__(self) -> None:
126
+ def __init__(self, allyx: bool = False) -> None:
127
127
  self.name = f"adjustor_asus"
128
128
  self.priority = 6
129
129
  self.log = "adja"
@@ -132,11 +132,16 @@ class AsusDriverPlugin(HHDPlugin):
132
132
  self.enforce_limits = True
133
133
  self.startup = True
134
134
  self.old_conf = None
135
+ self.mode = None
136
+ self.cycle_tdp = None
135
137
 
136
138
  self.queue_fan = None
137
139
  self.queue_tdp = None
138
140
  self.new_tdp = None
141
+ self.new_mode = None
139
142
  self.old_target = None
143
+ self.pp = None
144
+ self.allyx = allyx
140
145
 
141
146
  def settings(self):
142
147
  if not self.enabled:
@@ -147,8 +152,25 @@ class AsusDriverPlugin(HHDPlugin):
147
152
 
148
153
  self.initialized = True
149
154
  out = {"tdp": {"asus": load_relative_yaml("settings.yml")}}
155
+
156
+ # Set units
157
+ if self.allyx:
158
+ out["tdp"]["asus"]["children"]["tdp_v2"]["modes"]["quiet"]["unit"] = "13W"
159
+ out["tdp"]["asus"]["children"]["tdp_v2"]["modes"]["balanced"]["unit"] = "17W"
160
+ out["tdp"]["asus"]["children"]["tdp_v2"]["modes"]["performance"][
161
+ "unit"
162
+ ] = "25W"
163
+ else:
164
+ out["tdp"]["asus"]["children"]["tdp_v2"]["modes"]["quiet"]["unit"] = "10W"
165
+ out["tdp"]["asus"]["children"]["tdp_v2"]["modes"]["balanced"]["unit"] = "15W"
166
+ out["tdp"]["asus"]["children"]["tdp_v2"]["modes"]["performance"][
167
+ "unit"
168
+ ] = "20W"
169
+
150
170
  if not self.enforce_limits:
151
- out["tdp"]["asus"]["children"]["tdp"]["max"] = 50
171
+ out["tdp"]["asus"]["children"]["tdp_v2"]["modes"]["custom"]["children"]["tdp"][
172
+ "max"
173
+ ] = 50
152
174
  return out
153
175
 
154
176
  def open(
@@ -204,78 +226,120 @@ class AsusDriverPlugin(HHDPlugin):
204
226
  # TDP
205
227
  #
206
228
  new_target = None
207
-
208
- # Reset fan curve on mode change
209
- # Has to happen before setting the stdp, ftdp values, in case
210
- # we are in custom mode
211
- fan_mode = conf["tdp.asus.fan.mode"].to(str)
212
- if fan_mode != self.old_conf["fan.mode"].to(str) and fan_mode != "manual":
213
- pass
214
-
215
- # Check user changed values
216
- if self.new_tdp:
217
- steady = self.new_tdp
218
- self.new_tdp = None
219
- conf["tdp.asus.tdp"] = steady
229
+ new_tdp = self.new_tdp
230
+ self.new_tdp = None
231
+ new_mode = self.new_mode
232
+ self.new_mode = None
233
+ ally_x = self.allyx
234
+ if new_tdp:
235
+ # For TDP values received from steam, set the appropriate
236
+ # mode to get a better experience.
237
+ if new_tdp == (13 if ally_x else 10):
238
+ mode = "quiet"
239
+ elif new_tdp == (17 if ally_x else 15):
240
+ mode = "balanced"
241
+ elif new_tdp == 25 or new_tdp == 30:
242
+ mode = "performance"
243
+ else:
244
+ mode = "custom"
245
+ conf["tdp.asus.tdp_v2.mode"] = mode
246
+ elif new_mode:
247
+ mode = new_mode
248
+ conf["tdp.asus.tdp_v2.mode"] = mode
220
249
  else:
221
- steady = conf["tdp.asus.tdp"].to(int)
222
-
223
- steady_updated = steady and steady != self.old_conf["tdp"].to(int)
250
+ mode = conf["tdp.asus.tdp_v2.mode"].to(str)
251
+ self.mode = mode
252
+
253
+ tdp_reset = False
254
+ if mode is not None and mode != self.old_conf["tdp_v2.mode"].to(str):
255
+ tdp_reset = True
256
+
257
+ # Handle EPP for presets
258
+ if tdp_reset and mode != "custom":
259
+ match mode:
260
+ case "quiet":
261
+ set_platform_profile("quiet")
262
+ new_target = "power"
263
+ case "balanced":
264
+ set_platform_profile("balanced")
265
+ new_target = "balanced"
266
+ case _: # "performance":
267
+ set_platform_profile("performance")
268
+ new_target = "performance"
269
+
270
+ # In custom mode, re-apply settings with debounce
271
+ if mode == "custom":
272
+ # Check user changed values
273
+ if new_tdp:
274
+ steady = new_tdp
275
+ conf["tdp.asus.tdp_v2.custom.tdp"] = steady
276
+ else:
277
+ steady = conf["tdp.asus.tdp_v2.custom.tdp"].to(int)
224
278
 
225
- if self.startup and (steady > MAX_TDP_START or steady < MIN_TDP_START):
226
- logger.warning(
227
- f"TDP ({steady}) outside the device spec. Resetting for stability reasons."
279
+ steady_updated = steady and steady != self.old_conf["tdp_v2.custom.tdp"].to(
280
+ int
228
281
  )
229
- steady = min(max(steady, MIN_TDP_START), MAX_TDP_START)
230
- conf["tdp.asus.tdp"] = steady
231
- steady_updated = True
232
-
233
- boost = conf["tdp.asus.boost"].to(bool)
234
- boost_updated = boost != self.old_conf["boost"].to(bool)
235
-
236
- # If yes, queue an update
237
- # Debounce
238
- if self.startup or steady_updated or boost_updated:
239
- self.queue_tdp = curr + APPLY_DELAY
240
-
241
- tdp_set = self.queue_tdp and self.queue_tdp < curr
242
- if tdp_set:
243
- if steady < 5:
244
- steady = 5
245
- if steady < 13:
246
- set_platform_profile("quiet")
247
- new_target = "power"
248
- elif steady < 20:
249
- set_platform_profile("balanced")
250
- new_target = "balanced"
251
- else:
252
- set_platform_profile("performance")
253
- new_target = "performance"
254
-
255
- self.queue_tdp = None
256
- if boost:
257
- # TODO: Use different boost values depending on whether plugged in
258
- time.sleep(TDP_DELAY)
259
- set_tdp(
260
- "fast", FTDP_FN, min(max(steady, MAX_TDP), int(steady * 35 / 25))
261
- )
262
- time.sleep(TDP_DELAY)
263
- set_tdp(
264
- "slow", STDP_FN, min(max(steady, MAX_TDP), int(steady * 30 / 25))
282
+ steady_updated |= tdp_reset
283
+
284
+ if self.startup and (steady > MAX_TDP_START or steady < MIN_TDP_START):
285
+ logger.warning(
286
+ f"TDP ({steady}) outside the device spec. Resetting for stability reasons."
265
287
  )
266
- time.sleep(TDP_DELAY)
267
- set_tdp("steady", CTDP_FN, steady)
268
- else:
269
- time.sleep(TDP_DELAY)
270
- set_tdp("fast", FTDP_FN, steady)
271
- time.sleep(TDP_DELAY)
272
- set_tdp("slow", STDP_FN, steady)
273
- time.sleep(TDP_DELAY)
274
- set_tdp("steady", CTDP_FN, steady)
288
+ steady = min(max(steady, MIN_TDP_START), MAX_TDP_START)
289
+ conf["tdp.asus.tdp_v2.custom.tdp"] = steady
290
+ steady_updated = True
291
+
292
+ boost = conf["tdp.asus.tdp_v2.custom.boost"].to(bool)
293
+ boost_updated = boost != self.old_conf["tdp_v2.custom.boost"].to(bool)
294
+
295
+ # If yes, queue an update
296
+ # Debounce
297
+ if self.startup or steady_updated or boost_updated:
298
+ self.queue_tdp = curr + APPLY_DELAY
299
+
300
+ tdp_set = self.queue_tdp and self.queue_tdp < curr
301
+ if tdp_set:
302
+ if steady < 5:
303
+ steady = 5
304
+ if steady < (15 if self.allyx else 13):
305
+ set_platform_profile("quiet")
306
+ new_target = "power"
307
+ elif steady < (22 if self.allyx else 20):
308
+ set_platform_profile("balanced")
309
+ new_target = "balanced"
310
+ else:
311
+ set_platform_profile("performance")
312
+ new_target = "performance"
313
+
314
+ self.queue_tdp = None
315
+ if boost:
316
+ # TODO: Use different boost values depending on whether plugged in
317
+ time.sleep(TDP_DELAY)
318
+ set_tdp(
319
+ "fast",
320
+ FTDP_FN,
321
+ min(max(steady, MAX_TDP), int(steady * 35 / 25)),
322
+ )
323
+ time.sleep(TDP_DELAY)
324
+ set_tdp(
325
+ "slow",
326
+ STDP_FN,
327
+ min(max(steady, MAX_TDP), int(steady * 30 / 25)),
328
+ )
329
+ time.sleep(TDP_DELAY)
330
+ set_tdp("steady", CTDP_FN, steady)
331
+ else:
332
+ time.sleep(TDP_DELAY)
333
+ set_tdp("fast", FTDP_FN, steady)
334
+ time.sleep(TDP_DELAY)
335
+ set_tdp("slow", STDP_FN, steady)
336
+ time.sleep(TDP_DELAY)
337
+ set_tdp("steady", CTDP_FN, steady)
275
338
 
276
339
  if new_target and new_target != self.old_target:
277
340
  self.old_target = new_target
278
341
  self.emit({"type": "energy", "status": new_target})
342
+ self.pp = new_target
279
343
 
280
344
  # Handle fan curve resets
281
345
  if conf["tdp.asus.fan.manual.reset"].to(bool):
@@ -283,16 +347,31 @@ class AsusDriverPlugin(HHDPlugin):
283
347
  for k, v in zip(POINTS, DEFAULT_CURVE):
284
348
  conf[f"tdp.asus.fan.manual.st{k}"] = v
285
349
 
286
- # # Handle fan curve limits
287
- # if conf["tdp.asus.fan.manual.enforce_limits"].to(bool):
288
- # for k, v in zip(POINTS, MIN_CURVE):
289
- # if conf[f"tdp.asus.fan.manual.st{k}"].to(int) < v:
290
- # conf[f"tdp.asus.fan.manual.st{k}"] = v
350
+ manual_fan_curve = conf["tdp.asus.fan.mode"].to(str) == "manual"
351
+
352
+ # Handle fan curve limits by Asus
353
+ # by enforcing minimum values based on power profile
354
+ # which is a proxy of the current platform profile but still
355
+ # a bit of a hack. TODO: Get the exact limits.
356
+ # FIXME: Revisit limits
357
+ # if manual_fan_curve:
358
+ # match self.pp:
359
+ # case "balanced":
360
+ # min_val = 45
361
+ # case "performance":
362
+ # min_val = 60
363
+ # case _: # quiet
364
+ # min_val = 17
365
+
366
+ # for k in POINTS:
367
+ # if conf[f"tdp.asus.fan.manual.st{k}"].to(int) < min_val:
368
+ # conf[f"tdp.asus.fan.manual.st{k}"] = min_val
291
369
 
292
370
  # Check if fan curve has changed
293
371
  # Use debounce logic on these changes
294
- if tdp_set and conf["tdp.asus.fan.mode"].to(str) == "manual":
372
+ if tdp_reset and manual_fan_curve:
295
373
  self.queue_fan = curr + APPLY_DELAY
374
+
296
375
  for i in POINTS:
297
376
  if conf[f"tdp.asus.fan.manual.st{i}"].to(int) != self.old_conf[
298
377
  f"fan.manual.st{i}"
@@ -329,6 +408,7 @@ class AsusDriverPlugin(HHDPlugin):
329
408
  self.queue_fan = None
330
409
 
331
410
  # Save current config
411
+ self.cycle_tdp = conf["tdp.asus.cycle_tdp"].to(bool)
332
412
  self.old_conf = conf["tdp.asus"]
333
413
 
334
414
  if self.startup:
@@ -338,15 +418,35 @@ class AsusDriverPlugin(HHDPlugin):
338
418
  for ev in events:
339
419
  if ev["type"] == "tdp":
340
420
  self.new_tdp = ev["tdp"]
341
- if ev["type"] == "ppd":
342
- # TODO: Replace with power modes after refactor
421
+ elif ev["type"] == "ppd":
343
422
  match ev["status"]:
344
423
  case "power":
345
- self.new_tdp = 8
424
+ self.new_mode = "quiet"
425
+ case "balanced":
426
+ self.new_mode = "balanced"
427
+ case "performance":
428
+ self.new_mode = "performance"
429
+ elif self.cycle_tdp and ev['type'] == "special" and ev['event'] == "xbox_y":
430
+ match self.mode:
431
+ case "quiet":
432
+ self.new_mode = "balanced"
433
+ event = "tdp_cycle_balanced"
346
434
  case "balanced":
347
- self.new_tdp = 15
435
+ self.new_mode = "performance"
436
+ event = "tdp_cycle_performance"
348
437
  case "performance":
349
- self.new_tdp = 25
438
+ self.new_mode = "custom"
439
+ event = "tdp_cycle_custom"
440
+ case "custom":
441
+ self.new_mode = "quiet"
442
+ event = "tdp_cycle_quiet"
443
+ case _:
444
+ self.new_mode = "balanced"
445
+ event = "tdp_cycle_balanced"
446
+
447
+ logger.info(f"Cycling TDP to '{self.new_mode}'")
448
+ if self.emit:
449
+ self.emit({"type": "special", "event": event})
350
450
 
351
451
  def close(self):
352
452
  pass
@@ -4,27 +4,54 @@ tags: [hide-title]
4
4
  hint: >-
5
5
  Uses the interface of Armory Crate to set the TDP of the device.
6
6
  children:
7
- tdp:
8
- type: int
9
- title: TDP
10
- hint: >-
11
- Average TDP Target.
12
-
13
- Sets the values STAMP and Skin Power Limit to it without boost.
14
- With boost, it sets the fast value to 53/30*tdp and the slow value to 43/30*tdp.
15
- Boost is recommended for desktop use.
7
+ tdp_v2:
8
+ type: mode
9
+ title: TDP Mode
10
+ default: balanced
11
+ modes:
12
+ quiet:
13
+ type: container
14
+ title: Silent
15
+ unit:
16
+ balanced:
17
+ type: container
18
+ title: Performance
19
+ unit:
20
+ performance:
21
+ type: container
22
+ title: Turbo
23
+ unit:
24
+ custom:
25
+ type: container
26
+ title: Custom
27
+ unit: → 25/30W
28
+ children:
29
+ tdp:
30
+ type: int
31
+ title: TDP
32
+ hint: >-
33
+ Average TDP Target.
34
+ TDP Boost is recommended for desktop use and does not affect gaming.
16
35
 
17
- min: 5
18
- max: 30
19
- step: 1
20
- default: 15
21
- unit: W
22
- boost:
36
+ min: 0
37
+ max: 30
38
+ step: 1
39
+ default: 30
40
+ unit: W
41
+ boost:
42
+ type: bool
43
+ title: TDP Boost
44
+ default: True
45
+ hint: >-
46
+ Allows the device to temporarily boost by setting appropriate slow and fast TDPs.
47
+
48
+ cycle_tdp:
23
49
  type: bool
24
- title: TDP Boost
25
- default: True
50
+ title: Change TDP with View+Y
26
51
  hint: >-
27
- Allows the device to boost by setting appropriate slow and fast TDPs.
52
+ Allows you to cycle through TDP modes with the View+Y key combination.
53
+ Recommended to use with ROG Swap, as the View button will be muted to games.
54
+ default: False
28
55
 
29
56
  fan:
30
57
  type: mode
@@ -94,8 +121,7 @@ children:
94
121
  title: Fan Curve Limitation
95
122
  type: display
96
123
  default: >-
97
- Asus hardware limits the minimum fan curve depending on TDP.
98
- The minimums are 25%, 50%, and 75% for low, medium, and high TDPs.
124
+ Asus hardware limits the minimum fan curve depending on TDP mode.
99
125
 
100
126
  charge_limit:
101
127
  type: multiple
@@ -110,16 +136,4 @@ children:
110
136
  p90: 90%
111
137
  p95: 95%
112
138
  disabled: Unset
113
- default: disabled
114
-
115
- # disclaimer:
116
- # title: Sleep Bug
117
- # type: display
118
- # tags: [ non-essential ]
119
- # default: >-
120
- # There is an Asus kernel/BIOS bug that will sometimes limit TDP to 10W
121
- # after sleep.
122
- # As the kernel driver is used, this is unfixable from within
123
- # Handheld Daemon (currently investigated).
124
- # As an alternative, use SimpleDeckyTDP with RyzenAdj after
125
- # disabling TDP controls from "Settings".
139
+ default: disabled
@@ -0,0 +1,146 @@
1
+ import os
2
+ import subprocess
3
+ from typing import Literal
4
+ import shutil
5
+
6
+ import time
7
+ import signal
8
+ from hhd.plugins import Context, HHDPlugin, load_relative_yaml
9
+ from hhd.plugins.conf import Config
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class GeneralPowerPlugin(HHDPlugin):
16
+
17
+ def __init__(
18
+ self,
19
+ ) -> None:
20
+ self.name = f"adjustor_general"
21
+ self.priority = 8
22
+ self.log = "gpow"
23
+ self.last_check = None
24
+ self.target = None
25
+ self.old_sched = None
26
+ self.sched_proc = None
27
+ self.ppd_supported = None
28
+
29
+ def settings(self):
30
+ sets = load_relative_yaml("./settings.yml")
31
+
32
+ # PPD
33
+ if self.ppd_supported is None:
34
+ self.ppd_supported = False
35
+ if ppc := shutil.which('powerprofilesctl'):
36
+ try:
37
+ if os.environ.get("HHD_PPD_MASK", None):
38
+ logger.info("Unmasking Power Profiles Daemon in the case it was masked.")
39
+ os.system('systemctl unmask power-profiles-daemon')
40
+ subprocess.run(
41
+ [ppc],
42
+ check=True,
43
+ stdin=subprocess.DEVNULL,
44
+ stdout=subprocess.DEVNULL,
45
+ stderr=subprocess.DEVNULL,
46
+ )
47
+ self.ppd_supported = True
48
+ except Exception as e:
49
+ logger.warning(f"powerprofilectl returned with error:\n{e}")
50
+
51
+ if not self.ppd_supported:
52
+ del sets["children"]["profile"]
53
+
54
+ # SchedExt
55
+ self.avail_scheds = {}
56
+ avail_pretty = {}
57
+ kernel_supports = os.path.isfile("/sys/kernel/sched_ext/state")
58
+ if kernel_supports:
59
+ for sched, pretty in sets["children"]["sched"]["options"].items():
60
+ if sched == "disabled":
61
+ avail_pretty[sched] = pretty
62
+ continue
63
+
64
+ exe = shutil.which(sched)
65
+ if exe:
66
+ self.avail_scheds[sched] = exe
67
+ avail_pretty[sched] = pretty
68
+
69
+ if self.avail_scheds:
70
+ sets["children"]["sched"]["options"] = avail_pretty
71
+ else:
72
+ del sets["children"]["sched"]
73
+
74
+ self.logged_boost = True
75
+ return {
76
+ "tdp": {"general": sets},
77
+ }
78
+
79
+ def update(self, conf: Config):
80
+ # Handle ppd
81
+ if self.ppd_supported:
82
+ curr = time.time()
83
+ new_profile = conf.get("tdp.general.profile", self.target)
84
+ if new_profile != self.target and new_profile and self.target:
85
+ logger.info(f"Setting power profile to '{new_profile}'")
86
+ self.target = new_profile
87
+ try:
88
+ subprocess.run(
89
+ [shutil.which('powerprofilesctl'), "set", new_profile],
90
+ check=True,
91
+ stdout=subprocess.PIPE,
92
+ stderr=subprocess.PIPE,
93
+ )
94
+ except Exception as e:
95
+ self.ppd_supported = False
96
+ logger.warning(f"powerprofilesctl returned with error:\n{e}")
97
+ self.ppd_supported = False
98
+ elif not self.last_check or curr - self.last_check > 2:
99
+ # Update profile every 2 seconds
100
+ self.last_check = curr
101
+ try:
102
+ res = subprocess.run(
103
+ [shutil.which('powerprofilesctl'), "get"],
104
+ check=True,
105
+ stdout=subprocess.PIPE,
106
+ stderr=subprocess.PIPE,
107
+ )
108
+ self.target = res.stdout.decode().strip() # type: ignore
109
+ if self.target != conf["tdp.general.profile"].to(str):
110
+ conf["tdp.general.profile"] = self.target
111
+ except Exception as e:
112
+ self.ppd_supported = False
113
+ logger.warning(f"powerprofilectl returned with error:\n{e}")
114
+ self.ppd_supported = False
115
+
116
+ # Handle sched
117
+ if self.avail_scheds:
118
+ # Check health and print error
119
+ if self.sched_proc and self.sched_proc.poll():
120
+ err = self.sched_proc.poll()
121
+ self.sched_proc = None
122
+ logger.error(
123
+ f"Scheduler from sched_ext '{self.old_sched}' closed with error code: {err}"
124
+ )
125
+
126
+ new_sched = conf.get("tdp.general.sched", "disabled")
127
+ if new_sched != self.old_sched:
128
+ self.close_sched()
129
+ self.old_sched = new_sched
130
+ if new_sched and new_sched != "disabled":
131
+ logger.info(f"Starting sched_ext scheduler '{new_sched}'")
132
+ self.sched_proc = subprocess.Popen(
133
+ self.avail_scheds[new_sched],
134
+ stderr=subprocess.DEVNULL,
135
+ stdout=subprocess.DEVNULL,
136
+ )
137
+
138
+ def close_sched(self):
139
+ if self.sched_proc is not None:
140
+ logger.info(f"Closing sched_ext scheduler '{self.old_sched}'.")
141
+ self.sched_proc.send_signal(signal.SIGINT)
142
+ self.sched_proc.wait()
143
+ self.sched_proc = None
144
+
145
+ def close(self):
146
+ self.close_sched()
@@ -0,0 +1,27 @@
1
+ title: Power
2
+ tags: [hide-title]
3
+ type: container
4
+ children:
5
+ profile:
6
+ type: multiple
7
+ # tags: [ordinal] # Steamdeck has issues with this
8
+ title: Power Profile
9
+ hint: >-
10
+ Allows setting the power profile of the system using Power Profiles Daemon.
11
+ options:
12
+ power-saver: Powersave
13
+ balanced: Balanced
14
+ performance: Performance
15
+ default: balanced
16
+ sched:
17
+ type: multiple
18
+ title: Custom Scheduler
19
+ hint: >-
20
+ Allows attaching a scheduler to the kernel sched_ext.
21
+ Schedulers need to be installed and kernel needs to support sched_ext.
22
+ options:
23
+ disabled: Disabled
24
+ scx_lavd: LAVD
25
+ scx_bpfland: bpfland
26
+ scx_rusty: rusty
27
+ default: disabled
@@ -165,6 +165,7 @@ class LenovoDriverPlugin(HHDPlugin):
165
165
  conf["tdp.lenovo.tdp.mode"] = mode
166
166
  elif new_mode:
167
167
  mode = new_mode
168
+ conf["tdp.lenovo.tdp.mode"] = mode
168
169
  else:
169
170
  mode = conf["tdp.lenovo.tdp.mode"].to(str)
170
171
  if mode is not None and mode != self.old_conf["tdp.mode"].to(str):
@@ -10,16 +10,20 @@ children:
10
10
  modes:
11
11
  quiet:
12
12
  type: container
13
- title: Quiet (8W)
13
+ title: Quiet
14
+ unit: 8W
14
15
  balanced:
15
16
  type: container
16
- title: Balanced (15W)
17
+ title: Balanced
18
+ unit: 15W
17
19
  performance:
18
20
  type: container
19
- title: Performance (20W)
21
+ title: Performance
22
+ unit: 20W
20
23
  custom:
21
24
  type: container
22
- title: Custom (up to 25-30W)
25
+ title: Custom
26
+ unit: → 25/30W
23
27
  children:
24
28
  tdp:
25
29
  type: int
@@ -46,6 +50,12 @@ children:
46
50
  hint: >-
47
51
  Allows the device to boost by setting appropriate slow and fast TDPs.
48
52
 
53
+ cycle_info:
54
+ type: display
55
+ title: " "
56
+ tags: [non-essential]
57
+ default: "Legion L + Y changes TDP Mode"
58
+
49
59
  ffss:
50
60
  type: bool
51
61
  title: Set Fan to Full Speed
@@ -256,7 +256,7 @@ def autodetect(existing: Sequence[HHDPlugin]) -> Sequence[HHDPlugin]:
256
256
  default_tdp = 15
257
257
  max_tdp = 30
258
258
 
259
- if prod == "83E1":
259
+ if prod == "83E1" and not bool(os.environ.get("HHD_ADJ_ALLY")):
260
260
  drivers.append(LenovoDriverPlugin())
261
261
  drivers_matched = True
262
262
  use_acpi_call = True
@@ -265,8 +265,9 @@ def autodetect(existing: Sequence[HHDPlugin]) -> Sequence[HHDPlugin]:
265
265
  "ROG Ally RC71L" in prod
266
266
  or "ROG Ally X RC72L" in prod
267
267
  or bool(os.environ.get("HHD_ADJ_DEBUG"))
268
+ or bool(os.environ.get("HHD_ADJ_ALLY"))
268
269
  ):
269
- drivers.append(AsusDriverPlugin())
270
+ drivers.append(AsusDriverPlugin("RC72L" in prod))
270
271
  drivers_matched = True
271
272
 
272
273
  if os.environ.get("HHD_ADJ_DEBUG") or os.environ.get("HHD_ENABLE_SMU"):
@@ -322,8 +323,10 @@ def autodetect(existing: Sequence[HHDPlugin]) -> Sequence[HHDPlugin]:
322
323
  break
323
324
 
324
325
  if not drivers:
325
- logger.info(f"No tdp drivers found for this device, exiting Adjustor.")
326
- return []
326
+ from .drivers.general import GeneralPowerPlugin
327
+
328
+ logger.info(f"No tdp drivers found for this device, using generic plugin.")
329
+ return [GeneralPowerPlugin()]
327
330
 
328
331
  return [
329
332
  *drivers,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: adjustor
3
- Version: 3.3.1
3
+ Version: 3.4.0
4
4
  Summary: Adjustor, a userspace program for managing the TDP of handheld devices.
5
5
  Author-email: Kapenekakis Antheas <pypi@antheas.dev>
6
6
  Project-URL: Homepage, https://github.com/hhd-dev/adjustor
@@ -38,7 +38,7 @@ except intel handhelds and older prior to 6XXX AMD handhelds.
38
38
  ## TDP Control
39
39
  For the ROG Ally, Ally X and Legion Go that have an ACPI/EC implementation for
40
40
  bios and fan curves,
41
- Adjustor uses the manufactuer functions for setting TDP.
41
+ Adjustor uses the manufacturer functions for setting TDP.
42
42
  For the Allys, the asus-wmi kernel driver is used to set the tdp and manage the
43
43
  fan curves.
44
44
  For the Go, Lenovo's WMI methods are called through `acpi_call`, which will hopefully
@@ -53,14 +53,14 @@ For more, see [AMD TDP Control Details](#amd-tdp).
53
53
  In all cases, there are checks to ensure that the TDP is within the safe range
54
54
  of the processors.
55
55
 
56
- ## Energy Management
57
- Adjustor can also manage the energy profile of the processor, by setting EPP
56
+ ## Energy Management in Handhelds
57
+ Adjustor can also manage the energy profile of the processor in handhelds, by setting EPP
58
58
  and proper frequency values.
59
59
  After we transitioned people away from Decky plugins (which had some governor controls)
60
60
  to using Handheld Daemon for TDP, we found that Power Profiles Daemon (PPD)
61
61
  would use aggressive CPU values.
62
62
  These values are optimized for devices that have a dedicated power budget for the CPU
63
- (e.g., laptops, desktops), which caused issues with handhelds.
63
+ (e.g., laptops, desktops), which causes undesirable behavior handhelds.
64
64
 
65
65
  For example, the balanced PPD profile would set EPP to balance_performance and
66
66
  enable CPU boost, which would increase the draw of the CPU during gaming by 2W
@@ -91,6 +91,48 @@ TDP range instead of CPU values (which is the user's expectation).
91
91
  Of course, depending on TDP and user preference, the CPU governor values will be set
92
92
  accordingly.
93
93
 
94
+ ## Energy Management in other computers
95
+ As we design Handheld Daemon to be enabled in more Deck style devices (e.g., HTPCs),
96
+ these devices have different power requirements and processors (e.g., Intel), which
97
+ are better managed with Power Profiles Daemon.
98
+ It is the aim of the Handheld Daemon project to become a general Deck style session
99
+ manager for anything gamescope related, with useful features for all devices.
100
+
101
+ In these cases, starting with 3.4, for devices that are not in the CPU/device
102
+ whitelist (includes only AMD U series APUs and handhelds), Adjustor contains a
103
+ general energy management plugin that allows for switching the PPD power profile
104
+ from game mode.
105
+ In addition, it supports [sched_ext schedulers](#sched_ext).
106
+
107
+ This means that for general devices, Handheld Daemon uses PPD, and for handhelds,
108
+ Handheld Daemon becomes PPD.
109
+ This can be confusing for distribution maintainers and users, as they can and
110
+ should install both (e.g., Adjustor uses the Power Profile Daemon polkits).
111
+
112
+ In any case, Adjustor will never break/conflict with PPD and contains helpful
113
+ messages about disabling PPD in case the optimized handheld plugin is loaded.
114
+ For distribution maintainers that ship both and want Handheld Daemon to work
115
+ without user intervention, the environment variable `HHD_PPD_MASK` is provided.
116
+ If and only if it is set e.g., by using a systemd service extension, Handheld Daemon
117
+ will mask and disable PPD if optimized energy management is supported and enabled (e.g., for handhelds).
118
+ If the general plugin is loaded, it will unmask PPD during startup.
119
+ This means that Power Management will work properly for all devices without manual
120
+ intervention or whitelisting by distribution maintainers.
121
+
122
+ ## Sched_ext<a name="sched-ext"></a>
123
+ Starting with version 3.3, Adjustor can attach sched_ext schedulers to the
124
+ kernel if those are supported and installed.
125
+ Adjustor manages the lifetime of the scheduler, including launching and attaching
126
+ it, without using a systemd service, and is fully responsive to quirk scheduler
127
+ switches.
128
+
129
+ Schedulers are whitelisted in a case by case basis, with LAVD, rusty, and bpfland
130
+ being supported in the current version (the `scx_` namespace is crowded with e.g.,
131
+ test schedulers).
132
+ Of course, only installed schedulers are shown if and only if the kernel supports
133
+ them.
134
+ Get in touch to add your favorite scheduler, as it is a single line change.
135
+
94
136
  ## AMD TDP Control Details<a name="amd-tdp"></a>
95
137
  Adjustor controls TDP through the Dynamic Power and Thermal Configuration Interface
96
138
  of AMD, which exposes a superset of the parameters that can be currently found in
@@ -99,7 +141,7 @@ This vendor interface is part of the ACPI ASL library, and provided through the
99
141
  ALIB method 0x0C.
100
142
  The underlying implementation of the interface is SMU calls.
101
143
  This means that as long as the kernel module `acpi_call` is loaded, Adjustor
102
- can control TDP in an equivalent way to [RyzenAdj](https://github.dev/FlyGoat/RyzenAdj/).
144
+ can control TDP in a similar way to [RyzenAdj](https://github.dev/FlyGoat/RyzenAdj/).
103
145
 
104
146
  The ABI of this vendor function (as it is provided to manufacturers) can be
105
147
  considered mostly stable, so little work is needed between subsequent
@@ -109,8 +151,9 @@ Of course, support for processors is only added after the ACPI bindings have
109
151
  been reviewed, to avoid surprises.
110
152
  Both the Ally and Legion Go use this function, in the exact same way, so setting
111
153
  TDP with it is very stable, and we have had no reported crashes.
112
- It should not be used (and is not used) with those devices, however, as the
113
- manufacturer functions will interfere.
154
+ It can not be used and is not used with those devices, however, as the
155
+ manufacturer functions would interfere and provide a better user experience,
156
+ such as setting appropriate fan curves and changing the power light color on the Legion Go.
114
157
 
115
158
  Unfortunately for devices that do have an ACPI/EC implementation for TDP, there
116
159
  is no official way of setting TDP on demand, either on Linux or Windows, with
@@ -27,6 +27,8 @@ src/adjustor/drivers/amd/ppd.py
27
27
  src/adjustor/drivers/amd/settings.yml
28
28
  src/adjustor/drivers/asus/__init__.py
29
29
  src/adjustor/drivers/asus/settings.yml
30
+ src/adjustor/drivers/general/__init__.py
31
+ src/adjustor/drivers/general/settings.yml
30
32
  src/adjustor/drivers/lenovo/__init__.py
31
33
  src/adjustor/drivers/lenovo/settings.yml
32
34
  src/adjustor/drivers/smu/__init__.py
File without changes
File without changes
File without changes
File without changes