amd-debug-tools 0.2.1__tar.gz → 0.2.3__tar.gz

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.

Potentially problematic release.


This version of amd-debug-tools might be problematic. Click here for more details.

Files changed (50) hide show
  1. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/PKG-INFO +4 -3
  2. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/README.md +3 -2
  3. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/common.py +24 -0
  4. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/failures.py +14 -2
  5. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/installer.py +34 -2
  6. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/kernel.py +8 -0
  7. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/prerequisites.py +95 -45
  8. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/s2idle.py +45 -30
  9. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/sleep_report.py +128 -85
  10. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/templates/html +4 -2
  11. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/templates/md +0 -7
  12. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/validator.py +20 -15
  13. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug_tools.egg-info/PKG-INFO +4 -3
  14. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_common.py +112 -0
  15. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_installer.py +8 -6
  16. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_prerequisites.py +204 -45
  17. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_s2idle.py +93 -28
  18. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_sleep_report.py +29 -0
  19. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_validator.py +119 -12
  20. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/LICENSE +0 -0
  21. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/pyproject.toml +0 -0
  22. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/setup.cfg +0 -0
  23. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/__init__.py +0 -0
  24. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/acpi.py +0 -0
  25. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/bash/amd-s2idle +0 -0
  26. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/battery.py +0 -0
  27. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/bios.py +0 -0
  28. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/database.py +0 -0
  29. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/display.py +0 -0
  30. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/pstate.py +0 -0
  31. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/s2idle-hook +0 -0
  32. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/templates/stdout +0 -0
  33. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/templates/txt +0 -0
  34. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug/wake.py +0 -0
  35. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug_tools.egg-info/SOURCES.txt +0 -0
  36. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug_tools.egg-info/dependency_links.txt +0 -0
  37. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug_tools.egg-info/entry_points.txt +0 -0
  38. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug_tools.egg-info/requires.txt +0 -0
  39. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/amd_debug_tools.egg-info/top_level.txt +0 -0
  40. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/launcher.py +0 -0
  41. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_acpi.py +0 -0
  42. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_batteries.py +0 -0
  43. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_bios.py +0 -0
  44. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_database.py +0 -0
  45. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_display.py +0 -0
  46. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_failures.py +0 -0
  47. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_kernel.py +0 -0
  48. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_launcher.py +0 -0
  49. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_pstate.py +0 -0
  50. {amd_debug_tools-0.2.1 → amd_debug_tools-0.2.3}/src/test_wake.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amd-debug-tools
3
- Version: 0.2.1
3
+ Version: 0.2.3
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
 
@@ -101,8 +101,9 @@ The following optional arguments are supported for this command:
101
101
  --format FORMAT Format of the report to produce (html, txt or md)
102
102
  --report-file File to write the report to
103
103
  --tool-debug Enable tool debug logging
104
- --report-debug Include debug messages in the report
105
-
104
+ --report-debug
105
+ --no-report-debug
106
+ Include debug messages in report (WARNING: can significantly increase report size)
106
107
  If the tool is launched with an environment that can call `xdg-open`, the report
107
108
  will be opened in a browser.
108
109
 
@@ -14,6 +14,7 @@ import struct
14
14
  import subprocess
15
15
  import re
16
16
  import sys
17
+ from ast import literal_eval
17
18
  from datetime import date, timedelta
18
19
 
19
20
 
@@ -278,6 +279,19 @@ def get_property_pyudev(properties, key, fallback=""):
278
279
  return ""
279
280
 
280
281
 
282
+ def find_ip_version(base_path, kind, hw_ver) -> bool:
283
+ """Determine if an IP version is present on the system"""
284
+ b = os.path.join(base_path, "ip_discovery", "die", "0", kind, "0")
285
+ for key, expected_value in hw_ver.items():
286
+ p = os.path.join(b, key)
287
+ if not os.path.exists(p):
288
+ return False
289
+ v = int(read_file(p))
290
+ if v != expected_value:
291
+ return False
292
+ return True
293
+
294
+
281
295
  def read_msr(msr, cpu):
282
296
  """Read a Model-Specific Register (MSR) value from the CPU."""
