zenmaster 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zenmaster/__init__.py +5 -0
- zenmaster/__main__.py +3 -0
- zenmaster/apply.py +58 -0
- zenmaster/assets/AMD/PawnIO/AMDFamily0F.bin +0 -0
- zenmaster/assets/AMD/PawnIO/AMDFamily10.bin +0 -0
- zenmaster/assets/AMD/PawnIO/AMDFamily17.bin +0 -0
- zenmaster/assets/AMD/PawnIO/AMDReset.bin +0 -0
- zenmaster/assets/AMD/PawnIO/RyzenSMU.bin +0 -0
- zenmaster/cli.py +397 -0
- zenmaster/hardware.py +145 -0
- zenmaster/linux.py +241 -0
- zenmaster/runner.py +466 -0
- zenmaster/smu.py +66 -0
- zenmaster/table.py +151 -0
- zenmaster/windows.py +320 -0
- zenmaster-0.1.0.dist-info/METADATA +261 -0
- zenmaster-0.1.0.dist-info/RECORD +20 -0
- zenmaster-0.1.0.dist-info/WHEEL +5 -0
- zenmaster-0.1.0.dist-info/entry_points.txt +2 -0
- zenmaster-0.1.0.dist-info/top_level.txt +1 -0
zenmaster/__init__.py
ADDED
zenmaster/__main__.py
ADDED
zenmaster/apply.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import shlex
|
|
3
|
+
from zenmaster import runner, smu
|
|
4
|
+
|
|
5
|
+
_SKIN_ARGS = {"apu-skin-temp", "dgpu-skin-temp", "skin-temp-limit"}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _skin_scale(arg_name: str, value: int) -> int:
|
|
9
|
+
return value * 256 if arg_name in _SKIN_ARGS else value
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def apply(args_str: str, family: str) -> tuple[list[dict], bool]:
|
|
13
|
+
tokens = shlex.split(args_str) if args_str.strip() else []
|
|
14
|
+
results: list[dict] = []
|
|
15
|
+
had_rejection = False
|
|
16
|
+
|
|
17
|
+
for token in tokens:
|
|
18
|
+
token = token.lstrip("-")
|
|
19
|
+
if not token:
|
|
20
|
+
continue
|
|
21
|
+
|
|
22
|
+
if "=" in token:
|
|
23
|
+
name, _, val_str = token.partition("=")
|
|
24
|
+
try:
|
|
25
|
+
value = int(val_str, 0)
|
|
26
|
+
except ValueError:
|
|
27
|
+
results.append({"arg": name, "value": 0, "mailbox": "", "opcode": 0, "status": 0,
|
|
28
|
+
"error": f"invalid value '{val_str}'"})
|
|
29
|
+
continue
|
|
30
|
+
else:
|
|
31
|
+
name, value = token, 0
|
|
32
|
+
|
|
33
|
+
matches = runner.lookup(family, name)
|
|
34
|
+
if not matches:
|
|
35
|
+
results.append({"arg": name, "value": value, "mailbox": "", "opcode": 0, "status": 0,
|
|
36
|
+
"error": f"not supported on {family}"})
|
|
37
|
+
had_rejection = True
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
smu_val = _skin_scale(name, value)
|
|
41
|
+
smu_val = max(0, min(0xFFFFFFFF, smu_val))
|
|
42
|
+
|
|
43
|
+
any_ok = False
|
|
44
|
+
for is_mp1, op in matches:
|
|
45
|
+
if is_mp1:
|
|
46
|
+
status = smu.send_mp1(family, op, smu_val)
|
|
47
|
+
mailbox = "MP1"
|
|
48
|
+
else:
|
|
49
|
+
status = smu.send_rsmu(family, op, smu_val)
|
|
50
|
+
mailbox = "RSMU"
|
|
51
|
+
if status == smu.SMU_OK:
|
|
52
|
+
any_ok = True
|
|
53
|
+
results.append({"arg": name, "value": value, "mailbox": mailbox, "opcode": op, "status": status})
|
|
54
|
+
|
|
55
|
+
if not any_ok:
|
|
56
|
+
had_rejection = True
|
|
57
|
+
|
|
58
|
+
return results, had_rejection
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
zenmaster/cli.py
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import struct
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from zenmaster import __version__, runner, smu
|
|
11
|
+
from zenmaster.apply import apply
|
|
12
|
+
from zenmaster.hardware import CpuInfo, detect
|
|
13
|
+
from zenmaster.table import read_table
|
|
14
|
+
|
|
15
|
+
_CATEGORIES: dict[str, list[str]] = {
|
|
16
|
+
"Power limits": ["stapm-limit", "fast-limit", "slow-limit", "ppt-limit",
|
|
17
|
+
"apu-slow-limit", "stapm-time", "slow-time"],
|
|
18
|
+
"Thermal": ["tctl-temp", "chtc-temp", "apu-skin-temp", "dgpu-skin-temp",
|
|
19
|
+
"skin-temp-limit"],
|
|
20
|
+
"VRM & Currents": ["vrm-current", "vrmmax-current", "vrmsoc-current",
|
|
21
|
+
"vrmsocmax-current", "vrmgfx-current", "vrmgfxmax-current",
|
|
22
|
+
"psi0-current", "psi0soc-current", "psi3cpu-current",
|
|
23
|
+
"psi3gfx-current", "prochot-deassertion-ramp"],
|
|
24
|
+
"Clocks": ["max-cpuclk", "min-cpuclk", "max-gfxclk", "min-gfxclk",
|
|
25
|
+
"gfx-clk", "max-socclk-frequency", "min-socclk-frequency",
|
|
26
|
+
"max-fclk-frequency", "min-fclk-frequency",
|
|
27
|
+
"max-vcn", "min-vcn", "max-lclk", "min-lclk",
|
|
28
|
+
"oc-clk", "per-core-oc-clk", "set-boost-limit-frequency",
|
|
29
|
+
"set-vmin-freq"],
|
|
30
|
+
"Overclocking": ["enable-oc", "disable-oc", "oc-volt", "pbo-scalar",
|
|
31
|
+
"set-coall", "set-coper", "set-cogfx",
|
|
32
|
+
"set-gpuclockoverdrive-byvid"],
|
|
33
|
+
"Power states": ["power-saving", "max-performance",
|
|
34
|
+
"enable-feature", "disable-feature"],
|
|
35
|
+
"Query / get": ["get-pbo-scalar", "get-sustained-power-and-thm-limit",
|
|
36
|
+
"get-overclocking-support", "get-max-cpu-clk",
|
|
37
|
+
"get-min-gfx-clk", "get-max-gfx-clk", "get-curr-gfx-clk",
|
|
38
|
+
"get-pbo-fused-power-limit", "get-pbo-fused-slow-limit",
|
|
39
|
+
"get-pbo-fused-fast-limit", "get-pbo-fused-apu-slow-limit",
|
|
40
|
+
"get-pbo-fused-vrmtdc-limit", "get-pbo-fused-vrmsoc-current",
|
|
41
|
+
"get-pbo-fused-tctl-temp", "get-coper-options",
|
|
42
|
+
"get-cogfx-options", "disable-prochot",
|
|
43
|
+
"set-fll-btc-enable", "set-vddoff-vid",
|
|
44
|
+
"set-ulv-vid", "setcpu-freqto-ramstate",
|
|
45
|
+
"stopcpu-freqto-ramstate"],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_ARG_UNITS: dict[str, str] = {
|
|
49
|
+
"stapm-limit": "mW", "fast-limit": "mW", "slow-limit": "mW",
|
|
50
|
+
"ppt-limit": "mW", "apu-slow-limit": "mW",
|
|
51
|
+
"stapm-time": "s", "slow-time": "s",
|
|
52
|
+
"tctl-temp": "°C", "chtc-temp": "°C", "apu-skin-temp": "°C",
|
|
53
|
+
"dgpu-skin-temp": "°C", "skin-temp-limit": "°C",
|
|
54
|
+
"vrm-current": "mA", "vrmmax-current": "mA", "vrmsoc-current": "mA",
|
|
55
|
+
"vrmsocmax-current": "mA", "vrmgfx-current": "mA", "vrmgfxmax-current": "mA",
|
|
56
|
+
"psi0-current": "mA", "psi0soc-current": "mA",
|
|
57
|
+
"psi3cpu-current": "mA", "psi3gfx-current": "mA",
|
|
58
|
+
"oc-clk": "MHz", "per-core-oc-clk": "MHz", "max-cpuclk": "MHz",
|
|
59
|
+
"min-cpuclk": "MHz", "max-gfxclk": "MHz", "min-gfxclk": "MHz",
|
|
60
|
+
"gfx-clk": "MHz", "max-socclk-frequency": "MHz", "min-socclk-frequency": "MHz",
|
|
61
|
+
"max-fclk-frequency": "MHz", "min-fclk-frequency": "MHz",
|
|
62
|
+
"oc-volt": "mV",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_ARG_DESCS: dict[str, str] = {
|
|
66
|
+
"stapm-limit": "Sustained Power Limit — STAPM LIMIT",
|
|
67
|
+
"fast-limit": "Actual Power Limit — PPT LIMIT FAST",
|
|
68
|
+
"slow-limit": "Average Power Limit — PPT LIMIT SLOW",
|
|
69
|
+
"ppt-limit": "Platform Package Tracking power limit",
|
|
70
|
+
"apu-slow-limit": "APU PPT Slow limit for A+A dGPU platform — PPT LIMIT APU",
|
|
71
|
+
"stapm-time": "STAPM constant time",
|
|
72
|
+
"slow-time": "Slow PPT constant time",
|
|
73
|
+
"tctl-temp": "Tctl Temperature Limit — THM LIMIT CORE",
|
|
74
|
+
"chtc-temp": "CHTC Temperature Limit",
|
|
75
|
+
"apu-skin-temp": "APU Skin Temperature Limit — STT LIMIT APU",
|
|
76
|
+
"dgpu-skin-temp": "dGPU Skin Temperature Limit — STT LIMIT dGPU",
|
|
77
|
+
"skin-temp-limit": "Skin Temperature Power Limit",
|
|
78
|
+
"vrm-current": "VRM Current Limit — TDC LIMIT VDD",
|
|
79
|
+
"vrmmax-current": "VRM Maximum Current Limit — EDC LIMIT VDD",
|
|
80
|
+
"vrmsoc-current": "VRM SoC Current Limit — TDC LIMIT SOC",
|
|
81
|
+
"vrmsocmax-current": "VRM SoC Maximum Current Limit — EDC LIMIT SOC",
|
|
82
|
+
"vrmgfx-current": "VRM GFX Current Limit — TDC LIMIT GFX",
|
|
83
|
+
"vrmgfxmax-current": "VRM GFX Maximum Current Limit — EDC LIMIT GFX",
|
|
84
|
+
"psi0-current": "PSI0 VDD Current Limit",
|
|
85
|
+
"psi0soc-current": "PSI0 SoC Current Limit",
|
|
86
|
+
"psi3cpu-current": "PSI3 CPU Current Limit",
|
|
87
|
+
"psi3gfx-current": "PSI3 GFX Current Limit",
|
|
88
|
+
"prochot-deassertion-ramp": "Ramp time after PROCHOT deasserts; higher = tighter post-throttle limits",
|
|
89
|
+
"max-cpuclk": "Maximum CPU clock frequency",
|
|
90
|
+
"min-cpuclk": "Minimum CPU clock frequency",
|
|
91
|
+
"max-gfxclk": "Maximum GFX clock frequency",
|
|
92
|
+
"min-gfxclk": "Minimum GFX clock frequency",
|
|
93
|
+
"gfx-clk": "Forced GFX clock speed (Renoir only)",
|
|
94
|
+
"max-socclk-frequency": "Maximum SoC clock frequency",
|
|
95
|
+
"min-socclk-frequency": "Minimum SoC clock frequency",
|
|
96
|
+
"max-fclk-frequency": "Maximum Infinity Fabric (CPU↔GPU) frequency",
|
|
97
|
+
"min-fclk-frequency": "Minimum Infinity Fabric (CPU↔GPU) frequency",
|
|
98
|
+
"max-vcn": "Maximum Video Core Next (VCE) frequency",
|
|
99
|
+
"min-vcn": "Minimum Video Core Next (VCE) frequency",
|
|
100
|
+
"max-lclk": "Maximum Data Launch Clock frequency",
|
|
101
|
+
"min-lclk": "Minimum Data Launch Clock frequency",
|
|
102
|
+
"oc-clk": "Forced all-core clock speed (Renoir and up)",
|
|
103
|
+
"per-core-oc-clk": "Forced per-core clock speed (Renoir and up)",
|
|
104
|
+
"set-boost-limit-frequency": "Boost frequency ceiling",
|
|
105
|
+
"set-vmin-freq": "Minimum voltage frequency floor",
|
|
106
|
+
"enable-oc": "Enable overclocking mode (Renoir and up)",
|
|
107
|
+
"disable-oc": "Disable overclocking mode (Renoir and up)",
|
|
108
|
+
"oc-volt": "Forced core VID: (1.55 − target_V) / 0.00625 (Renoir and up)",
|
|
109
|
+
"pbo-scalar": "Precision Boost Overdrive scalar",
|
|
110
|
+
"set-coall": "All-core Curve Optimiser offset",
|
|
111
|
+
"set-coper": "Per-core Curve Optimiser offset",
|
|
112
|
+
"set-cogfx": "iGPU Curve Optimiser offset",
|
|
113
|
+
"set-gpuclockoverdrive-byvid": "Set GPU clock overdrive by VID",
|
|
114
|
+
"power-saving": "Apply power-saving profile (AC-unplugged behavior)",
|
|
115
|
+
"max-performance": "Apply max-performance profile (AC-plugged behavior)",
|
|
116
|
+
"enable-feature": "Enable a CPU/SMU feature by feature ID",
|
|
117
|
+
"disable-feature": "Disable a CPU/SMU feature by feature ID",
|
|
118
|
+
"get-pbo-scalar": "Query current PBO scalar value",
|
|
119
|
+
"get-sustained-power-and-thm-limit":"Query fused sustained power and thermal limit",
|
|
120
|
+
"get-overclocking-support": "Query overclocking support flags",
|
|
121
|
+
"get-max-cpu-clk": "Query maximum CPU clock limit",
|
|
122
|
+
"get-min-gfx-clk": "Query minimum GFX clock",
|
|
123
|
+
"get-max-gfx-clk": "Query maximum GFX clock",
|
|
124
|
+
"get-curr-gfx-clk": "Query current GFX clock",
|
|
125
|
+
"get-pbo-fused-power-limit": "Query PBO fused sustained power limit",
|
|
126
|
+
"get-pbo-fused-slow-limit": "Query PBO fused slow power limit",
|
|
127
|
+
"get-pbo-fused-fast-limit": "Query PBO fused fast power limit",
|
|
128
|
+
"get-pbo-fused-apu-slow-limit": "Query PBO fused APU slow power limit",
|
|
129
|
+
"get-pbo-fused-vrmtdc-limit": "Query PBO fused VRM TDC current limit",
|
|
130
|
+
"get-pbo-fused-vrmsoc-current": "Query PBO fused VRM SoC current",
|
|
131
|
+
"get-pbo-fused-tctl-temp": "Query PBO fused Tctl temperature",
|
|
132
|
+
"get-coper-options": "Query available per-core Curve Optimiser options",
|
|
133
|
+
"get-cogfx-options": "Query available iGPU Curve Optimiser options",
|
|
134
|
+
"disable-prochot": "Disable PROCHOT thermal throttle signal",
|
|
135
|
+
"set-fll-btc-enable": "Enable FLL BTC mode",
|
|
136
|
+
"set-vddoff-vid": "Set VDD-off VID",
|
|
137
|
+
"set-ulv-vid": "Set Ultra-Low Voltage VID",
|
|
138
|
+
"setcpu-freqto-ramstate": "Lock CPU frequency to RAM state",
|
|
139
|
+
"stopcpu-freqto-ramstate": "Release CPU frequency from RAM state lock",
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _is_root() -> bool:
|
|
144
|
+
if platform.system() == "Windows":
|
|
145
|
+
try:
|
|
146
|
+
import ctypes
|
|
147
|
+
import ctypes.wintypes
|
|
148
|
+
k32 = ctypes.windll.kernel32
|
|
149
|
+
adv = ctypes.windll.advapi32
|
|
150
|
+
tok = ctypes.c_void_p()
|
|
151
|
+
if not adv.OpenProcessToken(k32.GetCurrentProcess(), 0x0008, ctypes.byref(tok)):
|
|
152
|
+
return bool(ctypes.windll.shell32.IsUserAnAdmin())
|
|
153
|
+
elevation = ctypes.c_uint(0)
|
|
154
|
+
size = ctypes.c_uint(ctypes.sizeof(elevation))
|
|
155
|
+
ok = adv.GetTokenInformation(tok, 20, ctypes.byref(elevation), size, ctypes.byref(size))
|
|
156
|
+
k32.CloseHandle(tok)
|
|
157
|
+
return bool(elevation.value) if ok else False
|
|
158
|
+
except Exception:
|
|
159
|
+
return False
|
|
160
|
+
return os.geteuid() == 0
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _show_help(info: CpuInfo) -> None:
|
|
164
|
+
supported = set(runner.get_supported_args(info.family))
|
|
165
|
+
socket = runner.get_socket(info.family) or "unknown"
|
|
166
|
+
print(f"ZenMaster — Ryzen Power Management Tool")
|
|
167
|
+
print()
|
|
168
|
+
print("Usage: zenmaster [OPTIONS] [TUNING ARGS...]")
|
|
169
|
+
print()
|
|
170
|
+
print("Options:")
|
|
171
|
+
print(" --info Show CPU and backend info")
|
|
172
|
+
print(" --json Machine-readable JSON output")
|
|
173
|
+
print(" --reapply=N Re-apply settings every N seconds (foreground)")
|
|
174
|
+
if smu.pm_table_supported(info.family):
|
|
175
|
+
print(" --table Show labeled power metrics table")
|
|
176
|
+
print(" --dump-table Dump raw PM table floats with hex offsets")
|
|
177
|
+
print()
|
|
178
|
+
|
|
179
|
+
if not supported:
|
|
180
|
+
print(f"No SMU support found for family '{info.family}'.")
|
|
181
|
+
else:
|
|
182
|
+
print(f"Tuning arguments for {info.name} ({info.family}, {socket}):")
|
|
183
|
+
shown: set[str] = set()
|
|
184
|
+
for category, args in _CATEGORIES.items():
|
|
185
|
+
in_category = [a for a in args if a in supported and a not in shown]
|
|
186
|
+
if not in_category:
|
|
187
|
+
continue
|
|
188
|
+
print(f"\n {category}:")
|
|
189
|
+
for arg in in_category:
|
|
190
|
+
unit = _ARG_UNITS.get(arg, "")
|
|
191
|
+
unit_str = f"<{unit}>" if unit else "<value>"
|
|
192
|
+
left = f"--{arg}={unit_str}"
|
|
193
|
+
desc = _ARG_DESCS.get(arg, "Unknown command")
|
|
194
|
+
print(f" {left:<38} {desc}")
|
|
195
|
+
shown.add(arg)
|
|
196
|
+
|
|
197
|
+
uncategorised = [a for a in runner.get_supported_args(info.family) if a not in shown]
|
|
198
|
+
if uncategorised:
|
|
199
|
+
print("\n Other:")
|
|
200
|
+
for arg in uncategorised:
|
|
201
|
+
left = f"--{arg}=<value>"
|
|
202
|
+
desc = _ARG_DESCS.get(arg, "Unknown command")
|
|
203
|
+
print(f" {left:<38} {desc}")
|
|
204
|
+
|
|
205
|
+
print()
|
|
206
|
+
print(f"WARNING: Use at your own risk!")
|
|
207
|
+
print(f"Version: {__version__} | By HorizonUnix | GPL-3.0")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _show_info(info: CpuInfo, backend: str | None, json_out: bool) -> None:
|
|
211
|
+
socket = runner.get_socket(info.family) or "unknown"
|
|
212
|
+
if json_out:
|
|
213
|
+
print(json.dumps({
|
|
214
|
+
"name": info.name,
|
|
215
|
+
"family": info.family,
|
|
216
|
+
"arch": info.arch,
|
|
217
|
+
"type": info.type,
|
|
218
|
+
"socket": socket,
|
|
219
|
+
"backend": backend,
|
|
220
|
+
"cpu_family_int": info.cpu_family_int,
|
|
221
|
+
"cpu_model_int": info.cpu_model_int,
|
|
222
|
+
}, indent=2))
|
|
223
|
+
else:
|
|
224
|
+
print(f"Name : {info.name}")
|
|
225
|
+
print(f"Family : {info.family} ({info.arch})")
|
|
226
|
+
print(f"Type : {info.type}")
|
|
227
|
+
print(f"Socket : {socket}")
|
|
228
|
+
print(f"Backend: {backend or 'not initialised'}")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _format_results(results: list[dict], info: CpuInfo, backend: str | None,
|
|
232
|
+
json_out: bool, rejected: bool) -> str:
|
|
233
|
+
if json_out:
|
|
234
|
+
socket = runner.get_socket(info.family) or "unknown"
|
|
235
|
+
out = {
|
|
236
|
+
"cpu": info.name,
|
|
237
|
+
"family": info.family,
|
|
238
|
+
"socket": socket,
|
|
239
|
+
"backend": backend,
|
|
240
|
+
"results": [
|
|
241
|
+
{
|
|
242
|
+
"arg": r["arg"],
|
|
243
|
+
"value": r["value"],
|
|
244
|
+
"mailbox": r.get("mailbox", ""),
|
|
245
|
+
"opcode": f"0x{r['opcode']:02X}" if r.get("opcode") else "",
|
|
246
|
+
"status": (smu.status_name(r["status"]) if r.get("status")
|
|
247
|
+
else r.get("error", "unsupported")),
|
|
248
|
+
}
|
|
249
|
+
for r in results
|
|
250
|
+
],
|
|
251
|
+
"rejected": rejected,
|
|
252
|
+
}
|
|
253
|
+
return json.dumps(out, indent=2)
|
|
254
|
+
else:
|
|
255
|
+
lines = []
|
|
256
|
+
for r in results:
|
|
257
|
+
if "error" in r:
|
|
258
|
+
lines.append(f"{r['arg']} -> {r['error']}")
|
|
259
|
+
else:
|
|
260
|
+
status_str = smu.status_name(r["status"])
|
|
261
|
+
lines.append(
|
|
262
|
+
f"{r['arg']} [{r['mailbox']} 0x{r['opcode']:02X}] = {r['value']} -> {status_str}"
|
|
263
|
+
)
|
|
264
|
+
return "\n".join(lines)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _require_pm_table(json_out: bool, family: str = "") -> bytes:
|
|
268
|
+
if not smu.pm_table_supported(family):
|
|
269
|
+
msg = "PM table not available on this platform/family"
|
|
270
|
+
if json_out:
|
|
271
|
+
print(json.dumps({"error": msg}))
|
|
272
|
+
else:
|
|
273
|
+
print(f"ZenMaster: {msg}", file=sys.stderr)
|
|
274
|
+
sys.exit(1)
|
|
275
|
+
data = smu.read_pm_table(family)
|
|
276
|
+
if not data:
|
|
277
|
+
msg = "Failed to read PM table"
|
|
278
|
+
if json_out:
|
|
279
|
+
print(json.dumps({"error": msg}))
|
|
280
|
+
else:
|
|
281
|
+
print(f"ZenMaster: {msg}", file=sys.stderr)
|
|
282
|
+
sys.exit(1)
|
|
283
|
+
return data
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _show_table(json_out: bool, family: str = "") -> None:
|
|
287
|
+
data = _require_pm_table(json_out, family)
|
|
288
|
+
ver = smu.read_pm_table_version(family)
|
|
289
|
+
rows = read_table(data, ver)
|
|
290
|
+
|
|
291
|
+
if json_out:
|
|
292
|
+
print(json.dumps({
|
|
293
|
+
"pm_table_version": f"0x{ver:08X}",
|
|
294
|
+
"fields": [{"name": label, "value": val, "flag": flag}
|
|
295
|
+
for label, val, flag in rows],
|
|
296
|
+
}, indent=2))
|
|
297
|
+
else:
|
|
298
|
+
print(f"PM Table Version: 0x{ver:08X}")
|
|
299
|
+
fmt = "| {:<21} | {:>9.3f} | {:<20} |"
|
|
300
|
+
sep = "+" + "-" * 23 + "+" + "-" * 11 + "+" + "-" * 22 + "+"
|
|
301
|
+
print(sep)
|
|
302
|
+
for label, val, flag in rows:
|
|
303
|
+
print(fmt.format(label, val, flag))
|
|
304
|
+
print(sep)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _dump_pm_table(json_out: bool, family: str = "") -> None:
|
|
308
|
+
data = _require_pm_table(json_out, family)
|
|
309
|
+
count = len(data) // 4
|
|
310
|
+
values = list(struct.unpack(f"<{count}f", data[:count * 4]))
|
|
311
|
+
|
|
312
|
+
if json_out:
|
|
313
|
+
print(json.dumps({"pm_table": values}, indent=2))
|
|
314
|
+
else:
|
|
315
|
+
for i, v in enumerate(values):
|
|
316
|
+
print(f"| 0x{i*4:04X} | {v:9.3f} |")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def main() -> None:
|
|
320
|
+
argv = sys.argv[1:]
|
|
321
|
+
|
|
322
|
+
if not argv or "--help" in argv or "-h" in argv:
|
|
323
|
+
try:
|
|
324
|
+
info = detect()
|
|
325
|
+
except Exception:
|
|
326
|
+
info = CpuInfo("Unknown", "Unknown", "Unknown", "Unknown", 0, 0)
|
|
327
|
+
_show_help(info)
|
|
328
|
+
sys.exit(0)
|
|
329
|
+
|
|
330
|
+
p = argparse.ArgumentParser(add_help=False)
|
|
331
|
+
p.add_argument("--info", action="store_true")
|
|
332
|
+
p.add_argument("--json", action="store_true", dest="json_out")
|
|
333
|
+
p.add_argument("--reapply", type=int, default=0, metavar="SECONDS")
|
|
334
|
+
p.add_argument("--dump-table", action="store_true", dest="dump_table")
|
|
335
|
+
p.add_argument("--table", action="store_true")
|
|
336
|
+
flags, rest = p.parse_known_args(argv)
|
|
337
|
+
|
|
338
|
+
info = detect()
|
|
339
|
+
|
|
340
|
+
if info.type not in ("Amd_Apu", "Amd_Desktop_Cpu"):
|
|
341
|
+
print(f"ZenMaster: unsupported CPU '{info.name}' (only AMD Ryzen supported)", file=sys.stderr)
|
|
342
|
+
sys.exit(1)
|
|
343
|
+
|
|
344
|
+
backend: str | None = None
|
|
345
|
+
|
|
346
|
+
if flags.info:
|
|
347
|
+
try:
|
|
348
|
+
backend = smu.init()
|
|
349
|
+
except RuntimeError as e:
|
|
350
|
+
if not flags.json_out:
|
|
351
|
+
print(f"ZenMaster: backend unavailable: {e}", file=sys.stderr)
|
|
352
|
+
_show_info(info, backend, flags.json_out)
|
|
353
|
+
if not rest and not flags.dump_table and not flags.table:
|
|
354
|
+
sys.exit(0)
|
|
355
|
+
|
|
356
|
+
if (flags.table or flags.dump_table or rest) and backend is None:
|
|
357
|
+
if not _is_root():
|
|
358
|
+
print("ZenMaster: root/admin privileges required.", file=sys.stderr)
|
|
359
|
+
print(" Run with sudo (Linux) or as Administrator (Windows).", file=sys.stderr)
|
|
360
|
+
sys.exit(1)
|
|
361
|
+
try:
|
|
362
|
+
backend = smu.init()
|
|
363
|
+
except RuntimeError as e:
|
|
364
|
+
print(f"ZenMaster: backend error: {e}", file=sys.stderr)
|
|
365
|
+
sys.exit(1)
|
|
366
|
+
|
|
367
|
+
if flags.table:
|
|
368
|
+
_show_table(flags.json_out, info.family)
|
|
369
|
+
if not rest and not flags.dump_table:
|
|
370
|
+
sys.exit(0)
|
|
371
|
+
|
|
372
|
+
if flags.dump_table:
|
|
373
|
+
_dump_pm_table(flags.json_out, info.family)
|
|
374
|
+
if not rest:
|
|
375
|
+
sys.exit(0)
|
|
376
|
+
|
|
377
|
+
if rest:
|
|
378
|
+
known = runner.all_known_args()
|
|
379
|
+
for token in rest:
|
|
380
|
+
name = token.lstrip("-").partition("=")[0].replace("_", "-").lower()
|
|
381
|
+
if name and name not in known:
|
|
382
|
+
_show_help(info)
|
|
383
|
+
sys.exit(0)
|
|
384
|
+
|
|
385
|
+
args_str = " ".join(rest)
|
|
386
|
+
try:
|
|
387
|
+
while True:
|
|
388
|
+
results, rejected = apply(args_str, info.family)
|
|
389
|
+
output = _format_results(results, info, backend, flags.json_out, rejected)
|
|
390
|
+
if output:
|
|
391
|
+
print(output)
|
|
392
|
+
if flags.reapply <= 0:
|
|
393
|
+
sys.exit(1 if rejected else 0)
|
|
394
|
+
time.sleep(flags.reapply)
|
|
395
|
+
except KeyboardInterrupt:
|
|
396
|
+
if not flags.json_out:
|
|
397
|
+
print("\nZenMaster: stopped.")
|
zenmaster/hardware.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class CpuInfo:
|
|
9
|
+
name: str
|
|
10
|
+
arch: str
|
|
11
|
+
family: str
|
|
12
|
+
type: str
|
|
13
|
+
cpu_family_int: int
|
|
14
|
+
cpu_model_int: int
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _parse_cpuinfo() -> tuple[int, int, str]:
|
|
18
|
+
cpu_family = cpu_model = 0
|
|
19
|
+
cpu_name = ""
|
|
20
|
+
seen_family = seen_model = False
|
|
21
|
+
try:
|
|
22
|
+
with open("/proc/cpuinfo") as f:
|
|
23
|
+
for line in f:
|
|
24
|
+
if ":" not in line:
|
|
25
|
+
continue
|
|
26
|
+
key, _, val = line.partition(":")
|
|
27
|
+
key, val = key.strip(), val.strip()
|
|
28
|
+
if key == "cpu family" and not seen_family:
|
|
29
|
+
cpu_family = int(val)
|
|
30
|
+
seen_family = True
|
|
31
|
+
elif key == "model" and not seen_model:
|
|
32
|
+
cpu_model = int(val)
|
|
33
|
+
seen_model = True
|
|
34
|
+
elif key == "model name" and not cpu_name:
|
|
35
|
+
cpu_name = val
|
|
36
|
+
if seen_family and seen_model and cpu_name:
|
|
37
|
+
break
|
|
38
|
+
except OSError:
|
|
39
|
+
pass
|
|
40
|
+
return cpu_family, cpu_model, cpu_name
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _parse_processor_identifier() -> tuple[int, int, str]:
|
|
44
|
+
identifier = os.environ.get("PROCESSOR_IDENTIFIER", "")
|
|
45
|
+
words = identifier.split()
|
|
46
|
+
cpu_family = cpu_model = 0
|
|
47
|
+
try:
|
|
48
|
+
fi = words.index("Family") + 1
|
|
49
|
+
mi = words.index("Model") + 1
|
|
50
|
+
cpu_family = int(words[fi])
|
|
51
|
+
cpu_model = int(words[mi].rstrip(","))
|
|
52
|
+
except (ValueError, IndexError):
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
cpu_name = ""
|
|
56
|
+
try:
|
|
57
|
+
import winreg
|
|
58
|
+
key = winreg.OpenKey(
|
|
59
|
+
winreg.HKEY_LOCAL_MACHINE,
|
|
60
|
+
r"HARDWARE\DESCRIPTION\System\CentralProcessor\0",
|
|
61
|
+
)
|
|
62
|
+
cpu_name = winreg.QueryValueEx(key, "ProcessorNameString")[0].strip()
|
|
63
|
+
winreg.CloseKey(key)
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
if not cpu_name:
|
|
67
|
+
cpu_name = identifier
|
|
68
|
+
return cpu_family, cpu_model, cpu_name
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _resolve_codename(cpu_name: str, cpu_family: int, cpu_model: int) -> tuple[str, str]:
|
|
72
|
+
if "Intel" in cpu_name:
|
|
73
|
+
return "Intel", "Intel"
|
|
74
|
+
|
|
75
|
+
arch = family = "Unknown"
|
|
76
|
+
|
|
77
|
+
if cpu_family == 23:
|
|
78
|
+
arch = "Zen 1 - Zen 2"
|
|
79
|
+
match cpu_model:
|
|
80
|
+
case 1: family = "SummitRidge"
|
|
81
|
+
case 8: family = "PinnacleRidge"
|
|
82
|
+
case 17 | 18: family = "RavenRidge"
|
|
83
|
+
case 24: family = "Picasso"
|
|
84
|
+
case 32: family = "Pollock" if any(s in cpu_name for s in ("15e", "15Ce", "20e")) else "Dali"
|
|
85
|
+
case 80: family = "FireFlight"
|
|
86
|
+
case 96: family = "Renoir"
|
|
87
|
+
case 104: family = "Lucienne"
|
|
88
|
+
case 113: family = "Matisse"
|
|
89
|
+
case 144 | 145: family = "VanGogh"
|
|
90
|
+
case 160: family = "Mendocino"
|
|
91
|
+
|
|
92
|
+
elif cpu_family == 25:
|
|
93
|
+
arch = "Zen 3 - Zen 4"
|
|
94
|
+
match cpu_model:
|
|
95
|
+
case 33: family = "Vermeer"
|
|
96
|
+
case 63 | 68: family = "Rembrandt"
|
|
97
|
+
case 80: family = "Cezanne_Barcelo"
|
|
98
|
+
case 97: family = "DragonRange" if "HX" in cpu_name else "Raphael"
|
|
99
|
+
case 116: family = "PhoenixPoint"
|
|
100
|
+
case 120: family = "PhoenixPoint2"
|
|
101
|
+
case 117: family = "HawkPoint"
|
|
102
|
+
case 124: family = "HawkPoint2"
|
|
103
|
+
|
|
104
|
+
elif cpu_family == 26:
|
|
105
|
+
arch = "Zen 5 - Zen 6"
|
|
106
|
+
match cpu_model:
|
|
107
|
+
case 68: family = "FireRange" if "HX" in cpu_name else "GraniteRidge"
|
|
108
|
+
case 96: family = "KrackanPoint"
|
|
109
|
+
case 104: family = "KrackanPoint2"
|
|
110
|
+
case 32 | 36: family = "StrixPoint"
|
|
111
|
+
case 112: family = "StrixHalo"
|
|
112
|
+
|
|
113
|
+
return arch, family
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
_DESKTOP_FAMILIES = {
|
|
117
|
+
"SummitRidge", "PinnacleRidge", "Matisse",
|
|
118
|
+
"Vermeer", "Raphael", "GraniteRidge",
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _cpu_type(family: str, arch: str) -> str:
|
|
123
|
+
if family in _DESKTOP_FAMILIES:
|
|
124
|
+
return "Amd_Desktop_Cpu"
|
|
125
|
+
if arch in ("Intel", "Unknown"):
|
|
126
|
+
return arch
|
|
127
|
+
return "Amd_Apu"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def detect() -> CpuInfo:
|
|
131
|
+
if platform.system() == "Windows":
|
|
132
|
+
cpu_family_int, cpu_model_int, name = _parse_processor_identifier()
|
|
133
|
+
else:
|
|
134
|
+
cpu_family_int, cpu_model_int, name = _parse_cpuinfo()
|
|
135
|
+
|
|
136
|
+
arch, family = _resolve_codename(name, cpu_family_int, cpu_model_int)
|
|
137
|
+
t = _cpu_type(family, arch)
|
|
138
|
+
return CpuInfo(
|
|
139
|
+
name=name,
|
|
140
|
+
arch=arch,
|
|
141
|
+
family=family,
|
|
142
|
+
type=t,
|
|
143
|
+
cpu_family_int=cpu_family_int,
|
|
144
|
+
cpu_model_int=cpu_model_int,
|
|
145
|
+
)
|