amd-debug-tools 0.2.5__py3-none-any.whl → 0.2.12__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.
- amd_debug/__init__.py +9 -2
- amd_debug/acpi.py +0 -1
- amd_debug/battery.py +0 -1
- amd_debug/bios.py +5 -4
- amd_debug/common.py +70 -3
- amd_debug/database.py +22 -5
- amd_debug/display.py +2 -3
- amd_debug/failures.py +43 -2
- amd_debug/installer.py +4 -5
- amd_debug/kernel.py +10 -7
- amd_debug/prerequisites.py +116 -16
- amd_debug/pstate.py +5 -4
- amd_debug/s2idle.py +22 -16
- amd_debug/sleep_report.py +1 -2
- amd_debug/ttm.py +157 -0
- amd_debug/validator.py +40 -19
- amd_debug/wake.py +0 -1
- amd_debug_tools-0.2.12.dist-info/METADATA +75 -0
- amd_debug_tools-0.2.12.dist-info/RECORD +47 -0
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.12.dist-info}/entry_points.txt +1 -0
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.12.dist-info}/top_level.txt +1 -0
- test_acpi.py +1 -1
- test_bios.py +26 -8
- test_common.py +117 -1
- test_database.py +1 -1
- test_display.py +6 -6
- test_installer.py +68 -1
- test_kernel.py +6 -5
- test_launcher.py +7 -0
- test_prerequisites.py +580 -3
- test_s2idle.py +25 -8
- test_ttm.py +276 -0
- test_validator.py +71 -9
- amd_debug_tools-0.2.5.dist-info/METADATA +0 -181
- amd_debug_tools-0.2.5.dist-info/RECORD +0 -45
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.12.dist-info}/WHEEL +0 -0
- {amd_debug_tools-0.2.5.dist-info → amd_debug_tools-0.2.12.dist-info}/licenses/LICENSE +0 -0
amd_debug/prerequisites.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
2
1
|
# SPDX-License-Identifier: MIT
|
|
3
2
|
|
|
4
3
|
"""
|
|
@@ -21,7 +20,7 @@ import pyudev
|
|
|
21
20
|
|
|
22
21
|
from amd_debug.wake import WakeIRQ
|
|
23
22
|
from amd_debug.display import Display
|
|
24
|
-
from amd_debug.kernel import get_kernel_log, SystemdLogger, DmesgLogger
|
|
23
|
+
from amd_debug.kernel import get_kernel_log, SystemdLogger, CySystemdLogger, DmesgLogger
|
|
25
24
|
from amd_debug.common import (
|
|
26
25
|
apply_prefix_wrapper,
|
|
27
26
|
BIT,
|
|
@@ -55,12 +54,15 @@ from amd_debug.failures import (
|
|
|
55
54
|
I2CHidBug,
|
|
56
55
|
KernelRingBufferWrapped,
|
|
57
56
|
LimitedCores,
|
|
57
|
+
MissingAmdCaptureModule,
|
|
58
58
|
MissingAmdgpu,
|
|
59
59
|
MissingAmdgpuFirmware,
|
|
60
60
|
MissingAmdPmc,
|
|
61
|
+
MissingGpu,
|
|
61
62
|
MissingDriver,
|
|
62
63
|
MissingIommuACPI,
|
|
63
64
|
MissingIommuPolicy,
|
|
65
|
+
MissingIsp4PlatformDriver,
|
|
64
66
|
MissingThunderbolt,
|
|
65
67
|
MissingXhciHcd,
|
|
66
68
|
MSRFailure,
|
|
@@ -128,13 +130,35 @@ class PrerequisiteValidator(AmdTool):
|
|
|
128
130
|
if not self.db.get_last_prereq_ts():
|
|
129
131
|
self.run()
|
|
130
132
|
|
|
133
|
+
def capture_nvidia(self):
|
|
134
|
+
"""Capture the NVIDIA GPU state"""
|
|
135
|
+
p = os.path.join("/", "proc", "driver", "nvidia", "version")
|
|
136
|
+
if not os.path.exists(p):
|
|
137
|
+
return True
|
|
138
|
+
try:
|
|
139
|
+
self.db.record_debug_file(p)
|
|
140
|
+
except PermissionError:
|
|
141
|
+
self.db.record_prereq("NVIDIA GPU version not readable", "👀")
|
|
142
|
+
return True
|
|
143
|
+
p = os.path.join("/", "proc", "driver", "nvidia", "gpus")
|
|
144
|
+
if not os.path.exists(p):
|
|
145
|
+
return True
|
|
146
|
+
for root, _dirs, files in os.walk(p, topdown=False):
|
|
147
|
+
for f in files:
|
|
148
|
+
try:
|
|
149
|
+
self.db.record_debug(f"NVIDIA {f}")
|
|
150
|
+
self.db.record_debug_file(os.path.join(root, f))
|
|
151
|
+
except PermissionError:
|
|
152
|
+
self.db.record_prereq("NVIDIA GPU {f} not readable", "👀")
|
|
153
|
+
return True
|
|
154
|
+
|
|
131
155
|
def capture_edid(self):
|
|
132
156
|
"""Capture and decode the EDID data"""
|
|
133
157
|
edids = self.display.get_edid()
|
|
134
158
|
if len(edids) == 0:
|
|
135
159
|
self.db.record_debug("No EDID data found")
|
|
136
160
|
return True
|
|
137
|
-
for
|
|
161
|
+
for p in edids:
|
|
138
162
|
output = None
|
|
139
163
|
for tool in ["di-edid-decode", "edid-decode"]:
|
|
140
164
|
try:
|
|
@@ -145,16 +169,17 @@ class PrerequisiteValidator(AmdTool):
|
|
|
145
169
|
break
|
|
146
170
|
except FileNotFoundError:
|
|
147
171
|
self.db.record_debug(f"{cmd} not installed")
|
|
148
|
-
except subprocess.CalledProcessError as
|
|
172
|
+
except subprocess.CalledProcessError as _e:
|
|
149
173
|
pass
|
|
150
174
|
if not output:
|
|
151
175
|
self.db.record_prereq("Failed to capture EDID table", "👀")
|
|
152
176
|
else:
|
|
153
|
-
self.db.record_debug(apply_prefix_wrapper(f"EDID for {
|
|
177
|
+
self.db.record_debug(apply_prefix_wrapper(f"EDID for {p}:", output))
|
|
154
178
|
return True
|
|
155
179
|
|
|
156
180
|
def check_amdgpu(self):
|
|
157
181
|
"""Check for the AMDGPU driver"""
|
|
182
|
+
count = 0
|
|
158
183
|
for device in self.pyudev.list_devices(subsystem="pci"):
|
|
159
184
|
klass = device.properties.get("PCI_CLASS")
|
|
160
185
|
if klass not in ["30000", "38000"]:
|
|
@@ -162,6 +187,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
162
187
|
pci_id = device.properties.get("PCI_ID")
|
|
163
188
|
if not pci_id.startswith("1002"):
|
|
164
189
|
continue
|
|
190
|
+
count += 1
|
|
165
191
|
if device.properties.get("DRIVER") != "amdgpu":
|
|
166
192
|
self.db.record_prereq("GPU driver `amdgpu` not loaded", "❌")
|
|
167
193
|
self.failures += [MissingAmdgpu()]
|
|
@@ -169,6 +195,10 @@ class PrerequisiteValidator(AmdTool):
|
|
|
169
195
|
slot = device.properties.get("PCI_SLOT_NAME")
|
|
170
196
|
|
|
171
197
|
self.db.record_prereq(f"GPU driver `amdgpu` bound to {slot}", "✅")
|
|
198
|
+
if count == 0:
|
|
199
|
+
self.db.record_prereq("Integrated GPU not found", "❌")
|
|
200
|
+
self.failures += [MissingGpu()]
|
|
201
|
+
return False
|
|
172
202
|
return True
|
|
173
203
|
|
|
174
204
|
def check_amdgpu_parameters(self):
|
|
@@ -650,7 +680,9 @@ class PrerequisiteValidator(AmdTool):
|
|
|
650
680
|
"""Check the source for kernel logs"""
|
|
651
681
|
if isinstance(self.kernel_log, SystemdLogger):
|
|
652
682
|
self.db.record_prereq("Logs are provided via systemd", "✅")
|
|
653
|
-
|
|
683
|
+
elif isinstance(self.kernel_log, CySystemdLogger):
|
|
684
|
+
self.db.record_prereq("Logs are provided via cysystemd", "✅")
|
|
685
|
+
elif isinstance(self.kernel_log, DmesgLogger):
|
|
654
686
|
self.db.record_prereq(
|
|
655
687
|
"Logs are provided via dmesg, timestamps may not be accurate over multiple cycles",
|
|
656
688
|
"🚦",
|
|
@@ -718,6 +750,9 @@ class PrerequisiteValidator(AmdTool):
|
|
|
718
750
|
p = os.path.join("/", "sys", "kernel", "debug", "gpio")
|
|
719
751
|
try:
|
|
720
752
|
contents = read_file(p)
|
|
753
|
+
except FileNotFoundError:
|
|
754
|
+
self.db.record_prereq("GPIO debugfs not available", "👀")
|
|
755
|
+
contents = None
|
|
721
756
|
except PermissionError:
|
|
722
757
|
self.db.record_debug(f"Unable to capture {p}")
|
|
723
758
|
contents = None
|
|
@@ -754,7 +789,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
754
789
|
"utf-8"
|
|
755
790
|
)
|
|
756
791
|
except FileNotFoundError:
|
|
757
|
-
self.db.record_prereq(
|
|
792
|
+
self.db.record_prereq("ethtool is missing", "👀")
|
|
758
793
|
return True
|
|
759
794
|
for line in output.split("\n"):
|
|
760
795
|
if "Supports Wake-on" in line:
|
|
@@ -946,6 +981,44 @@ class PrerequisiteValidator(AmdTool):
|
|
|
946
981
|
if debug_str:
|
|
947
982
|
self.db.record_debug(debug_str)
|
|
948
983
|
|
|
984
|
+
def check_isp4(self):
|
|
985
|
+
"""Check if camera supported by ISP is present and set up properly"""
|
|
986
|
+
devices = []
|
|
987
|
+
for dev in self.pyudev.list_devices(subsystem="acpi"):
|
|
988
|
+
# look for ACPI device for camera (OMNI5C10:00 is seen today)
|
|
989
|
+
if not dev.sys_name.startswith("OMNI5C10"):
|
|
990
|
+
continue
|
|
991
|
+
p = os.path.join(dev.sys_path, "path")
|
|
992
|
+
if not os.path.exists(p):
|
|
993
|
+
continue
|
|
994
|
+
devices.append(dev)
|
|
995
|
+
for dev in devices:
|
|
996
|
+
p = os.path.join(dev.sys_path, "physical_node", "driver")
|
|
997
|
+
if os.path.exists(p):
|
|
998
|
+
driver = os.path.basename(os.readlink(p))
|
|
999
|
+
else:
|
|
1000
|
+
driver = None
|
|
1001
|
+
if driver != "amd-isp4":
|
|
1002
|
+
self.db.record_prereq(
|
|
1003
|
+
f"ISP4 platform camera driver 'amd-isp4' not bound to {dev.sys_name}",
|
|
1004
|
+
"❌",
|
|
1005
|
+
)
|
|
1006
|
+
self.failures += [MissingIsp4PlatformDriver()]
|
|
1007
|
+
return False
|
|
1008
|
+
self.db.record_prereq(
|
|
1009
|
+
f"ISP4 platform camera driver 'amd-isp4' bound to {dev.sys_name}", "✅"
|
|
1010
|
+
)
|
|
1011
|
+
# check if `amd_capture` module is loaded
|
|
1012
|
+
p = os.path.join("/", "sys", "module", "amd_capture")
|
|
1013
|
+
if not os.path.exists(p):
|
|
1014
|
+
self.db.record_prereq(
|
|
1015
|
+
"Camera driver module 'amd_capture' not loaded", "❌"
|
|
1016
|
+
)
|
|
1017
|
+
self.failures += [MissingAmdCaptureModule()]
|
|
1018
|
+
return False
|
|
1019
|
+
self.db.record_prereq("Camera driver module 'amd_capture' loaded", "✅")
|
|
1020
|
+
return True
|
|
1021
|
+
|
|
949
1022
|
def map_acpi_path(self):
|
|
950
1023
|
"""Map of ACPI devices to ACPI paths"""
|
|
951
1024
|
devices = []
|
|
@@ -1007,6 +1080,21 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1007
1080
|
shutil.rmtree(tmpd)
|
|
1008
1081
|
return True
|
|
1009
1082
|
|
|
1083
|
+
def capture_cstates(self):
|
|
1084
|
+
"""Capture ACPI C state information for the first CPU (assumes the same for all CPUs)"""
|
|
1085
|
+
base = os.path.join("/", "sys", "bus", "cpu", "devices", "cpu0", "cpuidle")
|
|
1086
|
+
paths = {}
|
|
1087
|
+
for root, _dirs, files in os.walk(base, topdown=False):
|
|
1088
|
+
for fname in files:
|
|
1089
|
+
target = os.path.join(root, fname)
|
|
1090
|
+
with open(target, "rb") as f:
|
|
1091
|
+
paths[target] = f.read()
|
|
1092
|
+
debug_str = "ACPI C-state information\n"
|
|
1093
|
+
for path, data in paths.items():
|
|
1094
|
+
prefix = "│ " if path != list(paths.keys())[-1] else "└─"
|
|
1095
|
+
debug_str += f"{prefix}{path}: {data.decode('utf-8', 'ignore')}"
|
|
1096
|
+
self.db.record_debug(debug_str)
|
|
1097
|
+
|
|
1010
1098
|
def capture_battery(self):
|
|
1011
1099
|
"""Capture battery information"""
|
|
1012
1100
|
obj = Batteries()
|
|
@@ -1065,11 +1153,22 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1065
1153
|
# check for artificially limited CPUs
|
|
1066
1154
|
p = os.path.join("/", "sys", "devices", "system", "cpu", "kernel_max")
|
|
1067
1155
|
max_cpus = int(read_file(p)) + 1 # 0 indexed
|
|
1068
|
-
# https://
|
|
1069
|
-
# Extended Topology
|
|
1070
|
-
# CPUID 0x80000026 subleaf 1
|
|
1156
|
+
# https://docs.amd.com/v/u/en-US/40332-PUB_4.08
|
|
1157
|
+
# E.4.24 Function 8000_0026—Extended CPU Topology
|
|
1071
1158
|
try:
|
|
1072
|
-
|
|
1159
|
+
# Need to discover the socket level to look at all CCDs in the socket
|
|
1160
|
+
for level in range(0, 5):
|
|
1161
|
+
_, _, ecx, _ = read_cpuid(0, 0x80000026, level)
|
|
1162
|
+
level_type = (ecx >> 8) & 0xFF
|
|
1163
|
+
if level_type == 0:
|
|
1164
|
+
self.db.record_prereq(
|
|
1165
|
+
"Unable to discover CPU topology, didn't find socket level",
|
|
1166
|
+
"🚦",
|
|
1167
|
+
)
|
|
1168
|
+
return True
|
|
1169
|
+
if level_type == 4:
|
|
1170
|
+
break
|
|
1171
|
+
_, cpu_count, _, _ = read_cpuid(0, 0x80000026, level)
|
|
1073
1172
|
if cpu_count > max_cpus:
|
|
1074
1173
|
self.db.record_prereq(
|
|
1075
1174
|
f"The kernel has been limited to {max_cpus} CPU cores, "
|
|
@@ -1140,7 +1239,6 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1140
1239
|
if self.cpu_family == 0x1A and self.cpu_model in affected_1a:
|
|
1141
1240
|
found_iommu = False
|
|
1142
1241
|
found_acpi = False
|
|
1143
|
-
found_dmar = False
|
|
1144
1242
|
for dev in self.pyudev.list_devices(subsystem="iommu"):
|
|
1145
1243
|
found_iommu = True
|
|
1146
1244
|
debug_str += f"Found IOMMU {dev.sys_path}\n"
|
|
@@ -1227,9 +1325,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1227
1325
|
# ignore kernel warnings
|
|
1228
1326
|
taint &= ~BIT(9)
|
|
1229
1327
|
if taint != 0:
|
|
1230
|
-
self.db.record_prereq(f"Kernel is tainted: {taint}", "
|
|
1328
|
+
self.db.record_prereq(f"Kernel is tainted: {taint}", "🚦")
|
|
1231
1329
|
self.failures += [TaintedKernel()]
|
|
1232
|
-
return False
|
|
1233
1330
|
return True
|
|
1234
1331
|
|
|
1235
1332
|
def run(self):
|
|
@@ -1244,6 +1341,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1244
1341
|
self.capture_logind,
|
|
1245
1342
|
self.capture_pci_acpi,
|
|
1246
1343
|
self.capture_edid,
|
|
1344
|
+
self.capture_nvidia,
|
|
1345
|
+
self.capture_cstates,
|
|
1247
1346
|
]
|
|
1248
1347
|
checks = []
|
|
1249
1348
|
|
|
@@ -1273,6 +1372,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1273
1372
|
self.check_iommu,
|
|
1274
1373
|
self.check_asus_rog_ally,
|
|
1275
1374
|
self.check_dpia_pg_dmcub,
|
|
1375
|
+
self.check_isp4,
|
|
1276
1376
|
]
|
|
1277
1377
|
|
|
1278
1378
|
checks += [
|
|
@@ -1296,7 +1396,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1296
1396
|
if not check():
|
|
1297
1397
|
result = False
|
|
1298
1398
|
if not result:
|
|
1299
|
-
self.db.record_prereq(Headers.BrokenPrerequisites, "
|
|
1399
|
+
self.db.record_prereq(Headers.BrokenPrerequisites, "🚫")
|
|
1300
1400
|
self.db.sync()
|
|
1301
1401
|
clear_temporary_message(len(msg))
|
|
1302
1402
|
return result
|
|
@@ -1315,7 +1415,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1315
1415
|
logging.debug(line)
|
|
1316
1416
|
|
|
1317
1417
|
if len(self.failures) == 0:
|
|
1318
|
-
return
|
|
1418
|
+
return
|
|
1319
1419
|
print_color(Headers.ExplanationReport, "🗣️")
|
|
1320
1420
|
for item in self.failures:
|
|
1321
1421
|
item.get_failure()
|
amd_debug/pstate.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
2
1
|
# SPDX-License-Identifier: MIT
|
|
3
2
|
"""CPPC triage script for AMD systems"""
|
|
4
3
|
|
|
@@ -293,18 +292,20 @@ def parse_args():
|
|
|
293
292
|
action="store_true",
|
|
294
293
|
help="Enable tool debug logging",
|
|
295
294
|
)
|
|
296
|
-
|
|
295
|
+
parser.add_argument(
|
|
296
|
+
"--version", action="store_true", help="Show version information"
|
|
297
|
+
)
|
|
297
298
|
if len(sys.argv) == 1:
|
|
298
299
|
parser.print_help(sys.stderr)
|
|
299
300
|
sys.exit(1)
|
|
300
301
|
return parser.parse_args()
|
|
301
302
|
|
|
302
303
|
|
|
303
|
-
def main() -> None|int:
|
|
304
|
+
def main() -> None | int:
|
|
304
305
|
"""Main function"""
|
|
305
306
|
args = parse_args()
|
|
306
307
|
ret = False
|
|
307
|
-
if args.
|
|
308
|
+
if args.version:
|
|
308
309
|
print(version())
|
|
309
310
|
return
|
|
310
311
|
elif args.command == "triage":
|
amd_debug/s2idle.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
2
1
|
# SPDX-License-Identifier: MIT
|
|
3
2
|
"""s2idle analysis tool"""
|
|
4
3
|
import argparse
|
|
@@ -11,6 +10,7 @@ from datetime import date, timedelta, datetime
|
|
|
11
10
|
from amd_debug.common import (
|
|
12
11
|
convert_string_to_bool,
|
|
13
12
|
colorize_choices,
|
|
13
|
+
fatal_error,
|
|
14
14
|
is_root,
|
|
15
15
|
relaunch_sudo,
|
|
16
16
|
show_log_info,
|
|
@@ -61,21 +61,22 @@ def display_report_file(fname, fmt) -> None:
|
|
|
61
61
|
return
|
|
62
62
|
user = os.environ.get("SUDO_USER")
|
|
63
63
|
if user:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
cmd = [
|
|
65
|
+
"systemd-run",
|
|
66
|
+
"--user",
|
|
67
|
+
f"--machine={user}@.host",
|
|
68
|
+
"xdg-open",
|
|
69
|
+
os.path.abspath(fname),
|
|
70
|
+
]
|
|
71
|
+
ret = subprocess.call(cmd)
|
|
72
|
+
if ret:
|
|
73
|
+
fatal_error(f"Failed to open report: {ret}")
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
def get_report_file(report_file, extension) -> str:
|
|
76
77
|
"""Prompt user for report file"""
|
|
77
78
|
if extension == "stdout":
|
|
78
|
-
return
|
|
79
|
+
return ""
|
|
79
80
|
if not report_file:
|
|
80
81
|
return f"amd-s2idle-report-{date.today()}.{extension}"
|
|
81
82
|
return report_file
|
|
@@ -88,7 +89,7 @@ def get_report_format() -> str:
|
|
|
88
89
|
return "html"
|
|
89
90
|
|
|
90
91
|
|
|
91
|
-
def prompt_report_arguments(since, until, fname, fmt, report_debug) ->
|
|
92
|
+
def prompt_report_arguments(since, until, fname, fmt, report_debug) -> list:
|
|
92
93
|
"""Prompt user for report configuration"""
|
|
93
94
|
if not since:
|
|
94
95
|
default = Defaults.since
|
|
@@ -213,8 +214,10 @@ def run_test_cycle(
|
|
|
213
214
|
|
|
214
215
|
try:
|
|
215
216
|
duration, wait, count = prompt_test_arguments(duration, wait, count, rand)
|
|
217
|
+
total_seconds = (duration + wait) * count
|
|
218
|
+
until_time = datetime.now() + timedelta(seconds=total_seconds)
|
|
216
219
|
since, until, fname, fmt, report_debug = prompt_report_arguments(
|
|
217
|
-
datetime.now().isoformat(),
|
|
220
|
+
datetime.now().isoformat(), until_time.isoformat(), fname, fmt, True
|
|
218
221
|
)
|
|
219
222
|
except KeyboardInterrupt:
|
|
220
223
|
sys.exit("\nTest cancelled")
|
|
@@ -237,6 +240,7 @@ def run_test_cycle(
|
|
|
237
240
|
rand=rand,
|
|
238
241
|
logind=logind,
|
|
239
242
|
)
|
|
243
|
+
until = datetime.now()
|
|
240
244
|
else:
|
|
241
245
|
since = None
|
|
242
246
|
until = None
|
|
@@ -385,7 +389,9 @@ def parse_args():
|
|
|
385
389
|
help="Enable tool debug logging",
|
|
386
390
|
)
|
|
387
391
|
|
|
388
|
-
|
|
392
|
+
parser.add_argument(
|
|
393
|
+
"--version", action="store_true", help="Show version information"
|
|
394
|
+
)
|
|
389
395
|
|
|
390
396
|
if len(sys.argv) == 1:
|
|
391
397
|
parser.print_help(sys.stderr)
|
|
@@ -394,7 +400,7 @@ def parse_args():
|
|
|
394
400
|
return parser.parse_args()
|
|
395
401
|
|
|
396
402
|
|
|
397
|
-
def main() -> None|int:
|
|
403
|
+
def main() -> None | int:
|
|
398
404
|
"""Main function"""
|
|
399
405
|
args = parse_args()
|
|
400
406
|
ret = False
|
|
@@ -427,7 +433,7 @@ def main() -> None|int:
|
|
|
427
433
|
args.logind,
|
|
428
434
|
args.bios_debug,
|
|
429
435
|
)
|
|
430
|
-
elif args.
|
|
436
|
+
elif args.version:
|
|
431
437
|
print(version())
|
|
432
438
|
return
|
|
433
439
|
else:
|
amd_debug/sleep_report.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/python3
|
|
2
1
|
# SPDX-License-Identifier: MIT
|
|
3
2
|
|
|
4
3
|
import os
|
|
@@ -488,7 +487,7 @@ class SleepReport(AmdTool):
|
|
|
488
487
|
text = line.strip()
|
|
489
488
|
if not text:
|
|
490
489
|
continue
|
|
491
|
-
for group in ["🗣️", "❌", "🚦", "🦟", "
|
|
490
|
+
for group in ["🗣️", "❌", "🚦", "🦟", "🚫", "○"]:
|
|
492
491
|
if line.startswith(group):
|
|
493
492
|
text = line.split(group)[-1]
|
|
494
493
|
color = get_group_color(group)
|
amd_debug/ttm.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""TTM configuration tool"""
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import os
|
|
6
|
+
import argparse
|
|
7
|
+
from amd_debug.common import (
|
|
8
|
+
AmdTool,
|
|
9
|
+
bytes_to_gb,
|
|
10
|
+
gb_to_pages,
|
|
11
|
+
get_system_mem,
|
|
12
|
+
relaunch_sudo,
|
|
13
|
+
print_color,
|
|
14
|
+
reboot,
|
|
15
|
+
version,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
TTM_PARAM_PATH = "/sys/module/ttm/parameters/pages_limit"
|
|
19
|
+
MODPROBE_CONF_PATH = "/etc/modprobe.d/ttm.conf"
|
|
20
|
+
# Maximum percentage of total system memory to allow for TTM
|
|
21
|
+
MAX_MEMORY_PERCENTAGE = 90
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def maybe_reboot() -> bool:
|
|
25
|
+
"""Prompt to reboot system"""
|
|
26
|
+
response = input("Would you like to reboot the system now? (y/n): ").strip().lower()
|
|
27
|
+
if response in ("y", "yes"):
|
|
28
|
+
return reboot()
|
|
29
|
+
return True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AmdTtmTool(AmdTool):
|
|
33
|
+
"""Class for handling TTM page configuration"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, logging):
|
|
36
|
+
log_prefix = "ttm" if logging else None
|
|
37
|
+
super().__init__(log_prefix)
|
|
38
|
+
|
|
39
|
+
def get(self) -> bool:
|
|
40
|
+
"""Read current page limit"""
|
|
41
|
+
try:
|
|
42
|
+
with open(TTM_PARAM_PATH, "r", encoding="utf-8") as f:
|
|
43
|
+
pages = int(f.read().strip())
|
|
44
|
+
gb_value = bytes_to_gb(pages)
|
|
45
|
+
print_color(
|
|
46
|
+
f"Current TTM pages limit: {pages} pages ({gb_value:.2f} GB)", "💻"
|
|
47
|
+
)
|
|
48
|
+
except FileNotFoundError:
|
|
49
|
+
print_color(f"Error: Could not find {TTM_PARAM_PATH}", "❌")
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
total = get_system_mem()
|
|
53
|
+
if total > 0:
|
|
54
|
+
print_color(f"Total system memory: {total:.2f} GB", "💻")
|
|
55
|
+
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
def set(self, gb_value) -> bool:
|
|
59
|
+
"""Set a new page limit"""
|
|
60
|
+
relaunch_sudo()
|
|
61
|
+
|
|
62
|
+
# Check against system memory
|
|
63
|
+
total = get_system_mem()
|
|
64
|
+
if total > 0:
|
|
65
|
+
max_recommended_gb = total * MAX_MEMORY_PERCENTAGE / 100
|
|
66
|
+
|
|
67
|
+
if gb_value > total:
|
|
68
|
+
print_color(
|
|
69
|
+
f"{gb_value:.2f} GB is greater than total system memory ({total:.2f} GB)",
|
|
70
|
+
"❌",
|
|
71
|
+
)
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
if gb_value > max_recommended_gb:
|
|
75
|
+
print_color(
|
|
76
|
+
f"Warning: The requested value ({gb_value:.2f} GB) exceeds {MAX_MEMORY_PERCENTAGE}% of your system memory ({max_recommended_gb:.2f} GB).",
|
|
77
|
+
"🚦",
|
|
78
|
+
)
|
|
79
|
+
response = (
|
|
80
|
+
input(
|
|
81
|
+
"This could cause system instability. Continue anyway? (y/n): "
|
|
82
|
+
)
|
|
83
|
+
.strip()
|
|
84
|
+
.lower()
|
|
85
|
+
)
|
|
86
|
+
if response not in ("y", "yes"):
|
|
87
|
+
print_color("Operation cancelled.", "🚦")
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
pages = gb_to_pages(gb_value)
|
|
91
|
+
|
|
92
|
+
with open(MODPROBE_CONF_PATH, "w", encoding="utf-8") as f:
|
|
93
|
+
f.write(f"options ttm pages_limit={pages}\n")
|
|
94
|
+
print_color(
|
|
95
|
+
f"Successfully set TTM pages limit to {pages} pages ({gb_value:.2f} GB)",
|
|
96
|
+
"🐧",
|
|
97
|
+
)
|
|
98
|
+
print_color(f"Configuration written to {MODPROBE_CONF_PATH}", "🐧")
|
|
99
|
+
print_color("NOTE: You need to reboot for changes to take effect.", "○")
|
|
100
|
+
|
|
101
|
+
return maybe_reboot()
|
|
102
|
+
|
|
103
|
+
def clear(self) -> bool:
|
|
104
|
+
"""Clears the page limit"""
|
|
105
|
+
if not os.path.exists(MODPROBE_CONF_PATH):
|
|
106
|
+
print_color(f"{MODPROBE_CONF_PATH} doesn't exist", "❌")
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
relaunch_sudo()
|
|
110
|
+
|
|
111
|
+
os.remove(MODPROBE_CONF_PATH)
|
|
112
|
+
print_color(f"Configuration {MODPROBE_CONF_PATH} removed", "🐧")
|
|
113
|
+
|
|
114
|
+
return maybe_reboot()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def parse_args():
|
|
118
|
+
"""Parse command line arguments."""
|
|
119
|
+
parser = argparse.ArgumentParser(description="Manage TTM pages limit")
|
|
120
|
+
parser.add_argument("--set", type=float, help="Set pages limit in GB")
|
|
121
|
+
parser.add_argument(
|
|
122
|
+
"--clear", action="store_true", help="Clear a previously set page limit"
|
|
123
|
+
)
|
|
124
|
+
parser.add_argument(
|
|
125
|
+
"--version", action="store_true", help="Show version information"
|
|
126
|
+
)
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
"--tool-debug",
|
|
129
|
+
action="store_true",
|
|
130
|
+
help="Enable tool debug logging",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
return parser.parse_args()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def main() -> None | int:
|
|
137
|
+
"""Main function"""
|
|
138
|
+
|
|
139
|
+
args = parse_args()
|
|
140
|
+
tool = AmdTtmTool(args.tool_debug)
|
|
141
|
+
ret = False
|
|
142
|
+
|
|
143
|
+
if args.version:
|
|
144
|
+
print(version())
|
|
145
|
+
return
|
|
146
|
+
elif args.set is not None:
|
|
147
|
+
if args.set <= 0:
|
|
148
|
+
print("Error: GB value must be greater than 0")
|
|
149
|
+
return 1
|
|
150
|
+
ret = tool.set(args.set)
|
|
151
|
+
elif args.clear:
|
|
152
|
+
ret = tool.clear()
|
|
153
|
+
else:
|
|
154
|
+
ret = tool.get()
|
|
155
|
+
if ret is False:
|
|
156
|
+
return 1
|
|
157
|
+
return
|