From nobody Sun Nov 24 13:42:41 2024 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 395251AC443 for ; Tue, 13 Aug 2024 23:24:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723591452; cv=none; b=mANrUkWpRtHwwGnnDRBPuNfG5yN3nOf4KAqRLVIPLu7R27cuY+OoFV5Db/+ug3EYuGC8YWm9PHx9XRjmf588i0NVxA3zHThlEeZbXUk/Xt8HwfdyaGXK+1CemRGLgzCONj+lavOlFlhHrjm6xoqQL4U+Y/NVBRpu89xbage27uA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1723591452; c=relaxed/simple; bh=NbGBn1U+519h8TMHQA6b+S45MXVRj5iApKZjKvJ/IKM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nb5ilFN8Px/vqpNQc92qJ1CvciB8euglbqglScXqeCjOsUC43u8gIIaM34xGcuqnjO37EeDAKJdVcePr5LYo5Zq5RM+Flnl5ptJD6aQCjzBx10O22koUk5xZNfH4R326Fb4BlfSwOiIFkWiY5Wfo35rxUyUAV9BvXDPIiPLOiH8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=EPDaB/aJ; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="EPDaB/aJ" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 67C29C4AF16; Tue, 13 Aug 2024 23:24:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1723591451; bh=NbGBn1U+519h8TMHQA6b+S45MXVRj5iApKZjKvJ/IKM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EPDaB/aJsaUPTv8YdTrJZXFvwB8UPb2OFiRmESCNGuHi7mS4Lt2wGVw8tAt0wq28Y jxQL7Q5JukwfY3FDOCVkbyb41nR8Uy4EiQdwhsouCvzMtxCOv9TJ7layNJHfV15HrB nW/ubu7WxeZmtm9fcMwkXmQeik1cNl5le5WrY7XzhWxauB2sjv/sn+aGKf9CXcnxlk cw4AmiNfTGvpUmLVTIMyvtiXVPVuU8BLTv8OZmarbUT/yjqYhvQ4grZT7n4Y9UtHQ8 TjfYgOgWHsWdwOJPudUX7irqL9QJAvk6jjlbzFeIUkUIH4PCAkxy1EcqDYPoInWdju voQicc5xFNXVQ== Received: from mchehab by mail.kernel.org with local (Exim 4.98) (envelope-from ) id 1se0s5-00000002asc-1zvE; Wed, 14 Aug 2024 01:24:09 +0200 From: Mauro Carvalho Chehab To: Cc: Jonathan Cameron , Shiju Jose , Mauro Carvalho Chehab , Cleber Rosa , John Snow , linux-kernel@vger.kernel.org, qemu-devel@nongnu.org Subject: [PATCH v7 08/10] scripts/ghes_inject: add a script to generate GHES error inject Date: Wed, 14 Aug 2024 01:23:30 +0200 Message-ID: X-Mailer: git-send-email 2.46.0 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: Mauro Carvalho Chehab Content-Type: text/plain; charset="utf-8" Using the QMP GHESv2 API requires preparing a raw data array containing a CPER record. Add a helper script with subcommands to prepare such data. Currently, only ARM Processor error CPER record is supported. Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 3 + scripts/arm_processor_error.py | 371 +++++++++++++++++++++++++ scripts/ghes_inject.py | 51 ++++ scripts/qmp_helper.py | 486 +++++++++++++++++++++++++++++++++ 4 files changed, 911 insertions(+) create mode 100644 scripts/arm_processor_error.py create mode 100755 scripts/ghes_inject.py create mode 100644 scripts/qmp_helper.py diff --git a/MAINTAINERS b/MAINTAINERS index 1d8091818899..249ed2858198 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2083,6 +2083,9 @@ S: Maintained F: hw/arm/ghes_cper.c F: hw/acpi/ghes_cper_stub.c F: qapi/acpi-hest.json +F: scripts/ghes_inject.py +F: scripts/arm_processor_error.py +F: scripts/qmp_helper.py =20 ppc4xx L: qemu-ppc@nongnu.org diff --git a/scripts/arm_processor_error.py b/scripts/arm_processor_error.py new file mode 100644 index 000000000000..2643e4ddc5f3 --- /dev/null +++ b/scripts/arm_processor_error.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python3 +# +# pylint: disable=3DC0301,C0114,R0903,R0912,R0913,R0914,R0915,W0511 +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2024 Mauro Carvalho Chehab + +# TODO: current implementation has dummy defaults. +# +# For a better implementation, a QMP addition/call is needed to +# retrieve some data for ARM Processor Error injection: +# +# - ARM registers: power_state, mpidr. + +import argparse +import re + +from qmp_helper import qmp, util, cper_guid + +class ArmProcessorEinj: + """ + Implements ARM Processor Error injection via GHES + """ + + DESC =3D """ + Generates an ARM processor error CPER, compatible with + UEFI 2.9A Errata. + """ + + ACPI_GHES_ARM_CPER_LENGTH =3D 40 + ACPI_GHES_ARM_CPER_PEI_LENGTH =3D 32 + + # Context types + CONTEXT_AARCH32_EL1 =3D 1 + CONTEXT_AARCH64_EL1 =3D 5 + CONTEXT_MISC_REG =3D 8 + + def __init__(self, subparsers): + """Initialize the error injection class and add subparser""" + + # Valid choice values + self.arm_valid_bits =3D { + "mpidr": util.bit(0), + "affinity": util.bit(1), + "running": util.bit(2), + "vendor": util.bit(3), + } + + self.pei_flags =3D { + "first": util.bit(0), + "last": util.bit(1), + "propagated": util.bit(2), + "overflow": util.bit(3), + } + + self.pei_error_types =3D { + "cache": util.bit(1), + "tlb": util.bit(2), + "bus": util.bit(3), + "micro-arch": util.bit(4), + } + + self.pei_valid_bits =3D { + "multiple-error": util.bit(0), + "flags": util.bit(1), + "error-info": util.bit(2), + "virt-addr": util.bit(3), + "phy-addr": util.bit(4), + } + + self.data =3D bytearray() + + parser =3D subparsers.add_parser("arm", description=3Dself.DESC) + + arm_valid_bits =3D ",".join(self.arm_valid_bits.keys()) + flags =3D ",".join(self.pei_flags.keys()) + error_types =3D ",".join(self.pei_error_types.keys()) + pei_valid_bits =3D ",".join(self.pei_valid_bits.keys()) + + # UEFI N.16 ARM Validation bits + g_arm =3D parser.add_argument_group("ARM processor") + g_arm.add_argument("--arm", "--arm-valid", + help=3Df"ARM valid bits: {arm_valid_bits}") + g_arm.add_argument("-a", "--affinity", "--level", "--affinity-lev= el", + type=3Dlambda x: int(x, 0), + help=3D"Affinity level (when multiple levels ap= ply)") + g_arm.add_argument("-l", "--mpidr", type=3Dlambda x: int(x, 0), + help=3D"Multiprocessor Affinity Register") + g_arm.add_argument("-i", "--midr", type=3Dlambda x: int(x, 0), + help=3D"Main ID Register") + g_arm.add_argument("-r", "--running", + action=3Dargparse.BooleanOptionalAction, + default=3DNone, + help=3D"Indicates if the processor is running o= r not") + g_arm.add_argument("--psci", "--psci-state", + type=3Dlambda x: int(x, 0), + help=3D"Power State Coordination Interface - PS= CI state") + + # TODO: Add vendor-specific support + + # UEFI N.17 bitmaps (type and flags) + g_pei =3D parser.add_argument_group("ARM Processor Error Info (PEI= )") + g_pei.add_argument("-t", "--type", nargs=3D"+", + help=3Df"one or more error types: {error_types}") + g_pei.add_argument("-f", "--flags", nargs=3D"*", + help=3Df"zero or more error flags: {flags}") + g_pei.add_argument("-V", "--pei-valid", "--error-valid", nargs=3D"= *", + help=3Df"zero or more PEI valid bits: {pei_valid_b= its}") + + # UEFI N.17 Integer values + g_pei.add_argument("-m", "--multiple-error", nargs=3D"+", + help=3D"Number of errors: 0: Single error, 1: Mult= iple errors, 2-65535: Error count if known") + g_pei.add_argument("-e", "--error-info", nargs=3D"+", + help=3D"Error information (UEFI 2.10 tables N.18 t= o N.20)") + g_pei.add_argument("-p", "--physical-address", nargs=3D"+", + help=3D"Physical address") + g_pei.add_argument("-v", "--virtual-address", nargs=3D"+", + help=3D"Virtual address") + + # UEFI N.21 Context + g_ctx =3D parser.add_argument_group("Processor Context") + g_ctx.add_argument("--ctx-type", "--context-type", nargs=3D"*", + help=3D"Type of the context (0=3DARM32 GPR, 5=3DAR= M64 EL1, other values supported)") + g_ctx.add_argument("--ctx-size", "--context-size", nargs=3D"*", + help=3D"Minimal size of the context") + g_ctx.add_argument("--ctx-array", "--context-array", nargs=3D"*", + help=3D"Comma-separated arrays for each context") + + # Vendor-specific data + g_vendor =3D parser.add_argument_group("Vendor-specific data") + g_vendor.add_argument("--vendor", "--vendor-specific", nargs=3D"+", + help=3D"Vendor-specific byte arrays of data") + + parser.set_defaults(func=3Dself.send_cper) + + def send_cper(self, args): + """Parse subcommand arguments and send a CPER via QMP""" + + qmp_cmd =3D qmp(args.host, args.port, args.debug) + + is_cpu_type =3D re.compile(r"^([\w+]+\-)?arm\-cpu$") + cpus =3D qmp_cmd.search_qom("/machine/unattached/device", + "type", is_cpu_type) + + cper =3D {} + pei =3D {} + ctx =3D {} + vendor =3D {} + + arg =3D vars(args) + + # Handle global parameters + if args.arm: + arm_valid_init =3D False + cper["valid"] =3D util.get_choice(name=3D"valid", + value=3Dargs.arm, + choices=3Dself.arm_valid_bits, + suffixes=3D["-error", "-err"]) + else: + cper["valid"] =3D 0 + arm_valid_init =3D True + + if "running" in arg: + if args.running: + cper["running-state"] =3D util.bit(0) + else: + cper["running-state"] =3D 0 + else: + cper["running-state"] =3D 0 + + if arm_valid_init: + if args.affinity: + cper["valid"] |=3D self.arm_valid_bits["affinity"] + + if args.mpidr: + cper["valid"] |=3D self.arm_valid_bits["mpidr"] + + if "running-state" in cper: + cper["valid"] |=3D self.arm_valid_bits["running"] + + if args.psci: + cper["valid"] |=3D self.arm_valid_bits["running"] + + # Handle PEI + if not args.type: + args.type =3D ["cache-error"] + + util.get_mult_choices( + pei, + name=3D"valid", + values=3Dargs.pei_valid, + choices=3Dself.pei_valid_bits, + suffixes=3D["-valid", "--addr"], + ) + util.get_mult_choices( + pei, + name=3D"type", + values=3Dargs.type, + choices=3Dself.pei_error_types, + suffixes=3D["-error", "-err"], + ) + util.get_mult_choices( + pei, + name=3D"flags", + values=3Dargs.flags, + choices=3Dself.pei_flags, + suffixes=3D["-error", "-cap"], + ) + util.get_mult_int(pei, "error-info", args.error_info) + util.get_mult_int(pei, "multiple-error", args.multiple_error) + util.get_mult_int(pei, "phy-addr", args.physical_address) + util.get_mult_int(pei, "virt-addr", args.virtual_address) + + # Handle context + util.get_mult_int(ctx, "type", args.ctx_type, allow_zero=3DTrue) + util.get_mult_int(ctx, "minimal-size", args.ctx_size, allow_zero= =3DTrue) + util.get_mult_array(ctx, "register", args.ctx_array, allow_zero=3D= True) + + util.get_mult_array(vendor, "bytes", args.vendor, max_val=3D255) + + # Store PEI + pei_data =3D bytearray() + default_flags =3D self.pei_flags["first"] + default_flags |=3D self.pei_flags["last"] + + error_info_num =3D 0 + + for i, p in pei.items(): # pylint: disable=3DW0612 + error_info_num +=3D 1 + + # UEFI 2.10 doesn't define how to encode error information + # when multiple types are raised. So, provide a default only + # if a single type is there + if "error-info" not in p: + if p["type"] =3D=3D util.bit(1): + p["error-info"] =3D 0x0091000F + if p["type"] =3D=3D util.bit(2): + p["error-info"] =3D 0x0054007F + if p["type"] =3D=3D util.bit(3): + p["error-info"] =3D 0x80D6460FFF + if p["type"] =3D=3D util.bit(4): + p["error-info"] =3D 0x78DA03FF + + if "valid" not in p: + p["valid"] =3D 0 + if "multiple-error" in p: + p["valid"] |=3D self.pei_valid_bits["multiple-error"] + + if "flags" in p: + p["valid"] |=3D self.pei_valid_bits["flags"] + + if "error-info" in p: + p["valid"] |=3D self.pei_valid_bits["error-info"] + + if "phy-addr" in p: + p["valid"] |=3D self.pei_valid_bits["phy-addr"] + + if "virt-addr" in p: + p["valid"] |=3D self.pei_valid_bits["virt-addr"] + + # Version + util.data_add(pei_data, 0, 1) + + util.data_add(pei_data, + self.ACPI_GHES_ARM_CPER_PEI_LENGTH, 1) + + util.data_add(pei_data, p["valid"], 2) + util.data_add(pei_data, p["type"], 1) + util.data_add(pei_data, p.get("multiple-error", 1), 2) + util.data_add(pei_data, p.get("flags", default_flags), 1) + util.data_add(pei_data, p.get("error-info", 0), 8) + util.data_add(pei_data, p.get("virt-addr", 0xDEADBEEF), 8) + util.data_add(pei_data, p.get("phy-addr", 0xABBA0BAD), 8) + + # Store Context + ctx_data =3D bytearray() + context_info_num =3D 0 + + if ctx: + ret =3D qmp_cmd.send_cmd("query-target", may_open=3DTrue) + + default_ctx =3D self.CONTEXT_MISC_REG + + if "arch" in ret: + if ret["arch"] =3D=3D "aarch64": + default_ctx =3D self.CONTEXT_AARCH64_EL1 + elif ret["arch"] =3D=3D "arm": + default_ctx =3D self.CONTEXT_AARCH32_EL1 + + for k in sorted(ctx.keys()): + context_info_num +=3D 1 + + if "type" not in ctx[k]: + ctx[k]["type"] =3D default_ctx + + if "register" not in ctx[k]: + ctx[k]["register"] =3D [] + + reg_size =3D len(ctx[k]["register"]) + size =3D 0 + + if "minimal-size" in ctx: + size =3D ctx[k]["minimal-size"] + + size =3D max(size, reg_size) + + size =3D (size + 1) % 0xFFFE + + # Version + util.data_add(ctx_data, 0, 2) + + util.data_add(ctx_data, ctx[k]["type"], 2) + + util.data_add(ctx_data, 8 * size, 4) + + for r in ctx[k]["register"]: + util.data_add(ctx_data, r, 8) + + for i in range(reg_size, size): # pylint: disable=3DW0612 + util.data_add(ctx_data, 0, 8) + + # Vendor-specific bytes are not grouped + vendor_data =3D bytearray() + if vendor: + for k in sorted(vendor.keys()): + for b in vendor[k]["bytes"]: + util.data_add(vendor_data, b, 1) + + # Encode ARM Processor Error + data =3D bytearray() + + util.data_add(data, cper["valid"], 4) + + util.data_add(data, error_info_num, 2) + util.data_add(data, context_info_num, 2) + + # Calculate the length of the CPER data + cper_length =3D self.ACPI_GHES_ARM_CPER_LENGTH + cper_length +=3D len(pei_data) + cper_length +=3D len(vendor_data) + cper_length +=3D len(ctx_data) + util.data_add(data, cper_length, 4) + + util.data_add(data, arg.get("affinity-level", 0), 1) + + # Reserved + util.data_add(data, 0, 3) + + if "midr-el1" not in arg: + if cpus: + cmd_arg =3D { + 'path': cpus[0], + 'property': "midr" + } + ret =3D qmp_cmd.send_cmd("qom-get", cmd_arg, may_open=3DTr= ue) + if isinstance(ret, int): + arg["midr-el1"] =3D ret + + util.data_add(data, arg.get("mpidr-el1", 0), 8) + util.data_add(data, arg.get("midr-el1", 0), 8) + util.data_add(data, cper["running-state"], 4) + util.data_add(data, arg.get("psci-state", 0), 4) + + # Add PEI + data.extend(pei_data) + data.extend(ctx_data) + data.extend(vendor_data) + + self.data =3D data + + qmp_cmd.send_cper(cper_guid.CPER_PROC_ARM, self.data) diff --git a/scripts/ghes_inject.py b/scripts/ghes_inject.py new file mode 100755 index 000000000000..67cb6077bec8 --- /dev/null +++ b/scripts/ghes_inject.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2024 Mauro Carvalho Chehab + +""" +Handle ACPI GHESv2 error injection logic QEMU QMP interface. +""" + +import argparse +import sys + +from arm_processor_error import ArmProcessorEinj + +EINJ_DESC =3D """ +Handle ACPI GHESv2 error injection logic QEMU QMP interface. + +It allows using UEFI BIOS EINJ features to generate GHES records. + +It helps testing CPER and GHES drivers at the guest OS and how +userspace applications at the guest handle them. +""" + +def main(): + """Main program""" + + # Main parser - handle generic args like QEMU QMP TCP socket options + parser =3D argparse.ArgumentParser(formatter_class=3Dargparse.Argument= DefaultsHelpFormatter, + usage=3D"%(prog)s [options]", + description=3DEINJ_DESC) + + g_options =3D parser.add_argument_group("QEMU QMP socket options") + g_options.add_argument("-H", "--host", default=3D"localhost", type=3Ds= tr, + help=3D"host name") + g_options.add_argument("-P", "--port", default=3D4445, type=3Dint, + help=3D"TCP port number") + g_options.add_argument('-d', '--debug', action=3D'store_true') + + subparsers =3D parser.add_subparsers() + + ArmProcessorEinj(subparsers) + + args =3D parser.parse_args() + if "func" in args: + args.func(args) + else: + sys.exit(f"Please specify a valid command for {sys.argv[0]}") + +if __name__ =3D=3D "__main__": + main() diff --git a/scripts/qmp_helper.py b/scripts/qmp_helper.py new file mode 100644 index 000000000000..67b6aa4afa66 --- /dev/null +++ b/scripts/qmp_helper.py @@ -0,0 +1,486 @@ +#!/usr/bin/env python3 +# +# # pylint: disable=3DC0103,E0213,E1135,E1136,E1137,R0902,R0903,R0912,R0913 +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2024 Mauro Carvalho Chehab + +""" +Helper classes to be used by ghes_inject command classes. +""" + +import json +import sys + +from os import path as os_path + +try: + qemu_dir =3D os_path.abspath(os_path.dirname(os_path.dirname(__file__)= )) + sys.path.append(os_path.join(qemu_dir, 'python')) + + from qemu.qmp.legacy import QEMUMonitorProtocol + +except ModuleNotFoundError as exc: + print(f"Module '{exc.name}' not found.") + print("Try export PYTHONPATH=3Dtop-qemu-dir/python or run from top-qem= u-dir") + sys.exit(1) + +from base64 import b64encode + +class util: + """ + Ancillary functions to deal with bitmaps, parse arguments, + generate GUID and encode data on a bytearray buffer. + """ + + # + # Helper routines to handle multiple choice arguments + # + def get_choice(name, value, choices, suffixes=3DNone): + """Produce a list from multiple choice argument""" + + new_values =3D 0 + + if not value: + return new_values + + for val in value.split(","): + val =3D val.lower() + + if suffixes: + for suffix in suffixes: + val =3D val.removesuffix(suffix) + + if val not in choices.keys(): + if suffixes: + for suffix in suffixes: + if val + suffix in choices.keys(): + val +=3D suffix + break + + if val not in choices.keys(): + sys.exit(f"Error on '{name}': choice '{val}' is invalid.") + + val =3D choices[val] + + new_values |=3D val + + return new_values + + + def get_mult_array(mult, name, values, allow_zero=3DFalse, max_val=3DN= one): + """Add numbered hashes from integer lists""" + + if not allow_zero: + if not values: + return + else: + if values is None: + return + + if not values: + i =3D 0 + if i not in mult: + mult[i] =3D {} + + mult[i][name] =3D [] + return + + i =3D 0 + for value in values: + for val in value.split(","): + try: + val =3D int(val, 0) + except ValueError: + sys.exit(f"Error on '{name}': {val} is not an integer") + + if val < 0: + sys.exit(f"Error on '{name}': {val} is not unsigned") + + if max_val and val > max_val: + sys.exit(f"Error on '{name}': {val} is too little") + + if i not in mult: + mult[i] =3D {} + + if name not in mult[i]: + mult[i][name] =3D [] + + mult[i][name].append(val) + + i +=3D 1 + + + def get_mult_choices(mult, name, values, choices, + suffixes=3DNone, allow_zero=3DFalse): + """Add numbered hashes from multiple choice arguments""" + + if not allow_zero: + if not values: + return + else: + if values is None: + return + + i =3D 0 + for val in values: + new_values =3D util.get_choice(name, val, choices, suffixes) + + if i not in mult: + mult[i] =3D {} + + mult[i][name] =3D new_values + i +=3D 1 + + + def get_mult_int(mult, name, values, allow_zero=3DFalse): + """Add numbered hashes from integer arguments""" + if not allow_zero: + if not values: + return + else: + if values is None: + return + + i =3D 0 + for val in values: + try: + val =3D int(val, 0) + except ValueError: + sys.exit(f"Error on '{name}': {val} is not an integer") + + if val < 0: + sys.exit(f"Error on '{name}': {val} is not unsigned") + + if i not in mult: + mult[i] =3D {} + + mult[i][name] =3D val + i +=3D 1 + + + # + # Data encode helper functions + # + def bit(b): + """Simple macro to define a bit on a bitmask""" + return 1 << b + + + def data_add(data, value, num_bytes): + """Adds bytes from value inside a bitarray""" + + data.extend(value.to_bytes(num_bytes, byteorder=3D"little")) # py= lint: disable=3DE1101 + + def dump_bytearray(name, data): + """Does an hexdump of a byte array, grouping in bytes""" + + print(f"{name} ({len(data)} bytes):") + + for ln_start in range(0, len(data), 16): + ln_end =3D min(ln_start + 16, len(data)) + print(f" {ln_start:08x} ", end=3D"") + for i in range(ln_start, ln_end): + print(f"{data[i]:02x} ", end=3D"") + for i in range(ln_end, ln_start + 16): + print(" ", end=3D"") + print(" ", end=3D"") + for i in range(ln_start, ln_end): + if data[i] >=3D 32 and data[i] < 127: + print(chr(data[i]), end=3D"") + else: + print(".", end=3D"") + + print() + print() + +class guid: + """ + Simple class to handle GUID fields. + """ + + def __init__(self, time_low, time_mid, time_high, nodes): + """Initialize a GUID value""" + + assert len(nodes) =3D=3D 8 + + self.time_low =3D time_low + self.time_mid =3D time_mid + self.time_high =3D time_high + self.nodes =3D nodes + + def __str__(self): + """Output a GUID value on its default string representation""" + + clock =3D self.nodes[0] << 8 | self.nodes[1] + + node =3D 0 + for i in range(2, len(self.nodes)): + node =3D node << 8 | self.nodes[i] + + s =3D f"{self.time_low:08x}-{self.time_mid:04x}-" + s +=3D f"{self.time_high:04x}-{clock:04x}-{node:012x}" + return s + + def to_bytes(self): + """Output a GUID value in bytes""" + + data =3D bytearray() + + util.data_add(data, self.time_low, 4) + util.data_add(data, self.time_mid, 2) + util.data_add(data, self.time_high, 2) + data.extend(bytearray(self.nodes)) + + return data + +class qmp: + """ + Opens a connection and send/receive QMP commands. + """ + + def send_cmd(self, command, args=3DNone, may_open=3DFalse, return_erro= r=3DTrue): + """Send a command to QMP, optinally opening a connection""" + + if may_open: + self._connect() + elif not self.connected: + return False + + msg =3D { 'execute': command } + if args: + msg['arguments'] =3D args + + try: + obj =3D self.qmp_monitor.cmd_obj(msg) + # Can we use some other exception class here? + except Exception as e: # pylint: disable= =3DW0718 + print(f"Command: {command}") + print(f"Failed to inject error: {e}.") + return None + + if "return" in obj: + if isinstance(obj.get("return"), dict): + if obj["return"]: + return obj["return"] + return "OK" + + return obj["return"] + + if isinstance(obj.get("error"), dict): + error =3D obj["error"] + if return_error: + print(f"Command: {msg}") + print(f'{error["class"]}: {error["desc"]}') + else: + print(json.dumps(obj)) + + return None + + def _close(self): + """Shutdown and close the socket, if opened""" + if not self.connected: + return + + self.qmp_monitor.close() + self.connected =3D False + + def _connect(self): + """Connect to a QMP TCP/IP port, if not connected yet""" + + if self.connected: + return True + + try: + self.qmp_monitor.connect(negotiate=3DTrue) + except ConnectionError: + sys.exit(f"Can't connect to QMP host {self.host}:{self.port}") + + self.connected =3D True + + return True + + def __init__(self, host, port, debug=3DFalse): + """Initialize variables used by the QMP send logic""" + + self.connected =3D False + self.host =3D host + self.port =3D port + self.debug =3D debug + + # ACPI 6.1: 18.3.2.7.1 Generic Error Data: Generic Error Status Bl= ock + self.block_status =3D util.bit(0) # Uncorrectable + self.raw_data_offset =3D 0 + self.raw_data_length =3D 0 + self.error_severity =3D 0 # Recoverable + + # ACPI 6.1: 18.3.2.7.1 Generic Error Data: Generic Error Data Entry + self.validation_bits =3D 0 + self.flags =3D 0 + self.fru_id =3D bytearray(16) + self.fru_text =3D bytearray(20) + self.timestamp =3D 0 + + self.qmp_monitor =3D QEMUMonitorProtocol(address=3D(self.host, sel= f.port)) + + # + # Socket QMP send command + # + def send_cper_raw(self, cper_data): + """Send a raw CPER data to QEMU though QMP TCP socket""" + + data =3D b64encode(bytes(cper_data)).decode('ascii') + + cmd_arg =3D { + 'cper': data + } + + self._connect() + + if self.send_cmd("ghes-cper", cmd_arg): + print("Error injected.") + + def send_cper(self, notif_type, payload): + """Send commands to QEMU though QMP TCP socket""" + + # Fill CPER record header + + # Generic Error Status Block + gebs =3D bytearray() + + cper_length =3D len(payload) + data_length =3D cper_length + 72 # Size of Generic Error Data + + util.data_add(gebs, self.block_status, 4) + util.data_add(gebs, self.raw_data_offset, 4) + util.data_add(gebs, self.raw_data_length, 4) + util.data_add(gebs, data_length, 4) + util.data_add(gebs, self.error_severity, 4) + + + # Generic Error Data Entry + gede =3D bytearray() + + gede.extend(notif_type.to_bytes()) + util.data_add(gede, self.error_severity, 4) + util.data_add(gede, 0x300, 2) + util.data_add(gede, self.validation_bits, 1) + util.data_add(gede, self.flags, 1) + util.data_add(gede, cper_length, 4) + gede.extend(self.fru_id) + gede.extend(self.fru_text) + util.data_add(gede, self.timestamp, 8) + + cper_data =3D bytearray() + cper_data.extend(gebs) + cper_data.extend(gede) + cper_data.extend(bytearray(payload)) + + if self.debug: + print(f"GUID: {notif_type}") + + util.dump_bytearray("Generic Error Status Block", gebs) + util.dump_bytearray("Generic Error Data Entry", gede) + util.dump_bytearray("Payload", payload) + + self.send_cper_raw(cper_data) + + + def search_qom(self, path, prop, regex): + """ + Return a list of devices that match path array like: + + /machine/unattached/device + /machine/peripheral-anon/device + ... + """ + + found =3D [] + + i =3D 0 + while 1: + dev =3D f"{path}[{i}]" + args =3D { + 'path': dev, + 'property': prop + } + ret =3D self.send_cmd("qom-get", args, may_open=3DTrue, return= _error=3DFalse) + if not ret: + break + + if isinstance(ret, str): + if regex.search(ret): + found.append(dev) + + i +=3D 1 + if i > 10000: + print("Too many objects returned by qom-get!") + break + + return found + +class cper_guid: + """ + Contains CPER GUID, as per: + https://uefi.org/specs/UEFI/2.10/Apx_N_Common_Platform_Error_Record.ht= ml + """ + + CPER_PROC_GENERIC =3D guid(0x9876CCAD, 0x47B4, 0x4bdb, + [0xB6, 0x5E, 0x16, 0xF1, + 0x93, 0xC4, 0xF3, 0xDB]) + + CPER_PROC_X86 =3D guid(0xDC3EA0B0, 0xA144, 0x4797, + [0xB9, 0x5B, 0x53, 0xFA, + 0x24, 0x2B, 0x6E, 0x1D]) + + CPER_PROC_ITANIUM =3D guid(0xe429faf1, 0x3cb7, 0x11d4, + [0xbc, 0xa7, 0x00, 0x80, + 0xc7, 0x3c, 0x88, 0x81]) + + CPER_PROC_ARM =3D guid(0xE19E3D16, 0xBC11, 0x11E4, + [0x9C, 0xAA, 0xC2, 0x05, + 0x1D, 0x5D, 0x46, 0xB0]) + + CPER_PLATFORM_MEM =3D guid(0xA5BC1114, 0x6F64, 0x4EDE, + [0xB8, 0x63, 0x3E, 0x83, + 0xED, 0x7C, 0x83, 0xB1]) + + CPER_PLATFORM_MEM2 =3D guid(0x61EC04FC, 0x48E6, 0xD813, + [0x25, 0xC9, 0x8D, 0xAA, + 0x44, 0x75, 0x0B, 0x12]) + + CPER_PCIE =3D guid(0xD995E954, 0xBBC1, 0x430F, + [0xAD, 0x91, 0xB4, 0x4D, + 0xCB, 0x3C, 0x6F, 0x35]) + + CPER_PCI_BUS =3D guid(0xC5753963, 0x3B84, 0x4095, + [0xBF, 0x78, 0xED, 0xDA, + 0xD3, 0xF9, 0xC9, 0xDD]) + + CPER_PCI_DEV =3D guid(0xEB5E4685, 0xCA66, 0x4769, + [0xB6, 0xA2, 0x26, 0x06, + 0x8B, 0x00, 0x13, 0x26]) + + CPER_FW_ERROR =3D guid(0x81212A96, 0x09ED, 0x4996, + [0x94, 0x71, 0x8D, 0x72, + 0x9C, 0x8E, 0x69, 0xED]) + + CPER_DMA_GENERIC =3D guid(0x5B51FEF7, 0xC79D, 0x4434, + [0x8F, 0x1B, 0xAA, 0x62, + 0xDE, 0x3E, 0x2C, 0x64]) + + CPER_DMA_VT =3D guid(0x71761D37, 0x32B2, 0x45cd, + [0xA7, 0xD0, 0xB0, 0xFE, + 0xDD, 0x93, 0xE8, 0xCF]) + + CPER_DMA_IOMMU =3D guid(0x036F84E1, 0x7F37, 0x428c, + [0xA7, 0x9E, 0x57, 0x5F, + 0xDF, 0xAA, 0x84, 0xEC]) + + CPER_CCIX_PER =3D guid(0x91335EF6, 0xEBFB, 0x4478, + [0xA6, 0xA6, 0x88, 0xB7, + 0x28, 0xCF, 0x75, 0xD7]) + + CPER_CXL_PROT_ERR =3D guid(0x80B9EFB4, 0x52B5, 0x4DE3, + [0xA7, 0x77, 0x68, 0x78, + 0x4B, 0x77, 0x10, 0x48]) --=20 2.46.0