diff --git a/ArmPkg/Include/Library/ArmMmuLib.h b/ArmPkg/Include/Library/ArmMmuLib.h index 2ce948e8db1d..1cea71ee6e88 100644 --- a/ArmPkg/Include/Library/ArmMmuLib.h +++ b/ArmPkg/Include/Library/ArmMmuLib.h @@ -71,4 +71,59 @@ ArmSetMemoryAttributes ( IN UINT64 AttributeMask ); +#ifdef MDE_CPU_AARCH64 + +/** + Configure the protection attribute for the page tables + describing the memory region. + + The IPA space of a Realm is divided into two halves: + - Protected IPA space and + - Unprotected IPA space. + + Software in a Realm should treat the most significant bit of an + IPA as a protection attribute. + + A Protected IPA is an address in the lower half of a Realms IPA + space. The most significant bit of a Protected IPA is 0. + + An Unprotected IPA is an address in the upper half of a Realms + IPA space. The most significant bit of an Unprotected IPA is 1. + + Note: + - Configuring the memory region as Unprotected IPA enables the + Realm to share the memory region with the Host. + - This function updates the page table entries to reflect the + protection attribute. + - A separate call to transition the memory range using the Realm + Service Interface (RSI) RSI_IPA_STATE_SET command is additionally + required and is expected to be done outside this function. + - The caller must ensure that this function call is invoked by code + executing within the Realm. + + @param [in] BaseAddress Base address of the memory region. + @param [in] Length Length of the memory region. + @param [in] IpaWidth IPA width of the Realm. + @param [in] Share If TRUE, set the most significant + bit of the IPA to configure the memory + region as Unprotected IPA. + If FALSE, clear the most significant + bit of the IPA to configure the memory + region as Protected IPA. + + @retval EFI_SUCCESS IPA protection attribute updated. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_UNSUPPORTED RME is not supported. +**/ +EFI_STATUS +EFIAPI +SetMemoryProtectionAttribute ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINT64 Length, + IN UINT64 IpaWidth, + IN BOOLEAN Share + ); + +#endif + #endif // ARM_MMU_LIB_H_ diff --git a/ArmPkg/Include/Library/ArmSmcLib.h b/ArmPkg/Include/Library/ArmSmcLib.h index beef0175c35c..e80b74671a64 100644 --- a/ArmPkg/Include/Library/ArmSmcLib.h +++ b/ArmPkg/Include/Library/ArmSmcLib.h @@ -1,10 +1,13 @@ /** @file * * Copyright (c) 2021, NUVIA Inc. All rights reserved.
-* Copyright (c) 2012-2014, ARM Limited. All rights reserved. +* Copyright (c) 2012-2023, Arm Limited. All rights reserved. * * SPDX-License-Identifier: BSD-2-Clause-Patent * +* @par Reference(s): +* - SMC Calling Convention (SMCCC), ARM DEN 0028E, EAC0, 1.4 +* (https://developer.arm.com/documentation/den0028/e/) **/ #ifndef ARM_SMC_LIB_H_ @@ -13,6 +16,18 @@ /** * The size of the SMC arguments are different between AArch64 and AArch32. * The native size is used for the arguments. + * According to the SMCCC Section 2.6 SMC32/HVC32 argument passing + * When an SMC32/HVC32 call is made from AArch32: + * - Arguments are passed in registers R1-R7. + * - Results are returned in R0-R7. + * When an SMC32/HVC32 call is made from AArch64: + * - Arguments are passed in registers W1-W7. + * - Results are returned in W0-W7. + * + * According to SMCCC Section 2.7 SMC64/HVC64 argument passing + * When an SMC64/HVC64 call is made from AArch64: + * - Arguments are passed in registers X1-X17. + * - Results are returned in X0-X17. */ typedef struct { UINTN Arg0; @@ -23,13 +38,42 @@ typedef struct { UINTN Arg5; UINTN Arg6; UINTN Arg7; + #ifdef MDE_CPU_AARCH64 + UINTN Arg8; + UINTN Arg9; + UINTN Arg10; + UINTN Arg11; + #endif } ARM_SMC_ARGS; /** Trigger an SMC call - SMC calls can take up to 7 arguments and return up to 4 return values. - Therefore, the 4 first fields in the ARM_SMC_ARGS structure are used + According to the SMCCC Section 2.6 SMC32/HVC32 argument passing + When an SMC32/HVC32 call is made from AArch32: + - Arguments are passed in registers R1-R7. + - Results are returned in R0-R7. + When an SMC32/HVC32 call is made from AArch64: + - Arguments are passed in registers W1-W7. + - Results are returned in W0-W7. + + According to SMCCC Section 2.7 SMC64/HVC64 argument passing + When an SMC64/HVC64 call is made from AArch64: + - Arguments are passed in registers X1-X17. + - Results are returned in X0-X17. + + This means SMC calls can take up to 7/17 arguments and return up + to 7/17 return values. + + However, the current use-case: + - For SMC32/HVC32 calls made from AArch32/AArch64 up to 7 arguments + and 4 return values are required. Therefore, limit the maximum + arguments to 7 and return values to 4. + - For AMC64/HVC64 calls made from AArch64 up to 11 arguments and + return values are required. Therefore, limit the maximum arguments + and return values to 11. + + The fields in the ARM_SMC_ARGS structure are used for both input and output values. **/ diff --git a/ArmPkg/Library/ArmLib/AArch64/AArch64Lib.c b/ArmPkg/Library/ArmLib/AArch64/AArch64Lib.c index 53b202ff34a8..bed2ca24fe3e 100644 --- a/ArmPkg/Library/ArmLib/AArch64/AArch64Lib.c +++ b/ArmPkg/Library/ArmLib/AArch64/AArch64Lib.c @@ -1,7 +1,7 @@ /** @file Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.
- Portions copyright (c) 2011 - 2014, ARM Ltd. All rights reserved.
+ Portions copyright (c) 2011 - 2023, Arm Limited. All rights reserved.
Copyright (c) 2021, NUVIA Inc. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent @@ -95,3 +95,17 @@ ArmHasEte ( // The ID_AA64DFR0_EL1.TraceVer field identifies the presence of FEAT_ETE. return ((ArmReadIdAA64Dfr0 () & AARCH64_DFR0_TRACEVER) != 0); } + +/** Checks if RME is implemented. + + @retval TRUE RME is implemented. + @retval FALSE RME is not implemented. +**/ +BOOLEAN +EFIAPI +ArmHasRme ( + VOID + ) +{ + return ((ArmReadIdAA64Pfr0 () & AARCH64_PFR0_RME) != 0); +} diff --git a/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c b/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c index 45b7c8134165..ace042433409 100644 --- a/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c +++ b/ArmPkg/Library/ArmMmuLib/AArch64/ArmMmuLibCore.c @@ -7,6 +7,10 @@ * * SPDX-License-Identifier: BSD-2-Clause-Patent * +* @par Glossary: +* - Rsi or RSI - Realm Service Interface +* - IPA - Intermediate Physical Address +* - RIPAS - Realm IPA state **/ #include @@ -773,3 +777,89 @@ ArmMmuBaseLibConstructor ( return RETURN_SUCCESS; } + +/** + Configure the protection attribute for the page tables + describing the memory region. + + The IPA space of a Realm is divided into two halves: + - Protected IPA space and + - Unprotected IPA space. + + Software in a Realm should treat the most significant bit of an + IPA as a protection attribute. + + A Protected IPA is an address in the lower half of a Realms IPA + space. The most significant bit of a Protected IPA is 0. + + An Unprotected IPA is an address in the upper half of a Realms + IPA space. The most significant bit of an Unprotected IPA is 1. + + Note: + - Configuring the memory region as Unprotected IPA enables the + Realm to share the memory region with the Host. + - This function updates the page table entries to reflect the + protection attribute. + - A separate call to transition the memory range using the Realm + Service Interface (RSI) RSI_IPA_STATE_SET command is additionally + required and is expected to be done outside this function. + - The caller must ensure that this function call is invoked by code + executing within the Realm. + + @param [in] BaseAddress Base address of the memory region. + @param [in] Length Length of the memory region. + @param [in] IpaWidth IPA width of the Realm. + @param [in] Share If TRUE, set the most significant + bit of the IPA to configure the memory + region as Unprotected IPA. + If FALSE, clear the most significant + bit of the IPA to configure the memory + region as Protected IPA. + + @retval EFI_SUCCESS IPA protection attribute updated. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_UNSUPPORTED RME is not supported. +**/ +EFI_STATUS +EFIAPI +SetMemoryProtectionAttribute ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINT64 Length, + IN UINT64 IpaWidth, + IN BOOLEAN Share + ) +{ + UINT64 Attributes; + UINT64 Mask; + UINT64 ProtectionAttributeMask; + + if ((Length == 0) || (IpaWidth == 0)) { + return EFI_INVALID_PARAMETER; + } + + if (!ArmHasRme ()) { + return EFI_UNSUPPORTED; + } + + /* Software in a Realm should treat the most significant bit of an + IPA as a protection attribute. + */ + ProtectionAttributeMask = 1ULL << (IpaWidth - 1); + + if (Share) { + Attributes = ProtectionAttributeMask; + Mask = ~TT_ADDRESS_MASK_BLOCK_ENTRY; + } else { + Attributes = 0; + Mask = ~(TT_ADDRESS_MASK_BLOCK_ENTRY | ProtectionAttributeMask); + } + + return UpdateRegionMapping ( + BaseAddress, + Length, + Attributes, + Mask, + ArmGetTTBR0BaseAddress (), + TRUE + ); +} diff --git a/ArmPkg/Library/ArmSmcLib/AArch64/ArmSmc.S b/ArmPkg/Library/ArmSmcLib/AArch64/ArmSmc.S index a4443a41fe6c..da14f5021090 100644 --- a/ArmPkg/Library/ArmSmcLib/AArch64/ArmSmc.S +++ b/ArmPkg/Library/ArmSmcLib/AArch64/ArmSmc.S @@ -1,8 +1,11 @@ // -// Copyright (c) 2012-2014, ARM Limited. All rights reserved. +// Copyright (c) 2012-2023, Arm Limited. All rights reserved. // // SPDX-License-Identifier: BSD-2-Clause-Patent // +// @par Reference(s): +// - SMC Calling Convention (SMCCC), ARM DEN 0028E, EAC0, 1.4 +// (https://developer.arm.com/documentation/den0028/e/) // #include @@ -12,6 +15,8 @@ ASM_FUNC(ArmCallSmc) str x0, [sp, #-16]! // Load the SMC arguments values into the appropriate registers + ldp x10, x11, [x0, #80] + ldp x8, x9, [x0, #64] ldp x6, x7, [x0, #48] ldp x4, x5, [x0, #32] ldp x2, x3, [x0, #16] @@ -19,14 +24,17 @@ ASM_FUNC(ArmCallSmc) smc #0 - // Pop the ARM_SMC_ARGS structure address from the stack into x9 - ldr x9, [sp], #16 + // Pop the ARM_SMC_ARGS structure address from the stack into x13 + ldr x13, [sp], #16 // Store the SMC returned values into the ARM_SMC_ARGS structure. - // A SMC call can return up to 4 values - we do not need to store back x4-x7. - stp x2, x3, [x9, #16] - stp x0, x1, [x9, #0] - - mov x0, x9 + stp x10, x11, [x13, #80] + stp x8, x9, [x13, #64] + stp x6, x7, [x13, #48] + stp x4, x5, [x13, #32] + stp x2, x3, [x13, #16] + stp x0, x1, [x13, #0] + + mov x0, x13 ret diff --git a/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.c b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.c new file mode 100644 index 000000000000..8104bb4dbc10 --- /dev/null +++ b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.c @@ -0,0 +1,869 @@ +/** @file + The protocol provides support to allocate, free, map and umap a DMA buffer + for bus master (e.g PciHostBridge). When the execution context is a Realm, + the DMA operations must be performed on buffers that are shared with the Host. + Hence the RAMP protocol is used to manage the sharing of the DMA buffers or + in some cases to bounce the buffers. + + Copyright (c) 2017, AMD Inc. All rights reserved.
+ Copyright (c) 2017, Intel Corporation. All rights reserved.
+ Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "ArmCcaIoMmu.h" + +/** List of the MAP_INFO structures that have been set up by IoMmuMap() and not + yet torn down by IoMmuUnmap(). The list represents the full set of mappings + currently in effect. +*/ +STATIC LIST_ENTRY mMapInfos = INITIALIZE_LIST_HEAD_VARIABLE (mMapInfos); + +/** ASCII names for EDKII_IOMMU_OPERATION constants, for debug logging. +*/ +STATIC CONST CHAR8 *CONST +mBusMasterOperationName[EdkiiIoMmuOperationMaximum] = { + "Read", + "Write", + "CommonBuffer", + "Read64", + "Write64", + "CommonBuffer64" +}; + +/** Pointer to the Realm Aperture Management Protocol +*/ +extern EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL *mRamp; + +/** + Given the host address find a mapping node in the linked list. + + @param [in] HostAddress Host address. + + @return Pointer to the MapInfo node if found, otherwise NULL. +**/ +STATIC +MAP_INFO * +EFIAPI +FindMappingByHostAddress ( + IN VOID *HostAddress + ) +{ + LIST_ENTRY *Node; + LIST_ENTRY *NextNode; + MAP_INFO *MapInfo; + + for (Node = GetFirstNode (&mMapInfos); Node != &mMapInfos; Node = NextNode) { + NextNode = GetNextNode (&mMapInfos, Node); + MapInfo = CR (Node, MAP_INFO, Link, MAP_INFO_SIG); + if (MapInfo->HostAddress == HostAddress) { + return MapInfo; + } + } + + return NULL; +} + +/** + Map a shared buffer + + @param [in] Operation IoMMU operation to perform. + @param [in] HostAddress Pointer to the Host buffer. + @param [in] NumberOfBytes Number of bytes to map. + @param [in] BbAddress Bounce buffer address. + @param [in] BbPages Number of pages covering the bounce buffer. + @param [out] Mapping Pointer to the MapInfo node. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory. +**/ +STATIC +EFI_STATUS +MapSharedBuffer ( + IN EDKII_IOMMU_OPERATION Operation, + IN VOID *HostAddress, + IN UINTN NumberOfBytes, + IN EFI_PHYSICAL_ADDRESS BbAddress, + IN UINTN BbPages, + OUT MAP_INFO **Mapping + ) +{ + EFI_STATUS Status; + MAP_INFO *MapInfo; + + if (BbPages != EFI_SIZE_TO_PAGES (NumberOfBytes)) { + return EFI_INVALID_PARAMETER; + } + + // Allocate a MAP_INFO structure to remember the mapping when Unmap() is + // called later. + MapInfo = AllocateZeroPool (sizeof (MAP_INFO)); + if (MapInfo == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + InitializeListHead (&MapInfo->Link); + + // Initialize the MAP_INFO structure, except the NonParAddress field + MapInfo->Signature = MAP_INFO_SIG; + MapInfo->Operation = Operation; + MapInfo->NumberOfBytes = NumberOfBytes; + MapInfo->NumberOfPages = BbPages; + MapInfo->HostAddress = HostAddress; + MapInfo->BbAddress = BbAddress; + + // Open aperture here + Status = mRamp->OpenAperture ( + BbAddress, + BbPages, + &MapInfo->ApertureRef + ); + if (EFI_ERROR (Status)) { + goto FreeMapInfo; + } + + // Track all MAP_INFO structures. + InsertHeadList (&mMapInfos, &MapInfo->Link); + *Mapping = MapInfo; + return Status; + +FreeMapInfo: + FreePool (MapInfo); + return Status; +} + +/** + Unmap a shared buffer. + + @param [in] MapInfo Pointer to the MapInfo node. + @param [in] MemoryMapLocked The function is executing on the stack of + gBS->ExitBootServices(); changes to the UEFI + memory map are forbidden. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +STATIC +EFI_STATUS +EFIAPI +UnMapSharedBuffer ( + IN MAP_INFO *MapInfo, + IN BOOLEAN MemoryMapLocked + ) +{ + EFI_STATUS Status; + + if (MapInfo == NULL) { + return EFI_INVALID_PARAMETER; + } + + DEBUG (( + DEBUG_VERBOSE, + "%a: HostAddress = 0x%p, BbAddress = 0x%p\n", + __func__, + MapInfo->HostAddress, + MapInfo->BbAddress + )); + Status = mRamp->CloseAperture (MapInfo->ApertureRef); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "Failed to close aperture. Status = %r\n", + Status + )); + } + + RemoveEntryList (&MapInfo->Link); + + if (!MemoryMapLocked) { + FreePool (MapInfo); + } + + return Status; +} + +/** + Provides the controller-specific addresses required to access system memory + from a DMA bus master. On guest Realms, the DMA operations must be performed + on shared buffer hence we allocate a bounce buffer to map the HostAddress to + a DeviceAddress. The Realm Aperture Management protocol is then involved to + open the aperture for sharing the buffer pages with the Host OS. + + @param This The protocol instance pointer. + @param Operation Indicates if the bus master is going to read or + write to system memory. + @param HostAddress The system memory address to map to the PCI + controller. + @param NumberOfBytes On input the number of bytes to map. On output + the number of bytes that were mapped. + @param DeviceAddress The resulting map address for the bus master + PCI controller to use to access the hosts + HostAddress. + @param Mapping A resulting value to pass to Unmap(). + + @retval EFI_SUCCESS The range was mapped for the returned + NumberOfBytes. + @retval EFI_UNSUPPORTED The HostAddress cannot be mapped as a common + buffer. + @retval EFI_INVALID_PARAMETER One or more parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a + lack of resources. + @retval EFI_DEVICE_ERROR The system hardware could not map the requested + address. + +**/ +EFI_STATUS +EFIAPI +IoMmuMap ( + IN EDKII_IOMMU_PROTOCOL *This, + IN EDKII_IOMMU_OPERATION Operation, + IN VOID *HostAddress, + IN OUT UINTN *NumberOfBytes, + OUT EFI_PHYSICAL_ADDRESS *DeviceAddress, + OUT VOID **Mapping + ) +{ + EFI_STATUS Status; + MAP_INFO *MapInfo; + EFI_PHYSICAL_ADDRESS BbAddress; + UINTN Pages; + EFI_ALLOCATE_TYPE AllocateType; + + DEBUG (( + DEBUG_VERBOSE, + "%a: Operation=%a Host=0x%p Bytes=0x%lx\n", + __func__, + ((Operation >= 0 && + Operation < ARRAY_SIZE (mBusMasterOperationName)) ? + mBusMasterOperationName[Operation] : + "Invalid"), + HostAddress, + (UINT64)((NumberOfBytes == NULL) ? 0 : *NumberOfBytes) + )); + + if ((HostAddress == NULL) || + (NumberOfBytes == NULL) || + (DeviceAddress == NULL) || + (Mapping == NULL) || + (Operation >= EdkiiIoMmuOperationMaximum) || + (Operation < EdkiiIoMmuOperationBusMasterRead)) + { + return EFI_INVALID_PARAMETER; + } + + BbAddress = MAX_ADDRESS; + Pages = EFI_SIZE_TO_PAGES (*NumberOfBytes); + AllocateType = AllocateAnyPages; + switch (Operation) { + // For BusMasterRead[64] and BusMasterWrite[64] operations, a bounce buffer + // is necessary as the original buffer may not meet the page start/end and + // page size alignment requirements. Also we need to consider the case where + // the original buffer crosses the 4GB limit. + case EdkiiIoMmuOperationBusMasterRead: + case EdkiiIoMmuOperationBusMasterWrite: + BbAddress = BASE_4GB - 1; + AllocateType = AllocateMaxAddress; + // fall through + case EdkiiIoMmuOperationBusMasterRead64: + case EdkiiIoMmuOperationBusMasterWrite64: + // Allocate a bounce buffer. + Status = gBS->AllocatePages ( + AllocateType, + EfiBootServicesData, + Pages, + &BbAddress + ); + if (EFI_ERROR (Status)) { + goto Failed; + } + + // Open aperture here + Status = MapSharedBuffer ( + Operation, + HostAddress, + *NumberOfBytes, + BbAddress, + Pages, + &MapInfo + ); + if (EFI_ERROR (Status)) { + goto FreeBounceBuffer; + } + + break; + + // For BusMasterCommonBuffer[64] operations, the buffer is already allocated + // and mapped in a call to AllocateBuffer(). So, we only need to return the + // device address and the mapping info + case EdkiiIoMmuOperationBusMasterCommonBuffer: + // fall through + case EdkiiIoMmuOperationBusMasterCommonBuffer64: + MapInfo = FindMappingByHostAddress (HostAddress); + if (MapInfo == NULL) { + ASSERT (MapInfo == NULL); + goto Failed; + } + + BbAddress = MapInfo->BbAddress; + break; + + default: + // Operation is invalid + Status = EFI_INVALID_PARAMETER; + goto Failed; + } // switch + + // If this is a read operation from the Bus Master's point of view, + // then copy the contents of the real buffer into the mapped buffer + // so the Bus Master can read the contents of the real buffer. + // No special action is needed for BusMasterCommonBuffer[64] operations. + if ((Operation == EdkiiIoMmuOperationBusMasterRead) || + (Operation == EdkiiIoMmuOperationBusMasterRead64)) + { + CopyMem ( + (VOID *)(UINTN)BbAddress, + (VOID *)(UINTN)HostAddress, + MapInfo->NumberOfBytes + ); + } + + // Populate output parameters. + *DeviceAddress = BbAddress; + *Mapping = MapInfo; + + DEBUG (( + DEBUG_VERBOSE, + "%a: Mapping=0x%p HostAddress = 0x%p BBAddress = 0x%Lx Pages=0x%Lx\n", + __func__, + MapInfo, + HostAddress, + MapInfo->BbAddress, + MapInfo->NumberOfPages + )); + + return EFI_SUCCESS; + +FreeBounceBuffer: + gBS->FreePages (BbAddress, Pages); + +Failed: + *NumberOfBytes = 0; + return Status; +} + +/** + Completes the Map() operation and releases any corresponding resources. + + This is an internal worker function that only extends the Map() API with + the MemoryMapLocked parameter. + + @param This The protocol instance pointer. + @param MapInfo The mapping value returned from Map(). + @param MemoryMapLocked The function is executing on the stack of + gBS->ExitBootServices(); changes to the UEFI + memory map are forbidden. + + @retval EFI_SUCCESS The range was unmapped. + @retval EFI_INVALID_PARAMETER Mapping is not a value that was returned by + Map(). + @retval EFI_DEVICE_ERROR The data was not committed to the target system + memory. +**/ +STATIC +EFI_STATUS +EFIAPI +IoMmuUnmapWorker ( + IN EDKII_IOMMU_PROTOCOL *This, + IN MAP_INFO *MapInfo, + IN BOOLEAN MemoryMapLocked + ) +{ + EFI_STATUS Status; + PHYSICAL_ADDRESS BbAddress; + UINTN Pages; + + DEBUG (( + DEBUG_VERBOSE, + "%a: MapInfo=0x%p MemoryMapLocked=%d\n", + __func__, + MapInfo, + MemoryMapLocked + )); + + if (MapInfo == NULL) { + return EFI_INVALID_PARAMETER; + } + + BbAddress = MapInfo->BbAddress; + Pages = MapInfo->NumberOfPages; + + // For BusMasterWrite[64] operations and BusMasterCommonBuffer[64] operations + // we have to copy the results, ultimately to the original place (i.e., + // "MapInfo->HostAddress"). + // No special operaton is needed for BusMasterCommonBuffer[64] operations. + switch (MapInfo->Operation) { + case EdkiiIoMmuOperationBusMasterCommonBuffer: + case EdkiiIoMmuOperationBusMasterCommonBuffer64: + ASSERT (BbAddress == (PHYSICAL_ADDRESS)MapInfo->HostAddress); + break; + case EdkiiIoMmuOperationBusMasterWrite: + case EdkiiIoMmuOperationBusMasterWrite64: + CopyMem ( + (VOID *)(UINTN)MapInfo->HostAddress, + (VOID *)(UINTN)BbAddress, + MapInfo->NumberOfBytes + ); + break; + + default: + // nothing to do for BusMasterRead[64] operations + break; + } + + // For all other operations, fill the late bounce buffer with zeros, and + // then release it (unless the UEFI memory map is locked). + if ((MapInfo->Operation != EdkiiIoMmuOperationBusMasterCommonBuffer) && + (MapInfo->Operation != EdkiiIoMmuOperationBusMasterCommonBuffer64)) + { + ZeroMem ( + (VOID *)(UINTN)BbAddress, + EFI_PAGES_TO_SIZE (Pages) + ); + + // UnMapSharedPages + Status = UnMapSharedBuffer (MapInfo, MemoryMapLocked); + ASSERT_EFI_ERROR (Status); + + if (!MemoryMapLocked) { + gBS->FreePages (BbAddress, Pages); + } + } + + return Status; +} + +/** + Completes the Map() operation and releases any corresponding resources. + + @param This The protocol instance pointer. + @param Mapping The mapping value returned from Map(). + + @retval EFI_SUCCESS The range was unmapped. + @retval EFI_INVALID_PARAMETER Mapping is not a value that was returned by + Map(). + @retval EFI_DEVICE_ERROR The data was not committed to the target system + memory. +**/ +EFI_STATUS +EFIAPI +IoMmuUnmap ( + IN EDKII_IOMMU_PROTOCOL *This, + IN VOID *Mapping + ) +{ + return IoMmuUnmapWorker ( + This, + (MAP_INFO *)Mapping, + FALSE // MemoryMapLocked + ); +} + +/** + Allocates pages that are suitable for an OperationBusMasterCommonBuffer or + OperationBusMasterCommonBuffer64 mapping. + + @param This The protocol instance pointer. + @param Type This parameter is not used and must be ignored. + @param MemoryType The type of memory to allocate, + EfiBootServicesData or EfiRuntimeServicesData. + @param Pages The number of pages to allocate. + @param HostAddress A pointer to store the base system memory + address of the allocated range. + @param Attributes The requested bit mask of attributes for the + allocated range. + + @retval EFI_SUCCESS The requested memory pages were allocated. + @retval EFI_UNSUPPORTED Attributes is unsupported. The only legal + attribute bits are MEMORY_WRITE_COMBINE and + MEMORY_CACHED. + @retval EFI_INVALID_PARAMETER One or more parameters are invalid. + @retval EFI_OUT_OF_RESOURCES The memory pages could not be allocated. + +**/ +EFI_STATUS +EFIAPI +IoMmuAllocateBuffer ( + IN EDKII_IOMMU_PROTOCOL *This, + IN EFI_ALLOCATE_TYPE Type, + IN EFI_MEMORY_TYPE MemoryType, + IN UINTN Pages, + IN OUT VOID **HostAddress, + IN UINT64 Attributes + ) +{ + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS BbAddress; + MAP_INFO *MapInfo; + + // Validate Attributes + if ((Attributes & EDKII_IOMMU_ATTRIBUTE_INVALID_FOR_ALLOCATE_BUFFER) != 0) { + return EFI_UNSUPPORTED; + } + + // Check for invalid inputs + if (HostAddress == NULL) { + return EFI_INVALID_PARAMETER; + } + + // The only valid memory types are EfiBootServicesData + if (MemoryType != EfiBootServicesData) { + return EFI_INVALID_PARAMETER; + } + + if (Pages >= MAX_UINTN) { + return EFI_INVALID_PARAMETER; + } + + BbAddress = (UINTN)-1; + if ((Attributes & EDKII_IOMMU_ATTRIBUTE_DUAL_ADDRESS_CYCLE) == 0) { + // Limit allocations to memory below 4GB + BbAddress = SIZE_4GB - 1; + } + + Status = gBS->AllocatePages ( + AllocateMaxAddress, + MemoryType, + Pages, + &BbAddress + ); + if (EFI_ERROR (Status)) { + // Set the host address to NULL in case of error + *HostAddress = NULL; + } else { + *HostAddress = (VOID *)(UINTN)BbAddress; + Status = MapSharedBuffer ( + EdkiiIoMmuOperationBusMasterCommonBuffer, + *HostAddress, + EFI_PAGES_TO_SIZE (Pages), + BbAddress, + Pages, + &MapInfo + ); + ASSERT_EFI_ERROR (Status); + } + + return Status; +} + +/** + Frees memory that was allocated with AllocateBuffer(). + + @param This The protocol instance pointer. + @param Pages The number of pages to free. + @param HostAddress The base system memory address of the allocated + range. + + @retval EFI_SUCCESS The requested memory pages were freed. + @retval EFI_INVALID_PARAMETER The memory range specified by HostAddress and + Pages was not allocated with AllocateBuffer(). + +**/ +EFI_STATUS +EFIAPI +IoMmuFreeBuffer ( + IN EDKII_IOMMU_PROTOCOL *This, + IN UINTN Pages, + IN VOID *HostAddress + ) +{ + EFI_STATUS Status; + MAP_INFO *MapInfo; + + // Release the common buffer itself. Unmap() has re-encrypted it in-place, so + // no need to zero it. + MapInfo = FindMappingByHostAddress (HostAddress); + if (MapInfo == NULL) { + ASSERT (0); + return EFI_NOT_FOUND; + } else { + // UnMapSharedPages + Status = UnMapSharedBuffer (MapInfo, FALSE); + ASSERT_EFI_ERROR (Status); + } + + return gBS->FreePages ((UINTN)HostAddress, Pages); +} + +/** + Set IOMMU attribute for a system memory. + + If the IOMMU protocol exists, the system memory cannot be used + for DMA by default. + + When a device requests a DMA access to system memory, + the device driver need use SetAttribute() to update the IOMMU + attribute to request DMA access (read and/or write). + + The DeviceHandle is used to identify which device submits the request. + The IOMMU implementation need to translate the device path to an IOMMU device + ID, and set the IOMMU hardware register accordingly. + 1) DeviceHandle can be a standard PCI device. + The memory for BusMasterRead needs EDKII_IOMMU_ACCESS_READ set. + The memory for BusMasterWrite needs EDKII_IOMMU_ACCESS_WRITE set. + The memory for BusMasterCommonBuffer needs + EDKII_IOMMU_ACCESS_READ|EDKII_IOMMU_ACCESS_WRITE set. + After the memory is used, the memory need set 0 to keep it being + protected. + 2) DeviceHandle can be an ACPI device (ISA, I2C, SPI, etc). + The memory for DMA access need set EDKII_IOMMU_ACCESS_READ and/or + EDKII_IOMMU_ACCESS_WRITE. + + @param[in] This The protocol instance pointer. + @param[in] DeviceHandle The device initiating the DMA access + request. + @param[in] Mapping The mapping value returned from Map(). + @param[in] IoMmuAccess The IOMMU access. + + @retval EFI_INVALID_PARAMETER A parameter was invalid. + @retval EFI_UNSUPPORTED The requested operation is not supported. + @retval EFI_SUCCESS Success. + +**/ +EFI_STATUS +EFIAPI +IoMmuSetAttribute ( + IN EDKII_IOMMU_PROTOCOL *This, + IN EFI_HANDLE DeviceHandle, + IN VOID *Mapping, + IN UINT64 IoMmuAccess + ) +{ + EFI_STATUS Status; + MAP_INFO *MapInfo; + + DEBUG (( + DEBUG_VERBOSE, + "%a: Mapping=0x%p Access=%lu\n", + __func__, + Mapping, + IoMmuAccess + )); + + if (Mapping == NULL) { + return EFI_INVALID_PARAMETER; + } + + Status = EFI_SUCCESS; + + // An IoMmuAccess value of 0 is always accepted, + // validate any non-zero value. + if (IoMmuAccess != 0) { + MapInfo = (MAP_INFO *)Mapping; + + // The mapping operation already implied the access mode. + // Validate that the supplied access mode matches operation + // access mode. + switch (MapInfo->Operation) { + case EdkiiIoMmuOperationBusMasterRead: + case EdkiiIoMmuOperationBusMasterRead64: + if (IoMmuAccess != EDKII_IOMMU_ACCESS_READ) { + Status = EFI_INVALID_PARAMETER; + } + + break; + + case EdkiiIoMmuOperationBusMasterWrite: + case EdkiiIoMmuOperationBusMasterWrite64: + if (IoMmuAccess != EDKII_IOMMU_ACCESS_WRITE) { + Status = EFI_INVALID_PARAMETER; + } + + break; + + case EdkiiIoMmuOperationBusMasterCommonBuffer: + case EdkiiIoMmuOperationBusMasterCommonBuffer64: + if (IoMmuAccess != + (EDKII_IOMMU_ACCESS_READ | EDKII_IOMMU_ACCESS_WRITE)) + { + Status = EFI_INVALID_PARAMETER; + } + + break; + + default: + Status = EFI_UNSUPPORTED; + } // switch + } + + return Status; +} + +/** Arm CCA IoMMU protocol +*/ +EDKII_IOMMU_PROTOCOL mArmCcaIoMmu = { + EDKII_IOMMU_PROTOCOL_REVISION, + IoMmuSetAttribute, + IoMmuMap, + IoMmuUnmap, + IoMmuAllocateBuffer, + IoMmuFreeBuffer, +}; + +/** + Notification function that is queued when gBS->ExitBootServices() signals the + EFI_EVENT_GROUP_EXIT_BOOT_SERVICES event group. This function signals another + event, received as Context, and returns. + + Signaling an event in this context is safe. The UEFI spec allows + gBS->SignalEvent() to return EFI_SUCCESS only; EFI_OUT_OF_RESOURCES is not + listed, hence memory is not allocated. The edk2 implementation also does not + release memory (and we only have to care about the edk2 implementation + because EDKII_IOMMU_PROTOCOL is edk2-specific anyway). + + @param[in] Event Event whose notification function is being invoked. + Event is permitted to request the queueing of this + function at TPL_CALLBACK or TPL_NOTIFY task + priority level. + + @param[in] EventToSignal Identifies the EFI_EVENT to signal. EventToSignal + is permitted to request the queueing of its + notification function only at TPL_CALLBACK level. +**/ +STATIC +VOID +EFIAPI +ArmCcaIoMmuExitBoot ( + IN EFI_EVENT Event, + IN VOID *EventToSignal + ) +{ + // (1) The NotifyFunctions of all the events in + // EFI_EVENT_GROUP_EXIT_BOOT_SERVICES will have been queued before + // ArmCcaIoMmuExitBoot() is entered. + // + // (2) ArmCcaIoMmuExitBoot() is executing minimally at TPL_CALLBACK. + // + // (3) ArmCcaIoMmuExitBoot() has been queued in unspecified order relative + // to the NotifyFunctions of all the other events in + // EFI_EVENT_GROUP_EXIT_BOOT_SERVICES whose NotifyTpl is the same as + // Event's. + // + // Consequences: + // + // - If Event's NotifyTpl is TPL_CALLBACK, then some other NotifyFunctions + // queued at TPL_CALLBACK may be invoked after ArmCcaIoMmuExitBoot() + // returns. + // + // - If Event's NotifyTpl is TPL_NOTIFY, then some other NotifyFunctions + // queued at TPL_NOTIFY may be invoked after ArmCcaIoMmuExitBoot() returns; + // plus *all* NotifyFunctions queued at TPL_CALLBACK will be invoked + // strictly after all NotifyFunctions queued at TPL_NOTIFY, including + // ArmCcaIoMmuExitBoot(), have been invoked. + // + // - By signaling EventToSignal here, whose NotifyTpl is TPL_CALLBACK, we + // queue EventToSignal's NotifyFunction after the NotifyFunctions of *all* + // events in EFI_EVENT_GROUP_EXIT_BOOT_SERVICES. + gBS->SignalEvent (EventToSignal); +} + +/** + Notification function that is queued after the notification functions of all + events in the EFI_EVENT_GROUP_EXIT_BOOT_SERVICES event group. The same memory + map restrictions apply. + + This function unmaps all currently existing IOMMU mappings. + + @param[in] Event Event whose notification function is being invoked. Event + is permitted to request the queueing of this function + only at TPL_CALLBACK task priority level. + + @param[in] Context Ignored. +**/ +STATIC +VOID +EFIAPI +ArmCcaIoMmuUnmapAllMappings ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + LIST_ENTRY *Node; + LIST_ENTRY *NextNode; + MAP_INFO *MapInfo; + + // All drivers that had set up IOMMU mappings have halted their respective + // controllers by now; tear down the mappings. + for (Node = GetFirstNode (&mMapInfos); Node != &mMapInfos; Node = NextNode) { + NextNode = GetNextNode (&mMapInfos, Node); + MapInfo = CR (Node, MAP_INFO, Link, MAP_INFO_SIG); + IoMmuUnmapWorker ( + &mArmCcaIoMmu, // This + MapInfo, // Mapping + TRUE // MemoryMapLocked + ); + } +} + +/** + Initialize and install the ArmCca IoMmu Protocol. + + @return RETURN_SUCCESS if successful, otherwise any other error. +**/ +EFI_STATUS +EFIAPI +ArmCcaInstallIoMmuProtocol ( + VOID + ) +{ + EFI_STATUS Status; + EFI_EVENT UnmapAllMappingsEvent; + EFI_EVENT ExitBootEvent; + EFI_HANDLE Handle; + + // Create the "late" event whose notification function will tear down all + // left-over IOMMU mappings. + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, // Type + TPL_CALLBACK, // NotifyTpl + ArmCcaIoMmuUnmapAllMappings, // NotifyFunction + NULL, // NotifyContext + &UnmapAllMappingsEvent // Event + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // Create the event whose notification function will be queued by + // gBS->ExitBootServices() and will signal the event created above. + Status = gBS->CreateEvent ( + EVT_SIGNAL_EXIT_BOOT_SERVICES, // Type + TPL_CALLBACK, // NotifyTpl + ArmCcaIoMmuExitBoot, // NotifyFunction + UnmapAllMappingsEvent, // NotifyContext + &ExitBootEvent // Event + ); + if (EFI_ERROR (Status)) { + goto CloseUnmapAllMappingsEvent; + } + + Handle = NULL; + Status = gBS->InstallMultipleProtocolInterfaces ( + &Handle, + &gEdkiiIoMmuProtocolGuid, + &mArmCcaIoMmu, + NULL + ); + if (!EFI_ERROR (Status)) { + return Status; + } + + // cleanup on error + gBS->CloseEvent (ExitBootEvent); + +CloseUnmapAllMappingsEvent: + gBS->CloseEvent (UnmapAllMappingsEvent); + + return Status; +} diff --git a/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.h b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.h new file mode 100644 index 000000000000..070f7bebf5bf --- /dev/null +++ b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmu.h @@ -0,0 +1,66 @@ +/** @file + The protocol provides support to allocate, free, map and umap a DMA buffer + for bus master (e.g PciHostBridge). When the execution context is a Realm, + the DMA operations must be performed on buffers that are shared with the HOST, + hence the RAMP protocol is used to manage the sharing of the DMA buffers or in + some cases bounce the buffers. + + Copyright (c) 2017, Intel Corporation. All rights reserved.
+ Copyright (c) 2017, AMD Inc. All rights reserved.
+ (C) Copyright 2017 Hewlett Packard Enterprise Development LP
+ Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef ARM_CCA_IOMMU_H_ +#define ARM_CCA_IOMMU_H_ + +#include + +#include +#include +#include +#include +#include +#include + +/** + A macro defning the signature for the MAP_INFO structure. +*/ +#define MAP_INFO_SIG SIGNATURE_64 ('M', 'A', 'P', '_', 'I', 'N', 'F', 'O') + +/** A structure describing the mapping for the buffers shared with the host. +*/ +typedef struct { + /// Signature. + UINT64 Signature; + /// Linked List node entry. + LIST_ENTRY Link; + /// IoMMU operation. + EDKII_IOMMU_OPERATION Operation; + /// Number of bytes. + UINTN NumberOfBytes; + /// Number of pages. + UINTN NumberOfPages; + /// Address of the Host buffer. + VOID *HostAddress; + + /// Address for the Bounce Buffer. + EFI_PHYSICAL_ADDRESS BbAddress; + /// Handle to the Aperture. + EFI_HANDLE ApertureRef; +} MAP_INFO; + +/** + Install IOMMU protocol to provide the DMA support for PciHostBridge and + RAMP. + +**/ +EFI_STATUS +EFIAPI +ArmCcaInstallIoMmuProtocol ( + VOID + ); + +#endif diff --git a/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.c b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.c new file mode 100644 index 000000000000..deba9dd5e720 --- /dev/null +++ b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.c @@ -0,0 +1,59 @@ +/** @file + + IoMmuArmBowDxe driver installs EDKII_IOMMU_PROTOCOL to support + DMA operations when the execution context is a Realm. + + Copyright (c) 2017, AMD Inc. All rights reserved.
+ Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "ArmCcaIoMmu.h" + +/** Pointer to the Realm Aperture Management Protocol +*/ +EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL *mRamp = NULL; + +/** Entrypoint of Arm CCA IoMMU Dxe. + + @param [in] ImageHandle Image handle of this driver. + @param [in] SystemTable Pointer to the EFI System Table. + + @return RETURN_SUCCESS if successful, otherwise any other error. +**/ +EFI_STATUS +EFIAPI +ArmCcaIoMmuDxeEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + EFI_HANDLE Handle; + + // When the execution context is a Realm, install ArmCcaIoMmu protocol + // otherwise install the placeholder protocol so that other dependent + // module can run. + Status = gBS->LocateProtocol ( + &gEfiRealmApertureManagementProtocolGuid, + NULL, + (VOID **)&mRamp + ); + if (!EFI_ERROR (Status)) { + // If the Realm Aperture Management Protocol is present + // then the execution context is a Realm. + Status = ArmCcaInstallIoMmuProtocol (); + } else { + DEBUG ((DEBUG_INFO, "Execution context is not a Realm.\n")); + Handle = NULL; + Status = gBS->InstallMultipleProtocolInterfaces ( + &Handle, + &gIoMmuAbsentProtocolGuid, + NULL, + NULL + ); + } + + return Status; +} diff --git a/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.inf b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.inf new file mode 100644 index 000000000000..b8e125296f4d --- /dev/null +++ b/ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.inf @@ -0,0 +1,45 @@ +## @file +# Driver provides the IOMMU protcol support for PciHostBridgeIo and others +# drivers. +# +# Copyright (c) 2017, AMD Inc. All rights reserved.
+# Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001001B + BASE_NAME = IoMmuDxe + FILE_GUID = AA6C1A48-A341-439C-950E-CC394FDFE144 + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = ArmCcaIoMmuDxeEntryPoint + +[Sources] + ArmCcaIoMmu.c + ArmCcaIoMmu.h + ArmCcaIoMmuDxe.c + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + OvmfPkg/OvmfPkg.dec + ArmVirtPkg/ArmVirtPkg.dec + +[LibraryClasses] + BaseLib + BaseMemoryLib + DebugLib + MemoryAllocationLib + UefiBootServicesTableLib + UefiDriverEntryPoint + +[Protocols] + gEdkiiIoMmuProtocolGuid ## SOMETIME_PRODUCES + gIoMmuAbsentProtocolGuid ## SOMETIME_PRODUCES + gEfiRealmApertureManagementProtocolGuid + +[Depex] + gEfiRealmApertureManagementProtocolGuid diff --git a/ArmVirtPkg/ArmVirt.dsc.inc b/ArmVirtPkg/ArmVirt.dsc.inc index cf0aba6f2b6f..784963617223 100644 --- a/ArmVirtPkg/ArmVirt.dsc.inc +++ b/ArmVirtPkg/ArmVirt.dsc.inc @@ -1,5 +1,5 @@ # -# Copyright (c) 2011 - 2022, ARM Limited. All rights reserved. +# Copyright (c) 2011 - 2023, ARM Limited. All rights reserved. # Copyright (c) 2014, Linaro Limited. All rights reserved. # Copyright (c) 2015 - 2018, Intel Corporation. All rights reserved. # Copyright (c) Microsoft Corporation. @@ -168,6 +168,8 @@ ReportStatusCodeLib|MdePkg/Library/BaseReportStatusCodeLibNull/BaseReportStatusCodeLibNull.inf + ArmCcaLib|ArmVirtPkg/Library/ArmCcaLibNull/ArmCcaLibNull.inf + [LibraryClasses.common.SEC] PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf @@ -182,6 +184,8 @@ DebugLib|ArmVirtPkg/Library/DebugLibFdtPL011Uart/DebugLibFdtPL011UartFlash.inf !endif + ArmCcaInitPeiLib|ArmVirtPkg/Library/ArmCcaInitPeiLibNull/ArmCcaInitPeiLibNull.inf + [LibraryClasses.common.PEI_CORE] PcdLib|MdePkg/Library/PeiPcdLib/PeiPcdLib.inf BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf diff --git a/ArmVirtPkg/ArmVirtKvmTool.dsc b/ArmVirtPkg/ArmVirtKvmTool.dsc index 6565ea35c124..f0f94bc52c4d 100644 --- a/ArmVirtPkg/ArmVirtKvmTool.dsc +++ b/ArmVirtPkg/ArmVirtKvmTool.dsc @@ -87,6 +87,13 @@ ArmMonitorLib|ArmVirtPkg/Library/ArmVirtMonitorLib/ArmVirtMonitorLib.inf ArmTrngLib|ArmPkg/Library/ArmTrngLib/ArmTrngLib.inf + ArmPlatformDeviceInfoLib|ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/ArmPlatformDeviceInfoLib.inf + +[LibraryClasses.AARCH64] + ArmCcaLib|ArmVirtPkg/Library/ArmCcaLib/ArmCcaLib.inf + ArmCcaRsiLib|ArmVirtPkg/Library/ArmCcaRsiLib/ArmCcaRsiLib.inf + RngLib|MdePkg/Library/BaseRngLib/BaseRngLib.inf + [LibraryClasses.common.SEC, LibraryClasses.common.PEI_CORE, LibraryClasses.common.PEIM] PciExpressLib|MdePkg/Library/BasePciExpressLib/BasePciExpressLib.inf PlatformHookLib|ArmVirtPkg/Library/Fdt16550SerialPortHookLib/EarlyFdt16550SerialPortHookLib.inf @@ -100,6 +107,9 @@ DebugLib|MdePkg/Library/DxeRuntimeDebugLibSerialPort/DxeRuntimeDebugLibSerialPort.inf !endif +[LibraryClasses.AARCH64.SEC, LibraryClasses.AARCH64.PEI_CORE, LibraryClasses.AARCH64.PEIM] + ArmCcaInitPeiLib|ArmVirtPkg/Library/ArmCcaInitPeiLib/ArmCcaInitPeiLib.inf + [LibraryClasses.common.UEFI_DRIVER] UefiScsiLib|MdePkg/Library/UefiScsiLib/UefiScsiLib.inf @@ -243,6 +253,7 @@ # ArmVirtPkg/PrePi/ArmVirtPrePiUniCoreRelocatable.inf { + DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf ExtractGuidedSectionLib|EmbeddedPkg/Library/PrePiExtractGuidedSectionLib/PrePiExtractGuidedSectionLib.inf NULL|MdeModulePkg/Library/LzmaCustomDecompressLib/LzmaCustomDecompressLib.inf PrePiLib|EmbeddedPkg/Library/PrePiLib/PrePiLib.inf @@ -388,9 +399,18 @@ # SecurityPkg/RandomNumberGenerator/RngDxe/RngDxe.inf -!if $(ARCH) == AARCH64 +[Components.AARCH64] # # ACPI Support # ArmVirtPkg/KvmtoolCfgMgrDxe/ConfigurationManagerDxe.inf -!endif + + # + # Realm Aperture Management + # + ArmVirtPkg/RealmApertureManagementProtocolDxe/RealmApertureManagementProtocolDxe.inf + + # + # IoMMU support for Arm CCA + # + ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.inf diff --git a/ArmVirtPkg/ArmVirtKvmTool.fdf b/ArmVirtPkg/ArmVirtKvmTool.fdf index cdf756c11289..07d86f649e01 100644 --- a/ArmVirtPkg/ArmVirtKvmTool.fdf +++ b/ArmVirtPkg/ArmVirtKvmTool.fdf @@ -212,6 +212,16 @@ READ_LOCK_STATUS = TRUE !include DynamicTablesPkg/DynamicTables.fdf.inc INF ArmVirtPkg/KvmtoolCfgMgrDxe/ConfigurationManagerDxe.inf + + # + # Realm Aperture Management + # + INF ArmVirtPkg/RealmApertureManagementProtocolDxe/RealmApertureManagementProtocolDxe.inf + + # + # IoMMU support for Arm CCA + # + INF ArmVirtPkg/ArmCcaIoMmuDxe/ArmCcaIoMmuDxe.inf !endif # diff --git a/ArmVirtPkg/ArmVirtPkg.dec b/ArmVirtPkg/ArmVirtPkg.dec index 6aa5ea05f4e9..52b2dddbfcdb 100644 --- a/ArmVirtPkg/ArmVirtPkg.dec +++ b/ArmVirtPkg/ArmVirtPkg.dec @@ -26,6 +26,10 @@ Include # Root include for the package [LibraryClasses] + ArmCcaInitPeiLib|Include/Library/ArmCcaInitPeiLib.h + ArmCcaLib|Include/Library/ArmCcaLib.h + ArmCcaRsiLib|Include/Library/ArmCcaRsiLib.h + ArmPlatformDeviceInfoLib|Include/Library/ArmPlatformDeviceInfoLib.h ArmVirtMemInfoLib|Include/Library/ArmVirtMemInfoLib.h [Guids.common] @@ -33,6 +37,7 @@ gEarlyPL011BaseAddressGuid = { 0xB199DEA9, 0xFD5C, 0x4A84, { 0x80, 0x82, 0x2F, 0x41, 0x70, 0x78, 0x03, 0x05 } } gEarly16550UartBaseAddressGuid = { 0xea67ca3e, 0x1f54, 0x436b, { 0x97, 0x88, 0xd4, 0xeb, 0x29, 0xc3, 0x42, 0x67 } } gArmVirtSystemMemorySizeGuid = { 0x504eccb9, 0x1bf0, 0x4420, { 0x86, 0x5d, 0xdc, 0x66, 0x06, 0xd4, 0x13, 0xbf } } + gArmCcaIpaWidthGuid = { 0xbdb66787, 0xfc8a, 0x412e, { 0xa0, 0x9b, 0x84, 0x96, 0x61, 0x81, 0x72, 0xc0 } } [PcdsFeatureFlag] # @@ -40,9 +45,18 @@ # gArmVirtTokenSpaceGuid.PcdTpm2SupportEnabled|FALSE|BOOLEAN|0x00000004 +[Protocols] + gEfiRealmApertureManagementProtocolGuid = { 0x585c00be, 0xcf7c, 0x4db8, { 0x8a, 0xa2, 0x49, 0xd, 0x67, 0xf5, 0xf6, 0xe6 } } + [PcdsFixedAtBuild, PcdsPatchableInModule] ## # This is the physical address of Rsdp which is the core struct of Acpi. # Cloud Hypervisor has no other way to pass Rsdp address to the guest except use a PCD. # gArmVirtTokenSpaceGuid.PcdCloudHvAcpiRsdpBaseAddress|0x0|UINT64|0x00000005 + +[PcdsFixedAtBuild] + # + # Maximum number of platform devices that can be discovered from a FDT. + # + gArmVirtTokenSpaceGuid.PcdMaxPlatDeviceCount|0x10|UINT64|0x00000006 diff --git a/ArmVirtPkg/Include/Library/ArmCcaInitPeiLib.h b/ArmVirtPkg/Include/Library/ArmCcaInitPeiLib.h new file mode 100644 index 000000000000..439a70a54a21 --- /dev/null +++ b/ArmVirtPkg/Include/Library/ArmCcaInitPeiLib.h @@ -0,0 +1,49 @@ +/** @file + Library that implements the Arm CCA helper functions. + + Copyright (c) 2022 2023, Arm Ltd. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + - Rsi or RSI - Realm Service Interface + - IPA - Intermediate Physical Address + - RIPAS - Realm IPA state +**/ + +#ifndef ARM_CCA_INIT_PEI_LIB_ +#define ARM_CCA_INIT_PEI_LIB_ + +#include + +/** + Configure the System Memory region as Protected RAM. + + When a VMM creates a Realm, a small amount of DRAM (which contains the + firmware image) and the initial content is configured as Protected RAM. + The remaining System Memory is in the Protected Empty state. The firmware + must then initialise the remaining System Memory as Protected RAM before + it can be accessed. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The execution context is not in a Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaConfigureSystemMemory ( + VOID + ); + +/** + Perform Arm CCA specific initialisations. + + @retval RETURN_SUCCESS Success or execution context is not a Realm. + @retval RETURN_OUT_OF_RESOURCES Out of resources. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +ArmCcaInitialize ( + VOID + ); + +#endif // ARM_CCA_LIB_ diff --git a/ArmVirtPkg/Include/Library/ArmCcaLib.h b/ArmVirtPkg/Include/Library/ArmCcaLib.h new file mode 100644 index 000000000000..32ed28495497 --- /dev/null +++ b/ArmVirtPkg/Include/Library/ArmCcaLib.h @@ -0,0 +1,136 @@ +/** @file + Library that implements the Arm CCA helper functions. + + Copyright (c) 2022 - 2023, Arm Ltd. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + - Rsi or RSI - Realm Service Interface + - IPA - Intermediate Physical Address + - RIPAS - Realm IPA state +**/ + +#ifndef ARM_CCA_LIB_ +#define ARM_CCA_LIB_ + +#include +#include + +/** + Check if running in a Realm. + + @retval TRUE The execution is within the context of a Realm. + @retval FALSE The execution is not within the context of a Realm. +**/ +BOOLEAN +EFIAPI +IsRealm ( + VOID + ); + +/** + Configure the protection attribute for the page tables + describing the memory region. + + The IPA space of a Realm is divided into two halves: + - Protected IPA space and + - Unprotected IPA space. + + Software in a Realm should treat the most significant bit of an + IPA as a protection attribute. + + A Protected IPA is an address in the lower half of a Realms IPA + space. The most significant bit of a Protected IPA is 0. + + An Unprotected IPA is an address in the upper half of a Realms + IPA space. The most significant bit of an Unprotected IPA is 1. + + Note: + - Configuring the memory region as Unprotected IPA enables the + Realm to share the memory region with the Host. + - This function updates the page table entries to reflect the + protection attribute. + - A separate call to transition the memory range using the Realm + Service Interface (RSI) RSI_IPA_STATE_SET command is additionally + required and is expected to be done outside this function. + + @param [in] BaseAddress Base address of the memory region. + @param [in] Length Length of the memory region. + @param [in] IpaWidth IPA width of the Realm. + @param [in] Share If TRUE, set the most significant + bit of the IPA to configure the memory + region as Unprotected IPA. + If FALSE, clear the most significant + bit of the IPA to configure the memory + region as Protected IPA. + + @retval RETURN_SUCCESS IPA protection attribute updated. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The request is not initiated in a + Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaSetMemoryProtectAttribute ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINT64 Length, + IN UINT64 IpaWidth, + IN BOOLEAN Share + ); + +/** + Return the IPA width of the Realm. + + The IPA width of the Realm is used to configure the protection attribute + for memory regions, see ArmCcaSetMemoryProtectAttribute(). + + The IPA width of the Realm is present in the Realm config which is read + when the ArmCcaInitPeiLib library hook function ArmCcaInitialize () is + called in the PrePi phase. ArmCcaInitialize () stores the IPA width of + the Realm in a GUID HOB gArmCcaIpaWidthGuid. + + This function searches the GUID HOB gArmCcaIpaWidthGuid and returns the + IPA width value stored therein. + + Note: + - This function must only be called after ArmCcaInitialize () has setup + the GUID HOB gArmCcaIpaWidthGuid. + + @param [out] IpaWidth IPA width of the Realm. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_NOT_FOUND The GUID HOB gArmCcaIpaWidthGuid is not + found and could mean that this function + was called before ArmCcaInitialize () + has created and initialised the GUID + HOB gArmCcaIpaWidthGuid. +**/ +RETURN_STATUS +EFIAPI +GetIpaWidth ( + OUT UINT64 *IpaWidth + ); + +/** Check if the address range is protected MMIO + + @param [in] BaseAddress Base address of the device MMIO region. + @param [in] Length Length of the device MMIO region. + @param [out] IsProtectedMmio TRUE - if the RIPAS for the address range is + protected MMIO. + FALSE - if the RIPAS for the address range is + not protected MMIO. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The request is not initiated in a + Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaMemRangeIsProtectedMmio ( + IN UINT64 BaseAddress, + IN UINT64 Length, + OUT BOOLEAN *IsProtectedMmio + ); + +#endif // ARM_CCA_LIB_ diff --git a/ArmVirtPkg/Include/Library/ArmCcaRsiLib.h b/ArmVirtPkg/Include/Library/ArmCcaRsiLib.h new file mode 100644 index 000000000000..3b389847eb42 --- /dev/null +++ b/ArmVirtPkg/Include/Library/ArmCcaRsiLib.h @@ -0,0 +1,407 @@ +/** @file + Library that implements the Arm CCA Realm Service Interface calls. + + Copyright (c) 2022 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + - Rsi or RSI - Realm Service Interface + - IPA - Intermediate Physical Address + - RIPAS - Realm IPA state + - RIM - Realm Initial Measurement + - REM - Realm Extensible Measurement + + @par Reference(s): + - Realm Management Monitor (RMM) Specification, version 1.0-rel0 + (https://developer.arm.com/documentation/den0137/) +**/ + +#ifndef ARM_CCA_RSI_LIB_ +#define ARM_CCA_RSI_LIB_ + +#include + +/** + A macro defining the size of a Realm Granule. + See Section A2.2, RMM Specification, version A-bet0 + DNBXXX A Granule is a unit of physical memory whose size is 4KB. +*/ +#define REALM_GRANULE_SIZE SIZE_4KB + +/** + A macro defining the mask for the RSI RIPAS type. + See Section B4.4.5 RsiRipas type, RMM Specification, version A-bet0. +*/ +#define RIPAS_TYPE_MASK 0xFF + +/* Maximum challenge data size in bits. +*/ +#define MAX_CHALLENGE_DATA_SIZE_BITS 512 + +/* Minimum recommended challenge data size in bits. +*/ +#define MIN_CHALLENGE_DATA_SIZE_BITS 256 + +/* Maximum measurement data size in bytes. + See Section C1.11 RmmRealmMeasurement type, RMM Specification, version A-bet0 + The width of the RmmRealmMeasurement type is 512 bits. +*/ +#define MAX_MEASUREMENT_DATA_SIZE_BYTES 64 + +/* Minimum and Maximum indices for REMs + See Section A2.1.3 Realm attributes, RMM Specification, version A-bet0 + IFMPYL - Attributes of a Realm include an array of measurement values. The + first entry in this array is a RIM. The remaining entries in this array are + REMs. +*/ +#define MIN_REM_INDEX 1 +#define MAX_REM_INDEX 4 + +/* The values of the RsiHashAlgorithm enumeration. + SHA-256 (Secure Hash Standard (SHS)) +*/ +#define RSI_HASH_SHA_256 0 + +/* The values of the RsiHashAlgorithm enumeration. + SHA-512 (Secure Hash Standard (SHS)) +*/ +#define RSI_HASH_SHA_512 1 + +/* The RsiRipasChangeFlags fieldset contains flags provided by + the Realm when requesting a RIPAS change. + See section B4.4.8 RsiRipasChangeFlags type in the + RMM Specification, version 1.0-eac2. + The following macros prefixed RIPAS_CHANGE_FLAGS_xxx + define the values of the RsiRipasChangeFlags fieldset. +*/ + +/* A RIPAS change from DESTROYED should not be permitted. + See section B4.4.7 RsiRipasChangeDestroyed type in the + RMM Specification, version 1.0-eac2 +*/ +#define RIPAS_CHANGE_FLAGS_RSI_NO_CHANGE_DESTROYED 0 + +/* A RIPAS change from DESTROYED should be permitted. + See section B4.4.7 RsiRipasChangeDestroyed type in the + RMM Specification, version 1.0-eac2 +*/ +#define RIPAS_CHANGE_FLAGS_RSI_CHANGE_DESTROYED 1 + +/* The RsiResponse type is a value returned by the + RSI_IPA_STATE_SET command and represents whether + the Host accepted or rejected a Realm request. + See section B4.4.6 RsiResponse type in the + RMM Specification, version 1.0-eac3. + The width of the RsiResponse enumeration is 1 bit + and the following macros prefixed RIPAS_CHANGE_RESPONSE_xxx + define the values of the RsiResponse type. +*/ + +/* The RIPAS change request to RAM was accepted + by the host. +*/ +#define RIPAS_CHANGE_RESPONSE_ACCEPT 0 + +/* The RIPAS change request to RAM was rejected + by the host. +*/ +#define RIPAS_CHANGE_RESPONSE_REJECT 1 + +/* A mask for the RSI Response bit */ +#define RSI_RESPONSE_MASK BIT0 + +/** An enum describing the RSI RIPAS. + See Section A5.2.2 Realm IPA state, RMM Specification, version 1.0-rel0 +*/ +typedef enum Ripas { + RipasEmpty, ///< Unused IPA location. + RipasRam, ///< Private code or data owned by the Realm. + RipasDestroyed, ///< An address which is inaccessible to the Realm. + RipasDev, ///< MMIO address where an assigned Realm device is mapped. + RipasMax ///< A valid RIPAS type value is less than RipasMax. +} RIPAS; + +/* The maximum length of the Realm Personalisation Value (RPV). +*/ +#define REALM_CFG_RPV_SIZE 64 + +/* The size of the Realm Config is 4KB. +*/ +#define REALM_CFG_SIZE SIZE_4KB + +/* Helper macros to define the RealmConfig structure. +*/ +#define REALM_CFG_OFFSET_IPA_WIDTH 0 +#define REALM_CFG_OFFSET_HASH_ALGO (REALM_CFG_OFFSET_IPA_WIDTH + sizeof (UINT64)) +#define REALM_CFG_OFFSET_RESERVED (REALM_CFG_OFFSET_HASH_ALGO + sizeof (UINT8)) +#define REALM_CFG_OFFSET_RPV 0x200 +#define REALM_CFG_OFFSET_RESERVED1 (REALM_CFG_OFFSET_RPV + REALM_CFG_RPV_SIZE) + +#pragma pack(1) + +/** A structure describing the Realm Configuration. + See Section B5.4.5 RsiRealmConfig type, RMM Specification, version 1.0-rel0 + The width of the RsiRealmConfig structure is 4096 (0x1000) bytes. +*/ +typedef struct RealmConfig { + /// Width of IPA in bits. + UINT64 IpaWidth; + /// Width of the RsiHashAlgorithm enumeration is 8 bits. + UINT8 HashAlgorithm; + /// Reserved + UINT8 Reserved[REALM_CFG_OFFSET_RPV - REALM_CFG_OFFSET_RESERVED]; + /// Realm Personalisation Value + UINT8 Rpv[REALM_CFG_RPV_SIZE]; + /// Unused bits of the RsiRealmConfig structure should be zero. + UINT8 Reserved1[REALM_CFG_SIZE - REALM_CFG_OFFSET_RESERVED1]; +} REALM_CONFIG; + +/** A structure describing the Host Call arguments + See Section 4.4.2 RsiHostCall type, RMM Specification, version 1.0-bet2 +*/ +typedef struct HostCallArgs { + UINT16 Imm; + UINT8 Reserved1[6]; + + UINT64 Gprs0; + UINT64 Gprs1; + UINT64 Gprs2; + UINT64 Gprs3; + UINT64 Gprs4; + UINT64 Gprs5; + UINT64 Gprs6; + UINT64 Gprs7; + UINT64 Gprs8; + UINT64 Gprs9; + UINT64 Gprs10; + UINT64 Gprs11; + UINT64 Gprs12; + UINT64 Gprs13; + UINT64 Gprs14; + UINT64 Gprs15; + UINT64 Gprs16; + UINT64 Gprs17; + UINT64 Gprs18; + UINT64 Gprs19; + UINT64 Gprs20; + UINT64 Gprs21; + UINT64 Gprs22; + UINT64 Gprs23; + UINT64 Gprs24; + UINT64 Gprs25; + UINT64 Gprs26; + UINT64 Gprs27; + UINT64 Gprs28; + UINT64 Gprs29; + UINT64 Gprs30; +} HOST_CALL_ARGS; + +#pragma pack() + +/** + Retrieve an attestation token from the RMM. + + @param [in] ChallengeData Pointer to the challenge data to be + included in the attestation token. + @param [in] ChallengeDataSizeBits Size of the challenge data in bits. + @param [out] TokenBuffer Pointer to a buffer to store the + retrieved attestation token. + @param [out] TokenBufferSize Length of token data returned. + + Note: The TokenBuffer allocated must be freed by the caller + using RsiFreeAttestationToken(). + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_ABORTED The operation was aborted as the state + of the Realm or REC does not match the + state expected by the command. + Or the Token generation failed for an + unknown or IMPDEF reason. + @retval RETURN_NOT_READY The operation requested by the command + is not complete. +**/ +RETURN_STATUS +EFIAPI +RsiGetAttestationToken ( + IN CONST UINT8 *CONST ChallengeData, + IN UINT64 ChallengeDataSizeBits, + OUT UINT8 **CONST TokenBuffer, + OUT UINT64 *CONST TokenBufferSize + ); + +/** + Free the attestation token buffer. + + @param [in] TokenBuffer Pointer to the retrieved + attestation token. + @param [in] TokenBufferSize Size of the token buffer. +**/ +VOID +RsiFreeAttestationToken ( + IN UINT8 *CONST TokenBuffer, + IN UINT64 CONST TokenBufferSize + ); + +/** + Returns the IPA state for the page pointed by the address. + + @param [in] Base Base of target IPA region. + @param [in, out] Top End of target IPA region on input. + Top of IPA region which has the + reported RIPAS value on return. + @param [out] State The RIPAS state for the address specified. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiGetIpaState ( + IN UINT64 *Base, + IN OUT UINT64 **Top, + OUT RIPAS *State + ); + +/** + Sets the IPA state for the pages pointed by the memory range. + + @param [in] Address Address to the start of the memory range. + @param [in] Size Length of the memory range. + @param [in] State The RIPAS state to be configured. + @param [in] Flags The RIPAS change flags. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_ACCESS_DENIED RIPAS change request was rejected. +**/ +RETURN_STATUS +EFIAPI +RsiSetIpaState ( + IN UINT64 *Address, + IN UINT64 Size, + IN RIPAS State, + IN UINT64 Flags + ); + +/** + Extends a measurement to a REM. + + @param [in] MeasurementIndex Index of the REM. + @param [in] Measurement Pointer to the measurement buffer. + @param [in] MeasurementSize Size of the measurement data. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiExtendMeasurement ( + IN UINTN MeasurementIndex, + IN CONST UINT8 *CONST Measurement, + IN UINTN MeasurementSize + ); + +/** + Read the measurement value from a REM. + + @param [in] MeasurementIndex Index of the REM. + @param [out] MeasurementBuffer Pointer to store the measurement data. + @param [in] MeasurementBufferSize Size of the measurement buffer. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiReadMeasurement ( + IN UINTN MeasurementIndex, + OUT UINT8 *CONST MeasurementBuffer, + IN UINTN MeasurementBufferSize + ); + +/** + Read the Realm Configuration. + + @param [out] Config Pointer to the address of the buffer to retrieve + the Realm configuration. + + Note: The buffer to retrieve the Realm configuration must be aligned to the + Realm granule size. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiGetRealmConfig ( + IN REALM_CONFIG *Config + ); + +/** + Make a Host Call. + + A Host call can be used by a Realm to make a hypercall. + On Realm execution of HVC, an Unknown exception is taken to the Realm. + + @param [in] Args Pointer to the IPA of the Host call data + structure. + + Note: The IPA of the Host call arguments data structure must be aligned + to the Realm granule size. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiHostCall ( + IN HOST_CALL_ARGS *Args + ); + +/** + Get the version of the RSI implementation. + + @param [out] UefiImpl The version of the RSI specification + implemented by the UEFI firmware. + @param [out] RmmImplLow The low version of the RSI specification + implemented by the RMM. + @param [out] RmmImplHigh The high version of the RSI specification + implemented by the RMM. + + @retval RETURN_SUCCESS Success. + @retval RETURN_UNSUPPORTED The execution context is not a Realm. + @retval RETURN_INCOMPATIBLE_VERSION The Firmware and RMM specification + revisions are not compatible. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiGetVersion ( + OUT UINT32 *CONST UefiImpl, + OUT UINT32 *CONST RmmImplLow, + OUT UINT32 *CONST RmmImplHigh + ); + +/** + Get the features supported by the RSI implementation. + + RMM implementations across different CCA platforms may support + disparate features and may offer disparate configuration options + for Realms. The features supported by an RSI implementation are + discovered by reading feature pseudo-register values using the + RSI_FEATURES command. + + @param [in] FeatureRegIndex The Feature Register Index. + @param [out] FeatureRegValue The Feature Register Value. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiGetFeatures ( + IN UINT64 FeatureRegIndex, + OUT UINT64 *FeatureRegValue + ); + +#endif // ARM_CCA_RSI_LIB_ diff --git a/ArmVirtPkg/Include/Library/ArmPlatformDeviceInfoLib.h b/ArmVirtPkg/Include/Library/ArmPlatformDeviceInfoLib.h new file mode 100644 index 000000000000..ea713884ef51 --- /dev/null +++ b/ArmVirtPkg/Include/Library/ArmPlatformDeviceInfoLib.h @@ -0,0 +1,58 @@ +/** @file + Arm Platform Device Info Library + + Copyright (c) 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef ARM_PLATFORM_DEVICE_INFO_LIB_H_ +#define ARM_PLATFORM_DEVICE_INFO_LIB_H_ + +#include +#include + +/* Maximum number of platform devices that can be discovered from a FDT. +*/ +#define MAX_PLAT_DEVICE_COUNT FixedPcdGet64 (PcdMaxPlatDeviceCount) + +/** A DEVICE_INFO structure that contains a brief description of the + device and the Base address and range. +**/ +typedef struct DeviceInfo { + /// Brief description of the device. + CHAR8 Desc[16]; + /// The MMIO Base address. + UINT64 BaseAddress; + /// The base address range. + UINT64 Length; +} DEVICE_INFO; + +/** A structure that contains the information about the devices available + on a platform. +**/ +typedef struct PlatDevInfo { + /// An array containing the information about the platform devices. + DEVICE_INFO Dev[MAX_PLAT_DEVICE_COUNT]; + /// The max count of devices. + UINTN MaxDevices; +} PLATFROM_DEVICE_INFO; + +/** Parse the platform FDT and populate the platform device info. + + @param [in] FdtBase Pointer to the platform FDT. + @param [in] PlatInfo Pointer to the platform device info to populate. + + @retval EFI_SUCCESS Success. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_NOT_FOUND Parser did not find any device information + in the FDT. +**/ +EFI_STATUS +EFIAPI +ArmParsePlatformDeviceFdt ( + IN VOID *FdtBase, + IN PLATFROM_DEVICE_INFO *PlatInfo + ); + +#endif // ARM_PLATFORM_DEVICE_INFO_LIB_H_ diff --git a/ArmVirtPkg/Include/Library/ArmVirtMemInfoLib.h b/ArmVirtPkg/Include/Library/ArmVirtMemInfoLib.h index 7812c2e28657..b70a96ed923e 100644 --- a/ArmVirtPkg/Include/Library/ArmVirtMemInfoLib.h +++ b/ArmVirtPkg/Include/Library/ArmVirtMemInfoLib.h @@ -1,6 +1,6 @@ /** @file - Copyright (c) 2011-2013, ARM Limited. All rights reserved. + Copyright (c) 2011-2023, Arm Limited. All rights reserved. Copyright (c) 2017, Linaro, Ltd. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent @@ -32,4 +32,21 @@ ArmVirtGetMemoryMap ( OUT ARM_MEMORY_REGION_DESCRIPTOR **VirtualMemoryMap ); +/** + Configure the MMIO regions as shared with the VMM. + + Set the protection attribute for the MMIO regions as Unprotected IPA. + + @param[in] IpaWidth IPA width of the Realm. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The execution context is not in a Realm. +**/ +EFI_STATUS +EFIAPI +ArmCcaConfigureMmio ( + IN UINT64 IpaWidth + ); + #endif diff --git a/ArmVirtPkg/Include/Protocol/RealmApertureManagementProtocol.h b/ArmVirtPkg/Include/Protocol/RealmApertureManagementProtocol.h new file mode 100644 index 000000000000..0f45fd296fd5 --- /dev/null +++ b/ArmVirtPkg/Include/Protocol/RealmApertureManagementProtocol.h @@ -0,0 +1,103 @@ +/** @file + Realm Aperture Management Protocol (RAMP) + On Arm CCA Systems the Realm protects access and visibility of Guest memory + and code execution from software outside the realm. + + However, software executing in a Realm needs to interact with the external + world. This may be done using virtualised disk, network interfaces, etc. + The drivers for these virtualised devices need to share buffers with the host + OS to exchange information/data. + + Since the Guest memory is protected by the Realm, the host cannot access these + buffers unless the IPA state of the buffers is changed to Protected EMPTY by + the software executing in the Realm. + + By enabling the sharing of the buffers, we are essentially opening an + aperture so that the host OS can access the range of pages that are shared. + + The virtual firmware (Guest firmware) needs a mechanism to manage the sharing + of buffers. The Realm Aperture Management Protocol provides an interface that + UEFI drivers/modules can use to enable/disable the sharing of buffers with the + Host. The protocol also tracks open apertures and ensures they are shut on + ExitBootServices. + + Copyright (c) 2022 - 2023, ARM Ltd. All rights reserved.
+ + SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Glossary: + - RAMP - Realm Aperture Management Protocol +**/ + +#ifndef REALM_APERTURE_MANAGEMENT_PROTOCOL_H_ +#define REALM_APERTURE_MANAGEMENT_PROTOCOL_H_ + +/** This macro defines the Realm Aperture Management Protocol GUID. + + GUID: {585C00BE-CF7C-4DB8-8AA2-490D67F5F6E6} +*/ +#define EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL_GUID \ + { 0x585c00be, 0xcf7c, 0x4db8, \ + { 0x8a, 0xa2, 0x49, 0xd, 0x67, 0xf5, 0xf6, 0xe6 } \ + }; + +/** This macro defines the Realm Aperture Management Protocol Revision. +*/ +#define EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL_REVISION 0x00010000 + +#pragma pack(1) + +/** Enables sharing of the memory buffers with the host. + + @param [in] Memory Pointer to the page start address. + @param [in] Pages Number of pages to share. + @param [out] ApertureReference Reference to the opened aperture. + + @retval EFI_SUCCESS Success. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + @retval EFI_ACCESS_DENIED Aperture already open over memory region. +**/ +typedef +EFI_STATUS +(EFIAPI *EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL_OPEN_APERTURE)( + IN CONST EFI_PHYSICAL_ADDRESS Memory, + IN CONST UINTN Pages, + OUT EFI_HANDLE *CONST ApertureReference + ); + +/** Disables the sharing of the buffers. + + @param [in] ApertureReference Reference to the aperture for closing. + + @retval EFI_SUCCESS The operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_NOT_FOUND The required buffer information is not found. +**/ +typedef +EFI_STATUS +(EFIAPI *EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL_CLOSE_APERTURE)( + IN CONST EFI_HANDLE ApertureReference + ); + +/** A structure describing the interface provided by the Realm Aperture + Management Protocol. +*/ +typedef struct RealmApertureManagementProtocol { + /// The Realm Aperture Management Protocol revision. + UINT64 Revision; + + /// Shares Realm Pages(s) with the Host. + EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL_OPEN_APERTURE OpenAperture; + + /// Makes the Realm Pages(s) private to the Realm. + EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL_CLOSE_APERTURE CloseAperture; +} EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL; + +/** The Realm Aperture Management Protocol GUID. +*/ +extern EFI_GUID gEfiRealmApertureManagementProtocolGuid; + +#pragma pack() + +#endif // REALM_APERTURE_MANAGEMENT_PROTOCOL_H_ diff --git a/ArmVirtPkg/KvmtoolCfgMgrDxe/ConfigurationManager.c b/ArmVirtPkg/KvmtoolCfgMgrDxe/ConfigurationManager.c index 96fbea33350d..1574979432d3 100644 --- a/ArmVirtPkg/KvmtoolCfgMgrDxe/ConfigurationManager.c +++ b/ArmVirtPkg/KvmtoolCfgMgrDxe/ConfigurationManager.c @@ -1,7 +1,7 @@ /** @file Configuration Manager Dxe - Copyright (c) 2021 - 2022, Arm Limited. All rights reserved.
+ Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent @@ -699,6 +699,22 @@ GetStandardNameSpaceObject ( // IORT is only required for GicV3/4 // AcpiTableCount -= 1; + } else { + // + // Check if we have support for ITS. + // + Status = DynamicPlatRepoGetObject ( + PlatformRepo->DynamicPlatformRepo, + CREATE_CM_ARM_OBJECT_ID (EArmObjGicItsInfo), + CM_NULL_TOKEN, + &CmObjDesc + ); + if (EFI_ERROR (Status)) { + // + // IORT is only required for GicV3/4 if ITS is present. + // + AcpiTableCount -= 1; + } } Status = HandleCmObject ( diff --git a/ArmVirtPkg/Library/ArmCcaInitPeiLib/ArmCcaInitPeiLib.c b/ArmVirtPkg/Library/ArmCcaInitPeiLib/ArmCcaInitPeiLib.c new file mode 100644 index 000000000000..e59a990da425 --- /dev/null +++ b/ArmVirtPkg/Library/ArmCcaInitPeiLib/ArmCcaInitPeiLib.c @@ -0,0 +1,117 @@ +/** @file + Library that implements the Arm CCA initialisation in PEI phase. + + Copyright (c) 2022 2023, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Glossary: + - Rsi or RSI - Realm Service Interface + - IPA - Intermediate Physical Address + - RIPAS - Realm IPA state +**/ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + Configure the System Memory region as Protected RAM. + + When a VMM creates a Realm, a small amount of DRAM (which contains the + firmware image) and the initial content is configured as Protected RAM. + The remaining System Memory is in the Protected Empty state. The firmware + must then initialise the remaining System Memory as Protected RAM before + it can be accessed. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The execution context is not in a Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaConfigureSystemMemory ( + VOID + ) +{ + RETURN_STATUS Status; + + if (!IsRealm ()) { + return RETURN_UNSUPPORTED; + } + + Status = RsiSetIpaState ( + (UINT64 *)PcdGet64 (PcdSystemMemoryBase), + PcdGet64 (PcdSystemMemorySize), + RipasRam, + RIPAS_CHANGE_FLAGS_RSI_NO_CHANGE_DESTROYED + ); + if (RETURN_ERROR (Status)) { + // Panic + CpuDeadLoop (); + } + + return Status; +} + +/** + Perform Arm CCA specific initialisations. + + @retval RETURN_SUCCESS Success or execution context is not a Realm. + @retval RETURN_OUT_OF_RESOURCES Out of resources. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +ArmCcaInitialize ( + VOID + ) +{ + EFI_STATUS Status; + REALM_CONFIG *Config; + UINT64 *IpaWidthHobData; + + if (!IsRealm ()) { + // Noting to do as the execution context is not a Realm. + return RETURN_SUCCESS; + } + + // Read the Realm Config and store the IPA width in a GUID HOB. + Config = AllocatePages (EFI_SIZE_TO_PAGES (sizeof (REALM_CONFIG))); + if (Config == NULL) { + ASSERT (0); + return RETURN_OUT_OF_RESOURCES; + } + + ZeroMem (Config, sizeof (REALM_CONFIG)); + + Status = RsiGetRealmConfig (Config); + if (RETURN_ERROR (Status)) { + ASSERT (0); + return Status; + } + + IpaWidthHobData = BuildGuidHob ( + &gArmCcaIpaWidthGuid, + sizeof (*IpaWidthHobData) + ); + if (IpaWidthHobData == NULL) { + ASSERT (0); + FreePages (Config, EFI_SIZE_TO_PAGES (sizeof (REALM_CONFIG))); + return RETURN_OUT_OF_RESOURCES; + } + + *IpaWidthHobData = Config->IpaWidth; + + FreePages (Config, EFI_SIZE_TO_PAGES (sizeof (REALM_CONFIG))); + + // Configure the MMIO memory regions. + return ArmCcaConfigureMmio (*IpaWidthHobData); +} diff --git a/ArmVirtPkg/Library/ArmCcaInitPeiLib/ArmCcaInitPeiLib.inf b/ArmVirtPkg/Library/ArmCcaInitPeiLib/ArmCcaInitPeiLib.inf new file mode 100644 index 000000000000..f2a321d9cdfc --- /dev/null +++ b/ArmVirtPkg/Library/ArmCcaInitPeiLib/ArmCcaInitPeiLib.inf @@ -0,0 +1,39 @@ +## @file +# Library that implements the Arm CCA initialisation in PEI phase. +# +# Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001001B + BASE_NAME = ArmCcaInitPeiLib + FILE_GUID = 9A8C3768-79ED-487E-8155-BBF4DD638296 + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = ArmCcaInitPeiLib + +[Sources] + ArmCcaInitPeiLib.c + +[Packages] + ArmPkg/ArmPkg.dec + ArmVirtPkg/ArmVirtPkg.dec + MdePkg/MdePkg.dec + +[LibraryClasses] + ArmCcaLib + ArmCcaRsiLib + ArmLib + ArmMmuLib + ArmVirtMemInfoLib + BaseLib + +[Pcd] + gArmTokenSpaceGuid.PcdSystemMemoryBase + gArmTokenSpaceGuid.PcdSystemMemorySize + +[Guids] + gArmCcaIpaWidthGuid diff --git a/ArmVirtPkg/Library/ArmCcaInitPeiLibNull/ArmCcaInitPeiLibNull.c b/ArmVirtPkg/Library/ArmCcaInitPeiLibNull/ArmCcaInitPeiLibNull.c new file mode 100644 index 000000000000..5b606208dbcf --- /dev/null +++ b/ArmVirtPkg/Library/ArmCcaInitPeiLibNull/ArmCcaInitPeiLibNull.c @@ -0,0 +1,59 @@ +/** @file + Library that implements a NULL implementation of the ArmCcaInitPeiLib. + + Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Glossary: + - Rsi or RSI - Realm Service Interface + - IPA - Intermediate Physical Address + - RIPAS - Realm IPA state +**/ + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + Configure the System Memory region as Protected RAM. + + When a VMM creates a Realm, a small amount of DRAM (which contains the + firmware image) and the initial content is configured as Protected RAM. + The remaining System Memory is in the Protected Empty state. The firmware + must then initialise the remaining System Memory as Protected RAM before + it can be accessed. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The execution context is not in a Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaConfigureSystemMemory ( + VOID + ) +{ + return RETURN_UNSUPPORTED; +} + +/** + Perform Arm CCA specific initialisations. + + @retval EFI_SUCCESS Success or execution context is not a Realm. + @retval EFI_OUT_OF_RESOURCES Out of resources. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +EFI_STATUS +EFIAPI +ArmCcaInitialize ( + VOID + ) +{ + // Noting to do as the execution context is not a Realm. + return EFI_SUCCESS; +} diff --git a/ArmVirtPkg/Library/ArmCcaInitPeiLibNull/ArmCcaInitPeiLibNull.inf b/ArmVirtPkg/Library/ArmCcaInitPeiLibNull/ArmCcaInitPeiLibNull.inf new file mode 100644 index 000000000000..f039c7abdb6d --- /dev/null +++ b/ArmVirtPkg/Library/ArmCcaInitPeiLibNull/ArmCcaInitPeiLibNull.inf @@ -0,0 +1,27 @@ +## @file +# Library that implements a NULL implementation of the ArmCcaInitPeiLib. +# +# Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001001B + BASE_NAME = ArmCcaInitPeiLib + FILE_GUID = 60686C60-8433-49EE-9F2C-DDC424A95652 + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = ArmCcaInitPeiLib + +[Sources] + ArmCcaInitPeiLibNull.c + +[Packages] + ArmPkg/ArmPkg.dec + ArmVirtPkg/ArmVirtPkg.dec + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseLib diff --git a/ArmVirtPkg/Library/ArmCcaLib/ArmCcaLib.c b/ArmVirtPkg/Library/ArmCcaLib/ArmCcaLib.c new file mode 100644 index 000000000000..e6ca1b889ea8 --- /dev/null +++ b/ArmVirtPkg/Library/ArmCcaLib/ArmCcaLib.c @@ -0,0 +1,243 @@ +/** @file + Library that implements the Arm CCA helper functions. + + Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Glossary: + - Rsi or RSI - Realm Service Interface + - IPA - Intermediate Physical Address + - RIPAS - Realm IPA state +**/ +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/** + Check if running in a Realm. + + @retval TRUE The execution is within the context of a Realm. + @retval FALSE The execution is not within the context of a Realm. +**/ +BOOLEAN +EFIAPI +IsRealm ( + VOID + ) +{ + RETURN_STATUS Status; + UINT32 UefiImpl; + UINT32 RmmImplLow; + UINT32 RmmImplHigh; + STATIC BOOLEAN RealmWorld = FALSE; + STATIC BOOLEAN FlagsInitialised = FALSE; + + if (!FlagsInitialised) { + FlagsInitialised = TRUE; + if (ArmHasRme ()) { + Status = RsiGetVersion ( + &UefiImpl, + &RmmImplLow, + &RmmImplHigh + ); + if (!RETURN_ERROR (Status)) { + RealmWorld = TRUE; + } + } + } + + return RealmWorld; +} + +/** + Configure the protection attribute for the page tables + describing the memory region. + + The IPA space of a Realm is divided into two halves: + - Protected IPA space and + - Unprotected IPA space. + + Software in a Realm should treat the most significant bit of an + IPA as a protection attribute. + + A Protected IPA is an address in the lower half of a Realms IPA + space. The most significant bit of a Protected IPA is 0. + + An Unprotected IPA is an address in the upper half of a Realms + IPA space. The most significant bit of an Unprotected IPA is 1. + + Note: + - Configuring the memory region as Unprotected IPA enables the + Realm to share the memory region with the Host. + - This function updates the page table entries to reflect the + protection attribute. + - A separate call to transition the memory range using the Realm + Service Interface (RSI) RSI_IPA_STATE_SET command is additionally + required and is expected to be done outside this function. + + @param [in] BaseAddress Base address of the memory region. + @param [in] Length Length of the memory region. + @param [in] IpaWidth IPA width of the Realm. + @param [in] Share If TRUE, set the most significant + bit of the IPA to configure the memory + region as Unprotected IPA. + If FALSE, clear the most significant + bit of the IPA to configure the memory + region as Protected IPA. + + @retval RETURN_SUCCESS IPA protection attribute updated. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The request is not initiated in a + Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaSetMemoryProtectAttribute ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINT64 Length, + IN UINT64 IpaWidth, + IN BOOLEAN Share + ) +{ + if (!IsRealm ()) { + return RETURN_UNSUPPORTED; + } + + if ((Length == 0) || (IpaWidth == 0)) { + return RETURN_INVALID_PARAMETER; + } + + return SetMemoryProtectionAttribute ( + BaseAddress, + Length, + IpaWidth, + Share + ); +} + +/** + Return the IPA width of the Realm. + + The IPA width of the Realm is used to configure the protection attribute + for memory regions, see ArmCcaSetMemoryProtectAttribute(). + + The IPA width of the Realm is present in the Realm config which is read + when the ArmCcaInitPeiLib library hook function ArmCcaInitialize () is + called in the PrePi phase. ArmCcaInitialize () stores the IPA width of + the Realm in a GUID HOB gArmCcaIpaWidthGuid. + + This function searches the GUID HOB gArmCcaIpaWidthGuid and returns the + IPA width value stored therein. + + Note: + - This function must only be called after ArmCcaInitialize () has setup + the GUID HOB gArmCcaIpaWidthGuid. + + @param [out] IpaWidth IPA width of the Realm. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_NOT_FOUND The GUID HOB gArmCcaIpaWidthGuid is not + found and could mean that this function + was called before ArmCcaInitialize () + has created and initialised the GUID + HOB gArmCcaIpaWidthGuid. +**/ +RETURN_STATUS +EFIAPI +GetIpaWidth ( + OUT UINT64 *IpaWidth + ) +{ + VOID *Hob; + UINT64 *CcaIpaWidth; + + if (IpaWidth == NULL) { + return RETURN_INVALID_PARAMETER; + } + + Hob = GetFirstGuidHob (&gArmCcaIpaWidthGuid); + if ((Hob == NULL) || + (GET_GUID_HOB_DATA_SIZE (Hob) != sizeof (UINT64))) + { + return RETURN_NOT_FOUND; + } + + CcaIpaWidth = GET_GUID_HOB_DATA (Hob); + if ((UINT64)*CcaIpaWidth == 0) { + return RETURN_NOT_FOUND; + } + + *IpaWidth = *CcaIpaWidth; + + return RETURN_SUCCESS; +} + +/** Check if the address range is protected MMIO + + @param [in] BaseAddress Base address of the device MMIO region. + @param [in] Length Length of the device MMIO region. + @param [out] IsProtectedMmio TRUE - if the RIPAS for the address range is + protected MMIO. + FALSE - if the RIPAS for the address range is + not protected MMIO. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The request is not initiated in a + Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaMemRangeIsProtectedMmio ( + IN UINT64 BaseAddress, + IN UINT64 Length, + OUT BOOLEAN *IsProtectedMmio + ) +{ + RETURN_STATUS RetStatus; + UINT64 *Base; + UINT64 *Top; + UINT64 *End; + RIPAS State; + + if ((Length == 0) || (IsProtectedMmio == NULL)) { + return RETURN_INVALID_PARAMETER; + } + + Base = (UINT64 *)BaseAddress; + End = (UINT64 *)(BaseAddress + Length); + + while (Base < End) { + Top = End; + RetStatus = RsiGetIpaState ( + Base, + &Top, + &State + ); + if (RETURN_ERROR (RetStatus)) { + // An error occurred. + return RetStatus; + } + + if ((Top <= Base) || (State != RipasDev)) { + // The address range is not protected MMIO. + *IsProtectedMmio = FALSE; + return RetStatus; + } + + Base = Top; + } // while + + *IsProtectedMmio = TRUE; + return RETURN_SUCCESS; +} diff --git a/ArmVirtPkg/Library/ArmCcaLib/ArmCcaLib.inf b/ArmVirtPkg/Library/ArmCcaLib/ArmCcaLib.inf new file mode 100644 index 000000000000..7d90b4535d69 --- /dev/null +++ b/ArmVirtPkg/Library/ArmCcaLib/ArmCcaLib.inf @@ -0,0 +1,34 @@ +## @file +# Library that implements the Arm CCA helper functions. +# +# Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001001B + BASE_NAME = ArmCcaLib + FILE_GUID = 11C18743-52F9-405E-B35B-D7BE91A26F9F + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = ArmCcaLib + +[Sources] + ArmCcaLib.c + +[Packages] + ArmPkg/ArmPkg.dec + ArmVirtPkg/ArmVirtPkg.dec + MdePkg/MdePkg.dec + +[LibraryClasses] + ArmCcaRsiLib + ArmLib + ArmMmuLib + BaseLib + HobLib + +[Guids] + gArmCcaIpaWidthGuid diff --git a/ArmVirtPkg/Library/ArmCcaLibNull/ArmCcaLibNull.c b/ArmVirtPkg/Library/ArmCcaLibNull/ArmCcaLibNull.c new file mode 100644 index 000000000000..907b6cd30f09 --- /dev/null +++ b/ArmVirtPkg/Library/ArmCcaLibNull/ArmCcaLibNull.c @@ -0,0 +1,142 @@ +/** @file + Null implemmentation of the ArmCcaLib library. + + Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Glossary: + - Rsi or RSI - Realm Service Interface + - IPA - Intermediate Physical Address + - RIPAS - Realm IPA state +**/ +#include + +/** + Check if running in a Realm. + + @retval TRUE The execution is within the context of a Realm. + @retval FALSE The execution is not within the context of a Realm. +**/ +BOOLEAN +EFIAPI +IsRealm ( + VOID + ) +{ + return FALSE; +} + +/** + Configure the protection attribute for the page tables + describing the memory region. + + The IPA space of a Realm is divided into two halves: + - Protected IPA space and + - Unprotected IPA space. + + Software in a Realm should treat the most significant bit of an + IPA as a protection attribute. + + A Protected IPA is an address in the lower half of a Realms IPA + space. The most significant bit of a Protected IPA is 0. + + An Unprotected IPA is an address in the upper half of a Realms + IPA space. The most significant bit of an Unprotected IPA is 1. + + Note: + - Configuring the memory region as Unprotected IPA enables the + Realm to share the memory region with the Host. + - This function updates the page table entries to reflect the + protection attribute. + - A separate call to transition the memory range using the Realm + Service Interface (RSI) RSI_IPA_STATE_SET command is additionally + required and is expected to be done outside this function. + + @param [in] BaseAddress Base address of the memory region. + @param [in] Length Length of the memory region. + @param [in] IpaWidth IPA width of the Realm. + @param [in] Share If TRUE, set the most significant + bit of the IPA to configure the memory + region as Unprotected IPA. + If FALSE, clear the most significant + bit of the IPA to configure the memory + region as Protected IPA. + + @retval RETURN_SUCCESS IPA protection attribute updated. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The request is not initiated in a + Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaSetMemoryProtectAttribute ( + IN EFI_PHYSICAL_ADDRESS BaseAddress, + IN UINT64 Length, + IN UINT64 IpaWidth, + IN BOOLEAN Share + ) +{ + return RETURN_UNSUPPORTED; +} + +/** + Return the IPA width of the Realm. + + The IPA width of the Realm is used to configure the protection attribute + for memory regions, see ArmCcaSetMemoryProtectAttribute(). + + The IPA width of the Realm is present in the Realm config which is read + when the ArmCcaInitPeiLib library hook function ArmCcaInitialize () is + called in the PrePi phase. ArmCcaInitialize () stores the IPA width of + the Realm in a GUID HOB gArmCcaIpaWidthGuid. + + This function searches the GUID HOB gArmCcaIpaWidthGuid and returns the + IPA width value stored therein. + + Note: + - This function must only be called after ArmCcaInitialize () has setup + the GUID HOB gArmCcaIpaWidthGuid. + + @param [out] IpaWidth IPA width of the Realm. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_NOT_FOUND The GUID HOB gArmCcaIpaWidthGuid is not + found and could mean that this function + was called before ArmCcaInitialize () + has created and initialised the GUID + HOB gArmCcaIpaWidthGuid. +**/ +RETURN_STATUS +EFIAPI +GetIpaWidth ( + OUT UINT64 *IpaWidth + ) +{ + return RETURN_UNSUPPORTED; +} + +/** Check if the address range is protected MMIO + + @param [in] BaseAddress Base address of the device MMIO region. + @param [in] Length Length of the device MMIO region. + @param [out] IsProtectedMmio TRUE - if the RIPAS for the address range is + protected MMIO. + FALSE - if the RIPAS for the address range is + not protected MMIO. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The request is not initiated in a + Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaMemRangeIsProtectedMmio ( + IN UINT64 BaseAddress, + IN UINT64 Length, + OUT BOOLEAN *IsProtectedMmio + ) +{ + return RETURN_UNSUPPORTED; +} diff --git a/ArmVirtPkg/Library/ArmCcaLibNull/ArmCcaLibNull.inf b/ArmVirtPkg/Library/ArmCcaLibNull/ArmCcaLibNull.inf new file mode 100644 index 000000000000..8f6c251afb14 --- /dev/null +++ b/ArmVirtPkg/Library/ArmCcaLibNull/ArmCcaLibNull.inf @@ -0,0 +1,28 @@ +## @file +# Null implemmentation of the ArmCcaLib library. +# +# Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001001B + BASE_NAME = ArmCcaLib + FILE_GUID = 9E3F7AAA-10A6-4513-A960-B87F4D7DCFC5 + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = ArmCcaLib + +[Sources] + ArmCcaLibNull.c + + +[Packages] + ArmVirtPkg/ArmVirtPkg.dec + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseLib + diff --git a/ArmVirtPkg/Library/ArmCcaRsiLib/ArmCcaRsi.h b/ArmVirtPkg/Library/ArmCcaRsiLib/ArmCcaRsi.h new file mode 100644 index 000000000000..fdde88cc38c0 --- /dev/null +++ b/ArmVirtPkg/Library/ArmCcaRsiLib/ArmCcaRsi.h @@ -0,0 +1,60 @@ +/** @file + Definitions for Arm CCA Realm Service Interface. + + Copyright (c) 2022 - 2023, ARM Ltd. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Glossary: + - Rsi or RSI - Realm Service Interface + - IPA - Intermediate Physical Address + - RIPAS - Realm IPA state + + @par Reference(s): + - Realm Management Monitor (RMM) Specification, version 1.0-rel0 + (https://developer.arm.com/documentation/den0137/) +**/ + +#ifndef ARM_CCA_RSI_H_ +#define ARM_CCA_RSI_H_ + +// FIDs for Realm Service Interface calls. +#define FID_RSI_ATTESTATION_TOKEN_CONTINUE 0xC4000195 +#define FID_RSI_ATTESTATION_TOKEN_INIT 0xC4000194 +#define FID_RSI_FEATURES 0xC4000191 +#define FID_RSI_HOST_CALL 0xC4000199 +#define FID_RSI_IPA_STATE_GET 0xC4000198 +#define FID_RSI_IPA_STATE_SET 0xC4000197 +#define FID_RSI_MEASUREMENT_EXTEND 0xC4000193 +#define FID_RSI_MEASUREMENT_READ 0xC4000192 +#define FID_RSI_REALM_CONFIG 0xC4000196 +#define FID_RSI_VERSION 0xC4000190 + +/** RSI Command Return codes + See Section B5.4.1, RMM Specification, version 1.0-rel0. + The width of the RsiCommandReturnCode enumeration is 64 bits. +*/ +#define RSI_SUCCESS 0ULL +#define RSI_ERROR_INPUT 1ULL +#define RSI_ERROR_STATE 2ULL +#define RSI_INCOMPLETE 3ULL +#define RSI_ERROR_UNKNOWN 4ULL + +/** RSI interface Version + See Section B4.4.3, RMM Specification, version A-bet0. + The width of the RsiInterfaceVersion fieldset is 64 bits. +*/ +#define RSI_VER_MINOR_MASK 0x0000FFFFULL +#define RSI_VER_MAJOR_MASK 0x7FFF0000ULL +#define RSI_VER_MAJOR_SHIFT 16 +#define RSI_VERSION_MASK (RSI_VER_MAJOR_MASK | RSI_VER_MINOR_MASK) + +#define RMM_VERSION(Major, Minor) ((Minor & RSI_VER_MINOR_MASK) | \ + ((Major << RSI_VER_MAJOR_SHIFT) & RSI_VER_MAJOR_MASK)) + +#define GET_MAJOR_REVISION(Rev) \ + ((Rev & RSI_VER_MAJOR_MASK) >> RSI_VER_MAJOR_SHIFT) + +#define GET_MINOR_REVISION(Rev) \ + ((Rev & RSI_VER_MINOR_MASK)) + +#endif // ARM_CCA_RSI_H_ diff --git a/ArmVirtPkg/Library/ArmCcaRsiLib/ArmCcaRsiLib.c b/ArmVirtPkg/Library/ArmCcaRsiLib/ArmCcaRsiLib.c new file mode 100644 index 000000000000..184709933faf --- /dev/null +++ b/ArmVirtPkg/Library/ArmCcaRsiLib/ArmCcaRsiLib.c @@ -0,0 +1,777 @@ +/** @file + Library that implements the Arm CCA Realm Service Interface calls. + + Copyright (c) 2022 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Glossary: + - Rsi or RSI - Realm Service Interface + - IPA - Intermediate Physical Address + - RIPAS - Realm IPA state + - REM - Realm Extensible Measurement + + @par Reference(s): + - Realm Management Monitor (RMM) Specification, version 1.0-rel0 + (https://developer.arm.com/documentation/den0137/) + +**/ +#include + +#include +#include +#include +#include +#include +#include +#include "ArmCcaRsi.h" + +/** The version of RSI specification implemented by this module. +*/ +STATIC CONST UINT32 mRsiImplVersion = RMM_VERSION (1, 0); + +/** + Convert the RSI status code to EFI Status code. + + @param [in] RsiCommandReturnCode RSI status code. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_ABORTED The operation was aborted as the state + of the Realm or REC does not match the + state expected by the command. + @retval RETURN_NOT_READY The operation requested by the command + is not complete. + **/ +STATIC +RETURN_STATUS +RsiCmdStatusToEfiStatus ( + IN UINT64 RsiCommandReturnCode + ) +{ + switch (RsiCommandReturnCode) { + case RSI_SUCCESS: + return RETURN_SUCCESS; + case RSI_ERROR_INPUT: + return RETURN_INVALID_PARAMETER; + case RSI_ERROR_STATE: + case RSI_ERROR_UNKNOWN: + return RETURN_ABORTED; + case RSI_INCOMPLETE: + return RETURN_NOT_READY; + default: + // Unknown error code. + ASSERT (0); + break; + } // switch + + return RETURN_ABORTED; +} + +/** + Check if the address is aligned to the size of the Realm granule. + + @param [in] Address Address to check granule alignment. + + @retval TRUE Address is aligned to the Realm granule size. + @retval FALSE Address is not aligned to the Realm granule size. +**/ +STATIC +BOOLEAN +EFIAPI +AddrIsGranuleAligned ( + IN UINT64 *Address + ) +{ + if (((UINT64)Address & (REALM_GRANULE_SIZE - 1)) != 0) { + return FALSE; + } + + return TRUE; +} + +/** + Continue the operation to retrieve an attestation token. + + @param [out] TokenBuffer Pointer to a buffer to store the + retrieved attestation token. + @param [in] Offset Offset within Token buffer granule + to start of buffer in bytes. + @param [in,out] TokenSize On input size of the token buffer, + and on output size of the token + returned if operation is successful, + otherwise 0. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_ABORTED The operation was aborted as the state + of the Realm or REC does not match the + state expected by the command. + Or the Token generation failed for an + unknown or IMPDEF reason. + @retval RETURN_NOT_READY The operation requested by the command + is not complete. + **/ +STATIC +RETURN_STATUS +EFIAPI +RsiAttestationTokenContinue ( + OUT UINT8 *CONST TokenBuffer, + IN UINT64 CONST Offset, + IN OUT UINT64 *CONST TokenSize + ) +{ + RETURN_STATUS Status; + ARM_SMC_ARGS SmcCmd; + + ZeroMem (&SmcCmd, sizeof (SmcCmd)); + SmcCmd.Arg0 = FID_RSI_ATTESTATION_TOKEN_CONTINUE; + // Set the IPA of the Granule to which the token will be written. + SmcCmd.Arg1 = (UINTN)TokenBuffer; + // Set the Offset within Granule to start of buffer in bytes + SmcCmd.Arg2 = (UINTN)Offset; + // Set the size of the buffer in bytes + SmcCmd.Arg3 = (UINTN)*TokenSize; + + ArmCallSmc (&SmcCmd); + Status = RsiCmdStatusToEfiStatus (SmcCmd.Arg0); + if (!RETURN_ERROR (Status)) { + // Update the token size + *TokenSize = SmcCmd.Arg1; + } else { + // Clear the TokenBuffer on error. + ZeroMem (TokenBuffer, *TokenSize); + *TokenSize = 0; + } + + return Status; +} + +/** + Initialize the operation to retrieve an attestation token. + + @param [in] ChallengeData Pointer to the challenge data to be + included in the attestation token. + @param [in] ChallengeDataSizeBits Size of the challenge data in bits. + @param [out] MaxTokenSize Pointer to an integer to retrieve + the maximum attestation token size. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + **/ +STATIC +RETURN_STATUS +EFIAPI +RsiAttestationTokenInit ( + IN CONST UINT8 *CONST ChallengeData, + IN UINT64 ChallengeDataSizeBits, + OUT UINT64 *CONST MaxTokenSize + ) +{ + RETURN_STATUS Status; + ARM_SMC_ARGS SmcCmd; + UINT8 *Buffer8; + CONST UINT8 *Data8; + UINT64 Count; + UINT8 TailBits; + + /* See A7.2.2 Attestation token generation, RMM Specification, version A-bet0 + IWTKDD - If the size of the challenge provided by the relying party is less + than 64 bytes, it should be zero-padded prior to calling + RSI_ATTESTATION_TOKEN_INIT. + + Therefore, zero out the SmcCmd memory before coping the ChallengeData + bits. + */ + ZeroMem (&SmcCmd, sizeof (SmcCmd)); + SmcCmd.Arg0 = FID_RSI_ATTESTATION_TOKEN_INIT; + // Copy challenge data. + Buffer8 = (UINT8 *)&SmcCmd.Arg1; + Data8 = ChallengeData; + + // First copy whole bytes + Count = ChallengeDataSizeBits >> 3; + CopyMem (Buffer8, Data8, Count); + + // Now copy any remaining tail bits. + TailBits = ChallengeDataSizeBits & (8 - 1); + if (TailBits > 0) { + // Advance buffer pointers. + Buffer8 += Count; + Data8 += Count; + + // Copy tail byte. + *Buffer8 = *Data8; + + // Clear unused tail bits. + *Buffer8 &= ~(0xFF << TailBits); + } + + ArmCallSmc (&SmcCmd); + Status = RsiCmdStatusToEfiStatus (SmcCmd.Arg0); + if (RETURN_ERROR (Status)) { + // Set the max token size to zero + *MaxTokenSize = 0; + } else { + *MaxTokenSize = SmcCmd.Arg1; + } + + return Status; +} + +/** + Free the attestation token buffer. + + @param [in] TokenBuffer Pointer to the retrieved + attestation token. + @param [in] TokenBufferSize Size of the token buffer. +**/ +VOID +RsiFreeAttestationToken ( + IN UINT8 *CONST TokenBuffer, + IN UINT64 CONST TokenBufferSize + ) +{ + if (TokenBuffer != NULL) { + if (TokenBufferSize > 0) { + // Scrub the token buffer + ZeroMem (TokenBuffer, TokenBufferSize); + } + + FreePool (TokenBuffer); + } +} + +/** + Retrieve an attestation token from the RMM. + + @param [in] ChallengeData Pointer to the challenge data to be + included in the attestation token. + @param [in] ChallengeDataSizeBits Size of the challenge data in bits. + @param [out] TokenBuffer Pointer to a buffer to store the + retrieved attestation token. + @param [out] TokenBufferSize Length of token data returned. + + Note: The TokenBuffer allocated must be freed by the caller + using RsiFreeAttestationToken(). + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_ABORTED The operation was aborted as the state + of the Realm or REC does not match the + state expected by the command. + Or the Token generation failed for an + unknown or IMPDEF reason. + @retval RETURN_NOT_READY The operation requested by the command + is not complete. +**/ +RETURN_STATUS +EFIAPI +RsiGetAttestationToken ( + IN CONST UINT8 *CONST ChallengeData, + IN UINT64 ChallengeDataSizeBits, + OUT UINT8 **CONST TokenBuffer, + OUT UINT64 *CONST TokenBufferSize + ) +{ + RETURN_STATUS Status; + UINT8 *Granule; + UINT64 GranuleSize; + UINT64 Offset; + UINT8 *Token; + UINT64 TokenSize; + UINT64 MaxTokenSize; + + if ((TokenBuffer == NULL) || + (TokenBufferSize == NULL) || + (ChallengeData == NULL)) + { + return RETURN_INVALID_PARAMETER; + } + + if (ChallengeDataSizeBits > MAX_CHALLENGE_DATA_SIZE_BITS) { + return RETURN_INVALID_PARAMETER; + } + + /* See A7.2.2 Attestation token generation, RMM Specification, version A-bet0 + IWTKDD - Arm recommends that the challenge should contain at least 32 bytes + of unique data. + */ + if (ChallengeDataSizeBits < MIN_CHALLENGE_DATA_SIZE_BITS) { + DEBUG ((DEBUG_WARN, "Minimum Challenge data size should be 32 bytes\n")); + } + + Status = RsiAttestationTokenInit ( + ChallengeData, + ChallengeDataSizeBits, + &MaxTokenSize + ); + if (RETURN_ERROR (Status)) { + ASSERT (0); + return Status; + } + + // Allocate a granule to retrieve the attestation token chunk. + Granule = (UINT8 *)AllocateAlignedPages ( + EFI_SIZE_TO_PAGES (REALM_GRANULE_SIZE), + REALM_GRANULE_SIZE + ); + if (Granule == NULL) { + ASSERT (0); + return RETURN_OUT_OF_RESOURCES; + } + + // Allocate a buffer to store the retrieved attestation token. + Token = AllocateZeroPool (MaxTokenSize); + if (Token == NULL) { + ASSERT (0); + Status = RETURN_OUT_OF_RESOURCES; + goto exit_handler; + } + + TokenSize = 0; + do { + // Retrieve one Granule of data per loop iteration + ZeroMem (Granule, REALM_GRANULE_SIZE); + Offset = 0; + do { + // Retrieve sub-Granule chunk of data per loop iteration + GranuleSize = REALM_GRANULE_SIZE - Offset; + Status = RsiAttestationTokenContinue ( + Granule, + Offset, + &GranuleSize + ); + Offset += GranuleSize; + } while ((Status == RETURN_NOT_READY) && (Offset < REALM_GRANULE_SIZE)); + + if (RETURN_ERROR (Status) && (Status != RETURN_NOT_READY)) { + ASSERT (0); + goto exit_handler1; + } + + // "Offset" bytes of data are now ready for consumption from "Granule" + // Copy the new token data from the Granule. + CopyMem (&Token[TokenSize], Granule, Offset); + TokenSize += Offset; + } while ((Status == RETURN_NOT_READY) && (TokenSize < MaxTokenSize)); + + *TokenBuffer = Token; + *TokenBufferSize = TokenSize; + goto exit_handler; + +exit_handler1: + if (Token != NULL) { + // Scrub the old Token + ZeroMem (Token, TokenSize); + FreePool (Token); + } + + *TokenBuffer = NULL; + *TokenBufferSize = 0; + +exit_handler: + // Scrub the Granule buffer + ZeroMem (Granule, REALM_GRANULE_SIZE); + FreePages (Granule, EFI_SIZE_TO_PAGES (REALM_GRANULE_SIZE)); + + return Status; +} + +/** + Returns the IPA state for the page pointed by the address. + + @param [in] Base Base of target IPA region. + @param [in, out] Top End of target IPA region on input. + Top of IPA region which has the + reported RIPAS value on return. + @param [out] State The RIPAS state for the address specified. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiGetIpaState ( + IN UINT64 *Base, + IN OUT UINT64 **Top, + OUT RIPAS *State + ) +{ + RETURN_STATUS Status; + ARM_SMC_ARGS SmcCmd; + + if ((State == NULL) || + (!AddrIsGranuleAligned (Base)) || + (!AddrIsGranuleAligned (*Top)) || + (*Top < Base)) + { + return RETURN_INVALID_PARAMETER; + } + + ZeroMem (&SmcCmd, sizeof (SmcCmd)); + SmcCmd.Arg0 = FID_RSI_IPA_STATE_GET; + SmcCmd.Arg1 = (UINTN)Base; + SmcCmd.Arg2 = (UINTN)*Top; + + ArmCallSmc (&SmcCmd); + Status = RsiCmdStatusToEfiStatus (SmcCmd.Arg0); + if (!RETURN_ERROR (Status)) { + *Top = (UINT64 *)SmcCmd.Arg1; + *State = (RIPAS)(SmcCmd.Arg2 & RIPAS_TYPE_MASK); + } + + return Status; +} + +/** + Sets the IPA state for the pages pointed by the memory range. + + @param [in] Address Address to the start of the memory range. + @param [in] Size Length of the memory range. + @param [in] State The RIPAS state to be configured. + @param [in] Flags The RIPAS change flags. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_ACCESS_DENIED RIPAS change request was rejected. +**/ +RETURN_STATUS +EFIAPI +RsiSetIpaState ( + IN UINT64 *Address, + IN UINT64 Size, + IN RIPAS State, + IN UINT64 Flags + ) +{ + RETURN_STATUS Status; + UINT64 *BaseAddress; + UINT64 *EndAddress; + ARM_SMC_ARGS SmcCmd; + + if ((Size == 0) || + ((Size & (REALM_GRANULE_SIZE - 1)) != 0) || + (!AddrIsGranuleAligned (Address))) + { + return RETURN_INVALID_PARAMETER; + } + + BaseAddress = Address; + // Divide Size by 8 for the pointer arithmetic + // to work, as we are adding to UINT64*. + EndAddress = Address + (Size >> 3); + + while (Size > 0) { + ZeroMem (&SmcCmd, sizeof (SmcCmd)); + SmcCmd.Arg0 = FID_RSI_IPA_STATE_SET; + SmcCmd.Arg1 = (UINTN)BaseAddress; + SmcCmd.Arg2 = (UINTN)EndAddress; + SmcCmd.Arg3 = (UINTN)State; + SmcCmd.Arg4 = Flags; + + ArmCallSmc (&SmcCmd); + Status = RsiCmdStatusToEfiStatus (SmcCmd.Arg0); + if (RETURN_ERROR (Status)) { + break; + } + + BaseAddress = (UINT64 *)SmcCmd.Arg1; + Size = EndAddress - BaseAddress; + + if ((SmcCmd.Arg2 & RSI_RESPONSE_MASK) == RIPAS_CHANGE_RESPONSE_REJECT) { + Status = RETURN_ACCESS_DENIED; + break; + } + } // while + + return Status; +} + +/** + Extends a measurement to a REM. + + @param [in] MeasurementIndex Index of the REM. + @param [in] Measurement Pointer to the measurement buffer. + @param [in] MeasurementSize Size of the measurement data. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiExtendMeasurement ( + IN UINTN MeasurementIndex, + IN CONST UINT8 *CONST Measurement, + IN UINTN MeasurementSize + ) +{ + ARM_SMC_ARGS SmcCmd; + UINT64 *Data64; + + if ((MeasurementIndex < MIN_REM_INDEX) || + (MeasurementIndex > MAX_REM_INDEX) || + (Measurement == NULL) || + (MeasurementSize == 0) || + (MeasurementSize > MAX_MEASUREMENT_DATA_SIZE_BYTES)) + { + return RETURN_INVALID_PARAMETER; + } + + ZeroMem (&SmcCmd, sizeof (SmcCmd)); + + SmcCmd.Arg0 = FID_RSI_MEASUREMENT_EXTEND; + SmcCmd.Arg1 = MeasurementIndex; + SmcCmd.Arg2 = MeasurementSize; + + Data64 = &SmcCmd.Arg3; + CopyMem (Data64, Measurement, MeasurementSize); + + ArmCallSmc (&SmcCmd); + return RsiCmdStatusToEfiStatus (SmcCmd.Arg0); +} + +/** + Read the measurement value from a REM. + + @param [in] MeasurementIndex Index of the REM. + @param [out] MeasurementBuffer Pointer to store the measurement data. + @param [in] MeasurementBufferSize Size of the measurement buffer. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiReadMeasurement ( + IN UINTN MeasurementIndex, + OUT UINT8 *CONST MeasurementBuffer, + IN UINTN MeasurementBufferSize + ) +{ + RETURN_STATUS Status; + ARM_SMC_ARGS SmcCmd; + UINT64 *Data64; + + if ((MeasurementIndex < MIN_REM_INDEX) || + (MeasurementIndex > MAX_REM_INDEX) || + (MeasurementBuffer == NULL)) + { + return RETURN_INVALID_PARAMETER; + } + + if (MeasurementBufferSize < MAX_MEASUREMENT_DATA_SIZE_BYTES) { + return RETURN_BUFFER_TOO_SMALL; + } + + ZeroMem (&SmcCmd, sizeof (SmcCmd)); + SmcCmd.Arg0 = FID_RSI_MEASUREMENT_READ; + SmcCmd.Arg1 = MeasurementIndex; + + ArmCallSmc (&SmcCmd); + Status = RsiCmdStatusToEfiStatus (SmcCmd.Arg0); + if (!RETURN_ERROR (Status)) { + Data64 = &SmcCmd.Arg1; + CopyMem (MeasurementBuffer, Data64, MAX_MEASUREMENT_DATA_SIZE_BYTES); + } + + return Status; +} + +/** + Read the Realm Configuration. + + @param [out] Config Pointer to the address of the buffer to retrieve + the Realm configuration. + + Note: The buffer to retrieve the Realm configuration must be aligned to the + Realm granule size. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiGetRealmConfig ( + OUT REALM_CONFIG *Config + ) +{ + ARM_SMC_ARGS SmcCmd; + + if ((Config == NULL) || (!AddrIsGranuleAligned ((UINT64 *)Config))) { + return RETURN_INVALID_PARAMETER; + } + + // Add static asserts to check that the Realm Config is as what we expect. + STATIC_ASSERT (sizeof (REALM_CONFIG) == SIZE_4KB); + STATIC_ASSERT ( + OFFSET_OF (REALM_CONFIG, IpaWidth) == REALM_CFG_OFFSET_IPA_WIDTH + ); + STATIC_ASSERT ( + OFFSET_OF (REALM_CONFIG, HashAlgorithm) == REALM_CFG_OFFSET_HASH_ALGO + ); + STATIC_ASSERT ( + OFFSET_OF (REALM_CONFIG, Reserved) == REALM_CFG_OFFSET_RESERVED + ); + STATIC_ASSERT ( + OFFSET_OF (REALM_CONFIG, Rpv) == REALM_CFG_OFFSET_RPV + ); + STATIC_ASSERT ( + OFFSET_OF (REALM_CONFIG, Reserved1) == REALM_CFG_OFFSET_RESERVED1 + ); + + ZeroMem (&SmcCmd, sizeof (SmcCmd)); + SmcCmd.Arg0 = FID_RSI_REALM_CONFIG; + SmcCmd.Arg1 = (UINTN)Config; + + ArmCallSmc (&SmcCmd); + return RsiCmdStatusToEfiStatus (SmcCmd.Arg0); +} + +/** + Make a Host Call. + + A Host call can be used by a Realm to make a hypercall. + On Realm execution of HVC, an Unknown exception is taken to the Realm. + + @param [in] Args Pointer to the IPA of the Host call data + structure. + + Note: The IPA of the Host call arguments data structure must be aligned + to the Realm granule size. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiHostCall ( + IN HOST_CALL_ARGS *Args + ) +{ + ARM_SMC_ARGS SmcCmd; + + // The RMM specification, version 1.0-eac1, relaxes the alignment + // requirement for RSI_HOST_CALL from 4KB to 256B. Also see RMM + // specification, sections B4.3.3 RSI_HOST_CALL command and + // section B4.3.3.2 Failure conditions. + if ((Args == NULL) || (((UINT64)Args & (0x100 - 1)) != 0)) { + return RETURN_INVALID_PARAMETER; + } + + // See RMM specification, version 1.0-bet1, Section B4.4.2 RsiHostCall type + // The width of the RsiHostCall structure is 256 (0x100) bytes. + STATIC_ASSERT (sizeof (HOST_CALL_ARGS) == 0x100); + + // Clear the reserved fields + ZeroMem (&Args->Reserved1, sizeof (Args->Reserved1)); + + ZeroMem (&SmcCmd, sizeof (SmcCmd)); + SmcCmd.Arg0 = FID_RSI_HOST_CALL; + SmcCmd.Arg1 = (UINTN)Args; + + ArmCallSmc (&SmcCmd); + return RsiCmdStatusToEfiStatus (SmcCmd.Arg0); +} + +/** + Get the version of the RSI implementation. + + @param [out] UefiImpl The version of the RSI specification + implemented by the UEFI firmware. + @param [out] RmmImplLow The low version of the RSI specification + implemented by the RMM. + @param [out] RmmImplHigh The high version of the RSI specification + implemented by the RMM. + + @retval RETURN_SUCCESS Success. + @retval RETURN_UNSUPPORTED The execution context is not a Realm. + @retval RETURN_INCOMPATIBLE_VERSION The Firmware and RMM specification + revisions are not compatible. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiGetVersion ( + OUT UINT32 *CONST UefiImpl, + OUT UINT32 *CONST RmmImplLow, + OUT UINT32 *CONST RmmImplHigh + ) +{ + RETURN_STATUS Status; + ARM_SMC_ARGS SmcCmd; + + if ((UefiImpl == NULL) || (RmmImplLow == NULL) || (RmmImplHigh == NULL)) { + return RETURN_INVALID_PARAMETER; + } + + ZeroMem (&SmcCmd, sizeof (SmcCmd)); + SmcCmd.Arg0 = FID_RSI_VERSION; + SmcCmd.Arg1 = mRsiImplVersion; + ArmCallSmc (&SmcCmd); + if (SmcCmd.Arg0 == MAX_UINT64) { + // This FID is not implemented, which means + // we are not running in a Realm, therefore + // return the error code as unsupported. + return RETURN_UNSUPPORTED; + } + + *RmmImplLow = (SmcCmd.Arg1 & RSI_VERSION_MASK); + *RmmImplHigh = (SmcCmd.Arg2 & RSI_VERSION_MASK); + *UefiImpl = mRsiImplVersion; + + // The RSI_VERSION command does not have any failure + // conditions see section B5.3.10.2 Failure conditions + // Therefore the only defined return values are + // RSI_SUCCESS and RSI_ERROR_INPUT. + Status = RsiCmdStatusToEfiStatus (SmcCmd.Arg0); + if (Status == RETURN_INVALID_PARAMETER) { + // RSI_VERSION returns RSI_ERROR_INPUT when + // the RMM does not support an interface revision + // which is compatible with the requested revision. + // Since RSI_ERROR_INPUT is mapped to RETURN_INVALID_PARAMETER + // by RsiCmdStatusToEfiStatus(), return the status code as + // RETURN_INCOMPATIBLE_VERSION. + return RETURN_INCOMPATIBLE_VERSION; + } + + // Add an assert in case RMM returns a different error code than expected. + ASSERT (Status == RETURN_SUCCESS); + return Status; +} + +/** + Get the features supported by the RSI implementation. + + RMM implementations across different CCA platforms may support + disparate features and may offer disparate configuration options + for Realms. The features supported by an RSI implementation are + discovered by reading feature pseudo-register values using the + RSI_FEATURES command. + + @param [in] FeatureRegIndex The Feature Register Index. + @param [out] FeatureRegValue The Feature Register Value. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. +**/ +RETURN_STATUS +EFIAPI +RsiGetFeatures ( + IN UINT64 FeatureRegIndex, + OUT UINT64 *FeatureRegValue + ) +{ + ARM_SMC_ARGS SmcCmd; + + if (FeatureRegValue == NULL) { + return RETURN_INVALID_PARAMETER; + } + + ZeroMem (&SmcCmd, sizeof (SmcCmd)); + SmcCmd.Arg0 = FID_RSI_FEATURES; + SmcCmd.Arg1 = FeatureRegIndex; + + ArmCallSmc (&SmcCmd); + *FeatureRegValue = SmcCmd.Arg1; + return RsiCmdStatusToEfiStatus (SmcCmd.Arg0); +} diff --git a/ArmVirtPkg/Library/ArmCcaRsiLib/ArmCcaRsiLib.inf b/ArmVirtPkg/Library/ArmCcaRsiLib/ArmCcaRsiLib.inf new file mode 100644 index 000000000000..1e2b72f31258 --- /dev/null +++ b/ArmVirtPkg/Library/ArmCcaRsiLib/ArmCcaRsiLib.inf @@ -0,0 +1,29 @@ +## @file +# Library that implements the Arm CCA Realm Service Interface calls. +# +# Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.
+# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001001B + BASE_NAME = ArmCcaRsiLib + FILE_GUID = 5EF34A0A-28B5-4E57-A999-CC1528FC629A + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = ArmCcaRsiLib + +[Sources] + ArmCcaRsiLib.c + ArmCcaRsi.h + +[Packages] + ArmPkg/ArmPkg.dec + ArmVirtPkg/ArmVirtPkg.dec + MdePkg/MdePkg.dec + +[LibraryClasses] + ArmLib + ArmSmcLib diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/ArmPlatformDeviceInfoLib.inf b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/ArmPlatformDeviceInfoLib.inf new file mode 100644 index 000000000000..e3491abaa6ab --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/ArmPlatformDeviceInfoLib.inf @@ -0,0 +1,50 @@ +## @file +# Arm Platform Device Info library. +# +# Copyright (c) 2024, Arm Limited. All rights reserved. +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001001B + BASE_NAME = ArmPlatformDeviceInfoLib + FILE_GUID = 7f5f99e7-4022-4b56-8d68-c0dc8f41aab2 + MODULE_TYPE = BASE + VERSION_STRING = 1.0 + LIBRARY_CLASS = ArmPlatformDeviceInfoLib + +[Sources] + HwInfoParser/Arm/Gic/ArmGicCParser.c + HwInfoParser/Arm/Gic/ArmGicCParser.h + HwInfoParser/Arm/Gic/ArmGicDispatcher.c + HwInfoParser/Arm/Gic/ArmGicDispatcher.h + HwInfoParser/Arm/Gic/ArmGicDParser.c + HwInfoParser/Arm/Gic/ArmGicDParser.h + HwInfoParser/Arm/Gic/ArmGicRParser.c + HwInfoParser/Arm/Gic/ArmGicRParser.h + HwInfoParser/Common/DeviceParser.c + HwInfoParser/Common/DeviceParser.h + HwInfoParser/FdtInfoParser.c + HwInfoParser/FdtInfoParser.h + HwInfoParser/FdtUtility.c + HwInfoParser/FdtUtility.h + HwInfoParser/Pci/PciConfigSpaceParser.c + HwInfoParser/Pci/PciConfigSpaceParser.h + HwInfoParser/Serial/SerialPortParser.c + HwInfoParser/Serial/SerialPortParser.h + HwInfoParser/Rtc/RtcParser.c + HwInfoParser/Rtc/RtcParser.h + +[Packages] + ArmVirtPkg/ArmVirtPkg.dec + EmbeddedPkg/EmbeddedPkg.dec + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseLib + DebugLib + +[Pcd] + gArmVirtTokenSpaceGuid.PcdMaxPlatDeviceCount diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicCParser.c b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicCParser.c new file mode 100755 index 000000000000..b961a777d16b --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicCParser.c @@ -0,0 +1,436 @@ +/** @file + Arm Gic cpu parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/arm/cpus.yaml + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml + - linux/Documentation/devicetree/bindings/arm/pmu.yaml +**/ + +#include "FdtUtility.h" +#include "FdtInfoParser.h" +#include "ArmGicCParser.h" +#include "ArmGicDispatcher.h" + +/** Parse a "cpus" node and its children "cpu" nodes and return + the CPU node count. + + @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). + @param [out] CpuNodeCount The count of CPU nodes. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +CpusNodeParser ( + IN CONST VOID *Fdt, + OUT UINT32 *CpuNodeCount + ) +{ + EFI_STATUS Status; + INT32 CpusNode; + INT32 AddressCells; + + if (Fdt == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + // The "cpus" node resides at the root of the DT. Fetch it. + CpusNode = fdt_path_offset (Fdt, "/cpus"); + if (CpusNode < 0) { + return EFI_NOT_FOUND; + } + + AddressCells = fdt_address_cells (Fdt, CpusNode); + if (AddressCells < 0) { + ASSERT (FALSE); + return EFI_ABORTED; + } + + // Count the number of "cpu" nodes under the "cpus" node. + Status = FdtCountNamedNodeInBranch (Fdt, CpusNode, "cpu", CpuNodeCount); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + if (*CpuNodeCount == 0) { + ASSERT (FALSE); + return EFI_NOT_FOUND; + } + + return Status; +} + +/** Parse a Gic compatible interrupt-controller node, + extracting GicCv2 information. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] Gicv2IntcNode Offset of a Gicv2 compatible + interrupt-controller node. + @param [in] CpuNodeCount The count of CPU nodes. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +GicCv2IntcNodeParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 Gicv2IntcNode, + IN UINT32 CpuNodeCount + ) +{ + EFI_STATUS Status; + UINT32 Index; + INT32 AddressCells; + INT32 SizeCells; + VOID *Fdt; + CONST UINT8 *GicCValue; + CONST UINT8 *GicCRange; + + CONST UINT8 *Data; + INT32 DataSize; + UINT32 RegSize; + UINT32 RegCount; + UINT64 PhysicalBaseAddress; + UINT64 PhysicalBaseAddressRange; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + GicCValue = NULL; + GicCRange = NULL; + + // Get the #address-cells and #size-cells property values. + Status = FdtGetParentAddressInfo ( + Fdt, + Gicv2IntcNode, + &AddressCells, + &SizeCells + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + // Don't support more than 64 bits and less than 32 bits addresses. + if ((AddressCells < 1) || + (AddressCells > 2) || + (SizeCells < 1) || + (SizeCells > 2)) + { + ASSERT (FALSE); + return EFI_ABORTED; + } + + RegSize = (AddressCells + SizeCells) * sizeof (UINT32); + + Data = fdt_getprop (Fdt, Gicv2IntcNode, "reg", &DataSize); + if ((Data == NULL) || + (DataSize < 0) || + ((DataSize % RegSize) != 0)) + { + // If error or wrong size. + ASSERT (FALSE); + return EFI_ABORTED; + } + + RegCount = DataSize/RegSize; + + switch (RegCount) { + case 2: + { + // GicC is at index 1 in the reg property. GicC is mandatory. + GicCValue = Data + (sizeof (UINT32) * + GET_DT_REG_ADDRESS_OFFSET (1, AddressCells, SizeCells)); + GicCRange = Data + (sizeof (UINT32) * + GET_DT_REG_ADDRESS_OFFSET (2, AddressCells, SizeCells)); + break; + } + default: + { + // Not enough or too much information. + ASSERT (FALSE); + return EFI_ABORTED; + } + } + + for (Index = 0; Index < CpuNodeCount; Index++) { + if (AddressCells == 2) { + PhysicalBaseAddress = FdtReadUnaligned64 ((UINT64 *)GicCValue); + PhysicalBaseAddressRange = FdtReadUnaligned64 ((UINT64 *)GicCRange); + } else { + PhysicalBaseAddress = FdtReadUnaligned32 ((UINT32 *)GicCValue); + PhysicalBaseAddressRange = FdtReadUnaligned32 ((UINT32 *)GicCRange); + } + + if (FdtParserHandle->HwAddressInfo != NULL) { + Status = FdtParserHandle->HwAddressInfo ( + FdtParserHandle->Context, + "GICC GicV2", + PhysicalBaseAddress, + PhysicalBaseAddressRange + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + break; + } + } + } // for + + return Status; +} + +/** Parse a Gic compatible interrupt-controller node, + extracting GicCv3 information. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] Gicv3IntcNode Offset of a Gicv3 compatible + interrupt-controller node. + @param [in] CpuNodeCount The count of CPU nodes. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +GicCv3IntcNodeParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 Gicv3IntcNode, + IN UINT32 CpuNodeCount + ) +{ + EFI_STATUS Status; + UINT32 Index; + INT32 AddressCells; + INT32 SizeCells; + UINT32 AdditionalRedistReg; + UINT64 PhysicalBaseAddress; + UINT64 PhysicalBaseAddressRange; + VOID *Fdt; + CONST UINT8 *GicCValue; + CONST UINT8 *GicCRange; + + CONST UINT8 *Data; + INT32 DataSize; + UINT32 RegSize; + UINT32 RegCount; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + GicCValue = NULL; + GicCRange = NULL; + + // Get the #address-cells and #size-cells property values. + Status = FdtGetParentAddressInfo ( + Fdt, + Gicv3IntcNode, + &AddressCells, + &SizeCells + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + // Don't support more than 64 bits and less than 32 bits addresses. + if ((AddressCells < 1) || + (AddressCells > 2) || + (SizeCells < 1) || + (SizeCells > 2)) + { + ASSERT (FALSE); + return EFI_ABORTED; + } + + // The "#redistributor-regions" property is optional. + Data = fdt_getprop (Fdt, Gicv3IntcNode, "#redistributor-regions", &DataSize); + if ((Data != NULL) && (DataSize == sizeof (UINT32))) { + ASSERT (FdtReadUnaligned32 ((UINT32 *)Data) > 1); + AdditionalRedistReg = FdtReadUnaligned32 ((UINT32 *)Data) - 1; + } else { + AdditionalRedistReg = 0; + } + + RegSize = (AddressCells + SizeCells) * sizeof (UINT32); + + /* + Ref: linux/blob/master/Documentation/devicetree/bindings/ + interrupt-controller/arm%2Cgic-v3.yaml + + reg: + description: | + Specifies base physical address(s) and size of the GIC + registers, in the following order: + - GIC Distributor interface (GICD) + - GIC Redistributors (GICR), one range per redistributor region + - GIC CPU interface (GICC) + - GIC Hypervisor interface (GICH) + - GIC Virtual CPU interface (GICV) + GICC, GICH and GICV are optional. + minItems: 2 + maxItems: 4096 + */ + Data = fdt_getprop (Fdt, Gicv3IntcNode, "reg", &DataSize); + if ((Data == NULL) || + (DataSize < 0) || + ((DataSize % RegSize) != 0)) + { + // If error or wrong size. + ASSERT (FALSE); + return EFI_ABORTED; + } + + RegCount = (DataSize / RegSize) - AdditionalRedistReg; + + // The GicD and GicR info is mandatory. + switch (RegCount) { + case 3: + { + // GicC is at index 2 in the reg property. GicC is optional. + // Even though GicC is optional, it is made mandatory in this parser. + GicCValue = Data + (sizeof (UINT32) * + GET_DT_REG_ADDRESS_OFFSET ( + 2 + AdditionalRedistReg, + AddressCells, + SizeCells + )); + GicCRange = Data + (sizeof (UINT32) * + GET_DT_REG_ADDRESS_OFFSET ( + 3 + AdditionalRedistReg, + AddressCells, + SizeCells + )); + // fall-through + } + case 2: + { + // GicR + // GicD + break; + } + default: + { + // Not enough or too much information. + ASSERT (FALSE); + return EFI_ABORTED; + } + } // switch + + if ((GicCValue != NULL) && (GicCRange != NULL)) { + for (Index = 0; Index < CpuNodeCount; Index++) { + if (AddressCells == 2) { + PhysicalBaseAddress = FdtReadUnaligned64 ((UINT64 *)GicCValue); + PhysicalBaseAddressRange = FdtReadUnaligned64 ((UINT64 *)GicCRange); + } else { + PhysicalBaseAddress = FdtReadUnaligned32 ((UINT32 *)GicCValue); + PhysicalBaseAddressRange = FdtReadUnaligned32 ((UINT32 *)GicCRange); + } + + if (FdtParserHandle->HwAddressInfo != NULL) { + Status = FdtParserHandle->HwAddressInfo ( + FdtParserHandle->Context, + "GICC GicV3", + PhysicalBaseAddress, + PhysicalBaseAddressRange + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + break; + } + } + } // for + } + + return Status; +} + +/** GICC information parser function. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] IntcNode Offset of the interrupt-controller node. + @param [in] CpuNodeCount The count of CPU nodes. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +ArmGicCInfoParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 IntcNode, + IN CONST UINT32 GicVersion + ) +{ + EFI_STATUS Status; + VOID *Fdt; + UINT32 CpuNodeCount; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + // Parse the "cpus" nodes and its children "cpu" nodes, and + // return the CPU node count. + Status = CpusNodeParser (Fdt, &CpuNodeCount); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + // Parse the interrupt-controller node according to the Gic version. + switch (GicVersion) { + case 2: + { + Status = GicCv2IntcNodeParser (FdtParserHandle, IntcNode, CpuNodeCount); + break; + } + case 3: + { + Status = GicCv3IntcNodeParser (FdtParserHandle, IntcNode, CpuNodeCount); + break; + } + default: + { + // Unsupported Gic version. + ASSERT (FALSE); + Status = EFI_UNSUPPORTED; + } + } + + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + goto exit_handler; + } + +exit_handler: + return Status; +} diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicCParser.h b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicCParser.h new file mode 100755 index 000000000000..fd23be976585 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicCParser.h @@ -0,0 +1,35 @@ +/** @file + Arm Gic cpu parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml +**/ + +#ifndef ARM_GICC_PARSER_H_ +#define ARM_GICC_PARSER_H_ + +/** GICC information parser function. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] IntcNode Offset of the interrupt-controller node. + @param [in] CpuNodeCount The count of CPU nodes. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +ArmGicCInfoParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 IntcNode, + IN CONST UINT32 GicVersion + ); + +#endif // ARM_GICC_PARSER_H_ diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDParser.c b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDParser.c new file mode 100755 index 000000000000..4872f5251b70 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDParser.c @@ -0,0 +1,169 @@ +/** @file + Arm Gic Distributor Parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml +**/ + +#include "FdtUtility.h" +#include "FdtInfoParser.h" +#include "ArmGicDispatcher.h" +#include "ArmGicDParser.h" + +/** Parse a Gic compatible interrupt-controller node, + extracting GicD information. + + This parser is valid for Gic v2 and v3. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] GicIntcNode Offset of a Gic compatible + interrupt-controller node. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +GicDIntcNodeParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 GicIntcNode + ) +{ + EFI_STATUS Status; + INT32 AddressCells; + INT32 SizeCells; + CONST UINT8 *Data; + INT32 DataSize; + UINT32 RegSize; + UINT64 PhysicalBaseAddress; + UINT64 PhysicalBaseAddressRange; + VOID *Fdt; + CONST UINT8 *GicDValue; + CONST UINT8 *GicDRange; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + Status = FdtGetParentAddressInfo ( + Fdt, + GicIntcNode, + &AddressCells, + &SizeCells + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + // Don't support more than 64 bits and less than 32 bits addresses. + if ((AddressCells < 1) || + (AddressCells > 2) || + (SizeCells < 1) || + (SizeCells > 2)) + { + ASSERT (FALSE); + return EFI_ABORTED; + } + + RegSize = (AddressCells + SizeCells) * sizeof (UINT32); + + Data = fdt_getprop (Fdt, GicIntcNode, "reg", &DataSize); + if ((Data == NULL) || + (DataSize < 0) || + ((DataSize % RegSize) != 0)) + { + // If error or wrong size. + ASSERT (FALSE); + return EFI_ABORTED; + } + + GicDValue = Data + (sizeof (UINT32) * + GET_DT_REG_ADDRESS_OFFSET (0, AddressCells, SizeCells)); + GicDRange = Data + sizeof (UINT32) * AddressCells; + + if (AddressCells == 2) { + PhysicalBaseAddress = FdtReadUnaligned64 ((UINT64 *)GicDValue); + PhysicalBaseAddressRange = FdtReadUnaligned64 ((UINT64 *)GicDRange); + } else { + PhysicalBaseAddress = FdtReadUnaligned32 ((UINT32 *)GicDValue); + PhysicalBaseAddressRange = FdtReadUnaligned32 ((UINT32 *)GicDRange); + } + + if (FdtParserHandle->HwAddressInfo != NULL) { + Status = FdtParserHandle->HwAddressInfo ( + FdtParserHandle->Context, + "GICD", + PhysicalBaseAddress, + PhysicalBaseAddressRange + ); + ASSERT_EFI_ERROR (Status); + } + + return Status; +} + +/** GICD information parser function. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + @param [in] GicVersion The version of the GIC. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +ArmGicDInfoParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch, + IN CONST UINT32 GicVersion + ) +{ + EFI_STATUS Status; + VOID *Fdt; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + if (!FdtNodeHasProperty (Fdt, FdtBranch, "interrupt-controller")) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + // Parse the interrupt-controller depending on its Gic version. + switch (GicVersion) { + case 2: + case 3: + { + // Set the Gic version, then parse the GicD information. + Status = GicDIntcNodeParser (FdtParserHandle, FdtBranch); + break; + } + default: + { + // Unsupported Gic version. + ASSERT (FALSE); + return EFI_UNSUPPORTED; + } + } + + return Status; +} diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDParser.h b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDParser.h new file mode 100755 index 000000000000..fd464f631cfb --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDParser.h @@ -0,0 +1,36 @@ +/** @file + Arm Gic Distributor Parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml +**/ + +#ifndef ARM_GICD_PARSER_H_ +#define ARM_GICD_PARSER_H_ + +/** GICD information parser function. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + @param [in] GicVersion The version of the GIC. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +ArmGicDInfoParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch, + IN CONST UINT32 GicVersion + ); + +#endif // ARM_GICD_PARSER_H_ diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDispatcher.c b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDispatcher.c new file mode 100755 index 000000000000..fe7eecbee7e5 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDispatcher.c @@ -0,0 +1,189 @@ +/** @file + Arm Gic dispatcher. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml +**/ + +#include "FdtUtility.h" +#include "FdtInfoParser.h" +#include "ArmGicCParser.h" +#include "ArmGicDispatcher.h" +#include "ArmGicDParser.h" +#include "ArmGicRParser.h" + +/** List of "compatible" property values for GicV2 interrupt nodes. + + Any other "compatible" value is not supported by this module. +*/ +STATIC CONST COMPATIBILITY_STR GicV2CompatibleStr[] = { + { "arm,cortex-a15-gic" } +}; + +/** COMPATIBILITY_INFO structure for the GICv2. +*/ +CONST COMPATIBILITY_INFO GicV2CompatibleInfo = { + ARRAY_SIZE (GicV2CompatibleStr), + GicV2CompatibleStr +}; + +/** List of "compatible" property values for GicV3 interrupt nodes. + + Any other "compatible" value is not supported by this module. +*/ +STATIC CONST COMPATIBILITY_STR GicV3CompatibleStr[] = { + { "arm,gic-v3" } +}; + +/** COMPATIBILITY_INFO structure for the GICv3. +*/ +CONST COMPATIBILITY_INFO GicV3CompatibleInfo = { + ARRAY_SIZE (GicV3CompatibleStr), + GicV3CompatibleStr +}; + +/** Get the Gic version of an interrupt-controller node. + + @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). + @param [in] IntcNode Interrupt-controller node. + @param [out] GicVersion If success, contains the Gic version of the + interrupt-controller node. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_UNSUPPORTED Unsupported. +**/ +STATIC +EFI_STATUS +EFIAPI +GetGicVersion ( + IN CONST VOID *Fdt, + IN INT32 IntcNode, + OUT UINT32 *GicVersion + ) +{ + if ((Fdt == NULL) || + (GicVersion == NULL)) + { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + if (FdtNodeIsCompatible (Fdt, IntcNode, &GicV2CompatibleInfo)) { + *GicVersion = 2; + } else if (FdtNodeIsCompatible (Fdt, IntcNode, &GicV3CompatibleInfo)) { + *GicVersion = 3; + } else { + // Unsupported Gic version. + ASSERT (FALSE); + return EFI_UNSUPPORTED; + } + + return EFI_SUCCESS; +} + +/** Gic dispatcher. + + This disptacher parses the Device tree for the following infromation: + - GICC + - GICD + - GICR + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +ArmGicDispatcher ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch + ) +{ + EFI_STATUS Status; + INT32 CpusNode; + INT32 IntcNode; + UINT32 GicVersion; + VOID *Fdt; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + // The "cpus" node resides at the root of the DT. Fetch it. + CpusNode = fdt_path_offset (Fdt, "/cpus"); + if (CpusNode < 0) { + return EFI_NOT_FOUND; + } + + // Get the interrupt-controller node associated to the "cpus" node. + Status = FdtGetIntcParentNode (Fdt, CpusNode, &IntcNode); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + if (Status == EFI_NOT_FOUND) { + // Should have found the node. + Status = EFI_ABORTED; + } + + return Status; + } + + Status = GetGicVersion (Fdt, IntcNode, &GicVersion); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + // Parse the GicC information. + Status = ArmGicCInfoParser (FdtParserHandle, IntcNode, GicVersion); + if (EFI_ERROR (Status)) { + // Don't try to parse GicD and GicMsiFrame information + // if no GicC information is found. Return. + ASSERT (Status == EFI_NOT_FOUND); + return Status; + } + + // Parse the GicD information of the "cpus" interrupt-controller node. + Status = ArmGicDInfoParser (FdtParserHandle, IntcNode, GicVersion); + if (EFI_ERROR (Status)) { + // EFI_NOT_FOUND is not tolerated at this point. + ASSERT (FALSE); + return Status; + } + + switch (GicVersion) { + case 4: + case 3: + { + // Parse the GicR information of the interrupt-controller node. + Status = ArmGicRInfoParser (FdtParserHandle, IntcNode, GicVersion); + if (EFI_ERROR (Status)) { + // EFI_NOT_FOUND is not tolerated at this point. + ASSERT (FALSE); + return Status; + } + + break; + } + default: + { + ASSERT (FALSE); + return EFI_UNSUPPORTED; + } + } + + return EFI_SUCCESS; +} diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDispatcher.h b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDispatcher.h new file mode 100755 index 000000000000..f09400bed1be --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicDispatcher.h @@ -0,0 +1,39 @@ +/** @file + Arm Gic dispatcher. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic.yaml + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml +**/ + +#ifndef ARM_GIC_DISPATCHER_H_ +#define ARM_GIC_DISPATCHER_H_ + +/** Gic dispatcher. + + This dispatcher parses the Device tree for the following information: + - GICC + - GICD + - GICR + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +ArmGicDispatcher ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch + ); + +#endif // ARM_GIC_DISPATCHER_H_ diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicRParser.c b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicRParser.c new file mode 100755 index 000000000000..cee42f7ddcb0 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicRParser.c @@ -0,0 +1,217 @@ +/** @file + Arm Gic Redistributor Parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml +**/ + +#include "FdtUtility.h" +#include "FdtInfoParser.h" +#include "ArmGicDispatcher.h" +#include "ArmGicRParser.h" + +/** Parse a Gic compatible interrupt-controller node, + extracting GicR information. + + This parser is valid for Gic v3 and higher. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] GicIntcNode Offset of a Gic compatible + interrupt-controller node. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +GicRIntcNodeParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 GicIntcNode + ) +{ + EFI_STATUS Status; + UINT32 Index; + UINT32 RedistReg; + UINT32 RegSize; + INT32 AddressCells; + INT32 SizeCells; + CONST UINT8 *Data; + INT32 DataSize; + VOID *Fdt; + UINT64 DiscoveryRangeBaseAddress; + UINT64 DiscoveryRangeLength; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + Status = FdtGetParentAddressInfo ( + Fdt, + GicIntcNode, + &AddressCells, + &SizeCells + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + // Don't support more than 64 bits and less than 32 bits addresses. + if ((AddressCells < 1) || + (AddressCells > 2) || + (SizeCells < 1) || + (SizeCells > 2)) + { + ASSERT (FALSE); + return EFI_ABORTED; + } + + // The "#redistributor-regions" property is optional. + // It indicates the number of GicR. + Data = fdt_getprop (Fdt, GicIntcNode, "#redistributor-regions", &DataSize); + if ((Data != NULL) && (DataSize == sizeof (UINT32))) { + // If available, must be on one cell. + RedistReg = FdtReadUnaligned32 ((UINT32 *)Data); + } else { + // The DT Spec says GicR is mandatory so we will + // always have one. + RedistReg = 1; + } + + /* + Ref: linux/blob/master/Documentation/devicetree/bindings/ + interrupt-controller/arm%2Cgic-v3.yaml + + reg: + description: | + Specifies base physical address(s) and size of the GIC + registers, in the following order: + - GIC Distributor interface (GICD) + - GIC Redistributors (GICR), one range per redistributor region + - GIC CPU interface (GICC) + - GIC Hypervisor interface (GICH) + - GIC Virtual CPU interface (GICV) + GICC, GICH and GICV are optional. + minItems: 2 + maxItems: 4096 + + Example: + interrupt-controller@2c010000 { + compatible = "arm,gic-v3"; + #interrupt-cells = <4>; + #address-cells = <1>; + #size-cells = <1>; + ranges; + interrupt-controller; + redistributor-stride = <0x0 0x40000>; // 256kB stride + #redistributor-regions = <2>; + reg = <0x2c010000 0x10000>, // GICD + <0x2d000000 0x800000>, // GICR 1: CPUs 0-31 + <0x2e000000 0x800000>, // GICR 2: CPUs 32-63 + <0x2c040000 0x2000>, // GICC + <0x2c060000 0x2000>, // GICH + <0x2c080000 0x2000>; // GICV + interrupts = <1 9 4>; + ... + } + */ + RegSize = (AddressCells + SizeCells) * sizeof (UINT32); + Data = fdt_getprop (Fdt, GicIntcNode, "reg", &DataSize); + if ((Data == NULL) || + (DataSize < 0) || + ((DataSize % RegSize) != 0)) + { + // If error or wrong size. + ASSERT (FALSE); + return EFI_ABORTED; + } + + Data += GET_DT_REG_ADDRESS_OFFSET (1, AddressCells, SizeCells) + * sizeof (UINT32); + for (Index = 0; Index < RedistReg; Index++) { + if (AddressCells == 2) { + DiscoveryRangeBaseAddress = FdtReadUnaligned64 ((UINT64 *)Data); + } else { + DiscoveryRangeBaseAddress = FdtReadUnaligned32 ((UINT32 *)Data); + } + + Data += sizeof (UINT32) * AddressCells; + + if (SizeCells == 2) { + DiscoveryRangeLength = FdtReadUnaligned64 ((UINT64 *)Data); + } else { + DiscoveryRangeLength = FdtReadUnaligned32 ((UINT32 *)Data); + } + + if (FdtParserHandle->HwAddressInfo != NULL) { + Status = FdtParserHandle->HwAddressInfo ( + FdtParserHandle->Context, + "GICR", + DiscoveryRangeBaseAddress, + DiscoveryRangeLength + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + break; + } + } + + Data += sizeof (UINT32) * SizeCells; + } // for + + return Status; +} + +/** GICR information parser function. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + @param [in] GicVersion The version of the GIC. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +ArmGicRInfoParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch, + IN CONST UINT32 GicVersion + ) +{ + EFI_STATUS Status; + VOID *Fdt; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + if (!FdtNodeHasProperty (Fdt, FdtBranch, "interrupt-controller")) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + if (GicVersion < 3) { + ASSERT (FALSE); + return EFI_UNSUPPORTED; + } + + Status = GicRIntcNodeParser (FdtParserHandle, FdtBranch); + ASSERT_EFI_ERROR (Status); + return Status; +} diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicRParser.h b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicRParser.h new file mode 100755 index 000000000000..ef7f8f38d4c0 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Arm/Gic/ArmGicRParser.h @@ -0,0 +1,35 @@ +/** @file + Arm Gic Redistributor Parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.yaml +**/ + +#ifndef ARM_GICR_PARSER_H_ +#define ARM_GICR_PARSER_H_ + +/** GICR information parser function. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + @param [in] GicVersion The version of the GIC. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +ArmGicRInfoParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch, + IN CONST UINT32 GicVersion + ); + +#endif // ARM_GICR_PARSER_H_ diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Common/DeviceParser.c b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Common/DeviceParser.c new file mode 100755 index 000000000000..eee243c6317b --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Common/DeviceParser.c @@ -0,0 +1,203 @@ +/** @file + Device Node Address Range Parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "FdtUtility.h" +#include "FdtInfoParser.h" + +/** Parse a Device node. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] DeviceNode Offset of a device node. + @param [in] Index Index of the device if there are multiple + instances of the same device type. + @param [in] DevDesc A NULL terminated string containing a short + description of the device. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_UNSUPPORTED Unsupported. +**/ +STATIC +EFI_STATUS +EFIAPI +DeviceNodeParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 DeviceNode, + IN CONST UINT32 Index, + IN CONST CHAR8 *DevDesc + ) +{ + EFI_STATUS Status; + CONST UINT8 *SizeValue; + + INT32 AddressCells; + INT32 SizeCells; + + CONST UINT8 *Data; + INT32 DataSize; + + // The physical base address for the device + UINT64 BaseAddress; + // The Base address length + UINT64 BaseAddressLength; + VOID *Fdt; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + Status = FdtGetParentAddressInfo ( + Fdt, + DeviceNode, + &AddressCells, + &SizeCells + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + // Don't support more than 64 bits and less than 32 bits addresses. + if ((AddressCells < 1) || + (AddressCells > 2) || + (SizeCells < 1) || + (SizeCells > 2)) + { + ASSERT (FALSE); + return EFI_ABORTED; + } + + Data = fdt_getprop (Fdt, DeviceNode, "reg", &DataSize); + if ((Data == NULL) || + (DataSize < (INT32)(sizeof (UINT32) * + GET_DT_REG_ADDRESS_OFFSET (1, AddressCells, SizeCells)) - 1)) + { + // If error or not enough space. + ASSERT (FALSE); + return EFI_ABORTED; + } + + if (AddressCells == 2) { + BaseAddress = FdtReadUnaligned64 ((UINT64 *)Data); + } else { + BaseAddress = FdtReadUnaligned32 ((UINT32 *)Data); + } + + SizeValue = Data + (sizeof (UINT32) * + GET_DT_REG_SIZE_OFFSET (0, AddressCells, SizeCells)); + if (SizeCells == 2) { + BaseAddressLength = FdtReadUnaligned64 ((UINT64 *)SizeValue); + } else { + BaseAddressLength = FdtReadUnaligned32 ((UINT32 *)SizeValue); + } + + if (FdtParserHandle->HwAddressInfo != NULL) { + Status = FdtParserHandle->HwAddressInfo ( + FdtParserHandle->Context, + DevDesc, + BaseAddress, + BaseAddressLength + ); + ASSERT_EFI_ERROR (Status); + } + + return Status; +} + +/** Device dispatcher. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + @param [in] CompatibleInfo List of "compatible" property values for + the device. + @param [in] DevDesc A NULL terminated string containing a short + description of the device. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +DeviceDispatcher ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch, + IN CONST COMPATIBILITY_INFO *CompatibleInfo, + IN CONST CHAR8 *DevDesc + ) +{ + EFI_STATUS Status; + INT32 DevNode; + UINT32 Index; + UINT32 DevNodeCount; + VOID *Fdt; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + // Count the number of device nodes. + Status = FdtCountCompatNodeInBranch ( + Fdt, + FdtBranch, + CompatibleInfo, + &DevNodeCount + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + if (DevNodeCount == 0) { + return EFI_NOT_FOUND; + } + + DevNode = FdtBranch; + for (Index = 0; Index < DevNodeCount; Index++) { + // Search the next device node in the branch. + Status = FdtGetNextCompatNodeInBranch ( + Fdt, + FdtBranch, + CompatibleInfo, + &DevNode + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + if (Status == EFI_NOT_FOUND) { + // Should have found the node. + Status = EFI_ABORTED; + } + + goto exit_handler; + } + + Status = DeviceNodeParser ( + FdtParserHandle, + DevNode, + Index, + DevDesc + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + goto exit_handler; + } + } // for + +exit_handler: + return Status; +} diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Common/DeviceParser.h b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Common/DeviceParser.h new file mode 100755 index 000000000000..5bf4dabed88f --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Common/DeviceParser.h @@ -0,0 +1,37 @@ +/** @file + Device Node Address Range Parser. + + Copyright (c) 2021 - 2024, ARM Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef DEVICE_PARSER_H_ +#define DEVICE_PARSER_H_ + +/** Device dispatcher. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + @param [in] CompatibleInfo List of "compatible" property values for + the device. + @param [in] DevDesc A NULL terminated string containing a short + description of the device. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +DeviceDispatcher ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch, + IN CONST COMPATIBILITY_INFO *CompatibleInfo, + IN CONST CHAR8 *DevDesc + ); + +#endif // DEVICE_PARSER_H_ diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtInfoParser.c b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtInfoParser.c new file mode 100755 index 000000000000..eb758b3317b0 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtInfoParser.c @@ -0,0 +1,191 @@ +/** @file + A Flattened Device Tree parser that scans the platform devices to + retrieve the base address and the range. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#include + +#include "FdtUtility.h" +#include "FdtInfoParser.h" +#include "Arm/Gic/ArmGicDispatcher.h" +#include "PciConfigSpaceParser.h" +#include "SerialPortParser.h" +#include "RtcParser.h" + +/** Function pointer to a parser function. + + @param [in] ParserHandle Handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +typedef +EFI_STATUS +(EFIAPI *FDT_HW_INFO_PARSER_FUNC)( + IN CONST FDT_HW_INFO_PARSER_HANDLE ParserHandle, + IN INT32 FdtBranch + ); + +/** Ordered table of parsers/dispatchers. + + A parser parses a Device Tree to retrieve relevant + hardware information. + + This can also be a dispatcher, i.e. a function that + is not parsing a Device Tree but calling other parsers. +*/ +STATIC CONST FDT_HW_INFO_PARSER_FUNC HwInfoParserTable[] = { + ArmGicDispatcher, + PciConfigInfoParser, + SerialPortDispatcher, + RtcDispatcher +}; + +/** Main dispatcher: sequentially call the parsers/dispatchers + of the HwInfoParserTable. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Parser did not find any device information + in the FDT. +**/ +STATIC +EFI_STATUS +EFIAPI +MainDispatcher ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch + ) +{ + EFI_STATUS Status; + UINT32 Index; + + if (fdt_check_header (FdtParserHandle->Fdt) < 0) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + for (Index = 0; Index < ARRAY_SIZE (HwInfoParserTable); Index++) { + Status = HwInfoParserTable[Index]( + FdtParserHandle, + FdtBranch + ); + if (EFI_ERROR (Status) && + (Status != EFI_NOT_FOUND)) + { + // If EFI_NOT_FOUND, the parser didn't find information in the DT. + // Don't trigger an error. + ASSERT (FALSE); + return Status; + } + } // for + + return EFI_SUCCESS; +} + +/** A callback function that is called with the information about + the platform device found in the FDT and is used to populate + the PLATFORM_DEVICE_INFO structure. + + @param [in] Context Pointer to the platform device information structure. + @param [in] Desc A NULL terminated string describing the device. + @param [in] Base Base address of the device. + @param [in] Range Base address range of the device. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_OUT_OF_RESOURCES Maximum platform device count exceeded. +**/ +STATIC +EFI_STATUS +EFIAPI +HwAddressInfo ( + IN VOID *Context, + IN CONST CHAR8 *Desc, + IN UINT64 Base, + IN UINT64 Range + ) +{ + PLATFROM_DEVICE_INFO *PlatInfo; + UINTN Index; + + if (Context == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + DEBUG ((DEBUG_INFO, "%a: 0x%llx - 0x%llx\n", Desc, Base, Range)); + + // The address range cannot be zero. + if (Range == 0) { + DEBUG (( + DEBUG_ERROR, + "Error: Invalid device range: %a: 0x%llx - 0x%llx\n", + Desc, + Base, + Range + )); + return EFI_INVALID_PARAMETER; + } + + PlatInfo = (PLATFROM_DEVICE_INFO *)Context; + if (PlatInfo->MaxDevices >= MAX_PLAT_DEVICE_COUNT) { + DEBUG ((DEBUG_ERROR, "Error: Maximum platform device count exceeded!\n")); + ASSERT (FALSE); + return EFI_OUT_OF_RESOURCES; + } + + Index = PlatInfo->MaxDevices++; + PlatInfo->Dev[Index].BaseAddress = Base; + PlatInfo->Dev[Index].Length = Range; + AsciiStrnCpyS (PlatInfo->Dev[Index].Desc, 16, Desc, AsciiStrLen (Desc)); + + return EFI_SUCCESS; +} + +/** Parse the platform FDT and populate the platform device info. + + @param [in] FdtBase Pointer to the platform FDT. + @param [in] PlatInfo Pointer to the platform device info to populate. + + @retval EFI_SUCCESS Success. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_NOT_FOUND Parser did not find any device information + in the FDT. +**/ +EFI_STATUS +EFIAPI +ArmParsePlatformDeviceFdt ( + IN VOID *FdtBase, + IN PLATFROM_DEVICE_INFO *PlatInfo + ) +{ + FDT_HW_INFO_PARSER Parser; + + if ((FdtBase == NULL) || + (PlatInfo == NULL) || + (fdt_check_header (FdtBase) != 0)) + { + return EFI_INVALID_PARAMETER; + } + + Parser.Fdt = FdtBase; + Parser.HwAddressInfo = HwAddressInfo; + Parser.Context = PlatInfo; + + return MainDispatcher (&Parser, 0); +} diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtInfoParser.h b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtInfoParser.h new file mode 100755 index 000000000000..8362a851d388 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtInfoParser.h @@ -0,0 +1,52 @@ +/** @file + A Flattened Device Tree parser that scans the platform devices to + retrieve the base address and the range. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#ifndef FDT_INFO_PARSER_H_ +#define FDT_INFO_PARSER_H_ + +/** Function pointer called by the parser to add information. + + @param [in] Context A pointer to the caller's context. + @param [in] Desc An optional NULL terminated string + describing the hardware resource. + @param [in] Base Base address. + @param [in] Range Address range. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_OUT_OF_RESOURCES Maximum platform device count exceeded. +**/ +typedef +EFI_STATUS +(EFIAPI *HW_ADDRESS_INFO)( + IN VOID *Context, + IN CONST CHAR8 *Desc, + IN UINT64 Base, + IN UINT64 Range + ); + +/** A structure describing the instance of the FdtHwInfoParser. +*/ +typedef struct FdtHwInfoParser { + /// Pointer to the HwDataSource i.e. the + /// Flattened Device Tree (Fdt). + VOID *Fdt; + + /// Pointer to the caller's context. + VOID *Context; + + /// Callback function to notify information + /// about platform devices found in the FDT. + HW_ADDRESS_INFO HwAddressInfo; +} FDT_HW_INFO_PARSER; + +/** A pointer type for FDT_HW_INFO_PARSER. +*/ +typedef FDT_HW_INFO_PARSER *FDT_HW_INFO_PARSER_HANDLE; + +#endif // FDT_INFO_PARSER_H_ diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtUtility.c b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtUtility.c new file mode 100755 index 000000000000..3bf059a0d1bc --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtUtility.c @@ -0,0 +1,879 @@ +/** @file + Flattened device tree utility. + + Copyright (c) 2021, ARM Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - Device tree Specification - Release v0.3 + - linux/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.yaml + - linux//Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.yaml +**/ + +#include "FdtUtility.h" + +/** Check whether a node has the input name. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node to check the name. + @param [in] SearchName Node name to search. + This is a NULL terminated string. + + @retval True The node has the input name. + @retval FALSE Otherwise, or error. +**/ +STATIC +BOOLEAN +EFIAPI +FdtNodeHasName ( + IN CONST VOID *Fdt, + IN INT32 Node, + IN CONST VOID *SearchName + ) +{ + CONST CHAR8 *NodeName; + UINT32 Length; + + if ((Fdt == NULL) || + (SearchName == NULL)) + { + ASSERT (FALSE); + return FALSE; + } + + // Always compare the whole string. Don't stop at the "@" char. + Length = (UINT32)AsciiStrLen (SearchName); + + // Get the address of the node name. + NodeName = fdt_offset_ptr (Fdt, Node + FDT_TAGSIZE, Length + 1); + if (NodeName == NULL) { + return FALSE; + } + + // SearchName must be longer than the node name. + if (Length > AsciiStrLen (NodeName)) { + return FALSE; + } + + if (AsciiStrnCmp (NodeName, SearchName, Length) != 0) { + return FALSE; + } + + // The name matches perfectly, or + // the node name is XXX@addr and the XXX matches. + if ((NodeName[Length] == '\0') || + (NodeName[Length] == '@')) + { + return TRUE; + } + + return FALSE; +} + +/** Iterate through the list of strings in the Context, + and check whether at least one string is matching the + "compatible" property of the node. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node to operate the check on. + @param [in] CompatInfo COMPATIBILITY_INFO containing the list of compatible + strings to compare with the "compatible" property + of the node. + + @retval TRUE At least one string matched, the node is compatible. + @retval FALSE Otherwise, or error. +**/ +BOOLEAN +EFIAPI +FdtNodeIsCompatible ( + IN CONST VOID *Fdt, + IN INT32 Node, + IN CONST VOID *CompatInfo + ) +{ + UINT32 Index; + CONST COMPATIBILITY_STR *CompatibleTable; + UINT32 Count; + CONST VOID *Prop; + INT32 PropLen; + + if ((Fdt == NULL) || + (CompatInfo == NULL)) + { + ASSERT (FALSE); + return FALSE; + } + + Count = ((COMPATIBILITY_INFO *)CompatInfo)->Count; + CompatibleTable = ((COMPATIBILITY_INFO *)CompatInfo)->CompatTable; + + // Get the "compatible" property. + Prop = fdt_getprop (Fdt, Node, "compatible", &PropLen); + if ((Prop == NULL) || (PropLen < 0)) { + return FALSE; + } + + for (Index = 0; Index < Count; Index++) { + if (fdt_stringlist_contains ( + Prop, + PropLen, + CompatibleTable[Index].CompatStr + )) + { + return TRUE; + } + } // for + + return FALSE; +} + +/** Check whether a node has a property. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node to operate the check on. + @param [in] PropertyName Name of the property to search. + This is a NULL terminated string. + + @retval True The node has the property. + @retval FALSE Otherwise, or error. +**/ +BOOLEAN +EFIAPI +FdtNodeHasProperty ( + IN CONST VOID *Fdt, + IN INT32 Node, + IN CONST VOID *PropertyName + ) +{ + INT32 Size; + CONST VOID *Prop; + + if ((Fdt == NULL) || + (PropertyName == NULL)) + { + ASSERT (FALSE); + return FALSE; + } + + Prop = fdt_getprop (Fdt, Node, PropertyName, &Size); + if ((Prop == NULL) || (Size < 0)) { + return FALSE; + } + + return TRUE; +} + +/** Get the next node in the whole DT fulfilling a condition. + + The condition to fulfill is checked by the NodeChecker function. + Context is passed to NodeChecker. + + The Device tree is traversed in a depth-first search, starting from Node. + The input Node is skipped. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in, out] Node At entry: Node offset to start the search. + This first node is skipped. + Write (-1) to search the whole tree. + At exit: If success, contains the offset of + the next node fulfilling the + condition. + @param [in, out] Depth Depth is incremented/decremented of the depth + difference between the input Node and the + output Node. + E.g.: If the output Node is a child node + of the input Node, contains (+1). + @param [in] NodeChecker Function called to check if the condition + is fulfilled. + @param [in] Context Context for the NodeChecker. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND No matching node found. +**/ +STATIC +EFI_STATUS +EFIAPI +FdtGetNextCondNode ( + IN CONST VOID *Fdt, + IN OUT INT32 *Node, + IN OUT INT32 *Depth, + IN NODE_CHECKER_FUNC NodeChecker, + IN CONST VOID *Context + ) +{ + INT32 CurrNode; + + if ((Fdt == NULL) || + (Node == NULL) || + (Depth == NULL) || + (NodeChecker == NULL)) + { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + CurrNode = *Node; + do { + CurrNode = fdt_next_node (Fdt, CurrNode, Depth); + if ((CurrNode == -FDT_ERR_NOTFOUND) || + (*Depth < 0)) + { + // End of the tree, no matching node found. + return EFI_NOT_FOUND; + } else if (CurrNode < 0) { + // An error occurred. + ASSERT (FALSE); + return EFI_ABORTED; + } + } while (!NodeChecker (Fdt, CurrNode, Context)); + + // Matching node found. + *Node = CurrNode; + return EFI_SUCCESS; +} + +/** Get the next node in a branch fulfilling a condition. + + The condition to fulfill is checked by the NodeChecker function. + Context is passed to NodeChecker. + + The Device tree is traversed in a depth-first search, starting from Node. + The input Node is skipped. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this + branch. + Write (-1) to search the whole tree. + @param [in] NodeChecker Function called to check if the condition + is fulfilled. + @param [in] Context Context for the NodeChecker. + @param [in, out] Node At entry: Node offset to start the search. + This first node is skipped. + Write (-1) to search the whole tree. + At exit: If success, contains the offset + of the next node in the branch + fulfilling the condition. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND No matching node found. +**/ +STATIC +EFI_STATUS +EFIAPI +FdtGetNextCondNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN NODE_CHECKER_FUNC NodeChecker, + IN CONST VOID *Context, + IN OUT INT32 *Node + ) +{ + EFI_STATUS Status; + INT32 CurrNode; + INT32 Depth; + + if ((Fdt == NULL) || + (Node == NULL) || + (NodeChecker == NULL)) + { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + CurrNode = FdtBranch; + Depth = 0; + + // First, check the Node is in the sub-nodes of the branch. + // This allows to find the relative depth of Node in the branch. + if (CurrNode != *Node) { + for (CurrNode = fdt_next_node (Fdt, CurrNode, &Depth); + (CurrNode >= 0) && (Depth > 0); + CurrNode = fdt_next_node (Fdt, CurrNode, &Depth)) + { + if (CurrNode == *Node) { + // Node found. + break; + } + } // for + + if ((CurrNode < 0) || (Depth <= 0)) { + // Node is not a node in the branch, or an error occurred. + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + } + + // Get the next node in the tree fulfilling the condition, + // in any branch. + Status = FdtGetNextCondNode ( + Fdt, + Node, + &Depth, + NodeChecker, + Context + ); + if (EFI_ERROR (Status)) { + ASSERT (Status == EFI_NOT_FOUND); + return Status; + } + + if (Depth <= 0) { + // The node found is not in the right branch. + return EFI_NOT_FOUND; + } + + return EFI_SUCCESS; +} + +/** Get the next node in a branch having a matching name. + + The Device tree is traversed in a depth-first search, starting from Node. + The input Node is skipped. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] NodeName The node name to search. + This is a NULL terminated string. + @param [in, out] Node At entry: Node offset to start the search. + This first node is skipped. + Write (-1) to search the whole tree. + At exit: If success, contains the offset of + the next node in the branch + having a matching name. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND No matching node found. +**/ +EFI_STATUS +EFIAPI +FdtGetNextNamedNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST CHAR8 *NodeName, + IN OUT INT32 *Node + ) +{ + return FdtGetNextCondNodeInBranch ( + Fdt, + FdtBranch, + FdtNodeHasName, + NodeName, + Node + ); +} + +/** Get the next node in a branch with at least one compatible property. + + The Device tree is traversed in a depth-first search, starting from Node. + The input Node is skipped. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] CompatNamesInfo Table of compatible strings to compare with + the compatible property of the node. + @param [in, out] Node At entry: Node offset to start the search. + This first node is skipped. + Write (-1) to search the whole tree. + At exit: If success, contains the offset of + the next node in the branch + being compatible. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND No matching node found. +**/ +EFI_STATUS +EFIAPI +FdtGetNextCompatNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST COMPATIBILITY_INFO *CompatNamesInfo, + IN OUT INT32 *Node + ) +{ + return FdtGetNextCondNodeInBranch ( + Fdt, + FdtBranch, + FdtNodeIsCompatible, + (CONST VOID *)CompatNamesInfo, + Node + ); +} + +/** Get the next node in a branch having the PropName property. + + The Device tree is traversed in a depth-first search, starting from Node. + The input Node is skipped. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] PropName Name of the property to search. + This is a NULL terminated string. + @param [in, out] Node At entry: Node offset to start the search. + This first node is skipped. + Write (-1) to search the whole tree. + At exit: If success, contains the offset of + the next node in the branch + being compatible. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND No matching node found. +**/ +EFI_STATUS +EFIAPI +FdtGetNextPropNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST CHAR8 *PropName, + IN OUT INT32 *Node + ) +{ + return FdtGetNextCondNodeInBranch ( + Fdt, + FdtBranch, + FdtNodeHasProperty, + (CONST VOID *)PropName, + Node + ); +} + +/** Count the number of Device Tree nodes fulfilling a condition + in a Device Tree branch. + + The condition to fulfill is checked by the NodeChecker function. + Context is passed to NodeChecker. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] NodeChecker Function called to check the condition is + fulfilled. + @param [in] Context Context for the NodeChecker. + @param [out] NodeCount If success, contains the count of nodes + fulfilling the condition. + Can be 0. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +FdtCountCondNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN NODE_CHECKER_FUNC NodeChecker, + IN CONST VOID *Context, + OUT UINT32 *NodeCount + ) +{ + EFI_STATUS Status; + INT32 CurrNode; + + if ((Fdt == NULL) || + (NodeChecker == NULL) || + (NodeCount == NULL)) + { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + *NodeCount = 0; + CurrNode = FdtBranch; + while (TRUE) { + Status = FdtGetNextCondNodeInBranch ( + Fdt, + FdtBranch, + NodeChecker, + Context, + &CurrNode + ); + if (EFI_ERROR (Status) && + (Status != EFI_NOT_FOUND)) + { + ASSERT (FALSE); + return Status; + } else if (Status == EFI_NOT_FOUND) { + break; + } + + (*NodeCount)++; + } + + return EFI_SUCCESS; +} + +/** Count the number of nodes in a branch with the input name. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] NodeName Node name to search. + This is a NULL terminated string. + @param [out] NodeCount If success, contains the count of nodes + fulfilling the condition. + Can be 0. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtCountNamedNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST CHAR8 *NodeName, + OUT UINT32 *NodeCount + ) +{ + return FdtCountCondNodeInBranch ( + Fdt, + FdtBranch, + FdtNodeHasName, + NodeName, + NodeCount + ); +} + +/** Count the number of nodes in a branch with at least + one compatible property. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] CompatNamesInfo Table of compatible strings to + compare with the compatible property + of the node. + @param [out] NodeCount If success, contains the count of nodes + fulfilling the condition. + Can be 0. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtCountCompatNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST COMPATIBILITY_INFO *CompatNamesInfo, + OUT UINT32 *NodeCount + ) +{ + return FdtCountCondNodeInBranch ( + Fdt, + FdtBranch, + FdtNodeIsCompatible, + CompatNamesInfo, + NodeCount + ); +} + +/** Count the number of nodes in a branch having the PropName property. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] PropName Name of the property to search. + This is a NULL terminated string. + @param [out] NodeCount If success, contains the count of nodes + fulfilling the condition. + Can be 0. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtCountPropNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST CHAR8 *PropName, + OUT UINT32 *NodeCount + ) +{ + return FdtCountCondNodeInBranch ( + Fdt, + FdtBranch, + FdtNodeHasProperty, + PropName, + NodeCount + ); +} + +/** Get the interrupt-controller node handling the interrupts of + the input node. + + To do this, recursively search a node with either the "interrupt-controller" + or the "interrupt-parent" property in the parents of Node. + + Devicetree Specification, Release v0.3, + 2.4.1 "Properties for Interrupt Generating Devices": + Because the hierarchy of the nodes in the interrupt tree + might not match the devicetree, the interrupt-parent + property is available to make the definition of an + interrupt parent explicit. The value is the phandle to the + interrupt parent. If this property is missing from a + device, its interrupt parent is assumed to be its devicetree + parent. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node to start the search. + @param [out] IntcNode If success, contains the offset of the + interrupt-controller node. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_NOT_FOUND No interrupt-controller node found. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtGetIntcParentNode ( + IN CONST VOID *Fdt, + IN INT32 Node, + OUT INT32 *IntcNode + ) +{ + CONST UINT32 *PHandle; + INT32 Size; + CONST VOID *Prop; + + if ((Fdt == NULL) || + (IntcNode == NULL)) + { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + while (TRUE) { + // Check whether the node has the "interrupt-controller" property. + Prop = fdt_getprop (Fdt, Node, "interrupt-controller", &Size); + if ((Prop != NULL) && (Size >= 0)) { + // The interrupt-controller has been found. + *IntcNode = Node; + return EFI_SUCCESS; + } else { + // Check whether the node has the "interrupt-parent" property. + PHandle = fdt_getprop (Fdt, Node, "interrupt-parent", &Size); + if ((PHandle != NULL) && (Size == sizeof (UINT32))) { + // The phandle of the interrupt-controller has been found. + // Search the node having this phandle and return it. + Node = fdt_node_offset_by_phandle (Fdt, FdtReadUnaligned32 (PHandle)); + if (Node < 0) { + ASSERT (FALSE); + return EFI_ABORTED; + } + + *IntcNode = Node; + return EFI_SUCCESS; + } else if (Size != -FDT_ERR_NOTFOUND) { + ASSERT (FALSE); + return EFI_ABORTED; + } + } + + if (Node == 0) { + // We are at the root of the tree. Not parent available. + return EFI_NOT_FOUND; + } + + // Get the parent of the node. + Node = fdt_parent_offset (Fdt, Node); + if (Node < 0) { + // An error occurred. + ASSERT (FALSE); + return EFI_ABORTED; + } + } // while +} + +/** Get the "interrupt-cells" property value of the node. + + The "interrupts" property requires to know the number of cells used + to encode an interrupt. This information is stored in the + interrupt-controller of the input Node. + + @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). + @param [in] IntcNode Offset of an interrupt-controller node. + @param [out] IntCells If success, contains the "interrupt-cells" + property of the IntcNode. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +FdtGetInterruptCellsInfo ( + IN CONST VOID *Fdt, + IN INT32 IntcNode, + OUT INT32 *IntCells + ) +{ + CONST UINT32 *Data; + INT32 Size; + + if ((Fdt == NULL) || + (IntCells == NULL)) + { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Data = fdt_getprop (Fdt, IntcNode, "#interrupt-cells", &Size); + if ((Data == NULL) || (Size != sizeof (UINT32))) { + // If error or not on one UINT32 cell. + ASSERT (FALSE); + return EFI_ABORTED; + } + + *IntCells = FdtReadUnaligned32 (Data); + + return EFI_SUCCESS; +} + +/** Get the "#address-cells" and/or "#size-cells" property of the node. + + According to the Device Tree specification, s2.3.5 "#address-cells and + #size-cells": + "If missing, a client program should assume a default value of 2 for + #address-cells, and a value of 1 for #size-cells." + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node having to get the + "#address-cells" and "#size-cells" + properties from. + @param [out] AddressCells If success, number of address-cells. + If the property is not available, + default value is 2. + @param [out] SizeCells If success, number of size-cells. + If the property is not available, + default value is 1. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtGetAddressInfo ( + IN CONST VOID *Fdt, + IN INT32 Node, + OUT INT32 *AddressCells, OPTIONAL + OUT INT32 *SizeCells OPTIONAL + ) +{ + if (Fdt == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + if (AddressCells != NULL) { + *AddressCells = fdt_address_cells (Fdt, Node); + if (*AddressCells < 0) { + ASSERT (FALSE); + return EFI_ABORTED; + } + } + + if (SizeCells != NULL) { + *SizeCells = fdt_size_cells (Fdt, Node); + if (*SizeCells < 0) { + ASSERT (FALSE); + return EFI_ABORTED; + } + } + + return EFI_SUCCESS; +} + +/** Get the "#address-cells" and/or "#size-cells" property of the parent node. + + According to the Device Tree specification, s2.3.5 "#address-cells and + #size-cells": + "If missing, a client program should assume a default value of 2 for + #address-cells, and a value of 1 for #size-cells." + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node having to get the + "#address-cells" and "#size-cells" + properties from its parent. + @param [out] AddressCells If success, number of address-cells. + If the property is not available, + default value is 2. + @param [out] SizeCells If success, number of size-cells. + If the property is not available, + default value is 1. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtGetParentAddressInfo ( + IN CONST VOID *Fdt, + IN INT32 Node, + OUT INT32 *AddressCells, OPTIONAL + OUT INT32 *SizeCells OPTIONAL + ) +{ + if (Fdt == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Node = fdt_parent_offset (Fdt, Node); + if (Node < 0) { + // End of the tree, or an error occurred. + ASSERT (FALSE); + return EFI_ABORTED; + } + + return FdtGetAddressInfo (Fdt, Node, AddressCells, SizeCells); +} + +/** A helper function to support unaligned read access for UINT64 types. + + @param [in] Data Pointer to UINT64 data that may be unaligned. + + @retval The UINT64 value read from the input data pointer. +**/ +UINT64 +FdtReadUnaligned64 ( + CONST UINT64 *Data + ) +{ + return SwapBytes64 (ReadUnaligned64 (Data)); +} + +/** A helper function to support unaligned read access for UINT32 types. + + @param [in] Data Pointer to a UINT32 data that may be unaligned. + + @retval The UINT32 value read from the input data pointer. +**/ +UINT32 +FdtReadUnaligned32 ( + CONST UINT32 *Data + ) +{ + return SwapBytes32 (ReadUnaligned32 (Data)); +} diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtUtility.h b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtUtility.h new file mode 100755 index 000000000000..79e601622258 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/FdtUtility.h @@ -0,0 +1,517 @@ +/** @file + Flattened device tree utility. + + Copyright (c) 2021, ARM Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - Device tree Specification - Release v0.3 + - linux/Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.yaml + - linux//Documentation/devicetree/bindings/interrupt-controller/arm%2Cgic.yaml +**/ + +#ifndef FDT_UTILITY_H_ +#define FDT_UTILITY_H_ + +#include +#include +#include +#include +#include +#include + +/** Get the offset of an address in a "reg" Device Tree property. + + In a Device Tree, the "reg" property stores address/size couples. + They are stored on N 32-bits cells. + Based on the value of the #address-cells, the #size-cells and the + index in the "reg" property, compute the number of 32-bits cells + to skip. + + @param [in] Index Index in the reg property. + @param [in] AddrCells Number of cells used to store an address. + @param [in] SizeCells Number of cells used to store the size of + an address. + + @retval Number of 32-bits cells to skip to access the address. +*/ +#define GET_DT_REG_ADDRESS_OFFSET(Index, AddrCells, SizeCells) ( \ + (Index) * ((AddrCells) + (SizeCells)) \ + ) + +/** Get the offset of an address size in a "reg" Device Tree property. + + In a Device Tree, the "reg" property stores address/size couples. + They are stored on N 32-bits cells. + Based on the value of the #address-cells, the #size-cells and the + index in the "reg" property, compute the number of 32-bits cells + to skip. + + @param [in] Index Index in the reg property. + @param [in] AddrCells Number of cells used to store an address. + @param [in] SizeCells Number of cells used to store the size of + an address. + + @retval Number of 32-bits cells to skip to access the address size. +*/ +#define GET_DT_REG_SIZE_OFFSET(Index, AddrCells, SizeCells) ( \ + GET_DT_REG_ADDRESS_OFFSET ((Index), (AddrCells), (SizeCells)) + \ + (SizeCells) \ + ) + +/// Maximum string length for compatible names. +#define COMPATIBLE_STR_LEN (32U) + +/// Interrupt macros +#define PPI_OFFSET (16U) +#define SPI_OFFSET (32U) +#define DT_PPI_IRQ (1U) +#define DT_SPI_IRQ (0U) +#define DT_IRQ_IS_EDGE_TRIGGERED(x) ((((x) & (BIT0 | BIT1)) != 0)) +#define DT_IRQ_IS_ACTIVE_LOW(x) ((((x) & (BIT1 | BIT3)) != 0)) +#define IRQ_TYPE_OFFSET (0U) +#define IRQ_NUMBER_OFFSET (1U) +#define IRQ_FLAGS_OFFSET (2U) + +/** Get the interrupt Id of an interrupt described in a fdt. + + Data must describe a GIC interrupt. A GIC interrupt is on at least + 3 UINT32 cells. + This function DOES NOT SUPPORT extended SPI range and extended PPI range. + + @param [in] Data Pointer to the first cell of an "interrupts" property. + + @retval The interrupt id. +**/ +UINT32 +EFIAPI +FdtGetInterruptId ( + UINT32 CONST *Data + ); + +/** Get the ACPI interrupt flags of an interrupt described in a fdt. + + Data must describe a GIC interrupt. A GIC interrupt is on at least + 3 UINT32 cells. + + @param [in] Data Pointer to the first cell of an "interrupts" property. + + @retval The interrupt flags (for ACPI). +**/ +UINT32 +EFIAPI +FdtGetInterruptFlags ( + UINT32 CONST *Data + ); + +/** A structure describing a compatibility string. +*/ +typedef struct CompatStr { + CONST CHAR8 CompatStr[COMPATIBLE_STR_LEN]; +} COMPATIBILITY_STR; + +/** Structure containing a list of compatible names and their count. +*/ +typedef struct CompatibilityInfo { + /// Count of entries in the NAME_TABLE. + UINT32 Count; + + /// Pointer to a table storing the names. + CONST COMPATIBILITY_STR *CompatTable; +} COMPATIBILITY_INFO; + +/** Operate a check on a Device Tree node. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] NodeOffset Offset of the node to compare input string. + @param [in] Context Context to operate the check on the node. + + @retval True The check is correct. + @retval FALSE Otherwise, or error. +**/ +typedef +BOOLEAN +(EFIAPI *NODE_CHECKER_FUNC)( + IN CONST VOID *Fdt, + IN INT32 NodeOffset, + IN CONST VOID *Context + ); + +/** Iterate through the list of strings in the Context, + and check whether at least one string is matching the + "compatible" property of the node. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node to operate the check on. + @param [in] CompatInfo COMPATIBILITY_INFO containing the list of compatible + strings to compare with the "compatible" property + of the node. + + @retval TRUE At least one string matched, the node is compatible. + @retval FALSE Otherwise, or error. +**/ +BOOLEAN +EFIAPI +FdtNodeIsCompatible ( + IN CONST VOID *Fdt, + IN INT32 Node, + IN CONST VOID *CompatInfo + ); + +/** Check whether a node has a property. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node to operate the check on. + @param [in] PropertyName Name of the property to search. + This is a NULL terminated string. + + @retval True The node has the property. + @retval FALSE Otherwise, or error. +**/ +BOOLEAN +EFIAPI +FdtNodeHasProperty ( + IN CONST VOID *Fdt, + IN INT32 Node, + IN CONST VOID *PropertyName + ); + +/** Get the next node in a branch having a matching name. + + The Device tree is traversed in a depth-first search, starting from Node. + The input Node is skipped. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] NodeName The node name to search. + This is a NULL terminated string. + @param [in, out] Node At entry: Node offset to start the search. + This first node is skipped. + Write (-1) to search the whole tree. + At exit: If success, contains the offset of + the next node in the branch + having a matching name. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND No matching node found. +**/ +EFI_STATUS +EFIAPI +FdtGetNextNamedNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST CHAR8 *NodeName, + IN OUT INT32 *Node + ); + +/** Get the next node in a branch with at least one compatible property. + + The Device tree is traversed in a depth-first search, starting from Node. + The input Node is skipped. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] CompatNamesInfo Table of compatible strings to compare with + the compatible property of the node. + @param [in, out] Node At entry: Node offset to start the search. + This first node is skipped. + Write (-1) to search the whole tree. + At exit: If success, contains the offset of + the next node in the branch + being compatible. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND No matching node found. +**/ +EFI_STATUS +EFIAPI +FdtGetNextCompatNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST COMPATIBILITY_INFO *CompatNamesInfo, + IN OUT INT32 *Node + ); + +/** Get the next node in a branch having the PropName property. + + The Device tree is traversed in a depth-first search, starting from Node. + The input Node is skipped. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] PropName Name of the property to search. + This is a NULL terminated string. + @param [in, out] Node At entry: Node offset to start the search. + This first node is skipped. + Write (-1) to search the whole tree. + At exit: If success, contains the offset of + the next node in the branch + being compatible. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND No matching node found. +**/ +EFI_STATUS +EFIAPI +FdtGetNextPropNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST CHAR8 *PropName, + IN OUT INT32 *Node + ); + +/** Count the number of nodes in a branch with the input name. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] NodeName Node name to search. + This is a NULL terminated string. + @param [out] NodeCount If success, contains the count of nodes + fulfilling the condition. + Can be 0. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtCountNamedNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST CHAR8 *NodeName, + OUT UINT32 *NodeCount + ); + +/** Count the number of nodes in a branch with at least + one compatible property. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] CompatibleTable Table of compatible strings to + compare with the compatible property + of the node. + @param [out] NodeCount If success, contains the count of nodes + fulfilling the condition. + Can be 0. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtCountCompatNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST COMPATIBILITY_INFO *CompatNamesInfo, + OUT UINT32 *NodeCount + ); + +/** Count the number of nodes in a branch having the PropName property. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] FdtBranch Only search in the sub-nodes of this branch. + Write (-1) to search the whole tree. + @param [in] PropName Name of the property to search. + This is a NULL terminated string. + @param [out] NodeCount If success, contains the count of nodes + fulfilling the condition. + Can be 0. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtCountPropNodeInBranch ( + IN CONST VOID *Fdt, + IN INT32 FdtBranch, + IN CONST CHAR8 *PropName, + OUT UINT32 *NodeCount + ); + +/** Get the interrupt-controller node handling the interrupts of + the input node. + + To do this, recursively search a node with either the "interrupt-controller" + or the "interrupt-parent" property in the parents of Node. + + Devicetree Specification, Release v0.3, + 2.4.1 "Properties for Interrupt Generating Devices": + Because the hierarchy of the nodes in the interrupt tree + might not match the devicetree, the interrupt-parent + property is available to make the definition of an + interrupt parent explicit. The value is the phandle to the + interrupt parent. If this property is missing from a + device, its interrupt parent is assumed to be its devicetree + parent. + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node to start the search. + @param [out] IntcNode If success, contains the offset of the + interrupt-controller node. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_NOT_FOUND No interrupt-controller node found. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtGetIntcParentNode ( + IN CONST VOID *Fdt, + IN INT32 Node, + OUT INT32 *IntcNode + ); + +/** Get the "interrupt-cells" property value of the node. + + The "interrupts" property requires to know the number of cells used + to encode an interrupt. This information is stored in the + interrupt-controller of the input Node. + + @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). + @param [in] IntcNode Offset of an interrupt-controller node. + @param [out] IntCells If success, contains the "interrupt-cells" + property of the IntcNode. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +FdtGetInterruptCellsInfo ( + IN CONST VOID *Fdt, + IN INT32 IntcNode, + OUT INT32 *InterruptCells + ); + +/** Get the "#address-cells" and/or "#size-cells" property of the node. + + According to the Device Tree specification, s2.3.5 "#address-cells and + #size-cells": + "If missing, a client program should assume a default value of 2 for + #address-cells, and a value of 1 for #size-cells." + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node having to get the + "#address-cells" and "#size-cells" + properties from. + @param [out] AddressCells If success, number of address-cells. + If the property is not available, + default value is 2. + @param [out] SizeCells If success, number of size-cells. + If the property is not available, + default value is 1. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtGetAddressInfo ( + IN CONST VOID *Fdt, + IN INT32 Node, + OUT INT32 *AddressCells, OPTIONAL + OUT INT32 *SizeCells OPTIONAL + ); + +/** Get the "#address-cells" and/or "#size-cells" property of the parent node. + + According to the Device Tree specification, s2.3.5 "#address-cells and + #size-cells": + "If missing, a client program should assume a default value of 2 for + #address-cells, and a value of 1 for #size-cells." + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node having to get the + "#address-cells" and "#size-cells" + properties from its parent. + @param [out] AddressCells If success, number of address-cells. + If the property is not available, + default value is 2. + @param [out] SizeCells If success, number of size-cells. + If the property is not available, + default value is 1. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtGetParentAddressInfo ( + IN CONST VOID *Fdt, + IN INT32 Node, + OUT INT32 *AddressCells, OPTIONAL + OUT INT32 *SizeCells OPTIONAL + ); + +/** For relevant architectures, get the "#address-cells" and/or "#size-cells" + property of the node. + + According to the Device Tree specification, s2.3.5 "#address-cells and + #size-cells": + "If missing, a client program should assume a default value of 2 for + #address-cells, and a value of 1 for #size-cells." + + @param [in] Fdt Pointer to a Flattened Device Tree. + @param [in] Node Offset of the node having to get the + "#address-cells" and "#size-cells" + properties from. + @param [out] AddressCells If success, number of address-cells. + If the property is not available, + default value is 2. + @param [out] SizeCells If success, number of size-cells. + If the property is not available, + default value is 1. + + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +EFI_STATUS +EFIAPI +FdtGetIntcAddressCells ( + IN CONST VOID *Fdt, + IN INT32 Node, + OUT INT32 *AddressCells, OPTIONAL + OUT INT32 *SizeCells OPTIONAL + ); + +/** A helper function to support unaligned read access for UINT64 types. + + @param [in] Data Pointer to UINT64 data that may be unaligned. + + @retval The UINT64 value read from the input data pointer. +**/ +UINT64 +FdtReadUnaligned64 ( + CONST UINT64 *Data + ); + +/** A helper function to support unaligned read access for UINT32 types. + + @param [in] Data Pointer to a UINT32 data that may be unaligned. + + @retval The UINT32 value read from the input data pointer. +**/ +UINT32 +FdtReadUnaligned32 ( + CONST UINT32 *Data + ); + +#endif // FDT_UTILITY_H_ diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Pci/PciConfigSpaceParser.c b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Pci/PciConfigSpaceParser.c new file mode 100755 index 000000000000..6bf70c662449 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Pci/PciConfigSpaceParser.c @@ -0,0 +1,467 @@ +/** @file + PCI Configuration Space Parser. + + Copyright (c) 2021 - 2024, ARM Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/pci/host-generic-pci.yaml + - PCI Firmware Specification - Revision 3.0 + - Open Firmware Recommended Practice: Interrupt Mapping, Version 0.9 + - Devicetree Specification Release v0.3 + - linux kernel code +**/ + +#include "FdtUtility.h" +#include "FdtInfoParser.h" +#include "PciConfigSpaceParser.h" + +/** List of "compatible" property values for host PCIe bridges nodes. + + Any other "compatible" value is not supported by this module. +*/ +STATIC CONST COMPATIBILITY_STR PciCompatibleStr[] = { + { "pci-host-ecam-generic" } +}; + +/** COMPATIBILITY_INFO structure for the PCIe. +*/ +STATIC CONST COMPATIBILITY_INFO PciCompatibleInfo = { + ARRAY_SIZE (PciCompatibleStr), + PciCompatibleStr +}; + +/** PCI configuration space attributes. +*/ +STATIC CONST CHAR8 *PciConfigSpaceAttributesInfo[] = { + "PCI Cfg Space", + "PCI I/O Space", + "PCI 32bit Mem", + "PCI 64bit Mem" +}; + +/** Get the Segment group (also called: Domain Id) of a host-pci node. + + kernel/Documentation/devicetree/bindings/pci/pci.txt: + "It is required to either not set this property at all or set it for all + host bridges in the system" + + The function checks the "linux,pci-domain" property of the host-pci node. + Either all host-pci nodes must have this property, or none of them. If the + property is available, read it. Otherwise dynamically assign the Ids. + + @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). + @param [in] HostPciNode Offset of a host-pci node. + @param [out] SegGroup Segment group assigned to the host-pci controller. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +GetPciSegGroup ( + IN CONST VOID *Fdt, + IN INT32 HostPciNode, + OUT INT32 *SegGroup + ) +{ + CONST UINT8 *Data; + INT32 DataSize; + STATIC INT32 LocalSegGroup = 0; + + if ((Fdt == NULL) || + (SegGroup == NULL)) + { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Data = fdt_getprop (Fdt, HostPciNode, "linux,pci-domain", &DataSize); + if ((Data == NULL) || (DataSize < 0)) { + // Did not find property, assign the DomainIds ourselves. + if (LocalSegGroup < 0) { + // "linux,pci-domain" property was defined for another node. + ASSERT (FALSE); + return EFI_ABORTED; + } + + *SegGroup = LocalSegGroup++; + return EFI_SUCCESS; + } + + if ((DataSize > sizeof (UINT32)) || + (LocalSegGroup > 0)) + { + // Property on more than 1 cell or + // "linux,pci-domain" property was not defined for a node. + ASSERT (FALSE); + return EFI_ABORTED; + } + + // If one node has the "linux,pci-domain" property, then all the host-pci + // nodes must have it. + LocalSegGroup = -1; + + *SegGroup = FdtReadUnaligned32 ((UINT32 *)Data); + return EFI_SUCCESS; +} + +/** Parse the bus-range controlled by this host-pci node. + + @param [in] Fdt Pointer to a Flattened Device Tree (Fdt). + @param [in] HostPciNode Offset of a host-pci node. + @param [out] StartBusNumber Start bus number + @param [out] EndBusNumber End bus number + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +PopulateBusRange ( + IN CONST VOID *Fdt, + IN INT32 HostPciNode, + OUT UINT8 *StartBusNumber, + OUT UINT8 *EndBusNumber + ) +{ + CONST UINT8 *Data; + INT32 DataSize; + UINT32 StartBus; + UINT32 EndBus; + + if ((Fdt == NULL) || + (StartBusNumber == NULL) || + (EndBusNumber == NULL)) + { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Data = fdt_getprop (Fdt, HostPciNode, "bus-range", &DataSize); + if ((Data == NULL) || (DataSize < 0)) { + // No evidence this property is mandatory. Use default values. + StartBus = 0; + EndBus = 255; + } else if (DataSize == (2 * sizeof (UINT32))) { + // If available, the property is on two integers. + StartBus = FdtReadUnaligned32 ((UINT32 *)Data); + Data += sizeof (UINT32); + EndBus = FdtReadUnaligned32 ((UINT32 *)Data); + } else { + ASSERT (FALSE); + return EFI_ABORTED; + } + + *StartBusNumber = StartBus; + *EndBusNumber = EndBus; + + return EFI_SUCCESS; +} + +/** Parse the PCI address map. + + The PCI address map is available in the "ranges" device-tree property. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] HostPciNode Offset of a host-pci node. + @param [in] AddressCells # of cells used to encode an address on + the parent bus. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +ParseAddressMap ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 HostPciNode, + IN INT32 AddressCells + ) +{ + EFI_STATUS Status; + CONST UINT8 *Data; + INT32 DataSize; + UINT32 Index; + UINT32 Offset; + UINT32 AddressMapSize; + UINT32 Count; + UINT32 PciAddressAttr; + UINT8 SpaceCode; + UINT64 PciAddress; + UINT64 CpuAddress; + UINT64 AddressSize; + VOID *Fdt; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + // The mapping is done on AddressMapSize bytes. + AddressMapSize = (PCI_ADDRESS_CELLS + AddressCells + PCI_SIZE_CELLS) * + sizeof (UINT32); + + Data = fdt_getprop (Fdt, HostPciNode, "ranges", &DataSize); + if ((Data == NULL) || + (DataSize < 0) || + ((DataSize % AddressMapSize) != 0)) + { + // If error or not on AddressMapSize bytes. + ASSERT (FALSE); + return EFI_ABORTED; + } + + Count = DataSize / AddressMapSize; + Status = EFI_SUCCESS; + for (Index = 0; Index < Count; Index++) { + Offset = Index * AddressMapSize; + + // Pci address attributes + PciAddressAttr = FdtReadUnaligned32 ((UINT32 *)&Data[Offset]); + SpaceCode = READ_PCI_SS (PciAddressAttr); + Offset += sizeof (UINT32); + + // Pci address + PciAddress = FdtReadUnaligned64 ((UINT64 *)&Data[Offset]); + Offset += (PCI_ADDRESS_CELLS - 1) * sizeof (UINT32); + + // Cpu address + if (AddressCells == 2) { + CpuAddress = FdtReadUnaligned64 ((UINT64 *)&Data[Offset]); + } else { + CpuAddress = FdtReadUnaligned32 ((UINT32 *)&Data[Offset]); + } + + Offset += AddressCells * sizeof (UINT32); + + // Address size + AddressSize = FdtReadUnaligned64 ((UINT64 *)&Data[Offset]); + Offset += PCI_SIZE_CELLS * sizeof (UINT32); + + if (FdtParserHandle->HwAddressInfo != NULL) { + Status = FdtParserHandle->HwAddressInfo ( + FdtParserHandle->Context, + PciConfigSpaceAttributesInfo[SpaceCode], + CpuAddress, + AddressSize + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + break; + } + } + } // for + + return Status; +} + +/** Parse a Host-pci node. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] HostPciNode Offset of a host-pci node. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. +**/ +STATIC +EFI_STATUS +EFIAPI +PciNodeParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 HostPciNode + ) +{ + EFI_STATUS Status; + INT32 AddressCells; + INT32 SizeCells; + CONST UINT8 *Data; + CONST UINT8 *Range; + INT32 DataSize; + INT32 SegGroup; + UINT8 StartBusNumber; + UINT8 EndBusNumber; + UINT64 ConfigSpaceBaseAddress; + UINT64 ConfigSpaceBaseAddressRange; + VOID *Fdt; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + // Segment Group / DomainId + Status = GetPciSegGroup (Fdt, HostPciNode, &SegGroup); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + // Bus range + Status = PopulateBusRange ( + Fdt, + HostPciNode, + &StartBusNumber, + &EndBusNumber + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + Status = FdtGetParentAddressInfo ( + Fdt, + HostPciNode, + &AddressCells, + &SizeCells + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + // Only support 32/64 bits addresses. + if ((AddressCells < 1) || + (AddressCells > 2) || + (SizeCells < 1) || + (SizeCells > 2)) + { + ASSERT (FALSE); + return EFI_ABORTED; + } + + Data = fdt_getprop (Fdt, HostPciNode, "reg", &DataSize); + if ((Data == NULL) || + (DataSize != ((AddressCells + SizeCells) * sizeof (UINT32)))) + { + // If error or wrong size. + ASSERT (FALSE); + return EFI_ABORTED; + } + + Range = Data + (sizeof (UINT32) * AddressCells); + + // Base address + if (AddressCells == 2) { + ConfigSpaceBaseAddress = FdtReadUnaligned64 ((UINT64 *)Data); + ConfigSpaceBaseAddressRange = FdtReadUnaligned64 ((UINT64 *)Range); + } else { + ConfigSpaceBaseAddress = FdtReadUnaligned32 ((UINT32 *)Data); + ConfigSpaceBaseAddressRange = FdtReadUnaligned32 ((UINT32 *)Range); + } + + if (FdtParserHandle->HwAddressInfo != NULL) { + Status = FdtParserHandle->HwAddressInfo ( + FdtParserHandle->Context, + "PCI Config", + ConfigSpaceBaseAddress, + ConfigSpaceBaseAddressRange + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + } + + // Address map + Status = ParseAddressMap ( + FdtParserHandle, + HostPciNode, + AddressCells + ); + ASSERT_EFI_ERROR (Status); + + return Status; +} + +/** PCI information parser function. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +PciConfigInfoParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch + ) +{ + EFI_STATUS Status; + UINT32 Index; + INT32 PciNode; + UINT32 PciNodeCount; + VOID *Fdt; + + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + Fdt = FdtParserHandle->Fdt; + + // Only search host-pci devices. + // PCI Firmware Specification Revision 3.0, s4.1.2. "MCFG Table Description": + // "This table directly refers to PCI Segment Groups defined in the system + // via the _SEG object in the ACPI name space for the applicable host bridge + // device." + Status = FdtCountCompatNodeInBranch ( + Fdt, + FdtBranch, + &PciCompatibleInfo, + &PciNodeCount + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + return Status; + } + + if (PciNodeCount == 0) { + return EFI_NOT_FOUND; + } + + // Parse each host-pci node in the branch. + PciNode = FdtBranch; + for (Index = 0; Index < PciNodeCount; Index++) { + Status = FdtGetNextCompatNodeInBranch ( + Fdt, + FdtBranch, + &PciCompatibleInfo, + &PciNode + ); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + if (Status == EFI_NOT_FOUND) { + // Should have found the node. + Status = EFI_ABORTED; + } + + return Status; + } + + Status = PciNodeParser (FdtParserHandle, PciNode); + if (EFI_ERROR (Status)) { + ASSERT (FALSE); + break; + } + } // for + + return Status; +} diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Pci/PciConfigSpaceParser.h b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Pci/PciConfigSpaceParser.h new file mode 100755 index 000000000000..656afc2c207c --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Pci/PciConfigSpaceParser.h @@ -0,0 +1,78 @@ +/** @file + PCI Configuration Space Parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/pci/host-generic-pci.yaml + - PCI Firmware Specification - Revision 3.0 + - Open Firmware Recommended Practice: Interrupt Mapping, Version 0.9 + - Devicetree Specification Release v0.3 + - linux kernel code +**/ + +#ifndef PCI_CONFIG_SPACE_PARSER_H_ +#define PCI_CONFIG_SPACE_PARSER_H_ + +/** Read LEN bits at OFF offsets bits of the ADDR. + + @param [in] ADDR Address to read the bits from. + @param [in] OFF Offset of the bits to read. + @param [in] LEN Number of bits to read. + + @return The bits read. +**/ +#define READ_BITS(ADDR, OFF, LEN) (((ADDR) >> (OFF)) & ((1<<(LEN))-1)) + +/* Pci address attributes. +*/ +/// 0 if relocatable. +#define READ_PCI_N(ADDR) READ_BITS((ADDR), 31, 1) +/// 1 if prefetchable. +#define READ_PCI_P(ADDR) READ_BITS((ADDR), 30, 1) +/// 1 if aliased. +#define READ_PCI_T(ADDR) READ_BITS((ADDR), 29, 1) + +/** Space code. + + 00: Configuration Space + 01: I/O Space + 10: 32-bit-address Memory Space + 11: 64-bit-address Memory Space +*/ +#define READ_PCI_SS(ADDR) READ_BITS((ADDR), 24, 2) +/// Bus number. +#define READ_PCI_BBBBBBBB(ADDR) READ_BITS((ADDR), 16, 8) +/// Device number. +#define READ_PCI_DDDDD(ADDR) READ_BITS((ADDR), 11, 5) + +/** Number of device-tree cells used for PCI nodes properties. + + Values are well defined, except the "#interrupt-cells" which + is assumed to be 1. +*/ +#define PCI_ADDRESS_CELLS 3U +#define PCI_SIZE_CELLS 2U +#define PCI_INTERRUPTS_CELLS 1U + +/** PCI information parser function. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +PciConfigInfoParser ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch + ); + +#endif // PCI_CONFIG_SPACE_PARSER_H_ diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Rtc/RtcParser.c b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Rtc/RtcParser.c new file mode 100644 index 000000000000..4df322165fae --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Rtc/RtcParser.c @@ -0,0 +1,62 @@ +/** @file + RTC Parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - Documentation/devicetree/bindings/rtc/arm,pl031.yaml + - Documentation/devicetree/bindings/rtc/rtc-cmos.txt +**/ + +#include "FdtUtility.h" +#include "FdtInfoParser.h" +#include "Common/DeviceParser.h" + +/** List of "compatible" property values for RTC nodes. + + Any other "compatible" value is not supported by this module. +*/ +STATIC CONST COMPATIBILITY_STR RtcCompatibleStr[] = { + { "motorola,mc146818" }, + { "arm,pl031" } +}; + +/** COMPATIBILITY_INFO structure for the RtcCompatible. +*/ +CONST COMPATIBILITY_INFO RtcCompatibleInfo = { + ARRAY_SIZE (RtcCompatibleStr), + RtcCompatibleStr +}; + +/** RTC dispatcher. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +RtcDispatcher ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch + ) +{ + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + return DeviceDispatcher ( + FdtParserHandle, + FdtBranch, + &RtcCompatibleInfo, + "RTC" + ); +} diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Rtc/RtcParser.h b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Rtc/RtcParser.h new file mode 100644 index 000000000000..8b65c8663296 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Rtc/RtcParser.h @@ -0,0 +1,34 @@ +/** @file + RTC Parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - Documentation/devicetree/bindings/rtc/arm,pl031.yaml + - Documentation/devicetree/bindings/rtc/rtc-cmos.txt +**/ + +#ifndef RTC_PARSER_H_ +#define RTC_PARSER_H_ + +/** Rtc dispatcher. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +RtcDispatcher ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch + ); + +#endif // RTC_PARSER_H_ diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Serial/SerialPortParser.c b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Serial/SerialPortParser.c new file mode 100755 index 000000000000..550c6d6aa2f0 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Serial/SerialPortParser.c @@ -0,0 +1,65 @@ +/** @file + Serial Port Parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/serial/serial.yaml + - linux/Documentation/devicetree/bindings/serial/8250.txt + - linux/Documentation/devicetree/bindings/serial/arm_sbsa_uart.txt + - linux/Documentation/devicetree/bindings/serial/pl011.yaml +**/ + +#include "FdtUtility.h" +#include "FdtInfoParser.h" +#include "Common/DeviceParser.h" + +/** List of "compatible" property values for serial port nodes. + + Any other "compatible" value is not supported by this module. +*/ +STATIC CONST COMPATIBILITY_STR SerialCompatibleStr[] = { + { "ns16550a" }, + { "arm,sbsa-uart" }, + { "arm,pl011" } +}; + +/** COMPATIBILITY_INFO structure for the SerialCompatible. +*/ +CONST COMPATIBILITY_INFO SerialCompatibleInfo = { + ARRAY_SIZE (SerialCompatibleStr), + SerialCompatibleStr +}; + +/** SerialPort dispatcher. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +SerialPortDispatcher ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch + ) +{ + if (FdtParserHandle == NULL) { + ASSERT (FALSE); + return EFI_INVALID_PARAMETER; + } + + return DeviceDispatcher ( + FdtParserHandle, + FdtBranch, + &SerialCompatibleInfo, + "Serial Port" + ); +} diff --git a/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Serial/SerialPortParser.h b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Serial/SerialPortParser.h new file mode 100755 index 000000000000..bed8d499e619 --- /dev/null +++ b/ArmVirtPkg/Library/ArmPlatformDeviceInfoLib/HwInfoParser/Serial/SerialPortParser.h @@ -0,0 +1,34 @@ +/** @file + Serial Port Parser. + + Copyright (c) 2021 - 2024, Arm Limited. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Reference(s): + - linux/Documentation/devicetree/bindings/serial/serial.yaml + - linux/Documentation/devicetree/bindings/serial/8250.txt +**/ + +#ifndef SERIAL_PORT_PARSER_H_ +#define SERIAL_PORT_PARSER_H_ + +/** SerialPort dispatcher. + + @param [in] FdtParserHandle A handle to the parser instance. + @param [in] FdtBranch When searching for DT node name, restrict + the search to this Device Tree branch. + + @retval EFI_SUCCESS The function completed successfully. + @retval EFI_ABORTED An error occurred. + @retval EFI_INVALID_PARAMETER Invalid parameter. + @retval EFI_NOT_FOUND Not found. + @retval EFI_UNSUPPORTED Unsupported. +**/ +EFI_STATUS +EFIAPI +SerialPortDispatcher ( + IN CONST FDT_HW_INFO_PARSER_HANDLE FdtParserHandle, + IN INT32 FdtBranch + ); + +#endif // SERIAL_PORT_PARSER_H_ diff --git a/ArmVirtPkg/Library/CloudHvVirtMemInfoLib/CloudHvVirtMemInfoLib.c b/ArmVirtPkg/Library/CloudHvVirtMemInfoLib/CloudHvVirtMemInfoLib.c index 98cc13870599..0f444b693fa3 100644 --- a/ArmVirtPkg/Library/CloudHvVirtMemInfoLib/CloudHvVirtMemInfoLib.c +++ b/ArmVirtPkg/Library/CloudHvVirtMemInfoLib/CloudHvVirtMemInfoLib.c @@ -1,6 +1,6 @@ /** @file - Copyright (c) 2022, Arm Limited. All rights reserved. + Copyright (c) 2022 - 2023, Arm Limited. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent @@ -241,3 +241,23 @@ ArmVirtGetMemoryMap ( *VirtualMemoryMap = VirtualMemoryTable; } + +/** + Configure the MMIO regions as shared with the VMM. + + Set the protection attribute for the MMIO regions as Unprotected IPA. + + @param[in] IpaWidth IPA width of the Realm. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The execution context is not in a Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaConfigureMmio ( + IN UINT64 IpaWidth + ) +{ + return RETURN_UNSUPPORTED; +} diff --git a/ArmVirtPkg/Library/KvmtoolVirtMemInfoLib/KvmtoolVirtMemInfoLib.c b/ArmVirtPkg/Library/KvmtoolVirtMemInfoLib/KvmtoolVirtMemInfoLib.c index 79412897f225..e3e849d0aaa3 100644 --- a/ArmVirtPkg/Library/KvmtoolVirtMemInfoLib/KvmtoolVirtMemInfoLib.c +++ b/ArmVirtPkg/Library/KvmtoolVirtMemInfoLib/KvmtoolVirtMemInfoLib.c @@ -1,21 +1,30 @@ /** @file Kvmtool virtual memory map library. - Copyright (c) 2018 - 2020, ARM Limited. All rights reserved. + Copyright (c) 2018 - 2024, Arm Limited. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include +#include +#include #include +#include +#include #include #include #include #include // Number of Virtual Memory Map Descriptors -#define MAX_VIRTUAL_MEMORY_MAP_DESCRIPTORS 5 +#define MAX_VIRTUAL_MEMORY_MAP_DESCRIPTORS (MAX_PLAT_DEVICE_COUNT + 4) + +// A platform device information structure used to +// collate the MMIO base address and range for the +// Platform devices. +STATIC PLATFROM_DEVICE_INFO mPlatInfo; /** Return the Virtual Memory Map of your platform @@ -38,11 +47,28 @@ ArmVirtGetMemoryMap ( ARM_MEMORY_REGION_DESCRIPTOR *VirtualMemoryTable; UINTN Idx; EFI_PHYSICAL_ADDRESS TopOfAddressSpace; + VOID *DtbBase; + UINTN Devices; + EFI_STATUS Status; ASSERT (VirtualMemoryMap != NULL); TopOfAddressSpace = LShiftU64 (1ULL, ArmGetPhysicalAddressBits ()); + // Parse the FDT and populate the PLATFROM_DEVICE_INFO structure with + // the MMIO base address and range for the devices present in the FDT. + DtbBase = (VOID *)(UINTN)PcdGet64 (PcdDeviceTreeInitialBaseAddress); + + ZeroMem (&mPlatInfo, sizeof (PLATFROM_DEVICE_INFO)); + Status = ArmParsePlatformDeviceFdt (DtbBase, &mPlatInfo); + if (EFI_ERROR (Status)) { + return; + } + + if (mPlatInfo.MaxDevices > MAX_VIRTUAL_MEMORY_MAP_DESCRIPTORS) { + return; + } + VirtualMemoryTable = (ARM_MEMORY_REGION_DESCRIPTOR *) AllocatePages ( EFI_SIZE_TO_PAGES ( @@ -66,26 +92,27 @@ ArmVirtGetMemoryMap ( VirtualMemoryTable[Idx].Length = PcdGet64 (PcdSystemMemorySize); VirtualMemoryTable[Idx].Attributes = ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK; - // Peripheral space before DRAM - VirtualMemoryTable[++Idx].PhysicalBase = 0x0; - VirtualMemoryTable[Idx].VirtualBase = 0x0; - VirtualMemoryTable[Idx].Length = PcdGet64 (PcdSystemMemoryBase); - VirtualMemoryTable[Idx].Attributes = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE; - - // Peripheral space after DRAM - VirtualMemoryTable[++Idx].PhysicalBase = PcdGet64 (PcdSystemMemoryBase) + - PcdGet64 (PcdSystemMemorySize); - VirtualMemoryTable[Idx].VirtualBase = VirtualMemoryTable[Idx].PhysicalBase; - VirtualMemoryTable[Idx].Length = TopOfAddressSpace - - VirtualMemoryTable[Idx].PhysicalBase; - VirtualMemoryTable[Idx].Attributes = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE; - // Map the FV region as normal executable memory VirtualMemoryTable[++Idx].PhysicalBase = PcdGet64 (PcdFvBaseAddress); VirtualMemoryTable[Idx].VirtualBase = VirtualMemoryTable[Idx].PhysicalBase; VirtualMemoryTable[Idx].Length = FixedPcdGet32 (PcdFvSize); VirtualMemoryTable[Idx].Attributes = ARM_MEMORY_REGION_ATTRIBUTE_WRITE_BACK; + // Map peripheral devices + for (Devices = 0; Devices < mPlatInfo.MaxDevices; Devices++) { + VirtualMemoryTable[++Idx].PhysicalBase = mPlatInfo.Dev[Devices].BaseAddress; + VirtualMemoryTable[Idx].VirtualBase = mPlatInfo.Dev[Devices].BaseAddress; + // Some devices may only use few registers and would report the length + // accordingly. Although this is correct, the device is expected to reserve + // at least a page for MMIO page mapping. Therefore, align the address + // range to the nearest page size. + VirtualMemoryTable[Idx].Length = ALIGN_VALUE ( + mPlatInfo.Dev[Devices].Length, + EFI_PAGE_SIZE + ); + VirtualMemoryTable[Idx].Attributes = ARM_MEMORY_REGION_ATTRIBUTE_DEVICE; + } + // End of Table VirtualMemoryTable[++Idx].PhysicalBase = 0; VirtualMemoryTable[Idx].VirtualBase = 0; @@ -96,3 +123,63 @@ ArmVirtGetMemoryMap ( *VirtualMemoryMap = VirtualMemoryTable; } + +/** Configure the MMIO regions as shared with the VMM. + + Set the protection attribute for the MMIO regions that do not belong to + the Realm Device as Unprotected IPA. + + @param[in] IpaWidth IPA width of the Realm. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The execution context is not in a Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaConfigureMmio ( + IN UINT64 IpaWidth + ) +{ + RETURN_STATUS Status; + UINTN Devices; + UINT64 BaseAddress; + UINT64 Length; + BOOLEAN IsProtectedMmio; + + if (!IsRealm ()) { + return RETURN_UNSUPPORTED; + } + + for (Devices = 0; Devices < mPlatInfo.MaxDevices; Devices++) { + BaseAddress = mPlatInfo.Dev[Devices].BaseAddress; + Length = ALIGN_VALUE (mPlatInfo.Dev[Devices].Length, EFI_PAGE_SIZE); + + Status = ArmCcaMemRangeIsProtectedMmio ( + BaseAddress, + Length, + &IsProtectedMmio + ); + if (RETURN_ERROR (Status)) { + break; + } + + if (IsProtectedMmio) { + // The Realm Device memory is not shared with the host. So skip. + continue; + } + + // Set the protection attribute for the Peripheral memory as host shared. + Status = ArmCcaSetMemoryProtectAttribute ( + BaseAddress, + Length, + IpaWidth, + TRUE + ); + if (RETURN_ERROR (Status)) { + break; + } + } // for + + return Status; +} diff --git a/ArmVirtPkg/Library/KvmtoolVirtMemInfoLib/KvmtoolVirtMemInfoLib.inf b/ArmVirtPkg/Library/KvmtoolVirtMemInfoLib/KvmtoolVirtMemInfoLib.inf index a354e734ab1b..0e256a01c64e 100644 --- a/ArmVirtPkg/Library/KvmtoolVirtMemInfoLib/KvmtoolVirtMemInfoLib.inf +++ b/ArmVirtPkg/Library/KvmtoolVirtMemInfoLib/KvmtoolVirtMemInfoLib.inf @@ -1,7 +1,7 @@ ## @file # Kvmtool virtual memory map library. # -# Copyright (c) 2018, ARM Limited. All rights reserved. +# Copyright (c) 2018 - 2023, Arm Limited. All rights reserved. # # SPDX-License-Identifier: BSD-2-Clause-Patent # @@ -24,9 +24,12 @@ EmbeddedPkg/EmbeddedPkg.dec MdeModulePkg/MdeModulePkg.dec MdePkg/MdePkg.dec + OvmfPkg/OvmfPkg.dec [LibraryClasses] + ArmCcaLib ArmLib + ArmPlatformDeviceInfoLib BaseLib BaseMemoryLib DebugLib @@ -37,6 +40,8 @@ gArmTokenSpaceGuid.PcdFvBaseAddress gArmTokenSpaceGuid.PcdSystemMemoryBase gArmTokenSpaceGuid.PcdSystemMemorySize + gUefiOvmfPkgTokenSpaceGuid.PcdDeviceTreeInitialBaseAddress [FixedPcd] gArmTokenSpaceGuid.PcdFvSize + gArmVirtTokenSpaceGuid.PcdMaxPlatDeviceCount diff --git a/ArmVirtPkg/Library/QemuVirtMemInfoLib/QemuVirtMemInfoLib.c b/ArmVirtPkg/Library/QemuVirtMemInfoLib/QemuVirtMemInfoLib.c index d435d725983d..13f034232fb2 100644 --- a/ArmVirtPkg/Library/QemuVirtMemInfoLib/QemuVirtMemInfoLib.c +++ b/ArmVirtPkg/Library/QemuVirtMemInfoLib/QemuVirtMemInfoLib.c @@ -1,6 +1,7 @@ /** @file Copyright (c) 2014-2017, Linaro Limited. All rights reserved. + Copyright (c) 2023, Arm Limited. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent @@ -122,3 +123,23 @@ ArmVirtGetMemoryMap ( *VirtualMemoryMap = VirtualMemoryTable; } + +/** + Configure the MMIO regions as shared with the VMM. + + Set the protection attribute for the MMIO regions as Unprotected IPA. + + @param[in] IpaWidth IPA width of the Realm. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The execution context is not in a Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaConfigureMmio ( + IN UINT64 IpaWidth + ) +{ + return RETURN_UNSUPPORTED; +} diff --git a/ArmVirtPkg/Library/XenVirtMemInfoLib/XenVirtMemInfoLib.c b/ArmVirtPkg/Library/XenVirtMemInfoLib/XenVirtMemInfoLib.c index ac0c75aecfe5..954382c05504 100644 --- a/ArmVirtPkg/Library/XenVirtMemInfoLib/XenVirtMemInfoLib.c +++ b/ArmVirtPkg/Library/XenVirtMemInfoLib/XenVirtMemInfoLib.c @@ -1,6 +1,7 @@ /** @file Copyright (c) 2014-2017, Linaro Limited. All rights reserved. + Copyright (c) 2023, Arm Limited. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent @@ -55,3 +56,23 @@ ArmVirtGetMemoryMap ( *VirtualMemoryMap = mVirtualMemoryTable; } + +/** + Configure the MMIO regions as shared with the VMM. + + Set the protection attribute for the MMIO regions as Unprotected IPA. + + @param[in] IpaWidth IPA width of the Realm. + + @retval RETURN_SUCCESS Success. + @retval RETURN_INVALID_PARAMETER A parameter is invalid. + @retval RETURN_UNSUPPORTED The execution context is not in a Realm. +**/ +RETURN_STATUS +EFIAPI +ArmCcaConfigureMmio ( + IN UINT64 IpaWidth + ) +{ + return RETURN_UNSUPPORTED; +} diff --git a/ArmVirtPkg/PrePi/AArch64/ModuleEntryPoint.S b/ArmVirtPkg/PrePi/AArch64/ModuleEntryPoint.S index 06a0020f250c..32a622d2f721 100644 --- a/ArmVirtPkg/PrePi/AArch64/ModuleEntryPoint.S +++ b/ArmVirtPkg/PrePi/AArch64/ModuleEntryPoint.S @@ -1,5 +1,5 @@ // -// Copyright (c) 2011-2013, ARM Limited. All rights reserved. +// Copyright (c) 2011-2023, Arm Limited. All rights reserved. // Copyright (c) 2015-2016, Linaro Limited. All rights reserved. // // SPDX-License-Identifier: BSD-2-Clause-Patent @@ -28,6 +28,10 @@ ASM_FUNC(_ModuleEntryPoint) bl ASM_PFX(DiscoverDramFromDt) + // Check if we are in a Realm and configure + // the System Memory as Protected RAM. + bl ASM_PFX(ArmCcaConfigureSystemMemory) + // Get ID of this CPU in Multicore system bl ASM_PFX(ArmReadMpidr) // Keep a copy of the MpId register value diff --git a/ArmVirtPkg/PrePi/ArmVirtPrePiUniCoreRelocatable.inf b/ArmVirtPkg/PrePi/ArmVirtPrePiUniCoreRelocatable.inf index ed8b89e2b68c..d79cc2be5797 100755 --- a/ArmVirtPkg/PrePi/ArmVirtPrePiUniCoreRelocatable.inf +++ b/ArmVirtPkg/PrePi/ArmVirtPrePiUniCoreRelocatable.inf @@ -1,6 +1,6 @@ #/** @file # -# Copyright (c) 2011-2015, ARM Ltd. All rights reserved.
+# Copyright (c) 2011-2023, Arm Limited. All rights reserved.
# Copyright (c) 2015, Linaro Ltd. All rights reserved.
# # SPDX-License-Identifier: BSD-2-Clause-Patent @@ -38,6 +38,7 @@ OvmfPkg/OvmfPkg.dec [LibraryClasses] + ArmCcaInitPeiLib BaseLib DebugLib FdtLib diff --git a/ArmVirtPkg/PrePi/PrePi.c b/ArmVirtPkg/PrePi/PrePi.c index 9dbb5af942e7..002f42a2b962 100755 --- a/ArmVirtPkg/PrePi/PrePi.c +++ b/ArmVirtPkg/PrePi/PrePi.c @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,7 @@ PrePiMain ( CHAR8 Buffer[100]; UINTN CharCount; UINTN StacksSize; + RETURN_STATUS RetStatus; // Initialize the architecture specific bits ArchInitialize (); @@ -61,6 +63,12 @@ PrePiMain ( Status = MemoryPeim (UefiMemoryBase, FixedPcdGet32 (PcdSystemMemoryUefiRegionSize)); ASSERT_EFI_ERROR (Status); + // Perform the Arm CCA specific initialisations. + RetStatus = ArmCcaInitialize (); + if (RETURN_ERROR (RetStatus)) { + CpuDeadLoop (); + } + // Initialize the Serial Port SerialPortInitialize (); CharCount = AsciiSPrint ( diff --git a/ArmVirtPkg/RealmApertureManagementProtocolDxe/RealmApertureManagementProtocolDxe.c b/ArmVirtPkg/RealmApertureManagementProtocolDxe/RealmApertureManagementProtocolDxe.c new file mode 100644 index 000000000000..9212f0f6d252 --- /dev/null +++ b/ArmVirtPkg/RealmApertureManagementProtocolDxe/RealmApertureManagementProtocolDxe.c @@ -0,0 +1,660 @@ +/** @file + Realm Aperture Management Protocol Dxe + + Copyright (c) 2022 - 2023, Arm Limited. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent + + @par Glossary: + - IPA - Intermediate Physical Address + - RAMP - Realm Aperture Management Protocol + - RIPAS - Realm IPA state + - RSI - Realm Service Interface +**/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + A macro defining the signature for the aperture information structure. +*/ +#define APERTURE_INFO_SIG SIGNATURE_64 ('A', 'P', 'E', 'R', 'T', 'U', 'R', 'E') + +/** + A structure describing the aperture. +*/ +typedef struct { + /// Signature for identifying this structure. + UINT64 Signature; + + /// The linked list entry. + LIST_ENTRY Link; + + /// The base address for the start of the aperture. + EFI_PHYSICAL_ADDRESS BaseAddress; + + /// The number of pages covered by the aperture. + UINTN Pages; + + /// The bit mask of attributes for the memory region. The + /// bit mask of available attributes is defined in GetMemoryMap(). + UINT64 MemoryAttributes; + + /// The RIPAS for the aperture. + RIPAS Ripas; +} APERTURE_INFO; + +/** + List of the APERTURE_INFO structures that have been set up by OpenAperture() + and not yet torn down by CloseAperture(). The list represents the full set + of open apertures currently in effect. +*/ +STATIC +LIST_ENTRY mApertureInfos = INITIALIZE_LIST_HEAD_VARIABLE (mApertureInfos); + +/** + A local variable to store the IPA width of the Realm. The IPA width + of the Realm is required to configure the protection attribute of + memory regions. +*/ +STATIC UINT64 mIpaWidth; + +/** Checks if an open aperture is overlapping the memory region. + + @param [in] MemStart Pointer to the page start address. + @param [in] Pages Number of pages to share. + + @retval TRUE If memory region overlaps an open aperture. + @retval FALSE Memory region does not overlap any open apertures. +**/ +STATIC +BOOLEAN +EFIAPI +IsApertureOverlapping ( + IN CONST EFI_PHYSICAL_ADDRESS MemStart, + IN CONST UINTN Pages + ) +{ + LIST_ENTRY *Node; + LIST_ENTRY *NextNode; + APERTURE_INFO *ApertureInfo; + EFI_PHYSICAL_ADDRESS MemEnd; + EFI_PHYSICAL_ADDRESS ApertureStart; + EFI_PHYSICAL_ADDRESS ApertureEnd; + + MemEnd = MemStart + (EFI_PAGE_SIZE * Pages) - 1; + + // All drivers that had opened the apertures have halted their respective + // controllers by now; close all the apertures. + for ( + Node = GetFirstNode (&mApertureInfos); + Node != &mApertureInfos; + Node = NextNode + ) + { + NextNode = GetNextNode (&mApertureInfos, Node); + ApertureInfo = CR (Node, APERTURE_INFO, Link, APERTURE_INFO_SIG); + ApertureStart = ApertureInfo->BaseAddress; + ApertureEnd = ApertureStart + (EFI_PAGE_SIZE * ApertureInfo->Pages) - 1; + + if (((ApertureStart >= MemStart) && (ApertureStart <= MemEnd)) || + ((ApertureEnd >= MemStart) && (ApertureEnd <= MemEnd)) || + ((MemStart >= ApertureStart) && (MemStart <= ApertureEnd)) || + ((MemEnd >= ApertureStart) && (MemEnd <= ApertureEnd))) + { + return TRUE; + } + } + + return FALSE; +} + +/** Enables sharing of the memory buffers with the host. + + @param [in] Memory Pointer to the page start address. + @param [in] Pages Number of pages to share. + @param [out] ApertureReference Reference to the opened aperture. + + @retval EFI_SUCCESS Success. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + @retval EFI_ACCESS_DENIED Aperture already open over memory region. +**/ +STATIC +EFI_STATUS +EFIAPI +RampOpenAperture ( + IN CONST EFI_PHYSICAL_ADDRESS Memory, + IN CONST UINTN Pages, + OUT EFI_HANDLE *CONST ApertureReference + ) +{ + EFI_STATUS Status; + EFI_STATUS Status1; + APERTURE_INFO *ApertInfo; + EFI_GCD_MEMORY_SPACE_DESCRIPTOR GcdDescriptor; + EFI_PHYSICAL_ADDRESS MemRangeAddr; + UINTN Index; + + if ((Memory == 0) || + (Pages == 0) || + (ApertureReference == NULL) || + ((Memory & (EFI_PAGE_SIZE - 1)) != 0)) + { + return EFI_INVALID_PARAMETER; + } + + // The pages size must be aligned to the Realm Granule size. + STATIC_ASSERT ((EFI_PAGE_SIZE & (REALM_GRANULE_SIZE - 1)) == 0); + + // Checks if we already have an open aperture that overlaps the + // memory region. If so return the request as invalid. + if (IsApertureOverlapping (Memory, Pages)) { + return EFI_INVALID_PARAMETER; + } + + MemRangeAddr = Memory; + for (Index = 0; Index < Pages; Index++) { + Status = gDS->GetMemorySpaceDescriptor (MemRangeAddr, &GcdDescriptor); + if (EFI_ERROR (Status)) { + return Status; + } + + DEBUG (( + DEBUG_INFO, + "%a: Memory = 0x%lx, MemType = %a\n", + __func__, + MemRangeAddr, + ((GcdDescriptor.Attributes & EFI_MEMORY_RUNTIME) == EFI_MEMORY_RUNTIME) ? + "Runtime Services Memory" : "Boot Services Memory" + )); + + // We currently do not have a usecase where we would want to open apertures + // for runtime services memory + if ((GcdDescriptor.Attributes & EFI_MEMORY_RUNTIME) == EFI_MEMORY_RUNTIME) { + return EFI_UNSUPPORTED; + } + + MemRangeAddr += EFI_PAGE_SIZE; + } // for + + Status = ArmCcaSetMemoryProtectAttribute ( + Memory, + EFI_PAGES_TO_SIZE (Pages), + mIpaWidth, + TRUE + ); + if (RETURN_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: Failed to update page tables for Protected EMPTY page mapping, " + "Address = %p, Pages = 0x%lx, Status = %r\n", + Memory, + Pages, + Status + )); + return Status; + } + + // Allocate a APERTURE_INFO structure to remember the apertures opened. + ApertInfo = AllocateZeroPool (sizeof (APERTURE_INFO)); + if (ApertInfo == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto error_handler1; + } + + InitializeListHead (&ApertInfo->Link); + ApertInfo->Signature = APERTURE_INFO_SIG; + ApertInfo->BaseAddress = Memory; + ApertInfo->Pages = Pages; + ApertInfo->MemoryAttributes = GcdDescriptor.Attributes; + ApertInfo->Ripas = RipasEmpty; + + DEBUG (( + DEBUG_INFO, + "%a: ApertRef = 0x%p, Memory = 0x%lx, Pages = 0x%x, " + "MemoryAttributes = 0x%x, Ripas = 0x%x\n", + __func__, + ApertInfo, + ApertInfo->BaseAddress, + ApertInfo->Pages, + ApertInfo->MemoryAttributes, + ApertInfo->Ripas + )); + + // Set the Realm IPA state to Empty to open the Aperture + Status = RsiSetIpaState ( + (UINT64 *)Memory, + (Pages * EFI_PAGE_SIZE), + RipasEmpty, + RIPAS_CHANGE_FLAGS_RSI_NO_CHANGE_DESTROYED + ); + if (RETURN_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: RSI Set IPA State failed, Address = %p, Pages = 0x%lx, " + "Status = %r\n", + Memory, + Pages, + Status + )); + goto error_handler; + } + + DEBUG (( + DEBUG_INFO, + "SUCCESS: RSI Set IPA State complete, Address = %p, Pages = 0x%lx, " + "Status = %r\n", + Memory, + Pages, + Status + )); + + InsertHeadList (&mApertureInfos, &ApertInfo->Link); + *ApertureReference = (EFI_HANDLE *)&ApertInfo->Link; + + return Status; + +error_handler: + + FreePool (ApertInfo); + +error_handler1: + Status1 = ArmCcaSetMemoryProtectAttribute ( + Memory, + EFI_PAGES_TO_SIZE (Pages), + mIpaWidth, + FALSE + ); + if (RETURN_ERROR (Status1)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: Failed to update page tables to Protected page mapping, " + "Address = %p, Pages = 0x%lx, Status = %r\n", + Memory, + Pages, + Status1 + )); + } + + *ApertureReference = NULL; + // return the first error code + return Status; +} + +/** Disables the sharing of the buffers. + + @param [in] ApertureReference Reference to the aperture for closing. + + @retval EFI_SUCCESS The operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_NOT_FOUND The required buffer information is not found. +**/ +STATIC +EFI_STATUS +EFIAPI +RampCloseAperture ( + IN CONST EFI_HANDLE ApertureReference + ) +{ + EFI_STATUS Status; + APERTURE_INFO *ApertInfo; + + if (ApertureReference == NULL) { + return EFI_INVALID_PARAMETER; + } + + ApertInfo = NULL; + ApertInfo = CR (ApertureReference, APERTURE_INFO, Link, APERTURE_INFO_SIG); + if (ApertInfo == NULL) { + return EFI_NOT_FOUND; + } + + DEBUG (( + DEBUG_INFO, + "%a: ApertRef = 0x%p, Memory = 0x%lx, Pages = 0x%x, " + "MemoryAttributes = 0x%x, Ripas = 0x%x\n", + __func__, + ApertInfo, + ApertInfo->BaseAddress, + ApertInfo->Pages, + ApertInfo->MemoryAttributes, + ApertInfo->Ripas + )); + + // Set the Realm IPA state to RAM to close the Aperture + Status = RsiSetIpaState ( + (UINT64 *)ApertInfo->BaseAddress, + (ApertInfo->Pages * EFI_PAGE_SIZE), + RipasRam, + RIPAS_CHANGE_FLAGS_RSI_NO_CHANGE_DESTROYED + ); + if (RETURN_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: RSI Set IPA State failed, Address = %p, Pages = 0x%lx, " + "Status = %r\n", + ApertInfo->BaseAddress, + ApertInfo->Pages, + Status + )); + return Status; + } + + Status = ArmCcaSetMemoryProtectAttribute ( + ApertInfo->BaseAddress, + EFI_PAGES_TO_SIZE (ApertInfo->Pages), + mIpaWidth, + FALSE + ); + if (RETURN_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: Failed to update page tables for Protected RAM page mapping," + "Address = %p, Pages = 0x%lx, Status = %r\n", + ApertInfo->BaseAddress, + ApertInfo->Pages, + Status + )); + } + + RemoveEntryList (&ApertInfo->Link); + FreePool (ApertInfo); + + return Status; +} + +/** Closes all open apertures. + +**/ +STATIC +VOID +EFIAPI +RampCloseAllApertures ( + VOID + ) +{ + LIST_ENTRY *Node; + LIST_ENTRY *NextNode; + APERTURE_INFO *ApertureInfo; + + // All drivers that had opened the apertures have halted their respective + // controllers by now; close all the apertures. + for ( + Node = GetFirstNode (&mApertureInfos); + Node != &mApertureInfos; + Node = NextNode + ) + { + NextNode = GetNextNode (&mApertureInfos, Node); + ApertureInfo = CR (Node, APERTURE_INFO, Link, APERTURE_INFO_SIG); + RampCloseAperture (&ApertureInfo->Link); + } +} + +/** + Notification function that is queued after the notification functions of all + events in the EFI_EVENT_GROUP_EXIT_BOOT_SERVICES event group. + + This function invokes the closing of all open apertures. + + @param[in] Event Event whose notification function is being invoked. Event + is permitted to request the queueing of this function + only at TPL_CALLBACK task priority level. + + @param[in] Context Ignored. +**/ +STATIC +VOID +EFIAPI +OnRampExitBootServicesEvent ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + RampCloseAllApertures (); +} + +/** + Notification function that is queued when gBS->ExitBootServices() signals the + EFI_EVENT_GROUP_EXIT_BOOT_SERVICES event group. This function signals another + event, received as Context, and returns. + + Signaling an event in this context is safe. The UEFI spec allows + gBS->SignalEvent() to return EFI_SUCCESS only; EFI_OUT_OF_RESOURCES is not + listed, hence memory is not allocated. + + @param[in] Event Event whose notification function is being invoked. + Event is permitted to request the queueing of this + function at TPL_CALLBACK or TPL_NOTIFY task + priority level. + + @param[in] EventToSignal Identifies the EFI_EVENT to signal. EventToSignal + is permitted to request the queueing of its + notification function only at TPL_CALLBACK level. +**/ +STATIC +VOID +EFIAPI +RampExitBootServices ( + IN EFI_EVENT Event, + IN VOID *EventToSignal + ) +{ + // (1) The NotifyFunctions of all the events in + // EFI_EVENT_GROUP_EXIT_BOOT_SERVICES will have been queued before + // RampExitBootServices() is entered. + // + // (2) RampExitBootServices() is executing minimally at TPL_CALLBACK. + // + // (3) RampExitBootServices() has been queued in unspecified order relative + // to the NotifyFunctions of all the other events in + // EFI_EVENT_GROUP_EXIT_BOOT_SERVICES whose NotifyTpl is the same as + // Event's. + // + // Consequences: + // + // - If Event's NotifyTpl is TPL_CALLBACK, then some other NotifyFunctions + // queued at TPL_CALLBACK may be invoked after RampExitBootServices() + // returns. + // + // - If Event's NotifyTpl is TPL_NOTIFY, then some other NotifyFunctions + // queued at TPL_NOTIFY may be invoked after RampExitBootServices() + // returns; plus *all* NotifyFunctions queued at TPL_CALLBACK will be + // invoked strictly after all NotifyFunctions queued at TPL_NOTIFY, + // including RampExitBootServices(), have been invoked. + // + // - By signaling EventToSignal here, whose NotifyTpl is TPL_CALLBACK, we + // queue EventToSignal's NotifyFunction after the NotifyFunctions of *all* + // events in EFI_EVENT_GROUP_EXIT_BOOT_SERVICES. + gBS->SignalEvent (EventToSignal); +} + +/** A structure describing the Realm Aperture Management protocol. +*/ +STATIC +CONST +EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL Ramp = { + EDKII_REALM_APERTURE_MANAGEMENT_PROTOCOL_REVISION, + RampOpenAperture, + RampCloseAperture +}; + +/** + This routine is called to close all apertures before system reset. + + @param[in] ResetType The type of reset to perform. + @param[in] ResetStatus The status code for the reset. + @param[in] DataSize The size, in bytes, of ResetData. + @param[in] ResetData For a ResetType of EfiResetCold, EfiResetWarm, or + EfiResetShutdown the data buffer starts with a Null- + terminated string, optionally followed by additional + binary data. The string is a description that the + caller may use to further indicate the reason for + the system reset. ResetData is only valid if + ResetStatus is something other than EFI_SUCCESS + unless the ResetType is EfiResetPlatformSpecific + where a minimum amount of ResetData is always + required. + For a ResetType of EfiResetPlatformSpecific the data + buffer also starts with a Null-terminated string + that is followed by an EFI_GUID that describes the + specific type of reset to perform. +**/ +VOID +EFIAPI +OnResetEvent ( + IN EFI_RESET_TYPE ResetType, + IN EFI_STATUS ResetStatus, + IN UINTN DataSize, + IN VOID *ResetData OPTIONAL + ) +{ + RampCloseAllApertures (); +} + +/** + Hook the system reset to close all apertures. + + @param[in] Event Event whose notification function is being invoked + @param[in] Context Pointer to the notification function's context +**/ +VOID +EFIAPI +OnResetNotificationInstall ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EFI_STATUS Status; + EFI_RESET_NOTIFICATION_PROTOCOL *ResetNotify; + + Status = gBS->LocateProtocol ( + &gEfiResetNotificationProtocolGuid, + NULL, + (VOID **)&ResetNotify + ); + if (!EFI_ERROR (Status)) { + Status = ResetNotify->RegisterResetNotify (ResetNotify, OnResetEvent); + ASSERT_EFI_ERROR (Status); + DEBUG ((DEBUG_INFO, "RAMP: Hook system reset to close all apertures.\n")); + gBS->CloseEvent (Event); + } +} + +/** Entry point for Realm Aperture Management Protocol Dxe + + @param [in] ImageHandle Handle for this image. + @param [in] SystemTable Pointer to the EFI system table. + + @retval EFI_SUCCESS When executing in a Realm the RAMP was + installed successfully. + When execution context is not a Realm, this + function returns success indicating nothing + needs to be done and allow other modules to + run. + @retval EFI_OUT_OF_RESOURCES There was not enough memory to install the + protocols. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + +**/ +EFI_STATUS +EFIAPI +RealmApertureManagementProtocolDxeInitialize ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + EFI_HANDLE Handle; + EFI_EVENT CloseAllAperturesEvent; + EFI_EVENT ExitBootEvent; + VOID *Registration; + + // When the execution context is a Realm, install the Realm Aperture + // Management protocol otherwise return success so that other modules + // can run. + if (!IsRealm ()) { + return EFI_SUCCESS; + } + + // Retrieve the IPA Width of the Realm for subsequent use to configure + // the protection attribute of memory regions. + Status = GetIpaWidth (&mIpaWidth); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: Failed to get Ipa Width, Status = %r\n", + Status + )); + ASSERT (0); + return Status; + } + + /* + Create the "late" event whose notification function will close all + apertures. + */ + Status = gBS->CreateEvent ( + EVT_NOTIFY_SIGNAL, // Type + TPL_CALLBACK, // NotifyTpl + OnRampExitBootServicesEvent, // NotifyFunction + NULL, // NotifyContext + &CloseAllAperturesEvent // Event + ); + if (EFI_ERROR (Status)) { + return Status; + } + + /* + Create the event whose notification function will be queued by + gBS->ExitBootServices() and will signal the event created above. + */ + Status = gBS->CreateEvent ( + EVT_SIGNAL_EXIT_BOOT_SERVICES, // Type + TPL_CALLBACK, // NotifyTpl + RampExitBootServices, // NotifyFunction + CloseAllAperturesEvent, // NotifyContext + &ExitBootEvent // Event + ); + if (EFI_ERROR (Status)) { + goto error_handler1; + } + + Handle = NULL; + Status = gBS->InstallMultipleProtocolInterfaces ( + &Handle, + &gEfiRealmApertureManagementProtocolGuid, + &Ramp, + NULL + ); + if (!EFI_ERROR (Status)) { + // RAMP Protocol installed successfully + // Hook the system reset to close all apertures. + EfiCreateProtocolNotifyEvent ( + &gEfiResetNotificationProtocolGuid, + TPL_CALLBACK, + OnResetNotificationInstall, + NULL, + &Registration + ); + return Status; + } + + // cleanup on error + gBS->CloseEvent (ExitBootEvent); + +error_handler1: + gBS->CloseEvent (CloseAllAperturesEvent); + return Status; +} diff --git a/ArmVirtPkg/RealmApertureManagementProtocolDxe/RealmApertureManagementProtocolDxe.inf b/ArmVirtPkg/RealmApertureManagementProtocolDxe/RealmApertureManagementProtocolDxe.inf new file mode 100644 index 000000000000..2e3021b82bd7 --- /dev/null +++ b/ArmVirtPkg/RealmApertureManagementProtocolDxe/RealmApertureManagementProtocolDxe.inf @@ -0,0 +1,48 @@ +## @file +# Module to manage the sharing of buffers in a Realm with the Host. +# +# Copyright (c) 2022 - 2023, Arm Limited. All rights reserved. +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 0x0001001B + BASE_NAME = RealmApertureManagementProtocolDxe + FILE_GUID = CEC2F7D5-2564-46D4-A23F-501623F7F56A + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = RealmApertureManagementProtocolDxeInitialize + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = AARCH64 +# + +[Sources] + RealmApertureManagementProtocolDxe.c + +[Packages] + ArmVirtPkg/ArmVirtPkg.dec + MdeModulePkg/MdeModulePkg.dec + MdePkg/MdePkg.dec + +[LibraryClasses] + ArmCcaLib + ArmCcaRsiLib + BaseLib + DxeServicesTableLib + MemoryAllocationLib + PrintLib + UefiBootServicesTableLib + UefiDriverEntryPoint + UefiLib + +[Protocols] + gEfiRealmApertureManagementProtocolGuid ## SOMETIME_PRODUCES + gEfiResetNotificationProtocolGuid ## CONSUMES + +[Depex] + TRUE diff --git a/MdePkg/Include/AArch64/AArch64.h b/MdePkg/Include/AArch64/AArch64.h index 9650608e7bab..86ba0cb10038 100644 --- a/MdePkg/Include/AArch64/AArch64.h +++ b/MdePkg/Include/AArch64/AArch64.h @@ -1,7 +1,7 @@ /** @file Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.
- Copyright (c) 2011 - 2021, Arm Limited. All rights reserved.
+ Copyright (c) 2011 - 2023, Arm Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent @@ -32,6 +32,7 @@ // ID_AA64PFR0 - AArch64 Processor Feature Register 0 definitions #define AARCH64_PFR0_FP (0xF << 16) #define AARCH64_PFR0_GIC (0xF << 24) +#define AARCH64_PFR0_RME (0xFULL << 52) // ID_AA64DFR0 - AArch64 Debug Feature Register 0 definitions #define AARCH64_DFR0_TRACEVER (0xFULL << 4) diff --git a/MdePkg/Include/Library/ArmLib.h b/MdePkg/Include/Library/ArmLib.h index 087cddfb76c3..c7541d963771 100644 --- a/MdePkg/Include/Library/ArmLib.h +++ b/MdePkg/Include/Library/ArmLib.h @@ -1,7 +1,7 @@ /** @file Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.
- Copyright (c) 2011 - 2016, ARM Ltd. All rights reserved.
+ Copyright (c) 2011 - 2023, Arm Limited. All rights reserved.
Copyright (c) 2020 - 2021, NUVIA Inc. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent @@ -770,6 +770,19 @@ ArmHasSecurityExtensions ( VOID ); +#else + +/** Checks if RME is implemented. + + @retval TRUE RME is implemented. + @retval FALSE RME is not implemented. +**/ +BOOLEAN +EFIAPI +ArmHasRme ( + VOID + ); + #endif // MDE_CPU_ARM #endif // ARM_LIB_H_ diff --git a/MdePkg/Library/BaseRngLib/AArch64/Rndr.c b/MdePkg/Library/BaseRngLib/AArch64/Rndr.c index 2c5344384059..1d097fd5c549 100644 --- a/MdePkg/Library/BaseRngLib/AArch64/Rndr.c +++ b/MdePkg/Library/BaseRngLib/AArch64/Rndr.c @@ -45,7 +45,14 @@ BaseRngLibConstructor ( // MSR. A non-zero value indicates that the processor supports the RNDR instruction. // Isar0 = ArmReadIdAA64Isar0Reg (); - mRndrSupported = !!((Isar0 >> ARM_ID_AA64ISAR0_EL1_RNDR_SHIFT) & ARM_ID_AA64ISAR0_EL1_RNDR_MASK); + mRndrSupported = (((Isar0 >> ARM_ID_AA64ISAR0_EL1_RNDR_SHIFT) & + ARM_ID_AA64ISAR0_EL1_RNDR_MASK) != 0); + if (!mRndrSupported) { + DEBUG (( + DEBUG_WARN, + "WARNING: BaseRngLib: RNDR instruction not supported by the processor.\n" + )); + } return EFI_SUCCESS; }