| 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # pylint: disable=C0103,E0213,E1135,E1136,E1137,R0902,R0903,R0912,R0913,R0917 |
| 4 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 5 | # |
| 6 | # Copyright (C) 2024-2025 Mauro Carvalho Chehab <mchehab+huawei@kernel.org> |
| 7 | |
| 8 | """ |
| 9 | Helper classes to be used by ghes_inject command classes. |
| 10 | """ |
| 11 | |
| 12 | import json |
| 13 | import sys |
| 14 | |
| 15 | from datetime import datetime |
| 16 | |
| 17 | try: |
| 18 | from qemu.qmp.legacy import QEMUMonitorProtocol |
| 19 | except ModuleNotFoundError as exc: |
| 20 | print(f"Module '{exc.name}' not found.", file=sys.stderr) |
| 21 | print(f"Try $builddir/run {' '.join(sys.argv)}", file=sys.stderr) |
| 22 | sys.exit(1) |
| 23 | |
| 24 | from base64 import b64encode |
| 25 | |
| 26 | class util: |
| 27 | """ |
| 28 | Ancillary functions to deal with bitmaps, parse arguments, |
| 29 | generate GUID and encode data on a bytearray buffer. |
| 30 | """ |
| 31 | |
| 32 | # |
| 33 | # Helper routines to handle multiple choice arguments |
| 34 | # |
| 35 | def get_choice(name, value, choices, suffixes=None, bitmask=True): |
| 36 | """Produce a list from multiple choice argument""" |
| 37 | |
| 38 | new_values = 0 |
| 39 | |
| 40 | if not value: |
| 41 | return new_values |
| 42 | |
| 43 | for val in value.split(","): |
| 44 | val = val.lower() |
| 45 | |
| 46 | if suffixes: |
| 47 | for suffix in suffixes: |
| 48 | val = val.removesuffix(suffix) |
| 49 | |
| 50 | if val not in choices.keys(): |
| 51 | if suffixes: |
| 52 | for suffix in suffixes: |
| 53 | if val + suffix in choices.keys(): |
| 54 | val += suffix |
| 55 | break |
| 56 | |
| 57 | if val not in choices.keys(): |
| 58 | sys.exit(f"Error on '{name}': choice '{val}' is invalid.") |
| 59 | |
| 60 | val = choices[val] |
| 61 | |
| 62 | if bitmask: |
| 63 | new_values |= val |
| 64 | else: |
| 65 | if new_values: |
| 66 | sys.exit(f"Error on '{name}': only one value is accepted.") |
| 67 | |
| 68 | new_values = val |
| 69 | |
| 70 | return new_values |
| 71 | |
| 72 | def get_array(name, values, max_val=None): |
| 73 | """Add numbered hashes from integer lists into an array""" |
| 74 | |
| 75 | array = [] |
| 76 | |
| 77 | for value in values: |
| 78 | for val in value.split(","): |
| 79 | try: |
| 80 | val = int(val, 0) |
| 81 | except ValueError: |
| 82 | sys.exit(f"Error on '{name}': {val} is not an integer") |
| 83 | |
| 84 | if val < 0: |
| 85 | sys.exit(f"Error on '{name}': {val} is not unsigned") |
| 86 | |
| 87 | if max_val and val > max_val: |
| 88 | sys.exit(f"Error on '{name}': {val} is too little") |
| 89 | |
| 90 | array.append(val) |
| 91 | |
| 92 | return array |
| 93 | |
| 94 | def get_mult_array(mult, name, values, allow_zero=False, max_val=None): |
| 95 | """Add numbered hashes from integer lists""" |
| 96 | |
| 97 | if not allow_zero: |
| 98 | if not values: |
| 99 | return |
| 100 | else: |
| 101 | if values is None: |
| 102 | return |
| 103 | |
| 104 | if not values: |
| 105 | i = 0 |
| 106 | if i not in mult: |
| 107 | mult[i] = {} |
| 108 | |
| 109 | mult[i][name] = [] |
| 110 | return |
| 111 | |
| 112 | i = 0 |
| 113 | for value in values: |
| 114 | for val in value.split(","): |
| 115 | try: |
| 116 | val = int(val, 0) |
| 117 | except ValueError: |
| 118 | sys.exit(f"Error on '{name}': {val} is not an integer") |
| 119 | |
| 120 | if val < 0: |
| 121 | sys.exit(f"Error on '{name}': {val} is not unsigned") |
| 122 | |
| 123 | if max_val and val > max_val: |
| 124 | sys.exit(f"Error on '{name}': {val} is too little") |
| 125 | |
| 126 | if i not in mult: |
| 127 | mult[i] = {} |
| 128 | |
| 129 | if name not in mult[i]: |
| 130 | mult[i][name] = [] |
| 131 | |
| 132 | mult[i][name].append(val) |
| 133 | |
| 134 | i += 1 |
| 135 | |
| 136 | |
| 137 | def get_mult_choices(mult, name, values, choices, |
| 138 | suffixes=None, allow_zero=False): |
| 139 | """Add numbered hashes from multiple choice arguments""" |
| 140 | |
| 141 | if not allow_zero: |
| 142 | if not values: |
| 143 | return |
| 144 | else: |
| 145 | if values is None: |
| 146 | return |
| 147 | |
| 148 | i = 0 |
| 149 | for val in values: |
| 150 | new_values = util.get_choice(name, val, choices, suffixes) |
| 151 | |
| 152 | if i not in mult: |
| 153 | mult[i] = {} |
| 154 | |
| 155 | mult[i][name] = new_values |
| 156 | i += 1 |
| 157 | |
| 158 | |
| 159 | def get_mult_int(mult, name, values, allow_zero=False): |
| 160 | """Add numbered hashes from integer arguments""" |
| 161 | if not allow_zero: |
| 162 | if not values: |
| 163 | return |
| 164 | else: |
| 165 | if values is None: |
| 166 | return |
| 167 | |
| 168 | i = 0 |
| 169 | for val in values: |
| 170 | try: |
| 171 | val = int(val, 0) |
| 172 | except ValueError: |
| 173 | sys.exit(f"Error on '{name}': {val} is not an integer") |
| 174 | |
| 175 | if val < 0: |
| 176 | sys.exit(f"Error on '{name}': {val} is not unsigned") |
| 177 | |
| 178 | if i not in mult: |
| 179 | mult[i] = {} |
| 180 | |
| 181 | mult[i][name] = val |
| 182 | i += 1 |
| 183 | |
| 184 | |
| 185 | # |
| 186 | # Data encode helper functions |
| 187 | # |
| 188 | def bit(b): |
| 189 | """Simple macro to define a bit on a bitmask""" |
| 190 | return 1 << b |
| 191 | |
| 192 | |
| 193 | def data_add(data, value, num_bytes): |
| 194 | """Adds bytes from value inside a bitarray""" |
| 195 | |
| 196 | data.extend(value.to_bytes(num_bytes, byteorder="little")) # pylint: disable=E1101 |
| 197 | |
| 198 | def dump_bytearray(name, data): |
| 199 | """Does an hexdump of a byte array, grouping in bytes""" |
| 200 | |
| 201 | print(f"{name} ({len(data)} bytes):") |
| 202 | |
| 203 | for ln_start in range(0, len(data), 16): |
| 204 | ln_end = min(ln_start + 16, len(data)) |
| 205 | print(f" {ln_start:08x} ", end="") |
| 206 | for i in range(ln_start, ln_end): |
| 207 | print(f"{data[i]:02x} ", end="") |
| 208 | for i in range(ln_end, ln_start + 16): |
| 209 | print(" ", end="") |
| 210 | print(" ", end="") |
| 211 | for i in range(ln_start, ln_end): |
| 212 | if data[i] >= 32 and data[i] < 127: |
| 213 | print(chr(data[i]), end="") |
| 214 | else: |
| 215 | print(".", end="") |
| 216 | |
| 217 | print() |
| 218 | print() |
| 219 | |
| 220 | def time(string): |
| 221 | """Handle BCD timestamps used on Generic Error Data Block""" |
| 222 | |
| 223 | time = None |
| 224 | |
| 225 | # Formats to be used when parsing time stamps |
| 226 | formats = [ |
| 227 | "%Y-%m-%d %H:%M:%S", |
| 228 | ] |
| 229 | |
| 230 | if string == "now": |
| 231 | time = datetime.now() |
| 232 | |
| 233 | if time is None: |
| 234 | for fmt in formats: |
| 235 | try: |
| 236 | time = datetime.strptime(string, fmt) |
| 237 | break |
| 238 | except ValueError: |
| 239 | pass |
| 240 | |
| 241 | if time is None: |
| 242 | raise ValueError("Invalid time format") |
| 243 | |
| 244 | return time |
| 245 | |
| 246 | class guid: |
| 247 | """ |
| 248 | Simple class to handle GUID fields. |
| 249 | """ |
| 250 | |
| 251 | def __init__(self, time_low, time_mid, time_high, nodes): |
| 252 | """Initialize a GUID value""" |
| 253 | |
| 254 | assert len(nodes) == 8 |
| 255 | |
| 256 | self.time_low = time_low |
| 257 | self.time_mid = time_mid |
| 258 | self.time_high = time_high |
| 259 | self.nodes = nodes |
| 260 | |
| 261 | @classmethod |
| 262 | def UUID(cls, guid_str): |
| 263 | """Initialize a GUID using a string on its standard format""" |
| 264 | |
| 265 | if len(guid_str) != 36: |
| 266 | print("Size not 36") |
| 267 | raise ValueError('Invalid GUID size') |
| 268 | |
| 269 | # It is easier to parse without separators. So, drop them |
| 270 | guid_str = guid_str.replace('-', '') |
| 271 | |
| 272 | if len(guid_str) != 32: |
| 273 | print("Size not 32", guid_str, len(guid_str)) |
| 274 | raise ValueError('Invalid GUID hex size') |
| 275 | |
| 276 | time_low = 0 |
| 277 | time_mid = 0 |
| 278 | time_high = 0 |
| 279 | nodes = [] |
| 280 | |
| 281 | for i in reversed(range(16, 32, 2)): |
| 282 | h = guid_str[i:i + 2] |
| 283 | value = int(h, 16) |
| 284 | nodes.insert(0, value) |
| 285 | |
| 286 | time_high = int(guid_str[12:16], 16) |
| 287 | time_mid = int(guid_str[8:12], 16) |
| 288 | time_low = int(guid_str[0:8], 16) |
| 289 | |
| 290 | return cls(time_low, time_mid, time_high, nodes) |
| 291 | |
| 292 | def __str__(self): |
| 293 | """Output a GUID value on its default string representation""" |
| 294 | |
| 295 | clock = self.nodes[0] << 8 | self.nodes[1] |
| 296 | |
| 297 | node = 0 |
| 298 | for i in range(2, len(self.nodes)): |
| 299 | node = node << 8 | self.nodes[i] |
| 300 | |
| 301 | s = f"{self.time_low:08x}-{self.time_mid:04x}-" |
| 302 | s += f"{self.time_high:04x}-{clock:04x}-{node:012x}" |
| 303 | return s |
| 304 | |
| 305 | def to_bytes(self): |
| 306 | """Output a GUID value in bytes""" |
| 307 | |
| 308 | data = bytearray() |
| 309 | |
| 310 | util.data_add(data, self.time_low, 4) |
| 311 | util.data_add(data, self.time_mid, 2) |
| 312 | util.data_add(data, self.time_high, 2) |
| 313 | data.extend(bytearray(self.nodes)) |
| 314 | |
| 315 | return data |
| 316 | |
| 317 | class qmp: |
| 318 | """ |
| 319 | Opens a connection and send/receive QMP commands. |
| 320 | """ |
| 321 | |
| 322 | def send_cmd(self, command, args=None, may_open=False, return_error=True): |
| 323 | """Send a command to QMP, optinally opening a connection""" |
| 324 | |
| 325 | if may_open: |
| 326 | self._connect() |
| 327 | elif not self.connected: |
| 328 | return False |
| 329 | |
| 330 | msg = { 'execute': command } |
| 331 | if args: |
| 332 | msg['arguments'] = args |
| 333 | |
| 334 | try: |
| 335 | obj = self.qmp_monitor.cmd_obj(msg) |
| 336 | # Can we use some other exception class here? |
| 337 | except Exception as e: # pylint: disable=W0718 |
| 338 | print(f"Command: {command}") |
| 339 | print(f"Failed to inject error: {e}.") |
| 340 | return None |
| 341 | |
| 342 | if "return" in obj: |
| 343 | if isinstance(obj.get("return"), dict): |
| 344 | if obj["return"]: |
| 345 | return obj["return"] |
| 346 | return "OK" |
| 347 | |
| 348 | return obj["return"] |
| 349 | |
| 350 | if isinstance(obj.get("error"), dict): |
| 351 | error = obj["error"] |
| 352 | if return_error: |
| 353 | print(f"Command: {msg}") |
| 354 | print(f'{error["class"]}: {error["desc"]}') |
| 355 | else: |
| 356 | print(json.dumps(obj)) |
| 357 | |
| 358 | return None |
| 359 | |
| 360 | def _close(self): |
| 361 | """Shutdown and close the socket, if opened""" |
| 362 | if not self.connected: |
| 363 | return |
| 364 | |
| 365 | self.qmp_monitor.close() |
| 366 | self.connected = False |
| 367 | |
| 368 | def _connect(self): |
| 369 | """Connect to a QMP TCP/IP port, if not connected yet""" |
| 370 | |
| 371 | if self.connected: |
| 372 | return True |
| 373 | |
| 374 | try: |
| 375 | self.qmp_monitor.connect(negotiate=True) |
| 376 | except ConnectionError: |
| 377 | sys.exit(f"Can't connect to QMP host {self.host}:{self.port}") |
| 378 | |
| 379 | self.connected = True |
| 380 | |
| 381 | return True |
| 382 | |
| 383 | BLOCK_STATUS_BITS = { |
| 384 | "uncorrectable": util.bit(0), |
| 385 | "correctable": util.bit(1), |
| 386 | "multi-uncorrectable": util.bit(2), |
| 387 | "multi-correctable": util.bit(3), |
| 388 | } |
| 389 | |
| 390 | ERROR_SEVERITY = { |
| 391 | "recoverable": 0, |
| 392 | "fatal": 1, |
| 393 | "corrected": 2, |
| 394 | "none": 3, |
| 395 | } |
| 396 | |
| 397 | VALIDATION_BITS = { |
| 398 | "fru-id": util.bit(0), |
| 399 | "fru-text": util.bit(1), |
| 400 | "timestamp": util.bit(2), |
| 401 | } |
| 402 | |
| 403 | GEDB_FLAGS_BITS = { |
| 404 | "recovered": util.bit(0), |
| 405 | "prev-error": util.bit(1), |
| 406 | "simulated": util.bit(2), |
| 407 | } |
| 408 | |
| 409 | GENERIC_DATA_SIZE = 72 |
| 410 | |
| 411 | def argparse(parser): |
| 412 | """Prepare a parser group to query generic error data""" |
| 413 | |
| 414 | block_status_bits = ",".join(qmp.BLOCK_STATUS_BITS.keys()) |
| 415 | error_severity_enum = ",".join(qmp.ERROR_SEVERITY.keys()) |
| 416 | validation_bits = ",".join(qmp.VALIDATION_BITS.keys()) |
| 417 | gedb_flags_bits = ",".join(qmp.GEDB_FLAGS_BITS.keys()) |
| 418 | |
| 419 | g_gen = parser.add_argument_group("Generic Error Data") # pylint: disable=E1101 |
| 420 | g_gen.add_argument("--block-status", |
| 421 | help=f"block status bits: {block_status_bits}") |
| 422 | g_gen.add_argument("--raw-data", nargs="+", |
| 423 | help="Raw data inside the Error Status Block") |
| 424 | g_gen.add_argument("--error-severity", "--severity", |
| 425 | help=f"error severity: {error_severity_enum}") |
| 426 | g_gen.add_argument("--gen-err-valid-bits", |
| 427 | "--generic-error-validation-bits", |
| 428 | help=f"validation bits: {validation_bits}") |
| 429 | g_gen.add_argument("--fru-id", type=guid.UUID, |
| 430 | help="GUID representing a physical device") |
| 431 | g_gen.add_argument("--fru-text", |
| 432 | help="ASCII string identifying the FRU hardware") |
| 433 | g_gen.add_argument("--timestamp", type=util.time, |
| 434 | help="Time when the error info was collected") |
| 435 | g_gen.add_argument("--precise", "--precise-timestamp", |
| 436 | action='store_true', |
| 437 | help="Marks the timestamp as precise if --timestamp is used") |
| 438 | g_gen.add_argument("--gedb-flags", |
| 439 | help=f"General Error Data Block flags: {gedb_flags_bits}") |
| 440 | |
| 441 | def set_args(self, args): |
| 442 | """Set the arguments optionally defined via self.argparse()""" |
| 443 | |
| 444 | if args.block_status: |
| 445 | self.block_status = util.get_choice(name="block-status", |
| 446 | value=args.block_status, |
| 447 | choices=self.BLOCK_STATUS_BITS, |
| 448 | bitmask=False) |
| 449 | if args.raw_data: |
| 450 | self.raw_data = util.get_array("raw-data", args.raw_data, |
| 451 | max_val=255) |
| 452 | print(self.raw_data) |
| 453 | |
| 454 | if args.error_severity: |
| 455 | self.error_severity = util.get_choice(name="error-severity", |
| 456 | value=args.error_severity, |
| 457 | choices=self.ERROR_SEVERITY, |
| 458 | bitmask=False) |
| 459 | |
| 460 | if args.fru_id: |
| 461 | self.fru_id = args.fru_id.to_bytes() |
| 462 | if not args.gen_err_valid_bits: |
| 463 | self.validation_bits |= self.VALIDATION_BITS["fru-id"] |
| 464 | |
| 465 | if args.fru_text: |
| 466 | text = bytearray(args.fru_text.encode('ascii')) |
| 467 | if len(text) > 20: |
| 468 | sys.exit("FRU text is too big to fit") |
| 469 | |
| 470 | self.fru_text = text |
| 471 | if not args.gen_err_valid_bits: |
| 472 | self.validation_bits |= self.VALIDATION_BITS["fru-text"] |
| 473 | |
| 474 | if args.timestamp: |
| 475 | time = args.timestamp |
| 476 | century = int(time.year / 100) |
| 477 | |
| 478 | bcd = bytearray() |
| 479 | util.data_add(bcd, (time.second // 10) << 4 | (time.second % 10), 1) |
| 480 | util.data_add(bcd, (time.minute // 10) << 4 | (time.minute % 10), 1) |
| 481 | util.data_add(bcd, (time.hour // 10) << 4 | (time.hour % 10), 1) |
| 482 | |
| 483 | if args.precise: |
| 484 | util.data_add(bcd, 1, 1) |
| 485 | else: |
| 486 | util.data_add(bcd, 0, 1) |
| 487 | |
| 488 | util.data_add(bcd, (time.day // 10) << 4 | (time.day % 10), 1) |
| 489 | util.data_add(bcd, (time.month // 10) << 4 | (time.month % 10), 1) |
| 490 | util.data_add(bcd, |
| 491 | ((time.year % 100) // 10) << 4 | (time.year % 10), 1) |
| 492 | util.data_add(bcd, ((century % 100) // 10) << 4 | (century % 10), 1) |
| 493 | |
| 494 | self.timestamp = bcd |
| 495 | if not args.gen_err_valid_bits: |
| 496 | self.validation_bits |= self.VALIDATION_BITS["timestamp"] |
| 497 | |
| 498 | if args.gen_err_valid_bits: |
| 499 | self.validation_bits = util.get_choice(name="validation", |
| 500 | value=args.gen_err_valid_bits, |
| 501 | choices=self.VALIDATION_BITS) |
| 502 | |
| 503 | def __init__(self, host, port, debug=False): |
| 504 | """Initialize variables used by the QMP send logic""" |
| 505 | |
| 506 | self.connected = False |
| 507 | self.host = host |
| 508 | self.port = port |
| 509 | self.debug = debug |
| 510 | |
| 511 | # ACPI 6.1: 18.3.2.7.1 Generic Error Data: Generic Error Status Block |
| 512 | self.block_status = self.BLOCK_STATUS_BITS["uncorrectable"] |
| 513 | self.raw_data = [] |
| 514 | self.error_severity = self.ERROR_SEVERITY["recoverable"] |
| 515 | |
| 516 | # ACPI 6.1: 18.3.2.7.1 Generic Error Data: Generic Error Data Entry |
| 517 | self.validation_bits = 0 |
| 518 | self.flags = 0 |
| 519 | self.fru_id = bytearray(16) |
| 520 | self.fru_text = bytearray(20) |
| 521 | self.timestamp = bytearray(8) |
| 522 | |
| 523 | self.qmp_monitor = QEMUMonitorProtocol(address=(self.host, self.port)) |
| 524 | |
| 525 | # |
| 526 | # Socket QMP send command |
| 527 | # |
| 528 | def send_cper_raw(self, cper_data): |
| 529 | """Send a raw CPER data to QEMU though QMP TCP socket""" |
| 530 | |
| 531 | data = b64encode(bytes(cper_data)).decode('ascii') |
| 532 | |
| 533 | cmd_arg = { |
| 534 | 'cper': data |
| 535 | } |
| 536 | |
| 537 | self._connect() |
| 538 | |
| 539 | if self.send_cmd("inject-ghes-v2-error", cmd_arg): |
| 540 | print("Error injected.") |
| 541 | |
| 542 | def send_cper(self, notif_type, payload): |
| 543 | """Send commands to QEMU though QMP TCP socket""" |
| 544 | |
| 545 | # Fill CPER record header |
| 546 | |
| 547 | # NOTE: bits 4 to 13 of block status contain the number of |
| 548 | # data entries in the data section. This is currently unsupported. |
| 549 | |
| 550 | cper_length = len(payload) |
| 551 | data_length = cper_length + len(self.raw_data) + self.GENERIC_DATA_SIZE |
| 552 | |
| 553 | # Generic Error Data Entry |
| 554 | gede = bytearray() |
| 555 | |
| 556 | gede.extend(notif_type.to_bytes()) |
| 557 | util.data_add(gede, self.error_severity, 4) |
| 558 | util.data_add(gede, 0x300, 2) |
| 559 | util.data_add(gede, self.validation_bits, 1) |
| 560 | util.data_add(gede, self.flags, 1) |
| 561 | util.data_add(gede, cper_length, 4) |
| 562 | gede.extend(self.fru_id) |
| 563 | gede.extend(self.fru_text) |
| 564 | gede.extend(self.timestamp) |
| 565 | |
| 566 | # Generic Error Status Block |
| 567 | gebs = bytearray() |
| 568 | |
| 569 | if self.raw_data: |
| 570 | raw_data_offset = len(gebs) |
| 571 | else: |
| 572 | raw_data_offset = 0 |
| 573 | |
| 574 | util.data_add(gebs, self.block_status, 4) |
| 575 | util.data_add(gebs, raw_data_offset, 4) |
| 576 | util.data_add(gebs, len(self.raw_data), 4) |
| 577 | util.data_add(gebs, data_length, 4) |
| 578 | util.data_add(gebs, self.error_severity, 4) |
| 579 | |
| 580 | cper_data = bytearray() |
| 581 | cper_data.extend(gebs) |
| 582 | cper_data.extend(gede) |
| 583 | cper_data.extend(bytearray(self.raw_data)) |
| 584 | cper_data.extend(bytearray(payload)) |
| 585 | |
| 586 | if self.debug: |
| 587 | print(f"GUID: {notif_type}") |
| 588 | |
| 589 | util.dump_bytearray("Generic Error Status Block", gebs) |
| 590 | util.dump_bytearray("Generic Error Data Entry", gede) |
| 591 | |
| 592 | if self.raw_data: |
| 593 | util.dump_bytearray("Raw data", bytearray(self.raw_data)) |
| 594 | |
| 595 | util.dump_bytearray("Payload", payload) |
| 596 | |
| 597 | self.send_cper_raw(cper_data) |
| 598 | |
| 599 | |
| 600 | def search_qom(self, path, prop, regex): |
| 601 | """ |
| 602 | Return a list of devices that match path array like: |
| 603 | |
| 604 | /machine/unattached/device |
| 605 | /machine/peripheral-anon/device |
| 606 | ... |
| 607 | """ |
| 608 | |
| 609 | found = [] |
| 610 | |
| 611 | i = 0 |
| 612 | while 1: |
| 613 | dev = f"{path}[{i}]" |
| 614 | args = { |
| 615 | 'path': dev, |
| 616 | 'property': prop |
| 617 | } |
| 618 | ret = self.send_cmd("qom-get", args, may_open=True, |
| 619 | return_error=False) |
| 620 | if not ret: |
| 621 | break |
| 622 | |
| 623 | if isinstance(ret, str): |
| 624 | if regex.search(ret): |
| 625 | found.append(dev) |
| 626 | |
| 627 | i += 1 |
| 628 | if i > 10000: |
| 629 | print("Too many objects returned by qom-get!") |
| 630 | break |
| 631 | |
| 632 | return found |
| 633 | |
| 634 | class cper_guid: |
| 635 | """ |
| 636 | Contains CPER GUID, as per: |
| 637 | https://uefi.org/specs/UEFI/2.10/Apx_N_Common_Platform_Error_Record.html |
| 638 | """ |
| 639 | |
| 640 | CPER_PROC_GENERIC = guid(0x9876CCAD, 0x47B4, 0x4bdb, |
| 641 | [0xB6, 0x5E, 0x16, 0xF1, |
| 642 | 0x93, 0xC4, 0xF3, 0xDB]) |
| 643 | |
| 644 | CPER_PROC_X86 = guid(0xDC3EA0B0, 0xA144, 0x4797, |
| 645 | [0xB9, 0x5B, 0x53, 0xFA, |
| 646 | 0x24, 0x2B, 0x6E, 0x1D]) |
| 647 | |
| 648 | CPER_PROC_ITANIUM = guid(0xe429faf1, 0x3cb7, 0x11d4, |
| 649 | [0xbc, 0xa7, 0x00, 0x80, |
| 650 | 0xc7, 0x3c, 0x88, 0x81]) |
| 651 | |
| 652 | CPER_PROC_ARM = guid(0xE19E3D16, 0xBC11, 0x11E4, |
| 653 | [0x9C, 0xAA, 0xC2, 0x05, |
| 654 | 0x1D, 0x5D, 0x46, 0xB0]) |
| 655 | |
| 656 | CPER_PLATFORM_MEM = guid(0xA5BC1114, 0x6F64, 0x4EDE, |
| 657 | [0xB8, 0x63, 0x3E, 0x83, |
| 658 | 0xED, 0x7C, 0x83, 0xB1]) |
| 659 | |
| 660 | CPER_PLATFORM_MEM2 = guid(0x61EC04FC, 0x48E6, 0xD813, |
| 661 | [0x25, 0xC9, 0x8D, 0xAA, |
| 662 | 0x44, 0x75, 0x0B, 0x12]) |
| 663 | |
| 664 | CPER_PCIE = guid(0xD995E954, 0xBBC1, 0x430F, |
| 665 | [0xAD, 0x91, 0xB4, 0x4D, |
| 666 | 0xCB, 0x3C, 0x6F, 0x35]) |
| 667 | |
| 668 | CPER_PCI_BUS = guid(0xC5753963, 0x3B84, 0x4095, |
| 669 | [0xBF, 0x78, 0xED, 0xDA, |
| 670 | 0xD3, 0xF9, 0xC9, 0xDD]) |
| 671 | |
| 672 | CPER_PCI_DEV = guid(0xEB5E4685, 0xCA66, 0x4769, |
| 673 | [0xB6, 0xA2, 0x26, 0x06, |
| 674 | 0x8B, 0x00, 0x13, 0x26]) |
| 675 | |
| 676 | CPER_FW_ERROR = guid(0x81212A96, 0x09ED, 0x4996, |
| 677 | [0x94, 0x71, 0x8D, 0x72, |
| 678 | 0x9C, 0x8E, 0x69, 0xED]) |
| 679 | |
| 680 | CPER_DMA_GENERIC = guid(0x5B51FEF7, 0xC79D, 0x4434, |
| 681 | [0x8F, 0x1B, 0xAA, 0x62, |
| 682 | 0xDE, 0x3E, 0x2C, 0x64]) |
| 683 | |
| 684 | CPER_DMA_VT = guid(0x71761D37, 0x32B2, 0x45cd, |
| 685 | [0xA7, 0xD0, 0xB0, 0xFE, |
| 686 | 0xDD, 0x93, 0xE8, 0xCF]) |
| 687 | |
| 688 | CPER_DMA_IOMMU = guid(0x036F84E1, 0x7F37, 0x428c, |
| 689 | [0xA7, 0x9E, 0x57, 0x5F, |
| 690 | 0xDF, 0xAA, 0x84, 0xEC]) |
| 691 | |
| 692 | CPER_CCIX_PER = guid(0x91335EF6, 0xEBFB, 0x4478, |
| 693 | [0xA6, 0xA6, 0x88, 0xB7, |
| 694 | 0x28, 0xCF, 0x75, 0xD7]) |
| 695 | |
| 696 | CPER_CXL_PROT_ERR = guid(0x80B9EFB4, 0x52B5, 0x4DE3, |
| 697 | [0xA7, 0x77, 0x68, 0x78, |
| 698 | 0x4B, 0x77, 0x10, 0x48]) |