| Prachotan Bathi | e1cba52 | 2025-09-18 14:21:54 -0500 | [diff] [blame] | 1 | /** |
| 2 | * See: https://developer.arm.com/documentation/den0085/latest/ |
| 3 | * Minimal parser/generator for ARM RAS CPER section (Table 20/21) |
| 4 | * Author: prachotan.bathi@arm.com |
| 5 | */ |
| 6 | #include <libcper/Cper.h> |
| 7 | #include <stdlib.h> |
| 8 | #include <string.h> |
| 9 | #include <stdbool.h> |
| 10 | #include <inttypes.h> |
| 11 | #include <libcper/base64.h> |
| 12 | #include <libcper/cper-utils.h> |
| 13 | #include <libcper/sections/cper-section-arm-ras.h> |
| 14 | #include <libcper/log.h> |
| 15 | |
| 16 | /* |
| 17 | * Fixed-size fields in EFI_ARM_RAS_NODE. |
| 18 | * |
| 19 | * IPInstance: 16 bytes, serialized as a 32-character hex string. |
| 20 | * IPType: 24 bytes, serialized as a 48-character hex string. |
| 21 | * UserData: 16 bytes, but we emit up to 15 chars to keep a terminator. |
| 22 | */ |
| 23 | static void arm_ras_set_desc_string_valid(char **desc_string) |
| 24 | { |
| 25 | if (desc_string) { |
| 26 | *desc_string = malloc(SECTION_DESC_STRING_SIZE); |
| 27 | if (*desc_string) { |
| 28 | snprintf(*desc_string, SECTION_DESC_STRING_SIZE, |
| 29 | "ARM RAS error occured"); |
| 30 | } |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | static void arm_ras_set_desc_string_invalid(const char *reason, |
| 35 | char **desc_string) |
| 36 | { |
| 37 | if (desc_string) { |
| 38 | *desc_string = malloc(SECTION_DESC_STRING_SIZE); |
| 39 | if (*desc_string) { |
| 40 | snprintf(*desc_string, SECTION_DESC_STRING_SIZE, |
| 41 | "ARM RAS (empty): %s", |
| 42 | reason ? reason : "unspecified"); |
| 43 | } |
| 44 | } |
| 45 | } |
| 46 | |
| 47 | static bool arm_ras_read_node(EFI_ARM_RAS_NODE *node, const UINT8 *section, |
| 48 | UINT32 size, char **desc_string, |
| 49 | json_object **root) |
| 50 | { |
| 51 | char reason[SECTION_DESC_STRING_SIZE]; |
| 52 | *root = json_object_new_object(); |
| 53 | if (size < sizeof(EFI_ARM_RAS_NODE)) { |
| 54 | cper_print_log("ARM RAS section too small: %u < %zu", |
| 55 | (unsigned)size, sizeof(EFI_ARM_RAS_NODE)); |
| 56 | snprintf(reason, sizeof(reason), "invalid/too small %u < %zu", |
| 57 | (unsigned)size, sizeof(EFI_ARM_RAS_NODE)); |
| 58 | arm_ras_set_desc_string_invalid(reason, desc_string); |
| 59 | return false; |
| 60 | } |
| 61 | |
| 62 | memcpy(node, section, sizeof(*node)); |
| 63 | UINT32 descriptorCount = node->ErrorSyndromeArrayNumEntries; |
| 64 | UINT64 descBytes = (UINT64)descriptorCount * |
| 65 | (UINT64)sizeof(EFI_ARM_RAS_ERROR_RECORD_DESCRIPTOR); |
| 66 | if (descBytes > (UINT64)(size - node->ErrorSyndromeArrayOffset)) { |
| 67 | cper_print_log("ARM RAS descriptor array out of range"); |
| 68 | snprintf(reason, sizeof(reason), |
| 69 | "descriptor array out of range"); |
| 70 | arm_ras_set_desc_string_invalid(reason, desc_string); |
| 71 | return false; |
| 72 | } |
| 73 | if (node->Revision != 1) { |
| 74 | cper_print_log("Unsupported ARM RAS revision: %u", |
| 75 | node->Revision); |
| 76 | snprintf(reason, sizeof(reason), |
| 77 | "unsupported ARM RAS revision: %u", node->Revision); |
| 78 | arm_ras_set_desc_string_invalid(reason, desc_string); |
| 79 | return false; |
| 80 | } |
| 81 | |
| 82 | if (node->ErrorSyndromeArrayOffset < sizeof(EFI_ARM_RAS_NODE) || |
| 83 | node->ErrorSyndromeArrayOffset >= size || |
| 84 | (node->AuxiliaryDataOffset && node->AuxiliaryDataOffset >= size)) { |
| 85 | cper_print_log("Invalid ARM RAS offsets"); |
| 86 | snprintf(reason, sizeof(reason), "invalid ARM RAS offsets"); |
| 87 | arm_ras_set_desc_string_invalid(reason, desc_string); |
| 88 | return false; |
| 89 | } |
| 90 | |
| 91 | return true; |
| 92 | } |
| 93 | |
| 94 | static const char *ipInstanceFormat[] = { "PE", "SystemPhysicalAddress", |
| 95 | "LocalAddressIdentifier", |
| 96 | "SocSpecificIpIdentifier" }; |
| 97 | |
| 98 | static const char *componentType[] = { |
| 99 | "ProcessorErrorNode", "MemoryErrorNode", "SMMUErrorNode", |
| 100 | "VendorDefinedErrorNode", "GICErrorNode", "PciExpressErrorNode", |
| 101 | "ProxyErrorNode", |
| 102 | }; |
| 103 | |
| 104 | static void arm_ras_add_fixed_fields(json_object *root, |
| 105 | const EFI_ARM_RAS_NODE *node) |
| 106 | { |
| Ed Tanous | 6c5d2f3 | 2026-02-02 15:18:15 -0800 | [diff] [blame] | 107 | add_uint(root, "revision", node->Revision); |
| Prachotan Bathi | e1cba52 | 2025-09-18 14:21:54 -0500 | [diff] [blame] | 108 | add_dict(root, "componentType", node->ComponentType, componentType, |
| 109 | sizeof(componentType) / sizeof(componentType[0])); |
| Ed Tanous | 6c5d2f3 | 2026-02-02 15:18:15 -0800 | [diff] [blame] | 110 | add_uint(root, "errorSyndromeArrayNumEntries", |
| 111 | node->ErrorSyndromeArrayNumEntries); |
| Prachotan Bathi | e1cba52 | 2025-09-18 14:21:54 -0500 | [diff] [blame] | 112 | |
| 113 | add_dict(root, "ipInstanceFormat", node->IPInstanceFormat, |
| 114 | ipInstanceFormat, |
| 115 | sizeof(ipInstanceFormat) / sizeof(ipInstanceFormat[0])); |
| 116 | |
| 117 | json_object *ipInstance = json_object_new_object(); |
| 118 | switch (node->IPInstanceFormat) { |
| 119 | case 0: |
| 120 | add_int_hex_64(ipInstance, "mpidrEl1", |
| 121 | node->IPInstance.pe.MPIDR_EL1); |
| 122 | break; |
| 123 | case 1: |
| 124 | add_int_hex_64(ipInstance, "systemPhysicalAddress", |
| 125 | node->IPInstance.systemPhysicalAddress |
| 126 | .SystemPhysicalAddress); |
| 127 | break; |
| 128 | case 2: |
| 129 | add_int_hex_64(ipInstance, "localAddressIdentifier", |
| 130 | node->IPInstance.localAddressIdentifier |
| 131 | .SocSpecificLocalAddressSpace); |
| 132 | add_int_hex_64( |
| 133 | ipInstance, "baseAddress", |
| 134 | node->IPInstance.localAddressIdentifier.BaseAddress); |
| 135 | break; |
| 136 | case 3: |
| Ed Tanous | 6c5d2f3 | 2026-02-02 15:18:15 -0800 | [diff] [blame] | 137 | add_string(ipInstance, "socSpecificIpIdentifier", |
| 138 | "<OpaqueData>"); |
| Prachotan Bathi | e1cba52 | 2025-09-18 14:21:54 -0500 | [diff] [blame] | 139 | break; |
| 140 | } |
| 141 | json_object_object_add(root, "ipInstance", ipInstance); |
| 142 | |
| 143 | const char *ipTypeFormat[] = { "PE", "SMMU_IIDR", "GIC_IIDR", "PIDR" }; |
| 144 | add_dict(root, "ipTypeFormat", node->IPTypeFormat, ipTypeFormat, |
| 145 | sizeof(ipTypeFormat) / sizeof(ipTypeFormat[0])); |
| 146 | |
| 147 | json_object *ipType = json_object_new_object(); |
| 148 | switch (node->IPTypeFormat) { |
| 149 | case 0: |
| 150 | add_int_hex_64(ipType, "midrEl1", |
| 151 | node->IPType.smmuIidr.MIDR_EL1); |
| 152 | add_int_hex_64(ipType, "revidrEl1", |
| 153 | node->IPType.smmuIidr.REVIDR_EL1); |
| 154 | add_int_hex_64(ipType, "aidrEl1", |
| 155 | node->IPType.smmuIidr.AIDR_EL1); |
| 156 | break; |
| 157 | case 1: |
| 158 | add_int_hex_32(ipType, "iidr", node->IPType.gicIidr.IIDR); |
| 159 | add_int_hex_32(ipType, "aidr", node->IPType.gicIidr.AIDR); |
| 160 | break; |
| 161 | case 2: |
| 162 | add_int_hex_8(ipType, "pidr3", node->IPType.pidr.PIDR3); |
| 163 | add_int_hex_8(ipType, "pidr2", node->IPType.pidr.PIDR2); |
| 164 | add_int_hex_8(ipType, "pidr1", node->IPType.pidr.PIDR1); |
| 165 | add_int_hex_8(ipType, "pidr0", node->IPType.pidr.PIDR0); |
| 166 | add_int_hex_8(ipType, "pidr7", node->IPType.pidr.PIDR7); |
| 167 | add_int_hex_8(ipType, "pidr6", node->IPType.pidr.PIDR6); |
| 168 | add_int_hex_8(ipType, "pidr5", node->IPType.pidr.PIDR5); |
| 169 | add_int_hex_8(ipType, "pidr4", node->IPType.pidr.PIDR4); |
| 170 | break; |
| 171 | } |
| 172 | |
| 173 | json_object_object_add(root, "ipType", ipType); |
| 174 | |
| 175 | add_untrusted_string(root, "userData", (const char *)node->UserData, |
| 176 | sizeof(node->UserData)); |
| 177 | } |
| 178 | |
| 179 | static json_object *arm_ras_parse_descriptors(const UINT8 *section, |
| 180 | const EFI_ARM_RAS_NODE *node, |
| 181 | UINT32 descriptorCount) |
| 182 | { |
| 183 | json_object *descArrObj = json_object_new_array(); |
| 184 | const UINT8 *desc_ptr = section + node->ErrorSyndromeArrayOffset; |
| 185 | |
| 186 | for (UINT32 i = 0; i < descriptorCount; i++) { |
| 187 | const UINT8 *cur = |
| 188 | desc_ptr + |
| 189 | i * sizeof(EFI_ARM_RAS_ERROR_RECORD_DESCRIPTOR); |
| 190 | EFI_ARM_RAS_ERROR_RECORD_DESCRIPTOR d; |
| 191 | memcpy(&d, cur, sizeof(d)); |
| 192 | json_object *desc = json_object_new_object(); |
| Ed Tanous | 6c5d2f3 | 2026-02-02 15:18:15 -0800 | [diff] [blame] | 193 | add_uint(desc, "errorRecordIndex", d.ErrorRecordIndex); |
| 194 | add_uint(desc, "rasExtensionRevisionField", |
| 195 | (d.RasExtensionRevision >> 4) & 0x0F); |
| 196 | add_uint(desc, "rasExtensionArchVersion", |
| 197 | d.RasExtensionRevision & 0x0F); |
| Prachotan Bathi | e1cba52 | 2025-09-18 14:21:54 -0500 | [diff] [blame] | 198 | add_int_hex_64(desc, "errorRecordFeatureRegister", d.ERR_FR); |
| 199 | add_int_hex_64(desc, "errorRecordControlRegister", d.ERR_CTLR); |
| 200 | add_int_hex_64(desc, "errorRecordPrimaryStatusRegister", |
| 201 | d.ERR_STATUS); |
| 202 | add_int_hex_64(desc, "errorRecordAddressRegister", d.ERR_ADDR); |
| 203 | add_int_hex_64(desc, "errorRecordMiscRegister0", d.ERR_MISC0); |
| 204 | add_int_hex_64(desc, "errorRecordMiscRegister1", d.ERR_MISC1); |
| 205 | if (d.RasExtensionRevision) { |
| 206 | add_int_hex_64(desc, "errorRecordMiscRegister2", |
| 207 | d.ERR_MISC2); |
| 208 | add_int_hex_64(desc, "errorRecordMiscRegister3", |
| 209 | d.ERR_MISC3); |
| 210 | } |
| 211 | json_object_array_add(descArrObj, desc); |
| 212 | } |
| 213 | |
| 214 | return descArrObj; |
| 215 | } |
| 216 | |
| 217 | /* |
| 218 | * Validate the fixed-size ARM RAS auxiliary header fields. |
| 219 | */ |
| 220 | static bool arm_ras_aux_hdr_valid(const EFI_ARM_RAS_AUX_DATA_HEADER *auxHdr, |
| 221 | UINT32 auxLen) |
| 222 | { |
| 223 | /* |
| 224 | * KVP array layout: |
| 225 | * - When there are no entries, the offset must be exactly the |
| 226 | * end of the aux blob. |
| 227 | * - When entries are present, the offset must be somewhere inside |
| 228 | * the aux blob (the upper bound is checked here, the lower |
| 229 | * bound below). |
| 230 | */ |
| 231 | bool kvOffsetValid = |
| 232 | ((auxHdr->KeyValuePairArrayEntryCount == 0 && |
| 233 | auxHdr->KeyValuePairArrayOffset == |
| 234 | auxHdr->AuxiliaryDataSize) || |
| 235 | (auxHdr->KeyValuePairArrayOffset < auxHdr->AuxiliaryDataSize)); |
| 236 | /* |
| 237 | * The spec requires (Table 22): |
| 238 | * - Version must be 1 |
| 239 | * - AuxiliaryDataSize is the total size of the aux block, including |
| 240 | * the header itself, and must: |
| 241 | * * fit within the remaining section buffer (<= auxLen) and |
| 242 | * * be at least large enough to hold the header. |
| 243 | */ |
| 244 | return (auxHdr->Version == 1) && |
| 245 | (auxHdr->AuxiliaryDataSize <= auxLen) && |
| 246 | (auxHdr->AuxiliaryDataSize >= |
| 247 | sizeof(EFI_ARM_RAS_AUX_DATA_HEADER)) && |
| 248 | kvOffsetValid && |
| 249 | (auxHdr->KeyValuePairArrayOffset >= |
| 250 | sizeof(EFI_ARM_RAS_AUX_DATA_HEADER)); |
| 251 | } |
| 252 | |
| 253 | static json_object * |
| 254 | arm_ras_aux_emit_header_fields(const EFI_ARM_RAS_AUX_DATA_HEADER *auxHdr) |
| 255 | { |
| 256 | /* Emit auxiliary header fields in spec order (Table 22): |
| 257 | * version, reserved0 (omitted - always zero), addressSpaceArrayEntryCount, |
| 258 | * auxiliaryDataSize, keyValuePairArrayOffset, keyValuePairArrayEntryCount, |
| 259 | * reserved1 (omitted). |
| 260 | */ |
| 261 | json_object *auxStructured = json_object_new_object(); |
| Ed Tanous | 6c5d2f3 | 2026-02-02 15:18:15 -0800 | [diff] [blame] | 262 | add_uint(auxStructured, "version", auxHdr->Version); |
| Prachotan Bathi | e1cba52 | 2025-09-18 14:21:54 -0500 | [diff] [blame] | 263 | return auxStructured; |
| 264 | } |
| 265 | |
| 266 | static bool |
| 267 | arm_ras_aux_parse_contexts(json_object *auxStructured, const UINT8 *aux_ptr, |
| 268 | const EFI_ARM_RAS_AUX_DATA_HEADER *auxHdr) |
| 269 | { |
| 270 | json_object *contexts = json_object_new_array(); |
| 271 | const UINT8 *cursor = aux_ptr + sizeof(EFI_ARM_RAS_AUX_DATA_HEADER); |
| 272 | UINT32 remaining = |
| 273 | auxHdr->AuxiliaryDataSize - sizeof(EFI_ARM_RAS_AUX_DATA_HEADER); |
| 274 | bool ok = true; |
| 275 | |
| 276 | for (UINT16 ci = 0; ci < auxHdr->AddressSpaceArrayEntryCount; ci++) { |
| 277 | if (remaining < sizeof(EFI_ARM_RAS_AUX_CONTEXT_HEADER)) { |
| 278 | ok = false; |
| 279 | cper_print_log( |
| 280 | "ARM RAS Auxiliary Data too small for context header: %u < %zu", |
| 281 | remaining, |
| 282 | sizeof(EFI_ARM_RAS_AUX_CONTEXT_HEADER)); |
| 283 | break; |
| 284 | } |
| 285 | const EFI_ARM_RAS_AUX_CONTEXT_HEADER *ctx = |
| 286 | (const EFI_ARM_RAS_AUX_CONTEXT_HEADER *)cursor; |
| 287 | if (ctx->Length < sizeof(EFI_ARM_RAS_AUX_CONTEXT_HEADER)) { |
| 288 | ok = false; |
| 289 | cper_print_log( |
| 290 | "ARM RAS Auxiliary Context length too small: %u < %zu", |
| 291 | ctx->Length, |
| 292 | sizeof(EFI_ARM_RAS_AUX_CONTEXT_HEADER)); |
| 293 | break; |
| 294 | } |
| 295 | UINT32 needed = sizeof(EFI_ARM_RAS_AUX_CONTEXT_HEADER) + |
| 296 | ctx->RegisterArrayEntryCount * |
| 297 | sizeof(EFI_ARM_RAS_AUX_MM_REG_ENTRY); |
| 298 | if (ctx->Length < needed || needed > remaining) { |
| 299 | ok = false; |
| 300 | cper_print_log( |
| 301 | "ARM RAS Auxiliary Context length too small or exceeds remaining data: %u < %u or %u > %u", |
| 302 | ctx->Length, needed, needed, remaining); |
| 303 | break; |
| 304 | } |
| 305 | UINT32 afterCtxOffset = |
| 306 | (UINT32)(cursor - aux_ptr) + ctx->Length; |
| 307 | if (afterCtxOffset > auxHdr->KeyValuePairArrayOffset) { |
| 308 | ok = false; |
| 309 | cper_print_log( |
| 310 | "ARM RAS Auxiliary Context overlaps KVP array"); |
| 311 | break; |
| 312 | } |
| 313 | |
| 314 | json_object *ctxObjInstance = json_object_new_object(); |
| 315 | json_object *flags = json_object_new_object(); |
| 316 | |
| 317 | static const char *addressSpaceIdentifierScope[2] = { |
| 318 | "SystemPhysicalAddressSpace", |
| 319 | "LocalAddressSpace", |
| 320 | }; |
| 321 | |
| 322 | add_bool_enum(flags, "addressSpaceIdentifierScope", |
| 323 | addressSpaceIdentifierScope, |
| 324 | ctx->AddressSpaceIdentifierScope); |
| 325 | json_object_object_add(ctxObjInstance, "flags", flags); |
| 326 | |
| 327 | if (ctx->AddressSpaceIdentifierScope == 1) { |
| Ed Tanous | 6c5d2f3 | 2026-02-02 15:18:15 -0800 | [diff] [blame] | 328 | add_uint(ctxObjInstance, "addressSpaceIdentifier", |
| 329 | (UINT64)ctx->AddressSpaceIdentifier); |
| Prachotan Bathi | e1cba52 | 2025-09-18 14:21:54 -0500 | [diff] [blame] | 330 | } |
| 331 | |
| 332 | json_object *regs = json_object_new_array(); |
| 333 | const EFI_ARM_RAS_AUX_MM_REG_ENTRY *regArr = |
| 334 | (const EFI_ARM_RAS_AUX_MM_REG_ENTRY |
| 335 | *)(cursor + |
| 336 | sizeof(EFI_ARM_RAS_AUX_CONTEXT_HEADER)); |
| 337 | for (UINT16 ri = 0; ri < ctx->RegisterArrayEntryCount; ri++) { |
| 338 | json_object *r = json_object_new_object(); |
| 339 | add_int_hex_64(r, "address", |
| 340 | regArr[ri].RegisterAddress); |
| 341 | add_int_hex_64(r, "value", regArr[ri].RegisterValue); |
| 342 | json_object_array_add(regs, r); |
| 343 | } |
| 344 | json_object_object_add(ctxObjInstance, "registers", regs); |
| 345 | json_object_array_add(contexts, ctxObjInstance); |
| 346 | |
| 347 | cursor += ctx->Length; |
| 348 | remaining -= ctx->Length; |
| 349 | } |
| 350 | |
| 351 | if (ok) { |
| 352 | json_object_object_add(auxStructured, "contexts", contexts); |
| 353 | } |
| 354 | |
| 355 | return ok; |
| 356 | } |
| 357 | |
| 358 | int is_mpam(EFI_GUID *key) |
| 359 | { |
| 360 | if (guid_equal(key, &EFI_ARM_RAS_KVP_UUID_MPAM_PARTID)) { |
| 361 | return 1; |
| 362 | } |
| 363 | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
| 364 | // The exact byte used here is arbitrary. |
| 365 | return key->Data4[0] % 2; |
| 366 | #endif |
| 367 | return 0; |
| 368 | } |
| 369 | |
| 370 | static void arm_ras_aux_parse_kvps(json_object *auxStructured, |
| 371 | const UINT8 *aux_ptr, |
| 372 | EFI_ARM_RAS_AUX_DATA_HEADER *auxHdr) |
| 373 | { |
| 374 | const UINT8 *kvBase = aux_ptr + auxHdr->KeyValuePairArrayOffset; |
| 375 | UINT32 kvAvail = |
| 376 | auxHdr->AuxiliaryDataSize - auxHdr->KeyValuePairArrayOffset; |
| 377 | UINT32 kvNeeded = auxHdr->KeyValuePairArrayEntryCount * |
| 378 | sizeof(EFI_ARM_RAS_AUX_KEY_VALUE_PAIR); |
| 379 | if (kvNeeded > kvAvail) { |
| 380 | return; |
| 381 | } |
| 382 | |
| 383 | json_object *kvps = json_object_new_array(); |
| 384 | EFI_ARM_RAS_AUX_KEY_VALUE_PAIR *kvArr = |
| 385 | (EFI_ARM_RAS_AUX_KEY_VALUE_PAIR *)kvBase; |
| 386 | for (UINT16 ki = 0; ki < auxHdr->KeyValuePairArrayEntryCount; ki++) { |
| 387 | json_object *kv = json_object_new_object(); |
| 388 | EFI_ARM_RAS_AUX_KEY_VALUE_PAIR *kvEntry = &kvArr[ki]; |
| 389 | EFI_GUID key = kvEntry->Key; |
| 390 | add_guid(kv, "key", &key); |
| 391 | |
| 392 | if (is_mpam(&key)) { |
| 393 | UINT16 partId = (UINT16)(kvEntry->Value & 0xFFFF); |
| Ed Tanous | 6c5d2f3 | 2026-02-02 15:18:15 -0800 | [diff] [blame] | 394 | add_uint(kv, "mpamPartId", partId); |
| Prachotan Bathi | e1cba52 | 2025-09-18 14:21:54 -0500 | [diff] [blame] | 395 | } else { |
| 396 | add_int_hex_64(kv, "value", kvEntry->Value); |
| 397 | } |
| 398 | json_object_array_add(kvps, kv); |
| 399 | } |
| 400 | json_object_object_add(auxStructured, "keyValuePairs", kvps); |
| 401 | } |
| 402 | |
| 403 | static json_object *arm_ras_parse_aux_data(const UINT8 *section, UINT32 size, |
| 404 | const EFI_ARM_RAS_NODE *node, |
| 405 | UINT64 descBytes) |
| 406 | { |
| 407 | if (!node->AuxiliaryDataOffset) { |
| 408 | return NULL; |
| 409 | } |
| 410 | |
| 411 | const UINT8 *aux_ptr = section + node->AuxiliaryDataOffset; |
| 412 | if (node->AuxiliaryDataOffset < sizeof(EFI_ARM_RAS_NODE) + descBytes) { |
| 413 | cper_print_log("ARM RAS aux offset overlaps descriptors"); |
| 414 | return NULL; |
| 415 | } |
| 416 | |
| 417 | UINT32 auxLen = size - node->AuxiliaryDataOffset; |
| 418 | if (auxLen < sizeof(EFI_ARM_RAS_AUX_DATA_HEADER)) { |
| 419 | cper_print_log("ARM RAS Auxiliary Data too small: %u < %zu", |
| 420 | auxLen, sizeof(EFI_ARM_RAS_AUX_DATA_HEADER)); |
| 421 | return NULL; |
| 422 | } |
| 423 | |
| 424 | EFI_ARM_RAS_AUX_DATA_HEADER *auxHdr = |
| 425 | (EFI_ARM_RAS_AUX_DATA_HEADER *)aux_ptr; |
| 426 | if (!arm_ras_aux_hdr_valid(auxHdr, auxLen)) { |
| 427 | cper_print_log( |
| 428 | "Invalid ARM RAS auxiliary header: version=%u, auxSize=%u, kvOffset=%u, kvCount=%u", |
| 429 | auxHdr->Version, auxHdr->AuxiliaryDataSize, |
| 430 | auxHdr->KeyValuePairArrayOffset, |
| 431 | auxHdr->KeyValuePairArrayEntryCount); |
| 432 | return NULL; |
| 433 | } |
| 434 | |
| 435 | json_object *auxStructured = arm_ras_aux_emit_header_fields(auxHdr); |
| 436 | if (!arm_ras_aux_parse_contexts(auxStructured, aux_ptr, auxHdr)) { |
| 437 | return auxStructured; |
| 438 | } |
| 439 | arm_ras_aux_parse_kvps(auxStructured, aux_ptr, auxHdr); |
| 440 | return auxStructured; |
| 441 | } |
| 442 | |
| 443 | json_object *cper_section_arm_ras_to_ir(const UINT8 *section, UINT32 size, |
| 444 | char **desc_string) |
| 445 | { |
| 446 | EFI_ARM_RAS_NODE node; |
| 447 | json_object *root = NULL; |
| 448 | if (!arm_ras_read_node(&node, section, size, desc_string, &root)) { |
| 449 | return root; |
| 450 | } |
| 451 | |
| 452 | UINT32 descriptorCount = node.ErrorSyndromeArrayNumEntries; |
| 453 | UINT64 descBytes = (UINT64)descriptorCount * |
| 454 | (UINT64)sizeof(EFI_ARM_RAS_ERROR_RECORD_DESCRIPTOR); |
| 455 | |
| 456 | arm_ras_set_desc_string_valid(desc_string); |
| 457 | arm_ras_add_fixed_fields(root, &node); |
| 458 | |
| 459 | json_object *descArray = |
| 460 | arm_ras_parse_descriptors(section, &node, descriptorCount); |
| 461 | json_object_object_add(root, "errorSyndromes", descArray); |
| 462 | |
| 463 | json_object *auxStructured = |
| 464 | arm_ras_parse_aux_data(section, size, &node, descBytes); |
| 465 | |
| 466 | if (auxStructured) { |
| 467 | json_object_object_add(root, "auxData", auxStructured); |
| 468 | } |
| 469 | |
| 470 | return root; |
| 471 | } |
| 472 | |
| 473 | static void arm_ras_fill_node_fixed_fields(EFI_ARM_RAS_NODE *node, |
| 474 | json_object *section) |
| 475 | { |
| 476 | json_object *obj = NULL; |
| 477 | memset(node, 0, sizeof(*node)); |
| 478 | if (json_object_object_get_ex(section, "revision", &obj)) { |
| 479 | node->Revision = (UINT32)json_object_get_uint64(obj); |
| 480 | } |
| 481 | if (json_object_object_get_ex(section, "componentType", &obj)) { |
| 482 | if (json_object_object_get_ex(obj, "raw", &obj)) { |
| 483 | node->ComponentType = (UINT8)json_object_get_int(obj); |
| 484 | } |
| 485 | } |
| 486 | if (json_object_object_get_ex(section, "ipInstanceFormat", &obj)) { |
| 487 | if (json_object_object_get_ex(obj, "raw", &obj)) { |
| 488 | node->IPInstanceFormat = |
| 489 | (UINT8)json_object_get_int(obj); |
| 490 | } |
| 491 | } |
| 492 | if (json_object_object_get_ex(section, "ipTypeFormat", &obj)) { |
| 493 | if (json_object_object_get_ex(obj, "raw", &obj)) { |
| 494 | node->IPTypeFormat = (UINT8)json_object_get_int(obj); |
| 495 | } |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | static void arm_ras_fill_node_identifiers(EFI_ARM_RAS_NODE *node, |
| 500 | json_object *section) |
| 501 | { |
| 502 | json_object *obj = NULL; |
| 503 | if (json_object_object_get_ex(section, "ipInstance", &obj)) { |
| 504 | get_value_hex_64(obj, "mpidrEl1", |
| 505 | &node->IPInstance.pe.MPIDR_EL1); |
| 506 | get_value_hex_64(obj, "systemPhysicalAddress", |
| 507 | &node->IPInstance.systemPhysicalAddress |
| 508 | .SystemPhysicalAddress); |
| 509 | get_value_hex_64(obj, "localAddressIdentifier", |
| 510 | &node->IPInstance.localAddressIdentifier |
| 511 | .SocSpecificLocalAddressSpace); |
| 512 | get_value_hex_64( |
| 513 | obj, "baseAddress", |
| 514 | &node->IPInstance.localAddressIdentifier.BaseAddress); |
| 515 | |
| 516 | //get_value_hex_64(obj, "socSpecificIpIdentifier", &node->IPInstance.socSpecificIpIdentifier.SocSpecificIPIdentifier); |
| 517 | } |
| 518 | if (json_object_object_get_ex(section, "ipType", &obj)) { |
| 519 | get_value_hex_64(obj, "midrEl1", |
| 520 | &node->IPType.smmuIidr.MIDR_EL1); |
| 521 | get_value_hex_64(obj, "revidrEl1", |
| 522 | &node->IPType.smmuIidr.REVIDR_EL1); |
| 523 | get_value_hex_64(obj, "aidrEl1", |
| 524 | &node->IPType.smmuIidr.AIDR_EL1); |
| 525 | get_value_hex_32(obj, "iidr", &node->IPType.gicIidr.IIDR); |
| 526 | get_value_hex_32(obj, "aidr", &node->IPType.gicIidr.AIDR); |
| 527 | get_value_hex_8(obj, "pidr3", &node->IPType.pidr.PIDR3); |
| 528 | get_value_hex_8(obj, "pidr2", &node->IPType.pidr.PIDR2); |
| 529 | get_value_hex_8(obj, "pidr1", &node->IPType.pidr.PIDR1); |
| 530 | get_value_hex_8(obj, "pidr0", &node->IPType.pidr.PIDR0); |
| 531 | get_value_hex_8(obj, "pidr7", &node->IPType.pidr.PIDR7); |
| 532 | get_value_hex_8(obj, "pidr6", &node->IPType.pidr.PIDR6); |
| 533 | get_value_hex_8(obj, "pidr5", &node->IPType.pidr.PIDR5); |
| 534 | get_value_hex_8(obj, "pidr4", &node->IPType.pidr.PIDR4); |
| 535 | } |
| 536 | } |
| 537 | |
| 538 | static void arm_ras_fill_node_user_data(EFI_ARM_RAS_NODE *node, |
| 539 | json_object *section) |
| 540 | { |
| 541 | json_object *t = NULL; |
| 542 | if (json_object_object_get_ex(section, "userData", &t)) { |
| 543 | const char *s = json_object_get_string(t); |
| 544 | int len = cper_printable_string_length(s, strlen(s) + 1); |
| 545 | |
| 546 | if (len < 0) { |
| 547 | cper_print_log("ARM RAS user data invalid len=%d", len); |
| 548 | return; |
| 549 | } |
| 550 | if ((size_t)len > sizeof(node->UserData)) { |
| 551 | cper_print_log("ARM RAS user data too long: %d > %zu", |
| 552 | len, sizeof(node->UserData)); |
| 553 | return; |
| 554 | } |
| 555 | memcpy(node->UserData, s, len); |
| 556 | node->UserData[len] = 0; |
| 557 | } |
| 558 | } |
| 559 | |
| 560 | static void arm_ras_init_descriptor_metadata(EFI_ARM_RAS_NODE *node, |
| 561 | json_object *section, |
| 562 | json_object **descArr, |
| 563 | UINT32 *descCount, |
| 564 | UINT32 *afterDescriptors) |
| 565 | { |
| 566 | *descArr = NULL; |
| 567 | *descCount = 0; |
| 568 | if (json_object_object_get_ex(section, "errorSyndromes", descArr)) { |
| 569 | *descCount = json_object_array_length(*descArr); |
| 570 | } |
| 571 | if (*descCount > 896) { |
| 572 | /* |
| 573 | * Per the RAS System Architecture (Arm IHI0100), the error_syndrome_array |
| 574 | * has at most 896 entries. Clamp larger inputs to avoid emitting |
| 575 | * non-architectural records. |
| 576 | */ |
| 577 | cper_print_log( |
| 578 | "ARM RAS error_syndrome_array entry count too large: %u > 896; clamping to 0", |
| 579 | (unsigned)*descCount); |
| 580 | *descCount = 0; |
| 581 | } |
| 582 | /* Compute offsets */ |
| 583 | node->ErrorSyndromeArrayOffset = (UINT16)sizeof(EFI_ARM_RAS_NODE); |
| 584 | *afterDescriptors = |
| 585 | sizeof(EFI_ARM_RAS_ERROR_RECORD_DESCRIPTOR) * (*descCount) + |
| 586 | node->ErrorSyndromeArrayOffset; |
| 587 | } |
| 588 | |
| 589 | static void arm_ras_build_aux_contexts(UINT8 *builtAux, UINT16 ctxCount, |
| 590 | json_object *contextsArr, |
| 591 | UINT32 headerSize) |
| 592 | { |
| 593 | UINT8 *cursor = builtAux + headerSize; |
| 594 | for (UINT16 ci = 0; ci < ctxCount; ci++) { |
| 595 | json_object *ctx = json_object_array_get_idx(contextsArr, ci); |
| 596 | if (!ctx) { |
| 597 | continue; |
| 598 | } |
| 599 | json_object *regsArr = NULL; |
| 600 | json_object_object_get_ex(ctx, "registers", ®sArr); |
| 601 | UINT16 regCount = |
| 602 | regsArr ? (UINT16)json_object_array_length(regsArr) : 0; |
| 603 | UINT32 length = (UINT32)sizeof(EFI_ARM_RAS_AUX_CONTEXT_HEADER) + |
| 604 | regCount * sizeof(EFI_ARM_RAS_AUX_MM_REG_ENTRY); |
| 605 | EFI_ARM_RAS_AUX_CONTEXT_HEADER *ch = |
| 606 | (EFI_ARM_RAS_AUX_CONTEXT_HEADER *)cursor; |
| 607 | ch->Length = length; |
| 608 | json_object *flagsObj = NULL; |
| 609 | json_object_object_get_ex(ctx, "flags", &flagsObj); |
| 610 | json_object *addressSpaceIdentifierScopeObj = NULL; |
| 611 | json_object_object_get_ex(flagsObj, |
| 612 | "addressSpaceIdentifierScope", |
| 613 | &addressSpaceIdentifierScopeObj); |
| 614 | const char *scope = |
| 615 | json_object_get_string(addressSpaceIdentifierScopeObj); |
| 616 | if (strcmp(scope, "LocalAddressSpace") == 0) { |
| 617 | ch->AddressSpaceIdentifierScope = 1; |
| 618 | } |
| 619 | ch->AddressSpaceIdentifierScope = |
| 620 | addressSpaceIdentifierScopeObj ? |
| 621 | (UINT8)json_object_get_int( |
| 622 | addressSpaceIdentifierScopeObj) : |
| 623 | 0; |
| 624 | ch->Reserved0 = 0; |
| 625 | ch->RegisterArrayEntryCount = regCount; |
| 626 | json_object *asidObj = NULL; |
| 627 | json_object_object_get_ex(ctx, "addressSpaceIdentifier", |
| 628 | &asidObj); |
| 629 | memset(ch->Reserved1, 0, sizeof(ch->Reserved1)); |
| 630 | EFI_ARM_RAS_AUX_MM_REG_ENTRY *regEntries = |
| 631 | (EFI_ARM_RAS_AUX_MM_REG_ENTRY |
| 632 | *)(cursor + |
| 633 | sizeof(EFI_ARM_RAS_AUX_CONTEXT_HEADER)); |
| 634 | for (UINT16 ri = 0; ri < regCount; ri++) { |
| 635 | json_object *r = json_object_array_get_idx(regsArr, ri); |
| 636 | if (!r) { |
| 637 | regEntries[ri].RegisterAddress = 0; |
| 638 | regEntries[ri].RegisterValue = 0; |
| 639 | continue; |
| 640 | } |
| 641 | get_value_hex_64(r, "address", |
| 642 | ®Entries[ri].RegisterAddress); |
| 643 | get_value_hex_64(r, "value", |
| 644 | ®Entries[ri].RegisterValue); |
| 645 | } |
| 646 | cursor += length; |
| 647 | } |
| 648 | } |
| 649 | |
| 650 | static void arm_ras_build_aux_kvps(UINT8 *builtAux, UINT16 kvpCount, |
| 651 | json_object *kvpArr, UINT32 kvpOffset) |
| 652 | { |
| 653 | EFI_ARM_RAS_AUX_KEY_VALUE_PAIR *kvOut = |
| 654 | (EFI_ARM_RAS_AUX_KEY_VALUE_PAIR *)(builtAux + kvpOffset); |
| 655 | for (UINT16 ki = 0; ki < kvpCount; ki++) { |
| 656 | json_object *kv = json_object_array_get_idx(kvpArr, ki); |
| 657 | if (!kv) { |
| 658 | continue; |
| 659 | } |
| 660 | json_object *keyObj = NULL; |
| 661 | json_object_object_get_ex(kv, "key", &keyObj); |
| 662 | const char *key = json_object_get_string(keyObj); |
| 663 | if (key) { |
| 664 | string_to_guid(&kvOut[ki].Key, key); |
| 665 | } |
| 666 | get_value_hex_64(kv, "value", &kvOut[ki].Value); |
| 667 | json_object *mpamPartIdObj = NULL; |
| 668 | if (json_object_object_get_ex(kv, "mpamPartId", |
| 669 | &mpamPartIdObj)) { |
| 670 | kvOut[ki].Value = |
| 671 | (UINT64)json_object_get_uint64(mpamPartIdObj); |
| 672 | } |
| 673 | } |
| 674 | } |
| 675 | |
| 676 | static void arm_ras_build_aux_blob(json_object *auxStructured, UINT8 **builtAux, |
| 677 | UINT32 *builtAuxLen) |
| 678 | { |
| 679 | *builtAux = NULL; |
| 680 | *builtAuxLen = 0; |
| 681 | if (!auxStructured) { |
| 682 | return; |
| 683 | } |
| 684 | |
| 685 | json_object *contextsArr = NULL; |
| 686 | json_object_object_get_ex(auxStructured, "contexts", &contextsArr); |
| 687 | json_object *kvpArr = NULL; |
| 688 | json_object_object_get_ex(auxStructured, "keyValuePairs", &kvpArr); |
| 689 | UINT16 ctxCount = |
| 690 | contextsArr ? (UINT16)json_object_array_length(contextsArr) : 0; |
| 691 | UINT16 kvpCount = kvpArr ? (UINT16)json_object_array_length(kvpArr) : 0; |
| 692 | /* First compute size of contexts region */ |
| 693 | UINT32 contextsSize = 0; |
| 694 | for (UINT16 i = 0; i < ctxCount; i++) { |
| 695 | json_object *ctx = json_object_array_get_idx(contextsArr, i); |
| 696 | if (!ctx) { |
| 697 | continue; |
| 698 | } |
| 699 | json_object *regsArr = NULL; |
| 700 | json_object_object_get_ex(ctx, "registers", ®sArr); |
| 701 | UINT16 regCount = |
| 702 | regsArr ? (UINT16)json_object_array_length(regsArr) : 0; |
| 703 | UINT32 length = (UINT32)sizeof(EFI_ARM_RAS_AUX_CONTEXT_HEADER) + |
| 704 | regCount * sizeof(EFI_ARM_RAS_AUX_MM_REG_ENTRY); |
| 705 | contextsSize += length; |
| 706 | } |
| 707 | UINT32 headerSize = sizeof(EFI_ARM_RAS_AUX_DATA_HEADER); |
| 708 | UINT32 kvpOffset = |
| 709 | headerSize + contextsSize; /* from start of aux block */ |
| 710 | UINT32 kvpSize = kvpCount * sizeof(EFI_ARM_RAS_AUX_KEY_VALUE_PAIR); |
| 711 | UINT32 auxSize = headerSize + contextsSize + kvpSize; |
| 712 | |
| 713 | /* Per the spec, AuxiliaryDataSize can be up to 2^32, but that's extreme and unrealistic. |
| 714 | * We limit auxSize to 0xFFFF to support reasonable sizes while keeping the code simple. |
| 715 | */ |
| 716 | if (auxSize > 0xFFFF) { |
| 717 | cper_print_log( |
| 718 | "Implementation doesn't support large AuxiliaryDataSize: %u > 0xFFFF", |
| 719 | auxSize); |
| 720 | return; |
| 721 | } |
| 722 | |
| 723 | UINT8 *buf = (UINT8 *)calloc(1, auxSize); |
| 724 | if (!buf) { |
| 725 | return; |
| 726 | } |
| 727 | |
| 728 | EFI_ARM_RAS_AUX_DATA_HEADER *hdr = (EFI_ARM_RAS_AUX_DATA_HEADER *)buf; |
| 729 | hdr->Version = 1; |
| 730 | hdr->Reserved0 = 0; |
| 731 | hdr->AddressSpaceArrayEntryCount = ctxCount; |
| 732 | hdr->AuxiliaryDataSize = auxSize; |
| 733 | hdr->KeyValuePairArrayOffset = kvpOffset; |
| 734 | hdr->KeyValuePairArrayEntryCount = kvpCount; |
| 735 | hdr->Reserved1 = 0; |
| 736 | |
| 737 | /* Write contexts */ |
| 738 | arm_ras_build_aux_contexts(buf, ctxCount, contextsArr, headerSize); |
| 739 | /* Write key-value pairs */ |
| 740 | arm_ras_build_aux_kvps(buf, kvpCount, kvpArr, kvpOffset); |
| 741 | |
| 742 | *builtAux = buf; |
| 743 | *builtAuxLen = auxSize; |
| 744 | } |
| 745 | |
| 746 | static void arm_ras_write_descriptors(json_object *descArr, UINT32 descCount, |
| 747 | FILE *out) |
| 748 | { |
| 749 | for (UINT32 i = 0; i < descCount; i++) { |
| 750 | EFI_ARM_RAS_ERROR_RECORD_DESCRIPTOR d; |
| 751 | memset(&d, 0, sizeof(d)); |
| 752 | json_object *dj = json_object_array_get_idx(descArr, i); |
| 753 | json_object *x = NULL; |
| 754 | if (json_object_object_get_ex(dj, "errorRecordIndex", &x)) { |
| 755 | d.ErrorRecordIndex = json_object_get_uint64(x); |
| 756 | } |
| 757 | /* Reconstruct rasExtensionRevision from split fields */ |
| 758 | UINT8 rev = 0; |
| 759 | UINT8 arch = 0; |
| 760 | if (json_object_object_get_ex(dj, "rasExtensionRevisionField", |
| 761 | &x)) { |
| 762 | rev = (UINT8)json_object_get_uint64(x); |
| 763 | } |
| 764 | if (json_object_object_get_ex(dj, "rasExtensionArchVersion", |
| 765 | &x)) { |
| 766 | arch = (UINT8)json_object_get_uint64(x); |
| 767 | } |
| 768 | d.RasExtensionRevision = ((rev & 0x0F) << 4) | (arch & 0x0F); |
| 769 | get_value_hex_64(dj, "errorRecordFeatureRegister", &d.ERR_FR); |
| 770 | get_value_hex_64(dj, "errorRecordControlRegister", &d.ERR_CTLR); |
| 771 | get_value_hex_64(dj, "errorRecordPrimaryStatusRegister", |
| 772 | &d.ERR_STATUS); |
| 773 | get_value_hex_64(dj, "errorRecordAddressRegister", &d.ERR_ADDR); |
| 774 | get_value_hex_64(dj, "errorRecordMiscRegister0", &d.ERR_MISC0); |
| 775 | get_value_hex_64(dj, "errorRecordMiscRegister1", &d.ERR_MISC1); |
| 776 | get_value_hex_64(dj, "errorRecordMiscRegister2", &d.ERR_MISC2); |
| 777 | get_value_hex_64(dj, "errorRecordMiscRegister3", &d.ERR_MISC3); |
| 778 | fwrite(&d, sizeof(d), 1, out); |
| 779 | } |
| 780 | } |
| 781 | |
| 782 | void ir_section_arm_ras_to_cper(json_object *section, FILE *out) |
| 783 | { |
| 784 | EFI_ARM_RAS_NODE node; |
| 785 | arm_ras_fill_node_fixed_fields(&node, section); |
| 786 | arm_ras_fill_node_identifiers(&node, section); |
| 787 | arm_ras_fill_node_user_data(&node, section); |
| 788 | |
| 789 | json_object *descArr = NULL; |
| 790 | UINT32 descCount = 0; |
| 791 | UINT32 afterDescriptors = 0; |
| 792 | arm_ras_init_descriptor_metadata(&node, section, &descArr, &descCount, |
| 793 | &afterDescriptors); |
| 794 | |
| 795 | json_object *auxStructured = NULL; |
| 796 | json_object_object_get_ex(section, "auxData", &auxStructured); |
| 797 | UINT8 *builtAux = NULL; |
| 798 | UINT32 builtAuxLen = 0; |
| 799 | arm_ras_build_aux_blob(auxStructured, &builtAux, &builtAuxLen); |
| 800 | if (builtAux) { |
| 801 | /* |
| 802 | * Architecturally safe: from the RAS System Architecture (Arm IHI0100), |
| 803 | * the header is 80 bytes and each ErrorSyndromes entry is 72 bytes, |
| 804 | * with at most 896 entries. So the maximum AuxiliaryDataOffset is |
| 805 | * 80 + 896 * 72 < 2^16, |
| 806 | * and fits in the UINT16 field. |
| 807 | */ |
| 808 | node.AuxiliaryDataOffset = (UINT16)afterDescriptors; |
| 809 | } else { |
| 810 | node.AuxiliaryDataOffset = 0; |
| 811 | } |
| 812 | node.ErrorSyndromeArrayNumEntries = descCount; // N |
| 813 | |
| 814 | fwrite(&node, sizeof(node), 1, out); |
| 815 | arm_ras_write_descriptors(descArr, descCount, out); |
| 816 | if (builtAux) { |
| 817 | fwrite(builtAux, builtAuxLen, 1, out); |
| 818 | free(builtAux); |
| 819 | } |
| 820 | fflush(out); |
| 821 | } |