/** @file
  Execute pending TPM2 requests from OS or BIOS.

  Caution: This module requires additional review when modified.
  This driver will have external input - variable.
  This external input must be validated carefully to avoid security issue.

  Tcg2ExecutePendingTpmRequest() will receive untrusted input and do validation.

Copyright (C) 2018, Red Hat, Inc.
Copyright (c) 2018, IBM Corporation. All rights reserved.<BR>
Copyright (c) 2013 - 2016, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <PiDxe.h>

#include <Guid/Tcg2PhysicalPresenceData.h>
#include <IndustryStandard/QemuTpm.h>
#include <Protocol/Tcg2Protocol.h>

#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/DxeServicesTableLib.h>
#include <Library/HiiLib.h>
#include <Library/HobLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PrintLib.h>
#include <Library/QemuFwCfgLib.h>
#include <Library/Tpm2CommandLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/UefiBootManagerLib.h>

#include <Library/Tcg2PhysicalPresenceLib.h>

#define CONFIRM_BUFFER_SIZE  4096

EFI_HII_HANDLE  mTcg2PpStringPackHandle;

#define TPM_PPI_FLAGS  (QEMU_TPM_PPI_FUNC_ALLOWED_USR_REQ)

STATIC volatile QEMU_TPM_PPI  *mPpi;

/**
  Reads QEMU PPI config from fw_cfg.

  @param[out]  The Config structure to read to.

  @retval EFI_SUCCESS           Operation completed successfully.
  @retval EFI_PROTOCOL_ERROR    Invalid fw_cfg entry size.
**/
STATIC
EFI_STATUS
QemuTpmReadConfig (
  OUT QEMU_FWCFG_TPM_CONFIG  *Config
  )
{
  EFI_STATUS            Status;
  FIRMWARE_CONFIG_ITEM  FwCfgItem;
  UINTN                 FwCfgSize;

  Status = QemuFwCfgFindFile ("etc/tpm/config", &FwCfgItem, &FwCfgSize);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (FwCfgSize != sizeof (*Config)) {
    return EFI_PROTOCOL_ERROR;
  }

  QemuFwCfgSelectItem (FwCfgItem);
  QemuFwCfgReadBytes (sizeof (*Config), Config);
  return EFI_SUCCESS;
}

