Add a decoder to help debugging injected CPERs. This is more
relevant when we add fuzzy-testing error inject, as the
decoder is helpful to identify what it packages will be sent
via QEMU to the firmware-fist logic.
By purpose, I opted to keep this completely independent from
the encoders implementation, as this can be used even when
there are no encoders for a certain GUID type (except for a
fuzzy logic test, which is pretty much independent of the
records meaning).
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
---
MAINTAINERS | 1 +
scripts/ghes_decode.py | 1158 ++++++++++++++++++++++++++++++++++++++++
scripts/qmp_helper.py | 3 +
3 files changed, 1162 insertions(+)
create mode 100644 scripts/ghes_decode.py
diff --git a/MAINTAINERS b/MAINTAINERS
index 36a2be3ddba7..a970c47dd089 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2225,6 +2225,7 @@ S: Maintained
F: hw/arm/ghes_cper.c
F: hw/acpi/ghes_cper_stub.c
F: qapi/acpi-hest.json
+F: scripts/ghes_decode.py
F: scripts/ghes_inject.py
F: scripts/arm_processor_error.py
F: scripts/qmp_helper.py
diff --git a/scripts/ghes_decode.py b/scripts/ghes_decode.py
new file mode 100644
index 000000000000..6f6446c95e58
--- /dev/null
+++ b/scripts/ghes_decode.py
@@ -0,0 +1,1158 @@
+#!/usr/bin/env python3
+#
+# pylint: disable=R0903,R0912,R0913,R0915,R0917,R1713,E1121,C0302,W0613
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright (C) 2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
+
+"""
+Helper classes to decode a generic error data entry.
+
+By purpose, the logic here is independent of the logic inside qmp_helper
+and other modules. With a different implementation, it is more likely to
+discover bugs at the error injection logic. Also, as this can be used to
+dump errors injected by reproducing an error mesage or for fuzzy error
+injection, it can't rely on the encoding logic inside each module of
+ghes_inject.py.
+
+To make the decoder simple, the decode logic here is at field level, not
+trying to decode bitmaps.
+"""
+
+from typing import Optional
+
+class DecodeField():
+ """
+ Helper functions to decode a field, printing its results
+ """
+
+ def __init__(self, cper_data: bytearray):
+ """Initialize the decoder with a cper bytearray"""
+ self.data = cper_data
+ self.pos = 0
+ self.past_end = False
+
+ @property
+ def remaining(self):
+ """Returns the number of bytes not decoded yet"""
+ return max(0, len(self.data) - self.pos)
+
+ @property
+ def is_end(self):
+ """
+ Returns true if all bytes were decoded and it didn't try
+ to read past the end.
+ """
+ if not self.past_end and self.pos == len(self.data):
+ return True
+
+ return False
+
+ def decode(self, name: str, size: int, ftype: str,
+ pos: Optional[int] = None,
+ show_incomplete: Optional[bool] = False) -> None:
+ """
+ Decodes and outputs a specified field from an ACPI table.
+
+ For ints, we opted to decode them byte by byte, thus not being
+ limited to an integer max size.
+
+ Arguments:
+ name: name of the field
+ size: number of bytes of the field
+ ftype: field type (str, int, guid, bcd)
+ pos: if specified, show a field at the specific position. If
+ not, use last position and increment it with size at the end
+ """
+ if pos:
+ cur_pos = pos
+ else:
+ cur_pos = self.pos
+
+ try:
+ if cur_pos + size > len(self.data):
+ if not pos:
+ self.past_end = True
+
+ if not show_incomplete:
+ decoded = "N/A"
+ return None
+
+ raw_data = self.data[cur_pos:cur_pos + size]
+
+ decoded = ""
+ if ftype == "str":
+ failures = False
+ for b in raw_data:
+ if b >= 32 and b <= 126: # pylint: disable=R1716
+ decoded += chr(b)
+ elif b:
+ decoded += '.'
+ failures = True
+ else:
+ decoded += r'\x0'
+
+ if failures:
+ decoded += " # warning: non-ascii chars found"
+
+ if self.past_end:
+ if decoded:
+ decoded += " "
+ decoded += "EOL"
+
+ elif ftype == "int":
+ i = 0
+ for b in reversed(raw_data):
+ i += 1
+ if len(raw_data) > 8 and i > 1:
+ decoded += " "
+
+ decoded += f"{b:02x}"
+
+ if self.past_end:
+ if decoded:
+ decoded += " "
+ decoded += "EOL"
+
+ elif ftype == "guid":
+ if len(raw_data) != 16 or size != 16:
+ decoded = "Invalid GUID"
+ else:
+ for b in reversed(raw_data[0:4]):
+ decoded += f"{b:02x}"
+
+ decoded += "-"
+
+ for b in reversed(raw_data[4:6]):
+ decoded += f"{b:02x}"
+
+ decoded += "-"
+
+ for b in reversed(raw_data[6:8]):
+ decoded += f"{b:02x}"
+
+ decoded += "-"
+
+ for b in raw_data[8:10]:
+ decoded += f"{b:02x}"
+
+ decoded += "-"
+
+ for b in raw_data[10:]:
+ decoded += f"{b:02x}"
+
+ raw_data = decoded
+
+ elif ftype == "bcd":
+ val = 0
+ for b in raw_data:
+ if (b & 0xf0) > 9 or (b & 0x0f) > 9:
+ raise ValueError("Invalid BCD value")
+ val = (val << 4) | (b & 0x0f)
+
+ decoded = f"{val:0{size * 2}x}"
+
+ if self.past_end:
+ if decoded:
+ decoded += " "
+ decoded += "EOL"
+ else:
+ decoded = f"Warning: Unknown format {ftype}"
+
+ except ValueError as e:
+ decoded = f"Error decoding {e}"
+
+ finally:
+ print(f"{name:<26s}: {decoded}")
+ if not pos:
+ self.pos += size
+
+ return raw_data
+
+
+class DecodeProcGeneric():
+ """
+ Class to decode a Generic Processor Error as defined at
+ UEFI 2.1 - N.2.2 Section Descriptor
+ """
+ # GUID for Generic Processor Error
+ guid = "9876ccad-47b4-4bdb-b65e-16f193c4f3db"
+
+ fields = [
+ ("Validation Bits", 8, "int"),
+ ("Processor Type", 1, "int"),
+ ("Processor ISA", 1, "int"),
+ ("Processor Error Type", 1, "int"),
+ ("Operation", 1, "int"),
+ ("Flags", 1, "int"),
+ ("Level", 1, "int"),
+ ("Reserved", 2, "int"),
+ ("CPU Version Info", 8, "int"),
+ ("CPU Brand String", 128, "str"),
+ ("Processor ID", 8, "int"),
+ ("Target Address", 8, "int"),
+ ("Requestor Identifier", 8, "int"),
+ ("Responder Identifier", 8, "int"),
+ ("Instruction IP", 8, "int")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode Generic Processor Error"""
+ print("Generic Processor Error")
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodeProcGeneric.guid, DecodeProcGeneric)]
+
+class DecodeProcX86():
+ """
+ Class to decode an x86 Processor Error as defined at
+ UEFI 2.1 - N.2.2 Section Descriptor
+ """
+
+ # GUID for x86 Processor Error
+ guid = "dc3ea0b0-a144-4797-b95b-53fa242b6e1d"
+
+ pei_fields = [
+ ("Error Structure Type", 16, "guid"),
+ ("Validation Bits", 8, "int"),
+ ("Check Information", 8, "int"),
+ ("Target Identifier", 8, "int"),
+ ("Requestor Identifier", 8, "int"),
+ ("Responder Identifier", 8, "int"),
+ ("Instruction Pointer", 8, "int")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode x86 Processor Error"""
+ print("x86 Processor Error")
+
+ val = self.cper.decode("Validation Bits", 8, "int")
+ try:
+ val_bits = int.from_bytes(val, byteorder='little')
+ except ValueError, TypeError:
+ val_bits = 0
+
+ error_info_num = (val_bits >> 2) & 0x3f # bits 2-7
+ context_info_num = (val_bits >> 8) & 0xff # bits 8-13
+
+ self.cper.decode("Local APIC_ID", 8, "int")
+ self.cper.decode("CPUID Info", 48, "int")
+
+ for pei in range(0, error_info_num):
+ if self.cper.past_end:
+ return
+
+ print()
+ print(f"Processor Error Info {pei}")
+ for name, size, ftype in self.pei_fields:
+ self.cper.decode(name, size, ftype)
+
+ for ctx in range(0, context_info_num):
+ if self.cper.past_end:
+ return
+
+ print()
+ print(f"Context {ctx}")
+
+ self.cper.decode("Register Context Type", 2, "int")
+
+ val = self.cper.decode("Register Array Size", 2, "int")
+ try:
+ context_size = int(int.from_bytes(val, byteorder='little') / 8)
+ except ValueError, TypeError:
+ context_size = 0
+
+ self.cper.decode("MSR Address", 4, "int")
+ self.cper.decode("MM Register Address", 8, "int")
+
+ for reg in range(0, context_size):
+ if self.cper.past_end:
+ return
+ self.cper.decode(f"Register offset {reg:<3}", 8, "int")
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodeProcX86.guid, DecodeProcX86)]
+
+class DecodeProcItanium():
+ """
+ Class to decode an Itanium Processor Error as defined at
+ UEFI 2.1 - N.2.2 Section Descriptor
+ """
+
+ # GUID for Itanium Processor Error
+ guid = "e429faf1-3cb7-11d4-bca7-0080c73c8881"
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """
+ Decode Itanium Processor Error.
+
+ Itanum processors stopped being sold in 2021. Probably not much
+ sense implementing a decoder for it.
+ """
+
+ print("Itanium Processor Error")
+
+ remaining = self.cper.remaining
+ if remaining:
+ print()
+ self.cper.decode("Data", remaining, "int")
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodeProcItanium.guid, DecodeProcItanium)]
+
+
+class DecodeProcArm():
+ """
+ Class to decode an ARM Processor Error as defined at
+ UEFI 2.6 - N.2.2 Section Descriptor
+ """
+
+ # GUID for ARM Processor Error
+ guid = "e19e3d16-bc11-11e4-9caa-c2051d5d46b0"
+
+ arm_pei_fields = [
+ ("Version", 1, "int"),
+ ("Length", 1, "int"),
+ ("valid", 2, "int"),
+ ("type", 1, "int"),
+ ("multiple-error", 2, "int"),
+ ("flags", 1, "int"),
+ ("error-info", 8, "int"),
+ ("virt-addr", 8, "int"),
+ ("phy-addr", 8, "int"),
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode Processor ARM"""
+
+ print("ARM Processor Error")
+
+ start = self.cper.pos
+
+ self.cper.decode("Valid", 4, "int")
+
+ val = self.cper.decode("Error Info num", 2, "int")
+ try:
+ error_info_num = int.from_bytes(val, byteorder='little')
+ except ValueError, TypeError:
+ error_info_num = 0
+
+ val = self.cper.decode("Context Info num", 2, "int")
+ try:
+ context_info_num = int.from_bytes(val, byteorder='little')
+ except ValueError, TypeError:
+ context_info_num = 0
+
+ val = self.cper.decode("Section Length", 4, "int")
+ try:
+ section_length = int.from_bytes(val, byteorder='little')
+ except ValueError, TypeError:
+ section_length = 0
+
+ self.cper.decode("Error affinity level", 1, "int")
+ self.cper.decode("Reserved", 3, "int")
+ self.cper.decode("MPIDR_EL1", 8, "int")
+ self.cper.decode("MIDR_EL1", 8, "int")
+ self.cper.decode("Running State", 4, "int")
+ self.cper.decode("PSCI State", 4, "int")
+
+ for pei in range(0, error_info_num):
+ if self.cper.past_end:
+ return
+
+ print()
+ print(f"Processor Error Info {pei}")
+ for name, size, ftype in self.arm_pei_fields:
+ self.cper.decode(name, size, ftype)
+
+ for ctx in range(0, context_info_num):
+ if self.cper.past_end:
+ return
+
+ print()
+ print(f"Context {ctx}")
+ self.cper.decode("Version", 2, "int")
+ self.cper.decode("Register Context Type", 2, "int")
+ val = self.cper.decode("Register Array Size", 4, "int")
+ try:
+ context_size = int(int.from_bytes(val, byteorder='little') / 8)
+ except ValueError:
+ context_size = 0
+
+ for reg in range(0, context_size):
+ if self.cper.past_end:
+ return
+ self.cper.decode(f"Register {reg:<3}", 8, "int")
+
+ remaining = max(section_length + start - self.cper.pos, 0)
+ if remaining:
+ print()
+ self.cper.decode("Vendor data", remaining, "int")
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodeProcArm.guid, DecodeProcArm)]
+
+
+class DecodePlatformMem():
+ """
+ Class to decode a Platform Memory Error as defined at
+ UEFI 2.1 - N.2.2 Section Descriptor
+ """
+
+ # GUID for Platform Memory Error
+ guid = "a5bc1114-6f64-4ede-b863-3e83ed7c83b1"
+
+ fields = [
+ ("Validation Bits", 8, "int"),
+ ("Error Status", 8, "int"),
+ ("Physical Address", 8, "int"),
+ ("Physical Address Mask", 8, "int"),
+ ("Node", 2, "int"),
+ ("Card", 2, "int"),
+ ("Module", 2, "int"),
+ ("Bank", 2, "int"),
+ ("Device", 2, "int"),
+ ("Row", 2, "int"),
+ ("Column", 2, "int"),
+ ("Bit Position", 2, "int"),
+ ("Requestor ID", 8, "int"),
+ ("Responder ID", 8, "int"),
+ ("Target ID", 8, "int"),
+ ("Memory Error Type", 1, "int"),
+ ("Extended", 1, "int"),
+ ("Rank Number", 2, "int"),
+ ("Card Handle", 2, "int"),
+ ("Module Handle", 2, "int")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode Platform Memory Error"""
+ print("Platform Memory Error")
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodePlatformMem.guid, DecodePlatformMem)]
+
+
+class DecodePlatformMem2():
+ """
+ Class to decode a Platform Memory Error (Type 2) as defined at
+ UEFI 2.5 - N.2.6. Memory Error Section 2
+ """
+
+ # GUID for Platform Memory Error Type 2
+ guid = "61ec04fc-48e6-d813-25c9-8daa44750b12"
+
+ fields = [
+ ("Validation Bits", 8, "int"),
+ ("Error Status", 8, "int"),
+ ("Physical Address", 8, "int"),
+ ("Physical Address Mask", 8, "int"),
+ ("Node", 2, "int"),
+ ("Card", 2, "int"),
+ ("Module", 2, "int"),
+ ("Bank", 2, "int"),
+ ("Device", 4, "int"),
+ ("Row", 4, "int"),
+ ("Column", 4, "int"),
+ ("Rank", 4, "int"),
+ ("Bit Position", 4, "int"),
+ ("Chip Identification", 1, "int"),
+ ("Memory Error Type", 1, "int"),
+ ("Status", 1, "int"),
+ ("Reserved", 1, "int"),
+ ("Requestor ID", 8, "int"),
+ ("Responder ID", 8, "int"),
+ ("Target ID", 8, "int"),
+ ("Card Handle", 4, "int"),
+ ("Module Handle", 4, "int")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode Platform Memory Error Type 2"""
+ print("Platform Memory Error Type 2")
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodePlatformMem2.guid, DecodePlatformMem2)]
+
+
+class DecodePCIe():
+ """
+ Class to decode a PCI Express Error as defined at
+ UEFI 2.1 - N.2.2 Section Descriptor
+ """
+ # GUID for PCI Express Error
+ guid = "d995e954-bbc1-430f-ad91-b44dcb3c6f35"
+
+ fields = [
+ ("Validation Bits", 8, "int"),
+ ("Port Type", 4, "int"),
+ ("Version", 4, "int"),
+ ("Command Status", 4, "int"),
+ ("RCRB High Address", 4, "int"),
+ ("Device ID", 16, "int"),
+ ("Device Serial Number", 8, "int"),
+ ("Bridge Control Status", 4, "int"),
+ ("Capability Structure", 60, "int"),
+ ("AER Info", 96, "int")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode PCI Express Error"""
+ print("PCI Express Error")
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodePCIe.guid, DecodePCIe)]
+
+
+class DecodePCIBus():
+ """
+ Class to decode a PCI Bus Error as defined at
+ UEFI 2.1 - N.2.2 Section Descriptor
+ """
+
+ # GUID for PCI Bus Error
+ guid = "c5753963-3b84-4095-bf78-eddad3f9c9dd"
+
+ fields = [
+ ("Validation Bits", 8, "int"),
+ ("Error Status", 8, "int"),
+ ("Error Type", 2, "int"),
+ ("Bus Id", 2, "int"),
+ ("Reserved", 4, "int"),
+ ("Bus Address", 8, "int"),
+ ("Bus Data", 8, "int"),
+ ("Bus Command", 8, "int"),
+ ("Bus Requestor Id", 8, "int"),
+ ("Bus Completer Id", 8, "int"),
+ ("Target Id", 8, "int")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode PCI Bus Error"""
+ print("PCI Bus Error")
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodePCIBus.guid, DecodePCIBus)]
+
+
+class DecodePCIDev():
+ """
+ Class to decode a PCI Device Error as defined at
+ UEFI 2.1 - N.2.2 Section Descriptor
+ """
+
+ # GUID for PCI Device Error
+ guid = "eb5e4685-ca66-4769-b6a2-26068b001326"
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode PCI Device Error"""
+ print("PCI Device Error")
+
+ self.cper.decode("Validation Bits", 8, "int")
+ self.cper.decode("Error Status", 8, "int")
+ self.cper.decode("Id Info", 16, "int")
+
+ val = self.cper.decode("Memory Number", 4, "int")
+ try:
+ mem_num = int.from_bytes(val, byteorder='little')
+ except ValueError, TypeError:
+ mem_num = 0
+
+ self.cper.decode("IO Number", 4, "int")
+
+ for mem in range(0, mem_num):
+ if self.cper.past_end:
+ return
+
+ print()
+ print(f"Register Data Pair {mem}")
+ self.cper.decode("Register 0", 8, "int")
+ self.cper.decode("Register 1", 8, "int")
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodePCIDev.guid, DecodePCIDev)]
+
+
+class DecodeFWError():
+ """
+ Class to decode a Firmware Error as defined at
+ UEFI 2.1 - N.2.2 Section Descriptor
+ """
+
+ # GUID for Firmware Error
+ guid = "81212a96-09ed-4996-9471-8d729c8e69ed"
+
+ # NOTE: UEFI 2.11 has a discrepancy, as it lists:
+ # byte offset 1: revision (1 byte)
+ # byte offset 1: reserved (7 bytes)
+ #
+ # both starting at position 1. We opted to change reserved size to 6,
+ # in order to better cope with the spec issues
+
+ fields = [
+ ("Firmware Error Record Type", 1, "int"),
+ ("Revision", 1, "int"),
+ ("Reserved", 6, "int"),
+ ("Record Identifier", 8, "int"),
+ ("Record identifier GUID extension", 16, "guid")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode Firmware Error"""
+ print("Firmware Error")
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodeFWError.guid, DecodeFWError)]
+
+
+class DecodeDMAGeneric():
+ """
+ Class to decode a Generic DMA Error as defined at
+ UEFI 2.2 - N.2.2 Section Descriptor
+ """
+
+ # GUID for Generic DMA Error
+ guid = "5b51fef7-c79d-4434-8f1b-aa62de3e2c64"
+
+ fields = [
+ ("Requester-ID", 2, "int"),
+ ("Segment Number", 2, "int"),
+ ("Fault Reason", 1, "int"),
+ ("Access Type", 1, "int"),
+ ("Address Type", 1, "int"),
+ ("Architecture Type", 1, "int"),
+ ("Device Address", 8, "int"),
+ ("Reserved", 16, "int")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode Generic DMA Error"""
+ print("Generic DMA Error")
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodeDMAGeneric.guid, DecodeDMAGeneric)]
+
+
+class DecodeDMAVT():
+ """
+ Class to decode a DMA Virtualization Technology Error as defined at
+ UEFI 2.2 - N.2.2 Section Descriptor
+ """
+
+ # GUID for DMA VT Error
+ guid = "71761d37-32b2-45cd-a7d0-b0fedd93e8cf"
+
+ fields = [
+ ("Version", 1, "int"),
+ ("Revision", 1, "int"),
+ ("OemId", 6, "int"),
+ ("Capability", 8, "int"),
+ ("Extended Capability", 8, "int"),
+ ("Global Command", 4, "int"),
+ ("Global Status", 4, "int"),
+ ("Fault Status", 4, "int"),
+ ("Reserved", 12, "int"),
+ ("Fault record", 16, "int"),
+ ("Root Entry", 16, "int"),
+ ("Context Entry", 16, "int"),
+ ("Level 6 Page Table Entry", 8, "int"),
+ ("Level 5 Page Table Entry", 8, "int"),
+ ("Level 4 Page Table Entry", 8, "int"),
+ ("Level 3 Page Table Entry", 8, "int"),
+ ("Level 2 Page Table Entry", 8, "int"),
+ ("Level 1 Page Table Entry", 8, "int")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode DMA VT Error"""
+ print("DMA VT Error")
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodeDMAVT.guid, DecodeDMAVT)]
+
+
+class DecodeDMAIOMMU():
+ """
+ Class to decode an IOMMU DMA Error as defined at
+ UEFI 2.2 - N.2.2 Section Descriptor
+ """
+
+ # GUID for IOMMU DMA Error
+ guid = "036f84e1-7f37-428c-a79e-575fdfaa84ec"
+
+ fields = [
+ ("Revision", 1, "int"),
+ ("Reserved", 7, "int"),
+ ("Control", 8, "int"),
+ ("Status", 8, "int"),
+ ("Reserved", 8, "int"),
+ ("Event Log Entry", 16, "int"),
+ ("Reserved", 16, "int"),
+ ("Device Table Entry", 32, "int"),
+ ("Level 6 Page Table Entry", 8, "int"),
+ ("Level 5 Page Table Entry", 8, "int"),
+ ("Level 4 Page Table Entry", 8, "int"),
+ ("Level 3 Page Table Entry", 8, "int"),
+ ("Level 2 Page Table Entry", 8, "int"),
+ ("Level 1 Page Table Entry", 8, "int")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode IOMMU DMA Error"""
+ print("IOMMU DMA Error")
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodeDMAIOMMU.guid, DecodeDMAIOMMU)]
+
+
+class DecodeCCIXPER():
+ """
+ Class to decode a CCIX Protocol Error as defined at
+ UEFI 2.8 - N.2.12. CCIX PER Log Error Section
+ """
+
+ # GUID for CCIX Protocol Error
+ guid = "91335ef6-ebfb-4478-a6a6-88b728cf75d7"
+
+ fields = [
+ ("Validation Bits", 8, "int"),
+ ("CCIX Source ID", 1, "int"),
+ ("CCIX Port ID", 1, "int"),
+ ("Reserved", 2, "int"),
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode CCIX Protocol Error"""
+ print("CCIX Protocol Error")
+
+ val = self.cper.decode("Length", 4, "int")
+ try:
+ length = int.from_bytes(val, byteorder='little')
+ except ValueError, TypeError:
+ length = 0
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ remaining = max(0, length - self.cper.pos)
+ for dword in range(0, int(remaining / 4)):
+ if self.cper.past_end:
+ return
+
+ self.cper.decode(f"CCIX PER log {dword}", 4, "int")
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodeCCIXPER.guid, DecodeCCIXPER)]
+
+
+class DecodeCXLProtErr():
+ """
+ Class to decode a CXL Protocol Error as defined at
+ UEFI 2.9 - N.2.13. Compute Express Link (CXL) Protocol Error Section
+ """
+
+ # GUID for CXL Protocol Error
+ guid = "80b9efb4-52b5-4de3-a777-68784b771048"
+
+ fields = [
+ ("Validation Bits", 8, "int"),
+ ("CXL Agent Type", 1, "int"),
+ ("Reserved", 7, "int"),
+ ("CXL Agent Address", 8, "int"),
+ ("Device ID", 16, "int"),
+ ("Device Serial Number", 8, "int"),
+ ("Capability Structure", 60, "int"),
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode CXL Protocol Error"""
+ print("CXL Protocol Error")
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ val = self.cper.decode("CXL DVSEC Length", 2, "int")
+ try:
+ cxl_devsec_len = int.from_bytes(val, byteorder='little')
+ except ValueError, TypeError:
+ cxl_devsec_len = 0
+
+ val = self.cper.decode("CXL Error Log Length", 2, "int")
+ try:
+ cxl_error_log_len = int.from_bytes(val, byteorder='little')
+ except ValueError, TypeError:
+ cxl_error_log_len = 0
+
+ self.cper.decode("Reserved", 4, "int")
+ self.cper.decode("CXL DVSEC", cxl_devsec_len, "int",
+ show_incomplete=True)
+ self.cper.decode("CXL Error Log", cxl_error_log_len, "int",
+ show_incomplete=True)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodeCXLProtErr.guid, DecodeCXLProtErr)]
+
+
+class DecodeCXLCompEvent():
+ """
+ Class to decode a CXL Component Error as defined at
+ UEFI 2.9 - N.2.14. CXL Component Events Section
+
+ Currently, the decoder handles only the common fields, displaying
+ the CXL Component Event Log field in bytes.
+ """
+
+ # GUIDs, as defined at CXL specification 3.2: 8.2.10.2.1 Event Records
+ # on Table 8-55. Common Event Record Format
+ #
+ # Please notice that, in practice, not all those events will be passed
+ # to OSPM. Some may be handled internally.
+ guids = [
+ ("General Media", "fbcd0a77-c260-417f-85a9-088b1621eba6"),
+ ("DRAM", "601dcbb3-9c06-4eab-b8af-4e9bfb5c9624"),
+ ("Memory Module", "fe927475-dd59-4339-a586-79bab113bc74"),
+ ("Memory Sparing", "e71f3a40-2d29-4092-8a39-4d1c966c7c65"),
+ ("Physical Switch", "77cf9271-9c02-470b-9fe4-bc7b75f2da97"),
+ ("Virtual Switch", "40d26425-3396-4c4d-a5da-3d472a63af25"),
+ ("Multi-Logical Device Port", "8dc44363-0c96-4710-b7bf-04bb99534c3f"),
+ ("Dynamic Capabilities", "ca95afa7-f183-4018-8c2f-95268e101a2a"),
+ ]
+
+ fields = [
+ ("Validation Bits", 8, "int"),
+ ("Device ID", 12, "int"),
+ ("Device Serial Number", 8, "int")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode CXL Protocol Error"""
+ for name, guid_event in DecodeCXLCompEvent.guids:
+ if guid == guid_event:
+ print(f"CXL {name} Event Record")
+ break
+
+ val = self.cper.decode("Length", 4, "int")
+ try:
+ length = int.from_bytes(val, byteorder='little')
+ except ValueError, TypeError:
+ length = 0
+
+ for name, size, ftype in self.fields:
+ self.cper.decode(name, size, ftype)
+
+ length = max(0, length - self.cper.pos)
+
+ self.cper.decode("CXL Component Event Log", length, "int",
+ show_incomplete=True)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+
+ guid_list = []
+
+ for _, guid in DecodeCXLCompEvent.guids:
+ guid_list.append((guid, DecodeCXLCompEvent))
+
+ return guid_list
+
+
+class DecodeFRUMemoryPoison():
+ """
+ Class to decode a CXL Protocol Error as defined at
+ UEFI 2.11 - N.2.15 FRU Memory Poison Section
+ """
+
+ # GUID for FRU Memory Poison Section
+ guid = "5e4706c1-5356-48c6-930b-52f2120a4458"
+
+ common_fields = [
+ ("Checksum", 4, "int"),
+ ("Validation Bits", 8, "int"),
+ ("FRU Architecture Type", 4, "int"),
+ ("FRU Architecture Value", 8, "int"),
+ ("FRU Identifier Type", 4, "int"),
+ ("FRU Identifier Value", 8, "int")
+ ]
+
+ poison_fields = [
+ ("Poison Timestamp", 8, "int"),
+ ("Hardware Identifier Type", 4, "int"),
+ ("Hardware Identifier Value", 8, "int"),
+ ("Address Type", 4, "int"),
+ ("Address Value", 8, "int")
+ ]
+
+ def __init__(self, cper: DecodeField):
+ self.cper = cper
+
+ def decode(self, guid):
+ """Decode CXL Protocol Error"""
+ print("FRU Memory Poison")
+
+ for name, size, ftype in self.common_fields:
+ self.cper.decode(name, size, ftype)
+
+ val = self.cper.decode("Poison List Entries", 4, "int")
+ try:
+ poison_list_entries = int.from_bytes(val, byteorder='little')
+ except ValueError, TypeError:
+ poison_list_entries = 0
+
+ for entry in range(0, poison_list_entries):
+ if self.cper.past_end:
+ return
+
+ print()
+ print(f"Poison List {entry}")
+ for name, size, ftype in self.poison_fields:
+ if self.cper.past_end:
+ return
+
+ self.cper.decode(name, size, ftype)
+
+ @staticmethod
+ def decode_list():
+ """
+ Returns a tuple with the GUID and class
+ """
+ return [(DecodeFRUMemoryPoison.guid, DecodeFRUMemoryPoison)]
+
+
+class DecodeGhesEntry():
+ """
+ Class to decode a GHESv2 element, as defined at:
+ ACPI 6.1: 18.3.2.8 Generic Hardware Error Source version 2
+ """
+
+ # Fields present on all CPER records
+ common_fields = [
+ # Generic Error Status Block fields
+ ("Block Status", 4, "int"),
+ ("Raw Data Offset", 4, "int"),
+ ("Raw Data Length", 4, "int"),
+ ("Data Length", 4, "int"),
+ ("Error Severity", 4, "int"),
+
+ # Generic Error Data Entry
+ ("Section Type", 16, "guid"),
+ ("Error Severity", 4, "int"),
+ ("Revision", 2, "int"),
+ ("Validation Bits", 1, "int"),
+ ("Flags", 1, "int"),
+ ("Error Data Length", 4, "int"),
+ ("FRU Id", 16, "guid"),
+ ("FRU Text", 20, "str"),
+ ("Timestamp", 8, "bcd"),
+ ]
+
+ def __init__(self, cper_data: bytearray):
+ """
+ Initializes a byte array, decoding it, printing results at the
+ screen.
+ """
+
+ # Create a decode list with the per-type decoders
+ decode_list = []
+ decode_list += DecodeProcGeneric.decode_list()
+ decode_list += DecodeProcX86.decode_list()
+ decode_list += DecodeProcItanium.decode_list()
+ decode_list += DecodeProcArm.decode_list()
+ decode_list += DecodePlatformMem.decode_list()
+ decode_list += DecodePlatformMem2.decode_list()
+ decode_list += DecodePCIe.decode_list()
+ decode_list += DecodePCIBus.decode_list()
+ decode_list += DecodePCIDev.decode_list()
+ decode_list += DecodeFWError.decode_list()
+ decode_list += DecodeDMAGeneric.decode_list()
+ decode_list += DecodeDMAVT.decode_list()
+ decode_list += DecodeDMAIOMMU.decode_list()
+ decode_list += DecodeCCIXPER.decode_list()
+ decode_list += DecodeCXLProtErr.decode_list()
+ decode_list += DecodeCXLCompEvent.decode_list()
+ decode_list += DecodeFRUMemoryPoison.decode_list()
+
+ # Handle common types
+ cper = DecodeField(cper_data)
+
+ fields = {}
+ for name, size, ftype in self.common_fields:
+ val = cper.decode(name, size, ftype)
+
+ if ftype == "int":
+ try:
+ val = int.from_bytes(val, byteorder='little')
+ except ValueError, TypeError:
+ val = 0
+
+ fields[name] = val
+
+ if fields.get("Raw Data Length"):
+ cper.decode("Raw Data", fields.get("Raw Data Length", 0),
+ "int", pos=fields.get("Raw Data Offset", 0))
+
+ if not fields.get("Section Type"):
+ return
+
+ print()
+
+ # Now, decode the rest of the record for known decoders
+ for guid, cls in decode_list:
+ if fields.get("Section Type", "") == guid:
+ dec = cls(cper)
+ dec.decode(guid)
+
+ if not cper.is_end:
+ print()
+ print("Warning: incomplete decode or broken CPER")
+ if cper.remaining:
+ cper.decode("Extra Data", cper.remaining, "int")
+
+ print()
+ return
+
+ # If we don't have a class to decode the full payload,
+ # output the undecoded part
+ print(f"Unknown GGID: {fields.get('Section Type', "")}")
+ remaining = cper.remaining
+ if remaining:
+ cper.decode("Payload", remaining, "int")
+
+ print()
diff --git a/scripts/qmp_helper.py b/scripts/qmp_helper.py
index d5ffd51f161e..3e5b7a6853cf 100755
--- a/scripts/qmp_helper.py
+++ b/scripts/qmp_helper.py
@@ -21,6 +21,7 @@
sys.path.append(os_path.join(qemu_dir, 'python'))
from qemu.qmp.legacy import QEMUMonitorProtocol
+ from ghes_decode import DecodeGhesEntry
except ModuleNotFoundError as exc:
print(f"Module '{exc.name}' not found.")
@@ -677,6 +678,8 @@ def send_cper(self, notif_type, payload,
util.dump_bytearray("Payload", payload)
+ DecodeGhesEntry(cper_data)
+
return self.send_cper_raw(cper_data, timeout=timeout)
def search_qom(self, path, prop, regex):
--
2.52.0
On Fri, 23 Jan 2026 14:35:21 +0100 Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote: > Add a decoder to help debugging injected CPERs. This is more > relevant when we add fuzzy-testing error inject, as the > decoder is helpful to identify what it packages will be sent > via QEMU to the firmware-fist logic. > > By purpose, I opted to keep this completely independent from > the encoders implementation, as this can be used even when > there are no encoders for a certain GUID type (except for a > fuzzy logic test, which is pretty much independent of the > records meaning). > > Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
© 2016 - 2026 Red Hat, Inc.