| /** @file | |
| ERST Table Generator | |
| Copyright (c) 2026, Arm Limited. All rights reserved. | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| @par Glossary: | |
| - Cm or CM - Configuration Manager | |
| - Obj or OBJ - Object | |
| @par Reference(s): | |
| - ACPI 6.6 Specification, May 2025 | |
| **/ | |
| #include <AcpiTableGenerator.h> | |
| #include <ConfigurationManagerHelper.h> | |
| #include <ConfigurationManagerObject.h> | |
| #include <IndustryStandard/Acpi66.h> | |
| #include <Library/BaseMemoryLib.h> | |
| #include <Library/DebugLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/TableHelperLib.h> | |
| #include <Protocol/ConfigurationManagerProtocol.h> | |
| #define ERST_RESERVED_SERIALIZATION_ACTION 0x0C | |
| /** Standard ERST Generator | |
| Requirements: | |
| The following Configuration Manager Object is required by this Generator: | |
| - EArchCommonObjErstInstructionsInfo | |
| */ | |
| /** Retrieve the ERST Serialization Instruction Entry information. */ | |
| GET_OBJECT_LIST ( | |
| EObjNameSpaceArchCommon, | |
| EArchCommonObjErstInstructionsInfo, | |
| CM_ARCH_COMMON_ERST_INSTRUCTIONS_INFO | |
| ); | |
| /** Return whether an ERST instruction accesses the Register Region. | |
| @param [in] Instruction ERST serialization instruction. | |
| @retval TRUE The instruction accesses the Register Region. | |
| @retval FALSE The instruction does not access the Register Region. | |
| **/ | |
| STATIC | |
| BOOLEAN | |
| EFIAPI | |
| IsRegisterAccessInstruction ( | |
| IN UINT8 Instruction | |
| ) | |
| { | |
| switch (Instruction) { | |
| case EFI_ACPI_6_6_ERST_READ_REGISTER: | |
| case EFI_ACPI_6_6_ERST_READ_REGISTER_VALUE: | |
| case EFI_ACPI_6_6_ERST_WRITE_REGISTER: | |
| case EFI_ACPI_6_6_ERST_WRITE_REGISTER_VALUE: | |
| case EFI_ACPI_6_6_ERST_LOAD_VAR1: | |
| case EFI_ACPI_6_6_ERST_LOAD_VAR2: | |
| case EFI_ACPI_6_6_ERST_STORE_VAR1: | |
| case EFI_ACPI_6_6_ERST_ADD_VALUE: | |
| case EFI_ACPI_6_6_ERST_SUBTRACT_VALUE: | |
| case EFI_ACPI_6_6_ERST_STALL_WHILE_TRUE: | |
| case EFI_ACPI_6_6_ERST_SKIP_NEXT_INSTRUCTION_IF_TRUE: | |
| case EFI_ACPI_6_6_ERST_SET_SRC_ADDRESS_BASE: | |
| case EFI_ACPI_6_6_ERST_SET_DST_ADDRESS_BASE: | |
| case EFI_ACPI_6_6_ERST_MOVE_DATA: | |
| return TRUE; | |
| default: | |
| return FALSE; | |
| } | |
| } | |
| /** Validate an ERST Mask against the Register Region bit width. | |
| @param [in] Instruction Pointer to an ERST CM instruction entry. | |
| @retval EFI_SUCCESS The Register Region and Mask are valid. | |
| @retval EFI_INVALID_PARAMETER The Register Region width or Mask is invalid. | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| ValidateRegisterMask ( | |
| IN CONST CM_ARCH_COMMON_ERST_INSTRUCTIONS_INFO *Instruction | |
| ) | |
| { | |
| CONST EFI_ACPI_6_6_GENERIC_ADDRESS_STRUCTURE *RegisterRegion; | |
| ASSERT (Instruction != NULL); | |
| RegisterRegion = &Instruction->RegisterRegion; | |
| // The ERST Mask field is 64 bits and cannot describe a wider bit range. | |
| if (RegisterRegion->RegisterBitWidth > 64) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "ERROR: ERST: Register Region width = %u exceeds 64 bits\n", | |
| RegisterRegion->RegisterBitWidth | |
| )); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if ((RegisterRegion->RegisterBitWidth < 64) && | |
| ((Instruction->Mask >> RegisterRegion->RegisterBitWidth) != 0)) | |
| { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "ERROR: ERST: Mask = 0x%llx exceeds Register Region width = %u\n", | |
| Instruction->Mask, | |
| RegisterRegion->RegisterBitWidth | |
| )); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** Validate the ERST CM instruction entry list. | |
| @param [in] Instructions Pointer to the CM instruction entry array. | |
| @param [in] InstructionCount Number of entries in Instructions. | |
| @retval EFI_SUCCESS The instruction list is valid. | |
| @retval EFI_INVALID_PARAMETER An instruction entry is invalid. | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| ValidateErstInstructions ( | |
| IN CONST CM_ARCH_COMMON_ERST_INSTRUCTIONS_INFO *Instructions, | |
| IN UINT32 InstructionCount | |
| ) | |
| { | |
| BOOLEAN ActionSeen[EFI_ACPI_6_6_ERST_GET_EXECUTE_OPERATION_TIMINGS + 1]; | |
| EFI_STATUS Status; | |
| UINT32 GroupEnd; | |
| UINT32 GroupStart; | |
| UINT32 Index; | |
| UINT8 PreviousAction; | |
| ASSERT (Instructions != NULL); | |
| ASSERT (InstructionCount != 0); | |
| ZeroMem (ActionSeen, sizeof (ActionSeen)); | |
| PreviousAction = MAX_UINT8; | |
| for (Index = 0; Index < InstructionCount; Index++) { | |
| if ((Instructions[Index].SerializationAction > | |
| EFI_ACPI_6_6_ERST_GET_EXECUTE_OPERATION_TIMINGS) || | |
| (Instructions[Index].SerializationAction == | |
| ERST_RESERVED_SERIALIZATION_ACTION)) | |
| { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "ERROR: ERST: Invalid Serialization Action encoding = 0x%x at index %u\n", | |
| Instructions[Index].SerializationAction, | |
| Index | |
| )); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if (Instructions[Index].Instruction > EFI_ACPI_6_6_ERST_MOVE_DATA) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "ERROR: ERST: Invalid Instruction encoding = 0x%x at index %u\n", | |
| Instructions[Index].Instruction, | |
| Index | |
| )); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if ((Instructions[Index].Flags & | |
| ~EFI_ACPI_6_6_ERST_PRESERVE_REGISTER) != 0) | |
| { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "ERROR: ERST: Invalid Flags = 0x%x at index %u\n", | |
| Instructions[Index].Flags, | |
| Index | |
| )); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if (Instructions[Index].SerializationAction != PreviousAction) { | |
| if (ActionSeen[Instructions[Index].SerializationAction]) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "ERROR: ERST: Instructions for Serialization Action = 0x%x " | |
| "are not consecutive.\n", | |
| Instructions[Index].SerializationAction | |
| )); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| ActionSeen[Instructions[Index].SerializationAction] = TRUE; | |
| PreviousAction = Instructions[Index].SerializationAction; | |
| } | |
| if (IsRegisterAccessInstruction (Instructions[Index].Instruction)) { | |
| Status = ValidateRegisterMask (&Instructions[Index]); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| } | |
| } | |
| GroupStart = 0; | |
| while (GroupStart < InstructionCount) { | |
| GroupEnd = GroupStart + 1; | |
| while ((GroupEnd < InstructionCount) && | |
| (Instructions[GroupEnd].SerializationAction == | |
| Instructions[GroupStart].SerializationAction)) | |
| { | |
| GroupEnd++; | |
| } | |
| for (Index = GroupStart; Index < GroupEnd; Index++) { | |
| if ((Instructions[Index].Instruction == EFI_ACPI_6_6_ERST_GOTO) && | |
| (Instructions[Index].Value >= (GroupEnd - GroupStart))) | |
| { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "ERROR: ERST: GOTO target = 0x%llx at index %u is outside its " | |
| "Serialization Action.\n", | |
| Instructions[Index].Value, | |
| Index | |
| )); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| } | |
| GroupStart = GroupEnd; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** Populate the ERST Serialization Instruction Entry list. | |
| @param [out] ErstEntries Pointer to the first ACPI instruction entry. | |
| @param [in] Instructions Pointer to the CM instruction entry array. | |
| @param [in] InstructionCount Number of entries in Instructions. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| PopulateErstInstructions ( | |
| OUT EFI_ACPI_6_6_ERST_SERIALIZATION_INSTRUCTION_ENTRY *ErstEntries, | |
| IN CONST CM_ARCH_COMMON_ERST_INSTRUCTIONS_INFO *Instructions, | |
| IN UINT32 InstructionCount | |
| ) | |
| { | |
| UINT32 Index; | |
| ASSERT (ErstEntries != NULL); | |
| ASSERT (Instructions != NULL); | |
| for (Index = 0; Index < InstructionCount; Index++) { | |
| ErstEntries[Index].SerializationAction = Instructions[Index].SerializationAction; | |
| ErstEntries[Index].Instruction = Instructions[Index].Instruction; | |
| ErstEntries[Index].Flags = Instructions[Index].Flags; | |
| ErstEntries[Index].Value = Instructions[Index].Value; | |
| ErstEntries[Index].Mask = Instructions[Index].Mask; | |
| CopyMem ( | |
| &ErstEntries[Index].RegisterRegion, | |
| &Instructions[Index].RegisterRegion, | |
| sizeof (ErstEntries[Index].RegisterRegion) | |
| ); | |
| } | |
| } | |
| /** Construct the ERST ACPI table. | |
| This function invokes the Configuration Manager protocol interface to get | |
| the Serialization Instruction Entries required for generating the table. | |
| @param [in] This Pointer to the table generator. | |
| @param [in] AcpiTableInfo Pointer to the ACPI Table Info. | |
| @param [in] CfgMgrProtocol Pointer to the Configuration Manager Protocol. | |
| @param [out] Table Pointer to the constructed ACPI Table. | |
| @retval EFI_SUCCESS Table generated successfully. | |
| @retval EFI_INVALID_PARAMETER A parameter or CM instruction is invalid. | |
| @retval EFI_NOT_FOUND The required object was not found. | |
| @retval EFI_OUT_OF_RESOURCES Memory allocation failed. | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| BuildErstTable ( | |
| IN CONST ACPI_TABLE_GENERATOR *CONST This, | |
| IN CONST CM_STD_OBJ_ACPI_TABLE_INFO *CONST AcpiTableInfo, | |
| IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, | |
| OUT EFI_ACPI_DESCRIPTION_HEADER **CONST Table | |
| ) | |
| { | |
| EFI_ACPI_6_6_ERROR_RECORD_SERIALIZATION_TABLE_HEADER *Erst; | |
| EFI_ACPI_6_6_ERST_SERIALIZATION_INSTRUCTION_ENTRY *ErstEntries; | |
| CM_ARCH_COMMON_ERST_INSTRUCTIONS_INFO *Instructions; | |
| UINT32 InstructionCount; | |
| EFI_STATUS Status; | |
| UINT32 TableSize; | |
| ASSERT (This != NULL); | |
| ASSERT (AcpiTableInfo != NULL); | |
| ASSERT (CfgMgrProtocol != NULL); | |
| ASSERT (Table != NULL); | |
| ASSERT (AcpiTableInfo->TableGeneratorId == This->GeneratorID); | |
| ASSERT (AcpiTableInfo->AcpiTableSignature == This->AcpiTableSignature); | |
| if ((AcpiTableInfo->AcpiTableRevision < This->MinAcpiTableRevision) || | |
| (AcpiTableInfo->AcpiTableRevision > This->AcpiTableRevision)) | |
| { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "ERROR: ERST: Requested table revision = %u is not supported. " | |
| "Supported table revision: Minimum = %u, Maximum = %u\n", | |
| AcpiTableInfo->AcpiTableRevision, | |
| This->MinAcpiTableRevision, | |
| This->AcpiTableRevision | |
| )); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| *Table = NULL; | |
| Instructions = NULL; | |
| InstructionCount = 0; | |
| Status = GetEArchCommonObjErstInstructionsInfo ( | |
| CfgMgrProtocol, | |
| CM_NULL_TOKEN, | |
| &Instructions, | |
| &InstructionCount | |
| ); | |
| if (Status == EFI_NOT_FOUND) { | |
| DEBUG ((DEBUG_INFO, "ERST: No instruction entries found; skipping table.\n")); | |
| return EFI_NOT_FOUND; | |
| } | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "ERROR: ERST: Failed to get instruction entries. Status = %r\n", | |
| Status | |
| )); | |
| return Status; | |
| } | |
| Status = ValidateErstInstructions (Instructions, InstructionCount); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| TableSize = | |
| sizeof (EFI_ACPI_6_6_ERROR_RECORD_SERIALIZATION_TABLE_HEADER) + | |
| (InstructionCount * | |
| sizeof (EFI_ACPI_6_6_ERST_SERIALIZATION_INSTRUCTION_ENTRY)); | |
| Erst = AllocateZeroPool (TableSize); | |
| if (Erst == NULL) { | |
| DEBUG ((DEBUG_ERROR, "ERROR: ERST: Failed to allocate the ERST table.\n")); | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| Status = AddAcpiHeader ( | |
| CfgMgrProtocol, | |
| This, | |
| &Erst->Header, | |
| AcpiTableInfo, | |
| TableSize | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| DEBUG (( | |
| DEBUG_ERROR, | |
| "ERROR: ERST: Failed to add ACPI header. Status = %r\n", | |
| Status | |
| )); | |
| FreePool (Erst); | |
| return Status; | |
| } | |
| ErstEntries = | |
| (EFI_ACPI_6_6_ERST_SERIALIZATION_INSTRUCTION_ENTRY *)(Erst + 1); | |
| PopulateErstInstructions ( | |
| ErstEntries, | |
| Instructions, | |
| InstructionCount | |
| ); | |
| Erst->SerializationHeaderSize = sizeof (*Erst) - sizeof (Erst->Header); | |
| Erst->InstructionEntryCount = InstructionCount; | |
| *Table = &Erst->Header; | |
| return EFI_SUCCESS; | |
| } | |
| /** Free resources allocated for constructing the ERST table. | |
| @param [in] This Pointer to the table generator. | |
| @param [in] AcpiTableInfo Pointer to the ACPI Table Info. | |
| @param [in] CfgMgrProtocol Pointer to the Configuration Manager | |
| Protocol interface. | |
| @param [in, out] Table Pointer to the constructed ACPI Table. | |
| @retval EFI_SUCCESS The resources were freed successfully. | |
| @retval EFI_INVALID_PARAMETER The table pointer is NULL. | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| EFIAPI | |
| FreeErstTableResources ( | |
| IN CONST ACPI_TABLE_GENERATOR *CONST This, | |
| IN CONST CM_STD_OBJ_ACPI_TABLE_INFO *CONST AcpiTableInfo, | |
| IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, | |
| IN OUT EFI_ACPI_DESCRIPTION_HEADER **CONST Table | |
| ) | |
| { | |
| ASSERT (This != NULL); | |
| ASSERT (AcpiTableInfo != NULL); | |
| ASSERT (CfgMgrProtocol != NULL); | |
| ASSERT (AcpiTableInfo->TableGeneratorId == This->GeneratorID); | |
| ASSERT (AcpiTableInfo->AcpiTableSignature == This->AcpiTableSignature); | |
| if (Table == NULL) { | |
| DEBUG ((DEBUG_ERROR, "ERROR: ERST: Invalid Table Pointer\n")); | |
| ASSERT (Table != NULL); | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| if (*Table != NULL) { | |
| FreePool (*Table); | |
| *Table = NULL; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** This macro defines the ERST Table Generator revision. */ | |
| #define ERST_GENERATOR_REVISION CREATE_REVISION (1, 0) | |
| /** The interface for the ERST Table Generator. */ | |
| STATIC | |
| CONST | |
| ACPI_TABLE_GENERATOR ErstGenerator = { | |
| // Generator ID | |
| CREATE_STD_ACPI_TABLE_GEN_ID (EStdAcpiTableIdErst), | |
| // Generator Description | |
| L"ACPI.STD.ERST.GENERATOR", | |
| // ACPI Table Signature | |
| EFI_ACPI_6_6_ERROR_RECORD_SERIALIZATION_TABLE_SIGNATURE, | |
| // ACPI Table Revision supported by this Generator | |
| EFI_ACPI_6_6_ERROR_RECORD_SERIALIZATION_TABLE_REVISION, | |
| // Minimum supported ACPI Table Revision | |
| EFI_ACPI_6_6_ERROR_RECORD_SERIALIZATION_TABLE_REVISION, | |
| // Creator ID | |
| TABLE_GENERATOR_CREATOR_ID, | |
| // Creator Revision | |
| ERST_GENERATOR_REVISION, | |
| // Build Table function | |
| BuildErstTable, | |
| // Free Resource function | |
| FreeErstTableResources, | |
| // Extended build function not needed. | |
| NULL, | |
| // Extended free resource function not needed. | |
| NULL | |
| }; | |
| /** Register the Generator with the ACPI Table Factory. | |
| @param [in] ImageHandle The handle to the image. | |
| @param [in] SystemTable Pointer to the System Table. | |
| @retval EFI_SUCCESS The Generator is registered. | |
| @retval EFI_INVALID_PARAMETER A parameter is invalid. | |
| @retval EFI_ALREADY_STARTED The Generator is already registered. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| AcpiErstLibConstructor ( | |
| IN EFI_HANDLE ImageHandle, | |
| IN EFI_SYSTEM_TABLE *SystemTable | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| Status = RegisterAcpiTableGenerator (&ErstGenerator); | |
| DEBUG ((DEBUG_INFO, "ERST: Register Generator. Status = %r\n", Status)); | |
| ASSERT_EFI_ERROR (Status); | |
| return Status; | |
| } | |
| /** Deregister the Generator from the ACPI Table Factory. | |
| @param [in] ImageHandle The handle to the image. | |
| @param [in] SystemTable Pointer to the System Table. | |
| @retval EFI_SUCCESS The Generator is deregistered. | |
| @retval EFI_INVALID_PARAMETER A parameter is invalid. | |
| @retval EFI_NOT_FOUND The Generator is not registered. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| AcpiErstLibDestructor ( | |
| IN EFI_HANDLE ImageHandle, | |
| IN EFI_SYSTEM_TABLE *SystemTable | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| Status = DeregisterAcpiTableGenerator (&ErstGenerator); | |
| DEBUG ((DEBUG_INFO, "ERST: Deregister Generator. Status = %r\n", Status)); | |
| ASSERT_EFI_ERROR (Status); | |
| return Status; | |
| } |