/**
  Initializes QEMU PPI memory region.

  @retval EFI_SUCCESS           Operation completed successfully.
  @retval EFI_PROTOCOL_ERROR    PPI address is invalid.
**/
STATIC
EFI_STATUS
QemuTpmInitPPI (
  VOID
  )
{
  EFI_STATUS                       Status;
  QEMU_FWCFG_TPM_CONFIG            Config;
  EFI_PHYSICAL_ADDRESS             PpiAddress64;
  EFI_GCD_MEMORY_SPACE_DESCRIPTOR  Descriptor;
  UINTN                            Idx;

  if (mPpi != NULL) {
    return EFI_SUCCESS;
  }

  Status = QemuTpmReadConfig (&Config);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  mPpi = (QEMU_TPM_PPI *)(UINTN)Config.PpiAddress;
  if (mPpi == NULL) {
    return EFI_PROTOCOL_ERROR;
  }

  DEBUG ((DEBUG_INFO, "[TPM2PP] mPpi=%p version=%d\n", mPpi, Config.TpmVersion));

  PpiAddress64 = (UINTN)mPpi;
  if ((PpiAddress64 & ~(UINT64)EFI_PAGE_MASK) !=
      ((PpiAddress64 + sizeof *mPpi - 1) & ~(UINT64)EFI_PAGE_MASK))
  {
    DEBUG ((DEBUG_ERROR, "[TPM2PP] mPpi crosses a page boundary\n"));
    goto InvalidPpiAddress;
  }

  Status = gDS->GetMemorySpaceDescriptor (PpiAddress64, &Descriptor);
  if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) {
    ASSERT_EFI_ERROR (Status);
    goto InvalidPpiAddress;
  }

  if (!EFI_ERROR (Status) &&
      ((Descriptor.GcdMemoryType != EfiGcdMemoryTypeMemoryMappedIo) &&
       (Descriptor.GcdMemoryType != EfiGcdMemoryTypeNonExistent)))
  {
    DEBUG ((DEBUG_ERROR, "[TPM2PP] mPpi has an invalid memory type\n"));
    goto InvalidPpiAddress;
  }

  for (Idx = 0; Idx < ARRAY_SIZE (mPpi->Func); Idx++) {
    mPpi->Func[Idx] = 0;
  }

  if (Config.TpmVersion == QEMU_TPM_VERSION_2) {
    mPpi->Func[TCG2_PHYSICAL_PRESENCE_NO_ACTION]         = TPM_PPI_FLAGS;
    mPpi->Func[TCG2_PHYSICAL_PRESENCE_CLEAR]             = TPM_PPI_FLAGS;
    mPpi->Func[TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR]      = TPM_PPI_FLAGS;
    mPpi->Func[TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR_2]    = TPM_PPI_FLAGS;
    mPpi->Func[TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR_3]    = TPM_PPI_FLAGS;
    mPpi->Func[TCG2_PHYSICAL_PRESENCE_SET_PCR_BANKS]     = TPM_PPI_FLAGS;
    mPpi->Func[TCG2_PHYSICAL_PRESENCE_CHANGE_EPS]        = TPM_PPI_FLAGS;
    mPpi->Func[TCG2_PHYSICAL_PRESENCE_LOG_ALL_DIGESTS]   = TPM_PPI_FLAGS;
    mPpi->Func[TCG2_PHYSICAL_PRESENCE_ENABLE_BLOCK_SID]  = TPM_PPI_FLAGS;
    mPpi->Func[TCG2_PHYSICAL_PRESENCE_DISABLE_BLOCK_SID] = TPM_PPI_FLAGS;
  }

  if (mPpi->In == 0) {
    mPpi->In          = 1;
    mPpi->Request     = TCG2_PHYSICAL_PRESENCE_NO_ACTION;
    mPpi->LastRequest = TCG2_PHYSICAL_PRESENCE_NO_ACTION;
    mPpi->NextStep    = TCG2_PHYSICAL_PRESENCE_NO_ACTION;
  }

  return EFI_SUCCESS;

InvalidPpiAddress:
  mPpi = NULL;
  return EFI_PROTOCOL_ERROR;
}

/**
  Get string by string id from HII Interface.

  @param[in] Id          String ID.

  @retval    CHAR16 *    String from ID.
  @retval    NULL        If error occurs.

**/
STATIC
CHAR16 *
Tcg2PhysicalPresenceGetStringById (
  IN  EFI_STRING_ID  Id
  )
{
  return HiiGetString (mTcg2PpStringPackHandle, Id, NULL);
}

/**
  Send ClearControl and Clear command to TPM.

  @param[in]  PlatformAuth      platform auth value. NULL means no platform auth change.

  @retval EFI_SUCCESS           Operation completed successfully.
  @retval EFI_TIMEOUT           The register can't run into the expected status in time.
  @retval EFI_BUFFER_TOO_SMALL  Response data buffer is too small.
  @retval EFI_DEVICE_ERROR      Unexpected device behavior.

**/
EFI_STATUS
EFIAPI
Tpm2CommandClear (
  IN TPM2B_AUTH  *PlatformAuth  OPTIONAL
  )
{
  EFI_STATUS         Status;
  TPMS_AUTH_COMMAND  *AuthSession;
  TPMS_AUTH_COMMAND  LocalAuthSession;

  if (PlatformAuth == NULL) {
    AuthSession = NULL;
  } else {
    AuthSession = &LocalAuthSession;
    ZeroMem (&LocalAuthSession, sizeof (LocalAuthSession));
    LocalAuthSession.sessionHandle = TPM_RS_PW;
    LocalAuthSession.hmac.size     = PlatformAuth->size;
    CopyMem (LocalAuthSession.hmac.buffer, PlatformAuth->buffer, PlatformAuth->size);
  }

  DEBUG ((DEBUG_INFO, "Tpm2ClearControl ... \n"));
  Status = Tpm2ClearControl (TPM_RH_PLATFORM, AuthSession, NO);
  DEBUG ((DEBUG_INFO, "Tpm2ClearControl - %r\n", Status));
  if (EFI_ERROR (Status)) {
    goto Done;
  }

  DEBUG ((DEBUG_INFO, "Tpm2Clear ... \n"));
  Status = Tpm2Clear (TPM_RH_PLATFORM, AuthSession);
  DEBUG ((DEBUG_INFO, "Tpm2Clear - %r\n", Status));

Done:
  ZeroMem (&LocalAuthSession.hmac, sizeof (LocalAuthSession.hmac));
  return Status;
}

