From nobody Sun Nov 24 16:08:45 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 55D8B1A7F70 for ; Thu, 1 Aug 2024 14:47:18 +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=1722523638; cv=none; b=ndSaUlImbNyLqlzMe+bi1+N3ZgJYandlbXBQNZ9dmWnHqJ5KKHAY4H2wSV0d1Toggn6VgvnOVoUIC8PEL0KKqPW4pb/zoq6aK8vE3F9cDxSL8C3gYUpOM5Da16Qbw2u+vSrRbhHTv5sFNaxPGKoGZr9jJdOjGkEnHhdHfsSMtN8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1722523638; c=relaxed/simple; bh=J469TvAJbu9OT0Cil07DbO38OnPS8L+awPtnfxvDPSo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=p4l7oNMas2JtBFRwbrC9RFWE7AXmlP4eew0U2xB4f4ao0Qv/Cc64pHyHL0olkPaGj9xj+Oo0KEtOm6eoVZQK+ioJcv0genBZobamORabzEjTVL4VGGnd1VWanVfMnuxn8L/gAcrLkk95KnH9g8M22OuxczmjcEXcLRv2KRaX3S0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=gN6Q4ysI; 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="gN6Q4ysI" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 940CFC32786; Thu, 1 Aug 2024 14:47:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1722523638; bh=J469TvAJbu9OT0Cil07DbO38OnPS8L+awPtnfxvDPSo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gN6Q4ysIMw+v+8CFFdoQg6KeqyzHSJ4rxFa71boQlJ2lqILHjkJyurjhvFp2aSVHd R8JGOvg9MVDKWmc+kh20tWdtBMMU8niLLCRnj0XG0hvL/s1iYwfd5T6H+5rIZoUTR4 00n0RoGS/c/7/ZJgJdBWi9K+D6CqCl9CzhfVoFu2YJ2LQNMwQTNHwK1+J7nP0Ku+27 dn+sI1/OSncOpOfF5+SiKA5r2IoQMCvhdlv1jTvxrJz5sPE/73rQ4VHZmbs8VO+7Hm ccUD4JHRAoG/XyCK/HnzPsfAzJw6p/UIXzXkCedpLCKbypiFFPFNgXQUQK7otaFomL tesLlDOfMzHaA== Received: from mchehab by mail.kernel.org with local (Exim 4.98) (envelope-from ) id 1sZX5H-00000001WCr-3Tj6; Thu, 01 Aug 2024 16:47:15 +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 v4 7/7] scripts/ghes_inject: add a script to generate GHES error inject Date: Thu, 1 Aug 2024 16:47:10 +0200 Message-ID: <0a9047370233f9e5b3363217d64ff2d93015466d.1722523312.git.mchehab+huawei@kernel.org> X-Mailer: git-send-email 2.45.2 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 | 1 + scripts/ghes_inject.py | 673 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 674 insertions(+) create mode 100755 scripts/ghes_inject.py diff --git a/MAINTAINERS b/MAINTAINERS index 655edcb6688c..9e4874bb552d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2081,6 +2081,7 @@ S: Maintained F: hw/arm/ghes_cper.c F: hw/acpi/ghes_cper_stub.c F: qapi/ghes-cper.json +F: scripts/ghes_inject.py =20 ppc4xx L: qemu-ppc@nongnu.org diff --git a/scripts/ghes_inject.py b/scripts/ghes_inject.py new file mode 100755 index 000000000000..99ee93bc2f34 --- /dev/null +++ b/scripts/ghes_inject.py @@ -0,0 +1,673 @@ +#!/usr/bin/env python3 +# +# pylint: disable=3DC0301, C0114, R0912, R0913, 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: +# +# - machine emulation architecture, as ARM current default is +# for AArch64; +# - ARM registers: power_state, midr, mpidr. + +import argparse +import json +import socket +import sys + +EINJ_DESCRIPTION =3D """ +Handle ACPI GHESv2 error injection logic QEMU QMP interface.\n + +It allows using UEFI BIOS EINJ features to generate GHES records. + +It helps testing Linux CPER and GHES drivers and to test rasdaemon +error handling logic. + +Currently, it support ARM processor error injection for ARM processor +events, being compatible with UEFI 2.9A Errata. + +This small utility works together with those QEMU additions: +- https://gitlab.com/mchehab_kernel/qemu/-/tree/arm-error-inject-v2 +""" + + +# +# Socket QMP send command +# +def qmp_command(host, port, commands): + """Send commands to QEMU though QMP TCP socket""" + + # Needed to negotiate QMP and for QEMU to accept the command + commands.insert(0, '{ "execute": "qmp_capabilities" } ') + + s =3D socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, port)) + + data =3D s.recv(1024) + + print("\t", data.decode("utf-8"), end=3D"") + + for command in commands: + print(command) + + s.sendall(command.encode("utf-8")) + data =3D s.recv(1024) + print("\t", data.decode("utf-8"), end=3D"") + + s.shutdown(socket.SHUT_WR) + while 1: + data =3D s.recv(1024) + if data =3D=3D b"": + break + print("\t", data.decode("utf-8")) + + s.close() + + +# +# 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 [] + + 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(): + sys.exit(f"Error on '{name}': choice {val} is invalid.") + + val =3D choices[val] + + new_values.append(val) + + return new_values + + +def get_mult_array(mult, name, values, allow_zero=3DFalse, max_val=3DNone): + """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 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. + If the value is a list, assume it to be a bitmap, where each + bit is on a different element inside a list. Such list is + converted to a single bitmap integer. + """ + + if isinstance(value, list): + bits =3D 0 + for b in value: + bits |=3D b + + value =3D bits + + data.extend(value.to_bytes(num_bytes, byteorder=3D"little")) + + +def to_uuid(time_low, time_mid, time_high, nodes): + """Create an integer array with elements from an UUID""" + + values =3D bytearray() + + data_add(values, time_low, 4) + data_add(values, time_mid, 2) + data_add(values, time_high, 2) + + for i in nodes: + data_add(values, i, 1) + + return list(values) + + +# +# Arm processor EINJ logic +# +ACPI_GHES_ARM_CPER_LENGTH =3D 40 +ACPI_GHES_ARM_CPER_PEI_LENGTH =3D 32 + +# TODO: query it from emulation. Current default valid only for Aarch64 +CONTEXT_AARCH64_EL1 =3D 5 + +CPER_ARM_PROCESSOR_ERROR =3D to_uuid(0xE19E3D16, 0xBC11, 0x11E4, + [0x9C, 0xAA, 0xC2, 0x05, + 0x1D, 0x5D, 0x46, 0xB0]) + + +class ArmProcessorEinj: + """ + Implements ARM Processor Error injection via GHES + """ + + def __init__(self, args=3DNone): + """ + Initialize the error injection class. There are two possible + ways to initialize it: + 1. passing a set of arguments; + 2. passing a dict with error inject command parameters. Each + column is handled in separate. + """ + + # Valid choice values + self.arm_valid_bits =3D { + "mpidr": bit(0), + "affinity": bit(1), + "running": bit(2), + "vendor": bit(3), + } + + self.pei_flags =3D { + "first": bit(0), + "last": bit(1), + "propagated": bit(2), + "overflow": bit(3), + } + + self.pei_error_types =3D { + "cache": bit(1), + "tlb": bit(2), + "bus": bit(3), + + "micro-arch": bit(4), + "vendor": bit(4), + } + + self.pei_valid_bits =3D { + "multiple-error": bit(0), + "flags": bit(1), + + "error": bit(2), + "error-info": bit(2), + + "virt": bit(3), + "virtual": bit(3), + + "phy": bit(4), + "physical": bit(4), + } + + self.arm =3D {} + + if not args: + self.args =3D args + return + + pei =3D {} + ctx =3D {} + vendor =3D {} + + # Handle global parameters + if args.arm: + arm_validation_init =3D False + self.arm["validation"] =3D get_choice(name=3D"validation", + value=3Dargs.arm, + choices=3Dself.arm_valid_b= its, + suffixes=3D["-error", "-er= r"]) + else: + self.arm["validation"] =3D [] + arm_validation_init =3D True + + if args.affinity: + self.arm["affinity-level"] =3D args.affinity + if arm_validation_init: + self.arm["validation"].append(self.arm_valid_bits["affinit= y"]) + else: + self.arm["affinity-level"] =3D 0 + + if args.mpidr: + self.arm["mpidr-el1"] =3D args.mpidr + if arm_validation_init: + self.arm["validation"].append(self.arm_valid_bits["mpidr"]) + else: + # TODO: query it from emulation + self.arm["mpidr-el1"] =3D 0 + + if args.midr: + self.arm["midr-el1"] =3D args.midr + else: + # TODO: query it from emulation + self.arm["midr-el1"] =3D 0 + + if args.running is not None: + if args.running: + self.arm["running-state"] =3D bit(0) + else: + self.arm["running-state"] =3D 0 + if arm_validation_init: + self.arm["validation"].append(self.arm_valid_bits["running= "]) + else: + # TODO: query it from emulation + self.arm["running-state"] =3D 0 + + if args.psci: + self.arm["psci-state"] =3D args.psci + if arm_validation_init: + self.arm["validation"].append(self.arm_valid_bits["running= "]) + else: + # TODO: query it from emulation + self.arm["psci-state"] =3D 0 + + # Handle PEI + if not args.type: + args.type =3D ["cache-error"] + + get_mult_choices( + pei, + name=3D"validation", + values=3Dargs.pei_valid, + choices=3Dself.pei_valid_bits, + suffixes=3D["-valid", "-info", "--information", "--addr"], + ) + get_mult_choices( + pei, + name=3D"type", + values=3Dargs.type, + choices=3Dself.pei_error_types, + suffixes=3D["-error", "-err"], + ) + get_mult_choices( + pei, + name=3D"flags", + values=3Dargs.flags, + choices=3Dself.pei_flags, + suffixes=3D["-error", "-cap"], + ) + get_mult_int(pei, "error-info", args.error_info) + get_mult_int(pei, "multiple-error", args.multiple_error) + get_mult_int(pei, "phy-addr", args.physical_address) + get_mult_int(pei, "virt-addr", args.virtual_address) + + for i, p in pei.items(): # pylint: disable=3DW0612 + # 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 len(p["type"]) =3D=3D 1: + if p["type"][0] =3D=3D bit(1): + p["error-info"] =3D 0x0091000F + if p["type"][0] =3D=3D bit(2): + p["error-info"] =3D 0x0054007F + if p["type"][0] =3D=3D bit(3): + p["error-info"] =3D 0x80D6460FFF + if p["type"][0] =3D=3D bit(4): + p["error-info"] =3D 0x78DA03FF + + if "validation" not in p: + p["validation"] =3D [] + if "multiple-error" in p: + p["validation"].append(self.pei_valid_bits["multiple-e= rror"]) + + if "flags" in p: + p["validation"].append(self.pei_valid_bits["flags"]) + + if "error-info" in p: + p["validation"].append(self.pei_valid_bits["error-info= "]) + + if "phy-addr" in p: + p["validation"].append(self.pei_valid_bits["phy-addr"]) + + if "virt-addr" in p: + p["validation"].append(self.pei_valid_bits["virt-addr"= ]) + + # Handle context + get_mult_int(ctx, "type", args.ctx_type, allow_zero=3DTrue) + get_mult_int(ctx, "minimal-size", args.ctx_size, allow_zero=3DTrue) + get_mult_array(ctx, "register", args.ctx_array, allow_zero=3DTrue) + + get_mult_array(vendor, "bytes", args.vendor, max_val=3D255) + + # Store PEI + self.arm["error"] =3D [] + for k in sorted(pei.keys()): + self.arm["error"].append(pei[k]) + + # Store Context + self.arm["context"] =3D [] + if ctx: + for k in sorted(ctx.keys()): + self.arm["context"].append(ctx[k]) + + # Vendor-specific bytes are not grouped + self.arm["vendor-specific"] =3D [] + if vendor: + for k in sorted(vendor.keys()): + self.arm["vendor-specific"] +=3D vendor[k]["bytes"] + + def encode_pei(self): + """Encode bytes at the PEI table""" + + data =3D bytearray() + + for pei in self.arm["error"]: + # Version + data_add(data, 0, 1) + + data_add(data, ACPI_GHES_ARM_CPER_PEI_LENGTH, 1) + + data_add(data, pei["validation"], 2) + data_add(data, pei["type"], 1) + data_add(data, pei.get("multiple-error", 1), 2) + data_add(data, pei.get("flags", 0), 1) + data_add(data, pei.get("error_info", 0), 8) + data_add(data, pei.get("virt_addr", 0xDEADBEEF), 8) + data_add(data, pei.get("phy_addr", 0xABBA0BAD), 8) + + return data + + def encode_vendor(self): + """Encode bytes at the vendor data""" + + data =3D bytearray() + + for i in self.arm.get("vendor-specific", []): + data_add(data, i, 1) + + return data + + def encode_context(self): + """Encode bytes for the register context""" + + data =3D bytearray() + + if "context" not in self.arm: + return data + + for ctx in self.arm["context"]: + if "type" not in ctx: + ctx["type"] =3D CONTEXT_AARCH64_EL1 + + reg_size =3D len(ctx["register"]) + size =3D 0 + + if "minimal-size" in ctx: + size =3D ctx["minimal-size"] + + size =3D max(size, reg_size) + + size =3D (size + 1) % 0xFFFE + + # Version + data_add(data, 0, 2) + + data_add(data, ctx["type"], 2) + + data_add(data, 8 * size, 4) + + for r in ctx["register"]: + data_add(data, r, 8) + + for i in range(reg_size, size): # pylint: disable=3DW0612 + data_add(data, 0, 8) + + return data + + def encode(self): + """Encode bytes for the ARM processor Error record""" + + pei_data =3D self.encode_pei() + vendor_data =3D self.encode_vendor() + context_data =3D self.encode_context() + + data =3D bytearray() + + data_add(data, self.arm["validation"], 4) + + error_info_num =3D len(self.arm["error"]) + data_add(data, error_info_num, 2) + + context_info_num =3D len(self.arm["context"]) + data_add(data, context_info_num, 2) + + # Calculate the length of the CPER data + cper_length =3D ACPI_GHES_ARM_CPER_LENGTH + cper_length +=3D len(pei_data) + cper_length +=3D len(vendor_data) + cper_length +=3D len(context_data) + data_add(data, cper_length, 4) + + data_add(data, self.arm["affinity-level"], 1) + + # Reserved + data_add(data, 0, 3) + + data_add(data, self.arm["mpidr-el1"], 8) + data_add(data, self.arm["midr-el1"], 8) + data_add(data, self.arm["running-state"], 4) + data_add(data, self.arm["psci-state"], 4) + + # Add PEI + data.extend(pei_data) + data.extend(context_data) + data.extend(vendor_data) + + return list(data) + + def run(self, host, port): + """Execute the QMP commands""" + + # Fill the commands to be sent + commands =3D [] + + cmd_arg =3D { + 'cper': { + 'notification-type': CPER_ARM_PROCESSOR_ERROR, + "raw-data": "" + } + } + + cmd_arg["cper"]["raw-data"] =3D self.encode() + + command =3D '{ "execute": "ghes-cper", ' + command +=3D '"arguments": ' + json.dumps(cmd_arg) + " }" + + commands.append(command) + + qmp_command(host, port, commands) + + +# +# Argument parser for ARM Processor CPER +# + + +def arm_handle_args(subparsers): + """Add a subparser for ARM-specific arguments""" + + parser =3D subparsers.add_parser("arm", help=3D"Generate an ARM proces= sor CPER") + + # UEFI N.16 ARM Validation bits + g_arm =3D parser.add_argument_group("ARM processor") + g_arm.add_argument("--arm", "--arm-valid", + help=3D"ARM validation bits: mpidr,affinity,running= ,vendor") + g_arm.add_argument("-a", "--affinity", "--level", "--affinity-level", + type=3Dlambda x: int(x, 0), + help=3D"Affinity level (when multiple levels apply)= ") + 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=3D= None, + help=3D"Indicates if the processor is running or no= t") + g_arm.add_argument("--psci", "--psci-state", type=3Dlambda x: int(x, 0= ), + help=3D"Power State Coordination Interface - PSCI s= tate") + + # 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=3D"one or more error types: cache,tlb,bus,vend= or") + g_pei.add_argument("-f", "--flags", nargs=3D"*", + help=3D"zero or more error flags: first-error,last-= error,propagated,overflow") + g_arm.add_argument("-V", "--pei-valid", "--error-valid", nargs=3D"*", + help=3D"zero or more PEI validation bits: multiple-= error,flags,error-info,virt,phy") + + # UEFI N.17 Integer values + g_pei.add_argument("-m", "--multiple-error", nargs=3D"+", + help=3D"Number of errors: 0: Single error, 1: Multi= ple 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 to= 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_pei.add_argument("--ctx-type", "--context-type", nargs=3D"*", + help=3D"Type of the context (0=3DARM32 GPR, 5=3DARM= 64 EL1, other values supported)") + g_pei.add_argument("--ctx-size", "--context-size", nargs=3D"*", + help=3D"Minimal size of the context") + g_pei.add_argument("--ctx-array", "--context-array", nargs=3D"*", + help=3D"Comma-separated arrays for each context") + + # Vendor-specific data + g_pei.add_argument("--vendor", "--vendor-specific", nargs=3D"+", + help=3D"Vendor-specific byte arrays of data") + + +# +# Main Program. Each error injection logic is handled by a separate subpar= ser +# + + +def main(): + """Main program""" + + # Main parser - handle generic args like QEMU QMP TCP socket options + parser =3D argparse.ArgumentParser(prog=3D"einj.py", + formatter_class=3Dargparse.RawDescrip= tionHelpFormatter, + usage=3D"%(prog)s [options]", + description=3DEINJ_DESCRIPTION, + epilog=3D"If a field is not defined, = a default value will be applied by QEMU.") + + 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") + + # Call subparsers + subparsers =3D parser.add_subparsers() + arm_handle_args(subparsers) + + args =3D parser.parse_args() + + # Handle subparser commands + if "arm" in args: + einj =3D ArmProcessorEinj(args) + einj.run(args.host, args.port) + else: + sys.exit("Error: type of error injection missing.") + + +if __name__ =3D=3D "__main__": + main() --=20 2.45.2