283
297
  p = f"/dev/cpu/{cpu}/msr"
@@ -308,6 +322,16 @@ def running_ssh():
308
322
  return "SSH_CLIENT" in os.environ or "SSH_TTY" in os.environ
309
323
 
310
324
 
325
+ def convert_string_to_bool(str_value) -> bool:
326
+ """convert a string to a boolean value"""
327
+ try:
328
+ value = literal_eval(str_value)
329
+ except (SyntaxError, ValueError):
330
+ value = None
331
+ sys.exit(f"Invalid entry: {str_value}")
332
+ return bool(value)
333
+
334
+
311
335
  def _git_describe() -> str:
312
336
  """Get the git description of the current commit"""
313
337
  try:
@@ -142,7 +142,7 @@ class MissingDriver(S0i3Failure):
142
142
  self.description = f"{slot} driver is missing"
143
143
  self.explanation = (
144
144
  f"No driver has been bound to PCI device {slot} "
145
- "Without a driver, the hardware may be able to enter a low power. "
145
+ "Without a driver, the hardware may be able to enter a low power "
146
146
  "state, but there may be spurious wake up events."
147
147
  )
148
148
 
@@ -416,7 +416,7 @@ class LowHardwareSleepResidency(S0i3Failure):
416
416
  super().__init__()
417
417
  self.description = "System had low hardware sleep residency"
418
418
  self.explanation = (
419
- f"The system was asleep for {timedelta(seconds=duration)}, but only spent {percent:.2%} "
419
+ f"The system was asleep for {timedelta(seconds=duration)}, but only spent {percent/100:.2%} "
420
420
  "of this time in a hardware sleep state. In sleep cycles that are at least "
421
421
  "60 seconds long it's expected you spend above 90 percent of the cycle in "
422
422
  "hardware sleep."
@@ -586,3 +586,15 @@ class RogAllyMcuPowerSave(S0i3Failure):
586
586
  "The MCU powersave feature is disabled which will cause problems "
587
587
  "with the controller after suspend/resume."
588
588
  )
589
+
590
+
591
+ class DmcubTooOld(S0i3Failure):
592
+ """DMCUB microcode is too old"""
593
+
594
+ def __init__(self, current, expected):
595
+ super().__init__()
596
+ self.description = "DMCUB microcode is too old"
597
+ self.explanation = (
598
+ f"The DMCUB microcode version {hex(current)} is older than the"
599
+ f"minimum suggested version {hex(expected)}."
600
+ )
@@ -26,6 +26,7 @@ class Headers: # pylint: disable=too-few-public-methods
26
26
 
27
27
  MissingIasl = "ACPI extraction tool `iasl` is missing"
28
28
  MissingEdidDecode = "EDID decoding tool `edid-decode` is missing"
29
+ MissingDiEdidDecode = "EDID decoding tool `di-edid-decode` is missing"
29
30
  MissingEthtool = "Ethtool is missing"
30
31
  InstallAction = "Attempting to install"
31
32
  MissingFwupd = "Firmware update library `fwupd` is missing"
@@ -35,6 +36,7 @@ class Headers: # pylint: disable=too-few-public-methods
35
36
  MissingTabulate = "Data library `tabulate` is missing"
36
37
  MissingJinja2 = "Template library `jinja2` is missing"
37
38
  MissingSeaborn = "Data visualization library `seaborn` is missing"
39
+ UnknownDistro = "No distro installation support available, install manually"
38
40
 
39
41
 
40
42
  class DistroPackage:
@@ -71,7 +73,8 @@ class DistroPackage:
71
73
  return False
72
74
  installer = ["pacman", "-Sy", self.arch]
73
75
  else:
74
- return False
76
+ print_color(Headers.UnknownDistro, "👀")
77
+ return True
75
78
 
76
79
  try:
77
80
  subprocess.check_call(installer)
@@ -188,6 +191,18 @@ class EdidDecodePackage(DistroPackage):
188
191
  )
189
192
 
190
193
 
