/** @file
  HII Config Access protocol implementation of TCG2 configuration module.
  NOTE: This module is only for reference only, each platform should have its own setup page.

Copyright (c) 2015 - 2018, Intel Corporation. All rights reserved.<BR>
(C) Copyright 2018 Hewlett Packard Enterprise Development LP<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "Tcg2ConfigImpl.h"
#include <Library/PcdLib.h>
#include <Library/Tpm2CommandLib.h>
#include <Library/Tpm2DeviceLib.h>
#include <Library/IoLib.h>

#include <Guid/TpmInstance.h>

#include <IndustryStandard/TpmPtp.h>

#define EFI_TCG2_EVENT_LOG_FORMAT_ALL  (EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2 | EFI_TCG2_EVENT_LOG_FORMAT_TCG_2)

TPM_INSTANCE_ID  mTpmInstanceId[TPM_DEVICE_MAX + 1] = TPM_INSTANCE_ID_LIST;

TCG2_CONFIG_PRIVATE_DATA  *mTcg2ConfigPrivateData;
TCG2_CONFIG_PRIVATE_DATA  mTcg2ConfigPrivateDataTemplate = {
  TCG2_CONFIG_PRIVATE_DATA_SIGNATURE,
  {
    Tcg2ExtractConfig,
    Tcg2RouteConfig,
    Tcg2Callback
  }
};

HII_VENDOR_DEVICE_PATH  mTcg2HiiVendorDevicePath = {
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_VENDOR_DP,
      {
        (UINT8)(sizeof (VENDOR_DEVICE_PATH)),
        (UINT8)((sizeof (VENDOR_DEVICE_PATH)) >> 8)
      }
    },
    TCG2_CONFIG_FORM_SET_GUID
  },
  {
    END_DEVICE_PATH_TYPE,
    END_ENTIRE_DEVICE_PATH_SUBTYPE,
    {
      (UINT8)(END_DEVICE_PATH_LENGTH),
      (UINT8)((END_DEVICE_PATH_LENGTH) >> 8)
    }
  }
};

UINT8  mCurrentPpRequest;

/**
  Return if PTP CRB is supported.

  @param[in] Register                Pointer to PTP register.

  @retval TRUE  PTP CRB is supported.
  @retval FALSE PTP CRB is unsupported.
**/
BOOLEAN
IsPtpCrbSupported (
  IN VOID  *Register
  )
{
  PTP_CRB_INTERFACE_IDENTIFIER  InterfaceId;

  //
  // Check interface id
  //
  InterfaceId.Uint32 = MmioRead32 ((UINTN)&((PTP_CRB_REGISTERS *)Register)->InterfaceId);

  if (((InterfaceId.Bits.InterfaceType == PTP_INTERFACE_IDENTIFIER_INTERFACE_TYPE_CRB) ||
       (InterfaceId.Bits.InterfaceType == PTP_INTERFACE_IDENTIFIER_INTERFACE_TYPE_FIFO)) &&
      (InterfaceId.Bits.CapCRB != 0))
  {
    return TRUE;
  }

  return FALSE;
}

/**
  Return if PTP FIFO is supported.

  @param[in] Register                Pointer to PTP register.

  @retval TRUE  PTP FIFO is supported.
  @retval FALSE PTP FIFO is unsupported.
**/
BOOLEAN
IsPtpFifoSupported (
  IN VOID  *Register
  )
{
  PTP_CRB_INTERFACE_IDENTIFIER  InterfaceId;

  //
  // Check interface id
  //
  InterfaceId.Uint32 = MmioRead32 ((UINTN)&((PTP_CRB_REGISTERS *)Register)->InterfaceId);

  if (((InterfaceId.Bits.InterfaceType == PTP_INTERFACE_IDENTIFIER_INTERFACE_TYPE_CRB) ||
       (InterfaceId.Bits.InterfaceType == PTP_INTERFACE_IDENTIFIER_INTERFACE_TYPE_FIFO)) &&
      (InterfaceId.Bits.CapFIFO != 0))
  {
    return TRUE;
  }

  return FALSE;
}

