/** @file

  Copyright (c) 2017-2023, Arm Limited. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

  System Control and Management Interface V3.2, latest version at:
  - https://developer.arm.com/documentation/den0056/latest/

**/

#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/ArmScmiPerformanceProtocol.h>

#include "ArmScmiPerformanceProtocolPrivate.h"
#include "ScmiPrivate.h"

/** Return version of the performance management protocol supported by SCP.
   firmware.

  @param[in]  This      A Pointer to SCMI_PERFORMANCE_PROTOCOL Instance.

  @param[out] Version   Version of the supported SCMI performance management
                        protocol.

  @retval EFI_SUCCESS       The version is returned.
  @retval EFI_DEVICE_ERROR  SCP returns an SCMI error.
  @retval !(EFI_SUCCESS)    Other errors.
**/
STATIC
EFI_STATUS
PerformanceGetVersion (
  IN  SCMI_PERFORMANCE_PROTOCOL  *This,
  OUT UINT32                     *Version
  )
{
  return ScmiGetProtocolVersion (ScmiProtocolIdPerformance, Version);
}

/** Return protocol attributes of the performance management protocol.

  @param[in] This         A Pointer to SCMI_PERFORMANCE_PROTOCOL Instance.

  @param[out] Attributes  Protocol attributes.

  @retval EFI_SUCCESS       Protocol attributes are returned.
  @retval EFI_DEVICE_ERROR  SCP returns an SCMI error.
  @retval !(EFI_SUCCESS)    Other errors.
**/
STATIC
EFI_STATUS
PerformanceGetAttributes (
  IN  SCMI_PERFORMANCE_PROTOCOL             *This,
  OUT SCMI_PERFORMANCE_PROTOCOL_ATTRIBUTES  *Attributes
  )
{
  EFI_STATUS  Status;
  UINT32      *ReturnValues;

  Status = ScmiGetProtocolAttributes (
             ScmiProtocolIdPerformance,
             &ReturnValues
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  CopyMem (
    Attributes,
    ReturnValues,
    sizeof (SCMI_PERFORMANCE_PROTOCOL_ATTRIBUTES)
    );

  return EFI_SUCCESS;
}

/** Return performance domain attributes.

  @param[in]  This        A Pointer to SCMI_PERFORMANCE_PROTOCOL Instance.
  @param[in]  DomainId    Identifier for the performance domain.

  @param[out] Attributes  Performance domain attributes.

  @retval EFI_SUCCESS       Domain attributes are returned.
  @retval EFI_DEVICE_ERROR  SCP returns an SCMI error.
  @retval !(EFI_SUCCESS)    Other errors.
**/
STATIC
EFI_STATUS
PerformanceDomainAttributes (
  IN  SCMI_PERFORMANCE_PROTOCOL           *This,
  IN  UINT32                              DomainId,
  OUT SCMI_PERFORMANCE_DOMAIN_ATTRIBUTES  *DomainAttributes
  )
{
  EFI_STATUS    Status;
  UINT32        *MessageParams;
  UINT32        *ReturnValues;
  UINT32        PayloadLength;
  SCMI_COMMAND  Cmd;

  Status = ScmiCommandGetPayload (&MessageParams);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *MessageParams = DomainId;

  Cmd.ProtocolId = ScmiProtocolIdPerformance;
  Cmd.MessageId  = ScmiMessageIdPerformanceDomainAttributes;

  PayloadLength = sizeof (DomainId);

  Status = ScmiCommandExecute (
             &Cmd,
             &PayloadLength,
             &ReturnValues
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  CopyMem (
    DomainAttributes,
    ReturnValues,
    sizeof (SCMI_PERFORMANCE_DOMAIN_ATTRIBUTES)
    );

  return EFI_SUCCESS;
}

/** Return list of performance domain levels of a given domain.

  @param[in] This        A Pointer to SCMI_PERFORMANCE_PROTOCOL Instance.
  @param[in] DomainId    Identifier for the performance domain.

  @param[out] NumLevels   Total number of levels a domain can support.

  @param[in,out]  LevelArraySize Size of the performance level array.

  @param[out] LevelArray   Array of the performance levels.

  @retval EFI_SUCCESS          Domain levels are returned.
  @retval EFI_DEVICE_ERROR     SCP returns an SCMI error.
  @retval EFI_BUFFER_TOO_SMALL LevelArraySize is too small for the result.
                               It has been updated to the size needed.
  @retval !(EFI_SUCCESS)       Other errors.
**/
STATIC
EFI_STATUS
PerformanceDescribeLevels (
  IN     SCMI_PERFORMANCE_PROTOCOL  *This,
  IN     UINT32                     DomainId,
  OUT    UINT32                     *NumLevels,
  IN OUT UINT32                     *LevelArraySize,
  OUT    SCMI_PERFORMANCE_LEVEL     *LevelArray
  )
{
  EFI_STATUS    Status;
  UINT32        PayloadLength;
  SCMI_COMMAND  Cmd;
  UINT32        *MessageParams;
  UINT32        LevelIndex;
  UINT32        RequiredSize;
  UINT32        LevelNo;
  UINT32        ReturnNumLevels;
  UINT32        ReturnRemainNumLevels;

  PERF_DESCRIBE_LEVELS  *Levels;

  Status = ScmiCommandGetPayload (&MessageParams);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  LevelIndex   = 0;
  RequiredSize = 0;

  *MessageParams++ = DomainId;

  Cmd.ProtocolId = ScmiProtocolIdPerformance;
  Cmd.MessageId  = ScmiMessageIdPerformanceDescribeLevels;

  do {
    *MessageParams = LevelIndex;

    // Note, PayloadLength is an IN/OUT parameter.
    PayloadLength = sizeof (DomainId) + sizeof (LevelIndex);

    Status = ScmiCommandExecute (
               &Cmd,
               &PayloadLength,
               (UINT32 **)&Levels
               );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    ReturnNumLevels       = NUM_PERF_LEVELS (Levels->NumLevels);
    ReturnRemainNumLevels = NUM_REMAIN_PERF_LEVELS (Levels->NumLevels);

    if (RequiredSize == 0) {
      *NumLevels = ReturnNumLevels + ReturnRemainNumLevels;

      RequiredSize =  (*NumLevels) * sizeof (SCMI_PERFORMANCE_LEVEL);
      if (RequiredSize > (*LevelArraySize)) {
        // Update LevelArraySize with required size.
        *LevelArraySize = RequiredSize;
        return EFI_BUFFER_TOO_SMALL;
      }
    }

    for (LevelNo = 0; LevelNo < ReturnNumLevels; LevelNo++) {
      CopyMem (
        &LevelArray[LevelIndex++],
        &Levels->PerfLevel[LevelNo],
        sizeof (SCMI_PERFORMANCE_LEVEL)
        );
    }
  } while (ReturnRemainNumLevels != 0);

  *LevelArraySize = RequiredSize;

  return EFI_SUCCESS;
}

/** Set performance limits of a domain.

  @param[in] This        A Pointer to SCMI_PERFORMANCE_PROTOCOL Instance.
  @param[in] DomainId    Identifier for the performance domain.
  @param[in] Limit       Performance limit to set.

  @retval EFI_SUCCESS          Performance limits set successfully.
  @retval EFI_DEVICE_ERROR     SCP returns an SCMI error.
  @retval !(EFI_SUCCESS)       Other errors.
**/
EFI_STATUS
PerformanceLimitsSet (
  IN SCMI_PERFORMANCE_PROTOCOL  *This,
  IN UINT32                     DomainId,
  IN SCMI_PERFORMANCE_LIMITS    *Limits
  )
{
  EFI_STATUS    Status;
  UINT32        PayloadLength;
  SCMI_COMMAND  Cmd;
  UINT32        *MessageParams;

  Status = ScmiCommandGetPayload (&MessageParams);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *MessageParams++ = DomainId;
  *MessageParams++ = Limits->RangeMax;
  *MessageParams   = Limits->RangeMin;

  Cmd.ProtocolId = ScmiProtocolIdPerformance;
  Cmd.MessageId  = ScmiMessageIdPerformanceLimitsSet;

  PayloadLength = sizeof (DomainId) + sizeof (SCMI_PERFORMANCE_LIMITS);

  Status = ScmiCommandExecute (
             &Cmd,
             &PayloadLength,
             NULL
             );

  return Status;
}

/** Get performance limits of a domain.

  @param[in]  This        A Pointer to SCMI_PERFORMANCE_PROTOCOL Instance.
  @param[in]  DomainId    Identifier for the performance domain.

  @param[out] Limit       Performance Limits of the domain.

  @retval EFI_SUCCESS          Performance limits are returned.
  @retval EFI_DEVICE_ERROR     SCP returns an SCMI error.
  @retval !(EFI_SUCCESS)       Other errors.
**/
EFI_STATUS
PerformanceLimitsGet (
  SCMI_PERFORMANCE_PROTOCOL  *This,
  UINT32                     DomainId,
  SCMI_PERFORMANCE_LIMITS    *Limits
  )
{
  EFI_STATUS    Status;
  UINT32        PayloadLength;
  SCMI_COMMAND  Cmd;
  UINT32        *MessageParams;

  SCMI_PERFORMANCE_LIMITS  *ReturnValues;

  Status = ScmiCommandGetPayload (&MessageParams);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *MessageParams = DomainId;

  Cmd.ProtocolId = ScmiProtocolIdPerformance;
  Cmd.MessageId  = ScmiMessageIdPerformanceLimitsGet;

  PayloadLength = sizeof (DomainId);

  Status = ScmiCommandExecute (
             &Cmd,
             &PayloadLength,
             (UINT32 **)&ReturnValues
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Limits->RangeMax = ReturnValues->RangeMax;
  Limits->RangeMin = ReturnValues->RangeMin;

  return EFI_SUCCESS;
}

/** Set performance level of a domain.

  @param[in]  This        A Pointer to SCMI_PERFORMANCE_PROTOCOL Instance.
  @param[in]  DomainId    Identifier for the performance domain.
  @param[in]  Level       Performance level of the domain.

  @retval EFI_SUCCESS          Performance level set successfully.
  @retval EFI_DEVICE_ERROR     SCP returns an SCMI error.
  @retval !(EFI_SUCCESS)       Other errors.
**/
EFI_STATUS
PerformanceLevelSet (
  IN SCMI_PERFORMANCE_PROTOCOL  *This,
  IN UINT32                     DomainId,
  IN UINT32                     Level
  )
{
  EFI_STATUS    Status;
  UINT32        PayloadLength;
  SCMI_COMMAND  Cmd;
  UINT32        *MessageParams;

  Status = ScmiCommandGetPayload (&MessageParams);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *MessageParams++ = DomainId;
  *MessageParams   = Level;

  Cmd.ProtocolId = ScmiProtocolIdPerformance;
  Cmd.MessageId  = ScmiMessageIdPerformanceLevelSet;

  PayloadLength = sizeof (DomainId) + sizeof (Level);

  Status = ScmiCommandExecute (
             &Cmd,
             &PayloadLength,
             NULL
             );

  return Status;
}

/** Get performance level of a domain.

  @param[in]  This        A Pointer to SCMI_PERFORMANCE_PROTOCOL Instance.
  @param[in]  DomainId    Identifier for the performance domain.

  @param[out] Level       Performance level of the domain.

  @retval EFI_SUCCESS          Performance level got successfully.
  @retval EFI_DEVICE_ERROR     SCP returns an SCMI error.
  @retval !(EFI_SUCCESS)       Other errors.
**/
EFI_STATUS
PerformanceLevelGet (
  IN  SCMI_PERFORMANCE_PROTOCOL  *This,
  IN  UINT32                     DomainId,
  OUT UINT32                     *Level
  )
{
  EFI_STATUS    Status;
  UINT32        PayloadLength;
  SCMI_COMMAND  Cmd;
  UINT32        *ReturnValues;
  UINT32        *MessageParams;

  Status = ScmiCommandGetPayload (&MessageParams);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *MessageParams = DomainId;

  Cmd.ProtocolId = ScmiProtocolIdPerformance;
  Cmd.MessageId  = ScmiMessageIdPerformanceLevelGet;

  PayloadLength = sizeof (DomainId);

  Status = ScmiCommandExecute (
             &Cmd,
             &PayloadLength,
             &ReturnValues
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *Level = *ReturnValues;

  return EFI_SUCCESS;
}

/** Discover the attributes of the FastChannel for the specified
    performance domain and the specified message.

  @param[in]  This        A Pointer to SCMI_PERFORMANCE_PROTOCOL Instance.
  @param[in]  DomainId    Identifier for the performance domain.
  @param[in]  MessageId   Message Id of the FastChannel to discover.
                          Must be one of:
                           - PERFORMANCE_LIMITS_SET
                           - PERFORMANCE_LIMITS_GET
                           - PERFORMANCE_LEVEL_SET
                           - PERFORMANCE_LEVEL_GET
  @param[out] FastChannel If success, contains the FastChannel description.

  @retval EFI_SUCCESS             Performance level got successfully.
  @retval EFI_DEVICE_ERROR        SCP returns an SCMI error.
  @retval EFI_INVALID_PARAMETER   Invalid parameter.
  @retval EFI_TIMEOUT             Time out.
  @retval EFI_UNSUPPORTED         Unsupported.
**/
EFI_STATUS
DescribeFastchannel (
  IN  SCMI_PERFORMANCE_PROTOCOL     *This,
  IN  UINT32                        DomainId,
  IN  SCMI_MESSAGE_ID_PERFORMANCE   MessageId,
  OUT SCMI_PERFORMANCE_FASTCHANNEL  *FastChannel
  )
{
  EFI_STATUS    Status;
  SCMI_COMMAND  Cmd;
  UINT32        PayloadLength;
  UINT32        *ReturnValues;
  UINT32        *MessageParams;

  if ((This == NULL)  ||
      (FastChannel == NULL))
  {
    return EFI_INVALID_PARAMETER;
  }

  Status = ScmiCommandGetPayload (&MessageParams);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  *MessageParams++ = DomainId;
  *MessageParams   = MessageId;

  Cmd.ProtocolId = ScmiProtocolIdPerformance;
  Cmd.MessageId  = ScmiMessageIdPerformanceDescribeFastchannel;
  PayloadLength  = sizeof (DomainId) + sizeof (MessageId);

  Status = ScmiCommandExecute (
             &Cmd,
             &PayloadLength,
             &ReturnValues
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  CopyMem (
    FastChannel,
    ReturnValues,
    sizeof (SCMI_PERFORMANCE_FASTCHANNEL)
    );

  return Status;
}

// Instance of the SCMI performance management protocol.
STATIC CONST SCMI_PERFORMANCE_PROTOCOL  PerformanceProtocol = {
  PerformanceGetVersion,
  PerformanceGetAttributes,
  PerformanceDomainAttributes,
  PerformanceDescribeLevels,
  PerformanceLimitsSet,
  PerformanceLimitsGet,
  PerformanceLevelSet,
  PerformanceLevelGet,
  DescribeFastchannel,
};

/** Initialize performance management protocol and install on a given Handle.

  @param[in] Handle              Handle to install performance management
                                 protocol.

  @retval EFI_SUCCESS            Performance protocol installed successfully.
**/
EFI_STATUS
ScmiPerformanceProtocolInit (
  IN EFI_HANDLE  *Handle
  )
{
  return gBS->InstallMultipleProtocolInterfaces (
                Handle,
                &gArmScmiPerformanceProtocolGuid,
                &PerformanceProtocol,
                NULL
                );
}