/**
  Change EPS.

  @param[in]  PlatformAuth      platform auth value. NULL means no platform auth change.

  @retval EFI_SUCCESS Operation completed successfully.
**/
STATIC
EFI_STATUS
Tpm2CommandChangeEps (
  IN TPM2B_AUTH  *PlatformAuth  OPTIONAL
  )
{
  EFI_STATUS         Status;
  TPMS_AUTH_COMMAND  *AuthSession;
  TPMS_AUTH_COMMAND  LocalAuthSession;

  if (PlatformAuth == NULL) {
    AuthSession = NULL;
  } else {
    AuthSession = &LocalAuthSession;
    ZeroMem (&LocalAuthSession, sizeof (LocalAuthSession));
    LocalAuthSession.sessionHandle = TPM_RS_PW;
    LocalAuthSession.hmac.size     = PlatformAuth->size;
    CopyMem (LocalAuthSession.hmac.buffer, PlatformAuth->buffer, PlatformAuth->size);
  }

  Status = Tpm2ChangeEPS (TPM_RH_PLATFORM, AuthSession);
  DEBUG ((DEBUG_INFO, "Tpm2ChangeEPS - %r\n", Status));

  ZeroMem (&LocalAuthSession.hmac, sizeof (LocalAuthSession.hmac));
  return Status;
}

/**
  Execute physical presence operation requested by the OS.

  @param[in]      PlatformAuth        platform auth value. NULL means no platform auth change.
  @param[in]      CommandCode         Physical presence operation value.
  @param[in]      CommandParameter    Physical presence operation parameter.

  @retval TCG_PP_OPERATION_RESPONSE_BIOS_FAILURE   Unknown physical presence operation.
  @retval TCG_PP_OPERATION_RESPONSE_BIOS_FAILURE   Error occurred during sending command to TPM or
                                                   receiving response from TPM.
  @retval Others                                   Return code from the TPM device after command execution.
**/
STATIC
UINT32
Tcg2ExecutePhysicalPresence (
  IN      TPM2B_AUTH  *PlatformAuth   OPTIONAL,
  IN      UINT32      CommandCode,
  IN      UINT32      CommandParameter
  )
{
  EFI_STATUS                       Status;
  EFI_TCG2_EVENT_ALGORITHM_BITMAP  TpmHashAlgorithmBitmap;
  UINT32                           ActivePcrBanks;

  switch (CommandCode) {
    case TCG2_PHYSICAL_PRESENCE_CLEAR:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR_2:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR_3:
      Status = Tpm2CommandClear (PlatformAuth);
      if (EFI_ERROR (Status)) {
        return TCG_PP_OPERATION_RESPONSE_BIOS_FAILURE;
      } else {
        return TCG_PP_OPERATION_RESPONSE_SUCCESS;
      }

    case TCG2_PHYSICAL_PRESENCE_SET_PCR_BANKS:
      Status = Tpm2GetCapabilitySupportedAndActivePcrs (&TpmHashAlgorithmBitmap, &ActivePcrBanks);
      ASSERT_EFI_ERROR (Status);

      //
      // PP spec requirements:
      //    Firmware should check that all requested (set) hashing algorithms are supported with respective PCR banks.
      //    Firmware has to ensure that at least one PCR banks is active.
      // If not, an error is returned and no action is taken.
      //
      if ((CommandParameter == 0) || ((CommandParameter & (~TpmHashAlgorithmBitmap)) != 0)) {
        DEBUG ((DEBUG_ERROR, "PCR banks %x to allocate are not supported by TPM. Skip operation\n", CommandParameter));
        return TCG_PP_OPERATION_RESPONSE_BIOS_FAILURE;
      }

      Status = Tpm2PcrAllocateBanks (PlatformAuth, TpmHashAlgorithmBitmap, CommandParameter);
      if (EFI_ERROR (Status)) {
        return TCG_PP_OPERATION_RESPONSE_BIOS_FAILURE;
      } else {
        return TCG_PP_OPERATION_RESPONSE_SUCCESS;
      }

    case TCG2_PHYSICAL_PRESENCE_CHANGE_EPS:
      Status = Tpm2CommandChangeEps (PlatformAuth);
      if (EFI_ERROR (Status)) {
        return TCG_PP_OPERATION_RESPONSE_BIOS_FAILURE;
      } else {
        return TCG_PP_OPERATION_RESPONSE_SUCCESS;
      }

    case TCG2_PHYSICAL_PRESENCE_LOG_ALL_DIGESTS:
      Status = Tpm2GetCapabilitySupportedAndActivePcrs (&TpmHashAlgorithmBitmap, &ActivePcrBanks);
      ASSERT_EFI_ERROR (Status);
      Status = Tpm2PcrAllocateBanks (PlatformAuth, TpmHashAlgorithmBitmap, TpmHashAlgorithmBitmap);
      if (EFI_ERROR (Status)) {
        return TCG_PP_OPERATION_RESPONSE_BIOS_FAILURE;
      } else {
        return TCG_PP_OPERATION_RESPONSE_SUCCESS;
      }

    default:
      if (CommandCode <= TCG2_PHYSICAL_PRESENCE_NO_ACTION_MAX) {
        return TCG_PP_OPERATION_RESPONSE_SUCCESS;
      } else {
        return TCG_PP_OPERATION_RESPONSE_BIOS_FAILURE;
      }
  }
}