/**
  Set PTP interface type.
  Do not update PcdActiveTpmInterfaceType here because interface change only happens on next _TPM_INIT

  @param[in] Register                Pointer to PTP register.
  @param[in] PtpInterface            PTP interface type.

  @retval EFI_SUCCESS                PTP interface type is set.
  @retval EFI_INVALID_PARAMETER      PTP interface type is invalid.
  @retval EFI_UNSUPPORTED            PTP interface type is unsupported.
  @retval EFI_WRITE_PROTECTED        PTP interface is locked.
**/
EFI_STATUS
SetPtpInterface (
  IN VOID   *Register,
  IN UINT8  PtpInterface
  )
{
  TPM2_PTP_INTERFACE_TYPE       PtpInterfaceCurrent;
  PTP_CRB_INTERFACE_IDENTIFIER  InterfaceId;

  PtpInterfaceCurrent = PcdGet8 (PcdActiveTpmInterfaceType);
  if ((PtpInterfaceCurrent != Tpm2PtpInterfaceFifo) &&
      (PtpInterfaceCurrent != Tpm2PtpInterfaceCrb))
  {
    return EFI_UNSUPPORTED;
  }

  InterfaceId.Uint32 = MmioRead32 ((UINTN)&((PTP_CRB_REGISTERS *)Register)->InterfaceId);
  if (InterfaceId.Bits.IntfSelLock != 0) {
    return EFI_WRITE_PROTECTED;
  }

  switch (PtpInterface) {
    case Tpm2PtpInterfaceFifo:
      if (InterfaceId.Bits.CapFIFO == 0) {
        return EFI_UNSUPPORTED;
      }

      InterfaceId.Bits.InterfaceSelector = PTP_INTERFACE_IDENTIFIER_INTERFACE_SELECTOR_FIFO;
      MmioWrite32 ((UINTN)&((PTP_CRB_REGISTERS *)Register)->InterfaceId, InterfaceId.Uint32);
      return EFI_SUCCESS;
    case Tpm2PtpInterfaceCrb:
      if (InterfaceId.Bits.CapCRB == 0) {
        return EFI_UNSUPPORTED;
      }

      InterfaceId.Bits.InterfaceSelector = PTP_INTERFACE_IDENTIFIER_INTERFACE_SELECTOR_CRB;
      MmioWrite32 ((UINTN)&((PTP_CRB_REGISTERS *)Register)->InterfaceId, InterfaceId.Uint32);
      return EFI_SUCCESS;
    default:
      return EFI_INVALID_PARAMETER;
  }
}

