| /** @file | |
| BERT table parser | |
| Copyright (c) 2026, Arm Limited. All rights reserved. | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| @par Reference(s): | |
| - ACPI 6.6 Specification - January 2025 | |
| **/ | |
| #include <IndustryStandard/Acpi.h> | |
| #include <Library/BaseLib.h> | |
| #include <Library/MemoryAllocationLib.h> | |
| #include <Library/UefiBootServicesTableLib.h> | |
| #include <Library/UefiLib.h> | |
| #include "AcpiParser.h" | |
| #include "AcpiTableParser.h" | |
| #include "AcpiViewConfig.h" | |
| // Local variables | |
| STATIC ACPI_DESCRIPTION_HEADER_INFO mAcpiHdrInfo; | |
| STATIC CONST UINT32 *mBootErrorRegionLength; | |
| /** | |
| Check that an entire physical address range is mapped as reserved memory. | |
| @param [in] BaseAddress Physical base address of the range. | |
| @param [in] Length Length of the range in bytes. | |
| @retval EFI_SUCCESS The entire range is mapped as reserved memory. | |
| @retval EFI_INVALID_PARAMETER The range is invalid. | |
| @retval EFI_NOT_FOUND Some or all of the range is not in the memory | |
| map. | |
| @retval EFI_NO_MAPPING Some or all of the range is not mapped as | |
| reserved memory. | |
| @retval EFI_OUT_OF_RESOURCES The memory map could not be allocated. | |
| @retval Others An error returned by GetMemoryMap(). | |
| **/ | |
| STATIC | |
| EFI_STATUS | |
| CheckRangeReserved ( | |
| IN EFI_PHYSICAL_ADDRESS BaseAddress, | |
| IN UINT64 Length | |
| ) | |
| { | |
| EFI_PHYSICAL_ADDRESS CurrentAddress; | |
| EFI_PHYSICAL_ADDRESS DescriptorEnd; | |
| EFI_PHYSICAL_ADDRESS RangeEnd; | |
| EFI_MEMORY_DESCRIPTOR *Descriptor; | |
| EFI_MEMORY_DESCRIPTOR *MemoryMap; | |
| EFI_STATUS Status; | |
| UINT32 DescriptorVersion; | |
| UINT64 DescriptorLength; | |
| UINTN DescriptorSize; | |
| UINTN MapKey; | |
| UINTN MemoryMapSize; | |
| UINTN Offset; | |
| BOOLEAN DescriptorFound; | |
| if ((Length == 0) || (BaseAddress > (MAX_UINT64 - Length))) { | |
| return EFI_INVALID_PARAMETER; | |
| } | |
| MemoryMap = NULL; | |
| MemoryMapSize = 0; | |
| Status = gBS->GetMemoryMap ( | |
| &MemoryMapSize, | |
| MemoryMap, | |
| &MapKey, | |
| &DescriptorSize, | |
| &DescriptorVersion | |
| ); | |
| if (Status != EFI_BUFFER_TOO_SMALL) { | |
| return Status; | |
| } | |
| do { | |
| MemoryMapSize += SIZE_1KB; | |
| MemoryMap = AllocatePool (MemoryMapSize); | |
| if (MemoryMap == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| Status = gBS->GetMemoryMap ( | |
| &MemoryMapSize, | |
| MemoryMap, | |
| &MapKey, | |
| &DescriptorSize, | |
| &DescriptorVersion | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| FreePool (MemoryMap); | |
| MemoryMap = NULL; | |
| } | |
| } while (Status == EFI_BUFFER_TOO_SMALL); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| RangeEnd = BaseAddress + Length; | |
| CurrentAddress = BaseAddress; | |
| while (CurrentAddress < RangeEnd) { | |
| DescriptorFound = FALSE; | |
| for (Offset = 0; Offset < MemoryMapSize; Offset += DescriptorSize) { | |
| Descriptor = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)MemoryMap + Offset); | |
| DescriptorLength = LShiftU64 (Descriptor->NumberOfPages, EFI_PAGE_SHIFT); | |
| DescriptorEnd = Descriptor->PhysicalStart + DescriptorLength; | |
| if ((CurrentAddress < Descriptor->PhysicalStart) || | |
| (CurrentAddress >= DescriptorEnd)) | |
| { | |
| continue; | |
| } | |
| if (Descriptor->Type != EfiReservedMemoryType) { | |
| Status = EFI_NO_MAPPING; | |
| goto FreeMemoryMap; | |
| } | |
| CurrentAddress = MIN (DescriptorEnd, RangeEnd); | |
| DescriptorFound = TRUE; | |
| break; | |
| } | |
| if (!DescriptorFound) { | |
| Status = EFI_NOT_FOUND; | |
| goto FreeMemoryMap; | |
| } | |
| } | |
| Status = EFI_SUCCESS; | |
| FreeMemoryMap: | |
| FreePool (MemoryMap); | |
| return Status; | |
| } | |
| /** | |
| Validate the Boot Error Region length. | |
| @param [in] Ptr Pointer to the start of the field data. | |
| @param [in] Length Length of the field. | |
| @param [in] Context Pointer to context specific information. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| ValidateBootErrorRegionLength ( | |
| IN UINT8 *Ptr, | |
| IN UINT32 Length, | |
| IN VOID *Context | |
| ) | |
| { | |
| CONST UINT32 RegionLength = *(UINT32 *)Ptr; | |
| if (RegionLength < sizeof (EFI_ACPI_6_6_GENERIC_ERROR_STATUS_STRUCTURE)) { | |
| IncrementErrorCount (); | |
| Print ( | |
| L"\nERROR: Boot Error Region Length must be at least 0x%x.", | |
| (UINT32)sizeof (EFI_ACPI_6_6_GENERIC_ERROR_STATUS_STRUCTURE) | |
| ); | |
| } | |
| } | |
| /** | |
| Validate the Boot Error Region address. | |
| @param [in] Ptr Pointer to the start of the field data. | |
| @param [in] Length Length of the field. | |
| @param [in] Context Pointer to context specific information. | |
| **/ | |
| STATIC | |
| VOID | |
| EFIAPI | |
| ValidateBootErrorRegion ( | |
| IN UINT8 *Ptr, | |
| IN UINT32 Length, | |
| IN VOID *Context | |
| ) | |
| { | |
| CONST UINT64 RegionAddress = *(UINT64 *)Ptr; | |
| EFI_STATUS Status; | |
| if (RegionAddress == 0) { | |
| IncrementErrorCount (); | |
| Print (L"\nERROR: Boot Error Region address must not be zero."); | |
| return; | |
| } | |
| if ((mBootErrorRegionLength != NULL) && | |
| (*mBootErrorRegionLength > (MAX_UINT64 - RegionAddress))) | |
| { | |
| IncrementErrorCount (); | |
| Print (L"\nERROR: Boot Error Region address range overflows."); | |
| return; | |
| } | |
| if ((mBootErrorRegionLength == NULL) || | |
| (*mBootErrorRegionLength < | |
| sizeof (EFI_ACPI_6_6_GENERIC_ERROR_STATUS_STRUCTURE))) | |
| { | |
| return; | |
| } | |
| Status = CheckRangeReserved (RegionAddress, *mBootErrorRegionLength); | |
| if ((Status == EFI_NOT_FOUND) || (Status == EFI_NO_MAPPING)) { | |
| IncrementErrorCount (); | |
| Print ( | |
| L"\nERROR: Boot Error Region must be entirely mapped as " | |
| L"EfiReservedMemoryType." | |
| ); | |
| } else if (EFI_ERROR (Status)) { | |
| IncrementErrorCount (); | |
| Print ( | |
| L"\nERROR: Failed to check the Boot Error Region memory type. " | |
| L"Status = %r.", | |
| Status | |
| ); | |
| } | |
| } | |
| /** | |
| An ACPI_PARSER array describing the ACPI BERT table. | |
| **/ | |
| STATIC CONST ACPI_PARSER mBertParser[] = { | |
| PARSE_ACPI_HEADER (&mAcpiHdrInfo), | |
| { L"Boot Error Region Length", 4, 36, L"0x%x", NULL, (VOID **)&mBootErrorRegionLength, ValidateBootErrorRegionLength, NULL }, | |
| { L"Boot Error Region", 8, 40, L"0x%lx", NULL, NULL, ValidateBootErrorRegion, NULL } | |
| }; | |
| /** | |
| This function parses the ACPI BERT table. | |
| When trace is enabled this function parses the BERT table and | |
| traces the ACPI table fields. | |
| This function also performs validation of the ACPI table fields. | |
| @param [in] Trace If TRUE, trace the ACPI fields. | |
| @param [in] Ptr Pointer to the start of the buffer. | |
| @param [in] AcpiTableLength Length of the ACPI table. | |
| @param [in] AcpiTableRevision Revision of the ACPI table. | |
| **/ | |
| VOID | |
| EFIAPI | |
| ParseAcpiBert ( | |
| IN BOOLEAN Trace, | |
| IN UINT8 *Ptr, | |
| IN UINT32 AcpiTableLength, | |
| IN UINT8 AcpiTableRevision | |
| ) | |
| { | |
| if (!Trace) { | |
| return; | |
| } | |
| ParseAcpi ( | |
| Trace, | |
| 0, | |
| "BERT", | |
| Ptr, | |
| AcpiTableLength, | |
| PARSER_PARAMS (mBertParser) | |
| ); | |
| } |