194
+ class DisplayInfoPackage(DistroPackage):
195
+ """display info package"""
196
+
197
+ def __init__(self):
198
+ super().__init__(
199
+ deb="libdisplay-info-bin",
200
+ rpm="libdisplay-info",
201
+ arch="libdisplay-info",
202
+ message=Headers.MissingDiEdidDecode,
203
+ )
204
+
205
+
191
206
  class FwupdPackage(DistroPackage):
192
207
  """Fwupd package"""
193
208
 
@@ -261,7 +276,19 @@ class Installer(AmdTool):
261
276
  package = EthtoolPackage()
262
277
  if not package.install():
263
278
  return False
279
+ # can be satisified by either edid-decode or di-edid-decode
264
280
  if "edid-decode" in self.requirements:
281
+ try:
282
+ di_edid = (
283
+ subprocess.call(
284
+ ["di-edid-decode", "--help"],
285
+ stdout=subprocess.DEVNULL,
286
+ stderr=subprocess.DEVNULL,
287
+ )
288
+ == 255
289
+ )
290
+ except FileNotFoundError:
291
+ di_edid = False
265
292
  try:
266
293
  edid = (
267
294
  subprocess.call(
@@ -271,7 +298,12 @@ class Installer(AmdTool):
271
298
  )
272
299
  except FileNotFoundError:
273
300
  edid = False
274
- if not edid:
301
+ if not di_edid and not edid:
302
+ # try to install di-edid-decode first
303
+ package = DisplayInfoPackage()
304
+ if package.install():
305
+ return True
306
+ # fall back to edid-decode instead
275
307
  package = EdidDecodePackage()
276
308
  if not package.install():
277
309
  return False
@@ -19,6 +19,7 @@ def get_kernel_command_line() -> str:
19
19
  "apparmor",
20
20
  "audit",
21
21
  "auto",
22
+ "bluetooth.disable_ertm",
22
23
  "boot",
23
24
  "BOOT_IMAGE",
24
25
  "console",
@@ -29,6 +30,7 @@ def get_kernel_command_line() -> str:
29
30
  "earlycon",
30
31
  "earlyprintk",
31
32
  "ether",
33
+ "init",
32
34
  "initrd",
33
35
  "ip",
34
36
  "LANG",
@@ -47,7 +49,9 @@ def get_kernel_command_line() -> str:
47
49
  "nfs.nfs4_unique_id",
48
50
  "nfsroot",
49
51
  "noplymouth",
52
+ "nowatchdog",
50
53
  "ostree",
54
+ "preempt",
51
55
  "quiet",
52
56
  "rd.dm.uuid",
53
57
  "rd.luks.allow-discards",
@@ -66,12 +70,15 @@ def get_kernel_command_line() -> str:
66
70
  "ro",
67
71
  "root",
68
72
  "rootflags",
73
+ "rootfstype",
69
74
  "roothash",
70
75
  "rw",
71
76
  "security",
77
+ "selinux",
72
78
  "showopts",
73
79
  "splash",
74
80
  "swap",
81
+ "systemd.machine_id",
75
82
  "systemd.mask",
76
83
  "systemd.show_status",
77
84
  "systemd.unit",
@@ -82,6 +89,7 @@ def get_kernel_command_line() -> str:
82
89
  "verbose",
83
90
  "vt.handoff",
84
91
  "zfs",
92
+ "zswap.enabled",
85
93
  ]
86
94
  # remove anything that starts with something in filtered from cmdline
87
95
  return " ".join([x for x in cmdline.split() if not x.startswith(tuple(filtered))])