/**
  This function allows a caller to extract the current configuration for one
  or more named elements from the target driver.

  @param[in]   This              Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param[in]   Request           A null-terminated Unicode string in
                                 <ConfigRequest> format.
  @param[out]  Progress          On return, points to a character in the Request
                                 string. Points to the string's null terminator if
                                 request was successful. Points to the most recent
                                 '&' before the first failing name/value pair (or
                                 the beginning of the string if the failure is in
                                 the first name/value pair) if the request was not
                                 successful.
  @param[out]  Results           A null-terminated Unicode string in
                                 <ConfigAltResp> format which has all values filled
                                 in for the names in the Request string. String to
                                 be allocated by the called function.

  @retval EFI_SUCCESS            The Results is filled with the requested values.
  @retval EFI_OUT_OF_RESOURCES   Not enough memory to store the results.
  @retval EFI_INVALID_PARAMETER  Request is illegal syntax, or unknown name.
  @retval EFI_NOT_FOUND          Routing data doesn't match any storage in this
                                 driver.

**/
EFI_STATUS
EFIAPI
Tcg2ExtractConfig (
  IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL  *This,
  IN CONST EFI_STRING                      Request,
  OUT EFI_STRING                           *Progress,
  OUT EFI_STRING                           *Results
  )
{
  if ((Progress == NULL) || (Results == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  *Progress = Request;
  return EFI_NOT_FOUND;
}

/**
  Save TPM request to variable space.

  @param[in] PpRequest             Physical Presence request command.

  @retval    EFI_SUCCESS           The operation is finished successfully.
  @retval    Others                Other errors as indicated.

**/
EFI_STATUS
SaveTcg2PpRequest (
  IN UINT8  PpRequest
  )
{
  UINT32      ReturnCode;
  EFI_STATUS  Status;

  ReturnCode = Tcg2PhysicalPresenceLibSubmitRequestToPreOSFunction (PpRequest, 0);
  if (ReturnCode == TCG_PP_SUBMIT_REQUEST_TO_PREOS_SUCCESS) {
    mCurrentPpRequest = PpRequest;
    Status            = EFI_SUCCESS;
  } else if (ReturnCode == TCG_PP_SUBMIT_REQUEST_TO_PREOS_GENERAL_FAILURE) {
    Status = EFI_OUT_OF_RESOURCES;
  } else if (ReturnCode == TCG_PP_SUBMIT_REQUEST_TO_PREOS_NOT_IMPLEMENTED) {
    Status = EFI_UNSUPPORTED;
  } else {
    Status = EFI_DEVICE_ERROR;
  }

  return Status;
}

/**
  Save TPM request to variable space.

  @param[in] PpRequestParameter    Physical Presence request parameter.

  @retval    EFI_SUCCESS           The operation is finished successfully.
  @retval    Others                Other errors as indicated.

**/
EFI_STATUS
SaveTcg2PpRequestParameter (
  IN UINT32  PpRequestParameter
  )
{
  UINT32      ReturnCode;
  EFI_STATUS  Status;

  ReturnCode = Tcg2PhysicalPresenceLibSubmitRequestToPreOSFunction (mCurrentPpRequest, PpRequestParameter);
  if (ReturnCode == TCG_PP_SUBMIT_REQUEST_TO_PREOS_SUCCESS) {
    Status = EFI_SUCCESS;
  } else if (ReturnCode == TCG_PP_SUBMIT_REQUEST_TO_PREOS_GENERAL_FAILURE) {
    Status = EFI_OUT_OF_RESOURCES;
  } else if (ReturnCode == TCG_PP_SUBMIT_REQUEST_TO_PREOS_NOT_IMPLEMENTED) {
    Status = EFI_UNSUPPORTED;
  } else {
    Status = EFI_DEVICE_ERROR;
  }

  return Status;
}

/**
  Save Tcg2 PCR Banks request request to variable space.

  @param[in] PCRBankIndex     PCR Bank Index.
  @param[in] Enable           Enable or disable this PCR Bank.

  @retval    EFI_SUCCESS           The operation is finished successfully.
  @retval    Others                Other errors as indicated.

**/
EFI_STATUS
SaveTcg2PCRBanksRequest (
  IN UINTN    PCRBankIndex,
  IN BOOLEAN  Enable
  )
{
  UINT32      ReturnCode;
  EFI_STATUS  Status;

  if (Enable) {
    mTcg2ConfigPrivateData->PCRBanksDesired |= (0x1 << PCRBankIndex);
  } else {
    mTcg2ConfigPrivateData->PCRBanksDesired &= ~(0x1 << PCRBankIndex);
  }

  ReturnCode = Tcg2PhysicalPresenceLibSubmitRequestToPreOSFunction (TCG2_PHYSICAL_PRESENCE_SET_PCR_BANKS, mTcg2ConfigPrivateData->PCRBanksDesired);
  if (ReturnCode == TCG_PP_SUBMIT_REQUEST_TO_PREOS_SUCCESS) {
    Status = EFI_SUCCESS;
  } else if (ReturnCode == TCG_PP_SUBMIT_REQUEST_TO_PREOS_GENERAL_FAILURE) {
    Status = EFI_OUT_OF_RESOURCES;
  } else if (ReturnCode == TCG_PP_SUBMIT_REQUEST_TO_PREOS_NOT_IMPLEMENTED) {
    Status = EFI_UNSUPPORTED;
  } else {
    Status = EFI_DEVICE_ERROR;
  }

  return Status;
}

/**
  This function processes the results of changes in configuration.

  @param[in]  This               Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param[in]  Configuration      A null-terminated Unicode string in <ConfigResp>
                                 format.
  @param[out] Progress           A pointer to a string filled in with the offset of
                                 the most recent '&' before the first failing
                                 name/value pair (or the beginning of the string if
                                 the failure is in the first name/value pair) or
                                 the terminating NULL if all was successful.

  @retval EFI_SUCCESS            The Results is processed successfully.
  @retval EFI_INVALID_PARAMETER  Configuration is NULL.
  @retval EFI_NOT_FOUND          Routing data doesn't match any storage in this
                                 driver.

**/
EFI_STATUS
EFIAPI
Tcg2RouteConfig (
  IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL  *This,
  IN CONST EFI_STRING                      Configuration,
  OUT EFI_STRING                           *Progress
  )
{
  if ((Configuration == NULL) || (Progress == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  *Progress = Configuration;

  return EFI_NOT_FOUND;
}

/**
  Get HID string of TPM2 ACPI device object

  @param[in]  Hid               Points to HID String Buffer.
  @param[in]  Size              HID String size in bytes. Must >= TPM_HID_ACPI_SIZE

  @return                       HID String get status.

**/
EFI_STATUS
GetTpm2HID (
  CHAR8  *Hid,
  UINTN  Size
  )
{
  EFI_STATUS  Status;
  UINT32      ManufacturerID;
  UINT32      FirmwareVersion1;
  UINT32      FirmwareVersion2;
  BOOLEAN     PnpHID;

  PnpHID = TRUE;

  ZeroMem (Hid, Size);

  //
  // Get Manufacturer ID
  //
  Status = Tpm2GetCapabilityManufactureID (&ManufacturerID);
  if (!EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "TPM_PT_MANUFACTURER 0x%08x\n", ManufacturerID));
    //
    // ManufacturerID defined in TCG Vendor ID Registry
    // may tailed with 0x00 or 0x20
    //
    if (((ManufacturerID >> 24) == 0x00) || ((ManufacturerID >> 24) == 0x20)) {
      //
      //  HID containing PNP ID "NNN####"
      //   NNN is uppercase letter for Vendor ID specified by manufacturer
      //
      CopyMem (Hid, &ManufacturerID, 3);
    } else {
      //
      //  HID containing ACP ID "NNNN####"
      //   NNNN is uppercase letter for Vendor ID specified by manufacturer
      //
      CopyMem (Hid, &ManufacturerID, 4);
      PnpHID = FALSE;
    }
  } else {
    DEBUG ((DEBUG_ERROR, "Get TPM_PT_MANUFACTURER failed %x!\n", Status));
    ASSERT (FALSE);
    return Status;
  }

  Status = Tpm2GetCapabilityFirmwareVersion (&FirmwareVersion1, &FirmwareVersion2);
  if (!EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "TPM_PT_FIRMWARE_VERSION_1 0x%x\n", FirmwareVersion1));
    DEBUG ((DEBUG_INFO, "TPM_PT_FIRMWARE_VERSION_2 0x%x\n", FirmwareVersion2));
    //
    //   #### is Firmware Version 1
    //
    if (PnpHID) {
      AsciiSPrint (Hid + 3, TPM_HID_PNP_SIZE - 3, "%02d%02d", ((FirmwareVersion1 & 0xFFFF0000) >> 16), (FirmwareVersion1 & 0x0000FFFF));
    } else {
      AsciiSPrint (Hid + 4, TPM_HID_ACPI_SIZE - 4, "%02d%02d", ((FirmwareVersion1 & 0xFFFF0000) >> 16), (FirmwareVersion1 & 0x0000FFFF));
    }
  } else {
    DEBUG ((DEBUG_ERROR, "Get TPM_PT_FIRMWARE_VERSION_X failed %x!\n", Status));
    ASSERT (FALSE);
    return Status;
  }

  return EFI_SUCCESS;
}

