amd-debug-tools 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of amd-debug-tools might be problematic. Click here for more details.
- amd_debug/__init__.py +45 -0
- amd_debug/acpi.py +107 -0
- amd_debug/bash/amd-s2idle +89 -0
- amd_debug/battery.py +87 -0
- amd_debug/bios.py +138 -0
- amd_debug/common.py +324 -0
- amd_debug/database.py +331 -0
- amd_debug/failures.py +588 -0
- amd_debug/installer.py +404 -0
- amd_debug/kernel.py +389 -0
- amd_debug/prerequisites.py +1215 -0
- amd_debug/pstate.py +314 -0
- amd_debug/s2idle-hook +72 -0
- amd_debug/s2idle.py +406 -0
- amd_debug/sleep_report.py +453 -0
- amd_debug/templates/html +427 -0
- amd_debug/templates/md +39 -0
- amd_debug/templates/stdout +13 -0
- amd_debug/templates/txt +23 -0
- amd_debug/validator.py +863 -0
- amd_debug/wake.py +111 -0
- amd_debug_tools-0.2.0.dist-info/METADATA +180 -0
- amd_debug_tools-0.2.0.dist-info/RECORD +27 -0
- amd_debug_tools-0.2.0.dist-info/WHEEL +5 -0
- amd_debug_tools-0.2.0.dist-info/entry_points.txt +4 -0
- amd_debug_tools-0.2.0.dist-info/licenses/LICENSE +19 -0
- amd_debug_tools-0.2.0.dist-info/top_level.txt +1 -0
amd_debug/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def amd_s2idle():
|
|
6
|
+
"""Launch the amd-s2idle tool."""
|
|
7
|
+
from . import s2idle # pylint: disable=import-outside-toplevel
|
|
8
|
+
|
|
9
|
+
return s2idle.main()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def amd_bios():
|
|
13
|
+
"""Launch the amd-bios tool."""
|
|
14
|
+
from . import bios # pylint: disable=import-outside-toplevel
|
|
15
|
+
|
|
16
|
+
return bios.main()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def amd_pstate():
|
|
20
|
+
"""Launch the amd-pstate tool."""
|
|
21
|
+
from . import pstate # pylint: disable=import-outside-toplevel
|
|
22
|
+
|
|
23
|
+
return pstate.main()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def install_dep_superset():
|
|
27
|
+
"""Install all supserset dependencies."""
|
|
28
|
+
from . import installer # pylint: disable=import-outside-toplevel
|
|
29
|
+
|
|
30
|
+
return installer.install_dep_superset()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def launch_tool(tool_name):
|
|
34
|
+
"""Launch a tool with the given name and arguments."""
|
|
35
|
+
tools = {
|
|
36
|
+
"amd_s2idle.py": amd_s2idle,
|
|
37
|
+
"amd_bios.py": amd_bios,
|
|
38
|
+
"amd_pstate.py": amd_pstate,
|
|
39
|
+
"install_deps.py": install_dep_superset,
|
|
40
|
+
}
|
|
41
|
+
if tool_name in tools:
|
|
42
|
+
return tools[tool_name]()
|
|
43
|
+
else:
|
|
44
|
+
print(f"\033[91mUnknown exe: {tool_name}\033[0m")
|
|
45
|
+
return False
|
amd_debug/acpi.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import logging
|
|
6
|
+
from amd_debug.common import BIT, read_file
|
|
7
|
+
|
|
8
|
+
ACPI_METHOD = "M460"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def search_acpi_tables(pattern):
|
|
12
|
+
"""Search for a pattern in ACPI tables"""
|
|
13
|
+
p = os.path.join("/", "sys", "firmware", "acpi", "tables")
|
|
14
|
+
|
|
15
|
+
for fn in os.listdir(p):
|
|
16
|
+
if not fn.startswith("SSDT") and not fn.startswith("DSDT"):
|
|
17
|
+
continue
|
|
18
|
+
fp = os.path.join(p, fn)
|
|
19
|
+
with open(fp, "rb") as file:
|
|
20
|
+
content = file.read()
|
|
21
|
+
if pattern.encode() in content:
|
|
22
|
+
return True
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AcpicaTracer:
|
|
27
|
+
"""Class for ACPI tracing"""
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self.acpi_base = os.path.join("/", "sys", "module", "acpi", "parameters")
|
|
31
|
+
keys = [
|
|
32
|
+
"trace_debug_layer",
|
|
33
|
+
"trace_debug_level",
|
|
34
|
+
"trace_method_name",
|
|
35
|
+
"trace_state",
|
|
36
|
+
]
|
|
37
|
+
self.original = {}
|
|
38
|
+
self.supported = False
|
|
39
|
+
for key in keys:
|
|
40
|
+
fname = os.path.join(self.acpi_base, key)
|
|
41
|
+
if not os.path.exists(fname):
|
|
42
|
+
logging.debug("ACPI Notify() debugging not available")
|
|
43
|
+
return
|
|
44
|
+
v = read_file(fname)
|
|
45
|
+
if v and v != "(null)":
|
|
46
|
+
self.original[key] = v
|
|
47
|
+
self.supported = True
|
|
48
|
+
|
|
49
|
+
def _write_expected(self, expected):
|
|
50
|
+
for key, value in expected.items():
|
|
51
|
+
p = os.path.join(self.acpi_base, key)
|
|
52
|
+
if isinstance(value, int):
|
|
53
|
+
t = str(int(value))
|
|
54
|
+
else:
|
|
55
|
+
t = value
|
|
56
|
+
with open(p, "w", encoding="utf-8") as w:
|
|
57
|
+
w.write(t)
|
|
58
|
+
|
|
59
|
+
def trace_notify(self):
|
|
60
|
+
"""Trace notify events"""
|
|
61
|
+
if not self.supported:
|
|
62
|
+
return False
|
|
63
|
+
expected = {
|
|
64
|
+
"trace_debug_layer": BIT(2),
|
|
65
|
+
"trace_debug_level": BIT(2),
|
|
66
|
+
"trace_state": "enable",
|
|
67
|
+
}
|
|
68
|
+
self._write_expected(expected)
|
|
69
|
+
logging.debug("Enabled ACPI debugging for ACPI_LV_INFO/ACPI_EVENTS")
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
def trace_bios(self):
|
|
73
|
+
"""Trace BIOS events"""
|
|
74
|
+
if not self.supported:
|
|
75
|
+
return False
|
|
76
|
+
if not search_acpi_tables(ACPI_METHOD):
|
|
77
|
+
logging.debug(
|
|
78
|
+
"will not work on this system: ACPI tables do not contain %s",
|
|
79
|
+
ACPI_METHOD,
|
|
80
|
+
)
|
|
81
|
+
return False
|
|
82
|
+
expected = {
|
|
83
|
+
"trace_debug_layer": BIT(7),
|
|
84
|
+
"trace_debug_level": BIT(4),
|
|
85
|
+
"trace_method_name": f"\\{ACPI_METHOD}",
|
|
86
|
+
"trace_state": "method",
|
|
87
|
+
}
|
|
88
|
+
self._write_expected(expected)
|
|
89
|
+
logging.debug("Enabled ACPI debugging for BIOS")
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
def disable(self):
|
|
93
|
+
"""Disable ACPI tracing"""
|
|
94
|
+
if not self.supported:
|
|
95
|
+
return False
|
|
96
|
+
expected = {
|
|
97
|
+
"trace_state": "disable",
|
|
98
|
+
}
|
|
99
|
+
self._write_expected(expected)
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
def restore(self):
|
|
103
|
+
"""Restore original ACPI tracing settings"""
|
|
104
|
+
if not self.supported:
|
|
105
|
+
return False
|
|
106
|
+
self._write_expected(self.original)
|
|
107
|
+
return True
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
_cmd_list=(
|
|
2
|
+
'install'
|
|
3
|
+
'uninstall'
|
|
4
|
+
'test'
|
|
5
|
+
'report'
|
|
6
|
+
'version'
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
_general_opts=(
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
_report_opts=(
|
|
13
|
+
'--since'
|
|
14
|
+
'--until'
|
|
15
|
+
'--report-file'
|
|
16
|
+
'--format'
|
|
17
|
+
'--report-debug'
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
_test_opts=(
|
|
21
|
+
'--wait'
|
|
22
|
+
'--count'
|
|
23
|
+
'--duration'
|
|
24
|
+
'--report-file'
|
|
25
|
+
'--format'
|
|
26
|
+
'--force'
|
|
27
|
+
'--logind'
|
|
28
|
+
'--tool-debug'
|
|
29
|
+
'--report-debug'
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
_format_opts=(
|
|
33
|
+
'md'
|
|
34
|
+
'html'
|
|
35
|
+
'txt'
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
_show_test_opts()
|
|
39
|
+
{
|
|
40
|
+
COMPREPLY+=( $(compgen -W '${_test_opts[@]}' -- "$cur") )
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_show_report_opts()
|
|
44
|
+
{
|
|
45
|
+
COMPREPLY+=( $(compgen -W '${_report_opts[@]}' -- "$cur") )
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_show_format_completion()
|
|
49
|
+
{
|
|
50
|
+
COMPREPLY+=( $(compgen -W '${_format_opts[@]}' -- "$cur") )
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
_amd_s2idle()
|
|
54
|
+
{
|
|
55
|
+
local cur prev command arg args
|
|
56
|
+
COMPREPLY=()
|
|
57
|
+
_get_comp_words_by_ref cur prev
|
|
58
|
+
_get_first_arg
|
|
59
|
+
_count_args
|
|
60
|
+
|
|
61
|
+
case $prev in
|
|
62
|
+
--log|--duration|--count|--wait|--since|--report-file)
|
|
63
|
+
return 0
|
|
64
|
+
;;
|
|
65
|
+
--format)
|
|
66
|
+
_show_format_completion
|
|
67
|
+
return 0
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
case $arg in
|
|
71
|
+
test)
|
|
72
|
+
_show_test_opts
|
|
73
|
+
;;
|
|
74
|
+
report)
|
|
75
|
+
_show_report_opts
|
|
76
|
+
;;
|
|
77
|
+
|
|
78
|
+
*)
|
|
79
|
+
#find first command
|
|
80
|
+
if [[ "$args" = "1" ]]; then
|
|
81
|
+
COMPREPLY=( $(compgen -W '${_cmd_list[@]}' -- "$cur") )
|
|
82
|
+
fi
|
|
83
|
+
;;
|
|
84
|
+
esac
|
|
85
|
+
|
|
86
|
+
return 0
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
complete -F _amd_s2idle amd-s2idle
|
amd_debug/battery.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
from pyudev import Context
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _get_property(prop, key, fallback="") -> str:
|
|
7
|
+
"""Get the property from the given key"""
|
|
8
|
+
try:
|
|
9
|
+
return prop.get(key, fallback)
|
|
10
|
+
except UnicodeDecodeError:
|
|
11
|
+
return ""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Batteries:
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
self.pyudev = Context()
|
|
17
|
+
|
|
18
|
+
def _get_battery(self, name) -> object:
|
|
19
|
+
"""Get the battery device object for the given name"""
|
|
20
|
+
for dev in self.pyudev.list_devices(
|
|
21
|
+
subsystem="power_supply", POWER_SUPPLY_TYPE="Battery"
|
|
22
|
+
):
|
|
23
|
+
if not "PNP0C0A" in dev.device_path:
|
|
24
|
+
continue
|
|
25
|
+
desc = _get_property(dev.properties, "POWER_SUPPLY_NAME", "Unknown")
|
|
26
|
+
if desc != name:
|
|
27
|
+
continue
|
|
28
|
+
return dev
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
def get_batteries(self) -> list:
|
|
32
|
+
"""Get a list of battery names on the system"""
|
|
33
|
+
names = []
|
|
34
|
+
for dev in self.pyudev.list_devices(
|
|
35
|
+
subsystem="power_supply", POWER_SUPPLY_TYPE="Battery"
|
|
36
|
+
):
|
|
37
|
+
if not "PNP0C0A" in dev.device_path:
|
|
38
|
+
continue
|
|
39
|
+
names.append(_get_property(dev.properties, "POWER_SUPPLY_NAME", "Unknown"))
|
|
40
|
+
return names
|
|
41
|
+
|
|
42
|
+
def get_energy_unit(self, name) -> str:
|
|
43
|
+
"""Get the energy unit for the given battery name"""
|
|
44
|
+
dev = self._get_battery(name)
|
|
45
|
+
if not dev:
|
|
46
|
+
return ""
|
|
47
|
+
energy = _get_property(dev.properties, "POWER_SUPPLY_ENERGY_NOW")
|
|
48
|
+
if energy:
|
|
49
|
+
return "µWh"
|
|
50
|
+
return "µAh"
|
|
51
|
+
|
|
52
|
+
def get_energy(self, name) -> int:
|
|
53
|
+
"""Get the current energy for the given battery name"""
|
|
54
|
+
dev = self._get_battery(name)
|
|
55
|
+
if not dev:
|
|
56
|
+
return ""
|
|
57
|
+
energy = _get_property(dev.properties, "POWER_SUPPLY_ENERGY_NOW")
|
|
58
|
+
if not energy:
|
|
59
|
+
energy = _get_property(dev.properties, "POWER_SUPPLY_CHARGE_NOW")
|
|
60
|
+
return energy
|
|
61
|
+
|
|
62
|
+
def get_energy_full(self, name) -> int:
|
|
63
|
+
"""Get the energy when full for the given battery name"""
|
|
64
|
+
dev = self._get_battery(name)
|
|
65
|
+
if not dev:
|
|
66
|
+
return ""
|
|
67
|
+
energy = _get_property(dev.properties, "POWER_SUPPLY_ENERGY_FULL")
|
|
68
|
+
if not energy:
|
|
69
|
+
energy = _get_property(dev.properties, "POWER_SUPPLY_CHARGE_FULL")
|
|
70
|
+
return energy
|
|
71
|
+
|
|
72
|
+
def get_description_string(self, name) -> str:
|
|
73
|
+
"""Get a description string for the given battery name"""
|
|
74
|
+
dev = self._get_battery(name)
|
|
75
|
+
if not dev:
|
|
76
|
+
return ""
|
|
77
|
+
man = _get_property(dev.properties, "POWER_SUPPLY_MANUFACTURER", "")
|
|
78
|
+
model = _get_property(dev.properties, "POWER_SUPPLY_MODEL_NAME", "")
|
|
79
|
+
full = self.get_energy_full(name)
|
|
80
|
+
full_design = _get_property(dev.properties, "POWER_SUPPLY_ENERGY_FULL_DESIGN")
|
|
81
|
+
if not full_design:
|
|
82
|
+
full_design = _get_property(
|
|
83
|
+
dev.properties, "POWER_SUPPLY_CHARGE_FULL_DESIGN"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
percent = float(full) / int(full_design)
|
|
87
|
+
return f"Battery {name} ({man} {model}) is operating at {percent:.2%} of design"
|
amd_debug/bios.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""s2idle analysis tool"""
|
|
4
|
+
import argparse
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
from amd_debug.common import (
|
|
8
|
+
AmdTool,
|
|
9
|
+
fatal_error,
|
|
10
|
+
get_log_priority,
|
|
11
|
+
minimum_kernel,
|
|
12
|
+
print_color,
|
|
13
|
+
relaunch_sudo,
|
|
14
|
+
show_log_info,
|
|
15
|
+
version,
|
|
16
|
+
)
|
|
17
|
+
from amd_debug.kernel import get_kernel_log, sscanf_bios_args
|
|
18
|
+
from amd_debug.acpi import AcpicaTracer
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AmdBios(AmdTool):
|
|
22
|
+
"""
|
|
23
|
+
AmdBios is a class which fetches the BIOS events from kernel logs.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, inf, debug):
|
|
27
|
+
log_prefix = "bios" if debug else None
|
|
28
|
+
super().__init__(log_prefix)
|
|
29
|
+
self.kernel_log = get_kernel_log(inf)
|
|
30
|
+
|
|
31
|
+
def set_tracing(self, enable):
|
|
32
|
+
"""Run the action"""
|
|
33
|
+
relaunch_sudo()
|
|
34
|
+
|
|
35
|
+
if not minimum_kernel(6, 16):
|
|
36
|
+
print_color(
|
|
37
|
+
"Support for BIOS debug logging was merged in mainline 6.16, "
|
|
38
|
+
"this tool may not work correctly unless support is manually "
|
|
39
|
+
"backported",
|
|
40
|
+
"🚦",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
tracer = AcpicaTracer()
|
|
44
|
+
ret = tracer.trace_bios() if enable else tracer.disable()
|
|
45
|
+
if ret:
|
|
46
|
+
action = "enabled" if enable else "disabled"
|
|
47
|
+
print_color(f"Set BIOS tracing to {action}", "✅")
|
|
48
|
+
else:
|
|
49
|
+
fatal_error(
|
|
50
|
+
"BIOS tracing not supported, please check your kernel for CONFIG_ACPI_DEBUG"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
def _analyze_kernel_log_line(self, line, priority):
|
|
56
|
+
"""Analyze a line from the kernel log"""
|
|
57
|
+
bios_args = sscanf_bios_args(line)
|
|
58
|
+
if bios_args:
|
|
59
|
+
if isinstance(bios_args, str):
|
|
60
|
+
print_color(bios_args, "🖴")
|
|
61
|
+
else:
|
|
62
|
+
return
|
|
63
|
+
else:
|
|
64
|
+
# strip timestamp
|
|
65
|
+
t = re.sub(r"^\[\s*\d+\.\d+\]", "", line).strip()
|
|
66
|
+
print_color(t, get_log_priority(priority))
|
|
67
|
+
|
|
68
|
+
def run(self):
|
|
69
|
+
"""Exfiltrate from the kernel log"""
|
|
70
|
+
self.kernel_log.process_callback(self._analyze_kernel_log_line)
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def parse_args():
|
|
75
|
+
"""Parse command line arguments"""
|
|
76
|
+
parser = argparse.ArgumentParser(
|
|
77
|
+
description="Parse a combined kernel/BIOS log.",
|
|
78
|
+
)
|
|
79
|
+
subparsers = parser.add_subparsers(help="Possible commands", dest="command")
|
|
80
|
+
parse_cmd = subparsers.add_parser(
|
|
81
|
+
"parse", help="Parse log for kernel and BIOS messages"
|
|
82
|
+
)
|
|
83
|
+
parse_cmd.add_argument(
|
|
84
|
+
"--input",
|
|
85
|
+
help="Optional input file to parse",
|
|
86
|
+
)
|
|
87
|
+
parse_cmd.add_argument(
|
|
88
|
+
"--tool-debug",
|
|
89
|
+
action="store_true",
|
|
90
|
+
help="Enable tool debug logging",
|
|
91
|
+
)
|
|
92
|
+
trace_cmd = subparsers.add_parser("trace", help="Enable or disable tracing")
|
|
93
|
+
trace_cmd.add_argument(
|
|
94
|
+
"--enable",
|
|
95
|
+
action="store_true",
|
|
96
|
+
help="Enable BIOS AML tracing",
|
|
97
|
+
)
|
|
98
|
+
trace_cmd.add_argument(
|
|
99
|
+
"--disable",
|
|
100
|
+
action="store_true",
|
|
101
|
+
help="Disable BIOS AML tracing",
|
|
102
|
+
)
|
|
103
|
+
trace_cmd.add_argument(
|
|
104
|
+
"--tool-debug",
|
|
105
|
+
action="store_true",
|
|
106
|
+
help="Enable tool debug logging",
|
|
107
|
+
)
|
|
108
|
+
subparsers.add_parser("version", help="Show version information")
|
|
109
|
+
|
|
110
|
+
if len(sys.argv) == 1:
|
|
111
|
+
parser.print_help(sys.stderr)
|
|
112
|
+
sys.exit(1)
|
|
113
|
+
|
|
114
|
+
args = parser.parse_args()
|
|
115
|
+
|
|
116
|
+
if args.command == "trace":
|
|
117
|
+
if args.enable and args.disable:
|
|
118
|
+
sys.exit("can't set both enable and disable")
|
|
119
|
+
if not args.enable and not args.disable:
|
|
120
|
+
sys.exit("must set either enable or disable")
|
|
121
|
+
|
|
122
|
+
return args
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def main():
|
|
126
|
+
"""Main function"""
|
|
127
|
+
args = parse_args()
|
|
128
|
+
ret = False
|
|
129
|
+
if args.command == "trace":
|
|
130
|
+
app = AmdBios(None, args.tool_debug)
|
|
131
|
+
ret = app.set_tracing(True if args.enable else False)
|
|
132
|
+
elif args.command == "parse":
|
|
133
|
+
app = AmdBios(args.input, args.tool_debug)
|
|
134
|
+
ret = app.run()
|
|
135
|
+
elif args.command == "version":
|
|
136
|
+
print(version())
|
|
137
|
+
show_log_info()
|
|
138
|
+
return ret
|