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 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