/**
  Read the specified key for user confirmation.

  @param[in]  CautionKey  If true,  F12 is used as confirm key;
                          If false, F10 is used as confirm key.

  @retval     TRUE        User confirmed the changes by input.
  @retval     FALSE       User discarded the changes.
**/
STATIC
BOOLEAN
Tcg2ReadUserKey (
  IN     BOOLEAN  CautionKey
  )
{
  EFI_STATUS     Status;
  EFI_INPUT_KEY  Key;
  UINT16         InputKey;

  InputKey = 0;
  do {
    Status = gBS->CheckEvent (gST->ConIn->WaitForKey);
    if (!EFI_ERROR (Status)) {
      Status = gST->ConIn->ReadKeyStroke (gST->ConIn, &Key);
      if (Key.ScanCode == SCAN_ESC) {
        InputKey = Key.ScanCode;
      }

      if ((Key.ScanCode == SCAN_F10) && !CautionKey) {
        InputKey = Key.ScanCode;
      }

      if ((Key.ScanCode == SCAN_F12) && CautionKey) {
        InputKey = Key.ScanCode;
      }
    }
  } while (InputKey == 0);

  if (InputKey != SCAN_ESC) {
    return TRUE;
  }

  return FALSE;
}

/**
  Fill Buffer With BootHashAlg.

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

**/
STATIC
VOID
Tcg2FillBufferWithBootHashAlg (
  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) {
      StrnCatS (Buffer, BufferSize / sizeof (CHAR16), L", ", (BufferSize / sizeof (CHAR16)) - StrLen (Buffer) - 1);
    }

    StrnCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA1", (BufferSize / sizeof (CHAR16)) - StrLen (Buffer) - 1);
  }

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

    StrnCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA256", (BufferSize / sizeof (CHAR16)) - StrLen (Buffer) - 1);
  }

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

    StrnCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA384", (BufferSize / sizeof (CHAR16)) - StrLen (Buffer) - 1);
  }

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

    StrnCatS (Buffer, BufferSize / sizeof (CHAR16), L"SHA512", (BufferSize / sizeof (CHAR16)) - StrLen (Buffer) - 1);
  }

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

    StrnCatS (Buffer, BufferSize / sizeof (CHAR16), L"SM3_256", (BufferSize / sizeof (CHAR16)) - StrLen (Buffer) - 1);
  }
}

