amd-debug-tools 0.2.0__py3-none-any.whl → 0.2.2__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/common.py +48 -0
- amd_debug/display.py +34 -0
- amd_debug/failures.py +13 -1
- amd_debug/installer.py +69 -5
- amd_debug/prerequisites.py +157 -56
- amd_debug/s2idle.py +46 -17
- amd_debug/sleep_report.py +2 -2
- amd_debug/templates/md +0 -7
- amd_debug/validator.py +3 -5
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/METADATA +4 -3
- amd_debug_tools-0.2.2.dist-info/RECORD +45 -0
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/WHEEL +1 -1
- amd_debug_tools-0.2.2.dist-info/top_level.txt +18 -0
- launcher.py +35 -0
- test_acpi.py +90 -0
- test_batteries.py +92 -0
- test_bios.py +250 -0
- test_common.py +444 -0
- test_database.py +284 -0
- test_display.py +143 -0
- test_failures.py +146 -0
- test_installer.py +281 -0
- test_kernel.py +205 -0
- test_launcher.py +53 -0
- test_prerequisites.py +1935 -0
- test_pstate.py +164 -0
- test_s2idle.py +868 -0
- test_sleep_report.py +167 -0
- test_validator.py +723 -0
- test_wake.py +216 -0
- amd_debug_tools-0.2.0.dist-info/RECORD +0 -27
- amd_debug_tools-0.2.0.dist-info/top_level.txt +0 -1
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/entry_points.txt +0 -0
- {amd_debug_tools-0.2.0.dist-info → amd_debug_tools-0.2.2.dist-info}/licenses/LICENSE +0 -0
amd_debug/prerequisites.py
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/python3
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
+
"""
|
|
5
|
+
This module contains the s0i3 prerequisite validator for amd-debug-tools.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import configparser
|
|
4
9
|
import logging
|
|
5
10
|
import os
|
|
6
11
|
import platform
|
|
@@ -9,15 +14,19 @@ import shutil
|
|
|
9
14
|
import subprocess
|
|
10
15
|
import tempfile
|
|
11
16
|
import struct
|
|
12
|
-
import pyudev
|
|
13
17
|
from datetime import datetime
|
|
14
18
|
from packaging import version
|
|
15
19
|
|
|
20
|
+
import pyudev
|
|
21
|
+
|
|
16
22
|
from amd_debug.wake import WakeIRQ
|
|
23
|
+
from amd_debug.display import Display
|
|
17
24
|
from amd_debug.kernel import get_kernel_log, SystemdLogger, DmesgLogger
|
|
18
25
|
from amd_debug.common import (
|
|
26
|
+
apply_prefix_wrapper,
|
|
19
27
|
BIT,
|
|
20
28
|
clear_temporary_message,
|
|
29
|
+
find_ip_version,
|
|
21
30
|
get_distro,
|
|
22
31
|
get_pretty_distro,
|
|
23
32
|
get_property_pyudev,
|
|
@@ -40,6 +49,7 @@ from amd_debug.failures import (
|
|
|
40
49
|
DevSlpDiskIssue,
|
|
41
50
|
DevSlpHostIssue,
|
|
42
51
|
DMArNotEnabled,
|
|
52
|
+
DmcubTooOld,
|
|
43
53
|
DmiNotSetup,
|
|
44
54
|
FadtWrong,
|
|
45
55
|
I2CHidBug,
|
|
@@ -111,12 +121,41 @@ class PrerequisiteValidator(AmdTool):
|
|
|
111
121
|
self.irqs = []
|
|
112
122
|
self.smu_version = ""
|
|
113
123
|
self.smu_program = ""
|
|
124
|
+
self.display = Display()
|
|
114
125
|
|
|
115
126
|
def capture_once(self):
|
|
116
127
|
"""Capture the prerequisites once"""
|
|
117
128
|
if not self.db.get_last_prereq_ts():
|
|
118
129
|
self.run()
|
|
119
130
|
|
|
131
|
+
def capture_edid(self):
|
|
132
|
+
"""Capture and decode the EDID data"""
|
|
133
|
+
edids = self.display.get_edid()
|
|
134
|
+
if len(edids) == 0:
|
|
135
|
+
self.db.record_debug("No EDID data found")
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
for name, p in edids.items():
|
|
139
|
+
output = None
|
|
140
|
+
for cmd in ["di-edid-decode", "edid-decode"]:
|
|
141
|
+
try:
|
|
142
|
+
cmd = ["edid-decode", p]
|
|
143
|
+
output = subprocess.check_output(
|
|
144
|
+
cmd, stderr=subprocess.DEVNULL
|
|
145
|
+
).decode("utf-8")
|
|
146
|
+
break
|
|
147
|
+
except FileNotFoundError:
|
|
148
|
+
self.db.record_debug(f"{cmd} not installed")
|
|
149
|
+
except subprocess.CalledProcessError as e:
|
|
150
|
+
self.db.record_debug(
|
|
151
|
+
f"failed to capture edid with {cmd}: {e.output}"
|
|
152
|
+
)
|
|
153
|
+
if not output:
|
|
154
|
+
self.db.record_prereq("Failed to capture EDID table", "👀")
|
|
155
|
+
else:
|
|
156
|
+
self.db.record_debug(apply_prefix_wrapper(f"EDID for {name}:", output))
|
|
157
|
+
return True
|
|
158
|
+
|
|
120
159
|
def check_amdgpu(self):
|
|
121
160
|
"""Check for the AMDGPU driver"""
|
|
122
161
|
for device in self.pyudev.list_devices(subsystem="pci"):
|
|
@@ -167,10 +206,10 @@ class PrerequisiteValidator(AmdTool):
|
|
|
167
206
|
if self.kernel_log.match_pattern("ath11k_pci.*wcn6855"):
|
|
168
207
|
match = self.kernel_log.match_pattern("ath11k_pci.*fw_version")
|
|
169
208
|
if match:
|
|
170
|
-
self.db.record_debug("WCN6855 version string:
|
|
209
|
+
self.db.record_debug(f"WCN6855 version string: {match}")
|
|
171
210
|
objects = match.split()
|
|
172
|
-
for i in
|
|
173
|
-
if
|
|
211
|
+
for i, obj in enumerate(objects):
|
|
212
|
+
if obj == "fw_build_id":
|
|
174
213
|
wcn6855 = objects[i + 1]
|
|
175
214
|
|
|
176
215
|
if wcn6855:
|
|
@@ -346,6 +385,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
346
385
|
|
|
347
386
|
def check_usb3(self):
|
|
348
387
|
"""Check for the USB4 controller"""
|
|
388
|
+
slots = []
|
|
349
389
|
for device in self.pyudev.list_devices(subsystem="pci", PCI_CLASS="C0330"):
|
|
350
390
|
slot = device.properties["PCI_SLOT_NAME"]
|
|
351
391
|
if device.properties.get("DRIVER") != "xhci_hcd":
|
|
@@ -354,18 +394,74 @@ class PrerequisiteValidator(AmdTool):
|
|
|
354
394
|
)
|
|
355
395
|
self.failures += [MissingXhciHcd()]
|
|
356
396
|
return False
|
|
357
|
-
|
|
397
|
+
slots += [slot]
|
|
398
|
+
if slots:
|
|
399
|
+
self.db.record_prereq(
|
|
400
|
+
f"USB3 driver `xhci_hcd` bound to {', '.join(slots)}", "✅"
|
|
401
|
+
)
|
|
402
|
+
return True
|
|
403
|
+
|
|
404
|
+
def check_dpia_pg_dmcub(self):
|
|
405
|
+
"""Check if DMUB is new enough to PG DPIA when no USB4 present"""
|
|
406
|
+
usb4_found = False
|
|
407
|
+
for device in self.pyudev.list_devices(subsystem="pci", PCI_CLASS="C0340"):
|
|
408
|
+
usb4_found = True
|
|
409
|
+
break
|
|
410
|
+
if usb4_found:
|
|
411
|
+
self.db.record_debug("USB4 routers found, no need to check DMCUB version")
|
|
412
|
+
return True
|
|
413
|
+
# Check if matching DCN present
|
|
414
|
+
for device in self.pyudev.list_devices(subsystem="pci"):
|
|
415
|
+
current = None
|
|
416
|
+
klass = device.properties.get("PCI_CLASS")
|
|
417
|
+
if klass not in ["30000", "38000"]:
|
|
418
|
+
continue
|
|
419
|
+
pci_id = device.properties.get("PCI_ID")
|
|
420
|
+
if not pci_id.startswith("1002"):
|
|
421
|
+
continue
|
|
422
|
+
hw_ver = {"major": 3, "minor": 5, "revision": 0}
|
|
423
|
+
if not find_ip_version(device.sys_path, "DMU", hw_ver):
|
|
424
|
+
continue
|
|
425
|
+
|
|
426
|
+
# DCN was found, lookup version from sysfs
|
|
427
|
+
p = os.path.join(device.sys_path, "fw_version", "dmcub_fw_version")
|
|
428
|
+
if os.path.exists(p):
|
|
429
|
+
current = int(read_file(p), 16)
|
|
430
|
+
|
|
431
|
+
# no sysfs, try to look for version from debugfs
|
|
432
|
+
if not current:
|
|
433
|
+
slot = device.properties["PCI_SLOT_NAME"]
|
|
434
|
+
p = os.path.join(
|
|
435
|
+
"/", "sys", "kernel", "debug", "dri", slot, "amdgpu_firmware_info"
|
|
436
|
+
)
|
|
437
|
+
contents = read_file(p)
|
|
438
|
+
for line in contents.split("\n"):
|
|
439
|
+
if not line.startswith("DMCUB"):
|
|
440
|
+
continue
|
|
441
|
+
current = int(line.split()[-1], 16)
|
|
442
|
+
if current:
|
|
443
|
+
expected = 0x09001B00
|
|
444
|
+
if current >= expected:
|
|
445
|
+
return True
|
|
446
|
+
self.db.record_prereq("DMCUB Firmware is outdated", "❌")
|
|
447
|
+
self.failures += [DmcubTooOld(current, expected)]
|
|
448
|
+
return False
|
|
358
449
|
return True
|
|
359
450
|
|
|
360
451
|
def check_usb4(self):
|
|
361
452
|
"""Check if the thunderbolt driver is loaded"""
|
|
453
|
+
slots = []
|
|
362
454
|
for device in self.pyudev.list_devices(subsystem="pci", PCI_CLASS="C0340"):
|
|
363
455
|
slot = device.properties["PCI_SLOT_NAME"]
|
|
364
456
|
if device.properties.get("DRIVER") != "thunderbolt":
|
|
365
457
|
self.db.record_prereq("USB4 driver `thunderbolt` missing", "❌")
|
|
366
458
|
self.failures += [MissingThunderbolt()]
|
|
367
459
|
return False
|
|
368
|
-
|
|
460
|
+
slots += [slot]
|
|
461
|
+
if slots:
|
|
462
|
+
self.db.record_prereq(
|
|
463
|
+
f"USB4 driver `thunderbolt` bound to {', '.join(slots)}", "✅"
|
|
464
|
+
)
|
|
369
465
|
return True
|
|
370
466
|
|
|
371
467
|
def check_sleep_mode(self):
|
|
@@ -478,8 +574,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
478
574
|
continue
|
|
479
575
|
if self.cpu_family and self.cpu_model and self.cpu_model_string:
|
|
480
576
|
self.db.record_prereq(
|
|
481
|
-
"
|
|
482
|
-
|
|
577
|
+
f"{self.cpu_model_string} "
|
|
578
|
+
f"(family {self.cpu_family:x} model {self.cpu_model:x})",
|
|
483
579
|
"💻",
|
|
484
580
|
)
|
|
485
581
|
break
|
|
@@ -487,7 +583,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
487
583
|
|
|
488
584
|
# See https://github.com/torvalds/linux/commit/ec6c0503190417abf8b8f8e3e955ae583a4e50d4
|
|
489
585
|
def check_fadt(self):
|
|
490
|
-
"""Check the kernel emitted a message
|
|
586
|
+
"""Check the kernel emitted a message indicating FADT had a bit set."""
|
|
491
587
|
found = False
|
|
492
588
|
if not self.kernel_log:
|
|
493
589
|
message = "Unable to test FADT from kernel log"
|
|
@@ -575,10 +671,10 @@ class PrerequisiteValidator(AmdTool):
|
|
|
575
671
|
"""Check if the user has permissions to write to /sys/power/state"""
|
|
576
672
|
p = os.path.join("/", "sys", "power", "state")
|
|
577
673
|
try:
|
|
578
|
-
with open(p, "w") as
|
|
674
|
+
with open(p, "w", encoding="utf-8") as _w:
|
|
579
675
|
pass
|
|
580
676
|
except PermissionError:
|
|
581
|
-
self.db.record_prereq("
|
|
677
|
+
self.db.record_prereq(f"{Headers.RootError}", "👀")
|
|
582
678
|
return False
|
|
583
679
|
except FileNotFoundError:
|
|
584
680
|
self.db.record_prereq("Kernel doesn't support power management", "❌")
|
|
@@ -608,7 +704,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
608
704
|
)
|
|
609
705
|
if show_warning:
|
|
610
706
|
self.db.record_prereq(
|
|
611
|
-
"Timer based wakeup doesn't work properly for your
|
|
707
|
+
"Timer based wakeup doesn't work properly for your "
|
|
708
|
+
"ASIC/firmware, please manually wake the system",
|
|
612
709
|
"🚦",
|
|
613
710
|
)
|
|
614
711
|
return True
|
|
@@ -631,7 +728,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
631
728
|
for line in contents.split("\n"):
|
|
632
729
|
if "WAKE_INT_MASTER_REG:" in line:
|
|
633
730
|
val = "en" if int(line.split()[1], 16) & BIT(15) else "dis"
|
|
634
|
-
self.db.record_debug("Windows GPIO 0 debounce:
|
|
731
|
+
self.db.record_debug(f"Windows GPIO 0 debounce: {val}abled")
|
|
635
732
|
continue
|
|
636
733
|
if not header and re.search("trigger", line):
|
|
637
734
|
debug_str += line + "\n"
|
|
@@ -654,9 +751,13 @@ class PrerequisiteValidator(AmdTool):
|
|
|
654
751
|
interface = device.properties.get("INTERFACE")
|
|
655
752
|
cmd = ["ethtool", interface]
|
|
656
753
|
wol_supported = False
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
754
|
+
try:
|
|
755
|
+
output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL).decode(
|
|
756
|
+
"utf-8"
|
|
757
|
+
)
|
|
758
|
+
except FileNotFoundError:
|
|
759
|
+
self.db.record_prereq(f"ethtool is missing", "👀")
|
|
760
|
+
return True
|
|
660
761
|
for line in output.split("\n"):
|
|
661
762
|
if "Supports Wake-on" in line:
|
|
662
763
|
val = line.split(":")[1].strip()
|
|
@@ -671,7 +772,9 @@ class PrerequisiteValidator(AmdTool):
|
|
|
671
772
|
self.db.record_prereq(f"{interface} has WoL enabled", "✅")
|
|
672
773
|
else:
|
|
673
774
|
self.db.record_prereq(
|
|
674
|
-
|
|
775
|
+
"Platform may have low hardware sleep residency "
|
|
776
|
+
"with Wake-on-lan disabled. Run `ethtool -s "
|
|
777
|
+
f"{interface} wol g` to enable it if necessary.",
|
|
675
778
|
"🚦",
|
|
676
779
|
)
|
|
677
780
|
return True
|
|
@@ -726,7 +829,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
726
829
|
# Dictionary of instance id to firmware version mappings that
|
|
727
830
|
# have been "reported" to be problematic
|
|
728
831
|
device_map = {
|
|
729
|
-
|
|
832
|
+
# https://gitlab.freedesktop.org/drm/amd/-/issues/3443
|
|
833
|
+
"8c36f7ee-cc11-4a36-b090-6363f54ecac2": "0.1.26",
|
|
730
834
|
}
|
|
731
835
|
interesting_plugins = ["nvme", "tpm", "uefi_capsule"]
|
|
732
836
|
if device.get_plugin() in interesting_plugins:
|
|
@@ -743,7 +847,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
743
847
|
item in device.get_guids() or item in device.get_instance_ids()
|
|
744
848
|
) and ver in device.get_version():
|
|
745
849
|
self.db.record_prereq(
|
|
746
|
-
|
|
850
|
+
"Platform may have problems resuming. Upgrade the "
|
|
851
|
+
f"firmware for '{device.get_name()}' if you have problems.",
|
|
747
852
|
"🚦",
|
|
748
853
|
)
|
|
749
854
|
return True
|
|
@@ -792,12 +897,13 @@ class PrerequisiteValidator(AmdTool):
|
|
|
792
897
|
else:
|
|
793
898
|
acpi_hid = ""
|
|
794
899
|
# set prefix if last device
|
|
795
|
-
prefix = "
|
|
796
|
-
debug_str += "{prefix}{name} [{acpi_hid}] : {acpi_path}\n"
|
|
797
|
-
prefix=prefix, name=name, acpi_hid=acpi_hid, acpi_path=acpi_path
|
|
798
|
-
)
|
|
900
|
+
prefix = "│ " if dev != devices[-1] else "└─"
|
|
901
|
+
debug_str += f"{prefix}{name} [{acpi_hid}] : {acpi_path}\n"
|
|
799
902
|
if "IDEA5002" in name:
|
|
800
|
-
remediation =
|
|
903
|
+
remediation = (
|
|
904
|
+
f"echo {parent.sys_path.split('/')[-1]} | "
|
|
905
|
+
f"sudo tee /sys/bus/i2c/drivers/{parent.driver}/unbind"
|
|
906
|
+
)
|
|
801
907
|
|
|
802
908
|
self.db.record_prereq(f"{name} may cause spurious wakeups", "❌")
|
|
803
909
|
self.failures += [I2CHidBug(name, remediation)]
|
|
@@ -834,22 +940,13 @@ class PrerequisiteValidator(AmdTool):
|
|
|
834
940
|
if os.path.exists(p):
|
|
835
941
|
acpi = read_file(p)
|
|
836
942
|
debug_str += (
|
|
837
|
-
"{prefix}{pci_slot_name} :
|
|
838
|
-
|
|
839
|
-
pci_slot_name=pci_slot_name,
|
|
840
|
-
vendor=database_vendor,
|
|
841
|
-
cls=database_class,
|
|
842
|
-
id=pci_id,
|
|
843
|
-
acpi=acpi,
|
|
844
|
-
)
|
|
943
|
+
f"{prefix}{pci_slot_name} : "
|
|
944
|
+
f"{database_vendor} {database_class} [{pci_id}] : {acpi}\n"
|
|
845
945
|
)
|
|
846
946
|
else:
|
|
847
|
-
debug_str +=
|
|
848
|
-
prefix
|
|
849
|
-
|
|
850
|
-
pci_slot_name=pci_slot_name,
|
|
851
|
-
cls=database_class,
|
|
852
|
-
id=pci_id,
|
|
947
|
+
debug_str += (
|
|
948
|
+
f"{prefix}{pci_slot_name} : "
|
|
949
|
+
f"{database_vendor} {database_class} [{pci_id}]\n"
|
|
853
950
|
)
|
|
854
951
|
if debug_str:
|
|
855
952
|
self.db.record_debug(debug_str)
|
|
@@ -908,7 +1005,9 @@ class PrerequisiteValidator(AmdTool):
|
|
|
908
1005
|
stdout=subprocess.DEVNULL,
|
|
909
1006
|
stderr=subprocess.DEVNULL,
|
|
910
1007
|
)
|
|
911
|
-
self.db.record_debug_file("
|
|
1008
|
+
self.db.record_debug_file(f"{prefix}.dsl")
|
|
1009
|
+
except FileNotFoundError as e:
|
|
1010
|
+
self.db.record_prereq(f"Failed to capture ACPI table: {e}", "👀")
|
|
912
1011
|
except subprocess.CalledProcessError as e:
|
|
913
1012
|
self.db.record_prereq(
|
|
914
1013
|
f"Failed to capture ACPI table: {e.output}", "👀"
|
|
@@ -925,10 +1024,10 @@ class PrerequisiteValidator(AmdTool):
|
|
|
925
1024
|
self.db.record_prereq(desc, "🔋")
|
|
926
1025
|
|
|
927
1026
|
def capture_logind(self):
|
|
1027
|
+
"""Capture logind.conf settings"""
|
|
928
1028
|
base = os.path.join("/", "etc", "systemd", "logind.conf")
|
|
929
1029
|
if not os.path.exists(base):
|
|
930
1030
|
return True
|
|
931
|
-
import configparser
|
|
932
1031
|
|
|
933
1032
|
config = configparser.ConfigParser()
|
|
934
1033
|
config.read(base)
|
|
@@ -982,7 +1081,8 @@ class PrerequisiteValidator(AmdTool):
|
|
|
982
1081
|
_, cpu_count, _, _ = read_cpuid(0, 0x80000026, 1)
|
|
983
1082
|
if cpu_count > max_cpus:
|
|
984
1083
|
self.db.record_prereq(
|
|
985
|
-
f"The kernel has been limited to {max_cpus} CPU cores,
|
|
1084
|
+
f"The kernel has been limited to {max_cpus} CPU cores, "
|
|
1085
|
+
f"but the system has {cpu_count} cores",
|
|
986
1086
|
"❌",
|
|
987
1087
|
)
|
|
988
1088
|
self.failures += [LimitedCores(cpu_count, max_cpus)]
|
|
@@ -996,12 +1096,6 @@ class PrerequisiteValidator(AmdTool):
|
|
|
996
1096
|
except PermissionError:
|
|
997
1097
|
self.db.record_prereq("CPUID checks unavailable", "🚦")
|
|
998
1098
|
|
|
999
|
-
if valid:
|
|
1000
|
-
self.db.record_prereq(
|
|
1001
|
-
f"{self.cpu_model_string} (family {self.cpu_family:x} model {self.cpu_model:x})",
|
|
1002
|
-
"✅",
|
|
1003
|
-
)
|
|
1004
|
-
|
|
1005
1099
|
return True
|
|
1006
1100
|
|
|
1007
1101
|
def check_msr(self):
|
|
@@ -1020,7 +1114,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1020
1114
|
if not check_bits(val, expect_val):
|
|
1021
1115
|
self.failures += [MSRFailure()]
|
|
1022
1116
|
return False
|
|
1023
|
-
|
|
1117
|
+
self.db.record_prereq("PC6 and CC6 enabled", "✅")
|
|
1024
1118
|
except FileNotFoundError:
|
|
1025
1119
|
self.db.record_prereq(
|
|
1026
1120
|
"Unable to check MSRs: MSR kernel module not loaded", "❌"
|
|
@@ -1064,13 +1158,16 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1064
1158
|
self.db.record_prereq("IOMMU disabled", "✅")
|
|
1065
1159
|
return True
|
|
1066
1160
|
debug_str += "DMA protection:\n"
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1161
|
+
p = os.path.join("/", "sys", "firmware", "acpi", "tables", "IVRS")
|
|
1162
|
+
with open(p, "rb") as f:
|
|
1163
|
+
data = f.read()
|
|
1164
|
+
if len(data) < 40:
|
|
1165
|
+
raise ValueError(
|
|
1166
|
+
"IVRS table appears too small to contain virtualization info."
|
|
1167
|
+
)
|
|
1168
|
+
virt_info = struct.unpack_from("I", data, 36)[0]
|
|
1169
|
+
debug_str += f"Virtualization info: 0x{virt_info:x}"
|
|
1170
|
+
found_dmar = (virt_info & 0x2) != 0
|
|
1074
1171
|
self.db.record_debug(debug_str)
|
|
1075
1172
|
if not found_dmar:
|
|
1076
1173
|
self.db.record_prereq(
|
|
@@ -1111,7 +1208,9 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1111
1208
|
if "pcie_port_pm=off" in cmdline:
|
|
1112
1209
|
return True
|
|
1113
1210
|
self.db.record_prereq(
|
|
1114
|
-
"Platform may hang resuming.
|
|
1211
|
+
"Platform may hang resuming. "
|
|
1212
|
+
"Upgrade your firmware or add pcie_port_pm=off to kernel command "
|
|
1213
|
+
"line if you have problems.",
|
|
1115
1214
|
"🚦",
|
|
1116
1215
|
)
|
|
1117
1216
|
return False
|
|
@@ -1139,6 +1238,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1139
1238
|
self.capture_linux_firmware,
|
|
1140
1239
|
self.capture_logind,
|
|
1141
1240
|
self.capture_pci_acpi,
|
|
1241
|
+
self.capture_edid,
|
|
1142
1242
|
]
|
|
1143
1243
|
checks = []
|
|
1144
1244
|
|
|
@@ -1167,6 +1267,7 @@ class PrerequisiteValidator(AmdTool):
|
|
|
1167
1267
|
self.check_smt,
|
|
1168
1268
|
self.check_iommu,
|
|
1169
1269
|
self.check_asus_rog_ally,
|
|
1270
|
+
self.check_dpia_pg_dmcub,
|
|
1170
1271
|
]
|
|
1171
1272
|
|
|
1172
1273
|
checks += [
|
amd_debug/s2idle.py
CHANGED
|
@@ -8,7 +8,15 @@ import subprocess
|
|
|
8
8
|
import sqlite3
|
|
9
9
|
|
|
10
10
|
from datetime import date, timedelta, datetime
|
|
11
|
-
from amd_debug.common import
|
|
11
|
+
from amd_debug.common import (
|
|
12
|
+
convert_string_to_bool,
|
|
13
|
+
colorize_choices,
|
|
14
|
+
is_root,
|
|
15
|
+
relaunch_sudo,
|
|
16
|
+
show_log_info,
|
|
17
|
+
version,
|
|
18
|
+
running_ssh,
|
|
19
|
+
)
|
|
12
20
|
|
|
13
21
|
from amd_debug.validator import SleepValidator
|
|
14
22
|
from amd_debug.installer import Installer
|
|
@@ -25,6 +33,7 @@ class Defaults:
|
|
|
25
33
|
since = date.today() - timedelta(days=60)
|
|
26
34
|
until = date.today() + timedelta(days=1)
|
|
27
35
|
format_choices = ["txt", "md", "html", "stdout"]
|
|
36
|
+
boolean_choices = ["true", "false"]
|
|
28
37
|
|
|
29
38
|
|
|
30
39
|
class Headers:
|
|
@@ -40,6 +49,7 @@ class Headers:
|
|
|
40
49
|
FormatDescription = "What format to output the report in"
|
|
41
50
|
MaxDurationDescription = "What is the maximum suspend cycle length (seconds)"
|
|
42
51
|
MaxWaitDescription = "What is the maximum time between suspend cycles (seconds)"
|
|
52
|
+
ReportDebugDescription = "Enable debug output in report (increased size)"
|
|
43
53
|
|
|
44
54
|
|
|
45
55
|
def display_report_file(fname, fmt) -> None:
|
|
@@ -51,12 +61,14 @@ def display_report_file(fname, fmt) -> None:
|
|
|
51
61
|
return
|
|
52
62
|
user = os.environ.get("SUDO_USER")
|
|
53
63
|
if user:
|
|
54
|
-
# ensure that xdg tools will know how to display the file
|
|
64
|
+
# ensure that xdg tools will know how to display the file
|
|
65
|
+
# (user may need to call tool with sudo -E)
|
|
55
66
|
if os.environ.get("XDG_SESSION_TYPE"):
|
|
56
67
|
subprocess.call(["sudo", "-E", "-u", user, "xdg-open", fname])
|
|
57
68
|
else:
|
|
58
69
|
print(
|
|
59
|
-
|
|
70
|
+
"To display report automatically in browser launch tool "
|
|
71
|
+
f"with '-E' argument (Example: sudo -E {sys.argv[0]})"
|
|
60
72
|
)
|
|
61
73
|
|
|
62
74
|
|
|
@@ -76,7 +88,7 @@ def get_report_format() -> str:
|
|
|
76
88
|
return "html"
|
|
77
89
|
|
|
78
90
|
|
|
79
|
-
def prompt_report_arguments(since, until, fname, fmt) -> str:
|
|
91
|
+
def prompt_report_arguments(since, until, fname, fmt, report_debug) -> str:
|
|
80
92
|
"""Prompt user for report configuration"""
|
|
81
93
|
if not since:
|
|
82
94
|
default = Defaults.since
|
|
@@ -98,12 +110,23 @@ def prompt_report_arguments(since, until, fname, fmt) -> str:
|
|
|
98
110
|
sys.exit(f"Invalid date, use YYYY-MM-DD: {e}")
|
|
99
111
|
|
|
100
112
|
if not fmt:
|
|
101
|
-
fmt = input(
|
|
113
|
+
fmt = input(
|
|
114
|
+
f"{Headers.FormatDescription} ({colorize_choices(Defaults.format_choices, get_report_format())})? "
|
|
115
|
+
)
|
|
102
116
|
if not fmt:
|
|
103
117
|
fmt = get_report_format()
|
|
104
118
|
if fmt not in Defaults.format_choices:
|
|
105
119
|
sys.exit(f"Invalid format: {fmt}")
|
|
106
|
-
|
|
120
|
+
if report_debug is None:
|
|
121
|
+
inp = (
|
|
122
|
+
input(
|
|
123
|
+
f"{Headers.ReportDebugDescription} ({colorize_choices(Defaults.boolean_choices, "true")})? "
|
|
124
|
+
)
|
|
125
|
+
.lower()
|
|
126
|
+
.capitalize()
|
|
127
|
+
)
|
|
128
|
+
report_debug = True if not inp else convert_string_to_bool(inp)
|
|
129
|
+
return [since, until, get_report_file(fname, fmt), fmt, report_debug]
|
|
107
130
|
|
|
108
131
|
|
|
109
132
|
def prompt_test_arguments(duration, wait, count, rand) -> list:
|
|
@@ -146,7 +169,9 @@ def prompt_test_arguments(duration, wait, count, rand) -> list:
|
|
|
146
169
|
def report(since, until, fname, fmt, tool_debug, report_debug) -> bool:
|
|
147
170
|
"""Generate a report from previous sleep cycles"""
|
|
148
171
|
try:
|
|
149
|
-
since, until, fname, fmt = prompt_report_arguments(
|
|
172
|
+
since, until, fname, fmt, report_debug = prompt_report_arguments(
|
|
173
|
+
since, until, fname, fmt, report_debug
|
|
174
|
+
)
|
|
150
175
|
except KeyboardInterrupt:
|
|
151
176
|
sys.exit("\nReport generation cancelled")
|
|
152
177
|
try:
|
|
@@ -176,12 +201,12 @@ def report(since, until, fname, fmt, tool_debug, report_debug) -> bool:
|
|
|
176
201
|
return True
|
|
177
202
|
|
|
178
203
|
|
|
179
|
-
def
|
|
204
|
+
def run_test_cycle(
|
|
180
205
|
duration, wait, count, fmt, fname, force, debug, rand, logind, bios_debug
|
|
181
206
|
) -> bool:
|
|
182
207
|
"""Run a test"""
|
|
183
208
|
app = Installer(tool_debug=debug)
|
|
184
|
-
app.set_requirements("iasl", "ethtool")
|
|
209
|
+
app.set_requirements("iasl", "ethtool", "edid-decode")
|
|
185
210
|
if not app.install_dependencies():
|
|
186
211
|
print("Failed to install dependencies")
|
|
187
212
|
return False
|
|
@@ -198,8 +223,8 @@ def test(
|
|
|
198
223
|
app = SleepValidator(tool_debug=debug, bios_debug=bios_debug)
|
|
199
224
|
try:
|
|
200
225
|
duration, wait, count = prompt_test_arguments(duration, wait, count, rand)
|
|
201
|
-
since, until, fname, fmt = prompt_report_arguments(
|
|
202
|
-
datetime.now().isoformat(), Defaults.until.isoformat(), fname, fmt
|
|
226
|
+
since, until, fname, fmt, report_debug = prompt_report_arguments(
|
|
227
|
+
datetime.now().isoformat(), Defaults.until.isoformat(), fname, fmt, True
|
|
203
228
|
)
|
|
204
229
|
except KeyboardInterrupt:
|
|
205
230
|
sys.exit("\nTest cancelled")
|
|
@@ -218,7 +243,7 @@ def test(
|
|
|
218
243
|
fname=fname,
|
|
219
244
|
fmt=fmt,
|
|
220
245
|
tool_debug=debug,
|
|
221
|
-
report_debug=
|
|
246
|
+
report_debug=report_debug,
|
|
222
247
|
)
|
|
223
248
|
app.run()
|
|
224
249
|
|
|
@@ -232,7 +257,7 @@ def test(
|
|
|
232
257
|
def install(debug) -> None:
|
|
233
258
|
"""Install the tool"""
|
|
234
259
|
installer = Installer(tool_debug=debug)
|
|
235
|
-
installer.set_requirements("iasl", "ethtool")
|
|
260
|
+
installer.set_requirements("iasl", "ethtool", "edid-decode")
|
|
236
261
|
if not installer.install_dependencies():
|
|
237
262
|
sys.exit("Failed to install dependencies")
|
|
238
263
|
try:
|
|
@@ -259,7 +284,8 @@ def parse_args():
|
|
|
259
284
|
"""Parse command line arguments"""
|
|
260
285
|
parser = argparse.ArgumentParser(
|
|
261
286
|
description="Swiss army knife for analyzing Linux s2idle problems",
|
|
262
|
-
epilog="The tool can run an immediate test with the 'test' command
|
|
287
|
+
epilog="The tool can run an immediate test with the 'test' command "
|
|
288
|
+
"or can be used to hook into systemd for building reports later.\n"
|
|
263
289
|
"All optional arguments will be prompted if needed.\n"
|
|
264
290
|
"To use non-interactively, please populate all optional arguments.",
|
|
265
291
|
)
|
|
@@ -282,7 +308,10 @@ def parse_args():
|
|
|
282
308
|
test_cmd.add_argument(
|
|
283
309
|
"--random",
|
|
284
310
|
action="store_true",
|
|
285
|
-
help=
|
|
311
|
+
help=(
|
|
312
|
+
"Run sleep cycles for random durations and wait, using the "
|
|
313
|
+
"--duration and --wait arguments as an upper bound",
|
|
314
|
+
),
|
|
286
315
|
)
|
|
287
316
|
test_cmd.add_argument(
|
|
288
317
|
"--force",
|
|
@@ -332,7 +361,7 @@ def parse_args():
|
|
|
332
361
|
)
|
|
333
362
|
report_cmd.add_argument(
|
|
334
363
|
"--report-debug",
|
|
335
|
-
action=
|
|
364
|
+
action=argparse.BooleanOptionalAction,
|
|
336
365
|
help="Include debug messages in report (WARNING: can significantly increase report size)",
|
|
337
366
|
)
|
|
338
367
|
|
|
@@ -385,7 +414,7 @@ def main():
|
|
|
385
414
|
)
|
|
386
415
|
elif args.action == "test":
|
|
387
416
|
relaunch_sudo()
|
|
388
|
-
ret =
|
|
417
|
+
ret = run_test_cycle(
|
|
389
418
|
args.duration,
|
|
390
419
|
args.wait,
|
|
391
420
|
args.count,
|
amd_debug/sleep_report.py
CHANGED
|
@@ -432,8 +432,8 @@ class SleepReport(AmdTool):
|
|
|
432
432
|
|
|
433
433
|
characters = print_temporary_message("Building report, please wait...")
|
|
434
434
|
|
|
435
|
-
# Build charts in the page for html
|
|
436
|
-
if len(self.df.index) > 1 and
|
|
435
|
+
# Build charts in the page for html format
|
|
436
|
+
if len(self.df.index) > 1 and self.format == "html":
|
|
437
437
|
self.build_battery_chart()
|
|
438
438
|
self.build_hw_sleep_chart()
|
|
439
439
|
|
amd_debug/templates/md
CHANGED
amd_debug/validator.py
CHANGED
|
@@ -469,12 +469,10 @@ class SleepValidator(AmdTool):
|
|
|
469
469
|
)
|
|
470
470
|
return False
|
|
471
471
|
except FileNotFoundError:
|
|
472
|
-
self.db.
|
|
473
|
-
return False
|
|
472
|
+
self.db.record_debug(f"HW sleep statistics file {p} is missing")
|
|
474
473
|
if not self.hw_sleep_duration:
|
|
475
474
|
self.db.record_cycle_data("Did not reach hardware sleep state", "❌")
|
|
476
|
-
|
|
477
|
-
return self.hw_sleep_duration is not None
|
|
475
|
+
return self.hw_sleep_duration
|
|
478
476
|
|
|
479
477
|
def capture_command_line(self):
|
|
480
478
|
"""Capture the kernel command line to debug"""
|
|
@@ -674,7 +672,7 @@ class SleepValidator(AmdTool):
|
|
|
674
672
|
def prep(self):
|
|
675
673
|
"""Prepare the system for suspend testing"""
|
|
676
674
|
self.last_suspend = datetime.now()
|
|
677
|
-
self.kernel_log.seek_tail()
|
|
675
|
+
self.kernel_log.seek_tail(self.last_suspend)
|
|
678
676
|
self.db.start_cycle(self.last_suspend)
|
|
679
677
|
self.kernel_duration = 0
|
|
680
678
|
self.hw_sleep_duration = 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amd-debug-tools
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: debug tools for AMD systems
|
|
5
5
|
Author-email: Mario Limonciello <superm1@kernel.org>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -125,8 +125,9 @@ The following optional arguments are supported for this command:
|
|
|
125
125
|
--format FORMAT Format of the report to produce (html, txt or md)
|
|
126
126
|
--report-file File to write the report to
|
|
127
127
|
--tool-debug Enable tool debug logging
|
|
128
|
-
--report-debug
|
|
129
|
-
|
|
128
|
+
--report-debug
|
|
129
|
+
--no-report-debug
|
|
130
|
+
Include debug messages in report (WARNING: can significantly increase report size)
|
|
130
131
|
If the tool is launched with an environment that can call `xdg-open`, the report
|
|
131
132
|
will be opened in a browser.
|
|
132
133
|
|