/**
  This function processes the results of changes in configuration
  for TCG2 version information.

  @param[in] Action             Specifies the type of action taken by the browser.
                                ASSERT if the Action is not EFI_BROWSER_ACTION_SUBMITTED.
  @param[in] QuestionId         A unique value which is sent to the original
                                exporting driver so that it can identify the type
                                of data to expect.
  @param[in] Type               The type of value for the question.
  @param[in] Value              A pointer to the data being sent to the original
                                exporting driver.

  @retval EFI_SUCCESS           The callback successfully handled the action.

**/
EFI_STATUS
Tcg2VersionInfoCallback (
  IN EFI_BROWSER_ACTION  Action,
  IN EFI_QUESTION_ID     QuestionId,
  IN UINT8               Type,
  IN EFI_IFR_TYPE_VALUE  *Value
  )
{
  EFI_INPUT_KEY  Key;
  UINT64         PcdTcg2PpiVersion;
  UINT8          PcdTpm2AcpiTableRev;

  ASSERT (Action == EFI_BROWSER_ACTION_SUBMITTED);

  if (QuestionId == KEY_TCG2_PPI_VERSION) {
    //
    // Get the PCD value after EFI_BROWSER_ACTION_SUBMITTED,
    // the SetVariable to TCG2_VERSION_NAME should have been done.
    // If the PCD value is not equal to the value set to variable,
    // the PCD is not DynamicHii type and does not map to the setup option.
    //
    PcdTcg2PpiVersion = 0;
    CopyMem (
      &PcdTcg2PpiVersion,
      PcdGetPtr (PcdTcgPhysicalPresenceInterfaceVer),
      AsciiStrSize ((CHAR8 *)PcdGetPtr (PcdTcgPhysicalPresenceInterfaceVer))
      );
    if (PcdTcg2PpiVersion != Value->u64) {
      CreatePopUp (
        EFI_LIGHTGRAY | EFI_BACKGROUND_BLUE,
        &Key,
        L"WARNING: PcdTcgPhysicalPresenceInterfaceVer is not DynamicHii type and does not map to this option!",
        L"The version configuring by this setup option will not work!",
        NULL
        );
    }
  } else if (QuestionId == KEY_TPM2_ACPI_REVISION) {
    //
    // Get the PCD value after EFI_BROWSER_ACTION_SUBMITTED,
    // the SetVariable to TCG2_VERSION_NAME should have been done.
    // If the PCD value is not equal to the value set to variable,
    // the PCD is not DynamicHii type and does not map to the setup option.
    //
    PcdTpm2AcpiTableRev = PcdGet8 (PcdTpm2AcpiTableRev);

    if (PcdTpm2AcpiTableRev != Value->u8) {
      CreatePopUp (
        EFI_LIGHTGRAY | EFI_BACKGROUND_BLUE,
        &Key,
        L"WARNING: PcdTpm2AcpiTableRev is not DynamicHii type and does not map to this option!",
        L"The Revision configuring by this setup option will not work!",
        NULL
        );
    }
  }

  return EFI_SUCCESS;
}

