diff --git a/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/DMAProtectionUnitTestApp.c b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/DMAProtectionUnitTestApp.c index baeaf0bba5..40dc3a6a24 100644 --- a/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/DMAProtectionUnitTestApp.c +++ b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/DMAProtectionUnitTestApp.c @@ -348,6 +348,8 @@ CheckBMETeardown ( ); } + UT_LOG_INFO ("PASSED: BME test.\n"); + return UNIT_TEST_PASSED; } // CheckBMETeardown() @@ -418,8 +420,8 @@ DMAProtectionUnitTestApp ( } AddTestCase (IommuTests, "All Hardware Definition Units Have IOMMU Enabled", "IOMMU.StatusRegister", CheckIOMMUEnabled, NULL, NULL, NULL); - AddTestCase (IommuTests, "BME Teardown at ExitBootServices", "IOMMU.BMETeardown", CheckBMETeardown, NULL, NULL, BMEContext); AddTestCase (IommuTests, "Verify excluded ranges are marked reserved", "IOMMU.ExcludedRangeTest", CheckExcludedRegions, NULL, NULL, NULL); + AddTestCase (IommuTests, "BME Teardown at ExitBootServices", "IOMMU.BMETeardown", CheckBMETeardown, NULL, NULL, BMEContext); // // Execute the tests. diff --git a/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/DMASmmuProtectionUnitTestApp.inf b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/DMASmmuProtectionUnitTestApp.inf new file mode 100644 index 0000000000..bae9f27102 --- /dev/null +++ b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/DMASmmuProtectionUnitTestApp.inf @@ -0,0 +1,60 @@ +## @file DMASmmuProtectionUnitTestApp.inf +## +# DMA Protection Unit Test App for ARM SMMUv3 platforms. +# Tests SMMU translation enable status and RMR reserved memory regions. +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + + +[Defines] + INF_VERSION = 0x00010006 + BASE_NAME = DMASmmuProtectionUnitTestApp + FILE_GUID = 7A8C2E45-B9F3-4D12-A6E8-9C1B5F3D2E7A + MODULE_TYPE = UEFI_APPLICATION + VERSION_STRING = 1.0 + ENTRY_POINT = DMAProtectionUnitTestApp + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = AARCH64 +# + +[Sources] + Acpi.c + Acpi.h + DMAProtectionTest.h + DMAProtectionUnitTestApp.c + SMMU/DmaProtection.h + SMMU/DMAProtectionTestArch.c + SMMU/IortAcpiTable.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + UefiTestingPkg/UefiTestingPkg.dec + UnitTestFrameworkPkg/UnitTestFrameworkPkg.dec + ShellPkg/ShellPkg.dec + +[Protocols] + gEfiPciIoProtocolGuid ## CONSUMES + +[LibraryClasses] + UefiApplicationEntryPoint + BaseLib + DebugLib + UnitTestLib + UnitTestBootLib + UnitTestPersistenceLib + IoLib + MemoryAllocationLib + ShellLib + PciLib + +[Guids] + gDMAUnitTestVariableGuid + gEfiAcpi20TableGuid + gEfiAcpi10TableGuid diff --git a/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/README.md b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/README.md index 74735a9956..1a6c1baafb 100644 --- a/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/README.md +++ b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/README.md @@ -16,6 +16,7 @@ Shell based UEFI unit test based off of the UnitTestFrameworkPkg that test for: 3. All excluded regions are set as (EfiReservedMemoryType or EfiACPIMemoryNVS) for IVRS. 4. Bus mastering enabled (BME) is disabled on ExitBootServices. Because we can no longer write to file after ExitBootServices a variable is used to store the test state and the machine. +5. For ARM SMMUv3, each SMMU unit in the IORT has translation enabled (or is otherwise DMA-safe via GBPA abort). Note: this unit test requires a restart to finish its testing. If you plan to use this unit test in automation make sure to set up your startup.nsh script properly. diff --git a/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/DMAProtectionTestArch.c b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/DMAProtectionTestArch.c new file mode 100644 index 0000000000..6a636b8f6e --- /dev/null +++ b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/DMAProtectionTestArch.c @@ -0,0 +1,376 @@ +/** @file -- DMAProtectionTestArch.c + +This file contains architecture specific DMA protection tests for ARM SMMU (SMMUv3): +1) Check the CR0 registers of the SMMUv3 nodes to verify SMMU translation is enabled. + An SMMU that is not enabled (SMMUEN == 0) is still considered DMA-safe only if + it is configured for global abort (GBPA.ABORT == 1), so all DMA is aborted. +2) Check that Command Queue is enabled (CMDQEN bit in CR0) +3) Check that Event Queue is enabled (EVTQEN bit in CR0) +4) Check that Stream Table Base is configured (STRTAB_BASE is not NULL) +5) Check that GERROR register is 0 (no global errors) +6) Check RMR (Reserved Memory Range) regions from IORT are found in the EFI memory map + and marked with an acceptable memory type (EfiReservedMemoryType) + +Copyright (c) Microsoft Corporation. All rights reserved. +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include +#include +#include +#include + +#include "DmaProtection.h" + +/// ================================================================================================ +/// ================================================================================================ +/// +/// TEST CASES +/// +/// ================================================================================================ +/// ================================================================================================ + +/** + Test to verify that the Reserved Memory Range (RMR) regions defined in the IORT + are found in the EFI memory map and marked with an acceptable memory type. + + For each RMR region, the test verifies that: + 1) An EFI memory map descriptor fully encompasses the RMR region, and + 2) That descriptor's memory type is acceptable, i.e. EfiReservedMemoryType + + If an RMR region is not found in the memory map, or is found but is not one of + the acceptable memory types, the test fails. + + @param[in] Context The unit test context (not used). + + @retval UNIT_TEST_PASSED All RMR regions were found with an acceptable memory type. + @retval UNIT_TEST_ERROR_TEST_FAILED A RMR region was not found, or had an unacceptable memory type. +**/ +UNIT_TEST_STATUS +EFIAPI +CheckExcludedRegions ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_MEMORY_DESCRIPTOR *EfiMemoryMap; + EFI_MEMORY_DESCRIPTOR *EfiMemoryMapEnd; + EFI_MEMORY_DESCRIPTOR *EfiMemNext; + UINTN EfiMemoryMapSize; + UINTN EfiMapKey; + UINTN EfiDescriptorSize; + UINT32 EfiDescriptorVersion; + EFI_ACPI_DESCRIPTION_HEADER *IortTable; + LIST_ENTRY RmrList; + LIST_ENTRY *Link; + RMR_LIST_NODE *RmrEntry; + BOOLEAN Found; + BOOLEAN FoundInMemoryMap; + UINT32 FoundMemoryType; + UNIT_TEST_STATUS TestStatus; + + // + // Step 1: Get IORT Table + // + IortTable = NULL; + Status = GetIortAcpiTable (&IortTable); + UT_ASSERT_NOT_EFI_ERROR (Status); + + // + // Step 2: Get the RMR (Reserved Memory Range) nodes from IORT Table + // + InitializeListHead (&RmrList); + Status = GetIortAcpiTableRmrList (IortTable, &RmrList); + UT_ASSERT_NOT_EFI_ERROR (Status); + if (IsListEmpty (&RmrList)) { + UT_LOG_INFO ("No RMRs Found in IORT\n"); + return UNIT_TEST_PASSED; + } + + // + // Step 3: Get the EFI memory map. + // + EfiMemoryMapSize = 0; + EfiMemoryMap = NULL; + Status = gBS->GetMemoryMap ( + &EfiMemoryMapSize, + EfiMemoryMap, + &EfiMapKey, + &EfiDescriptorSize, + &EfiDescriptorVersion + ); + if (Status == EFI_BUFFER_TOO_SMALL) { + EfiMemoryMap = (EFI_MEMORY_DESCRIPTOR *)AllocateZeroPool (EfiMemoryMapSize + 8*EfiDescriptorSize); + UT_ASSERT_NOT_NULL (EfiMemoryMap); + + Status = gBS->GetMemoryMap ( + &EfiMemoryMapSize, + EfiMemoryMap, + &EfiMapKey, + &EfiDescriptorSize, + &EfiDescriptorVersion + ); + UT_ASSERT_NOT_EFI_ERROR (Status); + } else { + UT_LOG_ERROR ("GetMemoryMap Failed\n"); + TestStatus = UNIT_TEST_ERROR_TEST_FAILED; + UT_ASSERT_STATUS_EQUAL (Status, TestStatus); + return UNIT_TEST_ERROR_TEST_FAILED; + } + + // + // Step 4: Step through memory map and verify each + // RMR memory range is marked reserved + // + EfiMemoryMapEnd = (EFI_MEMORY_DESCRIPTOR *)((UINT8 *)EfiMemoryMap + EfiMemoryMapSize); + TestStatus = UNIT_TEST_PASSED; + + for (Link = GetFirstNode (&RmrList); + !IsNull (&RmrList, Link); + Link = GetNextNode (&RmrList, Link)) + { + RmrEntry = RMR_LIST_NODE_FROM_LINK (Link); + Found = FALSE; + FoundInMemoryMap = FALSE; + FoundMemoryType = 0; + EfiMemNext = EfiMemoryMap; + + UT_LOG_INFO ("Checking RMR region: Base=0x%lX, Length=0x%lX\n", RmrEntry->BaseAddress, RmrEntry->Length); + + while (EfiMemNext < EfiMemoryMapEnd) { + // Check if memory range fully encompasses RMR + if ((EfiMemNext->PhysicalStart <= RmrEntry->BaseAddress) && + ((EfiMemNext->PhysicalStart + (EFI_PAGE_SIZE * EfiMemNext->NumberOfPages)) >= (RmrEntry->BaseAddress + RmrEntry->Length))) + { + FoundInMemoryMap = TRUE; + FoundMemoryType = EfiMemNext->Type; + + UT_LOG_INFO ( + "Found encompassing memory range: Base=0x%lX, Length=0x%lX, Type=%d\n", + EfiMemNext->PhysicalStart, + EFI_PAGE_SIZE * EfiMemNext->NumberOfPages, + EfiMemNext->Type + ); + + if (EfiMemNext->Type == EfiReservedMemoryType) { + Found = TRUE; + } + + break; + } + + // Move on to next descriptor + EfiMemNext = NEXT_MEMORY_DESCRIPTOR (EfiMemNext, EfiDescriptorSize); + } + + // + // Report whether the RMR region was located in the UEFI memory map, + // and if found, the memory type (even if it is not reserved). + // + if (!FoundInMemoryMap) { + UT_LOG_INFO ( + "RMR region Base=0x%lX, Length=0x%lX was NOT found in the UEFI memory map\n", + RmrEntry->BaseAddress, + RmrEntry->Length + ); + TestStatus = UNIT_TEST_ERROR_TEST_FAILED; + } + + if (!Found) { + UT_LOG_ERROR ( + "RMR between 0x%lX and 0x%lX NOT found with an acceptable memory type (Reserved)! Memory type found: %d\n", + RmrEntry->BaseAddress, + RmrEntry->BaseAddress + RmrEntry->Length, + FoundMemoryType + ); + TestStatus = UNIT_TEST_ERROR_TEST_FAILED; + } + } + + UT_LOG_INFO ("%a: Result=%d (%a)\n", __func__, TestStatus, (TestStatus == UNIT_TEST_PASSED) ? "PASSED" : "FAILED"); + + // + // Free every RMR entry appended by GetIortAcpiTableRmrList. + // + while (!IsListEmpty (&RmrList)) { + Link = GetFirstNode (&RmrList); + RmrEntry = RMR_LIST_NODE_FROM_LINK (Link); + RemoveEntryList (Link); + FreePool (RmrEntry); + } + + UT_ASSERT_STATUS_EQUAL (TestStatus, UNIT_TEST_PASSED); + return TestStatus; +} // CheckExcludedRegions() + +/** + Test to verify that all SMMUv3 units found in the IORT are configured to be + DMA-safe. + + For each SMMUv3 unit, if translation is enabled (CR0.SMMUEN == 1) this checks: + 1) CR0 register's SMMUEN bit to confirm the SMMU is actively translating + 2) CR0 register's CMDQEN bit to confirm the command queue is enabled + 3) CR0 register's EVTQEN bit to confirm the event queue is enabled + 4) STRTAB_BASE register is not NULL (stream table must be configured) + 5) GERROR register is 0 (no global errors) + + If translation is not enabled (CR0.SMMUEN == 0), the SMMU is still considered + DMA-safe (and the checks above are skipped) only if it is configured for global + abort (GBPA.ABORT == 1), so all DMA is aborted. Otherwise the SMMU is considered + unsafe and the test fails. + + @param[in] Context The unit test context (not used). + + @retval UNIT_TEST_PASSED All SMMU units are properly configured. + @retval UNIT_TEST_ERROR_TEST_FAILED An SMMU unit is not properly configured. +**/ +UNIT_TEST_STATUS +EFIAPI +CheckIOMMUEnabled ( + IN UNIT_TEST_CONTEXT Context + ) +{ + EFI_STATUS Status; + EFI_ACPI_DESCRIPTION_HEADER *IortTable; + UINTN SmmuCount; + UINT64 *SmmuBaseAddresses; + UINTN Iterator; + UINT32 Cr0Value; + UINT32 SmmuEnBit; + UINT32 CmdQEnBit; + UINT32 EvtQEnBit; + UINT64 StrTabBase; + UINT64 StrTabBaseAddr; + UINT32 GError; + UINT32 GbpaValue; + UINT32 AbortBit; + UNIT_TEST_STATUS TestStatus; + + // + // Step 1: Get IORT Table + // + IortTable = NULL; + Status = GetIortAcpiTable (&IortTable); + UT_ASSERT_NOT_EFI_ERROR (Status); + + // + // Step 2: Find and parse SMMUv3 nodes from IORT + // + SmmuCount = 0; + SmmuBaseAddresses = NULL; + Status = ParseIortAcpiTableSmmu (IortTable, &SmmuCount, &SmmuBaseAddresses); + UT_ASSERT_NOT_EFI_ERROR (Status); + + // + // Step 3: Check that we found at least one SMMU + // + UT_ASSERT_TRUE (SmmuCount > 0); + UT_LOG_INFO ("Found %d SMMUv3 units in IORT\n", SmmuCount); + + TestStatus = UNIT_TEST_PASSED; + + // + // Step 4: For each SMMU, check: + // - SMMU GBPA Set (GBPA.ABORT == 1), or: + // - SMMU Enable bit (SMMUEN) in CR0 register + // - Command Queue Enable bit (CMDQEN) in CR0 register + // - Event Queue Enable bit (EVTQEN) in CR0 register + // - Stream Table Base address is not NULL + // - GERROR register is 0 + // + for (Iterator = 0; Iterator < SmmuCount; Iterator++) { + UT_LOG_INFO ("Checking SMMUv3 at base address 0x%lX\n", SmmuBaseAddresses[Iterator]); + + // + // Read CR0 register + // + Cr0Value = MmioRead32 ((UINTN)(SmmuBaseAddresses[Iterator] + SMMU_CR0)); + UT_LOG_INFO ("CR0 Register Value: 0x%X\n", Cr0Value); + + // + // Check SMMUEN bit (bit 0) + // + SmmuEnBit = Cr0Value & SMMU_CR0_SMMUEN; + UT_LOG_INFO ("SMMUEN bit: %d\n", SmmuEnBit); + if (SmmuEnBit == 0) { + // + // SMMU translation is not enabled. The SMMU is still DMA-safe if it is + // configured for global abort (GBPA.ABORT == 1). + // + GbpaValue = MmioRead32 ((UINTN)(SmmuBaseAddresses[Iterator] + SMMU_GBPA)); + AbortBit = GbpaValue & SMMU_GBPA_ABORT; + UT_LOG_INFO ("GBPA Register Value: 0x%X, ABORT bit: %d\n", GbpaValue, AbortBit ? 1 : 0); + + if (AbortBit != 0) { + // + // Global abort is set: all DMA is aborted, so this SMMU is DMA-safe. + // Skip the remaining translation-related checks for this SMMU. + // + UT_LOG_INFO ("SMMUEN is disabled but global abort (GBPA.ABORT) is set for SMMUv3 at base address 0x%lX. SMMU is DMA-safe.\n", SmmuBaseAddresses[Iterator]); + continue; + } + + UT_LOG_ERROR ("SMMUEN bit is disabled and global abort is not set for SMMUv3 at base address 0x%lX\n", SmmuBaseAddresses[Iterator]); + TestStatus = UNIT_TEST_ERROR_TEST_FAILED; + continue; + } + + // + // Check CMDQEN bit (bit 3) - Command Queue must be enabled + // + CmdQEnBit = Cr0Value & SMMU_CR0_CMDQEN; + UT_LOG_INFO ("CMDQEN bit: %d\n", CmdQEnBit ? 1 : 0); + if (CmdQEnBit == 0) { + UT_LOG_ERROR ("CMDQEN bit is disabled for SMMUv3 at base address 0x%lX\n", SmmuBaseAddresses[Iterator]); + TestStatus = UNIT_TEST_ERROR_TEST_FAILED; + } + + // + // Check EVTQEN bit (bit 2) - Event Queue must be enabled + // + EvtQEnBit = Cr0Value & SMMU_CR0_EVTQEN; + UT_LOG_INFO ("EVTQEN bit: %d\n", EvtQEnBit ? 1 : 0); + if (EvtQEnBit == 0) { + UT_LOG_ERROR ("EVTQEN bit is disabled for SMMUv3 at base address 0x%lX\n", SmmuBaseAddresses[Iterator]); + TestStatus = UNIT_TEST_ERROR_TEST_FAILED; + } + + // + // Read STRTAB_BASE register and check it's not NULL + // If NULL, no stream IDs can undergo translation + // + StrTabBase = MmioRead64 ((UINTN)(SmmuBaseAddresses[Iterator] + SMMU_STRTAB_BASE)); + UT_LOG_INFO ("STRTAB_BASE Register Value: 0x%lX\n", StrTabBase); + + // Extract the address portion by masking out lower 6 bits (bits [5:0] are reserved/config) + StrTabBaseAddr = StrTabBase & ~SMMU_STRTAB_BASE_ADDR_MASK; + UT_LOG_INFO ("STRTAB_BASE Address: 0x%lX\n", StrTabBaseAddr); + if (StrTabBaseAddr == 0) { + UT_LOG_ERROR ("STRTAB_BASE is NULL for SMMUv3 at base address 0x%lX\n", SmmuBaseAddresses[Iterator]); + TestStatus = UNIT_TEST_ERROR_TEST_FAILED; + } + + // + // Read GERROR register and check it's 0 (no global errors) + // + GError = MmioRead32 ((UINTN)(SmmuBaseAddresses[Iterator] + SMMU_GERROR)); + UT_LOG_INFO ("GERROR Register Value: 0x%X\n", GError); + if (GError != 0) { + UT_LOG_ERROR ("GERROR register is non-zero for SMMUv3 at base address 0x%lX\n", SmmuBaseAddresses[Iterator]); + TestStatus = UNIT_TEST_ERROR_TEST_FAILED; + } + } + + UT_LOG_INFO ("%a: Result=%d (%a)\n", __func__, TestStatus, (TestStatus == UNIT_TEST_PASSED) ? "PASSED" : "FAILED"); + + // Free the SMMU base address array allocated by ParseIortAcpiTableSmmu. + FreePool (SmmuBaseAddresses); + SmmuBaseAddresses = NULL; + + UT_ASSERT_STATUS_EQUAL (TestStatus, UNIT_TEST_PASSED); + return TestStatus; +} // CheckIOMMUEnabled() diff --git a/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/DmaProtection.h b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/DmaProtection.h new file mode 100644 index 0000000000..41bdf48ced --- /dev/null +++ b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/DmaProtection.h @@ -0,0 +1,132 @@ +/** @file -- DmaProtection.h + +Header file for SMMU DMA protection tests. + +Copyright (c) Microsoft Corporation. All rights reserved. +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _DMA_PROTECTION_H_ +#define _DMA_PROTECTION_H_ + +#include +#include + +// +// IORT Table Signature +// +#define EFI_ACPI_6_0_IO_REMAPPING_TABLE_SIGNATURE SIGNATURE_32('I', 'O', 'R', 'T') + +// +// SMMUv3 Register Offsets +// +#define SMMU_CR0 0x0020 +#define SMMU_CR0ACK 0x0024 +#define SMMU_GBPA 0x0044 +#define SMMU_GERROR 0x0060 +#define SMMU_STRTAB_BASE 0x0080 + +// +// SMMUv3 CR0 Register Bits +// +#define SMMU_CR0_SMMUEN BIT0 // SMMU Enable bit +#define SMMU_CR0_EVTQEN BIT2 // Event Queue Enable bit +#define SMMU_CR0_CMDQEN BIT3 // Command Queue Enable bit + +// +// SMMUv3 GBPA Register Bits +// +#define SMMU_GBPA_ABORT BIT20 // Global Bypass Abort bit (abort all DMA when SMMUEN == 0) + +// +// STRTAB_BASE lower bits mask (bits [5:0] are reserved/config) +// +#define SMMU_STRTAB_BASE_ADDR_MASK 0x3FULL + +// +// Signature and structure used to track Reserved Memory Ranges (RMRs) parsed +// from the IORT. Nodes are linked into a doubly-linked list rooted at a +// caller-provided LIST_ENTRY head. Use RMR_LIST_NODE_FROM_LINK() to recover +// the containing RMR_LIST_NODE from a LIST_ENTRY *. +// +#define RMR_LIST_NODE_SIGNATURE SIGNATURE_32 ('R', 'M', 'R', 'N') + +typedef struct { + UINT32 Signature; + LIST_ENTRY Link; + UINT64 BaseAddress; + UINT64 Length; +} RMR_LIST_NODE; + +#define RMR_LIST_NODE_FROM_LINK(a) CR (a, RMR_LIST_NODE, Link, RMR_LIST_NODE_SIGNATURE) + +// +// Function Prototypes +// + +/** + Get the IORT ACPI table from the system. + + @param[out] IortTable Pointer to receive the IORT table pointer. + + @retval EFI_SUCCESS The IORT table was found. + @retval EFI_NOT_FOUND The IORT table was not found. + @retval EFI_INVALID_PARAMETER IortTable is NULL. +**/ +EFI_STATUS +EFIAPI +GetIortAcpiTable ( + OUT EFI_ACPI_DESCRIPTION_HEADER **IortTable + ); + +/** + Parse the IORT table to find all SMMUv3 nodes. + + @param[in] IortTable Pointer to the IORT table. + @param[out] SmmuCount Pointer to receive the count of SMMUv3 nodes. + @param[out] SmmuBaseAddresses Pointer to receive the array of SMMU base addresses. + Caller must free this memory when done. + + @retval EFI_SUCCESS SMMUv3 nodes were found and parsed. + @retval EFI_NOT_FOUND No SMMUv3 nodes found. + @retval EFI_INVALID_PARAMETER A required parameter is NULL. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. +**/ +EFI_STATUS +EFIAPI +ParseIortAcpiTableSmmu ( + IN EFI_ACPI_DESCRIPTION_HEADER *IortTable, + OUT UINTN *SmmuCount, + OUT UINT64 **SmmuBaseAddresses + ); + +/** + Parse the IORT table and append each Reserved Memory Range (RMR) descriptor + to the caller-provided doubly-linked list. + + Each entry appended is an RMR_LIST_NODE. The caller is responsible for: + - Initializing RmrList (e.g. via InitializeListHead) before calling. + - Freeing every appended RMR_LIST_NODE (e.g. via RemoveEntryList + FreePool) + when done. Entries appended before an error return must also be freed. + + If no RMR nodes are found, EFI_SUCCESS is returned and RmrList is left empty + (IsListEmpty returns TRUE). + + @param[in] IortTable Pointer to the IORT table. + @param[in,out] RmrList List head to append RMR entries to. + + @retval EFI_SUCCESS IORT was parsed; RmrList may be empty if there + are no RMR nodes. + @retval EFI_INVALID_PARAMETER IortTable or RmrList is NULL. + @retval EFI_OUT_OF_RESOURCES Failed to allocate an RMR_LIST_NODE. Any entries + appended before the failure remain in RmrList. +**/ +EFI_STATUS +EFIAPI +GetIortAcpiTableRmrList ( + IN EFI_ACPI_DESCRIPTION_HEADER *IortTable, + IN OUT LIST_ENTRY *RmrList + ); + +#endif // _DMA_PROTECTION_H_ diff --git a/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/IortAcpiTable.c b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/IortAcpiTable.c new file mode 100644 index 0000000000..0d0350f49c --- /dev/null +++ b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/IortAcpiTable.c @@ -0,0 +1,204 @@ +/** @file -- IortAcpiTable.c + +This file contains functions to parse the IORT ACPI table for SMMUv3 nodes +and Reserved Memory Range (RMR) nodes. + +Copyright (c) Microsoft Corporation. All rights reserved. +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include +#include +#include +#include + +#include "../Acpi.h" +#include "DmaProtection.h" + +/** + Get the IORT ACPI table. + + @param[out] IortTable Pointer to receive the IORT table pointer. + + @retval EFI_SUCCESS The IORT ACPI table is got. + @retval EFI_ALREADY_STARTED The IORT ACPI table has been got previously. + @retval EFI_NOT_FOUND The IORT ACPI table is not found. + @retval EFI_INVALID_PARAMETER IortTable is NULL. +**/ +EFI_STATUS +EFIAPI +GetIortAcpiTable ( + OUT EFI_ACPI_DESCRIPTION_HEADER **IortTable + ) +{ + if (IortTable == NULL) { + return EFI_INVALID_PARAMETER; + } + + return GetAcpiTable (EFI_ACPI_6_0_IO_REMAPPING_TABLE_SIGNATURE, (VOID **)IortTable); +} + +/** + Parse the IORT table to find all SMMUv3 nodes. + + @param[in] IortTable Pointer to the IORT table. + @param[out] SmmuCount Pointer to receive the count of SMMUv3 nodes. + @param[out] SmmuBaseAddresses Pointer to receive the array of SMMU base addresses. + Caller must free this memory when done. + + @retval EFI_SUCCESS SMMUv3 nodes were found and parsed. + @retval EFI_NOT_FOUND No SMMUv3 nodes found. + @retval EFI_INVALID_PARAMETER A required parameter is NULL. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. +**/ +EFI_STATUS +EFIAPI +ParseIortAcpiTableSmmu ( + IN EFI_ACPI_DESCRIPTION_HEADER *IortTable, + OUT UINTN *SmmuCount, + OUT UINT64 **SmmuBaseAddresses + ) +{ + EFI_ACPI_6_0_IO_REMAPPING_TABLE *Iort; + EFI_ACPI_6_0_IO_REMAPPING_NODE *Node; + EFI_ACPI_6_0_IO_REMAPPING_SMMU3_NODE *SmmuNode; + UINT32 Count; + UINTN SmmuIndex; + UINTN LocalSmmuCount; + UINT64 *LocalSmmuBaseAddresses; + + if ((IortTable == NULL) || (SmmuCount == NULL) || (SmmuBaseAddresses == NULL)) { + return EFI_INVALID_PARAMETER; + } + + Iort = (EFI_ACPI_6_0_IO_REMAPPING_TABLE *)IortTable; + Node = (EFI_ACPI_6_0_IO_REMAPPING_NODE *)((UINT8 *)Iort + Iort->NodeOffset); + + // First pass: count SMMUv3 nodes + LocalSmmuCount = 0; + for (Count = 0; Count < Iort->NumNodes; Count++) { + if (Node->Type == EFI_ACPI_IORT_TYPE_SMMUv3) { + LocalSmmuCount++; + } + + Node = (EFI_ACPI_6_0_IO_REMAPPING_NODE *)((UINT8 *)Node + Node->Length); + } + + if (LocalSmmuCount == 0) { + DEBUG ((DEBUG_ERROR, "%a: No SMMUv3 nodes found\n", __func__)); + *SmmuCount = 0; + *SmmuBaseAddresses = NULL; + return EFI_NOT_FOUND; + } + + // Allocate array for SMMU base addresses + LocalSmmuBaseAddresses = AllocateZeroPool (LocalSmmuCount * sizeof (UINT64)); + if (LocalSmmuBaseAddresses == NULL) { + DEBUG ((DEBUG_ERROR, "%a: Failed to allocate memory for SMMU base addresses\n", __func__)); + *SmmuCount = 0; + *SmmuBaseAddresses = NULL; + return EFI_OUT_OF_RESOURCES; + } + + // Second pass: collect base addresses + Node = (EFI_ACPI_6_0_IO_REMAPPING_NODE *)((UINT8 *)Iort + Iort->NodeOffset); + SmmuIndex = 0; + + for (Count = 0; Count < Iort->NumNodes; Count++) { + if (Node->Type == EFI_ACPI_IORT_TYPE_SMMUv3) { + SmmuNode = (EFI_ACPI_6_0_IO_REMAPPING_SMMU3_NODE *)Node; + LocalSmmuBaseAddresses[SmmuIndex] = SmmuNode->Base; + SmmuIndex++; + } + + Node = (EFI_ACPI_6_0_IO_REMAPPING_NODE *)((UINT8 *)Node + Node->Length); + } + + *SmmuCount = LocalSmmuCount; + *SmmuBaseAddresses = LocalSmmuBaseAddresses; + + return EFI_SUCCESS; +} + +/** + Parse the IORT table and append each Reserved Memory Range (RMR) descriptor + to the caller-provided doubly-linked list. + + This function iterates through all nodes in the IORT table looking for RMR + nodes (type 0x6). For each RMR node found, every valid memory range + descriptor (non-zero base and length) is wrapped in an RMR_LIST_NODE and + appended to RmrList via InsertTailList. + + The caller must have initialized RmrList (e.g. with InitializeListHead) + before calling this function, and is responsible for freeing every appended + entry (e.g. via RemoveEntryList + FreePool) when done. Entries appended + before an EFI_OUT_OF_RESOURCES return must also be freed. + + @param[in] IortTable Pointer to the IORT table. + @param[in,out] RmrList List head to append RMR entries to. + + @retval EFI_SUCCESS IORT was parsed; RmrList may be empty if there + are no RMR nodes. + @retval EFI_INVALID_PARAMETER IortTable or RmrList is NULL. + @retval EFI_OUT_OF_RESOURCES Failed to allocate an RMR_LIST_NODE. Any entries + appended before the failure remain in RmrList. +**/ +EFI_STATUS +EFIAPI +GetIortAcpiTableRmrList ( + IN EFI_ACPI_DESCRIPTION_HEADER *IortTable, + IN OUT LIST_ENTRY *RmrList + ) +{ + EFI_ACPI_6_0_IO_REMAPPING_TABLE *Iort; + EFI_ACPI_6_0_IO_REMAPPING_NODE *Node; + EFI_ACPI_6_0_IO_REMAPPING_RMR_NODE *RmrNode; + EFI_ACPI_6_0_IO_REMAPPING_MEM_RANGE_DESC *MemRangeDesc; + RMR_LIST_NODE *NewNode; + UINT32 Count; + UINT32 MemRangeIndex; + + if ((IortTable == NULL) || (RmrList == NULL)) { + DEBUG ((DEBUG_ERROR, "%a: Invalid parameter\n", __func__)); + return EFI_INVALID_PARAMETER; + } + + Iort = (EFI_ACPI_6_0_IO_REMAPPING_TABLE *)IortTable; + Node = (EFI_ACPI_6_0_IO_REMAPPING_NODE *)((UINT8 *)Iort + Iort->NodeOffset); + + // Iterate through all nodes looking for RMR nodes + for (Count = 0; Count < Iort->NumNodes; Count++) { + if (Node->Type == EFI_ACPI_IORT_TYPE_RMR) { + RmrNode = (EFI_ACPI_6_0_IO_REMAPPING_RMR_NODE *)Node; + + // Get pointer to memory range descriptor array + // MemRangeDescRef is offset from the start of the RMR node + MemRangeDesc = (EFI_ACPI_6_0_IO_REMAPPING_MEM_RANGE_DESC *)((UINT8 *)RmrNode + RmrNode->MemRangeDescRef); + + // Iterate through all memory range descriptors in this RMR node + for (MemRangeIndex = 0; MemRangeIndex < RmrNode->NumMemRangeDesc; MemRangeIndex++) { + // Only add valid ranges (non-zero base and length) + if ((MemRangeDesc[MemRangeIndex].Base > 0) && (MemRangeDesc[MemRangeIndex].Length > 0)) { + NewNode = AllocateZeroPool (sizeof (RMR_LIST_NODE)); + if (NewNode == NULL) { + DEBUG ((DEBUG_ERROR, "%a: Failed to allocate RMR_LIST_NODE\n", __func__)); + // Leave already-appended entries in RmrList; caller must free them. + return EFI_OUT_OF_RESOURCES; + } + + NewNode->Signature = RMR_LIST_NODE_SIGNATURE; + NewNode->BaseAddress = MemRangeDesc[MemRangeIndex].Base; + NewNode->Length = MemRangeDesc[MemRangeIndex].Length; + + InsertTailList (RmrList, &NewNode->Link); + } + } + } + + // Move to the next node + Node = (EFI_ACPI_6_0_IO_REMAPPING_NODE *)((UINT8 *)Node + Node->Length); + } + + return EFI_SUCCESS; +} diff --git a/UefiTestingPkg/UefiTestingPkg.dsc b/UefiTestingPkg/UefiTestingPkg.dsc index 2e2d40740c..3e7798af6b 100644 --- a/UefiTestingPkg/UefiTestingPkg.dsc +++ b/UefiTestingPkg/UefiTestingPkg.dsc @@ -156,6 +156,7 @@ # NOTE: These currently have source files that are only implemented for AARCH64. # If needed on X86, should port (and test) the functions. UefiTestingPkg/FunctionalSystemTests/MpManagement/Driver/MpManagement.inf + UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/DMASmmuProtectionUnitTestApp.inf [BuildOptions] #force deprecated interfaces off