/** @file | |
Arm SCMI Info Library. | |
Copyright (c) 2022 - 2023, Arm Limited. All rights reserved.<BR> | |
Arm Functional Fixed Hardware Specification: | |
- https://developer.arm.com/documentation/den0048/latest/ | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Library/AcpiLib.h> | |
#include <Library/DynamicTablesScmiInfoLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Protocol/ArmScmi.h> | |
#include <Protocol/ArmScmiPerformanceProtocol.h> | |
/** Arm FFH registers | |
Cf. Arm Functional Fixed Hardware Specification | |
s3.2 Performance management and Collaborative Processor Performance Control | |
*/ | |
#define ARM_FFH_DELIVERED_PERF_COUNTER_REGISTER 0x0 | |
#define ARM_FFH_REFERENCE_PERF_COUNTER_REGISTER 0x1 | |
/// Arm SCMI performance protocol. | |
STATIC SCMI_PERFORMANCE_PROTOCOL *ScmiPerfProtocol; | |
/** Arm SCMI Info Library constructor. | |
@param ImageHandle Image of the loaded driver. | |
@param SystemTable Pointer to the System Table. | |
@retval EFI_SUCCESS Success. | |
@retval EFI_DEVICE_ERROR Device error. | |
@retval EFI_INVALID_PARAMETER Invalid parameter. | |
@retval EFI_NOT_FOUND Not Found | |
@retval EFI_TIMEOUT Timeout. | |
@retval EFI_UNSUPPORTED Unsupported. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DynamicTablesScmiInfoLibConstructor ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
UINT32 Version; | |
Status = gBS->LocateProtocol ( | |
&gArmScmiPerformanceProtocolGuid, | |
NULL, | |
(VOID **)&ScmiPerfProtocol | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = ScmiPerfProtocol->GetVersion (ScmiPerfProtocol, &Version); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// FastChannels were added in SCMI v2.0 spec. | |
if (Version < PERFORMANCE_PROTOCOL_VERSION_V2) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"DynamicTablesScmiInfoLib requires SCMI version > 2.0\n" | |
)); | |
return EFI_UNSUPPORTED; | |
} | |
return Status; | |
} | |
/** Get the OPPs/performance states of a power domain. | |
This function is a wrapper around the SCMI PERFORMANCE_DESCRIBE_LEVELS | |
command. The list of discrete performance states is returned in a buffer | |
that must be freed by the caller. | |
@param[in] DomainId Identifier for the performance domain. | |
@param[out] LevelArray If success, pointer to the list of list of | |
performance state. This memory must be freed by | |
the caller. | |
@param[out] LevelArrayCount If success, contains the number of states in | |
LevelArray. | |
@retval EFI_SUCCESS Success. | |
@retval EFI_DEVICE_ERROR Device error. | |
@retval EFI_INVALID_PARAMETER Invalid parameter. | |
@retval EFI_TIMEOUT Time out. | |
@retval EFI_UNSUPPORTED Unsupported. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
DynamicTablesScmiInfoDescribeLevels ( | |
IN UINT32 DomainId, | |
OUT SCMI_PERFORMANCE_LEVEL **LevelArray, | |
OUT UINT32 *LevelArrayCount | |
) | |
{ | |
EFI_STATUS Status; | |
SCMI_PERFORMANCE_LEVEL *Array; | |
UINT32 Count; | |
UINT32 Size; | |
if ((ScmiPerfProtocol == NULL) || | |
(LevelArray == NULL) || | |
(LevelArrayCount == NULL)) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
// First call to get the number of levels. | |
Size = 0; | |
Status = ScmiPerfProtocol->DescribeLevels ( | |
ScmiPerfProtocol, | |
DomainId, | |
&Count, | |
&Size, | |
NULL | |
); | |
if (Status != EFI_BUFFER_TOO_SMALL) { | |
// EFI_SUCCESS is not a valid option. | |
if (Status == EFI_SUCCESS) { | |
return EFI_INVALID_PARAMETER; | |
} else { | |
return Status; | |
} | |
} | |
Array = AllocateZeroPool (Size); | |
if (Array == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
// Second call to get the descriptions of the levels. | |
Status = ScmiPerfProtocol->DescribeLevels ( | |
ScmiPerfProtocol, | |
DomainId, | |
&Count, | |
&Size, | |
Array | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
*LevelArray = Array; | |
*LevelArrayCount = Count; | |
return Status; | |
} | |
/** Populate a AML_CPC_INFO object based on SCMI information. | |
@param[in] DomainId Identifier for the performance domain. | |
@param[out] CpcInfo If success, this structure was populated from | |
information queried to the SCP. | |
@retval EFI_SUCCESS Success. | |
@retval EFI_DEVICE_ERROR Device error. | |
@retval EFI_INVALID_PARAMETER Invalid parameter. | |
@retval EFI_TIMEOUT Time out. | |
@retval EFI_UNSUPPORTED Unsupported. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DynamicTablesScmiInfoGetFastChannel ( | |
IN UINT32 DomainId, | |
OUT AML_CPC_INFO *CpcInfo | |
) | |
{ | |
EFI_STATUS Status; | |
SCMI_PERFORMANCE_FASTCHANNEL FcLevelGet; | |
SCMI_PERFORMANCE_FASTCHANNEL FcLimitsSet; | |
SCMI_PERFORMANCE_DOMAIN_ATTRIBUTES DomainAttributes; | |
SCMI_PERFORMANCE_LEVEL *LevelArray; | |
UINT32 LevelCount; | |
UINT64 FcLevelGetAddr; | |
UINT64 FcLimitsMaxSetAddr; | |
UINT64 FcLimitsMinSetAddr; | |
if ((ScmiPerfProtocol == NULL) || | |
(CpcInfo == NULL)) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
Status = ScmiPerfProtocol->DescribeFastchannel ( | |
ScmiPerfProtocol, | |
DomainId, | |
ScmiMessageIdPerformanceLevelSet, | |
&FcLevelGet | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = ScmiPerfProtocol->DescribeFastchannel ( | |
ScmiPerfProtocol, | |
DomainId, | |
ScmiMessageIdPerformanceLimitsSet, | |
&FcLimitsSet | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = ScmiPerfProtocol->GetDomainAttributes ( | |
ScmiPerfProtocol, | |
DomainId, | |
&DomainAttributes | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = DynamicTablesScmiInfoDescribeLevels (DomainId, &LevelArray, &LevelCount); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
/* Do some safety checks. | |
Only support FastChannels (and not doorbells) as this is | |
the only mechanism supported by SCP. | |
FcLimits[Get|Set] require 2 UINT32 values (max, then min) and | |
FcLimits[Get|Set] require 1 UINT32 value (level). | |
*/ | |
if ((FcLevelGet.ChanSize != sizeof (UINT32)) || | |
((FcLevelGet.Attributes & SCMI_PERF_FC_ATTRIB_HAS_DOORBELL) == | |
SCMI_PERF_FC_ATTRIB_HAS_DOORBELL) || | |
(FcLimitsSet.ChanSize != 2 * sizeof (UINT32)) || | |
((FcLimitsSet.Attributes & SCMI_PERF_FC_ATTRIB_HAS_DOORBELL) == | |
SCMI_PERF_FC_ATTRIB_HAS_DOORBELL)) | |
{ | |
Status = EFI_INVALID_PARAMETER; | |
goto exit_handler; | |
} | |
FcLevelGetAddr = ((UINT64)FcLevelGet.ChanAddrHigh << 32) | | |
FcLevelGet.ChanAddrLow; | |
FcLimitsMaxSetAddr = ((UINT64)FcLimitsSet.ChanAddrHigh << 32) | | |
FcLimitsSet.ChanAddrLow; | |
FcLimitsMinSetAddr = FcLimitsMaxSetAddr + 0x4; | |
CpcInfo->Revision = EFI_ACPI_6_5_AML_CPC_REVISION; | |
CpcInfo->HighestPerformanceInteger = LevelArray[LevelCount - 1].Level; | |
CpcInfo->NominalPerformanceInteger = DomainAttributes.SustainedPerfLevel; | |
CpcInfo->LowestNonlinearPerformanceInteger = LevelArray[0].Level; | |
CpcInfo->LowestPerformanceInteger = LevelArray[0].Level; | |
CpcInfo->DesiredPerformanceRegister.AddressSpaceId = EFI_ACPI_6_5_SYSTEM_MEMORY; | |
CpcInfo->DesiredPerformanceRegister.RegisterBitWidth = 32; | |
CpcInfo->DesiredPerformanceRegister.RegisterBitOffset = 0; | |
CpcInfo->DesiredPerformanceRegister.AccessSize = EFI_ACPI_6_5_DWORD; | |
CpcInfo->DesiredPerformanceRegister.Address = FcLevelGetAddr; | |
CpcInfo->MinimumPerformanceRegister.AddressSpaceId = EFI_ACPI_6_5_SYSTEM_MEMORY; | |
CpcInfo->MinimumPerformanceRegister.RegisterBitWidth = 32; | |
CpcInfo->MinimumPerformanceRegister.RegisterBitOffset = 0; | |
CpcInfo->MinimumPerformanceRegister.AccessSize = EFI_ACPI_6_5_DWORD; | |
CpcInfo->MinimumPerformanceRegister.Address = FcLimitsMinSetAddr; | |
CpcInfo->MaximumPerformanceRegister.AddressSpaceId = EFI_ACPI_6_5_SYSTEM_MEMORY; | |
CpcInfo->MaximumPerformanceRegister.RegisterBitWidth = 32; | |
CpcInfo->MaximumPerformanceRegister.RegisterBitOffset = 0; | |
CpcInfo->MaximumPerformanceRegister.AccessSize = EFI_ACPI_6_5_DWORD; | |
CpcInfo->MaximumPerformanceRegister.Address = FcLimitsMaxSetAddr; | |
CpcInfo->ReferencePerformanceCounterRegister.AddressSpaceId = EFI_ACPI_6_5_FUNCTIONAL_FIXED_HARDWARE; | |
CpcInfo->ReferencePerformanceCounterRegister.RegisterBitWidth = 0x40; | |
CpcInfo->ReferencePerformanceCounterRegister.RegisterBitOffset = 0; | |
CpcInfo->ReferencePerformanceCounterRegister.AccessSize = ARM_FFH_REFERENCE_PERF_COUNTER_REGISTER; | |
CpcInfo->ReferencePerformanceCounterRegister.Address = 0x4; | |
CpcInfo->DeliveredPerformanceCounterRegister.AddressSpaceId = EFI_ACPI_6_5_FUNCTIONAL_FIXED_HARDWARE; | |
CpcInfo->DeliveredPerformanceCounterRegister.RegisterBitWidth = 0x40; | |
CpcInfo->DeliveredPerformanceCounterRegister.RegisterBitOffset = 0; | |
CpcInfo->DeliveredPerformanceCounterRegister.AccessSize = ARM_FFH_DELIVERED_PERF_COUNTER_REGISTER; | |
CpcInfo->DeliveredPerformanceCounterRegister.Address = 0x4; | |
// SCMI should advertise performance values on a unified scale. So frequency | |
// values are not available. LowestFrequencyInteger and | |
// NominalFrequencyInteger are populated in the ConfigurationManager. | |
exit_handler: | |
FreePool (LevelArray); | |
return Status; | |
} |