/** @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; }