/** @file
  This library is used by FSP modules to measure data to TPM.

Copyright (c) 2020, Intel Corporation. All rights reserved. <BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <PiPei.h>
#include <Uefi.h>

#include <Library/BaseMemoryLib.h>
#include <Library/PeiServicesLib.h>
#include <Library/PeiServicesTablePointerLib.h>
#include <Library/PcdLib.h>
#include <Library/PrintLib.h>
#include <Library/DebugLib.h>
#include <Library/FspWrapperApiLib.h>
#include <Library/TpmMeasurementLib.h>
#include <Library/FspMeasurementLib.h>
#include <Library/TcgEventLogRecordLib.h>
#include <Library/HashLib.h>

#include <Ppi/Tcg.h>
#include <IndustryStandard/UefiTcgPlatform.h>

/**
  Tpm measure and log data, and extend the measurement result into a specific PCR.

  @param[in]  PcrIndex         PCR Index.
  @param[in]  EventType        Event type.
  @param[in]  EventLog         Measurement event log.
  @param[in]  LogLen           Event log length in bytes.
  @param[in]  HashData         The start of the data buffer to be hashed, extended.
  @param[in]  HashDataLen      The length, in bytes, of the buffer referenced by HashData
  @param[in]  Flags            Bitmap providing additional information.

  @retval EFI_SUCCESS           Operation completed successfully.
  @retval EFI_UNSUPPORTED       TPM device not available.
  @retval EFI_OUT_OF_RESOURCES  Out of memory.
  @retval EFI_DEVICE_ERROR      The operation was unsuccessful.
**/
EFI_STATUS
EFIAPI
TpmMeasureAndLogDataWithFlags (
  IN UINT32  PcrIndex,
  IN UINT32  EventType,
  IN VOID    *EventLog,
  IN UINT32  LogLen,
  IN VOID    *HashData,
  IN UINT64  HashDataLen,
  IN UINT64  Flags
  )
{
  EFI_STATUS         Status;
  EDKII_TCG_PPI      *TcgPpi;
  TCG_PCR_EVENT_HDR  TcgEventHdr;

  Status = PeiServicesLocatePpi (
             &gEdkiiTcgPpiGuid,
             0,
             NULL,
             (VOID **)&TcgPpi
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  TcgEventHdr.PCRIndex  = PcrIndex;
  TcgEventHdr.EventType = EventType;
  TcgEventHdr.EventSize = LogLen;

  Status = TcgPpi->HashLogExtendEvent (
                     TcgPpi,
                     Flags,
                     HashData,
                     (UINTN)HashDataLen,
                     &TcgEventHdr,
                     EventLog
                     );
  return Status;
}

/**
  Measure a FSP FirmwareBlob.

  @param[in]  Description             Description for this FirmwareBlob.
  @param[in]  FirmwareBlobBase        Base address of this FirmwareBlob.
  @param[in]  FirmwareBlobLength      Size in bytes of this FirmwareBlob.
  @param[in]  CfgRegionOffset         Configuration region offset in bytes.
  @param[in]  CfgRegionSize           Configuration region in bytes.

  @retval EFI_SUCCESS           Operation completed successfully.
  @retval EFI_UNSUPPORTED       TPM device not available.
  @retval EFI_OUT_OF_RESOURCES  Out of memory.
  @retval EFI_DEVICE_ERROR      The operation was unsuccessful.
**/
STATIC
EFI_STATUS
EFIAPI
MeasureFspFirmwareBlobWithCfg (
  IN CHAR8                 *Description OPTIONAL,
  IN EFI_PHYSICAL_ADDRESS  FirmwareBlobBase,
  IN UINT64                FirmwareBlobLength,
  IN UINT32                CfgRegionOffset,
  IN UINT32                CfgRegionSize
  )
{
  EFI_PLATFORM_FIRMWARE_BLOB      FvBlob, UpdBlob;
  PLATFORM_FIRMWARE_BLOB2_STRUCT  FvBlob2, UpdBlob2;
  VOID                            *FvName;
  UINT32                          FvEventType;
  VOID                            *FvEventLog, *UpdEventLog;
  UINT32                          FvEventLogSize, UpdEventLogSize;
  EFI_STATUS                      Status;
  HASH_HANDLE                     HashHandle;
  UINT8                           *HashBase;
  UINTN                           HashSize;
  TPML_DIGEST_VALUES              DigestList;

  FvName = TpmMeasurementGetFvName (FirmwareBlobBase, FirmwareBlobLength);

  if (((Description != NULL) || (FvName != NULL)) &&
      (PcdGet32 (PcdTcgPfpMeasurementRevision) >= TCG_EfiSpecIDEventStruct_SPEC_ERRATA_TPM2_REV_105))
  {
    if (Description != NULL) {
      AsciiSPrint ((CHAR8 *)FvBlob2.BlobDescription, sizeof (FvBlob2.BlobDescription), "%a", Description);
      AsciiSPrint ((CHAR8 *)UpdBlob2.BlobDescription, sizeof (UpdBlob2.BlobDescription), "%aUDP", Description);
    } else {
      AsciiSPrint ((CHAR8 *)FvBlob2.BlobDescription, sizeof (FvBlob2.BlobDescription), "Fv(%g)", FvName);
      AsciiSPrint ((CHAR8 *)UpdBlob2.BlobDescription, sizeof (UpdBlob2.BlobDescription), "(%g)UDP", FvName);
    }

    FvBlob2.BlobDescriptionSize = sizeof (FvBlob2.BlobDescription);
    FvBlob2.BlobBase            = FirmwareBlobBase;
    FvBlob2.BlobLength          = FirmwareBlobLength;
    FvEventType                 = EV_EFI_PLATFORM_FIRMWARE_BLOB2;
    FvEventLog                  = &FvBlob2;
    FvEventLogSize              = sizeof (FvBlob2);

    UpdBlob2.BlobDescriptionSize = sizeof (UpdBlob2.BlobDescription);
    UpdBlob2.BlobBase            = CfgRegionOffset;
    UpdBlob2.BlobLength          = CfgRegionSize;
    UpdEventLog                  = &UpdBlob2;
    UpdEventLogSize              = sizeof (UpdBlob2);
  } else {
    FvBlob.BlobBase   = FirmwareBlobBase;
    FvBlob.BlobLength = FirmwareBlobLength;
    FvEventType       = EV_EFI_PLATFORM_FIRMWARE_BLOB;
    FvEventLog        = &FvBlob;
    FvEventLogSize    = sizeof (FvBlob);

    UpdBlob.BlobBase   = CfgRegionOffset;
    UpdBlob.BlobLength = CfgRegionSize;
    UpdEventLog        = &UpdBlob;
    UpdEventLogSize    = sizeof (UpdBlob);
  }

  /** Initialize a SHA hash context. **/
  Status = HashStart (&HashHandle);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "HashStart failed - %r\n", Status));
    return Status;
  }

  /** Hash FSP binary before UDP **/
  HashBase = (UINT8 *)(UINTN)FirmwareBlobBase;
  HashSize = (UINTN)CfgRegionOffset;
  Status   = HashUpdate (HashHandle, HashBase, HashSize);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "HashUpdate failed - %r\n", Status));
    return Status;
  }

  /** Hash FSP binary after UDP **/
  HashBase = (UINT8 *)(UINTN)FirmwareBlobBase + CfgRegionOffset + CfgRegionSize;
  HashSize = (UINTN)(FirmwareBlobLength - CfgRegionOffset - CfgRegionSize);
  Status   = HashUpdate (HashHandle, HashBase, HashSize);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "HashUpdate failed - %r\n", Status));
    return Status;
  }

  /** Finalize the SHA hash. **/
  Status = HashCompleteAndExtend (HashHandle, 0, NULL, 0, &DigestList);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "HashCompleteAndExtend failed - %r\n", Status));
    return Status;
  }

  Status = TpmMeasureAndLogDataWithFlags (
             0,
             FvEventType,
             FvEventLog,
             FvEventLogSize,
             (UINT8 *)&DigestList,
             (UINTN)sizeof (DigestList),
             EDKII_TCG_PRE_HASH_LOG_ONLY
             );

  Status = TpmMeasureAndLogData (
             1,
             EV_PLATFORM_CONFIG_FLAGS,
             UpdEventLog,
             UpdEventLogSize,
             (UINT8 *)(UINTN)FirmwareBlobBase + CfgRegionOffset,
             CfgRegionSize
             );

  return Status;
}