/**
  This function processes the results of changes in configuration.

  @param[in]  This               Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param[in]  Action             Specifies the type of action taken by the browser.
  @param[in]  QuestionId         A unique value which is sent to the original
                                 exporting driver so that it can identify the type
                                 of data to expect.
  @param[in]  Type               The type of value for the question.
  @param[in]  Value              A pointer to the data being sent to the original
                                 exporting driver.
  @param[out] ActionRequest      On return, points to the action requested by the
                                 callback function.

  @retval EFI_SUCCESS            The callback successfully handled the action.
  @retval EFI_OUT_OF_RESOURCES   Not enough storage is available to hold the
                                 variable and its data.
  @retval EFI_DEVICE_ERROR       The variable could not be saved.
  @retval EFI_UNSUPPORTED        The specified Action is not supported by the
                                 callback.

**/
EFI_STATUS
EFIAPI
Tcg2Callback (
  IN CONST EFI_HII_CONFIG_ACCESS_PROTOCOL  *This,
  IN     EFI_BROWSER_ACTION                Action,
  IN     EFI_QUESTION_ID                   QuestionId,
  IN     UINT8                             Type,
  IN     EFI_IFR_TYPE_VALUE                *Value,
  OUT EFI_BROWSER_ACTION_REQUEST           *ActionRequest
  )
{
  EFI_STATUS                Status;
  EFI_INPUT_KEY             Key;
  CHAR8                     HidStr[16];
  CHAR16                    UnHidStr[16];
  TCG2_CONFIG_PRIVATE_DATA  *Private;

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

  Private = TCG2_CONFIG_PRIVATE_DATA_FROM_THIS (This);

  if (Action == EFI_BROWSER_ACTION_FORM_OPEN) {
    //
    // Update TPM2 HID info
    //
    if (QuestionId == KEY_TPM_DEVICE) {
      Status = GetTpm2HID (HidStr, 16);

      if (EFI_ERROR (Status)) {
        //
        //  Fail to get TPM2 HID
        //
        HiiSetString (Private->HiiHandle, STRING_TOKEN (STR_TPM2_ACPI_HID_CONTENT), L"Unknown", NULL);
      } else {
        AsciiStrToUnicodeStrS (HidStr, UnHidStr, 16);
        HiiSetString (Private->HiiHandle, STRING_TOKEN (STR_TPM2_ACPI_HID_CONTENT), UnHidStr, NULL);
      }
    }

    return EFI_SUCCESS;
  }

  if (Action == EFI_BROWSER_ACTION_CHANGING) {
    if (QuestionId == KEY_TPM_DEVICE_INTERFACE) {
      Status = SetPtpInterface ((VOID *)(UINTN)PcdGet64 (PcdTpmBaseAddress), Value->u8);
      if (EFI_ERROR (Status)) {
        CreatePopUp (
          EFI_LIGHTGRAY | EFI_BACKGROUND_BLUE,
          &Key,
          L"Error: Fail to set PTP interface!",
          NULL
          );
        return EFI_DEVICE_ERROR;
      }
    }
  }

  if (Action == EFI_BROWSER_ACTION_CHANGED) {
    if (QuestionId == KEY_TPM_DEVICE) {
      return EFI_SUCCESS;
    }

    if (QuestionId == KEY_TPM2_OPERATION) {
      return SaveTcg2PpRequest (Value->u8);
    }

    if (QuestionId == KEY_TPM2_OPERATION_PARAMETER) {
      return SaveTcg2PpRequestParameter (Value->u32);
    }

    if ((QuestionId >= KEY_TPM2_PCR_BANKS_REQUEST_0) && (QuestionId <= KEY_TPM2_PCR_BANKS_REQUEST_4)) {
      return SaveTcg2PCRBanksRequest (QuestionId - KEY_TPM2_PCR_BANKS_REQUEST_0, Value->b);
    }
  }

  if (Action == EFI_BROWSER_ACTION_SUBMITTED) {
    if ((QuestionId == KEY_TCG2_PPI_VERSION) || (QuestionId == KEY_TPM2_ACPI_REVISION)) {
      return Tcg2VersionInfoCallback (Action, QuestionId, Type, Value);
    }
  }

  return EFI_UNSUPPORTED;
}

/**
  Append Buffer With TpmAlgHash.

  @param[in] Buffer               Buffer to be appended.
  @param[in] BufferSize           Size of buffer.
  @param[in] TpmAlgHash           TpmAlgHash.

**/
VOID
AppendBufferWithTpmAlgHash (
  IN UINT16  *Buffer,
  IN UINTN   BufferSize,
  IN UINT32  TpmAlgHash
  )
{
  switch (TpmAlgHash) {
    case TPM_ALG_SHA1:
      if (Buffer[0] != 0) {
        StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
      }

      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA1");
      break;
    case TPM_ALG_SHA256:
      if (Buffer[0] != 0) {
        StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
      }

      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA256");
      break;
    case TPM_ALG_SHA384:
      if (Buffer[0] != 0) {
        StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
      }

      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA384");
      break;
    case TPM_ALG_SHA512:
      if (Buffer[0] != 0) {
        StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
      }

      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA512");
      break;
    case TPM_ALG_SM3_256:
      if (Buffer[0] != 0) {
        StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
      }

      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"SM3_256");
      break;
  }
}

