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.
@@ -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: %s" % match)
209
+ self.db.record_debug(f"WCN6855 version string: {match}")
171
210
  objects = match.split()
172
- for i in range(0, len(objects)):
173
- if objects[i] == "fw_build_id":
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
- self.db.record_prereq(f"USB3 driver `xhci_hcd` bound to {slot}", "✅")
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
- self.db.record_prereq(f"USB4 driver `thunderbolt` bound to {slot}", "✅")
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
- "%s (family %x model %x)"
482
- % (self.cpu_model_string, self.cpu_family, self.cpu_model),
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 specific to 6.0 or later indicating FADT had a bit set."""
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 w:
674
+ with open(p, "w", encoding="utf-8") as _w:
579
675
  pass
580
676
  except PermissionError:
581
- self.db.record_prereq("%s" % Headers.RootError, "👀")
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 ASIC/firmware, please manually wake the system",
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: %sabled" % val)
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
- output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL).decode(
658
- "utf-8"
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
- f"Platform may have low hardware sleep residency with Wake-on-lan disabled. Run `ethtool -s {interface} wol g` to enable it if necessary.",
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
- "8c36f7ee-cc11-4a36-b090-6363f54ecac2": "0.1.26", # https://gitlab.freedesktop.org/drm/amd/-/issues/3443
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
- f"Platform may have problems resuming. Upgrade the firmware for '{device.get_name()}' if you have problems.",
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 = "| " if dev != devices[-1] else "└─"
796
- debug_str += "{prefix}{name} [{acpi_hid}] : {acpi_path}\n".format(
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 = f"echo {parent.sys_path.split('/')[-1]} | sudo tee /sys/bus/i2c/drivers/{parent.driver}/unbind"
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} : {vendor} {cls} [{id}] : {acpi}\n".format(
838
- prefix=prefix,
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 += "{prefix}{pci_slot_name} : {vendor} {cls} [{id}]\n".format(
848
- prefix=prefix,
849
- vendor=database_vendor,
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("%s.dsl" % prefix)
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, but the system has {cpu_count} 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
- self.db.record_prereq("PC6 and CC6 enabled", "✅")
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
- for dev in self.pyudev.list_devices(
1068
- subsystem="thunderbolt", DEVTYPE="thunderbolt_domain"
1069
- ):
1070
- p = os.path.join(dev.sys_path, "iommu_dma_protection")
1071
- v = int(read_file(p))
1072
- debug_str += f"\t{p}: {v}\n"
1073
- found_dmar = v == 1
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. Upgrade your firmware or add pcie_port_pm=off to kernel command line if you have problems.",
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 is_root, relaunch_sudo, show_log_info, version, running_ssh
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 (user may need to call tool with sudo -E)
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
- f"To display report automatically in browser launch tool with '-E' argument (Example: sudo -E {sys.argv[0]})"
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(f"{Headers.FormatDescription} (default {get_report_format()})? ")
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
- return [since, until, get_report_file(fname, fmt), fmt]
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(since, until, fname, fmt)
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 test(
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=True,
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 or can be used to hook into systemd for building reports later.\n"
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="Run sleep cycles for random durations and wait, using the --duration and --wait arguments as an upper bound",
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="store_true",
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 = test(
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 and markdown formats
436
- if len(self.df.index) > 1 and (self.format == "html" or self.format == "md"):
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
@@ -28,12 +28,5 @@ Measured {{ prereq_date }}.
28
28
  ## Summary
29
29
  {{ summary }}
30
30
 
31
- {% if battery_svg %}
32
- {{ battery_svg | safe }}
33
- {% endif %}
34
- {% if hwsleep_svg %}
35
- {{ hwsleep_svg | safe }}
36
- {% endif %}
37
-
38
31
  ## Failures reported
39
32
  {{ failures }}
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.record_cycle_data("HW sleep statistics file missing", "❌")
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.0
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 Include debug messages in the report
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