diff --git a/DynamicTablesPkg/DynamicTables.dsc.inc b/DynamicTablesPkg/DynamicTables.dsc.inc index bbfdce916d..2b842e0548 100644 --- a/DynamicTablesPkg/DynamicTables.dsc.inc +++ b/DynamicTablesPkg/DynamicTables.dsc.inc @@ -2,7 +2,7 @@ # Dsc include file for Dynamic Tables Framework. # # Copyright (c) 2017 - 2026, Arm Limited. All rights reserved.
-# Copyright (C) 2024 - 2025 Advanced Micro Devices, Inc. All rights reserved.
+# Copyright (C) 2024 - 2026 Advanced Micro Devices, Inc. All rights reserved.
# Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# # SPDX-License-Identifier: BSD-2-Clause-Patent @@ -41,6 +41,7 @@ DynamicTablesPkg/Library/Acpi/Common/AcpiFadtLib/AcpiFadtLib.inf DynamicTablesPkg/Library/Acpi/Common/AcpiHmatLib/AcpiHmatLib.inf DynamicTablesPkg/Library/Acpi/Common/AcpiMcfgLib/AcpiMcfgLib.inf + DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/AcpiMsctLib.inf DynamicTablesPkg/Library/Acpi/Common/AcpiPcctLib/AcpiPcctLib.inf DynamicTablesPkg/Library/Acpi/Common/AcpiPpttLib/AcpiPpttLib.inf DynamicTablesPkg/Library/Acpi/Common/AcpiRawLib/AcpiRawLib.inf @@ -105,6 +106,7 @@ NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiCedtLib/AcpiCedtLib.inf NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiFadtLib/AcpiFadtLib.inf NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiMcfgLib/AcpiMcfgLib.inf + NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/AcpiMsctLib.inf NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiSlitLib/AcpiSlitLib.inf NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiSpcrLib/AcpiSpcrLib.inf NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiSpmiLib/AcpiSpmiLib.inf @@ -148,6 +150,7 @@ NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiFadtLib/AcpiFadtLib.inf NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiHmatLib/AcpiHmatLib.inf NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiMcfgLib/AcpiMcfgLib.inf + NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/AcpiMsctLib.inf NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiPcctLib/AcpiPcctLib.inf NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiPpttLib/AcpiPpttLib.inf NULL|DynamicTablesPkg/Library/Acpi/Common/AcpiRawLib/AcpiRawLib.inf diff --git a/DynamicTablesPkg/DynamicTablesPkg.dsc b/DynamicTablesPkg/DynamicTablesPkg.dsc index 91f46c2696..ebc6e02a00 100644 --- a/DynamicTablesPkg/DynamicTablesPkg.dsc +++ b/DynamicTablesPkg/DynamicTablesPkg.dsc @@ -3,7 +3,7 @@ # # Copyright (c) 2019, Linaro Limited. All rights reserved.
# Copyright (c) 2019 - 2022, Arm Limited. All rights reserved.
-# Copyright (C) 2024 - 2025 Advanced Micro Devices, Inc. All rights reserved.
+# Copyright (C) 2024 - 2026 Advanced Micro Devices, Inc. All rights reserved.
# # SPDX-License-Identifier: BSD-2-Clause-Patent # @@ -32,6 +32,7 @@ MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf + SortLib|MdeModulePkg/Library/BaseSortLib/BaseSortLib.inf UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf UefiDriverEntryPoint|MdePkg/Library/UefiDriverEntryPoint/UefiDriverEntryPoint.inf UefiLib|MdePkg/Library/UefiLib/UefiLib.inf diff --git a/DynamicTablesPkg/Include/AcpiTableGenerator.h b/DynamicTablesPkg/Include/AcpiTableGenerator.h index 0f827d2cac..b4ff0ae616 100755 --- a/DynamicTablesPkg/Include/AcpiTableGenerator.h +++ b/DynamicTablesPkg/Include/AcpiTableGenerator.h @@ -91,6 +91,9 @@ The Dynamic Tables Framework implements the following ACPI table generators: Configuration Manager and builds the HEST table. - EINJ : The EINJ generator collates the required platform information from the Configuration Manager and builds the EINJ table. + - MSCT : The MSCT (Maximum System Characteristics Table) generator collates + the Maximum System Characteristics information from the + Configuration Manager and builds the MSCT table. */ /** The ACPI_TABLE_GENERATOR_ID type describes ACPI table generator ID. @@ -133,6 +136,7 @@ typedef enum StdAcpiTableId { EStdAcpiTableIdHest, ///< Hest Generator EStdAcpiTableIdEinj, ///< EINJ Generator EStdAcpiTableIdHmat, ///< HMAT Generator + EStdAcpiTableIdMsct, ///< MSCT Generator EStdAcpiTableIdMax } ESTD_ACPI_TABLE_ID; diff --git a/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/AcpiMsctLib.inf b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/AcpiMsctLib.inf new file mode 100644 index 0000000000..17359ad303 --- /dev/null +++ b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/AcpiMsctLib.inf @@ -0,0 +1,44 @@ +## @file +# ACPI Maximum System Characteristics Table (MSCT) Generator +# +# Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved. +# +# SPDX-License-Identifier: BSD-2-Clause-Patent +## + +[Defines] + INF_VERSION = 1.30 + BASE_NAME = AcpiMsctLib + FILE_GUID = D4F5A6B7-C8D9-4E0A-B1C2-3D4E5F607182 + VERSION_STRING = 1.0 + MODULE_TYPE = DXE_DRIVER + LIBRARY_CLASS = NULL|DXE_DRIVER + CONSTRUCTOR = AcpiMsctLibConstructor + DESTRUCTOR = AcpiMsctLibDestructor + +[Sources] + MsctGenerator.c + MsctGenerator.h + +[Sources.x64] + X64/X64MsctGenerator.c + +[Sources.AARCH64] + Arm/ArmMsctGenerator.c + +[Sources.RISCV64] + Default/DefaultMsctGenerator.c + +[Sources.LOONGARCH64] + Default/DefaultMsctGenerator.c + +[Packages] + DynamicTablesPkg/DynamicTablesPkg.dec + MdeModulePkg/MdeModulePkg.dec + MdePkg/MdePkg.dec + +[LibraryClasses] + BaseLib + CmObjHelperLib + SortLib + diff --git a/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/Arm/ArmMsctGenerator.c b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/Arm/ArmMsctGenerator.c new file mode 100644 index 0000000000..9f28f97026 --- /dev/null +++ b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/Arm/ArmMsctGenerator.c @@ -0,0 +1,249 @@ +/** @file + ARM specific ACPI Maximum System Characteristics Table (MSCT) Generator + + Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#include + +// Module specific include files. +#include +#include +#include +#include +#include +#include + +#include "MsctGenerator.h" + +/** + ARM standard MSCT Generator + + Requirements: + The following Configuration Manager Object(s) are used by this Generator: + - EArmObjGicCInfo (OPTIONAL) +*/ + +/** This macro expands to a function that retrieves the GIC + CPU interface Information from the Configuration Manager. +*/ +GET_OBJECT_LIST ( + EObjNameSpaceArm, + EArmObjGicCInfo, + CM_ARM_GICC_INFO + ); + +/** Get architecture processor domain information. + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. + @param [out] ProcDomainInfo Pointer to the processor domain information. + @param [out] ProcDomainInfoCount Pointer to the count of processor domain + information structures. + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + @retval Other Other EFI_STATUS error from called functions. +**/ +EFI_STATUS +EFIAPI +GetArchProcessorDomainInfo ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT UINT32 **ProcDomainInfo, + OUT UINT32 *ProcDomainInfoCount + ) +{ + CM_ARM_GICC_INFO *CmProcDomain; + EFI_STATUS Status; + UINT32 Index; + UINT32 ProcDomainCount; + UINT32 *ProcDomain; + + if ((CfgMgrProtocol == NULL) || + (ProcDomainInfo == NULL) || + (ProcDomainInfoCount == NULL)) + { + ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER); + return EFI_INVALID_PARAMETER; + } + + CmProcDomain = NULL; + ProcDomain = NULL; + ProcDomainCount = 0; + + Status = GetEArmObjGicCInfo ( + CfgMgrProtocol, + CM_NULL_TOKEN, + &CmProcDomain, + &ProcDomainCount + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Failed to get GICC Info. Status = %r\n", + Status + )); + return Status; + } + + ProcDomain = AllocateZeroPool ( + sizeof (UINT32) * + ProcDomainCount + ); + if (ProcDomain == NULL) { + Status = EFI_OUT_OF_RESOURCES; + ASSERT_EFI_ERROR (Status); + return Status; + } + + for (Index = 0; Index < ProcDomainCount; Index++) { + Status = GetProximityDomainId ( + CfgMgrProtocol, + CmProcDomain[Index].ProximityDomain, + CmProcDomain[Index].ProximityDomainToken, + &ProcDomain[Index] + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "MSCT: Failed to get Proximity Domain ID for index %d. Status = %r\n", + Index, + Status + )); + ASSERT_EFI_ERROR (Status); + goto error_handler; + } + } + + *ProcDomainInfo = ProcDomain; + *ProcDomainInfoCount = ProcDomainCount; + return EFI_SUCCESS; + +error_handler: + + if (ProcDomain != NULL) { + FreePool (ProcDomain); + } + + return Status; +} + +/** Get the count of unique architecture clock domains. + + Enumerates all GIC CPU interfaces and returns the number of unique clock + proximity domain IDs in the system. + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. + @param [out] ClockDomainCount On success, the cardinal count of unique + clock domains. + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + @retval Other Other EFI_STATUS error from called functions. +**/ +EFI_STATUS +EFIAPI +GetArchClockDomainInfo ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT UINT32 *ClockDomainCount + ) +{ + EFI_STATUS Status; + UINT32 ProximityDomain; + UINT32 CmCount; + UINT32 Index; + UINT32 UniqueIndex; + CM_ARM_GICC_INFO *CmGiccInfo; + UINT32 *ClockDomains; + UINT32 UniqueCount; + BOOLEAN IsUnique; + + if ((CfgMgrProtocol == NULL) || + (ClockDomainCount == NULL)) + { + ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER); + return EFI_INVALID_PARAMETER; + } + + CmGiccInfo = NULL; + ClockDomains = NULL; + UniqueCount = 0; + CmCount = 0; + + Status = GetEArmObjGicCInfo ( + CfgMgrProtocol, + CM_NULL_TOKEN, + &CmGiccInfo, + &CmCount + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Failed to get GICC Info. Status = %r\n", + Status + )); + return Status; + } + + if ((CmGiccInfo == NULL) || (CmCount == 0)) { + *ClockDomainCount = 0; + return EFI_SUCCESS; + } + + // Allocate worst-case storage: every CPU could be in its own clock domain. + ClockDomains = AllocateZeroPool (sizeof (UINT32) * CmCount); + if (ClockDomains == NULL) { + Status = EFI_OUT_OF_RESOURCES; + ASSERT_EFI_ERROR (Status); + return Status; + } + + for (Index = 0; Index < CmCount; Index++) { + Status = GetProximityDomainId ( + CfgMgrProtocol, + CmGiccInfo[Index].ClockDomain, + CmGiccInfo[Index].ClockDomainToken, + &ProximityDomain + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Failed to get Clock Domain ID for index %d. Status = %r\n", + Index, + Status + )); + ASSERT_EFI_ERROR (Status); + goto error_handler; + } + + // Record the proximity domain ID only if not already seen. + IsUnique = TRUE; + for (UniqueIndex = 0; UniqueIndex < UniqueCount; UniqueIndex++) { + if (ClockDomains[UniqueIndex] == ProximityDomain) { + IsUnique = FALSE; + break; + } + } + + if (IsUnique) { + ClockDomains[UniqueCount++] = ProximityDomain; + } + } + + *ClockDomainCount = UniqueCount; + Status = EFI_SUCCESS; + +error_handler: + if (ClockDomains != NULL) { + FreePool (ClockDomains); + } + + return Status; +} diff --git a/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/Default/DefaultMsctGenerator.c b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/Default/DefaultMsctGenerator.c new file mode 100644 index 0000000000..2ee10a6654 --- /dev/null +++ b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/Default/DefaultMsctGenerator.c @@ -0,0 +1,67 @@ +/** @file + Default (stub) arch-specific ACPI MSCT Generator implementation. + + Used for architectures that do not provide processor or clock domain + information via the Configuration Manager (e.g. RISCV64, LOONGARCH64). + Both functions return EFI_NOT_FOUND so that the common generator + gracefully omits the arch-specific topology data from the MSCT. + + Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include + +// Module specific include files. +#include +#include +#include + +#include "MsctGenerator.h" + +/** Get architecture processor domain information. + + This stub implementation returns EFI_NOT_FOUND for architectures that + do not provide processor proximity domain data via the Configuration Manager. + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. + @param [out] ProcDomainInfo Pointer to the processor domain information. + @param [out] ProcDomainInfoCount Pointer to the count of processor domain + information structures. + + @retval EFI_NOT_FOUND Not supported on this architecture. +**/ +EFI_STATUS +EFIAPI +GetArchProcessorDomainInfo ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT UINT32 **ProcDomainInfo, + OUT UINT32 *ProcDomainInfoCount + ) +{ + return EFI_NOT_FOUND; +} + +/** Get the count of unique architecture clock domains. + + This stub implementation returns EFI_NOT_FOUND for architectures that + do not provide clock domain data via the Configuration Manager. + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. + @param [out] ClockDomainCount On success, the cardinal count of unique + clock domains. + + @retval EFI_NOT_FOUND Not supported on this architecture. +**/ +EFI_STATUS +EFIAPI +GetArchClockDomainInfo ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT UINT32 *ClockDomainCount + ) +{ + return EFI_NOT_FOUND; +} diff --git a/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/MsctGenerator.c b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/MsctGenerator.c new file mode 100644 index 0000000000..736f8788de --- /dev/null +++ b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/MsctGenerator.c @@ -0,0 +1,1092 @@ +/** @file + ACPI Maximum System Characteristics Table (MSCT) Generator + + Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#include +#include +#include + +// Module specific include files. +#include +#include +#include +#include +#include +#include + +#include "MsctGenerator.h" + +/** + Standard MSCT Generator + + Requirements: + The following Configuration Manager Object(s) are used by this Generator: + - EArchCommonObjMsctMaxPhysicalAddrInfo (REQUIRED) + - EArchCommonObjMemoryAffinityInfo (OPTIONAL) + - EArmObjGicCInfo (OPTIONAL) + - EX64ObjLocalApicX2ApicAffinityInfo (OPTIONAL) +*/ + +/** + This macro expands to a function that retrieves the maximum physical address + information from the Configuration Manager. +*/ +GET_OBJECT_LIST ( + EObjNameSpaceArchCommon, + EArchCommonObjMsctMaxPhysicalAddrInfo, + CM_ARCH_COMMON_MSCT_MAX_PHYSICAL_ADDR_INFO + ); + +/** + This macro expands to a function that retrieves the Memory Affinity + information from the Configuration Manager. +*/ +GET_OBJECT_LIST ( + EObjNameSpaceArchCommon, + EArchCommonObjMemoryAffinityInfo, + CM_ARCH_COMMON_MEMORY_AFFINITY_INFO + ); + +/** Add Id to Seen if not already present, incrementing *UniqueCount on success. + + Performs a linear scan of Seen[0..*UniqueCount-1]. If Id is not found, it is + appended at Seen[*UniqueCount] and *UniqueCount is incremented. The caller + must ensure the Seen array has capacity for at least one additional element. + + @param [in] Id Domain identifier to record. + @param [in, out] Seen Array of already-recorded unique IDs. + @param [in, out] UniqueCount Number of valid entries in Seen on entry; + incremented by one when Id is newly added. + +**/ +STATIC +VOID +AddIfUnique ( + IN UINT32 Id, + IN OUT UINT32 *Seen, + IN OUT UINT32 *UniqueCount + ) +{ + UINT32 SeenIndex; + + for (SeenIndex = 0; SeenIndex < *UniqueCount; SeenIndex++) { + if (Seen[SeenIndex] == Id) { + return; + } + } + + Seen[(*UniqueCount)++] = Id; +} + +/** Comparison function for sorting Maximum Proximity Domain. + + @param [in] Left Pointer to the left Proximity Domain structure. + @param [in] Right Pointer to the right Proximity Domain structure. + + @retval -1 If Left proximity domain < Right proximity domain + @retval 0 If Left proximity domain == Right proximity domain + @retval 1 If Left proximity domain > Right proximity domain +**/ +INTN +EFIAPI +SortByProximityDomainRange ( + IN CONST VOID *Left, + IN CONST VOID *Right + ) +{ + CONST EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE *LeftInfo; + CONST EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE *RightInfo; + + LeftInfo = (CONST EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE *)Left; + RightInfo = (CONST EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE *)Right; + + if (LeftInfo->ProximityDomainRangeLow < RightInfo->ProximityDomainRangeLow) { + return -1; + } else if (LeftInfo->ProximityDomainRangeLow > RightInfo->ProximityDomainRangeLow) { + return 1; + } else { + // ProximityDomainRangeLow are equal, compare ProximityDomainRangeHigh + if (LeftInfo->ProximityDomainRangeHigh < RightInfo->ProximityDomainRangeHigh) { + return -1; + } else if (LeftInfo->ProximityDomainRangeHigh > RightInfo->ProximityDomainRangeHigh) { + return 1; + } else { + return 0; + } + } +} + +/** Get processor domain information. + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. + @param [out] ProcDomainInfo Pointer to the processor domain information. + @param [out] ProcDomainInfoCount Pointer to the count of processor domain + information structures. + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + @retval Other Other EFI_STATUS error from called functions. +**/ +EFI_STATUS +EFIAPI +GetProcessorDomainInfo ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE **ProcDomainInfo, + OUT UINT32 *ProcDomainInfoCount + ) +{ + EFI_STATUS Status; + UINT32 *ProcDomainArch; + UINT32 ProcDomainArchCount; + UINT32 Index; + UINT32 ProcDomainIndex; + UINT32 ProcDomainCount; + + EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE *ProcDomain; + + if ((CfgMgrProtocol == NULL) || + (ProcDomainInfo == NULL) || + (ProcDomainInfoCount == NULL)) + { + ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER); + return EFI_INVALID_PARAMETER; + } + + ProcDomain = NULL; + ProcDomainArch = NULL; + ProcDomainArchCount = 0; + + Status = GetArchProcessorDomainInfo ( + CfgMgrProtocol, + &ProcDomainArch, + &ProcDomainArchCount + ); + if (EFI_ERROR (Status)) { + return Status; + } + + if ((ProcDomainArchCount == 0) || (ProcDomainArch == NULL)) { + return EFI_NOT_FOUND; + } + + ProcDomainCount = 0; + ProcDomain = NULL; + for (Index = 0; Index < ProcDomainArchCount; Index++) { + for (ProcDomainIndex = 0; ProcDomainIndex < ProcDomainCount; ProcDomainIndex++) { + if ((ProcDomain != NULL) && + (ProcDomain[ProcDomainIndex].ProximityDomainRangeLow == ProcDomainArch[Index])) + { + ProcDomain[ProcDomainIndex].MaximumProcessorCapacity++; + break; + } + } + + if (ProcDomainIndex == ProcDomainCount) { + ProcDomain = ReallocatePool ( + sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE) * ProcDomainCount, + sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE) * (ProcDomainCount + 1), + ProcDomain + ); + if (ProcDomain == NULL) { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Failed to reallocate memory for Processor Domain Info.\n" + )); + Status = EFI_OUT_OF_RESOURCES; + ASSERT_EFI_ERROR (Status); + goto error_handler; + } + + ProcDomain[ProcDomainCount].Revision = EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_REVISION; + ProcDomain[ProcDomainCount].Length = sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE); + ProcDomain[ProcDomainCount].ProximityDomainRangeLow = ProcDomainArch[Index]; + ProcDomain[ProcDomainCount].ProximityDomainRangeHigh = ProcDomainArch[Index]; + ProcDomain[ProcDomainCount].MaximumProcessorCapacity = 1; + ProcDomainCount++; + } + } + + *ProcDomainInfo = ProcDomain; + *ProcDomainInfoCount = ProcDomainCount; + + FreePool (ProcDomainArch); + return EFI_SUCCESS; + +error_handler: + if (ProcDomain != NULL) { + FreePool (ProcDomain); + } + + if (ProcDomainArch != NULL) { + FreePool (ProcDomainArch); + } + + return Status; +} + +/** Get memory domain information. + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. + @param [out] MemDomainInfo Pointer to the memory domain information. + @param [out] MemDomainInfoCount Pointer to the count of memory domain + information structures. + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + @retval Other Other EFI_STATUS error from called functions. +**/ +EFI_STATUS +EFIAPI +GetMemoryDomainInfo ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE **MemDomainInfo, + OUT UINT32 *MemDomainInfoCount + ) +{ + EFI_STATUS Status; + UINT32 Index; + UINT32 MemAffCount; + UINT32 ProximityDomain; + + CM_ARCH_COMMON_MEMORY_AFFINITY_INFO *MemAffInfo; + EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE *MemDomain; + + if ((CfgMgrProtocol == NULL) || + (MemDomainInfo == NULL) || + (MemDomainInfoCount == NULL)) + { + ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER); + return EFI_INVALID_PARAMETER; + } + + MemDomain = NULL; + MemAffInfo = NULL; + + /// Get Memory Affinity Information + Status = GetEArchCommonObjMemoryAffinityInfo ( + CfgMgrProtocol, + CM_NULL_TOKEN, + &MemAffInfo, + &MemAffCount + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Failed to get Memory Affinity Info. Status = %r\n", + Status + )); + ASSERT_EFI_ERROR (Status); + return Status; + } + + if ((MemAffInfo == NULL) || (MemAffCount == 0)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Invalid Memory Affinity Info data.\n" + )); + Status = EFI_NOT_FOUND; + return Status; + } + + MemDomain = AllocateZeroPool ( + MemAffCount * sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE) + ); + if (MemDomain == NULL) { + Status = EFI_OUT_OF_RESOURCES; + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Failed to allocate memory for Memory Domain Info. Status = %r\n", + Status + )); + ASSERT_EFI_ERROR (Status); + return Status; + } + + for (Index = 0; Index < MemAffCount; Index++) { + Status = GetProximityDomainId ( + CfgMgrProtocol, + MemAffInfo[Index].ProximityDomain, + MemAffInfo[Index].ProximityDomainToken, + &ProximityDomain + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "MSCT: Cannot obtain memory Proximity ID. Status = %r\n", + Status + )); + ASSERT_EFI_ERROR (Status); + goto return_handler; + } + + MemDomain[Index].Revision = EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_REVISION; + MemDomain[Index].Length = sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE); + + MemDomain[Index].ProximityDomainRangeLow = ProximityDomain; + MemDomain[Index].ProximityDomainRangeHigh = ProximityDomain; + MemDomain[Index].MaximumMemoryCapacity = MemAffInfo[Index].Length; + } + + PerformQuickSort ( + MemDomain, + MemAffCount, + sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE), + SortByProximityDomainRange + ); + + *MemDomainInfo = MemDomain; + *MemDomainInfoCount = MemAffCount; + return EFI_SUCCESS; + +return_handler: + if (MemDomain != NULL) { + FreePool (MemDomain); + } + + return Status; +} + +/** Extract unique memory proximity domain IDs from a populated MemDomainInfo array. + + The input array must already be sorted by ProximityDomainRangeLow, as + produced by GetMemoryDomainInfo(). Consecutive entries with the same + ProximityDomainRangeLow value are treated as duplicates and collapsed to a + single ID. The caller is responsible for freeing the returned array with + FreePool(). + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. Reserved for future use; + must not be NULL. + @param [in] MemDomainInfo Sorted array of memory domain information + structures, as returned by + GetMemoryDomainInfo(). May be NULL only + when MemDomainInfoCount is 0. + @param [in] MemDomainInfoCount Number of entries in MemDomainInfo. + @param [out] MemDomainIds On success, pointer to a caller-owned + array of unique memory proximity domain + IDs. Must be freed by the caller with + FreePool(). Set to NULL when count is 0. + @param [out] MemDomainIdsCount On success, number of entries in + MemDomainIds (cardinal count of unique + memory proximity domains). + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_NOT_FOUND MemDomainInfoCount is 0 or MemDomainInfo is NULL. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. +**/ +STATIC +EFI_STATUS +EFIAPI +GetUniqueMemoryDomainInfo ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + IN CONST EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE *CONST MemDomainInfo, + IN UINT32 MemDomainInfoCount, + OUT UINT32 **MemDomainIds, + OUT UINT32 *MemDomainIdsCount + ) +{ + UINT32 Index; + UINT32 UniqueCount; + UINT32 *UniqueIds; + + if ((CfgMgrProtocol == NULL) || + (MemDomainIds == NULL) || + (MemDomainIdsCount == NULL)) + { + ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER); + return EFI_INVALID_PARAMETER; + } + + if ((MemDomainInfo == NULL) || (MemDomainInfoCount == 0)) { + *MemDomainIds = NULL; + *MemDomainIdsCount = 0; + return EFI_NOT_FOUND; + } + + // Allocate worst-case: all entries could have distinct proximity domain IDs. + UniqueIds = AllocateZeroPool (sizeof (UINT32) * MemDomainInfoCount); + if (UniqueIds == NULL) { + ASSERT_EFI_ERROR (EFI_OUT_OF_RESOURCES); + return EFI_OUT_OF_RESOURCES; + } + + // The input array is sorted by ProximityDomainRangeLow; scan linearly and + // record a new entry only when the ID differs from the previous one. + UniqueCount = 0; + UniqueIds[0] = MemDomainInfo[0].ProximityDomainRangeLow; + UniqueCount = 1; + for (Index = 1; Index < MemDomainInfoCount; Index++) { + if (MemDomainInfo[Index].ProximityDomainRangeLow != + UniqueIds[UniqueCount - 1]) + { + UniqueIds[UniqueCount++] = MemDomainInfo[Index].ProximityDomainRangeLow; + } + } + + *MemDomainIds = UniqueIds; + *MemDomainIdsCount = UniqueCount; + return EFI_SUCCESS; +} + +/** Compute the total number of unique proximity domain IDs across the + proximity-domain-bearing sets (memory and processor) in the system. + + The two input sets may overlap (the same proximity domain ID can host both + CPUs and memory). This function unions the two sets and returns the cardinal + count of distinct IDs, which maps directly to the ACPI MSCT + MaximumNumberOfProximityDomains field (0-based, so the caller subtracts 1). + + Clock domains are intentionally excluded: per ACPI 6.5/6.6 section 5.2.19, + clock domains form a separate identifier namespace (_CDM) from proximity + domains (_PXM) and are reported via MaximumNumberOfClockDomains. + + @param [in] MemDomainIds Array of unique memory proximity domain + IDs, as returned by GetUniqueMemoryDomainInfo(). + May be NULL when MemDomainIdsCount is 0. + @param [in] MemDomainIdsCount Number of entries in MemDomainIds. + @param [in] ProcDomainInfo Array of unique processor proximity domain + structures, as returned by + GetProcessorDomainInfo(). Each entry's + ProximityDomainRangeLow is the domain ID. + May be NULL when ProcDomainInfoCount is 0. + @param [in] ProcDomainInfoCount Number of entries in ProcDomainInfo. + @param [out] UniqueDomainCount On success, the cardinal count of unique + proximity domain IDs across both sets. + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_INVALID_PARAMETER UniqueDomainCount is NULL. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. +**/ +STATIC +EFI_STATUS +EFIAPI +GetUniqueTotalDomainCount ( + IN CONST UINT32 *MemDomainIds, + IN UINT32 MemDomainIdsCount, + IN CONST EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE *ProcDomainInfo, + IN UINT32 ProcDomainInfoCount, + OUT UINT32 *UniqueDomainCount + ) +{ + UINT32 MaxTotal; + UINT32 UniqueCount; + UINT32 Index; + UINT32 *Seen; + + if (UniqueDomainCount == NULL) { + ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER); + return EFI_INVALID_PARAMETER; + } + + // Worst-case: all IDs are distinct across both sets. + MaxTotal = MemDomainIdsCount + ProcDomainInfoCount; + if (MaxTotal == 0) { + *UniqueDomainCount = 0; + return EFI_SUCCESS; + } + + Seen = AllocateZeroPool (sizeof (UINT32) * MaxTotal); + if (Seen == NULL) { + ASSERT_EFI_ERROR (EFI_OUT_OF_RESOURCES); + return EFI_OUT_OF_RESOURCES; + } + + UniqueCount = 0; + + // Union memory domain IDs. + for (Index = 0; Index < MemDomainIdsCount; Index++) { + AddIfUnique (MemDomainIds[Index], Seen, &UniqueCount); + } + + // Union processor domain IDs (ProximityDomainRangeLow is the unique ID per entry). + for (Index = 0; Index < ProcDomainInfoCount; Index++) { + AddIfUnique (ProcDomainInfo[Index].ProximityDomainRangeLow, Seen, &UniqueCount); + } + + FreePool (Seen); + *UniqueDomainCount = UniqueCount; + return EFI_SUCCESS; +} + +/** Update the MSCT Table with configuration data. + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. + @param [out] AcpiMsctTable Pointer to the ACPI MSCT table. + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + @retval Other Other EFI_STATUS error from called functions. +**/ +EFI_STATUS +EFIAPI +UpdateMsctTable ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_HEADER **AcpiMsctTable + ) +{ + EFI_STATUS Status; + UINT32 ClockDomainIdsCount; + UINT32 *MemDomainIds; + UINT32 MemDomainIdsCount; + UINT32 MaxProximityDomain; + UINT32 DomainInfoCount; + UINT32 DomainInfoIndex; + UINT32 MemDomainInfoCount; + UINT32 MemIndex; + UINT32 MemNextIndex; + UINT32 ProcDomainInfoCount; + UINT32 ProcIndex; + UINT32 TableSize; + UINT8 *DomainInfoPtr; + + CM_ARCH_COMMON_MSCT_MAX_PHYSICAL_ADDR_INFO *MaxPhysAddrInfo; + EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE *DomainInfo; + EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE *MemDomainInfo; + EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE *ProcDomainInfo; + EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_HEADER *MsctTable; + + MsctTable = NULL; + MemDomainInfo = NULL; + ProcDomainInfo = NULL; + DomainInfo = NULL; + DomainInfoPtr = NULL; + MaxPhysAddrInfo = NULL; + MemDomainIds = NULL; + + MemDomainInfoCount = 0; + ProcDomainInfoCount = 0; + DomainInfoCount = 0; + DomainInfoIndex = 0; + MaxProximityDomain = 0; + MemDomainIdsCount = 0; + + Status = GetEArchCommonObjMsctMaxPhysicalAddrInfo ( + CfgMgrProtocol, + CM_NULL_TOKEN, + &MaxPhysAddrInfo, + NULL + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Failed to get Max Physical Address Info. Status = %r\n", + Status + )); + goto error_handler; + } + + ClockDomainIdsCount = 0; + Status = GetArchClockDomainInfo ( + CfgMgrProtocol, + &ClockDomainIdsCount + ); + if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) { + goto error_handler; + } + + if ((!EFI_ERROR (Status)) && (ClockDomainIdsCount == 0)) { + // GetArchClockDomainInfo() succeeded but returned count=0, which is a + // Configuration Manager error — a successful query must report at least 1 + // clock domain. Platforms without _CDM objects should return EFI_NOT_FOUND. + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Clock domain query succeeded but returned count 0.\n" + )); + Status = EFI_INVALID_PARAMETER; + ASSERT_EFI_ERROR (Status); + goto error_handler; + } + + Status = GetMemoryDomainInfo ( + CfgMgrProtocol, + &MemDomainInfo, + &MemDomainInfoCount + ); + if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) { + goto error_handler; + } + + Status = GetUniqueMemoryDomainInfo ( + CfgMgrProtocol, + MemDomainInfo, + MemDomainInfoCount, + &MemDomainIds, + &MemDomainIdsCount + ); + if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) { + goto error_handler; + } + + Status = GetProcessorDomainInfo ( + CfgMgrProtocol, + &ProcDomainInfo, + &ProcDomainInfoCount + ); + if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) { + goto error_handler; + } + + // Find the system's total unique proximity domain ids (memory + processor). + // Clock domains are a separate namespace and are counted independently. + Status = GetUniqueTotalDomainCount ( + MemDomainIds, + MemDomainIdsCount, + ProcDomainInfo, + ProcDomainInfoCount, + &MaxProximityDomain + ); + if (EFI_ERROR (Status)) { + goto error_handler; + } + + DomainInfoCount = MemDomainInfoCount + ProcDomainInfoCount; + if (DomainInfoCount == 0) { + DEBUG (( + DEBUG_INFO, + "MSCT: No Memory/Processor Domain information found.\n" + )); + goto generate_msct_table; + } + + DomainInfo = AllocateZeroPool ( + sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE) * + DomainInfoCount + ); + if (DomainInfo == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto error_handler; + } + + // Accumulate the maximum memory capacity per proximity domain. The array is + // sorted by ProximityDomainRangeLow, so entries sharing a domain are + // contiguous. Fold every entry of a run into the run's first entry and + // invalidate the rest. + for (MemIndex = 0; MemIndex < MemDomainInfoCount; MemIndex++) { + if (MemDomainInfo[MemIndex].ProximityDomainRangeLow == MAX_UINT32) { + continue; + } + + for (MemNextIndex = MemIndex + 1; MemNextIndex < MemDomainInfoCount; MemNextIndex++) { + if (MemDomainInfo[MemNextIndex].ProximityDomainRangeLow != + MemDomainInfo[MemIndex].ProximityDomainRangeLow) + { + break; + } + + // Maximum memory capacity per domain. + MemDomainInfo[MemIndex].MaximumMemoryCapacity += + MemDomainInfo[MemNextIndex].MaximumMemoryCapacity; + // Invalidate the folded record. + MemDomainInfo[MemNextIndex].ProximityDomainRangeLow = MAX_UINT32; + MemDomainInfo[MemNextIndex].ProximityDomainRangeHigh = MAX_UINT32; + } + } + + /// + /// Perform union of MemDomainInfo and ProcDomainInfo for same proximity domain, letting: + /// - ProcDomainInfo[] with Proximity domains describing Processors with no memory associated + /// - MemDomainInfo[] with Proximity domains describing memory with no processor associated + /// + DomainInfoIndex = 0; + for (ProcIndex = 0; ProcIndex < ProcDomainInfoCount; ProcIndex++) { + if (ProcDomainInfo[ProcIndex].ProximityDomainRangeLow == MAX_UINT32) { + continue; + } + + for (MemIndex = 0; MemIndex < MemDomainInfoCount; MemIndex++) { + if (MemDomainInfo[MemIndex].ProximityDomainRangeLow == MAX_UINT32) { + continue; + } + + if (ProcDomainInfo[ProcIndex].ProximityDomainRangeLow == + MemDomainInfo[MemIndex].ProximityDomainRangeLow) + { + CopyMem ( + &DomainInfo[DomainInfoIndex], + &ProcDomainInfo[ProcIndex], + sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE) + ); + DomainInfo[DomainInfoIndex].MaximumMemoryCapacity = MemDomainInfo[MemIndex].MaximumMemoryCapacity; + DomainInfoIndex++; + + /// Invalidate the records + ProcDomainInfo[ProcIndex].ProximityDomainRangeLow = MAX_UINT32; + ProcDomainInfo[ProcIndex].ProximityDomainRangeHigh = MAX_UINT32; + MemDomainInfo[MemIndex].ProximityDomainRangeLow = MAX_UINT32; + MemDomainInfo[MemIndex].ProximityDomainRangeHigh = MAX_UINT32; + break; + } + } + } + + /// Club the symmetric continuous proximity domains. + if (ProcDomainInfoCount > 1) { + PerformQuickSort ( + ProcDomainInfo, + ProcDomainInfoCount, + sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE), + SortByProximityDomainRange + ); + for (ProcIndex = ProcDomainInfoCount - 1; ProcIndex > 0; ProcIndex--) { + if (ProcDomainInfo[ProcIndex].ProximityDomainRangeLow == MAX_UINT32) { + continue; + } + + if ((ProcDomainInfo[ProcIndex].ProximityDomainRangeLow == + (ProcDomainInfo[ProcIndex - 1].ProximityDomainRangeLow + 1)) && + (ProcDomainInfo[ProcIndex].MaximumProcessorCapacity == + (ProcDomainInfo[ProcIndex - 1].MaximumProcessorCapacity))) + { + ProcDomainInfo[ProcIndex - 1].ProximityDomainRangeHigh = + ProcDomainInfo[ProcIndex].ProximityDomainRangeHigh; + + ProcDomainInfo[ProcIndex].ProximityDomainRangeLow = MAX_UINT32; + ProcDomainInfo[ProcIndex].ProximityDomainRangeHigh = MAX_UINT32; + } + } + } + + /// Copy the processor proximity record (i.e. the processors with no memory associated) + for (ProcIndex = 0; ProcIndex < ProcDomainInfoCount; ProcIndex++) { + if (ProcDomainInfo[ProcIndex].ProximityDomainRangeLow == MAX_UINT32) { + continue; + } + + CopyMem ( + &DomainInfo[DomainInfoIndex], + &ProcDomainInfo[ProcIndex], + sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE) + ); + DomainInfoIndex++; + } + + /// Copy remaining memory record (i.e. the memory with no processor associated) + for (MemIndex = 0; MemIndex < MemDomainInfoCount; MemIndex++) { + if (MemDomainInfo[MemIndex].ProximityDomainRangeLow == MAX_UINT32) { + continue; + } + + CopyMem ( + &DomainInfo[DomainInfoIndex], + &MemDomainInfo[MemIndex], + sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE) + ); + DomainInfoIndex++; + } + + /// Sort the final list + PerformQuickSort ( + DomainInfo, + DomainInfoIndex, + sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE), + SortByProximityDomainRange + ); + +generate_msct_table: + /// Calculate the size needed for the MSCT table + TableSize = sizeof (EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_HEADER) + + (sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE) * DomainInfoIndex); + + /// Allocate the Buffer for MSCT table + MsctTable = (EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_HEADER *)AllocateZeroPool (TableSize); + if (MsctTable == NULL) { + Status = EFI_OUT_OF_RESOURCES; + DEBUG (( + DEBUG_ERROR, + "MSCT: Failed to allocate memory for MSCT Table, Size = %d, Status = %r\n", + TableSize, + Status + )); + ASSERT_EFI_ERROR (Status); + goto error_handler; + } + + if (DomainInfoIndex > 0) { + DomainInfoPtr = (UINT8 *)MsctTable + sizeof (EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_HEADER); + CopyMem ( + DomainInfoPtr, + (UINT8 *)DomainInfo, + sizeof (EFI_ACPI_6_5_MAXIMUM_PROXIMITY_DOMAIN_INFORMATION_STRUCTURE) * DomainInfoIndex + ); + MsctTable->OffsetProxDomInfo = sizeof (EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_HEADER); + } + + MsctTable->Header.Length = TableSize; + + /// + /// As per the specification maximum number of proximity domain + /// should be -1 of actual domain value. + /// If there is no domain information then it will be concidered as single domain. + /// + if (MaxProximityDomain > 0) { + MsctTable->MaximumNumberOfProximityDomains = MaxProximityDomain - 1; + } else { + MsctTable->MaximumNumberOfProximityDomains = 0; + } + + if (ClockDomainIdsCount > 0) { + MsctTable->MaximumNumberOfClockDomains = ClockDomainIdsCount - 1; + } else { + MsctTable->MaximumNumberOfClockDomains = 0; + } + + MsctTable->MaximumPhysicalAddress = MaxPhysAddrInfo->MaxPhysicalAddress; + *AcpiMsctTable = MsctTable; + + if (MemDomainIds != NULL) { + FreePool (MemDomainIds); + } + + if (MemDomainInfo != NULL) { + FreePool (MemDomainInfo); + } + + if (ProcDomainInfo != NULL) { + FreePool (ProcDomainInfo); + } + + if (DomainInfo != NULL) { + FreePool (DomainInfo); + } + + return EFI_SUCCESS; + +error_handler: + if (MemDomainIds != NULL) { + FreePool (MemDomainIds); + } + + if (MemDomainInfo != NULL) { + FreePool (MemDomainInfo); + } + + if (ProcDomainInfo != NULL) { + FreePool (ProcDomainInfo); + } + + if (DomainInfo != NULL) { + FreePool (DomainInfo); + } + + if (MsctTable != NULL) { + FreePool (MsctTable); + } + + return Status; +} + +/** Build the ACPI MSCT Table. + +@param [in] This Pointer to the table generator. +@param [in] AcpiTableInfo Pointer to the ACPI Table Info. +@param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol Interface. +@param [out] Table Pointer to the constructed ACPI Table. + +@retval EFI_SUCCESS Table generated successfully. +@retval EFI_INVALID_PARAMETER A parameter is invalid. +@retval EFI_OUT_OF_RESOURCES Memory allocation failed. +@retval Other Other EFI_STATUS error from called functions. + +**/ +STATIC +EFI_STATUS +EFIAPI +BuildMsctTable ( + IN CONST ACPI_TABLE_GENERATOR *CONST This, + IN CONST CM_STD_OBJ_ACPI_TABLE_INFO *CONST AcpiTableInfo, + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT EFI_ACPI_DESCRIPTION_HEADER **CONST Table + ) +{ + EFI_STATUS Status; + + EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_HEADER *MsctTable; + + ASSERT (This != NULL); + ASSERT (AcpiTableInfo != NULL); + ASSERT (CfgMgrProtocol != NULL); + ASSERT (Table != NULL); + ASSERT (AcpiTableInfo->TableGeneratorId == This->GeneratorID); + ASSERT (AcpiTableInfo->AcpiTableSignature == This->AcpiTableSignature); + + if ((AcpiTableInfo->AcpiTableRevision < This->MinAcpiTableRevision) || + (AcpiTableInfo->AcpiTableRevision > This->AcpiTableRevision)) + { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Requested table revision = %d, is not supported." + "Supported table revision: Minimum = %d, Maximum = %d\n", + AcpiTableInfo->AcpiTableRevision, + This->MinAcpiTableRevision, + This->AcpiTableRevision + )); + return EFI_INVALID_PARAMETER; + } + + *Table = NULL; + MsctTable = NULL; + + Status = UpdateMsctTable ( + CfgMgrProtocol, + &MsctTable + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Failed to update MSCT table. Status = %r\n", + Status + )); + ASSERT_EFI_ERROR (Status); + goto error_handler; + } + + // Add ACPI header + Status = AddAcpiHeader ( + CfgMgrProtocol, + This, + &MsctTable->Header, + AcpiTableInfo, + MsctTable->Header.Length + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "MSCT: Failed to add ACPI header. Status = %r\n", + Status + )); + ASSERT_EFI_ERROR (Status); + goto error_handler; + } + + *Table = (EFI_ACPI_DESCRIPTION_HEADER *)MsctTable; + return EFI_SUCCESS; + +error_handler: + if (MsctTable != NULL) { + FreePool (MsctTable); + } + + return Status; +} + +/** This macro defines the MSCT Table Generator revision. +*/ +#define MSCT_GENERATOR_REVISION CREATE_REVISION (1, 0) + +/** The interface for the MSCT Table Generator. +*/ +STATIC +CONST +ACPI_TABLE_GENERATOR MsctGenerator = { + // Generator ID + CREATE_STD_ACPI_TABLE_GEN_ID (EStdAcpiTableIdMsct), + // Generator Description + L"ACPI.STD.MSCT.GENERATOR", + // ACPI Table Signature + EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_SIGNATURE, + // ACPI Table Revision supported by this Generator + EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_REVISION, + // Minimum supported ACPI Table Revision + EFI_ACPI_6_5_MAXIMUM_SYSTEM_CHARACTERISTICS_TABLE_REVISION, + // Creator ID + TABLE_GENERATOR_CREATOR_ID, + // Creator Revision + MSCT_GENERATOR_REVISION, + // Build Table function + BuildMsctTable, + // Free Resource function + NULL, + // Extended build function not needed + NULL, + // Extended build function not implemented by the generator. + // Hence extended free resource function is not required. + NULL +}; + +/** AcpiMsctLib constructor + +@param[in] ImageHandle The firmware allocated handle for the EFI image. +@param[in] SystemTable A pointer to the EFI System Table. + +@retval EFI_SUCCESS The constructor always returns EFI_SUCCESS. +@retval Other Other EFI_STATUS error from called functions. + +**/ +EFI_STATUS +EFIAPI +AcpiMsctLibConstructor ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + + Status = RegisterAcpiTableGenerator (&MsctGenerator); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "MSCT: Register Generator Failed. Status = %r\n", + Status + )); + ASSERT_EFI_ERROR (Status); + } else { + DEBUG (( + DEBUG_INFO, + "MSCT: Register Generator. Status = %r\n", + Status + )); + } + + return Status; +} + +/** AcpiMsctLib destructor + +@param[in] ImageHandle The firmware allocated handle for the EFI image. +@param[in] SystemTable A pointer to the EFI System Table. + +@retval EFI_SUCCESS The destructor always returns EFI_SUCCESS. +@retval Other Other EFI_STATUS error from called functions. + +**/ +EFI_STATUS +EFIAPI +AcpiMsctLibDestructor ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + + Status = DeregisterAcpiTableGenerator (&MsctGenerator); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "MSCT: Deregister Generator Failed. Status = %r\n", + Status + )); + ASSERT_EFI_ERROR (Status); + } else { + DEBUG (( + DEBUG_INFO, + "MSCT: Deregister Generator. Status = %r\n", + Status + )); + } + + return Status; +} diff --git a/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/MsctGenerator.h b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/MsctGenerator.h new file mode 100644 index 0000000000..df81a4cbc4 --- /dev/null +++ b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/MsctGenerator.h @@ -0,0 +1,52 @@ +/** @file + ACPI Maximum System Characteristics Table (MSCT) Generator header file. + + Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#pragma once + +/** Get architecture processor domain information. + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. + @param [out] ProcDomainInfo Pointer to the processor domain information. + @param [out] ProcDomainInfoCount Pointer to the count of processor domain + information structures. + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + @retval Other Other EFI_STATUS error from called functions. +**/ +EFI_STATUS +EFIAPI +GetArchProcessorDomainInfo ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT UINT32 **ProcDomainInfo, + OUT UINT32 *ProcDomainInfoCount + ); + +/** Get the count of unique architecture clock domains. + + Enumerates all CPU interfaces and returns the number of unique clock + proximity domain IDs in the system. + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. + @param [out] ClockDomainCount On success, the cardinal count of unique + clock domains. + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + @retval Other Other EFI_STATUS error from called functions. +**/ +EFI_STATUS +EFIAPI +GetArchClockDomainInfo ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT UINT32 *ClockDomainCount + ); diff --git a/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/X64/X64MsctGenerator.c b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/X64/X64MsctGenerator.c new file mode 100644 index 0000000000..4fd58e45a9 --- /dev/null +++ b/DynamicTablesPkg/Library/Acpi/Common/AcpiMsctLib/X64/X64MsctGenerator.c @@ -0,0 +1,249 @@ +/** @file + X64 specific ACPI Maximum System Characteristics Table (MSCT) Generator + + Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include +#include +#include + +// Module specific include files. +#include +#include +#include +#include +#include +#include + +#include "MsctGenerator.h" + +/** + This macro is used to get the object information for the Local APIC X2APIC + Affinity object. +*/ +GET_OBJECT_LIST ( + EObjNameSpaceX64, + EX64ObjLocalApicX2ApicAffinityInfo, + CM_X64_LOCAL_APIC_X2APIC_AFFINITY_INFO + ); + +/** Get architecture processor domain information. + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. + @param [out] ProcDomainInfo Pointer to the processor domain information. + @param [out] ProcDomainInfoCount Pointer to the count of processor domain + information structures. + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + @retval Other Other EFI_STATUS error from called functions. +**/ +EFI_STATUS +EFIAPI +GetArchProcessorDomainInfo ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT UINT32 **ProcDomainInfo, + OUT UINT32 *ProcDomainInfoCount + ) +{ + CM_X64_LOCAL_APIC_X2APIC_AFFINITY_INFO *CmProcDomain; + EFI_STATUS Status; + UINT32 Index; + UINT32 ProcDomainCount; + UINT32 *ProcDomain; + + if ((CfgMgrProtocol == NULL) || + (ProcDomainInfo == NULL) || + (ProcDomainInfoCount == NULL)) + { + return EFI_INVALID_PARAMETER; + } + + CmProcDomain = NULL; + ProcDomain = NULL; + ProcDomainCount = 0; + Status = GetEX64ObjLocalApicX2ApicAffinityInfo ( + CfgMgrProtocol, + CM_NULL_TOKEN, + &CmProcDomain, + &ProcDomainCount + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "MSCT: Failed to get processor affinity information. Status = %r\n", + Status + )); + goto error_handler; + } + + if ((ProcDomainCount == 0) || (CmProcDomain == NULL)) { + DEBUG (( + DEBUG_ERROR, + "MSCT: No Local Apic/X2Apic Affinity information found.\n" + )); + Status = EFI_NOT_FOUND; + goto error_handler; + } + + ProcDomain = AllocateZeroPool ( + sizeof (UINT32) * + ProcDomainCount + ); + if (ProcDomain == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto error_handler; + } + + for (Index = 0; Index < ProcDomainCount; Index++) { + Status = GetProximityDomainId ( + CfgMgrProtocol, + CmProcDomain[Index].ProximityDomain, + CmProcDomain[Index].ProximityDomainToken, + &ProcDomain[Index] + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "MSCT: Failed to get Proximity Domain ID for index %d. Status = %r\n", + Index, + Status + )); + goto error_handler; + } + } + + *ProcDomainInfo = ProcDomain; + *ProcDomainInfoCount = ProcDomainCount; + return EFI_SUCCESS; + +error_handler: + if (ProcDomain != NULL) { + FreePool (ProcDomain); + } + + return Status; +} + +/** Get the count of unique architecture clock domains. + + Enumerates all Local APIC/X2APIC affinity entries and returns the number of + unique clock proximity domain IDs in the system. + + @param [in] CfgMgrProtocol Pointer to the Configuration Manager + Protocol. + @param [out] ClockDomainCount On success, the cardinal count of unique + clock domains. + + @retval EFI_SUCCESS Operation completed successfully. + @retval EFI_INVALID_PARAMETER A parameter is invalid. + @retval EFI_NOT_FOUND No affinity information was found. + @retval EFI_OUT_OF_RESOURCES Memory allocation failed. + @retval Other Other EFI_STATUS error from called functions. +**/ +EFI_STATUS +EFIAPI +GetArchClockDomainInfo ( + IN CONST EDKII_CONFIGURATION_MANAGER_PROTOCOL *CONST CfgMgrProtocol, + OUT UINT32 *ClockDomainCount + ) +{ + EFI_STATUS Status; + UINT32 ProximityDomain; + UINT32 CmCount; + UINT32 Index; + UINT32 UniqueIndex; + UINT32 UniqueCount; + UINT32 *ClockDomains; + BOOLEAN IsUnique; + CM_X64_LOCAL_APIC_X2APIC_AFFINITY_INFO *CmX2ApicAffinity; + + if ((CfgMgrProtocol == NULL) || + (ClockDomainCount == NULL)) + { + return EFI_INVALID_PARAMETER; + } + + CmX2ApicAffinity = NULL; + ClockDomains = NULL; + UniqueCount = 0; + CmCount = 0; + + Status = GetEX64ObjLocalApicX2ApicAffinityInfo ( + CfgMgrProtocol, + CM_NULL_TOKEN, + &CmX2ApicAffinity, + &CmCount + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Failed to get Local Apic/X2Apic Affinity Info. Status = %r\n", + Status + )); + return Status; + } + + if ((CmX2ApicAffinity == NULL) || (CmCount == 0)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Local Apic/X2Apic Affinity information not provided.\n" + )); + return EFI_NOT_FOUND; + } + + // Allocate worst-case storage: every CPU could be in its own clock domain. + ClockDomains = AllocateZeroPool (sizeof (UINT32) * CmCount); + if (ClockDomains == NULL) { + Status = EFI_OUT_OF_RESOURCES; + ASSERT_EFI_ERROR (Status); + return Status; + } + + for (Index = 0; Index < CmCount; Index++) { + Status = GetProximityDomainId ( + CfgMgrProtocol, + CmX2ApicAffinity[Index].ClockDomain, + CmX2ApicAffinity[Index].ClockDomainToken, + &ProximityDomain + ); + if (EFI_ERROR (Status)) { + DEBUG (( + DEBUG_ERROR, + "ERROR: MSCT: Failed to get Clock Domain ID for index %d. Status = %r\n", + Index, + Status + )); + ASSERT_EFI_ERROR (Status); + goto error_handler; + } + + // Record the proximity domain ID only if not already seen. + IsUnique = TRUE; + for (UniqueIndex = 0; UniqueIndex < UniqueCount; UniqueIndex++) { + if (ClockDomains[UniqueIndex] == ProximityDomain) { + IsUnique = FALSE; + break; + } + } + + if (IsUnique) { + ClockDomains[UniqueCount++] = ProximityDomain; + } + } + + *ClockDomainCount = UniqueCount; + Status = EFI_SUCCESS; + +error_handler: + if (ClockDomains != NULL) { + FreePool (ClockDomains); + } + + return Status; +}