@@ -26,6 +26,7 @@ from amd_debug.common import (
26
26
  apply_prefix_wrapper,
27
27
  BIT,
28
28
  clear_temporary_message,
29
+ find_ip_version,
29
30
  get_distro,
30
31
  get_pretty_distro,
31
32
  get_property_pyudev,
@@ -48,6 +49,7 @@ from amd_debug.failures import (
48
49
  DevSlpDiskIssue,
49
50
  DevSlpHostIssue,
50
51
  DMArNotEnabled,
52
+ DmcubTooOld,
51
53
  DmiNotSetup,
52
54
  FadtWrong,
53
55
  I2CHidBug,
@@ -132,22 +134,23 @@ class PrerequisiteValidator(AmdTool):
132
134
  if len(edids) == 0:
133
135
  self.db.record_debug("No EDID data found")
134
136
  return True
135
-
136
137
  for name, p in edids.items():
137
- try:
138
- cmd = ["edid-decode", p]
139
- output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL).decode(
140
- "utf-8"
141
- )
142
- except FileNotFoundError:
143
- self.db.record_prereq(
144
- "edid-decode not installed, unable to decode EDID", "👀"
145
- )
146
- return True
147
- except subprocess.CalledProcessError as e:
148
- self.db.record_prereq(f"Failed to capture EDID table: {e.output}", "👀")
149
- return False
150
- self.db.record_debug(apply_prefix_wrapper(f"EDID for {name}:", output))
138
+ output = None
139
+ for tool in ["di-edid-decode", "edid-decode"]:
140
+ try:
141
+ cmd = [tool, p]
142
+ output = subprocess.check_output(
143
+ cmd, stderr=subprocess.DEVNULL
144
+ ).decode("utf-8", errors="ignore")
145
+ break
146
+ except FileNotFoundError:
147
+ self.db.record_debug(f"{cmd} not installed")
148
+ except subprocess.CalledProcessError as e:
149
+ pass
150
+ if not output:
151
+ self.db.record_prereq("Failed to capture EDID table", "👀")
152
+ else:
153
+ self.db.record_debug(apply_prefix_wrapper(f"EDID for {name}:", output))
151
154
  return True
152
155
 
153
156
  def check_amdgpu(self):
@@ -239,7 +242,6 @@ class PrerequisiteValidator(AmdTool):
239
242
  for dev in self.pyudev.list_devices(subsystem="pci", DRIVER="nvme"):
240
243
  # https://git.kernel.org/torvalds/c/e79a10652bbd3
241
244
  if minimum_kernel(6, 10):
242
- self.db.record_debug("New enough kernel to avoid NVME check")
243
245
  break
244
246
  pci_slot_name = dev.properties["PCI_SLOT_NAME"]
245
247
  vendor = dev.properties.get("ID_VENDOR_FROM_DATABASE", "")
@@ -313,7 +315,6 @@ class PrerequisiteValidator(AmdTool):
313
315
  # not needed to check in newer kernels
314
316
  # see https://github.com/torvalds/linux/commit/77f1972bdcf7513293e8bbe376b9fe837310ee9c
315
317
  if minimum_kernel(6, 10):
316
- self.db.record_debug("New enough kernel to avoid HSMP check")
317
318
  return True
318
319
  f = os.path.join("/", "boot", f"config-{platform.uname().release}")
319
320
  if os.path.exists(f):
@@ -395,6 +396,53 @@ class PrerequisiteValidator(AmdTool):
395
396
  )
396
397
  return True
397
398
 
399
+ def check_dpia_pg_dmcub(self):
400
+ """Check if DMUB is new enough to PG DPIA when no USB4 present"""
401
+ usb4_found = False
402
+ for device in self.pyudev.list_devices(subsystem="pci", PCI_CLASS="C0340"):
403
+ usb4_found = True
404
+ break
405
+ if usb4_found:
406
+ self.db.record_debug("USB4 routers found, no need to check DMCUB version")
407
+ return True
408
+ # Check if matching DCN present
409
+ for device in self.pyudev.list_devices(subsystem="pci"):
410
+ current = None
411
+ klass = device.properties.get("PCI_CLASS")
412
+ if klass not in ["30000", "38000"]:
413
+ continue
414
+ pci_id = device.properties.get("PCI_ID")
415
+ if not pci_id.startswith("1002"):
416
+ continue
417
+ hw_ver = {"major": 3, "minor": 5, "revision": 0}
418
+ if not find_ip_version(device.sys_path, "DMU", hw_ver):
419
+ continue
420
+
421
+ # DCN was found, lookup version from sysfs
422
+ p = os.path.join(device.sys_path, "fw_version", "dmcub_fw_version")
423
+ if os.path.exists(p):
424
+ current = int(read_file(p), 16)
425
+
426
+ # no sysfs, try to look for version from debugfs
427
+ if not current:
428
+ slot = device.properties["PCI_SLOT_NAME"]
429
+ p = os.path.join(
430
+ "/", "sys", "kernel", "debug", "dri", slot, "amdgpu_firmware_info"
431
+ )
432
+ contents = read_file(p)
433
+ for line in contents.split("\n"):
434
+ if not line.startswith("DMCUB"):
435
+ continue
436
+ current = int(line.split()[-1], 16)
437
+ if current:
438
+ expected = 0x09001B00
439
+ if current >= expected:
440
+ return True
441
+ self.db.record_prereq("DMCUB Firmware is outdated", "❌")
442
+ self.failures += [DmcubTooOld(current, expected)]
443
+ return False
444
+ return True
445
+
398
446
  def check_usb4(self):
