| 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # pylint: disable=C0301,C0114,R0903,R0912,R0913,R0914,R0915,W0511 |
| 4 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 5 | # |
| 6 | # Copyright (C) 2024-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org> |
| 7 | |
| 8 | # TODO: current implementation has dummy defaults. |
| 9 | # |
| 10 | # For a better implementation, a QMP addition/call is needed to |
| 11 | # retrieve some data for ARM Processor Error injection: |
| 12 | # |
| 13 | # - ARM registers: power_state, mpidr. |
| 14 | |
| 15 | """ |
| 16 | Generate an ARM processor error CPER, compatible with |
| 17 | UEFI 2.9A Errata. |
| 18 | |
| 19 | Injecting such errors can be done using: |
| 20 | |
| 21 | $ ./scripts/ghes_inject.py arm |
| 22 | Error injected. |
| 23 | |
| 24 | Produces a simple CPER register, as detected on a Linux guest: |
| 25 | |
| 26 | [Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 1 |
| 27 | [Hardware Error]: event severity: recoverable |
| 28 | [Hardware Error]: Error 0, type: recoverable |
| 29 | [Hardware Error]: section_type: ARM processor error |
| 30 | [Hardware Error]: MIDR: 0x0000000000000000 |
| 31 | [Hardware Error]: running state: 0x0 |
| 32 | [Hardware Error]: Power State Coordination Interface state: 0 |
| 33 | [Hardware Error]: Error info structure 0: |
| 34 | [Hardware Error]: num errors: 2 |
| 35 | [Hardware Error]: error_type: 0x02: cache error |
| 36 | [Hardware Error]: error_info: 0x000000000091000f |
| 37 | [Hardware Error]: transaction type: Data Access |
| 38 | [Hardware Error]: cache error, operation type: Data write |
| 39 | [Hardware Error]: cache level: 2 |
| 40 | [Hardware Error]: processor context not corrupted |
| 41 | [Firmware Warn]: GHES: Unhandled processor error type 0x02: cache error |
| 42 | |
| 43 | The ARM Processor Error message can be customized via command line |
| 44 | parameters. For instance: |
| 45 | |
| 46 | $ ./scripts/ghes_inject.py arm --mpidr 0x444 --running --affinity 1 \ |
| 47 | --error-info 12345678 --vendor 0x13,123,4,5,1 --ctx-array 0,1,2,3,4,5 \ |
| 48 | -t cache tlb bus micro-arch tlb,micro-arch |
| 49 | Error injected. |
| 50 | |
| 51 | Injects this error, as detected on a Linux guest: |
| 52 | |
| 53 | [Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 1 |
| 54 | [Hardware Error]: event severity: recoverable |
| 55 | [Hardware Error]: Error 0, type: recoverable |
| 56 | [Hardware Error]: section_type: ARM processor error |
| 57 | [Hardware Error]: MIDR: 0x0000000000000000 |
| 58 | [Hardware Error]: Multiprocessor Affinity Register (MPIDR): 0x0000000000000000 |
| 59 | [Hardware Error]: error affinity level: 0 |
| 60 | [Hardware Error]: running state: 0x1 |
| 61 | [Hardware Error]: Power State Coordination Interface state: 0 |
| 62 | [Hardware Error]: Error info structure 0: |
| 63 | [Hardware Error]: num errors: 2 |
| 64 | [Hardware Error]: error_type: 0x02: cache error |
| 65 | [Hardware Error]: error_info: 0x0000000000bc614e |
| 66 | [Hardware Error]: cache level: 2 |
| 67 | [Hardware Error]: processor context not corrupted |
| 68 | [Hardware Error]: Error info structure 1: |
| 69 | [Hardware Error]: num errors: 2 |
| 70 | [Hardware Error]: error_type: 0x04: TLB error |
| 71 | [Hardware Error]: error_info: 0x000000000054007f |
| 72 | [Hardware Error]: transaction type: Instruction |
| 73 | [Hardware Error]: TLB error, operation type: Instruction fetch |
| 74 | [Hardware Error]: TLB level: 1 |
| 75 | [Hardware Error]: processor context not corrupted |
| 76 | [Hardware Error]: the error has not been corrected |
| 77 | [Hardware Error]: PC is imprecise |
| 78 | [Hardware Error]: Error info structure 2: |
| 79 | [Hardware Error]: num errors: 2 |
| 80 | [Hardware Error]: error_type: 0x08: bus error |
| 81 | [Hardware Error]: error_info: 0x00000080d6460fff |
| 82 | [Hardware Error]: transaction type: Generic |
| 83 | [Hardware Error]: bus error, operation type: Generic read (type of instruction or data request cannot be determined) |
| 84 | [Hardware Error]: affinity level at which the bus error occurred: 1 |
| 85 | [Hardware Error]: processor context corrupted |
| 86 | [Hardware Error]: the error has been corrected |
| 87 | [Hardware Error]: PC is imprecise |
| 88 | [Hardware Error]: Program execution can be restarted reliably at the PC associated with the error. |
| 89 | [Hardware Error]: participation type: Local processor observed |
| 90 | [Hardware Error]: request timed out |
| 91 | [Hardware Error]: address space: External Memory Access |
| 92 | [Hardware Error]: memory access attributes:0x20 |
| 93 | [Hardware Error]: access mode: secure |
| 94 | [Hardware Error]: Error info structure 3: |
| 95 | [Hardware Error]: num errors: 2 |
| 96 | [Hardware Error]: error_type: 0x10: micro-architectural error |
| 97 | [Hardware Error]: error_info: 0x0000000078da03ff |
| 98 | [Hardware Error]: Error info structure 4: |
| 99 | [Hardware Error]: num errors: 2 |
| 100 | [Hardware Error]: error_type: 0x14: TLB error|micro-architectural error |
| 101 | [Hardware Error]: Context info structure 0: |
| 102 | [Hardware Error]: register context type: AArch64 EL1 context registers |
| 103 | [Hardware Error]: 00000000: 00000000 00000000 |
| 104 | [Hardware Error]: Vendor specific error info has 5 bytes: |
| 105 | [Hardware Error]: 00000000: 13 7b 04 05 01 .{... |
| 106 | [Firmware Warn]: GHES: Unhandled processor error type 0x02: cache error |
| 107 | [Firmware Warn]: GHES: Unhandled processor error type 0x04: TLB error |
| 108 | [Firmware Warn]: GHES: Unhandled processor error type 0x08: bus error |
| 109 | [Firmware Warn]: GHES: Unhandled processor error type 0x10: micro-architectural error |
| 110 | [Firmware Warn]: GHES: Unhandled processor error type 0x14: TLB error|micro-architectural error |
| 111 | """ |
| 112 | |
| 113 | import argparse |
| 114 | import re |
| 115 | |
| 116 | from qmp_helper import qmp, util, cper_guid |
| 117 | |
| 118 | |
| 119 | class ArmProcessorEinj: |
| 120 | """ |
| 121 | Implements ARM Processor Error injection via GHES |
| 122 | """ |
| 123 | |
| 124 | DESC = """ |
| 125 | Generates an ARM processor error CPER, compatible with |
| 126 | UEFI 2.9A Errata. |
| 127 | """ |
| 128 | |
| 129 | ACPI_GHES_ARM_CPER_LENGTH = 40 |
| 130 | ACPI_GHES_ARM_CPER_PEI_LENGTH = 32 |
| 131 | |
| 132 | # Context types |
| 133 | CONTEXT_AARCH32_EL1 = 1 |
| 134 | CONTEXT_AARCH64_EL1 = 5 |
| 135 | CONTEXT_MISC_REG = 8 |
| 136 | |
| 137 | def __init__(self, subparsers): |
| 138 | """Initialize the error injection class and add subparser""" |
| 139 | |
| 140 | # Valid choice values |
| 141 | self.arm_valid_bits = { |
| 142 | "mpidr": util.bit(0), |
| 143 | "affinity": util.bit(1), |
| 144 | "running": util.bit(2), |
| 145 | "vendor": util.bit(3), |
| 146 | } |
| 147 | |
| 148 | self.pei_flags = { |
| 149 | "first": util.bit(0), |
| 150 | "last": util.bit(1), |
| 151 | "propagated": util.bit(2), |
| 152 | "overflow": util.bit(3), |
| 153 | } |
| 154 | |
| 155 | self.pei_error_types = { |
| 156 | "cache": util.bit(1), |
| 157 | "tlb": util.bit(2), |
| 158 | "bus": util.bit(3), |
| 159 | "micro-arch": util.bit(4), |
| 160 | } |
| 161 | |
| 162 | self.pei_valid_bits = { |
| 163 | "multiple-error": util.bit(0), |
| 164 | "flags": util.bit(1), |
| 165 | "error-info": util.bit(2), |
| 166 | "virt-addr": util.bit(3), |
| 167 | "phy-addr": util.bit(4), |
| 168 | } |
| 169 | |
| 170 | self.data = bytearray() |
| 171 | |
| 172 | parser = subparsers.add_parser("arm", description=self.DESC) |
| 173 | |
| 174 | arm_valid_bits = ",".join(self.arm_valid_bits.keys()) |
| 175 | flags = ",".join(self.pei_flags.keys()) |
| 176 | error_types = ",".join(self.pei_error_types.keys()) |
| 177 | pei_valid_bits = ",".join(self.pei_valid_bits.keys()) |
| 178 | |
| 179 | # UEFI N.16 ARM Validation bits |
| 180 | g_arm = parser.add_argument_group("ARM processor") |
| 181 | g_arm.add_argument("--arm", "--arm-valid", |
| 182 | help=f"ARM valid bits: {arm_valid_bits}") |
| 183 | g_arm.add_argument("-a", "--affinity", "--level", "--affinity-level", |
| 184 | type=lambda x: int(x, 0), |
| 185 | help="Affinity level (when multiple levels apply)") |
| 186 | g_arm.add_argument("-l", "--mpidr", type=lambda x: int(x, 0), |
| 187 | help="Multiprocessor Affinity Register") |
| 188 | g_arm.add_argument("-i", "--midr", type=lambda x: int(x, 0), |
| 189 | help="Main ID Register") |
| 190 | g_arm.add_argument("-r", "--running", |
| 191 | action=argparse.BooleanOptionalAction, |
| 192 | default=None, |
| 193 | help="Indicates if the processor is running or not") |
| 194 | g_arm.add_argument("--psci", "--psci-state", |
| 195 | type=lambda x: int(x, 0), |
| 196 | help="Power State Coordination Interface - PSCI state") |
| 197 | |
| 198 | # TODO: Add vendor-specific support |
| 199 | |
| 200 | # UEFI N.17 bitmaps (type and flags) |
| 201 | g_pei = parser.add_argument_group("ARM Processor Error Info (PEI)") |
| 202 | g_pei.add_argument("-t", "--type", nargs="+", |
| 203 | help=f"one or more error types: {error_types}") |
| 204 | g_pei.add_argument("-f", "--flags", nargs="*", |
| 205 | help=f"zero or more error flags: {flags}") |
| 206 | g_pei.add_argument("-V", "--pei-valid", "--error-valid", nargs="*", |
| 207 | help=f"zero or more PEI valid bits: {pei_valid_bits}") |
| 208 | |
| 209 | # UEFI N.17 Integer values |
| 210 | g_pei.add_argument("-m", "--multiple-error", nargs="+", |
| 211 | help="Number of errors: 0: Single error, 1: Multiple errors, 2-65535: Error count if known") |
| 212 | g_pei.add_argument("-e", "--error-info", nargs="+", |
| 213 | help="Error information (UEFI 2.10 tables N.18 to N.20)") |
| 214 | g_pei.add_argument("-p", "--physical-address", nargs="+", |
| 215 | help="Physical address") |
| 216 | g_pei.add_argument("-v", "--virtual-address", nargs="+", |
| 217 | help="Virtual address") |
| 218 | |
| 219 | # UEFI N.21 Context |
| 220 | g_ctx = parser.add_argument_group("Processor Context") |
| 221 | g_ctx.add_argument("--ctx-type", "--context-type", nargs="*", |
| 222 | help="Type of the context (0=ARM32 GPR, 5=ARM64 EL1, other values supported)") |
| 223 | g_ctx.add_argument("--ctx-size", "--context-size", nargs="*", |
| 224 | help="Minimal size of the context") |
| 225 | g_ctx.add_argument("--ctx-array", "--context-array", nargs="*", |
| 226 | help="Comma-separated arrays for each context") |
| 227 | |
| 228 | # Vendor-specific data |
| 229 | g_vendor = parser.add_argument_group("Vendor-specific data") |
| 230 | g_vendor.add_argument("--vendor", "--vendor-specific", nargs="+", |
| 231 | help="Vendor-specific byte arrays of data") |
| 232 | |
| 233 | # Add arguments for Generic Error Data |
| 234 | qmp.argparse(parser) |
| 235 | |
| 236 | parser.set_defaults(func=self.send_cper) |
| 237 | |
| 238 | def send_cper(self, args): |
| 239 | """Parse subcommand arguments and send a CPER via QMP""" |
| 240 | |
| 241 | qmp_cmd = qmp(args.host, args.port, args.debug) |
| 242 | |
| 243 | # Handle Generic Error Data arguments if any |
| 244 | qmp_cmd.set_args(args) |
| 245 | |
| 246 | is_cpu_type = re.compile(r"^([\w+]+\-)?arm\-cpu$") |
| 247 | cpus = qmp_cmd.search_qom("/machine/unattached/device", |
| 248 | "type", is_cpu_type) |
| 249 | |
| 250 | cper = {} |
| 251 | pei = {} |
| 252 | ctx = {} |
| 253 | vendor = {} |
| 254 | |
| 255 | arg = vars(args) |
| 256 | |
| 257 | # Handle global parameters |
| 258 | if args.arm: |
| 259 | arm_valid_init = False |
| 260 | cper["valid"] = util.get_choice(name="valid", |
| 261 | value=args.arm, |
| 262 | choices=self.arm_valid_bits, |
| 263 | suffixes=["-error", "-err"]) |
| 264 | else: |
| 265 | cper["valid"] = 0 |
| 266 | arm_valid_init = True |
| 267 | |
| 268 | if "running" in arg: |
| 269 | if args.running: |
| 270 | cper["running-state"] = util.bit(0) |
| 271 | else: |
| 272 | cper["running-state"] = 0 |
| 273 | else: |
| 274 | cper["running-state"] = 0 |
| 275 | |
| 276 | if arm_valid_init: |
| 277 | if args.affinity: |
| 278 | cper["valid"] |= self.arm_valid_bits["affinity"] |
| 279 | |
| 280 | if args.mpidr: |
| 281 | cper["valid"] |= self.arm_valid_bits["mpidr"] |
| 282 | |
| 283 | if "running-state" in cper: |
| 284 | cper["valid"] |= self.arm_valid_bits["running"] |
| 285 | |
| 286 | if args.psci: |
| 287 | cper["valid"] |= self.arm_valid_bits["running"] |
| 288 | |
| 289 | # Handle PEI |
| 290 | if not args.type: |
| 291 | args.type = ["cache-error"] |
| 292 | |
| 293 | util.get_mult_choices( |
| 294 | pei, |
| 295 | name="valid", |
| 296 | values=args.pei_valid, |
| 297 | choices=self.pei_valid_bits, |
| 298 | suffixes=["-valid", "--addr"], |
| 299 | ) |
| 300 | util.get_mult_choices( |
| 301 | pei, |
| 302 | name="type", |
| 303 | values=args.type, |
| 304 | choices=self.pei_error_types, |
| 305 | suffixes=["-error", "-err"], |
| 306 | ) |
| 307 | util.get_mult_choices( |
| 308 | pei, |
| 309 | name="flags", |
| 310 | values=args.flags, |
| 311 | choices=self.pei_flags, |
| 312 | suffixes=["-error", "-cap"], |
| 313 | ) |
| 314 | util.get_mult_int(pei, "error-info", args.error_info) |
| 315 | util.get_mult_int(pei, "multiple-error", args.multiple_error) |
| 316 | util.get_mult_int(pei, "phy-addr", args.physical_address) |
| 317 | util.get_mult_int(pei, "virt-addr", args.virtual_address) |
| 318 | |
| 319 | # Handle context |
| 320 | util.get_mult_int(ctx, "type", args.ctx_type, allow_zero=True) |
| 321 | util.get_mult_int(ctx, "minimal-size", args.ctx_size, allow_zero=True) |
| 322 | util.get_mult_array(ctx, "register", args.ctx_array, allow_zero=True) |
| 323 | |
| 324 | util.get_mult_array(vendor, "bytes", args.vendor, max_val=255) |
| 325 | |
| 326 | # Store PEI |
| 327 | pei_data = bytearray() |
| 328 | default_flags = self.pei_flags["first"] |
| 329 | default_flags |= self.pei_flags["last"] |
| 330 | |
| 331 | error_info_num = 0 |
| 332 | |
| 333 | for i, p in pei.items(): # pylint: disable=W0612 |
| 334 | error_info_num += 1 |
| 335 | |
| 336 | # UEFI 2.10 doesn't define how to encode error information |
| 337 | # when multiple types are raised. So, provide a default only |
| 338 | # if a single type is there |
| 339 | if "error-info" not in p: |
| 340 | if p["type"] == util.bit(1): |
| 341 | p["error-info"] = 0x0091000F |
| 342 | if p["type"] == util.bit(2): |
| 343 | p["error-info"] = 0x0054007F |
| 344 | if p["type"] == util.bit(3): |
| 345 | p["error-info"] = 0x80D6460FFF |
| 346 | if p["type"] == util.bit(4): |
| 347 | p["error-info"] = 0x78DA03FF |
| 348 | |
| 349 | if "valid" not in p: |
| 350 | p["valid"] = 0 |
| 351 | if "multiple-error" in p: |
| 352 | p["valid"] |= self.pei_valid_bits["multiple-error"] |
| 353 | |
| 354 | if "flags" in p: |
| 355 | p["valid"] |= self.pei_valid_bits["flags"] |
| 356 | |
| 357 | if "error-info" in p: |
| 358 | p["valid"] |= self.pei_valid_bits["error-info"] |
| 359 | |
| 360 | if "phy-addr" in p: |
| 361 | p["valid"] |= self.pei_valid_bits["phy-addr"] |
| 362 | |
| 363 | if "virt-addr" in p: |
| 364 | p["valid"] |= self.pei_valid_bits["virt-addr"] |
| 365 | |
| 366 | # Version |
| 367 | util.data_add(pei_data, 0, 1) |
| 368 | |
| 369 | util.data_add(pei_data, |
| 370 | self.ACPI_GHES_ARM_CPER_PEI_LENGTH, 1) |
| 371 | |
| 372 | util.data_add(pei_data, p["valid"], 2) |
| 373 | util.data_add(pei_data, p["type"], 1) |
| 374 | util.data_add(pei_data, p.get("multiple-error", 1), 2) |
| 375 | util.data_add(pei_data, p.get("flags", default_flags), 1) |
| 376 | util.data_add(pei_data, p.get("error-info", 0), 8) |
| 377 | util.data_add(pei_data, p.get("virt-addr", 0xDEADBEEF), 8) |
| 378 | util.data_add(pei_data, p.get("phy-addr", 0xABBA0BAD), 8) |
| 379 | |
| 380 | # Store Context |
| 381 | ctx_data = bytearray() |
| 382 | context_info_num = 0 |
| 383 | |
| 384 | if ctx: |
| 385 | ret = qmp_cmd.send_cmd("query-target", may_open=True) |
| 386 | |
| 387 | default_ctx = self.CONTEXT_MISC_REG |
| 388 | |
| 389 | if "arch" in ret: |
| 390 | if ret["arch"] == "aarch64": |
| 391 | default_ctx = self.CONTEXT_AARCH64_EL1 |
| 392 | elif ret["arch"] == "arm": |
| 393 | default_ctx = self.CONTEXT_AARCH32_EL1 |
| 394 | |
| 395 | for k in sorted(ctx.keys()): |
| 396 | context_info_num += 1 |
| 397 | |
| 398 | if "type" not in ctx[k]: |
| 399 | ctx[k]["type"] = default_ctx |
| 400 | |
| 401 | if "register" not in ctx[k]: |
| 402 | ctx[k]["register"] = [] |
| 403 | |
| 404 | reg_size = len(ctx[k]["register"]) |
| 405 | size = 0 |
| 406 | |
| 407 | if "minimal-size" in ctx: |
| 408 | size = ctx[k]["minimal-size"] |
| 409 | |
| 410 | size = max(size, reg_size) |
| 411 | |
| 412 | size = (size + 1) % 0xFFFE |
| 413 | |
| 414 | # Version |
| 415 | util.data_add(ctx_data, 0, 2) |
| 416 | |
| 417 | util.data_add(ctx_data, ctx[k]["type"], 2) |
| 418 | |
| 419 | util.data_add(ctx_data, 8 * size, 4) |
| 420 | |
| 421 | for r in ctx[k]["register"]: |
| 422 | util.data_add(ctx_data, r, 8) |
| 423 | |
| 424 | for i in range(reg_size, size): # pylint: disable=W0612 |
| 425 | util.data_add(ctx_data, 0, 8) |
| 426 | |
| 427 | # Vendor-specific bytes are not grouped |
| 428 | vendor_data = bytearray() |
| 429 | if vendor: |
| 430 | for k in sorted(vendor.keys()): |
| 431 | for b in vendor[k]["bytes"]: |
| 432 | util.data_add(vendor_data, b, 1) |
| 433 | |
| 434 | # Encode ARM Processor Error |
| 435 | data = bytearray() |
| 436 | |
| 437 | util.data_add(data, cper["valid"], 4) |
| 438 | |
| 439 | util.data_add(data, error_info_num, 2) |
| 440 | util.data_add(data, context_info_num, 2) |
| 441 | |
| 442 | # Calculate the length of the CPER data |
| 443 | cper_length = self.ACPI_GHES_ARM_CPER_LENGTH |
| 444 | cper_length += len(pei_data) |
| 445 | cper_length += len(vendor_data) |
| 446 | cper_length += len(ctx_data) |
| 447 | util.data_add(data, cper_length, 4) |
| 448 | |
| 449 | util.data_add(data, arg.get("affinity-level", 0), 1) |
| 450 | |
| 451 | # Reserved |
| 452 | util.data_add(data, 0, 3) |
| 453 | |
| 454 | if "midr-el1" not in arg: |
| 455 | if cpus: |
| 456 | cmd_arg = { |
| 457 | 'path': cpus[0], |
| 458 | 'property': "midr" |
| 459 | } |
| 460 | ret = qmp_cmd.send_cmd("qom-get", cmd_arg, may_open=True) |
| 461 | if isinstance(ret, int): |
| 462 | arg["midr-el1"] = ret |
| 463 | |
| 464 | util.data_add(data, arg.get("mpidr-el1", 0), 8) |
| 465 | util.data_add(data, arg.get("midr-el1", 0), 8) |
| 466 | util.data_add(data, cper["running-state"], 4) |
| 467 | util.data_add(data, arg.get("psci-state", 0), 4) |
| 468 | |
| 469 | # Add PEI |
| 470 | data.extend(pei_data) |
| 471 | data.extend(ctx_data) |
| 472 | data.extend(vendor_data) |
| 473 | |
| 474 | self.data = data |
| 475 | |
| 476 | qmp_cmd.send_cper(cper_guid.CPER_PROC_ARM, self.data) |