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..631af0a92d --- /dev/null +++ b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/DMASmmuProtectionUnitTestApp.inf @@ -0,0 +1,59 @@ +## @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 + DebugLib + UnitTestLib + UnitTestBootLib + UnitTestPersistenceLib + IoLib + MemoryAllocationLib + ShellLib + PciLib + +[Guids] + gDMAUnitTestVariableGuid + gEfiAcpi20TableGuid + gEfiAcpi10TableGuid diff --git a/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/DMAProtectionTestArch.c b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/DMAProtectionTestArch.c new file mode 100644 index 0000000000..c7998665a0 --- /dev/null +++ b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/DMAProtectionTestArch.c @@ -0,0 +1,422 @@ +/** @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 if: + a) It is configured for global abort (GBPA.ABORT == 1), so all DMA is aborted, or + b) It is in global bypass but has a Reserved Memory Range (RMR) associated with + it in the IORT (the RMR describes memory expected to bypass translation). +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 or + EfiRuntimeServicesData). + +Copyright (c) Microsoft Corporation. All rights reserved. +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#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 + or EfiRuntimeServicesData. + + 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; + RMRListNode *Head; + RMRListNode *Current; + 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 + // + Head = GetIortAcpiTableRmrList (IortTable); + if (Head == NULL) { + UT_LOG_INFO ("No RMRs Found in IORT\n"); + DEBUG ((DEBUG_INFO, "%a: No RMRs Found in IORT\n", __func__)); + return UNIT_TEST_PASSED; + } + + Current = Head; + + // + // 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"); + DEBUG ((DEBUG_ERROR, "%a: GetMemoryMap Failed\n", __func__)); + 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; + + while (Current != NULL) { + Found = FALSE; + FoundInMemoryMap = FALSE; + FoundMemoryType = 0; + EfiMemNext = EfiMemoryMap; + + UT_LOG_INFO ("Checking RMR region: Base=0x%lX, Length=0x%lX\n", Current->BaseAddress, Current->Length); + DEBUG ((DEBUG_INFO, "%a: Checking RMR region: Base=0x%lX, Length=0x%lX\n", __func__, Current->BaseAddress, Current->Length)); + + while (EfiMemNext < EfiMemoryMapEnd) { + // Check if memory range fully encompasses RMR + if ((EfiMemNext->PhysicalStart <= Current->BaseAddress) && + ((EfiMemNext->PhysicalStart + (EFI_PAGE_SIZE * EfiMemNext->NumberOfPages)) >= (Current->BaseAddress + Current->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 + ); + DEBUG (( + DEBUG_INFO, + "%a: Found encompassing memory range: Base=0x%lX, Length=0x%lX, Type=%d\n", + __func__, + EfiMemNext->PhysicalStart, + EFI_PAGE_SIZE * EfiMemNext->NumberOfPages, + EfiMemNext->Type + )); + + if ((EfiMemNext->Type == EfiReservedMemoryType) || + (EfiMemNext->Type == EfiRuntimeServicesData)) + { + 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", + Current->BaseAddress, + Current->Length + ); + DEBUG (( + DEBUG_INFO, + "%a: RMR region Base=0x%lX, Length=0x%lX was NOT found in the UEFI memory map\n", + __func__, + Current->BaseAddress, + Current->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 or RuntimeServicesData)! Memory type found: %d\n", + Current->BaseAddress, + Current->BaseAddress + Current->Length, + FoundMemoryType + ); + DEBUG (( + DEBUG_ERROR, + "%a: RMR between 0x%lX and 0x%lX NOT found with an acceptable memory type (Reserved or RuntimeServicesData)! Memory type found: %d\n", + __func__, + Current->BaseAddress, + Current->BaseAddress + Current->Length, + FoundMemoryType + )); + TestStatus = UNIT_TEST_ERROR_TEST_FAILED; + } + + Current = Current->Next; + } + + UT_LOG_INFO ("%a: Result=%d (%a)\n", __func__, TestStatus, (TestStatus == UNIT_TEST_PASSED) ? "PASSED" : "FAILED"); + DEBUG ((DEBUG_INFO, "%a: Result=%d (%a)\n", __func__, TestStatus, (TestStatus == UNIT_TEST_PASSED) ? "PASSED" : "FAILED")); + + 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) when either: + a) It is configured for global abort (GBPA.ABORT == 1), so all DMA is aborted, or + b) It is in global bypass but has a Reserved Memory Range (RMR) associated with + it in the IORT (the RMR describes memory expected to bypass translation). + 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); + DEBUG ((DEBUG_INFO, "%a: Found %d SMMUv3 units in IORT\n", __func__, 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]); + DEBUG ((DEBUG_INFO, "%a: Checking SMMUv3 at base address 0x%lX\n", __func__, SmmuBaseAddresses[Iterator])); + + // + // Read CR0 register + // + Cr0Value = MmioRead32 ((UINTN)(SmmuBaseAddresses[Iterator] + SMMU_CR0)); + UT_LOG_INFO ("CR0 Register Value: 0x%X\n", Cr0Value); + DEBUG ((DEBUG_INFO, "%a: CR0 Register Value: 0x%X\n", __func__, Cr0Value)); + + // + // Check SMMUEN bit (bit 0) + // + SmmuEnBit = Cr0Value & SMMU_CR0_SMMUEN; + UT_LOG_INFO ("SMMUEN bit: %d\n", SmmuEnBit); + DEBUG ((DEBUG_INFO, "%a: SMMUEN bit: %d\n", __func__, 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); + DEBUG ((DEBUG_INFO, "%a: GBPA Register Value: 0x%X, ABORT bit: %d\n", __func__, 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]); + DEBUG ((DEBUG_INFO, "%a: SMMUEN is disabled but global abort (GBPA.ABORT) is set for SMMUv3 at base address 0x%lX. SMMU is DMA-safe.\n", __func__, SmmuBaseAddresses[Iterator])); + continue; + } + + // + // SMMU is in global bypass (not abort) and not enabled. This is permitted + // only if the SMMU has a Reserved Memory Range (RMR) associated with it in + // the IORT, since the RMR describes memory that is expected to bypass + // translation. + // + if (SmmuHasAssociatedRmr (IortTable, SmmuBaseAddresses[Iterator])) { + UT_LOG_INFO ("SMMUEN is disabled and SMMU is in global bypass, but an RMR is associated with SMMUv3 at base address 0x%lX. This is permitted.\n", SmmuBaseAddresses[Iterator]); + DEBUG ((DEBUG_INFO, "%a: SMMUEN is disabled and SMMU is in global bypass, but an RMR is associated with SMMUv3 at base address 0x%lX. This is permitted.\n", __func__, SmmuBaseAddresses[Iterator])); + continue; + } + + UT_LOG_ERROR ("SMMUEN bit is disabled, global abort is not set, and no RMR is associated for SMMUv3 at base address 0x%lX\n", SmmuBaseAddresses[Iterator]); + DEBUG ((DEBUG_ERROR, "%a: SMMUEN bit is disabled, global abort is not set, and no RMR is associated for SMMUv3 at base address 0x%lX\n", __func__, 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); + DEBUG ((DEBUG_INFO, "%a: CMDQEN bit: %d\n", __func__, CmdQEnBit ? 1 : 0)); + if (CmdQEnBit == 0) { + UT_LOG_ERROR ("CMDQEN bit is disabled for SMMUv3 at base address 0x%lX\n", SmmuBaseAddresses[Iterator]); + DEBUG ((DEBUG_ERROR, "%a: CMDQEN bit is disabled for SMMUv3 at base address 0x%lX\n", __func__, 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); + DEBUG ((DEBUG_INFO, "%a: EVTQEN bit: %d\n", __func__, EvtQEnBit ? 1 : 0)); + if (EvtQEnBit == 0) { + UT_LOG_ERROR ("EVTQEN bit is disabled for SMMUv3 at base address 0x%lX\n", SmmuBaseAddresses[Iterator]); + DEBUG ((DEBUG_ERROR, "%a: EVTQEN bit is disabled for SMMUv3 at base address 0x%lX\n", __func__, 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); + DEBUG ((DEBUG_INFO, "%a: STRTAB_BASE Register Value: 0x%lX\n", __func__, 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); + DEBUG ((DEBUG_INFO, "%a: STRTAB_BASE Address: 0x%lX\n", __func__, StrTabBaseAddr)); + if (StrTabBaseAddr == 0) { + UT_LOG_ERROR ("STRTAB_BASE is NULL for SMMUv3 at base address 0x%lX\n", SmmuBaseAddresses[Iterator]); + DEBUG ((DEBUG_ERROR, "%a: STRTAB_BASE is NULL for SMMUv3 at base address 0x%lX\n", __func__, 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); + DEBUG ((DEBUG_INFO, "%a: GERROR Register Value: 0x%X\n", __func__, GError)); + if (GError != 0) { + UT_LOG_ERROR ("GERROR register is non-zero for SMMUv3 at base address 0x%lX\n", SmmuBaseAddresses[Iterator]); + DEBUG ((DEBUG_ERROR, "%a: GERROR register is non-zero for SMMUv3 at base address 0x%lX\n", __func__, SmmuBaseAddresses[Iterator])); + TestStatus = UNIT_TEST_ERROR_TEST_FAILED; + } + } + + UT_LOG_INFO ("%a: Result=%d (%a)\n", __func__, TestStatus, (TestStatus == UNIT_TEST_PASSED) ? "PASSED" : "FAILED"); + DEBUG ((DEBUG_INFO, "%a: Result=%d (%a)\n", __func__, TestStatus, (TestStatus == UNIT_TEST_PASSED) ? "PASSED" : "FAILED")); + + 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..6faf879966 --- /dev/null +++ b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/DmaProtection.h @@ -0,0 +1,127 @@ +/** @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 + +// +// RMR List Node Structure for tracking Reserved Memory Ranges +// +typedef struct _RMRListNode { + UINT64 BaseAddress; + UINT64 Length; + struct _RMRListNode *Next; +} RMRListNode; + +// +// 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 to find all RMR (Reserved Memory Range) nodes. + + @param[in] IortTable Pointer to the IORT table. + + @retval Pointer to head of linked list of RMR entries, or NULL if none found. +**/ +RMRListNode * +EFIAPI +GetIortAcpiTableRmrList ( + IN EFI_ACPI_DESCRIPTION_HEADER *IortTable + ); + +/** + Determine whether the given SMMUv3 (identified by its base address) has at + least one Reserved Memory Range (RMR) node associated with it via the RMR + node's ID mappings. + + @param[in] IortTable Pointer to the IORT table. + @param[in] SmmuBase Base address of the SMMUv3 to check. + + @retval TRUE At least one RMR node references the specified SMMUv3. + @retval FALSE No RMR node references the specified SMMUv3, or inputs invalid. +**/ +BOOLEAN +EFIAPI +SmmuHasAssociatedRmr ( + IN EFI_ACPI_DESCRIPTION_HEADER *IortTable, + IN UINT64 SmmuBase + ); + +#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..db4156d322 --- /dev/null +++ b/UefiTestingPkg/AuditTests/DMAProtectionAudit/UEFI/SMMU/IortAcpiTable.c @@ -0,0 +1,268 @@ +/** @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 "../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 to find all RMR (Reserved Memory Range) nodes + and return a linked list of memory ranges. + + This function iterates through all nodes in the IORT table looking for + RMR nodes (type 0x6). For each RMR node found, it extracts all memory + range descriptors and adds them to the returned linked list. + + @param[in] IortTable Pointer to the IORT table. + + @retval Pointer to head of linked list of RMR entries, or NULL if none found. +**/ +RMRListNode * +EFIAPI +GetIortAcpiTableRmrList ( + IN EFI_ACPI_DESCRIPTION_HEADER *IortTable + ) +{ + 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; + RMRListNode *Head; + RMRListNode *Current; + RMRListNode *NewNode; + UINT32 Count; + UINT32 MemRangeIndex; + + if (IortTable == NULL) { + DEBUG ((DEBUG_ERROR, "%a: IORT table not available\n", __func__)); + return NULL; + } + + Iort = (EFI_ACPI_6_0_IO_REMAPPING_TABLE *)IortTable; + Node = (EFI_ACPI_6_0_IO_REMAPPING_NODE *)((UINT8 *)Iort + Iort->NodeOffset); + + Head = NULL; + Current = NULL; + + // 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 (RMRListNode)); + if (NewNode == NULL) { + DEBUG ((DEBUG_ERROR, "%a: Failed to allocate RMRListNode\n", __func__)); + // Return what we have so far + return Head; + } + + NewNode->BaseAddress = MemRangeDesc[MemRangeIndex].Base; + NewNode->Length = MemRangeDesc[MemRangeIndex].Length; + NewNode->Next = NULL; + + // Add to linked list + if (Head == NULL) { + Head = NewNode; + Current = NewNode; + } else { + Current->Next = NewNode; + Current = NewNode; + } + } + } + } + + // Move to the next node + Node = (EFI_ACPI_6_0_IO_REMAPPING_NODE *)((UINT8 *)Node + Node->Length); + } + + return Head; +} + +/** + Determine whether the given SMMUv3 (identified by its base address) has at + least one Reserved Memory Range (RMR) node associated with it via the RMR + node's ID mappings. + + Each RMR node contains an ID mapping table; each mapping's OutputReference is + the byte offset (from the start of the IORT table) of the node the RMR is + associated with. This function walks every RMR node's ID mappings and checks + whether any of them reference an SMMUv3 node whose Base matches SmmuBase. + + @param[in] IortTable Pointer to the IORT table. + @param[in] SmmuBase Base address of the SMMUv3 to check. + + @retval TRUE At least one RMR node references the specified SMMUv3. + @retval FALSE No RMR node references the specified SMMUv3, or inputs invalid. +**/ +BOOLEAN +EFIAPI +SmmuHasAssociatedRmr ( + IN EFI_ACPI_DESCRIPTION_HEADER *IortTable, + IN UINT64 SmmuBase + ) +{ + 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_ID_TABLE *IdMapping; + EFI_ACPI_6_0_IO_REMAPPING_NODE *OutputNode; + EFI_ACPI_6_0_IO_REMAPPING_SMMU3_NODE *SmmuNode; + UINT32 Count; + UINT32 MappingIndex; + + if (IortTable == NULL) { + return FALSE; + } + + 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; + IdMapping = (EFI_ACPI_6_0_IO_REMAPPING_ID_TABLE *)((UINT8 *)RmrNode + RmrNode->Node.IdReference); + + // Walk each ID mapping and resolve the referenced output node + for (MappingIndex = 0; MappingIndex < RmrNode->Node.NumIdMappings; MappingIndex++) { + OutputNode = (EFI_ACPI_6_0_IO_REMAPPING_NODE *)((UINT8 *)Iort + IdMapping[MappingIndex].OutputReference); + + if (OutputNode->Type == EFI_ACPI_IORT_TYPE_SMMUv3) { + SmmuNode = (EFI_ACPI_6_0_IO_REMAPPING_SMMU3_NODE *)OutputNode; + if (SmmuNode->Base == SmmuBase) { + return TRUE; + } + } + } + } + + // Move to the next node + Node = (EFI_ACPI_6_0_IO_REMAPPING_NODE *)((UINT8 *)Node + Node->Length); + } + + return FALSE; +} 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