399
447
  """Check if the thunderbolt driver is loaded"""
400
448
  slots = []
@@ -474,7 +522,7 @@ class PrerequisiteValidator(AmdTool):
474
522
  f"{keys['sys_vendor']} {keys['product_name']} ({keys['product_family']})",
475
523
  "💻",
476
524
  )
477
- debug_str = "DMI data:\n"
525
+ debug_str = "DMI|value\n"
478
526
  for key, value in keys.items():
479
527
  if (
480
528
  "product_name" in key
@@ -482,7 +530,7 @@ class PrerequisiteValidator(AmdTool):
482
530
  or "product_family" in key
483
531
  ):
484
532
  continue
485
- debug_str += f"{key}: {value}\n"
533
+ debug_str += f"{key}| {value}\n"
486
534
  self.db.record_debug(debug_str)
487
535
  return True
488
536
 
@@ -698,9 +746,13 @@ class PrerequisiteValidator(AmdTool):
698
746
  interface = device.properties.get("INTERFACE")
699
747
  cmd = ["ethtool", interface]
700
748
  wol_supported = False
701
- output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL).decode(
702
- "utf-8"
703
- )
749
+ try:
750
+ output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL).decode(
751
+ "utf-8"
752
+ )
753
+ except FileNotFoundError:
754
+ self.db.record_prereq(f"ethtool is missing", "👀")
755
+ return True
704
756
  for line in output.split("\n"):
705
757
  if "Supports Wake-on" in line:
706
758
  val = line.split(":")[1].strip()
@@ -859,7 +911,7 @@ class PrerequisiteValidator(AmdTool):
859
911
  devices = []
860
912
  for dev in self.pyudev.list_devices(subsystem="pci"):
861
913
  devices.append(dev)
862
- debug_str = "PCI devices\n"
914
+ debug_str = "PCI Slot | Vendor | Class | ID | ACPI path\n"
863
915
  for dev in devices:
864
916
  pci_id = dev.properties["PCI_ID"].lower()
865
917
  pci_slot_name = dev.properties["PCI_SLOT_NAME"]
@@ -882,15 +934,12 @@ class PrerequisiteValidator(AmdTool):
882
934
  p = os.path.join(dev.sys_path, "firmware_node", "path")
883
935
  if os.path.exists(p):
884
936
  acpi = read_file(p)
885
- debug_str += (
886
- f"{prefix}{pci_slot_name} : "
887
- f"{database_vendor} {database_class} [{pci_id}] : {acpi}\n"
888
- )
889
937
  else:
890
- debug_str += (
891
- f"{prefix}{pci_slot_name} : "
892
- f"{database_vendor} {database_class} [{pci_id}]\n"
893
- )
938
+ acpi = ""
939
+ debug_str += (
940
+ f"{prefix}{pci_slot_name} | "
941
+ f"{database_vendor} | {database_class} | {pci_id} | {acpi}\n"
942
+ )
894
943
  if debug_str:
895
944
  self.db.record_debug(debug_str)
896
945
 
@@ -907,12 +956,8 @@ class PrerequisiteValidator(AmdTool):
907
956
  if status == 0:
908
957
  continue
909
958
  devices.append(dev)
910
- debug_str = "ACPI name: ACPI path [driver]\n"
959
+ debug_str = "ACPI name | ACPI path | Kernel driver\n"
911
960
  for dev in devices:
912
- if dev == devices[-1]:
913
- prefix = "└─"
914
- else:
915
- prefix = "│ "
916
961
  p = os.path.join(dev.sys_path, "path")
