/** @file

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

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

  @par Specification Reference:
  - Arm System Control and Management Interface - Platform Design Document
    (https://developer.arm.com/documentation/den0056/)
**/

#include <Base.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Protocol/ArmScmiBaseProtocol.h>
#include <Protocol/ArmScmiClockProtocol.h>
#include <Protocol/ArmScmiPerformanceProtocol.h>

#include "ArmScmiBaseProtocolPrivate.h"
#include "ArmScmiClockProtocolPrivate.h"
#include "ArmScmiPerformanceProtocolPrivate.h"
#include "ScmiDxe.h"
#include "ScmiPrivate.h"

STATIC CONST SCMI_PROTOCOL_ENTRY  Protocols[] = {
  { ScmiProtocolIdBase,        ScmiBaseProtocolInit        },
  { ScmiProtocolIdPerformance, ScmiPerformanceProtocolInit },
  { ScmiProtocolIdClock,       ScmiClockProtocolInit       }
};

/** ARM SCMI driver entry point function.

  This function installs the SCMI Base protocol and a list of other
  protocols is queried using the Base protocol. If protocol is supported,
  driver will call each protocol init function to install the protocol on
  the ImageHandle.

  @param[in] ImageHandle     Handle to this EFI Image which will be used to
                             install Base, Clock and Performance protocols.
  @param[in] SystemTable     A pointer to boot time system table.

  @retval EFI_SUCCESS       Driver initalized successfully.
  @retval EFI_UNSUPPORTED   If SCMI base protocol version is not supported.
  @retval !(EFI_SUCCESS)    Other errors.
**/
EFI_STATUS
EFIAPI
ArmScmiDxeEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS          Status;
  SCMI_BASE_PROTOCOL  *BaseProtocol;
  UINT32              Version;
  UINT32              Index;
  UINT32              NumProtocols;
  UINT32              ProtocolIndex;
  UINT8               *SupportedList;
  UINT32              SupportedListSize;

  // Every SCMI implementation must implement the base protocol.
  ASSERT (Protocols[0].Id == ScmiProtocolIdBase);

  Status = ScmiBaseProtocolInit (&ImageHandle);
  if (EFI_ERROR (Status)) {
    ASSERT (FALSE);
    return Status;
  }

  Status = gBS->LocateProtocol (
                  &gArmScmiBaseProtocolGuid,
                  NULL,
                  (VOID **)&BaseProtocol
                  );
  if (EFI_ERROR (Status)) {
    ASSERT (FALSE);
    return Status;
  }

  // Get SCMI Base protocol version.
  Status = BaseProtocol->GetVersion (BaseProtocol, &Version);
  if (EFI_ERROR (Status)) {
    ASSERT (FALSE);
    return Status;
  }

  // Accept any version between SCMI v1.0 and SCMI v2.0
  if ((Version < BASE_PROTOCOL_VERSION_V1) ||
      (Version > BASE_PROTOCOL_VERSION_V2))
  {
    ASSERT (FALSE);
    return EFI_UNSUPPORTED;
  }

  // Apart from Base protocol, SCMI may implement various other protocols,
  // query total protocols implemented by the SCP firmware.
  NumProtocols = 0;
  Status       = BaseProtocol->GetTotalProtocols (BaseProtocol, &NumProtocols);
  if (EFI_ERROR (Status)) {
    ASSERT (FALSE);
    return Status;
  }

  ASSERT (NumProtocols != 0);

  SupportedListSize = (NumProtocols * sizeof (*SupportedList));

  Status = gBS->AllocatePool (
                  EfiBootServicesData,
                  SupportedListSize,
                  (VOID **)&SupportedList
                  );
  if (EFI_ERROR (Status)) {
    ASSERT (FALSE);
    return Status;
  }

  // Get the list of protocols supported by SCP firmware on the platform.
  Status = BaseProtocol->DiscoverListProtocols (
                           BaseProtocol,
                           &SupportedListSize,
                           SupportedList
                           );
  if (EFI_ERROR (Status)) {
    gBS->FreePool (SupportedList);
    ASSERT (FALSE);
    return Status;
  }

  // Install supported protocol on ImageHandle.
  for (ProtocolIndex = 1; ProtocolIndex < ARRAY_SIZE (Protocols);
       ProtocolIndex++)
  {
    for (Index = 0; Index < NumProtocols; Index++) {
      if (Protocols[ProtocolIndex].Id == SupportedList[Index]) {
        Status = Protocols[ProtocolIndex].InitFn (&ImageHandle);
        if (EFI_ERROR (Status)) {
          ASSERT_EFI_ERROR (Status);
          return Status;
        }

        break;
      }
    }
  }

  gBS->FreePool (SupportedList);

  return EFI_SUCCESS;
}