/**
  Display the confirm text and get user confirmation.

  @param[in] TpmPpCommand             The requested TPM physical presence command.
  @param[in] TpmPpCommandParameter    The requested TPM physical presence command parameter.

  @retval    TRUE          The user has confirmed the changes.
  @retval    FALSE         The user doesn't confirm the changes.
**/
STATIC
BOOLEAN
Tcg2UserConfirm (
  IN      UINT32  TpmPpCommand,
  IN      UINT32  TpmPpCommandParameter
  )
{
  CHAR16                            *ConfirmText;
  CHAR16                            *TmpStr1;
  CHAR16                            *TmpStr2;
  UINTN                             BufSize;
  BOOLEAN                           CautionKey;
  BOOLEAN                           NoPpiInfo;
  UINT16                            Index;
  CHAR16                            DstStr[81];
  CHAR16                            TempBuffer[1024];
  CHAR16                            TempBuffer2[1024];
  EFI_TCG2_PROTOCOL                 *Tcg2Protocol;
  EFI_TCG2_BOOT_SERVICE_CAPABILITY  ProtocolCapability;
  UINT32                            CurrentPCRBanks;
  EFI_STATUS                        Status;

  TmpStr2     = NULL;
  CautionKey  = FALSE;
  NoPpiInfo   = FALSE;
  BufSize     = CONFIRM_BUFFER_SIZE;
  ConfirmText = AllocateZeroPool (BufSize);
  ASSERT (ConfirmText != NULL);

  mTcg2PpStringPackHandle = HiiAddPackages (&gEfiTcg2PhysicalPresenceGuid, gImageHandle, Tcg2PhysicalPresenceLibQemuStrings, NULL);
  ASSERT (mTcg2PpStringPackHandle != NULL);

  switch (TpmPpCommand) {
    case TCG2_PHYSICAL_PRESENCE_CLEAR:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR_2:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR_3:
      CautionKey = TRUE;
      TmpStr2    = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_CLEAR));

      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_HEAD_STR));
      UnicodeSPrint (ConfirmText, BufSize, TmpStr1, TmpStr2);
      FreePool (TmpStr1);

      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_WARNING_CLEAR));
      StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), TmpStr1, (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
      StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), L" \n\n", (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
      FreePool (TmpStr1);

      break;

    case TCG2_PHYSICAL_PRESENCE_SET_PCR_BANKS:
      Status = gBS->LocateProtocol (&gEfiTcg2ProtocolGuid, NULL, (VOID **)&Tcg2Protocol);
      ASSERT_EFI_ERROR (Status);

      ProtocolCapability.Size = sizeof (ProtocolCapability);
      Status                  = Tcg2Protocol->GetCapability (
                                                Tcg2Protocol,
                                                &ProtocolCapability
                                                );
      ASSERT_EFI_ERROR (Status);

      Status = Tcg2Protocol->GetActivePcrBanks (
                               Tcg2Protocol,
                               &CurrentPCRBanks
                               );
      ASSERT_EFI_ERROR (Status);

      CautionKey = TRUE;
      TmpStr2    = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_SET_PCR_BANKS));

      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_HEAD_STR));
      UnicodeSPrint (ConfirmText, BufSize, TmpStr1, TmpStr2);
      FreePool (TmpStr1);

      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_WARNING_SET_PCR_BANKS_1));
      StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), TmpStr1, (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
      FreePool (TmpStr1);

      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_WARNING_SET_PCR_BANKS_2));
      StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), TmpStr1, (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
      FreePool (TmpStr1);

      Tcg2FillBufferWithBootHashAlg (TempBuffer, sizeof (TempBuffer), TpmPpCommandParameter);
      Tcg2FillBufferWithBootHashAlg (TempBuffer2, sizeof (TempBuffer2), CurrentPCRBanks);

      TmpStr1 = AllocateZeroPool (BufSize);
      ASSERT (TmpStr1 != NULL);
      UnicodeSPrint (TmpStr1, BufSize, L"Current PCRBanks is 0x%x. (%s)\nNew PCRBanks is 0x%x. (%s)\n", CurrentPCRBanks, TempBuffer2, TpmPpCommandParameter, TempBuffer);

      StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), TmpStr1, (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
      StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), L" \n", (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
      FreePool (TmpStr1);

      break;

    case TCG2_PHYSICAL_PRESENCE_CHANGE_EPS:
      CautionKey = TRUE;
      TmpStr2    = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_CHANGE_EPS));

      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_HEAD_STR));
      UnicodeSPrint (ConfirmText, BufSize, TmpStr1, TmpStr2);
      FreePool (TmpStr1);

      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_WARNING_CHANGE_EPS_1));
      StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), TmpStr1, (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
      FreePool (TmpStr1);

      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_WARNING_CHANGE_EPS_2));
      StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), TmpStr1, (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
      FreePool (TmpStr1);

      break;

    case TCG2_PHYSICAL_PRESENCE_ENABLE_BLOCK_SID:
      TmpStr2 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TCG_STORAGE_ENABLE_BLOCK_SID));

      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TCG_STORAGE_HEAD_STR));
      UnicodeSPrint (ConfirmText, BufSize, TmpStr1, TmpStr2);
      FreePool (TmpStr1);
      break;

    case TCG2_PHYSICAL_PRESENCE_DISABLE_BLOCK_SID:
      TmpStr2 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TCG_STORAGE_DISABLE_BLOCK_SID));

      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TCG_STORAGE_HEAD_STR));
      UnicodeSPrint (ConfirmText, BufSize, TmpStr1, TmpStr2);
      FreePool (TmpStr1);
      break;

    default:
      ;
  }

  if (TmpStr2 == NULL) {
    FreePool (ConfirmText);
    return FALSE;
  }

  // Console for user interaction
  // We need to connect all trusted consoles for TCG PP. Here we treat all consoles in OVMF to be trusted consoles.
  EfiBootManagerConnectAllDefaultConsoles ();

  if (TpmPpCommand < TCG2_PHYSICAL_PRESENCE_STORAGE_MANAGEMENT_BEGIN) {
    if (CautionKey) {
      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_CAUTION_KEY));
    } else {
      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_ACCEPT_KEY));
    }

    StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), TmpStr1, (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
    FreePool (TmpStr1);

    if (NoPpiInfo) {
      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_NO_PPI_INFO));
      StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), TmpStr1, (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
      FreePool (TmpStr1);
    }

    TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TPM_REJECT_KEY));
  } else {
    if (CautionKey) {
      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TCG_STORAGE_CAUTION_KEY));
    } else {
      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TCG_STORAGE_ACCEPT_KEY));
    }

    StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), TmpStr1, (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
    FreePool (TmpStr1);

    if (NoPpiInfo) {
      TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TCG_STORAGE_NO_PPI_INFO));
      StrnCatS (ConfirmText, BufSize / sizeof (CHAR16), TmpStr1, (BufSize / sizeof (CHAR16)) - StrLen (ConfirmText) - 1);
      FreePool (TmpStr1);
    }

    TmpStr1 = Tcg2PhysicalPresenceGetStringById (STRING_TOKEN (TCG_STORAGE_REJECT_KEY));
  }

  BufSize -= StrSize (ConfirmText);
  UnicodeSPrint (ConfirmText + StrLen (ConfirmText), BufSize, TmpStr1, TmpStr2);

  DstStr[80] = L'\0';
  for (Index = 0; Index < StrLen (ConfirmText); Index += 80) {
    StrnCpyS (DstStr, sizeof (DstStr) / sizeof (CHAR16), ConfirmText + Index, sizeof (DstStr) / sizeof (CHAR16) - 1);
    Print (DstStr);
  }

  FreePool (TmpStr1);
  FreePool (TmpStr2);
  FreePool (ConfirmText);
  HiiRemovePackages (mTcg2PpStringPackHandle);

  if (Tcg2ReadUserKey (CautionKey)) {
    return TRUE;
  }

  return FALSE;
}