/**
  Measure a FSP FirmwareBlob.

  @param[in]  PcrIndex                PCR Index.
  @param[in]  Description             Description for this FirmwareBlob.
  @param[in]  FirmwareBlobBase        Base address of this FirmwareBlob.
  @param[in]  FirmwareBlobLength      Size in bytes of this FirmwareBlob.

  @retval EFI_SUCCESS           Operation completed successfully.
  @retval EFI_UNSUPPORTED       TPM device not available.
  @retval EFI_OUT_OF_RESOURCES  Out of memory.
  @retval EFI_DEVICE_ERROR      The operation was unsuccessful.
**/
EFI_STATUS
EFIAPI
MeasureFspFirmwareBlob (
  IN UINT32                PcrIndex,
  IN CHAR8                 *Description OPTIONAL,
  IN EFI_PHYSICAL_ADDRESS  FirmwareBlobBase,
  IN UINT64                FirmwareBlobLength
  )
{
  UINT32           FspMeasureMask;
  FSP_INFO_HEADER  *FspHeaderPtr;

  FspMeasureMask = PcdGet32 (PcdFspMeasurementConfig);
  if ((FspMeasureMask & FSP_MEASURE_FSPUPD) != 0) {
    FspHeaderPtr = (FSP_INFO_HEADER *)FspFindFspHeader (FirmwareBlobBase);
    if (FspHeaderPtr != NULL) {
      return MeasureFspFirmwareBlobWithCfg (
               Description,
               FirmwareBlobBase,
               FirmwareBlobLength,
               FspHeaderPtr->CfgRegionOffset,
               FspHeaderPtr->CfgRegionSize
               );
    }
  }

  return MeasureFirmwareBlob (PcrIndex, Description, FirmwareBlobBase, FirmwareBlobLength);
}
