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
|
@@ -0,0 +1,1215 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
import re
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
import struct
|
|
12
|
+
import pyudev
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from packaging import version
|
|
15
|
+
|
|
16
|
+
from amd_debug.wake import WakeIRQ
|
|
17
|
+
from amd_debug.kernel import get_kernel_log, SystemdLogger, DmesgLogger
|
|
18
|
+
from amd_debug.common import (
|
|
19
|
+
BIT,
|
|
20
|
+
clear_temporary_message,
|
|
21
|
+
get_distro,
|
|
22
|
+
get_pretty_distro,
|
|
23
|
+
get_property_pyudev,
|
|
24
|
+
is_root,
|
|
25
|
+
minimum_kernel,
|
|
26
|
+
print_color,
|
|
27
|
+
print_temporary_message,
|
|
28
|
+
read_file,
|
|
29
|
+
read_msr,
|
|
30
|
+
AmdTool,
|
|
31
|
+
)
|
|
32
|
+
from amd_debug.battery import Batteries
|
|
33
|
+
from amd_debug.database import SleepDatabase
|
|
34
|
+
from amd_debug.failures import (
|
|
35
|
+
AcpiNvmeStorageD3Enable,
|
|
36
|
+
AmdHsmpBug,
|
|
37
|
+
AmdgpuPpFeatureMask,
|
|
38
|
+
ASpmWrong,
|
|
39
|
+
DeepSleep,
|
|
40
|
+
DevSlpDiskIssue,
|
|
41
|
+
DevSlpHostIssue,
|
|
42
|
+
DMArNotEnabled,
|
|
43
|
+
DmiNotSetup,
|
|
44
|
+
FadtWrong,
|
|
45
|
+
I2CHidBug,
|
|
46
|
+
KernelRingBufferWrapped,
|
|
47
|
+
LimitedCores,
|
|
48
|
+
MissingAmdgpu,
|
|
49
|
+
MissingAmdgpuFirmware,
|
|
50
|
+
MissingAmdPmc,
|
|
51
|
+
MissingDriver,
|
|
52
|
+
MissingIommuACPI,
|
|
53
|
+
MissingIommuPolicy,
|
|
54
|
+
MissingThunderbolt,
|
|
55
|
+
MissingXhciHcd,
|
|
56
|
+
MSRFailure,
|
|
57
|
+
RogAllyMcuPowerSave,
|
|
58
|
+
RogAllyOldMcu,
|
|
59
|
+
SleepModeWrong,
|
|
60
|
+
SMTNotEnabled,
|
|
61
|
+
TaintedKernel,
|
|
62
|
+
UnservicedGpio,
|
|
63
|
+
UnsupportedModel,
|
|
64
|
+
WCN6855Bug,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# test if fwupd can report device firmware versions
|
|
68
|
+
try:
|
|
69
|
+
import gi
|
|
70
|
+
from gi.repository import GLib as _
|
|
71
|
+
|
|
72
|
+
gi.require_version("Fwupd", "2.0")
|
|
73
|
+
from gi.repository import Fwupd # pylint: disable=wrong-import-position
|
|
74
|
+
|
|
75
|
+
FWUPD = True
|
|
76
|
+
except ImportError:
|
|
77
|
+
FWUPD = False
|
|
78
|
+
except ValueError:
|
|
79
|
+
FWUPD = False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class Headers:
|
|
83
|
+
"""Headers for the script"""
|
|
84
|
+
|
|
85
|
+
NvmeSimpleSuspend = "platform quirk: setting simple suspend"
|
|
86
|
+
RootError = "Must be executed by root user"
|
|
87
|
+
BrokenPrerequisites = "Your system does not meet s2idle prerequisites!"
|
|
88
|
+
ExplanationReport = "Explanations for your system"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class PrerequisiteValidator(AmdTool):
|
|
92
|
+
"""Class to validate the prerequisites for s2idle"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, tool_debug):
|
|
95
|
+
log_prefix = "s2idle" if tool_debug else None
|
|
96
|
+
super().__init__(log_prefix)
|
|
97
|
+
|
|
98
|
+
self.kernel_log = get_kernel_log()
|
|
99
|
+
if not is_root():
|
|
100
|
+
raise PermissionError("not root user")
|
|
101
|
+
self.cpu_family = None
|
|
102
|
+
self.cpu_model = None
|
|
103
|
+
self.cpu_model_string = None
|
|
104
|
+
self.pyudev = pyudev.Context()
|
|
105
|
+
self.failures = []
|
|
106
|
+
self.db = SleepDatabase()
|
|
107
|
+
self.db.start_cycle(datetime.now())
|
|
108
|
+
self.debug = tool_debug
|
|
109
|
+
self.distro = get_distro()
|
|
110
|
+
self.cmdline = read_file(os.path.join("/proc", "cmdline"))
|
|
111
|
+
self.irqs = []
|
|
112
|
+
self.smu_version = ""
|
|
113
|
+
self.smu_program = ""
|
|
114
|
+
|
|
115
|
+
def capture_once(self):
|
|
116
|
+
"""Capture the prerequisites once"""
|
|
117
|
+
if not self.db.get_last_prereq_ts():
|
|
118
|
+
self.run()
|
|
119
|
+
|
|
120
|
+
def check_amdgpu(self):
|
|
121
|
+
"""Check for the AMDGPU driver"""
|
|
122
|
+
for device in self.pyudev.list_devices(subsystem="pci"):
|
|
123
|
+
klass = device.properties.get("PCI_CLASS")
|
|
124
|
+
if klass not in ["30000", "38000"]:
|
|
125
|
+
continue
|
|
126
|
+
pci_id = device.properties.get("PCI_ID")
|
|
127
|
+
if not pci_id.startswith("1002"):
|
|
128
|
+
continue
|
|
129
|
+
if device.properties.get("DRIVER") != "amdgpu":
|
|
130
|
+
self.db.record_prereq("GPU driver `amdgpu` not loaded", "❌")
|
|
131
|
+
self.failures += [MissingAmdgpu()]
|
|
132
|
+
return False
|
|
133
|
+
slot = device.properties.get("PCI_SLOT_NAME")
|
|
134
|
+
|
|
135
|
+
self.db.record_prereq(f"GPU driver `amdgpu` bound to {slot}", "✅")
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
def check_amdgpu_parameters(self):
|
|
139
|
+
"""Check for AMDGPU parameters"""
|
|
140
|
+
p = os.path.join("/", "sys", "module", "amdgpu", "parameters", "ppfeaturemask")
|
|
141
|
+
if os.path.exists(p):
|
|
142
|
+
v = read_file(p)
|
|
143
|
+
if v != "0xfff7bfff":
|
|
144
|
+
self.db.record_prereq(f"AMDGPU ppfeaturemask overridden to {v}", "❌")
|
|
145
|
+
self.failures += [AmdgpuPpFeatureMask()]
|
|
146
|
+
return False
|
|
147
|
+
if not self.kernel_log:
|
|
148
|
+
message = "Unable to test for amdgpu from kernel log"
|
|
149
|
+
self.db.record_prereq(message, "🚦")
|
|
150
|
+
return True
|
|
151
|
+
self.kernel_log.seek()
|
|
152
|
+
match = self.kernel_log.match_pattern("Direct firmware load for amdgpu.*failed")
|
|
153
|
+
if match and not "amdgpu/isp" in match:
|
|
154
|
+
self.db.record_prereq("GPU firmware missing", "❌")
|
|
155
|
+
self.failures += [MissingAmdgpuFirmware([match])]
|
|
156
|
+
return False
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
def check_wcn6855_bug(self):
|
|
160
|
+
"""Check if WCN6855 WLAN is affected by a bug that causes spurious wakeups"""
|
|
161
|
+
if not self.kernel_log:
|
|
162
|
+
message = "Unable to test for wcn6855 bug from kernel log"
|
|
163
|
+
self.db.record_prereq(message, "🚦")
|
|
164
|
+
return True
|
|
165
|
+
wcn6855 = False
|
|
166
|
+
self.kernel_log.seek()
|
|
167
|
+
if self.kernel_log.match_pattern("ath11k_pci.*wcn6855"):
|
|
168
|
+
match = self.kernel_log.match_pattern("ath11k_pci.*fw_version")
|
|
169
|
+
if match:
|
|
170
|
+
self.db.record_debug("WCN6855 version string: %s" % match)
|
|
171
|
+
objects = match.split()
|
|
172
|
+
for i in range(0, len(objects)):
|
|
173
|
+
if objects[i] == "fw_build_id":
|
|
174
|
+
wcn6855 = objects[i + 1]
|
|
175
|
+
|
|
176
|
+
if wcn6855:
|
|
177
|
+
components = wcn6855.split(".")
|
|
178
|
+
if int(components[-1]) >= 37 or int(components[-1]) == 23:
|
|
179
|
+
self.db.record_prereq(
|
|
180
|
+
f"WCN6855 WLAN (fw build id {wcn6855})",
|
|
181
|
+
"✅",
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
self.db.record_prereq(
|
|
185
|
+
f"WCN6855 WLAN may cause spurious wakeups (fw version {wcn6855})",
|
|
186
|
+
"❌",
|
|
187
|
+
)
|
|
188
|
+
self.failures += [WCN6855Bug()]
|
|
189
|
+
|
|
190
|
+
return True
|
|
191
|
+
|
|
192
|
+
def check_storage(self):
|
|
193
|
+
"""Check if storage devices are supported"""
|
|
194
|
+
has_sata = False
|
|
195
|
+
has_ahci = False
|
|
196
|
+
valid_nvme = {}
|
|
197
|
+
invalid_nvme = {}
|
|
198
|
+
valid_sata = False
|
|
199
|
+
valid_ahci = False
|
|
200
|
+
|
|
201
|
+
if not self.kernel_log:
|
|
202
|
+
message = "Unable to test storage from kernel log"
|
|
203
|
+
self.db.record_prereq(message, "🚦")
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
for dev in self.pyudev.list_devices(subsystem="pci", DRIVER="nvme"):
|
|
207
|
+
# https://git.kernel.org/torvalds/c/e79a10652bbd3
|
|
208
|
+
if minimum_kernel(6, 10):
|
|
209
|
+
self.db.record_debug("New enough kernel to avoid NVME check")
|
|
210
|
+
break
|
|
211
|
+
pci_slot_name = dev.properties["PCI_SLOT_NAME"]
|
|
212
|
+
vendor = dev.properties.get("ID_VENDOR_FROM_DATABASE", "")
|
|
213
|
+
model = dev.properties.get("ID_MODEL_FROM_DATABASE", "")
|
|
214
|
+
message = f"{vendor} {model}"
|
|
215
|
+
self.kernel_log.seek()
|
|
216
|
+
pattern = f"{pci_slot_name}.*{Headers.NvmeSimpleSuspend}"
|
|
217
|
+
if self.kernel_log.match_pattern(pattern):
|
|
218
|
+
valid_nvme[pci_slot_name] = message
|
|
219
|
+
if pci_slot_name not in valid_nvme:
|
|
220
|
+
invalid_nvme[pci_slot_name] = message
|
|
221
|
+
|
|
222
|
+
for dev in self.pyudev.list_devices(subsystem="pci", DRIVER="ahci"):
|
|
223
|
+
has_ahci = True
|
|
224
|
+
break
|
|
225
|
+
|
|
226
|
+
for dev in self.pyudev.list_devices(subsystem="block", ID_BUS="ata"):
|
|
227
|
+
has_sata = True
|
|
228
|
+
break
|
|
229
|
+
|
|
230
|
+
# Test AHCI
|
|
231
|
+
if has_ahci:
|
|
232
|
+
self.kernel_log.seek()
|
|
233
|
+
pattern = "ahci.*flags.*sadm.*sds"
|
|
234
|
+
if self.kernel_log.match_pattern(pattern):
|
|
235
|
+
valid_ahci = True
|
|
236
|
+
# Test SATA
|
|
237
|
+
if has_sata:
|
|
238
|
+
self.kernel_log.seek()
|
|
239
|
+
pattern = "ata.*Features.*Dev-Sleep"
|
|
240
|
+
if self.kernel_log.match_pattern(pattern):
|
|
241
|
+
valid_sata = True
|
|
242
|
+
|
|
243
|
+
if invalid_nvme:
|
|
244
|
+
for disk, _name in invalid_nvme.items():
|
|
245
|
+
message = f"NVME {invalid_nvme[disk].strip()} is not configured for s2idle in BIOS"
|
|
246
|
+
self.db.record_prereq(message, "❌")
|
|
247
|
+
num = len(invalid_nvme) + len(valid_nvme)
|
|
248
|
+
self.failures += [AcpiNvmeStorageD3Enable(invalid_nvme[disk], num)]
|
|
249
|
+
if valid_nvme:
|
|
250
|
+
for disk, _name in valid_nvme.items():
|
|
251
|
+
message = (
|
|
252
|
+
f"NVME {valid_nvme[disk].strip()} is configured for s2idle in BIOS"
|
|
253
|
+
)
|
|
254
|
+
self.db.record_prereq(message, "✅")
|
|
255
|
+
if has_sata:
|
|
256
|
+
if valid_sata:
|
|
257
|
+
message = "SATA supports DevSlp feature"
|
|
258
|
+
self.db.record_prereq(message, "✅")
|
|
259
|
+
else:
|
|
260
|
+
message = "SATA does not support DevSlp feature"
|
|
261
|
+
self.db.record_prereq(message, "❌")
|
|
262
|
+
self.failures += [DevSlpDiskIssue()]
|
|
263
|
+
if has_ahci:
|
|
264
|
+
if valid_ahci:
|
|
265
|
+
message = "AHCI is configured for DevSlp in BIOS"
|
|
266
|
+
self.db.record_prereq(message, "✅")
|
|
267
|
+
else:
|
|
268
|
+
message = "AHCI is not configured for DevSlp in BIOS"
|
|
269
|
+
self.db.record_prereq(message, "🚦")
|
|
270
|
+
self.failures += [DevSlpHostIssue()]
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
(len(invalid_nvme) == 0)
|
|
274
|
+
and (valid_sata or not has_sata)
|
|
275
|
+
and (valid_ahci or not has_sata)
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
def check_amd_hsmp(self):
|
|
279
|
+
"""Check for AMD HSMP driver"""
|
|
280
|
+
# not needed to check in newer kernels
|
|
281
|
+
# see https://github.com/torvalds/linux/commit/77f1972bdcf7513293e8bbe376b9fe837310ee9c
|
|
282
|
+
if minimum_kernel(6, 10):
|
|
283
|
+
self.db.record_debug("New enough kernel to avoid HSMP check")
|
|
284
|
+
return True
|
|
285
|
+
f = os.path.join("/", "boot", f"config-{platform.uname().release}")
|
|
286
|
+
if os.path.exists(f):
|
|
287
|
+
kconfig = read_file(f)
|
|
288
|
+
if "CONFIG_AMD_HSMP=y" in kconfig:
|
|
289
|
+
self.db.record_prereq(
|
|
290
|
+
"HSMP driver `amd_hsmp` driver may conflict with amd_pmc",
|
|
291
|
+
"❌",
|
|
292
|
+
)
|
|
293
|
+
self.failures += [AmdHsmpBug()]
|
|
294
|
+
return False
|
|
295
|
+
|
|
296
|
+
cmdline = read_file(os.path.join("/proc", "cmdline"))
|
|
297
|
+
blocked = "initcall_blacklist=hsmp_plt_init" in cmdline
|
|
298
|
+
|
|
299
|
+
p = os.path.join("/", "sys", "module", "amd_hsmp")
|
|
300
|
+
if os.path.exists(p) and not blocked:
|
|
301
|
+
self.db.record_prereq("`amd_hsmp` driver may conflict with amd_pmc", "❌")
|
|
302
|
+
self.failures += [AmdHsmpBug()]
|
|
303
|
+
return False
|
|
304
|
+
|
|
305
|
+
self.db.record_prereq(
|
|
306
|
+
f"HSMP driver `amd_hsmp` not detected (blocked: {blocked})",
|
|
307
|
+
"✅",
|
|
308
|
+
)
|
|
309
|
+
return True
|
|
310
|
+
|
|
311
|
+
def check_amd_pmc(self):
|
|
312
|
+
"""Check if the amd_pmc driver is loaded"""
|
|
313
|
+
for device in self.pyudev.list_devices(subsystem="platform", DRIVER="amd_pmc"):
|
|
314
|
+
message = "PMC driver `amd_pmc` loaded"
|
|
315
|
+
p = os.path.join(device.sys_path, "smu_program")
|
|
316
|
+
v = os.path.join(device.sys_path, "smu_fw_version")
|
|
317
|
+
if os.path.exists(v):
|
|
318
|
+
try:
|
|
319
|
+
self.smu_version = read_file(v)
|
|
320
|
+
self.smu_program = read_file(p)
|
|
321
|
+
except TimeoutError:
|
|
322
|
+
self.db.record_prereq(
|
|
323
|
+
"failed to communicate using `amd_pmc` driver", "❌"
|
|
324
|
+
)
|
|
325
|
+
return False
|
|
326
|
+
message += f" (Program {self.smu_program} Firmware {self.smu_version})"
|
|
327
|
+
self.db.record_prereq(message, "✅")
|
|
328
|
+
return True
|
|
329
|
+
self.failures += [MissingAmdPmc()]
|
|
330
|
+
self.db.record_prereq(
|
|
331
|
+
"PMC driver `amd_pmc` did not bind to any ACPI device", "❌"
|
|
332
|
+
)
|
|
333
|
+
return False
|
|
334
|
+
|
|
335
|
+
def check_wlan(self):
|
|
336
|
+
"""Checks for WLAN device"""
|
|
337
|
+
for device in self.pyudev.list_devices(subsystem="pci", PCI_CLASS="28000"):
|
|
338
|
+
slot = device.properties["PCI_SLOT_NAME"]
|
|
339
|
+
driver = device.properties.get("DRIVER")
|
|
340
|
+
if not driver:
|
|
341
|
+
self.db.record_prereq(f"WLAN device in {slot} missing driver", "🚦")
|
|
342
|
+
self.failures += [MissingDriver(slot)]
|
|
343
|
+
return False
|
|
344
|
+
self.db.record_prereq(f"WLAN driver `{driver}` bound to {slot}", "✅")
|
|
345
|
+
return True
|
|
346
|
+
|
|
347
|
+
def check_usb3(self):
|
|
348
|
+
"""Check for the USB4 controller"""
|
|
349
|
+
for device in self.pyudev.list_devices(subsystem="pci", PCI_CLASS="C0330"):
|
|
350
|
+
slot = device.properties["PCI_SLOT_NAME"]
|
|
351
|
+
if device.properties.get("DRIVER") != "xhci_hcd":
|
|
352
|
+
self.db.record_prereq(
|
|
353
|
+
f"USB3 controller for {slot} not using `xhci_hcd` driver", "❌"
|
|
354
|
+
)
|
|
355
|
+
self.failures += [MissingXhciHcd()]
|
|
356
|
+
return False
|
|
357
|
+
self.db.record_prereq(f"USB3 driver `xhci_hcd` bound to {slot}", "✅")
|
|
358
|
+
return True
|
|
359
|
+
|
|
360
|
+
def check_usb4(self):
|
|
361
|
+
"""Check if the thunderbolt driver is loaded"""
|
|
362
|
+
for device in self.pyudev.list_devices(subsystem="pci", PCI_CLASS="C0340"):
|
|
363
|
+
slot = device.properties["PCI_SLOT_NAME"]
|
|
364
|
+
if device.properties.get("DRIVER") != "thunderbolt":
|
|
365
|
+
self.db.record_prereq("USB4 driver `thunderbolt` missing", "❌")
|
|
366
|
+
self.failures += [MissingThunderbolt()]
|
|
367
|
+
return False
|
|
368
|
+
self.db.record_prereq(f"USB4 driver `thunderbolt` bound to {slot}", "✅")
|
|
369
|
+
return True
|
|
370
|
+
|
|
371
|
+
def check_sleep_mode(self):
|
|
372
|
+
"""Check if the system is configured for s2idle"""
|
|
373
|
+
fn = os.path.join("/", "sys", "power", "mem_sleep")
|
|
374
|
+
if not os.path.exists(fn):
|
|
375
|
+
self.db.record_prereq("Kernel doesn't support sleep", "❌")
|
|
376
|
+
return False
|
|
377
|
+
|
|
378
|
+
cmdline = read_file(os.path.join("/proc", "cmdline"))
|
|
379
|
+
if "mem_sleep_default=deep" in cmdline:
|
|
380
|
+
self.db.record_prereq(
|
|
381
|
+
"Kernel command line is configured for 'deep' sleep", "❌"
|
|
382
|
+
)
|
|
383
|
+
self.failures += [DeepSleep()]
|
|
384
|
+
return False
|
|
385
|
+
if "[s2idle]" not in read_file(fn):
|
|
386
|
+
self.failures += [SleepModeWrong()]
|
|
387
|
+
self.db.record_prereq(
|
|
388
|
+
"System isn't configured for s2idle in firmware setup", "❌"
|
|
389
|
+
)
|
|
390
|
+
return False
|
|
391
|
+
self.db.record_prereq("System is configured for s2idle", "✅")
|
|
392
|
+
return True
|
|
393
|
+
|
|
394
|
+
def capture_smbios(self):
|
|
395
|
+
"""Capture the SMBIOS (DMI) information"""
|
|
396
|
+
p = os.path.join("/", "sys", "class", "dmi", "id")
|
|
397
|
+
if not os.path.exists(p):
|
|
398
|
+
self.db.record_prereq("DMI data was not setup", "🚦")
|
|
399
|
+
self.failures += [DmiNotSetup()]
|
|
400
|
+
return False
|
|
401
|
+
else:
|
|
402
|
+
keys = {}
|
|
403
|
+
filtered = [
|
|
404
|
+
"product_serial",
|
|
405
|
+
"board_serial",
|
|
406
|
+
"board_asset_tag",
|
|
407
|
+
"chassis_asset_tag",
|
|
408
|
+
"chassis_serial",
|
|
409
|
+
"modalias",
|
|
410
|
+
"uevent",
|
|
411
|
+
"product_uuid",
|
|
412
|
+
]
|
|
413
|
+
for root, _dirs, files in os.walk(p, topdown=False):
|
|
414
|
+
files.sort()
|
|
415
|
+
for fname in files:
|
|
416
|
+
if "power" in root:
|
|
417
|
+
continue
|
|
418
|
+
if fname in filtered:
|
|
419
|
+
continue
|
|
420
|
+
contents = read_file(os.path.join(root, fname))
|
|
421
|
+
keys[fname] = contents
|
|
422
|
+
if (
|
|
423
|
+
"sys_vendor" not in keys
|
|
424
|
+
or "product_name" not in keys
|
|
425
|
+
or "product_family" not in keys
|
|
426
|
+
):
|
|
427
|
+
self.db.record_prereq("DMI data not found", "❌")
|
|
428
|
+
self.failures += [DmiNotSetup()]
|
|
429
|
+
return False
|
|
430
|
+
self.db.record_prereq(
|
|
431
|
+
f"{keys['sys_vendor']} {keys['product_name']} ({keys['product_family']})",
|
|
432
|
+
"💻",
|
|
433
|
+
)
|
|
434
|
+
debug_str = "DMI data:\n"
|
|
435
|
+
for key, value in keys.items():
|
|
436
|
+
if (
|
|
437
|
+
"product_name" in key
|
|
438
|
+
or "sys_vendor" in key
|
|
439
|
+
or "product_family" in key
|
|
440
|
+
):
|
|
441
|
+
continue
|
|
442
|
+
debug_str += f"{key}: {value}\n"
|
|
443
|
+
self.db.record_debug(debug_str)
|
|
444
|
+
return True
|
|
445
|
+
|
|
446
|
+
def check_lps0(self):
|
|
447
|
+
"""Check if LPS0 is enabled"""
|
|
448
|
+
for m in ["acpi", "acpi_x86"]:
|
|
449
|
+
p = os.path.join("/", "sys", "module", m, "parameters", "sleep_no_lps0")
|
|
450
|
+
if not os.path.exists(p):
|
|
451
|
+
continue
|
|
452
|
+
fail = read_file(p) == "Y"
|
|
453
|
+
if fail:
|
|
454
|
+
self.db.record_prereq("LPS0 _DSM disabled", "❌")
|
|
455
|
+
else:
|
|
456
|
+
self.db.record_prereq("LPS0 _DSM enabled", "✅")
|
|
457
|
+
return not fail
|
|
458
|
+
self.db.record_prereq("LPS0 _DSM not found", "👀")
|
|
459
|
+
return False
|
|
460
|
+
|
|
461
|
+
def get_cpu_vendor(self) -> str:
|
|
462
|
+
"""Fetch information about the CPU vendor"""
|
|
463
|
+
p = os.path.join("/", "proc", "cpuinfo")
|
|
464
|
+
vendor = ""
|
|
465
|
+
cpu = read_file(p)
|
|
466
|
+
for line in cpu.split("\n"):
|
|
467
|
+
if "vendor_id" in line:
|
|
468
|
+
vendor = line.split()[-1]
|
|
469
|
+
continue
|
|
470
|
+
elif "cpu family" in line:
|
|
471
|
+
self.cpu_family = int(line.split()[-1])
|
|
472
|
+
continue
|
|
473
|
+
elif "model name" in line:
|
|
474
|
+
self.cpu_model_string = line.split(":")[-1].strip()
|
|
475
|
+
continue
|
|
476
|
+
elif "model" in line:
|
|
477
|
+
self.cpu_model = int(line.split()[-1])
|
|
478
|
+
continue
|
|
479
|
+
if self.cpu_family and self.cpu_model and self.cpu_model_string:
|
|
480
|
+
self.db.record_prereq(
|
|
481
|
+
"%s (family %x model %x)"
|
|
482
|
+
% (self.cpu_model_string, self.cpu_family, self.cpu_model),
|
|
483
|
+
"💻",
|
|
484
|
+
)
|
|
485
|
+
break
|
|
486
|
+
return vendor
|
|
487
|
+
|
|
488
|
+
# See https://github.com/torvalds/linux/commit/ec6c0503190417abf8b8f8e3e955ae583a4e50d4
|
|
489
|
+
def check_fadt(self):
|
|
490
|
+
"""Check the kernel emitted a message specific to 6.0 or later indicating FADT had a bit set."""
|
|
491
|
+
found = False
|
|
492
|
+
if not self.kernel_log:
|
|
493
|
+
message = "Unable to test FADT from kernel log"
|
|
494
|
+
self.db.record_prereq(message, "🚦")
|
|
495
|
+
else:
|
|
496
|
+
self.kernel_log.seek()
|
|
497
|
+
matches = ["Low-power S0 idle used by default for system suspend"]
|
|
498
|
+
found = self.kernel_log.match_line(matches)
|
|
499
|
+
# try to look at FACP directly if not found (older kernel compat)
|
|
500
|
+
if not found:
|
|
501
|
+
self.db.record_debug("Fetching low power idle bit directly from FADT")
|
|
502
|
+
target = os.path.join("/", "sys", "firmware", "acpi", "tables", "FACP")
|
|
503
|
+
try:
|
|
504
|
+
with open(target, "rb") as r:
|
|
505
|
+
r.seek(0x70)
|
|
506
|
+
found = struct.unpack("<I", r.read(4))[0] & BIT(21)
|
|
507
|
+
except PermissionError:
|
|
508
|
+
self.db.record_prereq("FADT check unavailable", "🚦")
|
|
509
|
+
return True
|
|
510
|
+
if found:
|
|
511
|
+
message = "ACPI FADT supports Low-power S0 idle"
|
|
512
|
+
self.db.record_prereq(message, "✅")
|
|
513
|
+
else:
|
|
514
|
+
message = "ACPI FADT doesn't support Low-power S0 idle"
|
|
515
|
+
self.db.record_prereq(message, "❌")
|
|
516
|
+
self.failures += [FadtWrong()]
|
|
517
|
+
return found
|
|
518
|
+
|
|
519
|
+
def capture_kernel_version(self):
|
|
520
|
+
"""Log the kernel version used"""
|
|
521
|
+
self.db.record_prereq(f"{get_pretty_distro()}", "🐧")
|
|
522
|
+
self.db.record_prereq(f"Kernel {platform.uname().release}", "🐧")
|
|
523
|
+
|
|
524
|
+
def capture_irq(self):
|
|
525
|
+
"""Capture the IRQs to the log"""
|
|
526
|
+
p = os.path.join("/sys", "kernel", "irq")
|
|
527
|
+
for directory in os.listdir(p):
|
|
528
|
+
if os.path.isdir(os.path.join(p, directory)):
|
|
529
|
+
wake = WakeIRQ(directory, self.pyudev)
|
|
530
|
+
self.irqs.append([int(directory), str(wake)])
|
|
531
|
+
self.irqs.sort()
|
|
532
|
+
self.db.record_debug("Interrupts")
|
|
533
|
+
for irq in self.irqs:
|
|
534
|
+
# set prefix if last IRQ
|
|
535
|
+
prefix = "│ " if irq != self.irqs[-1] else "└─"
|
|
536
|
+
self.db.record_debug(f"{prefix}{irq[0]}: {irq[1]}")
|
|
537
|
+
return True
|
|
538
|
+
|
|
539
|
+
def capture_disabled_pins(self):
|
|
540
|
+
"""Capture disabled pins from pinctrl-amd"""
|
|
541
|
+
base = os.path.join("/", "sys", "module", "gpiolib_acpi", "parameters")
|
|
542
|
+
debug_str = ""
|
|
543
|
+
for parameter in ["ignore_wake", "ignore_interrupt"]:
|
|
544
|
+
f = os.path.join(base, parameter)
|
|
545
|
+
if not os.path.exists(f):
|
|
546
|
+
continue
|
|
547
|
+
with open(f, "r", encoding="utf-8") as r:
|
|
548
|
+
d = r.read().rstrip()
|
|
549
|
+
if d != "(null)":
|
|
550
|
+
debug_str += f"{f} is configured to {d}\n"
|
|
551
|
+
if debug_str:
|
|
552
|
+
debug_str = "Disabled pins:\n" + debug_str
|
|
553
|
+
self.db.record_debug(debug_str)
|
|
554
|
+
|
|
555
|
+
def check_logger(self):
|
|
556
|
+
"""Check the source for kernel logs"""
|
|
557
|
+
if isinstance(self.kernel_log, SystemdLogger):
|
|
558
|
+
self.db.record_prereq("Logs are provided via systemd", "✅")
|
|
559
|
+
if isinstance(self.kernel_log, DmesgLogger):
|
|
560
|
+
self.db.record_prereq(
|
|
561
|
+
"Logs are provided via dmesg, timestamps may not be accurate over multiple cycles",
|
|
562
|
+
"🚦",
|
|
563
|
+
)
|
|
564
|
+
header = self.kernel_log.capture_header()
|
|
565
|
+
if not re.search(r"Linux version .*", header):
|
|
566
|
+
self.db.record_prereq(
|
|
567
|
+
"Kernel ring buffer has wrapped, unable to accurately validate pre-requisites",
|
|
568
|
+
"❌",
|
|
569
|
+
)
|
|
570
|
+
self.failures += [KernelRingBufferWrapped()]
|
|
571
|
+
return False
|
|
572
|
+
return True
|
|
573
|
+
|
|
574
|
+
def check_permissions(self):
|
|
575
|
+
"""Check if the user has permissions to write to /sys/power/state"""
|
|
576
|
+
p = os.path.join("/", "sys", "power", "state")
|
|
577
|
+
try:
|
|
578
|
+
with open(p, "w") as w:
|
|
579
|
+
pass
|
|
580
|
+
except PermissionError:
|
|
581
|
+
self.db.record_prereq("%s" % Headers.RootError, "👀")
|
|
582
|
+
return False
|
|
583
|
+
except FileNotFoundError:
|
|
584
|
+
self.db.record_prereq("Kernel doesn't support power management", "❌")
|
|
585
|
+
return False
|
|
586
|
+
return True
|
|
587
|
+
|
|
588
|
+
def capture_linux_firmware(self):
|
|
589
|
+
"""Capture the linux-firmware package version"""
|
|
590
|
+
for num in range(0, 2):
|
|
591
|
+
p = os.path.join(
|
|
592
|
+
"/", "sys", "kernel", "debug", "dri", f"{num}", "amdgpu_firmware_info"
|
|
593
|
+
)
|
|
594
|
+
if os.path.exists(p):
|
|
595
|
+
self.db.record_debug_file(p)
|
|
596
|
+
|
|
597
|
+
def check_amd_cpu_hpet_wa(self):
|
|
598
|
+
"""Check if the CPU offers the HPET workaround"""
|
|
599
|
+
show_warning = False
|
|
600
|
+
if self.cpu_family == 0x17:
|
|
601
|
+
if self.cpu_model in [0x68, 0x60]:
|
|
602
|
+
show_warning = True
|
|
603
|
+
elif self.cpu_family == 0x19:
|
|
604
|
+
if self.cpu_model == 0x50:
|
|
605
|
+
if self.smu_version:
|
|
606
|
+
show_warning = version.parse(self.smu_version) < version.parse(
|
|
607
|
+
"64.53.0"
|
|
608
|
+
)
|
|
609
|
+
if show_warning:
|
|
610
|
+
self.db.record_prereq(
|
|
611
|
+
"Timer based wakeup doesn't work properly for your ASIC/firmware, please manually wake the system",
|
|
612
|
+
"🚦",
|
|
613
|
+
)
|
|
614
|
+
return True
|
|
615
|
+
|
|
616
|
+
def check_pinctrl_amd(self):
|
|
617
|
+
"""Check if the pinctrl_amd driver is loaded"""
|
|
618
|
+
debug_str = ""
|
|
619
|
+
for _device in self.pyudev.list_devices(
|
|
620
|
+
subsystem="platform", DRIVER="amd_gpio"
|
|
621
|
+
):
|
|
622
|
+
self.db.record_prereq("GPIO driver `pinctrl_amd` available", "✅")
|
|
623
|
+
p = os.path.join("/", "sys", "kernel", "debug", "gpio")
|
|
624
|
+
try:
|
|
625
|
+
contents = read_file(p)
|
|
626
|
+
except PermissionError:
|
|
627
|
+
self.db.record_debug(f"Unable to capture {p}")
|
|
628
|
+
contents = None
|
|
629
|
+
header = False
|
|
630
|
+
if contents:
|
|
631
|
+
for line in contents.split("\n"):
|
|
632
|
+
if "WAKE_INT_MASTER_REG:" in line:
|
|
633
|
+
val = "en" if int(line.split()[1], 16) & BIT(15) else "dis"
|
|
634
|
+
self.db.record_debug("Windows GPIO 0 debounce: %sabled" % val)
|
|
635
|
+
continue
|
|
636
|
+
if not header and re.search("trigger", line):
|
|
637
|
+
debug_str += line + "\n"
|
|
638
|
+
header = True
|
|
639
|
+
if re.search("edge", line) or re.search("level", line):
|
|
640
|
+
debug_str += line + "\n"
|
|
641
|
+
if "🔥" in line:
|
|
642
|
+
self.failures += [UnservicedGpio()]
|
|
643
|
+
return False
|
|
644
|
+
|
|
645
|
+
if debug_str:
|
|
646
|
+
self.db.record_debug(debug_str)
|
|
647
|
+
return True
|
|
648
|
+
self.db.record_prereq("GPIO driver `pinctrl_amd` not loaded", "❌")
|
|
649
|
+
return False
|
|
650
|
+
|
|
651
|
+
def check_network(self):
|
|
652
|
+
"""Check network devices for s2idle support"""
|
|
653
|
+
for device in self.pyudev.list_devices(subsystem="net", ID_NET_DRIVER="r8169"):
|
|
654
|
+
interface = device.properties.get("INTERFACE")
|
|
655
|
+
cmd = ["ethtool", interface]
|
|
656
|
+
wol_supported = False
|
|
657
|
+
output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL).decode(
|
|
658
|
+
"utf-8"
|
|
659
|
+
)
|
|
660
|
+
for line in output.split("\n"):
|
|
661
|
+
if "Supports Wake-on" in line:
|
|
662
|
+
val = line.split(":")[1].strip()
|
|
663
|
+
if "g" in val:
|
|
664
|
+
self.db.record_debug(f"{interface} supports WoL")
|
|
665
|
+
wol_supported = True
|
|
666
|
+
else:
|
|
667
|
+
self.db.record_debug(f"{interface} doesn't support WoL ({val})")
|
|
668
|
+
elif "Wake-on" in line and wol_supported:
|
|
669
|
+
val = line.split(":")[1].strip()
|
|
670
|
+
if "g" in val:
|
|
671
|
+
self.db.record_prereq(f"{interface} has WoL enabled", "✅")
|
|
672
|
+
else:
|
|
673
|
+
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.",
|
|
675
|
+
"🚦",
|
|
676
|
+
)
|
|
677
|
+
return True
|
|
678
|
+
|
|
679
|
+
def check_asus_rog_ally(self):
|
|
680
|
+
"""Check for MCU version on ASUS ROG Ally devices"""
|
|
681
|
+
for dev in self.pyudev.list_devices(subsystem="hid", DRIVER="asus_rog_ally"):
|
|
682
|
+
p = os.path.join(dev.sys_path, "mcu_version")
|
|
683
|
+
if not os.path.exists(p):
|
|
684
|
+
continue
|
|
685
|
+
v = int(read_file(p))
|
|
686
|
+
hid_id = get_property_pyudev(dev.properties, "HID_ID", "")
|
|
687
|
+
if "1ABE" in hid_id:
|
|
688
|
+
minv = 319
|
|
689
|
+
elif "1B4C" in hid_id:
|
|
690
|
+
minv = 313
|
|
691
|
+
else:
|
|
692
|
+
minv = None
|
|
693
|
+
if minv and v < minv:
|
|
694
|
+
self.db.record_prereq("ROG Ally MCU firmware too old", "❌")
|
|
695
|
+
self.failures += [RogAllyOldMcu(minv, v)]
|
|
696
|
+
return False
|
|
697
|
+
else:
|
|
698
|
+
self.db.record_debug("ASUS ROG MCU found with MCU version %d", v)
|
|
699
|
+
for dev in self.pyudev.list_devices(subsystem="firmware-attributes"):
|
|
700
|
+
p = os.path.join(
|
|
701
|
+
dev.sys_path, "attributes", "mcu_powersave", "current_value"
|
|
702
|
+
)
|
|
703
|
+
if not os.path.exists(p):
|
|
704
|
+
continue
|
|
705
|
+
v = int(read_file(p))
|
|
706
|
+
if v < 1:
|
|
707
|
+
self.db.record_prereq(
|
|
708
|
+
"Rog Ally doesn't have MCU powersave enabled", "❌"
|
|
709
|
+
)
|
|
710
|
+
self.failures += [RogAllyMcuPowerSave()]
|
|
711
|
+
return False
|
|
712
|
+
|
|
713
|
+
return True
|
|
714
|
+
|
|
715
|
+
def check_device_firmware(self):
|
|
716
|
+
"""Check for device firmware issues"""
|
|
717
|
+
if not FWUPD:
|
|
718
|
+
self.db.record_debug(
|
|
719
|
+
"Device firmware checks unavailable without gobject introspection"
|
|
720
|
+
)
|
|
721
|
+
return True
|
|
722
|
+
|
|
723
|
+
client = Fwupd.Client()
|
|
724
|
+
devices = client.get_devices()
|
|
725
|
+
for device in devices:
|
|
726
|
+
# Dictionary of instance id to firmware version mappings that
|
|
727
|
+
# have been "reported" to be problematic
|
|
728
|
+
device_map = {
|
|
729
|
+
"8c36f7ee-cc11-4a36-b090-6363f54ecac2": "0.1.26", # https://gitlab.freedesktop.org/drm/amd/-/issues/3443
|
|
730
|
+
}
|
|
731
|
+
interesting_plugins = ["nvme", "tpm", "uefi_capsule"]
|
|
732
|
+
if device.get_plugin() in interesting_plugins:
|
|
733
|
+
logging.debug(
|
|
734
|
+
"%s %s firmware version: '%s'",
|
|
735
|
+
device.get_vendor(),
|
|
736
|
+
device.get_name(),
|
|
737
|
+
device.get_version(),
|
|
738
|
+
)
|
|
739
|
+
logging.debug("| %s", device.get_guids())
|
|
740
|
+
logging.debug("└─%s", device.get_instance_ids())
|
|
741
|
+
for item, ver in device_map.items():
|
|
742
|
+
if (
|
|
743
|
+
item in device.get_guids() or item in device.get_instance_ids()
|
|
744
|
+
) and ver in device.get_version():
|
|
745
|
+
self.db.record_prereq(
|
|
746
|
+
f"Platform may have problems resuming. Upgrade the firmware for '{device.get_name()}' if you have problems.",
|
|
747
|
+
"🚦",
|
|
748
|
+
)
|
|
749
|
+
return True
|
|
750
|
+
|
|
751
|
+
def check_aspm(self):
|
|
752
|
+
"""Check if ASPM has been overridden"""
|
|
753
|
+
p = os.path.join("/", "sys", "module", "pcie_aspm", "parameters", "policy")
|
|
754
|
+
contents = read_file(p)
|
|
755
|
+
policy = ""
|
|
756
|
+
for word in contents.split(" "):
|
|
757
|
+
if word.startswith("["):
|
|
758
|
+
policy = word
|
|
759
|
+
break
|
|
760
|
+
if policy != "[default]":
|
|
761
|
+
self.db.record_prereq(f"ASPM policy set to {policy}", "❌")
|
|
762
|
+
self.failures += [ASpmWrong()]
|
|
763
|
+
return False
|
|
764
|
+
self.db.record_prereq("ASPM policy set to 'default'", "✅")
|
|
765
|
+
return True
|
|
766
|
+
|
|
767
|
+
def check_i2c_hid(self):
|
|
768
|
+
"""Check for I2C HID devices"""
|
|
769
|
+
devices = []
|
|
770
|
+
for dev in self.pyudev.list_devices(subsystem="input"):
|
|
771
|
+
if "NAME" not in dev.properties:
|
|
772
|
+
continue
|
|
773
|
+
parent = dev.find_parent(subsystem="i2c")
|
|
774
|
+
if parent is None:
|
|
775
|
+
continue
|
|
776
|
+
devices.append(dev)
|
|
777
|
+
if not devices:
|
|
778
|
+
return True
|
|
779
|
+
ret = True
|
|
780
|
+
debug_str = "I2C HID devices:\n"
|
|
781
|
+
for dev in devices:
|
|
782
|
+
name = dev.properties["NAME"]
|
|
783
|
+
parent = dev.find_parent(subsystem="i2c")
|
|
784
|
+
p = os.path.join(parent.sys_path, "firmware_node", "path")
|
|
785
|
+
if os.path.exists(p):
|
|
786
|
+
acpi_path = read_file(p)
|
|
787
|
+
else:
|
|
788
|
+
acpi_path = ""
|
|
789
|
+
p = os.path.join(parent.sys_path, "firmware_node", "hid")
|
|
790
|
+
if os.path.exists(p):
|
|
791
|
+
acpi_hid = read_file(p)
|
|
792
|
+
else:
|
|
793
|
+
acpi_hid = ""
|
|
794
|
+
# 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
|
+
)
|
|
799
|
+
if "IDEA5002" in name:
|
|
800
|
+
remediation = f"echo {parent.sys_path.split('/')[-1]} | sudo tee /sys/bus/i2c/drivers/{parent.driver}/unbind"
|
|
801
|
+
|
|
802
|
+
self.db.record_prereq(f"{name} may cause spurious wakeups", "❌")
|
|
803
|
+
self.failures += [I2CHidBug(name, remediation)]
|
|
804
|
+
ret = False
|
|
805
|
+
self.db.record_debug(debug_str)
|
|
806
|
+
return ret
|
|
807
|
+
|
|
808
|
+
def capture_pci_acpi(self):
|
|
809
|
+
"""Map ACPI to PCI devices"""
|
|
810
|
+
devices = []
|
|
811
|
+
for dev in self.pyudev.list_devices(subsystem="pci"):
|
|
812
|
+
devices.append(dev)
|
|
813
|
+
debug_str = "PCI devices\n"
|
|
814
|
+
for dev in devices:
|
|
815
|
+
pci_id = dev.properties["PCI_ID"].lower()
|
|
816
|
+
pci_slot_name = dev.properties["PCI_SLOT_NAME"]
|
|
817
|
+
database_class = get_property_pyudev(
|
|
818
|
+
dev.properties, "ID_PCI_SUBCLASS_FROM_DATABASE", ""
|
|
819
|
+
)
|
|
820
|
+
database_vendor = get_property_pyudev(
|
|
821
|
+
dev.properties, "ID_VENDOR_FROM_DATABASE", ""
|
|
822
|
+
)
|
|
823
|
+
if dev.parent.subsystem != "pci":
|
|
824
|
+
if dev == devices[-1]:
|
|
825
|
+
prefix = "└─"
|
|
826
|
+
else:
|
|
827
|
+
prefix = "│ "
|
|
828
|
+
else:
|
|
829
|
+
if dev == devices[-1]:
|
|
830
|
+
prefix = "└─"
|
|
831
|
+
else:
|
|
832
|
+
prefix = "├─ "
|
|
833
|
+
p = os.path.join(dev.sys_path, "firmware_node", "path")
|
|
834
|
+
if os.path.exists(p):
|
|
835
|
+
acpi = read_file(p)
|
|
836
|
+
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
|
+
)
|
|
845
|
+
)
|
|
846
|
+
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,
|
|
853
|
+
)
|
|
854
|
+
if debug_str:
|
|
855
|
+
self.db.record_debug(debug_str)
|
|
856
|
+
|
|
857
|
+
def map_acpi_path(self):
|
|
858
|
+
"""Map of ACPI devices to ACPI paths"""
|
|
859
|
+
devices = []
|
|
860
|
+
for dev in self.pyudev.list_devices(subsystem="acpi"):
|
|
861
|
+
p = os.path.join(dev.sys_path, "path")
|
|
862
|
+
if not os.path.exists(p):
|
|
863
|
+
continue
|
|
864
|
+
p = os.path.join(dev.sys_path, "status")
|
|
865
|
+
if os.path.exists(p):
|
|
866
|
+
status = int(read_file(p))
|
|
867
|
+
if status == 0:
|
|
868
|
+
continue
|
|
869
|
+
devices.append(dev)
|
|
870
|
+
debug_str = "ACPI name: ACPI path [driver]\n"
|
|
871
|
+
for dev in devices:
|
|
872
|
+
if dev == devices[-1]:
|
|
873
|
+
prefix = "└─"
|
|
874
|
+
else:
|
|
875
|
+
prefix = "│ "
|
|
876
|
+
p = os.path.join(dev.sys_path, "path")
|
|
877
|
+
pth = read_file(p)
|
|
878
|
+
p = os.path.join(dev.sys_path, "physical_node", "driver")
|
|
879
|
+
if os.path.exists(p):
|
|
880
|
+
driver = os.path.basename(os.readlink(p))
|
|
881
|
+
else:
|
|
882
|
+
driver = None
|
|
883
|
+
debug_str += f"{prefix}{dev.sys_name}: {pth} [{driver}]\n"
|
|
884
|
+
if debug_str:
|
|
885
|
+
self.db.record_debug(debug_str)
|
|
886
|
+
return True
|
|
887
|
+
|
|
888
|
+
def capture_acpi(self):
|
|
889
|
+
"""Capture ACPI tables"""
|
|
890
|
+
base = os.path.join("/", "sys", "firmware", "acpi", "tables")
|
|
891
|
+
for root, _dirs, files in os.walk(base, topdown=False):
|
|
892
|
+
for fname in files:
|
|
893
|
+
target = os.path.join(root, fname)
|
|
894
|
+
if "SSDT" in fname:
|
|
895
|
+
with open(target, "rb") as f:
|
|
896
|
+
s = f.read()
|
|
897
|
+
if s.find(b"_AEI") < 0:
|
|
898
|
+
continue
|
|
899
|
+
elif "IVRS" in fname:
|
|
900
|
+
pass
|
|
901
|
+
else:
|
|
902
|
+
continue
|
|
903
|
+
try:
|
|
904
|
+
tmpd = tempfile.mkdtemp()
|
|
905
|
+
prefix = os.path.join(tmpd, "acpi")
|
|
906
|
+
subprocess.check_call(
|
|
907
|
+
["iasl", "-p", prefix, "-d", target],
|
|
908
|
+
stdout=subprocess.DEVNULL,
|
|
909
|
+
stderr=subprocess.DEVNULL,
|
|
910
|
+
)
|
|
911
|
+
self.db.record_debug_file("%s.dsl" % prefix)
|
|
912
|
+
except subprocess.CalledProcessError as e:
|
|
913
|
+
self.db.record_prereq(
|
|
914
|
+
f"Failed to capture ACPI table: {e.output}", "👀"
|
|
915
|
+
)
|
|
916
|
+
finally:
|
|
917
|
+
shutil.rmtree(tmpd)
|
|
918
|
+
return True
|
|
919
|
+
|
|
920
|
+
def capture_battery(self):
|
|
921
|
+
"""Capture battery information"""
|
|
922
|
+
obj = Batteries()
|
|
923
|
+
for bat in obj.get_batteries():
|
|
924
|
+
desc = obj.get_description_string(bat)
|
|
925
|
+
self.db.record_prereq(desc, "🔋")
|
|
926
|
+
|
|
927
|
+
def capture_logind(self):
|
|
928
|
+
base = os.path.join("/", "etc", "systemd", "logind.conf")
|
|
929
|
+
if not os.path.exists(base):
|
|
930
|
+
return True
|
|
931
|
+
import configparser
|
|
932
|
+
|
|
933
|
+
config = configparser.ConfigParser()
|
|
934
|
+
config.read(base)
|
|
935
|
+
section = config["Login"]
|
|
936
|
+
if not section.keys():
|
|
937
|
+
self.db.record_debug("LOGIND: no configuration changes")
|
|
938
|
+
return True
|
|
939
|
+
self.db.record_debug("LOGIND: configuration changes:")
|
|
940
|
+
for key in section.keys():
|
|
941
|
+
self.db.record_debug(f"\t{key}: {section[key]}")
|
|
942
|
+
|
|
943
|
+
def check_cpu(self):
|
|
944
|
+
"""Check if the CPU is supported"""
|
|
945
|
+
|
|
946
|
+
def read_cpuid(cpu, leaf, subleaf):
|
|
947
|
+
"""Read CPUID using kernel userspace interface"""
|
|
948
|
+
p = os.path.join("/", "dev", "cpu", f"{cpu}", "cpuid")
|
|
949
|
+
if not os.path.exists(p):
|
|
950
|
+
os.system("modprobe cpuid")
|
|
951
|
+
with open(p, "rb") as f:
|
|
952
|
+
position = (subleaf << 32) | leaf
|
|
953
|
+
f.seek(position)
|
|
954
|
+
data = f.read(16)
|
|
955
|
+
return struct.unpack("4I", data)
|
|
956
|
+
|
|
957
|
+
valid = True
|
|
958
|
+
|
|
959
|
+
# check for supported models
|
|
960
|
+
if self.cpu_family == 0x17:
|
|
961
|
+
if self.cpu_model in range(0x30, 0x3F):
|
|
962
|
+
valid = False
|
|
963
|
+
if self.cpu_family == 0x19:
|
|
964
|
+
if self.cpu_model in [0x08, 0x18]:
|
|
965
|
+
valid = False
|
|
966
|
+
|
|
967
|
+
if not valid:
|
|
968
|
+
self.failures += [UnsupportedModel()]
|
|
969
|
+
self.db.record_prereq(
|
|
970
|
+
"This CPU model does not support hardware sleep over s2idle",
|
|
971
|
+
"❌",
|
|
972
|
+
)
|
|
973
|
+
return False
|
|
974
|
+
|
|
975
|
+
# check for artificially limited CPUs
|
|
976
|
+
p = os.path.join("/", "sys", "devices", "system", "cpu", "kernel_max")
|
|
977
|
+
max_cpus = int(read_file(p)) + 1 # 0 indexed
|
|
978
|
+
# https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/24594.pdf
|
|
979
|
+
# Extended Topology Enumeration (NumLogCores)
|
|
980
|
+
# CPUID 0x80000026 subleaf 1
|
|
981
|
+
try:
|
|
982
|
+
_, cpu_count, _, _ = read_cpuid(0, 0x80000026, 1)
|
|
983
|
+
if cpu_count > max_cpus:
|
|
984
|
+
self.db.record_prereq(
|
|
985
|
+
f"The kernel has been limited to {max_cpus} CPU cores, but the system has {cpu_count} cores",
|
|
986
|
+
"❌",
|
|
987
|
+
)
|
|
988
|
+
self.failures += [LimitedCores(cpu_count, max_cpus)]
|
|
989
|
+
return False
|
|
990
|
+
self.db.record_debug(f"CPU core count: {cpu_count} max: {max_cpus}")
|
|
991
|
+
except FileNotFoundError:
|
|
992
|
+
self.db.record_prereq(
|
|
993
|
+
"Unable to check CPU topology: cpuid kernel module not loaded", "❌"
|
|
994
|
+
)
|
|
995
|
+
return False
|
|
996
|
+
except PermissionError:
|
|
997
|
+
self.db.record_prereq("CPUID checks unavailable", "🚦")
|
|
998
|
+
|
|
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
|
+
return True
|
|
1006
|
+
|
|
1007
|
+
def check_msr(self):
|
|
1008
|
+
"""Check if PC6 or CC6 has been disabled"""
|
|
1009
|
+
|
|
1010
|
+
def check_bits(value, mask):
|
|
1011
|
+
return value & mask
|
|
1012
|
+
|
|
1013
|
+
expect = {
|
|
1014
|
+
0xC0010292: BIT(32), # PC6
|
|
1015
|
+
0xC0010296: (BIT(22) | BIT(14) | BIT(6)), # CC6
|
|
1016
|
+
}
|
|
1017
|
+
try:
|
|
1018
|
+
for reg, expect_val in expect.items():
|
|
1019
|
+
val = read_msr(reg, 0)
|
|
1020
|
+
if not check_bits(val, expect_val):
|
|
1021
|
+
self.failures += [MSRFailure()]
|
|
1022
|
+
return False
|
|
1023
|
+
self.db.record_prereq("PC6 and CC6 enabled", "✅")
|
|
1024
|
+
except FileNotFoundError:
|
|
1025
|
+
self.db.record_prereq(
|
|
1026
|
+
"Unable to check MSRs: MSR kernel module not loaded", "❌"
|
|
1027
|
+
)
|
|
1028
|
+
return False
|
|
1029
|
+
except PermissionError:
|
|
1030
|
+
self.db.record_prereq("MSR checks unavailable", "🚦")
|
|
1031
|
+
return True
|
|
1032
|
+
|
|
1033
|
+
def check_smt(self):
|
|
1034
|
+
"""Check if SMT is enabled"""
|
|
1035
|
+
p = os.path.join("/", "sys", "devices", "system", "cpu", "smt", "control")
|
|
1036
|
+
v = read_file(p)
|
|
1037
|
+
self.db.record_debug(f"SMT control: {v}")
|
|
1038
|
+
if v == "notsupported":
|
|
1039
|
+
return True
|
|
1040
|
+
p = os.path.join("/", "sys", "devices", "system", "cpu", "smt", "active")
|
|
1041
|
+
v = read_file(p)
|
|
1042
|
+
if v == "0":
|
|
1043
|
+
self.failures += [SMTNotEnabled()]
|
|
1044
|
+
self.db.record_prereq("SMT is not enabled", "❌")
|
|
1045
|
+
return False
|
|
1046
|
+
self.db.record_prereq("SMT enabled", "✅")
|
|
1047
|
+
return True
|
|
1048
|
+
|
|
1049
|
+
def check_iommu(self):
|
|
1050
|
+
"""Check IOMMU configuration"""
|
|
1051
|
+
affected_1a = (
|
|
1052
|
+
list(range(0x20, 0x2F)) + list(range(0x60, 0x6F)) + list(range(0x70, 0x7F))
|
|
1053
|
+
)
|
|
1054
|
+
debug_str = ""
|
|
1055
|
+
if self.cpu_family == 0x1A and self.cpu_model in affected_1a:
|
|
1056
|
+
found_iommu = False
|
|
1057
|
+
found_acpi = False
|
|
1058
|
+
found_dmar = False
|
|
1059
|
+
for dev in self.pyudev.list_devices(subsystem="iommu"):
|
|
1060
|
+
found_iommu = True
|
|
1061
|
+
debug_str += f"Found IOMMU {dev.sys_path}\n"
|
|
1062
|
+
break
|
|
1063
|
+
if not found_iommu:
|
|
1064
|
+
self.db.record_prereq("IOMMU disabled", "✅")
|
|
1065
|
+
return True
|
|
1066
|
+
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
|
|
1074
|
+
self.db.record_debug(debug_str)
|
|
1075
|
+
if not found_dmar:
|
|
1076
|
+
self.db.record_prereq(
|
|
1077
|
+
"IOMMU is misconfigured: Pre-boot DMA protection not enabled", "❌"
|
|
1078
|
+
)
|
|
1079
|
+
self.failures += [DMArNotEnabled()]
|
|
1080
|
+
return False
|
|
1081
|
+
for dev in self.pyudev.list_devices(subsystem="acpi"):
|
|
1082
|
+
if "MSFT0201" in dev.sys_path:
|
|
1083
|
+
found_acpi = True
|
|
1084
|
+
if not found_acpi:
|
|
1085
|
+
self.db.record_prereq(
|
|
1086
|
+
"IOMMU is misconfigured: missing MSFT0201 ACPI device", "❌"
|
|
1087
|
+
)
|
|
1088
|
+
self.failures += [MissingIommuACPI("MSFT0201")]
|
|
1089
|
+
return False
|
|
1090
|
+
# check that policy is bound to it
|
|
1091
|
+
for dev in self.pyudev.list_devices(subsystem="platform"):
|
|
1092
|
+
if "MSFT0201" in dev.sys_path:
|
|
1093
|
+
p = os.path.join(dev.sys_path, "iommu")
|
|
1094
|
+
if not os.path.exists(p):
|
|
1095
|
+
self.failures += [MissingIommuPolicy("MSFT0201")]
|
|
1096
|
+
return False
|
|
1097
|
+
self.db.record_prereq("IOMMU properly configured", "✅")
|
|
1098
|
+
return True
|
|
1099
|
+
|
|
1100
|
+
def check_port_pm_override(self):
|
|
1101
|
+
"""Check for PCIe port power management override"""
|
|
1102
|
+
if self.cpu_family != 0x19:
|
|
1103
|
+
return True
|
|
1104
|
+
if self.cpu_model not in [0x74, 0x78]:
|
|
1105
|
+
return True
|
|
1106
|
+
if version.parse(self.smu_version) > version.parse("76.60.0"):
|
|
1107
|
+
return True
|
|
1108
|
+
if version.parse(self.smu_version) < version.parse("76.18.0"):
|
|
1109
|
+
return True
|
|
1110
|
+
cmdline = read_file(os.path.join("/proc", "cmdline"))
|
|
1111
|
+
if "pcie_port_pm=off" in cmdline:
|
|
1112
|
+
return True
|
|
1113
|
+
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.",
|
|
1115
|
+
"🚦",
|
|
1116
|
+
)
|
|
1117
|
+
return False
|
|
1118
|
+
|
|
1119
|
+
def check_taint(self):
|
|
1120
|
+
"""Check if the kernel is tainted"""
|
|
1121
|
+
fn = os.path.join("/", "proc", "sys", "kernel", "tainted")
|
|
1122
|
+
taint = int(read_file(fn))
|
|
1123
|
+
# ignore kernel warnings
|
|
1124
|
+
taint &= ~BIT(9)
|
|
1125
|
+
if taint != 0:
|
|
1126
|
+
self.db.record_prereq(f"Kernel is tainted: {taint}", "❌")
|
|
1127
|
+
self.failures += [TaintedKernel()]
|
|
1128
|
+
return False
|
|
1129
|
+
return True
|
|
1130
|
+
|
|
1131
|
+
def run(self):
|
|
1132
|
+
"""Run the prerequisites check"""
|
|
1133
|
+
msg = "Checking prerequisites, please wait"
|
|
1134
|
+
print_temporary_message(msg)
|
|
1135
|
+
info = [
|
|
1136
|
+
self.capture_smbios,
|
|
1137
|
+
self.capture_kernel_version,
|
|
1138
|
+
self.capture_battery,
|
|
1139
|
+
self.capture_linux_firmware,
|
|
1140
|
+
self.capture_logind,
|
|
1141
|
+
self.capture_pci_acpi,
|
|
1142
|
+
]
|
|
1143
|
+
checks = []
|
|
1144
|
+
|
|
1145
|
+
vendor = self.get_cpu_vendor()
|
|
1146
|
+
if vendor == "AuthenticAMD":
|
|
1147
|
+
info += [
|
|
1148
|
+
self.capture_disabled_pins,
|
|
1149
|
+
]
|
|
1150
|
+
checks += [
|
|
1151
|
+
self.check_aspm,
|
|
1152
|
+
self.check_i2c_hid,
|
|
1153
|
+
self.check_pinctrl_amd,
|
|
1154
|
+
self.check_amd_hsmp,
|
|
1155
|
+
self.check_amd_pmc,
|
|
1156
|
+
self.check_amd_cpu_hpet_wa,
|
|
1157
|
+
self.check_port_pm_override,
|
|
1158
|
+
self.check_usb3,
|
|
1159
|
+
self.check_usb4,
|
|
1160
|
+
self.check_sleep_mode,
|
|
1161
|
+
self.check_storage,
|
|
1162
|
+
self.check_wcn6855_bug,
|
|
1163
|
+
self.check_amdgpu,
|
|
1164
|
+
self.check_amdgpu_parameters,
|
|
1165
|
+
self.check_cpu,
|
|
1166
|
+
self.check_msr,
|
|
1167
|
+
self.check_smt,
|
|
1168
|
+
self.check_iommu,
|
|
1169
|
+
self.check_asus_rog_ally,
|
|
1170
|
+
]
|
|
1171
|
+
|
|
1172
|
+
checks += [
|
|
1173
|
+
self.check_fadt,
|
|
1174
|
+
self.check_logger,
|
|
1175
|
+
self.check_lps0,
|
|
1176
|
+
self.check_permissions,
|
|
1177
|
+
self.check_wlan,
|
|
1178
|
+
self.check_taint,
|
|
1179
|
+
self.capture_acpi,
|
|
1180
|
+
self.map_acpi_path,
|
|
1181
|
+
self.check_device_firmware,
|
|
1182
|
+
self.check_network,
|
|
1183
|
+
]
|
|
1184
|
+
|
|
1185
|
+
for i in info:
|
|
1186
|
+
i()
|
|
1187
|
+
|
|
1188
|
+
result = True
|
|
1189
|
+
for check in checks:
|
|
1190
|
+
if not check():
|
|
1191
|
+
result = False
|
|
1192
|
+
if not result:
|
|
1193
|
+
self.db.record_prereq(Headers.BrokenPrerequisites, "💯")
|
|
1194
|
+
self.db.sync()
|
|
1195
|
+
clear_temporary_message(len(msg))
|
|
1196
|
+
return result
|
|
1197
|
+
|
|
1198
|
+
def report(self) -> None:
|
|
1199
|
+
"""Print a report of the results of the checks."""
|
|
1200
|
+
ts = self.db.get_last_prereq_ts()
|
|
1201
|
+
t0 = datetime.strptime(str(ts), "%Y%m%d%H%M%S")
|
|
1202
|
+
for row in self.db.report_prereq(t0):
|
|
1203
|
+
print_color(row[2], row[3])
|
|
1204
|
+
for row in self.db.report_debug(t0):
|
|
1205
|
+
for line in row[0].split("\n"):
|
|
1206
|
+
if self.debug:
|
|
1207
|
+
print_color(line, "🦟")
|
|
1208
|
+
else:
|
|
1209
|
+
logging.debug(line)
|
|
1210
|
+
|
|
1211
|
+
if len(self.failures) == 0:
|
|
1212
|
+
return True
|
|
1213
|
+
print_color(Headers.ExplanationReport, "🗣️")
|
|
1214
|
+
for item in self.failures:
|
|
1215
|
+
item.get_failure()
|