/** @file

  Copyright (c) 2017-2018, Arm Limited. All rights reserved.

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

  System Control and Management Interface V1.0
    http://infocenter.arm.com/help/topic/com.arm.doc.den0056a/
    DEN0056A_System_Control_and_Management_Interface.pdf
**/

#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/ArmScmiBaseProtocol.h>

#include "ArmScmiBaseProtocolPrivate.h"
#include "ScmiPrivate.h"

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

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

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

  @retval EFI_SUCCESS       The version of the protocol is returned.
  @retval EFI_DEVICE_ERROR  SCP returns an SCMI error.
  @retval !(EFI_SUCCESS)    Other errors.
**/
STATIC
EFI_STATUS
BaseGetVersion (
  IN  SCMI_BASE_PROTOCOL  *This,
  OUT UINT32              *Version
  )
{
  return ScmiGetProtocolVersion (SCMI_PROTOCOL_ID_BASE, Version);
}

/** Return total number of SCMI protocols supported by the SCP firmware.

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

  @param[out] TotalProtocols Total number of SCMI protocols supported.

  @retval EFI_SUCCESS       Total number of protocols supported are returned.
  @retval EFI_DEVICE_ERROR  SCP returns a SCMI error.
  @retval !(EFI_SUCCESS)    Other errors.
**/
STATIC
EFI_STATUS
BaseGetTotalProtocols (
  IN  SCMI_BASE_PROTOCOL  *This,
  OUT UINT32              *TotalProtocols
  )
{
  EFI_STATUS  Status;
  UINT32      *ReturnValues;

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

  *TotalProtocols = SCMI_TOTAL_PROTOCOLS (ReturnValues[0]);

  return EFI_SUCCESS;
}

/** Common function which returns vendor details.

  @param[in] MessageId       SCMI_MESSAGE_ID_BASE_DISCOVER_VENDOR
                             OR
                             SCMI_MESSAGE_ID_BASE_DISCOVER_SUB_VENDOR

  @param[out] VendorIdentifier ASCII name of the vendor/subvendor.

  @retval EFI_SUCCESS       VendorIdentifier is returned.
  @retval EFI_DEVICE_ERROR  SCP returns an SCMI error.
  @retval !(EFI_SUCCESS)    Other errors.
**/
STATIC
EFI_STATUS
BaseDiscoverVendorDetails (
  IN  SCMI_MESSAGE_ID_BASE  MessageId,
  OUT UINT8                 VendorIdentifier[SCMI_MAX_STR_LEN]
  )
{
  EFI_STATUS    Status;
  UINT32        *ReturnValues;
  SCMI_COMMAND  Cmd;
  UINT32        PayloadLength;

  Cmd.ProtocolId = SCMI_PROTOCOL_ID_BASE;
  Cmd.MessageId  = MessageId;

  PayloadLength = 0;

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

  AsciiStrCpyS (
    (CHAR8*)VendorIdentifier,
    SCMI_MAX_STR_LEN,
    (CONST CHAR8*)ReturnValues
    );

  return EFI_SUCCESS;
}

/** Return vendor name.

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

  @param[out] VendorIdentifier Null terminated ASCII string of up to
                               16 bytes with a vendor name.

  @retval EFI_SUCCESS       VendorIdentifier is returned.
  @retval EFI_DEVICE_ERROR  SCP returns a SCMI error.
  @retval !(EFI_SUCCESS)    Other errors.
**/
STATIC
EFI_STATUS
BaseDiscoverVendor (
  IN  SCMI_BASE_PROTOCOL  *This,
  OUT UINT8               VendorIdentifier[SCMI_MAX_STR_LEN]
  )
{
  return BaseDiscoverVendorDetails (
           SCMI_MESSAGE_ID_BASE_DISCOVER_VENDOR,
           VendorIdentifier
           );
}

/** Return sub vendor name.

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

  @param[out] VendorIdentifier Null terminated ASCII string of up to
                               16 bytes with a sub vendor name.

  @retval EFI_SUCCESS       VendorIdentifier is returned.
  @retval EFI_DEVICE_ERROR  SCP returns a SCMI error.
  @retval !(EFI_SUCCESS)    Other errors.
**/
EFI_STATUS
BaseDiscoverSubVendor (
  IN  SCMI_BASE_PROTOCOL  *This,
  OUT UINT8               VendorIdentifier[SCMI_MAX_STR_LEN]
  )
{
  return BaseDiscoverVendorDetails (
           SCMI_MESSAGE_ID_BASE_DISCOVER_SUB_VENDOR,
           VendorIdentifier
           );
}