/**
  Check if there is a valid physical presence command request. Also updates parameter value
  to whether the requested physical presence command already confirmed by user

   @param[out] RequestConfirmed          If the physical presence operation command required user confirm from UI.
                                           True, it indicates the command doesn't require user confirm, or already confirmed
                                                 in last boot cycle by user.
                                           False, it indicates the command need user confirm from UI.

   @retval  TRUE        Physical Presence operation command is valid.
   @retval  FALSE       Physical Presence operation command is invalid.

**/
STATIC
BOOLEAN
Tcg2HaveValidTpmRequest  (
  OUT     BOOLEAN  *RequestConfirmed
  )
{
  EFI_TCG2_PROTOCOL  *Tcg2Protocol;
  EFI_STATUS         Status;

  *RequestConfirmed = FALSE;

  if (mPpi->Request <= TCG2_PHYSICAL_PRESENCE_NO_ACTION_MAX) {
    //
    // Need TCG2 protocol.
    //
    Status = gBS->LocateProtocol (&gEfiTcg2ProtocolGuid, NULL, (VOID **)&Tcg2Protocol);
    if (EFI_ERROR (Status)) {
      return FALSE;
    }
  }

  switch (mPpi->Request) {
    case TCG2_PHYSICAL_PRESENCE_NO_ACTION:
    case TCG2_PHYSICAL_PRESENCE_LOG_ALL_DIGESTS:
      *RequestConfirmed = TRUE;
      return TRUE;

    case TCG2_PHYSICAL_PRESENCE_CLEAR:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR_2:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR_3:
    case TCG2_PHYSICAL_PRESENCE_SET_PCR_BANKS:
    case TCG2_PHYSICAL_PRESENCE_CHANGE_EPS:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_BLOCK_SID:
    case TCG2_PHYSICAL_PRESENCE_DISABLE_BLOCK_SID:
      break;

    default:
      //
      // Wrong Physical Presence command
      //
      return FALSE;
  }

  //
  // Physical Presence command is correct
  //
  return TRUE;
}