/**
  Fill Buffer With BootHashAlg.

  @param[in] Buffer               Buffer to be filled.
  @param[in] BufferSize           Size of buffer.
  @param[in] BootHashAlg          BootHashAlg.

**/
VOID
FillBufferWithBootHashAlg (
  IN UINT16  *Buffer,
  IN UINTN   BufferSize,
  IN UINT32  BootHashAlg
  )
{
  Buffer[0] = 0;
  if ((BootHashAlg & EFI_TCG2_BOOT_HASH_ALG_SHA1) != 0) {
    if (Buffer[0] != 0) {
      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
    }

    StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA1");
  }

  if ((BootHashAlg & EFI_TCG2_BOOT_HASH_ALG_SHA256) != 0) {
    if (Buffer[0] != 0) {
      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
    }

    StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA256");
  }

  if ((BootHashAlg & EFI_TCG2_BOOT_HASH_ALG_SHA384) != 0) {
    if (Buffer[0] != 0) {
      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
    }

    StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA384");
  }

  if ((BootHashAlg & EFI_TCG2_BOOT_HASH_ALG_SHA512) != 0) {
    if (Buffer[0] != 0) {
      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
    }

    StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA512");
  }

  if ((BootHashAlg & EFI_TCG2_BOOT_HASH_ALG_SM3_256) != 0) {
    if (Buffer[0] != 0) {
      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
    }

    StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"SM3_256");
  }
}

/**
  Set ConfigInfo according to TpmAlgHash and Tcg2HashAlgBitmap.

  @param[in,out] Tcg2ConfigInfo       TCG2 config info.
  @param[in]     TpmAlgHash           TpmAlgHash.
  @param[in]     Tcg2HashAlgBitmap    TCG2 Hash Algorithm Bitmap.

**/
VOID
SetConfigInfo (
  IN OUT TCG2_CONFIGURATION_INFO  *Tcg2ConfigInfo,
  IN UINT32                       TpmAlgHash,
  IN UINT32                       Tcg2HashAlgBitmap
  )
{
  switch (TpmAlgHash) {
    case TPM_ALG_SHA1:
      if ((Tcg2HashAlgBitmap & HASH_ALG_SHA1) != 0) {
        Tcg2ConfigInfo->Sha1Supported = TRUE;
      }

      break;
    case TPM_ALG_SHA256:
      if ((Tcg2HashAlgBitmap & HASH_ALG_SHA256) != 0) {
        Tcg2ConfigInfo->Sha256Supported = TRUE;
      }

      break;
    case TPM_ALG_SHA384:
      if ((Tcg2HashAlgBitmap & HASH_ALG_SHA384) != 0) {
        Tcg2ConfigInfo->Sha384Supported = TRUE;
      }

      break;
    case TPM_ALG_SHA512:
      if ((Tcg2HashAlgBitmap & HASH_ALG_SHA512) != 0) {
        Tcg2ConfigInfo->Sha512Supported = TRUE;
      }

      break;
    case TPM_ALG_SM3_256:
      if ((Tcg2HashAlgBitmap & HASH_ALG_SM3_256) != 0) {
        Tcg2ConfigInfo->Sm3Supported = TRUE;
      }

      break;
  }
}

/**
  Fill Buffer With TCG2EventLogFormat.

  @param[in] Buffer               Buffer to be filled.
  @param[in] BufferSize           Size of buffer.
  @param[in] TCG2EventLogFormat   TCG2EventLogFormat.

**/
VOID
FillBufferWithTCG2EventLogFormat (
  IN UINT16  *Buffer,
  IN UINTN   BufferSize,
  IN UINT32  TCG2EventLogFormat
  )
{
  Buffer[0] = 0;
  if ((TCG2EventLogFormat & EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2) != 0) {
    if (Buffer[0] != 0) {
      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
    }

    StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"TCG_1_2");
  }

  if ((TCG2EventLogFormat & EFI_TCG2_EVENT_LOG_FORMAT_TCG_2) != 0) {
    if (Buffer[0] != 0) {
      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
    }

    StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"TCG_2");
  }

  if ((TCG2EventLogFormat & (~EFI_TCG2_EVENT_LOG_FORMAT_ALL)) != 0) {
    if (Buffer[0] != 0) {
      StrCatS (Buffer, BufferSize / sizeof (CHAR16), L", ");
    }

    StrCatS (Buffer, BufferSize / sizeof (CHAR16), L"UNKNOWN");
  }
}

