Browse Source
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, by
using:
$ ghes_inject.py arm
which produces those warnings on Linux:
[ 705.032426] [Firmware Warn]: GHES: Unhandled processor error type 0x02: cache error
[ 774.866308] {4}[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 1
[ 774.866583] {4}[Hardware Error]: event severity: recoverable
[ 774.866738] {4}[Hardware Error]: Error 0, type: recoverable
[ 774.866889] {4}[Hardware Error]: section_type: ARM processor error
[ 774.867048] {4}[Hardware Error]: MIDR: 0x00000000000f0510
[ 774.867189] {4}[Hardware Error]: running state: 0x0
[ 774.867321] {4}[Hardware Error]: Power State Coordination Interface state: 0
[ 774.867511] {4}[Hardware Error]: Error info structure 0:
[ 774.867679] {4}[Hardware Error]: num errors: 2
[ 774.867801] {4}[Hardware Error]: error_type: 0x02: cache error
[ 774.867962] {4}[Hardware Error]: error_info: 0x000000000091000f
[ 774.868124] {4}[Hardware Error]: transaction type: Data Access
[ 774.868280] {4}[Hardware Error]: cache error, operation type: Data write
[ 774.868465] {4}[Hardware Error]: cache level: 2
[ 774.868592] {4}[Hardware Error]: processor context not corrupted
[ 774.868774] [Firmware Warn]: GHES: Unhandled processor error type 0x02: cache error
Such script allows customizing the error data, allowing to change
all fields at the record. Please use:
$ ghes_inject.py arm -h
For more details about its usage.
Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Message-ID: <5ea174638e33d23635332fa6d4ae9d751355f127.1758610789.git.mchehab+huawei@kernel.org>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
pull/305/head
committed by
Michael S. Tsirkin
4 changed files with 1233 additions and 0 deletions
@ -0,0 +1,476 @@ |
|||
#!/usr/bin/env python3 |
|||
# |
|||
# pylint: disable=C0301,C0114,R0903,R0912,R0913,R0914,R0915,W0511 |
|||
# SPDX-License-Identifier: GPL-2.0-or-later |
|||
# |
|||
# Copyright (C) 2024-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org> |
|||
|
|||
# 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. |
|||
|
|||
""" |
|||
Generate an ARM processor error CPER, compatible with |
|||
UEFI 2.9A Errata. |
|||
|
|||
Injecting such errors can be done using: |
|||
|
|||
$ ./scripts/ghes_inject.py arm |
|||
Error injected. |
|||
|
|||
Produces a simple CPER register, as detected on a Linux guest: |
|||
|
|||
[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 1 |
|||
[Hardware Error]: event severity: recoverable |
|||
[Hardware Error]: Error 0, type: recoverable |
|||
[Hardware Error]: section_type: ARM processor error |
|||
[Hardware Error]: MIDR: 0x0000000000000000 |
|||
[Hardware Error]: running state: 0x0 |
|||
[Hardware Error]: Power State Coordination Interface state: 0 |
|||
[Hardware Error]: Error info structure 0: |
|||
[Hardware Error]: num errors: 2 |
|||
[Hardware Error]: error_type: 0x02: cache error |
|||
[Hardware Error]: error_info: 0x000000000091000f |
|||
[Hardware Error]: transaction type: Data Access |
|||
[Hardware Error]: cache error, operation type: Data write |
|||
[Hardware Error]: cache level: 2 |
|||
[Hardware Error]: processor context not corrupted |
|||
[Firmware Warn]: GHES: Unhandled processor error type 0x02: cache error |
|||
|
|||
The ARM Processor Error message can be customized via command line |
|||
parameters. For instance: |
|||
|
|||
$ ./scripts/ghes_inject.py arm --mpidr 0x444 --running --affinity 1 \ |
|||
--error-info 12345678 --vendor 0x13,123,4,5,1 --ctx-array 0,1,2,3,4,5 \ |
|||
-t cache tlb bus micro-arch tlb,micro-arch |
|||
Error injected. |
|||
|
|||
Injects this error, as detected on a Linux guest: |
|||
|
|||
[Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 1 |
|||
[Hardware Error]: event severity: recoverable |
|||
[Hardware Error]: Error 0, type: recoverable |
|||
[Hardware Error]: section_type: ARM processor error |
|||
[Hardware Error]: MIDR: 0x0000000000000000 |
|||
[Hardware Error]: Multiprocessor Affinity Register (MPIDR): 0x0000000000000000 |
|||
[Hardware Error]: error affinity level: 0 |
|||
[Hardware Error]: running state: 0x1 |
|||
[Hardware Error]: Power State Coordination Interface state: 0 |
|||
[Hardware Error]: Error info structure 0: |
|||
[Hardware Error]: num errors: 2 |
|||
[Hardware Error]: error_type: 0x02: cache error |
|||
[Hardware Error]: error_info: 0x0000000000bc614e |
|||
[Hardware Error]: cache level: 2 |
|||
[Hardware Error]: processor context not corrupted |
|||
[Hardware Error]: Error info structure 1: |
|||
[Hardware Error]: num errors: 2 |
|||
[Hardware Error]: error_type: 0x04: TLB error |
|||
[Hardware Error]: error_info: 0x000000000054007f |
|||
[Hardware Error]: transaction type: Instruction |
|||
[Hardware Error]: TLB error, operation type: Instruction fetch |
|||
[Hardware Error]: TLB level: 1 |
|||
[Hardware Error]: processor context not corrupted |
|||
[Hardware Error]: the error has not been corrected |
|||
[Hardware Error]: PC is imprecise |
|||
[Hardware Error]: Error info structure 2: |
|||
[Hardware Error]: num errors: 2 |
|||
[Hardware Error]: error_type: 0x08: bus error |
|||
[Hardware Error]: error_info: 0x00000080d6460fff |
|||
[Hardware Error]: transaction type: Generic |
|||
[Hardware Error]: bus error, operation type: Generic read (type of instruction or data request cannot be determined) |
|||
[Hardware Error]: affinity level at which the bus error occurred: 1 |
|||
[Hardware Error]: processor context corrupted |
|||
[Hardware Error]: the error has been corrected |
|||
[Hardware Error]: PC is imprecise |
|||
[Hardware Error]: Program execution can be restarted reliably at the PC associated with the error. |
|||
[Hardware Error]: participation type: Local processor observed |
|||
[Hardware Error]: request timed out |
|||
[Hardware Error]: address space: External Memory Access |
|||
[Hardware Error]: memory access attributes:0x20 |
|||
[Hardware Error]: access mode: secure |
|||
[Hardware Error]: Error info structure 3: |
|||
[Hardware Error]: num errors: 2 |
|||
[Hardware Error]: error_type: 0x10: micro-architectural error |
|||
[Hardware Error]: error_info: 0x0000000078da03ff |
|||
[Hardware Error]: Error info structure 4: |
|||
[Hardware Error]: num errors: 2 |
|||
[Hardware Error]: error_type: 0x14: TLB error|micro-architectural error |
|||
[Hardware Error]: Context info structure 0: |
|||
[Hardware Error]: register context type: AArch64 EL1 context registers |
|||
[Hardware Error]: 00000000: 00000000 00000000 |
|||
[Hardware Error]: Vendor specific error info has 5 bytes: |
|||
[Hardware Error]: 00000000: 13 7b 04 05 01 .{... |
|||
[Firmware Warn]: GHES: Unhandled processor error type 0x02: cache error |
|||
[Firmware Warn]: GHES: Unhandled processor error type 0x04: TLB error |
|||
[Firmware Warn]: GHES: Unhandled processor error type 0x08: bus error |
|||
[Firmware Warn]: GHES: Unhandled processor error type 0x10: micro-architectural error |
|||
[Firmware Warn]: GHES: Unhandled processor error type 0x14: TLB error|micro-architectural error |
|||
""" |
|||
|
|||
import argparse |
|||
import re |
|||
|
|||
from qmp_helper import qmp, util, cper_guid |
|||
|
|||
|
|||
class ArmProcessorEinj: |
|||
""" |
|||
Implements ARM Processor Error injection via GHES |
|||
""" |
|||
|
|||
DESC = """ |
|||
Generates an ARM processor error CPER, compatible with |
|||
UEFI 2.9A Errata. |
|||
""" |
|||
|
|||
ACPI_GHES_ARM_CPER_LENGTH = 40 |
|||
ACPI_GHES_ARM_CPER_PEI_LENGTH = 32 |
|||
|
|||
# Context types |
|||
CONTEXT_AARCH32_EL1 = 1 |
|||
CONTEXT_AARCH64_EL1 = 5 |
|||
CONTEXT_MISC_REG = 8 |
|||
|
|||
def __init__(self, subparsers): |
|||
"""Initialize the error injection class and add subparser""" |
|||
|
|||
# Valid choice values |
|||
self.arm_valid_bits = { |
|||
"mpidr": util.bit(0), |
|||
"affinity": util.bit(1), |
|||
"running": util.bit(2), |
|||
"vendor": util.bit(3), |
|||
} |
|||
|
|||
self.pei_flags = { |
|||
"first": util.bit(0), |
|||
"last": util.bit(1), |
|||
"propagated": util.bit(2), |
|||
"overflow": util.bit(3), |
|||
} |
|||
|
|||
self.pei_error_types = { |
|||
"cache": util.bit(1), |
|||
"tlb": util.bit(2), |
|||
"bus": util.bit(3), |
|||
"micro-arch": util.bit(4), |
|||
} |
|||
|
|||
self.pei_valid_bits = { |
|||
"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 = bytearray() |
|||
|
|||
parser = subparsers.add_parser("arm", description=self.DESC) |
|||
|
|||
arm_valid_bits = ",".join(self.arm_valid_bits.keys()) |
|||
flags = ",".join(self.pei_flags.keys()) |
|||
error_types = ",".join(self.pei_error_types.keys()) |
|||
pei_valid_bits = ",".join(self.pei_valid_bits.keys()) |
|||
|
|||
# UEFI N.16 ARM Validation bits |
|||
g_arm = parser.add_argument_group("ARM processor") |
|||
g_arm.add_argument("--arm", "--arm-valid", |
|||
help=f"ARM valid bits: {arm_valid_bits}") |
|||
g_arm.add_argument("-a", "--affinity", "--level", "--affinity-level", |
|||
type=lambda x: int(x, 0), |
|||
help="Affinity level (when multiple levels apply)") |
|||
g_arm.add_argument("-l", "--mpidr", type=lambda x: int(x, 0), |
|||
help="Multiprocessor Affinity Register") |
|||
g_arm.add_argument("-i", "--midr", type=lambda x: int(x, 0), |
|||
help="Main ID Register") |
|||
g_arm.add_argument("-r", "--running", |
|||
action=argparse.BooleanOptionalAction, |
|||
default=None, |
|||
help="Indicates if the processor is running or not") |
|||
g_arm.add_argument("--psci", "--psci-state", |
|||
type=lambda x: int(x, 0), |
|||
help="Power State Coordination Interface - PSCI state") |
|||
|
|||
# TODO: Add vendor-specific support |
|||
|
|||
# UEFI N.17 bitmaps (type and flags) |
|||
g_pei = parser.add_argument_group("ARM Processor Error Info (PEI)") |
|||
g_pei.add_argument("-t", "--type", nargs="+", |
|||
help=f"one or more error types: {error_types}") |
|||
g_pei.add_argument("-f", "--flags", nargs="*", |
|||
help=f"zero or more error flags: {flags}") |
|||
g_pei.add_argument("-V", "--pei-valid", "--error-valid", nargs="*", |
|||
help=f"zero or more PEI valid bits: {pei_valid_bits}") |
|||
|
|||
# UEFI N.17 Integer values |
|||
g_pei.add_argument("-m", "--multiple-error", nargs="+", |
|||
help="Number of errors: 0: Single error, 1: Multiple errors, 2-65535: Error count if known") |
|||
g_pei.add_argument("-e", "--error-info", nargs="+", |
|||
help="Error information (UEFI 2.10 tables N.18 to N.20)") |
|||
g_pei.add_argument("-p", "--physical-address", nargs="+", |
|||
help="Physical address") |
|||
g_pei.add_argument("-v", "--virtual-address", nargs="+", |
|||
help="Virtual address") |
|||
|
|||
# UEFI N.21 Context |
|||
g_ctx = parser.add_argument_group("Processor Context") |
|||
g_ctx.add_argument("--ctx-type", "--context-type", nargs="*", |
|||
help="Type of the context (0=ARM32 GPR, 5=ARM64 EL1, other values supported)") |
|||
g_ctx.add_argument("--ctx-size", "--context-size", nargs="*", |
|||
help="Minimal size of the context") |
|||
g_ctx.add_argument("--ctx-array", "--context-array", nargs="*", |
|||
help="Comma-separated arrays for each context") |
|||
|
|||
# Vendor-specific data |
|||
g_vendor = parser.add_argument_group("Vendor-specific data") |
|||
g_vendor.add_argument("--vendor", "--vendor-specific", nargs="+", |
|||
help="Vendor-specific byte arrays of data") |
|||
|
|||
# Add arguments for Generic Error Data |
|||
qmp.argparse(parser) |
|||
|
|||
parser.set_defaults(func=self.send_cper) |
|||
|
|||
def send_cper(self, args): |
|||
"""Parse subcommand arguments and send a CPER via QMP""" |
|||
|
|||
qmp_cmd = qmp(args.host, args.port, args.debug) |
|||
|
|||
# Handle Generic Error Data arguments if any |
|||
qmp_cmd.set_args(args) |
|||
|
|||
is_cpu_type = re.compile(r"^([\w+]+\-)?arm\-cpu$") |
|||
cpus = qmp_cmd.search_qom("/machine/unattached/device", |
|||
"type", is_cpu_type) |
|||
|
|||
cper = {} |
|||
pei = {} |
|||
ctx = {} |
|||
vendor = {} |
|||
|
|||
arg = vars(args) |
|||
|
|||
# Handle global parameters |
|||
if args.arm: |
|||
arm_valid_init = False |
|||
cper["valid"] = util.get_choice(name="valid", |
|||
value=args.arm, |
|||
choices=self.arm_valid_bits, |
|||
suffixes=["-error", "-err"]) |
|||
else: |
|||
cper["valid"] = 0 |
|||
arm_valid_init = True |
|||
|
|||
if "running" in arg: |
|||
if args.running: |
|||
cper["running-state"] = util.bit(0) |
|||
else: |
|||
cper["running-state"] = 0 |
|||
else: |
|||
cper["running-state"] = 0 |
|||
|
|||
if arm_valid_init: |
|||
if args.affinity: |
|||
cper["valid"] |= self.arm_valid_bits["affinity"] |
|||
|
|||
if args.mpidr: |
|||
cper["valid"] |= self.arm_valid_bits["mpidr"] |
|||
|
|||
if "running-state" in cper: |
|||
cper["valid"] |= self.arm_valid_bits["running"] |
|||
|
|||
if args.psci: |
|||
cper["valid"] |= self.arm_valid_bits["running"] |
|||
|
|||
# Handle PEI |
|||
if not args.type: |
|||
args.type = ["cache-error"] |
|||
|
|||
util.get_mult_choices( |
|||
pei, |
|||
name="valid", |
|||
values=args.pei_valid, |
|||
choices=self.pei_valid_bits, |
|||
suffixes=["-valid", "--addr"], |
|||
) |
|||
util.get_mult_choices( |
|||
pei, |
|||
name="type", |
|||
values=args.type, |
|||
choices=self.pei_error_types, |
|||
suffixes=["-error", "-err"], |
|||
) |
|||
util.get_mult_choices( |
|||
pei, |
|||
name="flags", |
|||
values=args.flags, |
|||
choices=self.pei_flags, |
|||
suffixes=["-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=True) |
|||
util.get_mult_int(ctx, "minimal-size", args.ctx_size, allow_zero=True) |
|||
util.get_mult_array(ctx, "register", args.ctx_array, allow_zero=True) |
|||
|
|||
util.get_mult_array(vendor, "bytes", args.vendor, max_val=255) |
|||
|
|||
# Store PEI |
|||
pei_data = bytearray() |
|||
default_flags = self.pei_flags["first"] |
|||
default_flags |= self.pei_flags["last"] |
|||
|
|||
error_info_num = 0 |
|||
|
|||
for i, p in pei.items(): # pylint: disable=W0612 |
|||
error_info_num += 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"] == util.bit(1): |
|||
p["error-info"] = 0x0091000F |
|||
if p["type"] == util.bit(2): |
|||
p["error-info"] = 0x0054007F |
|||
if p["type"] == util.bit(3): |
|||
p["error-info"] = 0x80D6460FFF |
|||
if p["type"] == util.bit(4): |
|||
p["error-info"] = 0x78DA03FF |
|||
|
|||
if "valid" not in p: |
|||
p["valid"] = 0 |
|||
if "multiple-error" in p: |
|||
p["valid"] |= self.pei_valid_bits["multiple-error"] |
|||
|
|||
if "flags" in p: |
|||
p["valid"] |= self.pei_valid_bits["flags"] |
|||
|
|||
if "error-info" in p: |
|||
p["valid"] |= self.pei_valid_bits["error-info"] |
|||
|
|||
if "phy-addr" in p: |
|||
p["valid"] |= self.pei_valid_bits["phy-addr"] |
|||
|
|||
if "virt-addr" in p: |
|||
p["valid"] |= 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 = bytearray() |
|||
context_info_num = 0 |
|||
|
|||
if ctx: |
|||
ret = qmp_cmd.send_cmd("query-target", may_open=True) |
|||
|
|||
default_ctx = self.CONTEXT_MISC_REG |
|||
|
|||
if "arch" in ret: |
|||
if ret["arch"] == "aarch64": |
|||
default_ctx = self.CONTEXT_AARCH64_EL1 |
|||
elif ret["arch"] == "arm": |
|||
default_ctx = self.CONTEXT_AARCH32_EL1 |
|||
|
|||
for k in sorted(ctx.keys()): |
|||
context_info_num += 1 |
|||
|
|||
if "type" not in ctx[k]: |
|||
ctx[k]["type"] = default_ctx |
|||
|
|||
if "register" not in ctx[k]: |
|||
ctx[k]["register"] = [] |
|||
|
|||
reg_size = len(ctx[k]["register"]) |
|||
size = 0 |
|||
|
|||
if "minimal-size" in ctx: |
|||
size = ctx[k]["minimal-size"] |
|||
|
|||
size = max(size, reg_size) |
|||
|
|||
size = (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=W0612 |
|||
util.data_add(ctx_data, 0, 8) |
|||
|
|||
# Vendor-specific bytes are not grouped |
|||
vendor_data = 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 = 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 = self.ACPI_GHES_ARM_CPER_LENGTH |
|||
cper_length += len(pei_data) |
|||
cper_length += len(vendor_data) |
|||
cper_length += 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 = { |
|||
'path': cpus[0], |
|||
'property': "midr" |
|||
} |
|||
ret = qmp_cmd.send_cmd("qom-get", cmd_arg, may_open=True) |
|||
if isinstance(ret, int): |
|||
arg["midr-el1"] = 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 = data |
|||
|
|||
qmp_cmd.send_cper(cper_guid.CPER_PROC_ARM, self.data) |
|||
@ -0,0 +1,51 @@ |
|||
#!/usr/bin/env python3 |
|||
# |
|||
# SPDX-License-Identifier: GPL-2.0-or-later |
|||
# |
|||
# Copyright (C) 2024-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org> |
|||
|
|||
""" |
|||
Handle ACPI GHESv2 error injection logic QEMU QMP interface. |
|||
""" |
|||
|
|||
import argparse |
|||
import sys |
|||
|
|||
from arm_processor_error import ArmProcessorEinj |
|||
|
|||
EINJ_DESC = """ |
|||
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 = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, |
|||
usage="%(prog)s [options]", |
|||
description=EINJ_DESC) |
|||
|
|||
g_options = parser.add_argument_group("QEMU QMP socket options") |
|||
g_options.add_argument("-H", "--host", default="localhost", type=str, |
|||
help="host name") |
|||
g_options.add_argument("-P", "--port", default=4445, type=int, |
|||
help="TCP port number") |
|||
g_options.add_argument('-d', '--debug', action='store_true') |
|||
|
|||
subparsers = parser.add_subparsers() |
|||
|
|||
ArmProcessorEinj(subparsers) |
|||
|
|||
args = 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__ == "__main__": |
|||
main() |
|||
@ -0,0 +1,703 @@ |
|||
#!/usr/bin/env python3 |
|||
# |
|||
# pylint: disable=C0103,E0213,E1135,E1136,E1137,R0902,R0903,R0912,R0913,R0917 |
|||
# SPDX-License-Identifier: GPL-2.0-or-later |
|||
# |
|||
# Copyright (C) 2024-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org> |
|||
|
|||
""" |
|||
Helper classes to be used by ghes_inject command classes. |
|||
""" |
|||
|
|||
import json |
|||
import sys |
|||
|
|||
from datetime import datetime |
|||
from os import path as os_path |
|||
|
|||
try: |
|||
qemu_dir = 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=top-qemu-dir/python or run from top-qemu-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=None, bitmask=True): |
|||
"""Produce a list from multiple choice argument""" |
|||
|
|||
new_values = 0 |
|||
|
|||
if not value: |
|||
return new_values |
|||
|
|||
for val in value.split(","): |
|||
val = val.lower() |
|||
|
|||
if suffixes: |
|||
for suffix in suffixes: |
|||
val = val.removesuffix(suffix) |
|||
|
|||
if val not in choices.keys(): |
|||
if suffixes: |
|||
for suffix in suffixes: |
|||
if val + suffix in choices.keys(): |
|||
val += suffix |
|||
break |
|||
|
|||
if val not in choices.keys(): |
|||
sys.exit(f"Error on '{name}': choice '{val}' is invalid.") |
|||
|
|||
val = choices[val] |
|||
|
|||
if bitmask: |
|||
new_values |= val |
|||
else: |
|||
if new_values: |
|||
sys.exit(f"Error on '{name}': only one value is accepted.") |
|||
|
|||
new_values = val |
|||
|
|||
return new_values |
|||
|
|||
def get_array(name, values, max_val=None): |
|||
"""Add numbered hashes from integer lists into an array""" |
|||
|
|||
array = [] |
|||
|
|||
for value in values: |
|||
for val in value.split(","): |
|||
try: |
|||
val = 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") |
|||
|
|||
array.append(val) |
|||
|
|||
return array |
|||
|
|||
def get_mult_array(mult, name, values, allow_zero=False, max_val=None): |
|||
"""Add numbered hashes from integer lists""" |
|||
|
|||
if not allow_zero: |
|||
if not values: |
|||
return |
|||
else: |
|||
if values is None: |
|||
return |
|||
|
|||
if not values: |
|||
i = 0 |
|||
if i not in mult: |
|||
mult[i] = {} |
|||
|
|||
mult[i][name] = [] |
|||
return |
|||
|
|||
i = 0 |
|||
for value in values: |
|||
for val in value.split(","): |
|||
try: |
|||
val = 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] = {} |
|||
|
|||
if name not in mult[i]: |
|||
mult[i][name] = [] |
|||
|
|||
mult[i][name].append(val) |
|||
|
|||
i += 1 |
|||
|
|||
|
|||
def get_mult_choices(mult, name, values, choices, |
|||
suffixes=None, allow_zero=False): |
|||
"""Add numbered hashes from multiple choice arguments""" |
|||
|
|||
if not allow_zero: |
|||
if not values: |
|||
return |
|||
else: |
|||
if values is None: |
|||
return |
|||
|
|||
i = 0 |
|||
for val in values: |
|||
new_values = util.get_choice(name, val, choices, suffixes) |
|||
|
|||
if i not in mult: |
|||
mult[i] = {} |
|||
|
|||
mult[i][name] = new_values |
|||
i += 1 |
|||
|
|||
|
|||
def get_mult_int(mult, name, values, allow_zero=False): |
|||
"""Add numbered hashes from integer arguments""" |
|||
if not allow_zero: |
|||
if not values: |
|||
return |
|||
else: |
|||
if values is None: |
|||
return |
|||
|
|||
i = 0 |
|||
for val in values: |
|||
try: |
|||
val = 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] = {} |
|||
|
|||
mult[i][name] = val |
|||
i += 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="little")) # pylint: disable=E1101 |
|||
|
|||
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 = min(ln_start + 16, len(data)) |
|||
print(f" {ln_start:08x} ", end="") |
|||
for i in range(ln_start, ln_end): |
|||
print(f"{data[i]:02x} ", end="") |
|||
for i in range(ln_end, ln_start + 16): |
|||
print(" ", end="") |
|||
print(" ", end="") |
|||
for i in range(ln_start, ln_end): |
|||
if data[i] >= 32 and data[i] < 127: |
|||
print(chr(data[i]), end="") |
|||
else: |
|||
print(".", end="") |
|||
|
|||
print() |
|||
print() |
|||
|
|||
def time(string): |
|||
"""Handle BCD timestamps used on Generic Error Data Block""" |
|||
|
|||
time = None |
|||
|
|||
# Formats to be used when parsing time stamps |
|||
formats = [ |
|||
"%Y-%m-%d %H:%M:%S", |
|||
] |
|||
|
|||
if string == "now": |
|||
time = datetime.now() |
|||
|
|||
if time is None: |
|||
for fmt in formats: |
|||
try: |
|||
time = datetime.strptime(string, fmt) |
|||
break |
|||
except ValueError: |
|||
pass |
|||
|
|||
if time is None: |
|||
raise ValueError("Invalid time format") |
|||
|
|||
return time |
|||
|
|||
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) == 8 |
|||
|
|||
self.time_low = time_low |
|||
self.time_mid = time_mid |
|||
self.time_high = time_high |
|||
self.nodes = nodes |
|||
|
|||
@classmethod |
|||
def UUID(cls, guid_str): |
|||
"""Initialize a GUID using a string on its standard format""" |
|||
|
|||
if len(guid_str) != 36: |
|||
print("Size not 36") |
|||
raise ValueError('Invalid GUID size') |
|||
|
|||
# It is easier to parse without separators. So, drop them |
|||
guid_str = guid_str.replace('-', '') |
|||
|
|||
if len(guid_str) != 32: |
|||
print("Size not 32", guid_str, len(guid_str)) |
|||
raise ValueError('Invalid GUID hex size') |
|||
|
|||
time_low = 0 |
|||
time_mid = 0 |
|||
time_high = 0 |
|||
nodes = [] |
|||
|
|||
for i in reversed(range(16, 32, 2)): |
|||
h = guid_str[i:i + 2] |
|||
value = int(h, 16) |
|||
nodes.insert(0, value) |
|||
|
|||
time_high = int(guid_str[12:16], 16) |
|||
time_mid = int(guid_str[8:12], 16) |
|||
time_low = int(guid_str[0:8], 16) |
|||
|
|||
return cls(time_low, time_mid, time_high, nodes) |
|||
|
|||
def __str__(self): |
|||
"""Output a GUID value on its default string representation""" |
|||
|
|||
clock = self.nodes[0] << 8 | self.nodes[1] |
|||
|
|||
node = 0 |
|||
for i in range(2, len(self.nodes)): |
|||
node = node << 8 | self.nodes[i] |
|||
|
|||
s = f"{self.time_low:08x}-{self.time_mid:04x}-" |
|||
s += f"{self.time_high:04x}-{clock:04x}-{node:012x}" |
|||
return s |
|||
|
|||
def to_bytes(self): |
|||
"""Output a GUID value in bytes""" |
|||
|
|||
data = 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=None, may_open=False, return_error=True): |
|||
"""Send a command to QMP, optinally opening a connection""" |
|||
|
|||
if may_open: |
|||
self._connect() |
|||
elif not self.connected: |
|||
return False |
|||
|
|||
msg = { 'execute': command } |
|||
if args: |
|||
msg['arguments'] = args |
|||
|
|||
try: |
|||
obj = self.qmp_monitor.cmd_obj(msg) |
|||
# Can we use some other exception class here? |
|||
except Exception as e: # pylint: disable=W0718 |
|||
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 = 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 = 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=True) |
|||
except ConnectionError: |
|||
sys.exit(f"Can't connect to QMP host {self.host}:{self.port}") |
|||
|
|||
self.connected = True |
|||
|
|||
return True |
|||
|
|||
BLOCK_STATUS_BITS = { |
|||
"uncorrectable": util.bit(0), |
|||
"correctable": util.bit(1), |
|||
"multi-uncorrectable": util.bit(2), |
|||
"multi-correctable": util.bit(3), |
|||
} |
|||
|
|||
ERROR_SEVERITY = { |
|||
"recoverable": 0, |
|||
"fatal": 1, |
|||
"corrected": 2, |
|||
"none": 3, |
|||
} |
|||
|
|||
VALIDATION_BITS = { |
|||
"fru-id": util.bit(0), |
|||
"fru-text": util.bit(1), |
|||
"timestamp": util.bit(2), |
|||
} |
|||
|
|||
GEDB_FLAGS_BITS = { |
|||
"recovered": util.bit(0), |
|||
"prev-error": util.bit(1), |
|||
"simulated": util.bit(2), |
|||
} |
|||
|
|||
GENERIC_DATA_SIZE = 72 |
|||
|
|||
def argparse(parser): |
|||
"""Prepare a parser group to query generic error data""" |
|||
|
|||
block_status_bits = ",".join(qmp.BLOCK_STATUS_BITS.keys()) |
|||
error_severity_enum = ",".join(qmp.ERROR_SEVERITY.keys()) |
|||
validation_bits = ",".join(qmp.VALIDATION_BITS.keys()) |
|||
gedb_flags_bits = ",".join(qmp.GEDB_FLAGS_BITS.keys()) |
|||
|
|||
g_gen = parser.add_argument_group("Generic Error Data") # pylint: disable=E1101 |
|||
g_gen.add_argument("--block-status", |
|||
help=f"block status bits: {block_status_bits}") |
|||
g_gen.add_argument("--raw-data", nargs="+", |
|||
help="Raw data inside the Error Status Block") |
|||
g_gen.add_argument("--error-severity", "--severity", |
|||
help=f"error severity: {error_severity_enum}") |
|||
g_gen.add_argument("--gen-err-valid-bits", |
|||
"--generic-error-validation-bits", |
|||
help=f"validation bits: {validation_bits}") |
|||
g_gen.add_argument("--fru-id", type=guid.UUID, |
|||
help="GUID representing a physical device") |
|||
g_gen.add_argument("--fru-text", |
|||
help="ASCII string identifying the FRU hardware") |
|||
g_gen.add_argument("--timestamp", type=util.time, |
|||
help="Time when the error info was collected") |
|||
g_gen.add_argument("--precise", "--precise-timestamp", |
|||
action='store_true', |
|||
help="Marks the timestamp as precise if --timestamp is used") |
|||
g_gen.add_argument("--gedb-flags", |
|||
help=f"General Error Data Block flags: {gedb_flags_bits}") |
|||
|
|||
def set_args(self, args): |
|||
"""Set the arguments optionally defined via self.argparse()""" |
|||
|
|||
if args.block_status: |
|||
self.block_status = util.get_choice(name="block-status", |
|||
value=args.block_status, |
|||
choices=self.BLOCK_STATUS_BITS, |
|||
bitmask=False) |
|||
if args.raw_data: |
|||
self.raw_data = util.get_array("raw-data", args.raw_data, |
|||
max_val=255) |
|||
print(self.raw_data) |
|||
|
|||
if args.error_severity: |
|||
self.error_severity = util.get_choice(name="error-severity", |
|||
value=args.error_severity, |
|||
choices=self.ERROR_SEVERITY, |
|||
bitmask=False) |
|||
|
|||
if args.fru_id: |
|||
self.fru_id = args.fru_id.to_bytes() |
|||
if not args.gen_err_valid_bits: |
|||
self.validation_bits |= self.VALIDATION_BITS["fru-id"] |
|||
|
|||
if args.fru_text: |
|||
text = bytearray(args.fru_text.encode('ascii')) |
|||
if len(text) > 20: |
|||
sys.exit("FRU text is too big to fit") |
|||
|
|||
self.fru_text = text |
|||
if not args.gen_err_valid_bits: |
|||
self.validation_bits |= self.VALIDATION_BITS["fru-text"] |
|||
|
|||
if args.timestamp: |
|||
time = args.timestamp |
|||
century = int(time.year / 100) |
|||
|
|||
bcd = bytearray() |
|||
util.data_add(bcd, (time.second // 10) << 4 | (time.second % 10), 1) |
|||
util.data_add(bcd, (time.minute // 10) << 4 | (time.minute % 10), 1) |
|||
util.data_add(bcd, (time.hour // 10) << 4 | (time.hour % 10), 1) |
|||
|
|||
if args.precise: |
|||
util.data_add(bcd, 1, 1) |
|||
else: |
|||
util.data_add(bcd, 0, 1) |
|||
|
|||
util.data_add(bcd, (time.day // 10) << 4 | (time.day % 10), 1) |
|||
util.data_add(bcd, (time.month // 10) << 4 | (time.month % 10), 1) |
|||
util.data_add(bcd, |
|||
((time.year % 100) // 10) << 4 | (time.year % 10), 1) |
|||
util.data_add(bcd, ((century % 100) // 10) << 4 | (century % 10), 1) |
|||
|
|||
self.timestamp = bcd |
|||
if not args.gen_err_valid_bits: |
|||
self.validation_bits |= self.VALIDATION_BITS["timestamp"] |
|||
|
|||
if args.gen_err_valid_bits: |
|||
self.validation_bits = util.get_choice(name="validation", |
|||
value=args.gen_err_valid_bits, |
|||
choices=self.VALIDATION_BITS) |
|||
|
|||
def __init__(self, host, port, debug=False): |
|||
"""Initialize variables used by the QMP send logic""" |
|||
|
|||
self.connected = False |
|||
self.host = host |
|||
self.port = port |
|||
self.debug = debug |
|||
|
|||
# ACPI 6.1: 18.3.2.7.1 Generic Error Data: Generic Error Status Block |
|||
self.block_status = self.BLOCK_STATUS_BITS["uncorrectable"] |
|||
self.raw_data = [] |
|||
self.error_severity = self.ERROR_SEVERITY["recoverable"] |
|||
|
|||
# ACPI 6.1: 18.3.2.7.1 Generic Error Data: Generic Error Data Entry |
|||
self.validation_bits = 0 |
|||
self.flags = 0 |
|||
self.fru_id = bytearray(16) |
|||
self.fru_text = bytearray(20) |
|||
self.timestamp = bytearray(8) |
|||
|
|||
self.qmp_monitor = QEMUMonitorProtocol(address=(self.host, self.port)) |
|||
|
|||
# |
|||
# Socket QMP send command |
|||
# |
|||
def send_cper_raw(self, cper_data): |
|||
"""Send a raw CPER data to QEMU though QMP TCP socket""" |
|||
|
|||
data = b64encode(bytes(cper_data)).decode('ascii') |
|||
|
|||
cmd_arg = { |
|||
'cper': data |
|||
} |
|||
|
|||
self._connect() |
|||
|
|||
if self.send_cmd("inject-ghes-v2-error", cmd_arg): |
|||
print("Error injected.") |
|||
|
|||
def send_cper(self, notif_type, payload): |
|||
"""Send commands to QEMU though QMP TCP socket""" |
|||
|
|||
# Fill CPER record header |
|||
|
|||
# NOTE: bits 4 to 13 of block status contain the number of |
|||
# data entries in the data section. This is currently unsupported. |
|||
|
|||
cper_length = len(payload) |
|||
data_length = cper_length + len(self.raw_data) + self.GENERIC_DATA_SIZE |
|||
|
|||
# Generic Error Data Entry |
|||
gede = 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) |
|||
gede.extend(self.timestamp) |
|||
|
|||
# Generic Error Status Block |
|||
gebs = bytearray() |
|||
|
|||
if self.raw_data: |
|||
raw_data_offset = len(gebs) |
|||
else: |
|||
raw_data_offset = 0 |
|||
|
|||
util.data_add(gebs, self.block_status, 4) |
|||
util.data_add(gebs, raw_data_offset, 4) |
|||
util.data_add(gebs, len(self.raw_data), 4) |
|||
util.data_add(gebs, data_length, 4) |
|||
util.data_add(gebs, self.error_severity, 4) |
|||
|
|||
cper_data = bytearray() |
|||
cper_data.extend(gebs) |
|||
cper_data.extend(gede) |
|||
cper_data.extend(bytearray(self.raw_data)) |
|||
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) |
|||
|
|||
if self.raw_data: |
|||
util.dump_bytearray("Raw data", bytearray(self.raw_data)) |
|||
|
|||
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 = [] |
|||
|
|||
i = 0 |
|||
while 1: |
|||
dev = f"{path}[{i}]" |
|||
args = { |
|||
'path': dev, |
|||
'property': prop |
|||
} |
|||
ret = self.send_cmd("qom-get", args, may_open=True, |
|||
return_error=False) |
|||
if not ret: |
|||
break |
|||
|
|||
if isinstance(ret, str): |
|||
if regex.search(ret): |
|||
found.append(dev) |
|||
|
|||
i += 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.html |
|||
""" |
|||
|
|||
CPER_PROC_GENERIC = guid(0x9876CCAD, 0x47B4, 0x4bdb, |
|||
[0xB6, 0x5E, 0x16, 0xF1, |
|||
0x93, 0xC4, 0xF3, 0xDB]) |
|||
|
|||
CPER_PROC_X86 = guid(0xDC3EA0B0, 0xA144, 0x4797, |
|||
[0xB9, 0x5B, 0x53, 0xFA, |
|||
0x24, 0x2B, 0x6E, 0x1D]) |
|||
|
|||
CPER_PROC_ITANIUM = guid(0xe429faf1, 0x3cb7, 0x11d4, |
|||
[0xbc, 0xa7, 0x00, 0x80, |
|||
0xc7, 0x3c, 0x88, 0x81]) |
|||
|
|||
CPER_PROC_ARM = guid(0xE19E3D16, 0xBC11, 0x11E4, |
|||
[0x9C, 0xAA, 0xC2, 0x05, |
|||
0x1D, 0x5D, 0x46, 0xB0]) |
|||
|
|||
CPER_PLATFORM_MEM = guid(0xA5BC1114, 0x6F64, 0x4EDE, |
|||
[0xB8, 0x63, 0x3E, 0x83, |
|||
0xED, 0x7C, 0x83, 0xB1]) |
|||
|
|||
CPER_PLATFORM_MEM2 = guid(0x61EC04FC, 0x48E6, 0xD813, |
|||
[0x25, 0xC9, 0x8D, 0xAA, |
|||
0x44, 0x75, 0x0B, 0x12]) |
|||
|
|||
CPER_PCIE = guid(0xD995E954, 0xBBC1, 0x430F, |
|||
[0xAD, 0x91, 0xB4, 0x4D, |
|||
0xCB, 0x3C, 0x6F, 0x35]) |
|||
|
|||
CPER_PCI_BUS = guid(0xC5753963, 0x3B84, 0x4095, |
|||
[0xBF, 0x78, 0xED, 0xDA, |
|||
0xD3, 0xF9, 0xC9, 0xDD]) |
|||
|
|||
CPER_PCI_DEV = guid(0xEB5E4685, 0xCA66, 0x4769, |
|||
[0xB6, 0xA2, 0x26, 0x06, |
|||
0x8B, 0x00, 0x13, 0x26]) |
|||
|
|||
CPER_FW_ERROR = guid(0x81212A96, 0x09ED, 0x4996, |
|||
[0x94, 0x71, 0x8D, 0x72, |
|||
0x9C, 0x8E, 0x69, 0xED]) |
|||
|
|||
CPER_DMA_GENERIC = guid(0x5B51FEF7, 0xC79D, 0x4434, |
|||
[0x8F, 0x1B, 0xAA, 0x62, |
|||
0xDE, 0x3E, 0x2C, 0x64]) |
|||
|
|||
CPER_DMA_VT = guid(0x71761D37, 0x32B2, 0x45cd, |
|||
[0xA7, 0xD0, 0xB0, 0xFE, |
|||
0xDD, 0x93, 0xE8, 0xCF]) |
|||
|
|||
CPER_DMA_IOMMU = guid(0x036F84E1, 0x7F37, 0x428c, |
|||
[0xA7, 0x9E, 0x57, 0x5F, |
|||
0xDF, 0xAA, 0x84, 0xEC]) |
|||
|
|||
CPER_CCIX_PER = guid(0x91335EF6, 0xEBFB, 0x4478, |
|||
[0xA6, 0xA6, 0x88, 0xB7, |
|||
0x28, 0xCF, 0x75, 0xD7]) |
|||
|
|||
CPER_CXL_PROT_ERR = guid(0x80B9EFB4, 0x52B5, 0x4DE3, |
|||
[0xA7, 0x77, 0x68, 0x78, |
|||
0x4B, 0x77, 0x10, 0x48]) |
|||
Loading…
Reference in new issue