/**
  Check and execute the requested physical presence command.

  @param[in]      PlatformAuth      platform auth value. NULL means no platform auth change.
**/
STATIC
VOID
Tcg2ExecutePendingTpmRequest (
  IN      TPM2B_AUTH  *PlatformAuth OPTIONAL
  )
{
  BOOLEAN  RequestConfirmed;

  if (mPpi->Request == TCG2_PHYSICAL_PRESENCE_NO_ACTION) {
    //
    // No operation request
    //
    return;
  }

  if (!Tcg2HaveValidTpmRequest (&RequestConfirmed)) {
    //
    // Invalid operation request.
    //
    if (mPpi->Request <= TCG2_PHYSICAL_PRESENCE_NO_ACTION_MAX) {
      mPpi->Response = TCG_PP_OPERATION_RESPONSE_SUCCESS;
    } else {
      mPpi->Response = TCG_PP_OPERATION_RESPONSE_BIOS_FAILURE;
    }

    mPpi->LastRequest      = mPpi->Request;
    mPpi->Request          = TCG2_PHYSICAL_PRESENCE_NO_ACTION;
    mPpi->RequestParameter = 0;
    return;
  }

  if (!RequestConfirmed) {
    //
    // Print confirm text and wait for approval.
    //
    RequestConfirmed = Tcg2UserConfirm (mPpi->Request, mPpi->RequestParameter);
  }

  //
  // Execute requested physical presence command
  //
  mPpi->Response = TCG_PP_OPERATION_RESPONSE_USER_ABORT;
  if (RequestConfirmed) {
    mPpi->Response = Tcg2ExecutePhysicalPresence (
                       PlatformAuth,
                       mPpi->Request,
                       mPpi->RequestParameter
                       );
  }

  //
  // Clear request
  //
  mPpi->LastRequest      = mPpi->Request;
  mPpi->Request          = TCG2_PHYSICAL_PRESENCE_NO_ACTION;
  mPpi->RequestParameter = 0;

  if (mPpi->Response == TCG_PP_OPERATION_RESPONSE_USER_ABORT) {
    return;
  }

  //
  // Reset system to make new TPM settings in effect
  //
  switch (mPpi->LastRequest) {
    case TCG2_PHYSICAL_PRESENCE_CLEAR:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR_2:
    case TCG2_PHYSICAL_PRESENCE_ENABLE_CLEAR_3:
    case TCG2_PHYSICAL_PRESENCE_SET_PCR_BANKS:
    case TCG2_PHYSICAL_PRESENCE_CHANGE_EPS:
    case TCG2_PHYSICAL_PRESENCE_LOG_ALL_DIGESTS:
      break;

    case TCG2_PHYSICAL_PRESENCE_ENABLE_BLOCK_SID:
    case TCG2_PHYSICAL_PRESENCE_DISABLE_BLOCK_SID:
      break;

    default:
      if (mPpi->Request != TCG2_PHYSICAL_PRESENCE_NO_ACTION) {
        break;
      }

      return;
  }

  Print (L"Rebooting system to make TPM2 settings in effect\n");
  gRT->ResetSystem (EfiResetCold, EFI_SUCCESS, 0, NULL);
  ASSERT (FALSE);
}