917
962
  pth = read_file(p)
918
963
  p = os.path.join(dev.sys_path, "physical_node", "driver")
@@ -920,7 +965,7 @@ class PrerequisiteValidator(AmdTool):
920
965
  driver = os.path.basename(os.readlink(p))
921
966
  else:
922
967
  driver = None
923
- debug_str += f"{prefix}{dev.sys_name}: {pth} [{driver}]\n"
968
+ debug_str += f"{dev.sys_name} | {pth} | {driver}\n"
924
969
  if debug_str:
925
970
  self.db.record_debug(debug_str)
926
971
  return True
@@ -949,6 +994,8 @@ class PrerequisiteValidator(AmdTool):
949
994
  stderr=subprocess.DEVNULL,
950
995
  )
951
996
  self.db.record_debug_file(f"{prefix}.dsl")
997
+ except FileNotFoundError as e:
998
+ self.db.record_prereq(f"Failed to capture ACPI table: {e}", "👀")
952
999
  except subprocess.CalledProcessError as e:
953
1000
  self.db.record_prereq(
954
1001
  f"Failed to capture ACPI table: {e.output}", "👀"
@@ -1098,14 +1145,16 @@ class PrerequisiteValidator(AmdTool):
1098
1145
  if not found_iommu:
1099
1146
  self.db.record_prereq("IOMMU disabled", "✅")
1100
1147
  return True
1101
- debug_str += "DMA protection:\n"
1102
- for dev in self.pyudev.list_devices(
1103
- subsystem="thunderbolt", DEVTYPE="thunderbolt_domain"
1104
- ):
1105
- p = os.path.join(dev.sys_path, "iommu_dma_protection")
1106
- v = int(read_file(p))
1107
- debug_str += f"\t{p}: {v}\n"
1108
- found_dmar = v == 1
1148
+ p = os.path.join("/", "sys", "firmware", "acpi", "tables", "IVRS")
1149
+ with open(p, "rb") as f:
1150
+ data = f.read()
1151
+ if len(data) < 40:
1152
+ raise ValueError(
1153
+ "IVRS table appears too small to contain virtualization info."
1154
+ )
1155
+ virt_info = struct.unpack_from("I", data, 36)[0]
1156
+ debug_str += f"Virtualization info: 0x{virt_info:x}"
1157
+ found_dmar = (virt_info & 0x2) != 0
1109
1158
  self.db.record_debug(debug_str)
1110
1159
  if not found_dmar:
1111
1160
  self.db.record_prereq(
@@ -1205,6 +1254,7 @@ class PrerequisiteValidator(AmdTool):
1205
1254
  self.check_smt,
1206
1255
  self.check_iommu,
1207
1256
  self.check_asus_rog_ally,
1257
+ self.check_dpia_pg_dmcub,
1208
1258
  ]
1209
1259
 
1210
1260
  checks += [
@@ -9,6 +9,7 @@ import sqlite3
9
9
 
10
10
  from datetime import date, timedelta, datetime
11
11
  from amd_debug.common import (
12
+ convert_string_to_bool,
12
13
  colorize_choices,
13
14
  is_root,
14
15
  relaunch_sudo,
@@ -32,6 +33,7 @@ class Defaults:
32
33
  since = date.today() - timedelta(days=60)
33
34
  until = date.today() + timedelta(days=1)
34
35
  format_choices = ["txt", "md", "html", "stdout"]
36
+ boolean_choices = ["true", "false"]
35
37
 
36
38
 
37
39
  class Headers:
@@ -47,6 +49,7 @@ class Headers:
47
49
  FormatDescription = "What format to output the report in"
48
50
  MaxDurationDescription = "What is the maximum suspend cycle length (seconds)"
49
51
  MaxWaitDescription = "What is the maximum time between suspend cycles (seconds)"
52
+ ReportDebugDescription = "Enable debug output in report (increased size)"
50
53
 
51
54
 
52
55
  def display_report_file(fname, fmt) -> None:
@@ -85,7 +88,7 @@ def get_report_format() -> str:
85
88
  return "html"
86
89
 
87
90
 
88
- def prompt_report_arguments(since, until, fname, fmt) -> str:
91
+ def prompt_report_arguments(since, until, fname, fmt, report_debug) -> str:
89
92
  """Prompt user for report configuration"""
90
93
  if not since:
91
94
  default = Defaults.since
@@ -114,7 +117,16 @@ def prompt_report_arguments(since, until, fname, fmt) -> str:
114
117
  fmt = get_report_format()
115
118
  if fmt not in Defaults.format_choices:
116
119
  sys.exit(f"Invalid format: {fmt}")
117
- 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]
118
130
 
119
131
 
120
132
  def prompt_test_arguments(duration, wait, count, rand) -> list:
@@ -157,7 +169,9 @@ def prompt_test_arguments(duration, wait, count, rand) -> list:
157
169
  def report(since, until, fname, fmt, tool_debug, report_debug) -> bool:
158
170
  """Generate a report from previous sleep cycles"""
159
171
  try:
160
- 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
+ )
161
175
  except KeyboardInterrupt:
162
176
  sys.exit("\nReport generation cancelled")
163
177
  try:
@@ -197,6 +211,14 @@ def run_test_cycle(
197
211
  print("Failed to install dependencies")
198
212
  return False
199
213
 
214
+ try:
215
+ duration, wait, count = prompt_test_arguments(duration, wait, count, rand)
216
+ since, until, fname, fmt, report_debug = prompt_report_arguments(
217
+ datetime.now().isoformat(), Defaults.until.isoformat(), fname, fmt, True
218
+ )
219
+ except KeyboardInterrupt:
220
+ sys.exit("\nTest cancelled")
221
+
200
222
  try:
201
223
  app = PrerequisiteValidator(debug)
202
224
  run = app.run()
@@ -207,37 +229,32 @@ def run_test_cycle(
207
229
 
208
230
  if run or force:
209
231
  app = SleepValidator(tool_debug=debug, bios_debug=bios_debug)
210
- try:
211
- duration, wait, count = prompt_test_arguments(duration, wait, count, rand)
212
- since, until, fname, fmt = prompt_report_arguments(
213
- datetime.now().isoformat(), Defaults.until.isoformat(), fname, fmt
214
- )
215
- except KeyboardInterrupt:
216
- sys.exit("\nTest cancelled")
217
232
 
218
- app.run(
233
+ run = app.run(
219
234
  duration=duration,
220
235
  wait=wait,
221
236
  count=count,
222
237
  rand=rand,
223
238
  logind=logind,
224
239
  )
240
+ else:
241
+ since = None
242
+ until = None
243
+
244
+ app = SleepReport(
245
+ since=since,
246
+ until=until,
247
+ fname=fname,
248
+ fmt=fmt,
249
+ tool_debug=debug,
250
+ report_debug=report_debug,
251
+ )
252
+ app.run()
225
253
 
226
- app = SleepReport(
227
- since=since,
228
- until=until,
229
- fname=fname,
230
- fmt=fmt,
231
- tool_debug=debug,
232
- report_debug=True,
233
- )
234
- app.run()
235
-
236
- # open report in browser if it's html
237
- display_report_file(fname, fmt)
254
+ # open report in browser if it's html
255
+ display_report_file(fname, fmt)
238
256
 
239
- return True
240
- return False
257
+ return True
241
258
 
242
259
 
243
260
  def install(debug) -> None:
@@ -294,10 +311,8 @@ def parse_args():
294
311
  test_cmd.add_argument(
295
312
  "--random",
296
313
  action="store_true",
297
- help=(
298
- "Run sleep cycles for random durations and wait, using the "
299
- "--duration and --wait arguments as an upper bound",
300
- ),
314
+ help="Run sleep cycles for random durations and wait, using the "
315
+ "--duration and --wait arguments as an upper bound",
301
316
  )
302
317
  test_cmd.add_argument(
303
318
  "--force",
@@ -347,7 +362,7 @@ def parse_args():
347
362
  )
348
363
  report_cmd.add_argument(
349
364
  "--report-debug",
350
- action="store_true",
365
+ action=argparse.BooleanOptionalAction,
351
366
  help="Include debug messages in report (WARNING: can significantly increase report size)",
352
367
  )
353
368