/** Return implementation version.

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

  @param[out] ImplementationVersion Vendor specific implementation version.

  @retval EFI_SUCCESS       Implementation version is returned.
  @retval EFI_DEVICE_ERROR  SCP returns a SCMI error.
  @retval !(EFI_SUCCESS)    Other errors.
**/
STATIC
EFI_STATUS
BaseDiscoverImplVersion (
  IN  SCMI_BASE_PROTOCOL  *This,
  OUT UINT32              *ImplementationVersion
  )
{
  EFI_STATUS    Status;
  UINT32        *ReturnValues;
  SCMI_COMMAND  Cmd;
  UINT32        PayloadLength;

  Cmd.ProtocolId = SCMI_PROTOCOL_ID_BASE;
  Cmd.MessageId  = SCMI_MESSAGE_ID_BASE_DISCOVER_IMPLEMENTATION_VERSION;

  PayloadLength = 0;

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

  *ImplementationVersion = ReturnValues[0];

  return EFI_SUCCESS;
}

/** Return list of protocols.

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

  @param[out] ProtocolListSize  Size of the ProtocolList.

  @param[out] ProtocolList   Protocol list.

  @retval EFI_SUCCESS          List of protocols is returned.
  @retval EFI_BUFFER_TOO_SMALL ProtocolListSize is too small for the result.
                                It has been updated to the size needed.
  @retval EFI_DEVICE_ERROR     SCP returns a SCMI error.
  @retval !(EFI_SUCCESS)       Other errors.
**/
STATIC
EFI_STATUS
BaseDiscoverListProtocols (
  IN     SCMI_BASE_PROTOCOL  *This,
  IN OUT UINT32              *ProtocolListSize,
  OUT    UINT8               *ProtocolList
  )
{
  EFI_STATUS          Status;
  UINT32              TotalProtocols;
  UINT32              *MessageParams;
  BASE_DISCOVER_LIST  *DiscoverList;
  UINT32              Skip;
  UINT32              Index;
  SCMI_COMMAND        Cmd;
  UINT32              PayloadLength;
  UINT32              RequiredSize;

  Status = BaseGetTotalProtocols (This, &TotalProtocols);
  if (EFI_ERROR (Status)) {
    return Status;
  }

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

  RequiredSize = sizeof (UINT8) * TotalProtocols;
  if (*ProtocolListSize < RequiredSize) {
    *ProtocolListSize = RequiredSize;
    return EFI_BUFFER_TOO_SMALL;
  }

  Cmd.ProtocolId = SCMI_PROTOCOL_ID_BASE;
  Cmd.MessageId  = SCMI_MESSAGE_ID_BASE_DISCOVER_LIST_PROTOCOLS;

  Skip = 0;

  while (Skip < TotalProtocols) {

    *MessageParams = Skip;

    // Note PayloadLength is a IN/OUT parameter.
    PayloadLength = sizeof (Skip);

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

    for (Index = 0; Index < DiscoverList->NumProtocols; Index++) {
      ProtocolList[Skip++] = DiscoverList->Protocols[Index];
    }
  }

  *ProtocolListSize = RequiredSize;

  return EFI_SUCCESS;
}

// Instance of the SCMI Base protocol.
STATIC CONST SCMI_BASE_PROTOCOL BaseProtocol = {
  BaseGetVersion,
  BaseGetTotalProtocols,
  BaseDiscoverVendor,
  BaseDiscoverSubVendor,
  BaseDiscoverImplVersion,
  BaseDiscoverListProtocols
};

/** Initialize Base protocol and install protocol on a given handle.

   @param[in] Handle              Handle to install Base protocol.

   @retval EFI_SUCCESS            Base protocol interface installed
                                  successfully.
**/
EFI_STATUS
ScmiBaseProtocolInit (
  IN OUT EFI_HANDLE* Handle
  )
{
  return gBS->InstallMultipleProtocolInterfaces (
                Handle,
                &gArmScmiBaseProtocolGuid,
                &BaseProtocol,
                NULL
                );
}