/**
   Check and execute the pending TPM request.

   The TPM request may come from OS or BIOS. This API will display request information and wait
   for user confirmation if TPM request exists. The TPM request will be sent to TPM device after
   the TPM request is confirmed, and one or more reset may be required to make TPM request to
   take effect.

   This API should be invoked after console in and console out are all ready as they are required
   to display request information and get user input to confirm the request.

   @param[in]  PlatformAuth                   platform auth value. NULL means no platform auth change.
**/
VOID
EFIAPI
Tcg2PhysicalPresenceLibProcessRequest (
  IN      TPM2B_AUTH  *PlatformAuth  OPTIONAL
  )
{
  EFI_STATUS  Status;

  Status = QemuTpmInitPPI ();
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "[TPM2PP] no PPI\n"));
    return;
  }

  //
  // Check S4 resume
  //
  if (GetBootModeHob () == BOOT_ON_S4_RESUME) {
    DEBUG ((DEBUG_INFO, "S4 Resume, Skip TPM PP process!\n"));
    return;
  }

  DEBUG ((DEBUG_INFO, "[TPM2PP] PPRequest=%x (PPRequestParameter=%x)\n", mPpi->Request, mPpi->RequestParameter));
  Tcg2ExecutePendingTpmRequest (PlatformAuth);
}

/**
  The handler for TPM physical presence function:
  Return TPM Operation Response to OS Environment.

  @param[out]     MostRecentRequest Most recent operation request.
  @param[out]     Response          Response to the most recent operation request.

  @return Return Code for Return TPM Operation Response to OS Environment.
**/
UINT32
EFIAPI
Tcg2PhysicalPresenceLibReturnOperationResponseToOsFunction (
  OUT UINT32  *MostRecentRequest,
  OUT UINT32  *Response
  )
{
  EFI_STATUS  Status;

  DEBUG ((DEBUG_INFO, "[TPM2PP] ReturnOperationResponseToOsFunction\n"));

  Status = QemuTpmInitPPI ();
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "[TPM2PP] no PPI\n"));
    *MostRecentRequest = 0;
    *Response          = 0;
    return TCG_PP_RETURN_TPM_OPERATION_RESPONSE_FAILURE;
  }

  *MostRecentRequest = mPpi->LastRequest;
  *Response          = mPpi->Response;

  return TCG_PP_RETURN_TPM_OPERATION_RESPONSE_SUCCESS;
}

/**
  The handler for TPM physical presence function:
  Submit TPM Operation Request to Pre-OS Environment and
  Submit TPM Operation Request to Pre-OS Environment 2.

  Caution: This function may receive untrusted input.

  @param[in]      OperationRequest TPM physical presence operation request.
  @param[in]      RequestParameter TPM physical presence operation request parameter.

  @return Return Code for Submit TPM Operation Request to Pre-OS Environment and
          Submit TPM Operation Request to Pre-OS Environment 2.
**/
UINT32
EFIAPI
Tcg2PhysicalPresenceLibSubmitRequestToPreOSFunction (
  IN UINT32  OperationRequest,
  IN UINT32  RequestParameter
  )
{
  EFI_STATUS  Status;

  DEBUG ((DEBUG_INFO, "[TPM2PP] SubmitRequestToPreOSFunction, Request = %x, %x\n", OperationRequest, RequestParameter));

  Status = QemuTpmInitPPI ();
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "[TPM2PP] no PPI\n"));
    return TCG_PP_SUBMIT_REQUEST_TO_PREOS_GENERAL_FAILURE;
  }

  mPpi->Request          = OperationRequest;
  mPpi->RequestParameter = RequestParameter;

  return TCG_PP_SUBMIT_REQUEST_TO_PREOS_SUCCESS;
}
