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 GGUID 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 | 1155 ++++++++++++++++++++++++++++++++++++++++
scripts/qmp_helper.py | 3 +
3 files changed, 1159 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..6c7fdfe84e3a
--- /dev/null
+++ b/scripts/ghes_decode.py
@@ -0,0 +1,1155 @@
+#!/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 at 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
+ 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"),
+ ("MDL 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", None),
+ ("Raw Data Offset", 4, "int", "raw_data_offset"),
+ ("Raw Data Length", 4, "int", "raw_data_len"),
+ ("Data Length", 4, "int", None),
+ ("Error Severity", 4, "int", None),
+
+ # Generic Error Data Entry
+ ("Section Type", 16, "guid", "session_type"),
+ ("Error Severity", 4, "int", None),
+ ("Revision", 2, "int", None),
+ ("Validation Bits", 1, "int", None),
+ ("Flags", 1, "int", None),
+ ("Error Data Length", 4, "int", None),
+ ("FRU Id", 16, "guid", None),
+ ("FRU Text", 20, "str", None),
+ ("Timestamp", 8, "bcd", None),
+ ]
+
+ 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, var 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
+
+ if var is not None:
+ fields[var] = val
+
+ if fields["raw_data_len"]:
+ cper.decode("Raw Data", fields["raw_data_len"],
+ "int", pos=fields["raw_data_offset"])
+
+ if not fields["session_type"]:
+ return
+
+ print()
+
+ # Now, decode the rest of the record for known decoders
+ for guid, cls in decode_list:
+ if fields["session_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['session_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 63f3df2d75c3..32baca17ce10 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 Wed, 21 Jan 2026 12:25:15 +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 GGUID type (except for a
GGUID?
> fuzzy logic test, which is pretty much independent of the
> records meaning).
>
> Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
A few minor things inline.
I checked the specs, so all that stuff is fine including the spec bug
you mention (which I'm sure you've already reported :)
Jonathan
> ---
> MAINTAINERS | 1 +
> scripts/ghes_decode.py | 1155 ++++++++++++++++++++++++++++++++++++++++
> scripts/qmp_helper.py | 3 +
> 3 files changed, 1159 insertions(+)
> create mode 100644 scripts/ghes_decode.py
>
> diff --git a/scripts/ghes_decode.py b/scripts/ghes_decode.py
> new file mode 100644
> index 000000000000..6c7fdfe84e3a
> --- /dev/null
> +++ b/scripts/ghes_decode.py
> @@ -0,0 +1,1155 @@
> +#!/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 at the encoding logic inside each module of
rely on the encoding logic
> +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 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")
As for arm. Probably need sanity check it's the 8 byte version.
And for giggles even on x86 some are 16 bytes ;)
GDTR, IDTR
Meh, I don't care about those 16 byte ones, but not presenting the 4 byte
ones as 8 would be good.
> +
> + @staticmethod
> + def decode_list():
> + """
> + Returns a tuple with the GUID and class
> + """
> + return [(DecodeProcX86.guid, DecodeProcX86)]
> +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")
Maybe check it's not a 32 bit context? Don't decode it if it
is but don't try to decode it as 64 bit. Can get that from the
Register Context Type. Anything over 4 is fine.
> +
> + 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 DecodeDMAVT():
> + """
> + Class to decode a DMA Virtualization Technology Error as defined at
> + UEFI 2.2 - N.2.2 Section Descriptor
As below. Feels like it should be
N2.11.2 Intel VT for Directed I/O specific DMAr Error Section Descriptor.
Same for other places this comment exists.
> + """
> +
> + # GUID for DMA VT Error
> + guid = "71761d37-32b2-45cd-a7d0-b0fedd93e8cf"
> +class DecodeDMAIOMMU():
> + """
> + Class to decode an IOMMU DMA Error as defined at
> + UEFI 2.2 - N.2.2 Section Descriptor
Odd reference choice.
This stuff is in N2.11.3 IOMMU Specific DMAr Error Section
in 2.11. I'm too lazy to find it in the older spec.
> + """
> +
> + # GUID for IOMMU DMA Error
Maybe call it the IOMMU Specific DMAr Error
> + guid = "036f84e1-7f37-428c-a79e-575fdfaa84ec"
> +
> + fields = [
> +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
> + 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"),
As per earlier patch review I'm not sure we care about this and the following.
I don't think we'll ever see them in CPER records, unless going other something
else that encapsulates that format.
> + ("Virtual Switch", "40d26425-3396-4c4d-a5da-3d472a63af25"),
> + ("MDL Port", "8dc44363-0c96-4710-b7bf-04bb99534c3f"),
MLD -> Multi-Logical Device
> + ("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 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", None),
> + ("Raw Data Offset", 4, "int", "raw_data_offset"),
> + ("Raw Data Length", 4, "int", "raw_data_len"),
> + ("Data Length", 4, "int", None),
> + ("Error Severity", 4, "int", None),
> +
> + # Generic Error Data Entry
> + ("Section Type", 16, "guid", "session_type"),
Why session_type? Is idea it's the type of decode session we are
doing? Feels a bit too much like a typo from section_type.
> + ("Error Severity", 4, "int", None),
> + ("Revision", 2, "int", None),
> + ("Validation Bits", 1, "int", None),
> + ("Flags", 1, "int", None),
> + ("Error Data Length", 4, "int", None),
> + ("FRU Id", 16, "guid", None),
> + ("FRU Text", 20, "str", None),
> + ("Timestamp", 8, "bcd", None),
> + ]
> +
> + def __init__(self, cper_data: bytearray):
> + """
> + Initializes a byte array, decoding it, printing results at the
> + screen.
> + """
...
> +
> + # Handle common types
> + cper = DecodeField(cper_data)
> +
> + fields = {}
> + for name, size, ftype, var 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
> +
> + if var is not None:
> + fields[var] = val
> +
> + if fields["raw_data_len"]:
> + cper.decode("Raw Data", fields["raw_data_len"],
> + "int", pos=fields["raw_data_offset"])
> +
> + if not fields["session_type"]:
> + return
On Wed, 21 Jan 2026 13:27:38 +0000
Jonathan Cameron via qemu development <qemu-devel@nongnu.org> wrote:
> On Wed, 21 Jan 2026 12:25:15 +0100
> Mauro Carvalho Chehab <mchehab+huawei@kernel.org> wrote:
>
...
> > +class DecodeDMAVT():
> > + """
> > + Class to decode a DMA Virtualization Technology Error as defined at
> > + UEFI 2.2 - N.2.2 Section Descriptor
> As below. Feels like it should be
>
> N2.11.2 Intel VT for Directed I/O specific DMAr Error Section Descriptor.
> Same for other places this comment exists.
when reviewing the ghes.c driver, Igor requested to pick the oldest
possible spec when filling comments inside GHES. In this specific case,
the oldest one is UEFI 2.2:
https://uefi.org/sites/default/files/resources/UEFI_Spec_2_2_D.pdf
> > + """
> > +
> > + # GUID for DMA VT Error
> > + guid = "71761d37-32b2-45cd-a7d0-b0fedd93e8cf"
... there, at:
N.2.2 Section Descriptor
Table 227. Section Descriptor
Section Type shows several different GUIDs, including this one,
listed there as:
Intel® VT for Directed I/O specific DMAr section
•{0x71761D37, 0x32B2, 0x45cd, {0xA7, 0xD0, 0xB0,
•0xFE 0xDD, 0x93, 0xE8, 0xCF}}
That's basically why it is listed here for all the 9 GUIDs
defined at UEFI 2.2.
>
> > +class DecodeDMAIOMMU():
> > + """
> > + Class to decode an IOMMU DMA Error as defined at
> > + UEFI 2.2 - N.2.2 Section Descriptor
>
> Odd reference choice.
> This stuff is in N2.11.3 IOMMU Specific DMAr Error Section
> in 2.11. I'm too lazy to find it in the older spec.
Heh, most of the time I spent writing this patch were to seek
what spec version introduced each GUID ;-D
>
> > + """
> > +
> > + # GUID for IOMMU DMA Error
>
> Maybe call it the IOMMU Specific DMAr Error
>
> > + guid = "036f84e1-7f37-428c-a79e-575fdfaa84ec"
> > +
> > + fields = [
>
> > +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
> > + 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"),
>
> As per earlier patch review I'm not sure we care about this and the following.
> I don't think we'll ever see them in CPER records, unless going other something
> else that encapsulates that format.
I'm adding a notice before this table.
>
> > + ("Virtual Switch", "40d26425-3396-4c4d-a5da-3d472a63af25"),
> > + ("MDL Port", "8dc44363-0c96-4710-b7bf-04bb99534c3f"),
> MLD -> Multi-Logical Device
>
> > + ("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 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", None),
> > + ("Raw Data Offset", 4, "int", "raw_data_offset"),
> > + ("Raw Data Length", 4, "int", "raw_data_len"),
> > + ("Data Length", 4, "int", None),
> > + ("Error Severity", 4, "int", None),
> > +
> > + # Generic Error Data Entry
> > + ("Section Type", 16, "guid", "session_type"),
>
> Why session_type? Is idea it's the type of decode session we are
> doing? Feels a bit too much like a typo from section_type.
it is meant to be a dict key, but I'll just drop it, taking a different
approach.
> > + ("Error Severity", 4, "int", None),
> > + ("Revision", 2, "int", None),
> > + ("Validation Bits", 1, "int", None),
> > + ("Flags", 1, "int", None),
> > + ("Error Data Length", 4, "int", None),
> > + ("FRU Id", 16, "guid", None),
> > + ("FRU Text", 20, "str", None),
> > + ("Timestamp", 8, "bcd", None),
> > + ]
> > +
> > + def __init__(self, cper_data: bytearray):
> > + """
> > + Initializes a byte array, decoding it, printing results at the
> > + screen.
> > + """
>
> ...
>
> > +
> > + # Handle common types
> > + cper = DecodeField(cper_data)
> > +
> > + fields = {}
> > + for name, size, ftype, var 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
> > +
> > + if var is not None:
> > + fields[var] = val
> > +
> > + if fields["raw_data_len"]:
> > + cper.decode("Raw Data", fields["raw_data_len"],
> > + "int", pos=fields["raw_data_offset"])
> > +
> > + if not fields["session_type"]:
> > + return
>
>
On Wed, Jan 21, 2026 at 01:27:38PM +0000, Jonathan Cameron wrote:
> On Wed, 21 Jan 2026 12:25:15 +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 GGUID type (except for a
> GGUID?
>
> > fuzzy logic test, which is pretty much independent of the
> > records meaning).
> >
> > Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
>
> A few minor things inline.
>
> I checked the specs, so all that stuff is fine including the spec bug
> you mention (which I'm sure you've already reported :)
>
> Jonathan
>
> > ---
> > MAINTAINERS | 1 +
> > scripts/ghes_decode.py | 1155 ++++++++++++++++++++++++++++++++++++++++
> > scripts/qmp_helper.py | 3 +
> > 3 files changed, 1159 insertions(+)
> > create mode 100644 scripts/ghes_decode.py
> >
>
> > diff --git a/scripts/ghes_decode.py b/scripts/ghes_decode.py
> > new file mode 100644
> > index 000000000000..6c7fdfe84e3a
> > --- /dev/null
> > +++ b/scripts/ghes_decode.py
> > @@ -0,0 +1,1155 @@
> > +#!/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 at the encoding logic inside each module of
>
> rely on the encoding logic
>
> > +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 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")
>
> As for arm. Probably need sanity check it's the 8 byte version.
> And for giggles even on x86 some are 16 bytes ;)
> GDTR, IDTR
>
> Meh, I don't care about those 16 byte ones, but not presenting the 4 byte
> ones as 8 would be good.
As this decode is for debugging purposes, I tried to keep the code as simple
as possible. So, I avoided adding too much details on it. What is important,
IMO, is mainly to be able to quickly check if the Kernel report and rasdaemon
are properly decoding the message.
Also, to be fair, even on 32 bit systems, the size of this register is
8 bytes. Nothing prevent them to have values filled above 32-bits.
In a matter of fact, when using the fuzzy-testing command, the logic
there won't care about what is valid or not: right now, it will either:
- place a random number;
- place 0, if used like:
ghes_inject.py fuzzy --zero
Btw, if I'm not mistaken, currently, RAS is only enabled in QEMU for arm64.
>
>
>
> > +
> > + @staticmethod
> > + def decode_list():
> > + """
> > + Returns a tuple with the GUID and class
> > + """
> > + return [(DecodeProcX86.guid, DecodeProcX86)]
>
> > +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")
>
> Maybe check it's not a 32 bit context? Don't decode it if it
> is but don't try to decode it as 64 bit. Can get that from the
> Register Context Type. Anything over 4 is fine.
I can, but see above: this can actually be a 64-bit number even
when only 32-bits are valid (when used with fuzzy injector).
> > +
> > + 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 DecodeDMAVT():
> > + """
> > + Class to decode a DMA Virtualization Technology Error as defined at
> > + UEFI 2.2 - N.2.2 Section Descriptor
> As below. Feels like it should be
>
> N2.11.2 Intel VT for Directed I/O specific DMAr Error Section Descriptor.
> Same for other places this comment exists.
>
>
> > + """
> > +
> > + # GUID for DMA VT Error
> > + guid = "71761d37-32b2-45cd-a7d0-b0fedd93e8cf"
>
> > +class DecodeDMAIOMMU():
> > + """
> > + Class to decode an IOMMU DMA Error as defined at
> > + UEFI 2.2 - N.2.2 Section Descriptor
>
> Odd reference choice.
> This stuff is in N2.11.3 IOMMU Specific DMAr Error Section
> in 2.11. I'm too lazy to find it in the older spec.
>
> > + """
> > +
> > + # GUID for IOMMU DMA Error
>
> Maybe call it the IOMMU Specific DMAr Error
>
> > + guid = "036f84e1-7f37-428c-a79e-575fdfaa84ec"
> > +
> > + fields = [
>
> > +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
> > + 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"),
>
> As per earlier patch review I'm not sure we care about this and the following.
> I don't think we'll ever see them in CPER records, unless going other something
> else that encapsulates that format.
As I commented on patch 2, UEFI 2.11 accepts those in CPERs. So, except if
a new errata will drop them, from UEFI perspective, all those tyepes could
be found on CPERs sent to OSPM.
From my perspective, for fuzzy-testing, we'd like to be able to inject them
as well, to check if those won't cause any troubles at OSPM implementation.
See more below...
>
> > + ("Virtual Switch", "40d26425-3396-4c4d-a5da-3d472a63af25"),
> > + ("MDL Port", "8dc44363-0c96-4710-b7bf-04bb99534c3f"),
> MLD -> Multi-Logical Device
>
> > + ("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)
... here, we are not actually decoding the CXL-specific part of the
message, but, instead, displaying as a byte sequence.
When time comes and we implement "cxl" command(s) to the tool, we
may want to decode the event log according with the CXL spec.
For such purpose, we won't need to decode all types - just the ones
that makes sense in practice.
> > +
> > + @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 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", None),
> > + ("Raw Data Offset", 4, "int", "raw_data_offset"),
> > + ("Raw Data Length", 4, "int", "raw_data_len"),
> > + ("Data Length", 4, "int", None),
> > + ("Error Severity", 4, "int", None),
> > +
> > + # Generic Error Data Entry
> > + ("Section Type", 16, "guid", "session_type"),
>
> Why session_type? Is idea it's the type of decode session we are
> doing? Feels a bit too much like a typo from section_type.
>
>
> > + ("Error Severity", 4, "int", None),
> > + ("Revision", 2, "int", None),
> > + ("Validation Bits", 1, "int", None),
> > + ("Flags", 1, "int", None),
> > + ("Error Data Length", 4, "int", None),
> > + ("FRU Id", 16, "guid", None),
> > + ("FRU Text", 20, "str", None),
> > + ("Timestamp", 8, "bcd", None),
> > + ]
> > +
> > + def __init__(self, cper_data: bytearray):
> > + """
> > + Initializes a byte array, decoding it, printing results at the
> > + screen.
> > + """
>
> ...
>
> > +
> > + # Handle common types
> > + cper = DecodeField(cper_data)
> > +
> > + fields = {}
> > + for name, size, ftype, var 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
> > +
> > + if var is not None:
> > + fields[var] = val
> > +
> > + if fields["raw_data_len"]:
> > + cper.decode("Raw Data", fields["raw_data_len"],
> > + "int", pos=fields["raw_data_offset"])
> > +
> > + if not fields["session_type"]:
> > + return
>
--
Thanks,
Mauro
© 2016 - 2026 Red Hat, Inc.