Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions contrib/pc-sanity/bin/acpi_bios_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env python3

import subprocess


def print_bios_info():
"""Print BIOS information from DMI fields."""
dmi_fields = {
"date": "/sys/class/dmi/id/bios_date",
"release": "/sys/class/dmi/id/bios_release",
"vendor": "/sys/class/dmi/id/bios_vendor",
"version": "/sys/class/dmi/id/bios_version",
}

for field, path in dmi_fields.items():
try:
with open(path, "r") as f:
value = f.read().strip()
except (OSError, IOError) as e:
value = f"Unable to read {path}: {e}"

print(f"BIOS {field}: {value}")


def check_acpi_bios_errors():
"""
Check for ACPI BIOS errors in the current boot's kernel messages.
Raises SystemExit if errors are found.
"""
journal = subprocess.check_output(
["journalctl", "-b", "-k"],
universal_newlines=True,
stderr=subprocess.STDOUT,
)

lines = journal.splitlines()
acpi_error_lines = []
for i, line in enumerate(lines):
if "ACPI BIOS Error" in line:
acpi_error_lines.append(i)

if acpi_error_lines:
# Mimic grep -A 20: error line + 20 lines after it
picked_lines = set()
output_lines = []
last_added = -1

for error_line in acpi_error_lines:
# Add separator when next error outside of 20 lines context
if output_lines and error_line > last_added + 1:
output_lines.append("------")

for j in range(error_line, min(error_line + 21, len(lines))):
# Track picked lines to avoid duplicates if next error
# within 20 lines context
if j not in picked_lines:
picked_lines.add(j)
output_lines.append(lines[j])
last_added = j
print("!!! ACPI BIOS Error detected !!!")
print_bios_info()
for error_line in output_lines:
print(error_line)

raise SystemExit("ACPI BIOS Error detected in kernel messages")


def main():
check_acpi_bios_errors()
print("No ACPI BIOS errors detected in current boot")


if __name__ == "__main__":
main()
18 changes: 0 additions & 18 deletions contrib/pc-sanity/bin/bios-error.sh

This file was deleted.

53 changes: 53 additions & 0 deletions contrib/pc-sanity/tests/test_acpi_bios_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import unittest
from unittest.mock import patch, mock_open
import subprocess

from acpi_bios_error import check_acpi_bios_errors, main


class TestAcpiBiosError(unittest.TestCase):

@patch("subprocess.check_output")
def test_check_acpi_bios_errors_no_errors(self, mock_subprocess):
"""Test when no ACPI BIOS errors are found."""
mock_subprocess.return_value = """Sep 18 17:17:37 test-host kernel: ACPI: 28 ACPI AML tables successfully acquired and loaded
Sep 18 17:17:37 test-host kernel: ACPI Error: No pointer back to namespace node in package (___ptrval___) (20240827/dsargs-301)
Sep 18 17:17:37 test-host kernel: ACPI Error: No pointer back to namespace node in package (___ptrval___) (20240827/dsargs-301)
Sep 18 17:17:37 test-host kernel: ACPI: EC: EC started
Sep 18 17:17:37 test-host kernel: ACPI: EC: interrupt blocked
Sep 18 17:17:37 test-host kernel: ACPI: EC: EC_CMD/EC_SC=0x66, EC_DATA=0x62
Sep 18 17:17:37 test-host kernel: ACPI: EC: Boot ECDT EC used to handle transactions
Sep 18 17:17:37 test-host kernel: ACPI: [Firmware Bug]: BIOS _OSI(Linux) query ignored
Sep 18 17:17:37 test-host kernel: ACPI: USB4 _OSC: OS supports USB3+ DisplayPort+ PCIe+ XDomain+
Sep 18 17:17:37 test-host kernel: ACPI: USB4 _OSC: OS controls USB3+ DisplayPort+ PCIe+ XDomain+"""

check_acpi_bios_errors()
mock_subprocess.assert_called_once_with(
["journalctl", "-b", "-k"],
universal_newlines=True,
stderr=subprocess.STDOUT,
)

@patch("acpi_bios_error.print_bios_info")
@patch("subprocess.check_output")
def test_check_acpi_bios_errors_found(
self, mock_subprocess, mock_print_bios
):
"""Test when ACPI BIOS errors are detected."""
mock_subprocess.return_value = """Sep 18 17:17:37 test-host kernel: ACPI BIOS Error (bug): Failure creating named object [_SB.PC00.TXHC.RHUB.SS01._UPC], AE_ALREADY_EXISTS (20240827/dswload2-326)
Sep 18 17:17:37 test-host kernel: ACPI Error: AE_ALREADY_EXISTS, During name lookup/catalog (20240827/psobject-220)
Sep 18 17:17:37 test-host kernel: ACPI: Skipping parse of AML opcode: Method (0x0014)
Sep 18 17:17:37 test-host kernel: ACPI BIOS Error (bug): Failure creating named object [_SB.PC00.TXHC.RHUB.SS01._PLD], AE_ALREADY_EXISTS (20240827/dswload2-326)
Sep 18 17:17:37 test-host kernel: ACPI Error: AE_ALREADY_EXISTS, During name lookup/catalog (20240827/psobject-220)
Sep 18 17:17:37 test-host kernel: ACPI: Skipping parse of AML opcode: Method (0x0014)
Sep 18 17:17:37 test-host kernel: ACPI BIOS Error (bug): Could not resolve symbol [_SB.PC02.RP21.PXSX.TBDU.XHCI.RHUB.SS01], AE_NOT_FOUND (20240827/dswload2-162)
Sep 18 17:17:37 test-host kernel: ACPI Error: AE_NOT_FOUND, During name lookup/catalog (20240827/psobject-220)
Sep 18 17:17:37 test-host kernel: ACPI: Skipping parse of AML opcode: Scope (0x0010)
Sep 18 17:17:37 test-host kernel: ACPI: 28 ACPI AML tables successfully acquired and loaded"""

with self.assertRaises(SystemExit):
check_acpi_bios_errors()


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ template-unit: job
plugin: shell
category_id: com.canonical.plainbox::miscellanea
id: miscellanea/acpi-bios-error_{type}
command: bios-error.sh
command: acpi_bios_error.py
_summary: Check if kernel reports ACPI BIOS Error
_description:
Check if the kernel reports ACPI BIOS Error
Expand Down