/**
  This function publish the TCG2 configuration Form for TPM device.

  @param[in, out]  PrivateData   Points to TCG2 configuration private data.

  @retval EFI_SUCCESS            HII Form is installed for this network device.
  @retval EFI_OUT_OF_RESOURCES   Not enough resource for HII Form installation.
  @retval Others                 Other errors as indicated.

**/
EFI_STATUS
InstallTcg2ConfigForm (
  IN OUT TCG2_CONFIG_PRIVATE_DATA  *PrivateData
  )
{
  EFI_STATUS                       Status;
  EFI_HII_HANDLE                   HiiHandle;
  EFI_HANDLE                       DriverHandle;
  EFI_HII_CONFIG_ACCESS_PROTOCOL   *ConfigAccess;
  UINTN                            Index;
  TPML_PCR_SELECTION               Pcrs;
  CHAR16                           TempBuffer[1024];
  TCG2_CONFIGURATION_INFO          Tcg2ConfigInfo;
  TPM2_PTP_INTERFACE_TYPE          TpmDeviceInterfaceDetected;
  BOOLEAN                          IsCmdImp;
  EFI_TCG2_EVENT_ALGORITHM_BITMAP  Tcg2HashAlgorithmBitmap;

  DriverHandle = NULL;
  ConfigAccess = &PrivateData->ConfigAccess;
  Status       = gBS->InstallMultipleProtocolInterfaces (
                        &DriverHandle,
                        &gEfiDevicePathProtocolGuid,
                        &mTcg2HiiVendorDevicePath,
                        &gEfiHiiConfigAccessProtocolGuid,
                        ConfigAccess,
                        NULL
                        );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  PrivateData->DriverHandle = DriverHandle;

  //
  // Publish the HII package list
  //
  HiiHandle = HiiAddPackages (
                &gTcg2ConfigFormSetGuid,
                DriverHandle,
                Tcg2ConfigDxeStrings,
                Tcg2ConfigBin,
                NULL
                );
  if (HiiHandle == NULL) {
    gBS->UninstallMultipleProtocolInterfaces (
           DriverHandle,
           &gEfiDevicePathProtocolGuid,
           &mTcg2HiiVendorDevicePath,
           &gEfiHiiConfigAccessProtocolGuid,
           ConfigAccess,
           NULL
           );

    return EFI_OUT_OF_RESOURCES;
  }

  PrivateData->HiiHandle = HiiHandle;

  //
  // Update static data
  //
  switch (PrivateData->TpmDeviceDetected) {
    case TPM_DEVICE_NULL:
      HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_DEVICE_STATE_CONTENT), L"Not Found", NULL);
      break;
    case TPM_DEVICE_1_2:
      HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_DEVICE_STATE_CONTENT), L"TPM 1.2", NULL);
      break;
    case TPM_DEVICE_2_0_DTPM:
      HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_DEVICE_STATE_CONTENT), L"TPM 2.0", NULL);
      break;
    default:
      HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_DEVICE_STATE_CONTENT), L"Unknown", NULL);
      break;
  }

  Tcg2HashAlgorithmBitmap = PcdGet32 (PcdTcg2HashAlgorithmBitmap);

  ZeroMem (&Tcg2ConfigInfo, sizeof (Tcg2ConfigInfo));
  Status = Tpm2GetCapabilityPcrs (&Pcrs);
  if (EFI_ERROR (Status)) {
    HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TPM2_ACTIVE_HASH_ALGO_CONTENT), L"[Unknown]", NULL);
    HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TPM2_SUPPORTED_HASH_ALGO_CONTENT), L"[Unknown]", NULL);
  } else {
    TempBuffer[0] = 0;
    for (Index = 0; Index < Pcrs.count; Index++) {
      if (!IsZeroBuffer (Pcrs.pcrSelections[Index].pcrSelect, Pcrs.pcrSelections[Index].sizeofSelect)) {
        AppendBufferWithTpmAlgHash (TempBuffer, sizeof (TempBuffer), Pcrs.pcrSelections[Index].hash);
      }
    }

    HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TPM2_ACTIVE_HASH_ALGO_CONTENT), TempBuffer, NULL);

    TempBuffer[0] = 0;
    for (Index = 0; Index < Pcrs.count; Index++) {
      AppendBufferWithTpmAlgHash (TempBuffer, sizeof (TempBuffer), Pcrs.pcrSelections[Index].hash);
      SetConfigInfo (&Tcg2ConfigInfo, Pcrs.pcrSelections[Index].hash, Tcg2HashAlgorithmBitmap);
    }

    HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TPM2_SUPPORTED_HASH_ALGO_CONTENT), TempBuffer, NULL);
  }

  IsCmdImp = FALSE;
  Status   = Tpm2GetCapabilityIsCommandImplemented (TPM_CC_ChangeEPS, &IsCmdImp);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Tpm2GetCapabilityIsCmdImpl fails %r\n", Status));
  }

  Tcg2ConfigInfo.ChangeEPSSupported = IsCmdImp;

  FillBufferWithBootHashAlg (TempBuffer, sizeof (TempBuffer), Tcg2HashAlgorithmBitmap);
  HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_BIOS_HASH_ALGO_CONTENT), TempBuffer, NULL);

  //
  // Tcg2 Capability
  //
  FillBufferWithTCG2EventLogFormat (TempBuffer, sizeof (TempBuffer), PrivateData->ProtocolCapability.SupportedEventLogs);
  HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_SUPPORTED_EVENT_LOG_FORMAT_CONTENT), TempBuffer, NULL);

  FillBufferWithBootHashAlg (TempBuffer, sizeof (TempBuffer), PrivateData->ProtocolCapability.HashAlgorithmBitmap);
  HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_HASH_ALGO_BITMAP_CONTENT), TempBuffer, NULL);

  UnicodeSPrint (TempBuffer, sizeof (TempBuffer), L"%d", PrivateData->ProtocolCapability.NumberOfPCRBanks);
  HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_NUMBER_OF_PCR_BANKS_CONTENT), TempBuffer, NULL);

  FillBufferWithBootHashAlg (TempBuffer, sizeof (TempBuffer), PrivateData->ProtocolCapability.ActivePcrBanks);
  HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_ACTIVE_PCR_BANKS_CONTENT), TempBuffer, NULL);

  //
  // Update TPM device interface type
  //
  if (PrivateData->TpmDeviceDetected == TPM_DEVICE_2_0_DTPM) {
    TpmDeviceInterfaceDetected = PcdGet8 (PcdActiveTpmInterfaceType);
    switch (TpmDeviceInterfaceDetected) {
      case Tpm2PtpInterfaceTis:
        HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_DEVICE_INTERFACE_STATE_CONTENT), L"TIS", NULL);
        break;
      case Tpm2PtpInterfaceFifo:
        HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_DEVICE_INTERFACE_STATE_CONTENT), L"PTP FIFO", NULL);
        break;
      case Tpm2PtpInterfaceCrb:
        HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_DEVICE_INTERFACE_STATE_CONTENT), L"PTP CRB", NULL);
        break;
      default:
        HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_DEVICE_INTERFACE_STATE_CONTENT), L"Unknown", NULL);
        break;
    }

    Tcg2ConfigInfo.TpmDeviceInterfaceAttempt = TpmDeviceInterfaceDetected;
    switch (TpmDeviceInterfaceDetected) {
      case Tpm2PtpInterfaceTis:
        Tcg2ConfigInfo.TpmDeviceInterfacePtpFifoSupported = FALSE;
        Tcg2ConfigInfo.TpmDeviceInterfacePtpCrbSupported  = FALSE;
        HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_DEVICE_INTERFACE_CAPABILITY_CONTENT), L"TIS", NULL);
        break;
      case Tpm2PtpInterfaceFifo:
      case Tpm2PtpInterfaceCrb:
        Tcg2ConfigInfo.TpmDeviceInterfacePtpFifoSupported = IsPtpFifoSupported ((VOID *)(UINTN)PcdGet64 (PcdTpmBaseAddress));
        Tcg2ConfigInfo.TpmDeviceInterfacePtpCrbSupported  = IsPtpCrbSupported ((VOID *)(UINTN)PcdGet64 (PcdTpmBaseAddress));
        TempBuffer[0]                                     = 0;
        if (Tcg2ConfigInfo.TpmDeviceInterfacePtpFifoSupported) {
          if (TempBuffer[0] != 0) {
            StrCatS (TempBuffer, sizeof (TempBuffer) / sizeof (CHAR16), L", ");
          }

          StrCatS (TempBuffer, sizeof (TempBuffer) / sizeof (CHAR16), L"PTP FIFO");
        }

        if (Tcg2ConfigInfo.TpmDeviceInterfacePtpCrbSupported) {
          if (TempBuffer[0] != 0) {
            StrCatS (TempBuffer, sizeof (TempBuffer) / sizeof (CHAR16), L", ");
          }

          StrCatS (TempBuffer, sizeof (TempBuffer) / sizeof (CHAR16), L"PTP CRB");
        }

        HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_DEVICE_INTERFACE_CAPABILITY_CONTENT), TempBuffer, NULL);
        break;
      default:
        Tcg2ConfigInfo.TpmDeviceInterfacePtpFifoSupported = FALSE;
        Tcg2ConfigInfo.TpmDeviceInterfacePtpCrbSupported  = FALSE;
        HiiSetString (PrivateData->HiiHandle, STRING_TOKEN (STR_TCG2_DEVICE_INTERFACE_CAPABILITY_CONTENT), L"Unknown", NULL);
        break;
    }
  }

  //
  // Set ConfigInfo, to control the check box.
  //
  Status = gRT->SetVariable (
                  TCG2_STORAGE_INFO_NAME,
                  &gTcg2ConfigFormSetGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS,
                  sizeof (Tcg2ConfigInfo),
                  &Tcg2ConfigInfo
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Tcg2ConfigDriver: Fail to set TCG2_STORAGE_INFO_NAME\n"));
  }

  return EFI_SUCCESS;
}

/**
  This function removes TCG2 configuration Form.

  @param[in, out]  PrivateData   Points to TCG2 configuration private data.

**/
VOID
UninstallTcg2ConfigForm (
  IN OUT TCG2_CONFIG_PRIVATE_DATA  *PrivateData
  )
{
  //
  // Uninstall HII package list
  //
  if (PrivateData->HiiHandle != NULL) {
    HiiRemovePackages (PrivateData->HiiHandle);
    PrivateData->HiiHandle = NULL;
  }

  //
  // Uninstall HII Config Access Protocol
  //
  if (PrivateData->DriverHandle != NULL) {
    gBS->UninstallMultipleProtocolInterfaces (
           PrivateData->DriverHandle,
           &gEfiDevicePathProtocolGuid,
           &mTcg2HiiVendorDevicePath,
           &gEfiHiiConfigAccessProtocolGuid,
           &PrivateData->ConfigAccess,
           NULL
           );
    PrivateData->DriverHandle = NULL;
  }

  FreePool (PrivateData);
}
