/** @file
  Implementation of the HII for the Opal UEFI Driver.

Copyright (c) 2016 - 2018, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "OpalHii.h"
//
// Character definitions
//
#define UPPER_LOWER_CASE_OFFSET 0x20

//
// This is the generated IFR binary Data for each formset defined in VFR.
// This Data array is ready to be used as input of HiiAddPackages() to
// create a packagelist (which contains Form packages, String packages, etc).
//
extern UINT8  OpalPasswordFormBin[];

//
// This is the generated String package Data for all .UNI files.
// This Data array is ready to be used as input of HiiAddPackages() to
// create a packagelist (which contains Form packages, String packages, etc).
//
extern UINT8  OpalPasswordDxeStrings[];

CHAR16  OpalPasswordStorageName[] = L"OpalHiiConfig";

EFI_HII_CONFIG_ACCESS_PROTOCOL gHiiConfigAccessProtocol;

//
// Handle to the list of HII packages (forms and strings) for this driver
//
EFI_HII_HANDLE gHiiPackageListHandle = NULL;

//
// Package List GUID containing all form and string packages
//
const EFI_GUID gHiiPackageListGuid = PACKAGE_LIST_GUID;
const EFI_GUID gHiiSetupVariableGuid = SETUP_VARIABLE_GUID;

//
// Structure that contains state of the HII
// This structure is updated by Hii.cpp and its contents
// is rendered in the HII.
//
OPAL_HII_CONFIGURATION gHiiConfiguration;

//
// The device path containing the VENDOR_DEVICE_PATH and EFI_DEVICE_PATH_PROTOCOL
//
HII_VENDOR_DEVICE_PATH gHiiVendorDevicePath = {
    {
        {
            HARDWARE_DEVICE_PATH,
            HW_VENDOR_DP,
            {
                (UINT8)(sizeof(VENDOR_DEVICE_PATH)),
                (UINT8)((sizeof(VENDOR_DEVICE_PATH)) >> 8)
            }
        },
        OPAL_PASSWORD_CONFIG_GUID
    },
    {
        END_DEVICE_PATH_TYPE,
        END_ENTIRE_DEVICE_PATH_SUBTYPE,
        {
            (UINT8)(END_DEVICE_PATH_LENGTH),
            (UINT8)((END_DEVICE_PATH_LENGTH) >> 8)
        }
    }
};

/**
  Get saved OPAL request.

  @param[in]  OpalDisk      The disk needs to get the saved OPAL request.
  @param[out] OpalRequest   OPAL request got.

**/
VOID
GetSavedOpalRequest (
  IN OPAL_DISK              *OpalDisk,
  OUT OPAL_REQUEST          *OpalRequest
  )
{
  EFI_STATUS                Status;
  OPAL_REQUEST_VARIABLE     *TempVariable;
  OPAL_REQUEST_VARIABLE     *Variable;
  UINTN                     VariableSize;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePathInVariable;
  UINTN                     DevicePathSizeInVariable;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
  UINTN                     DevicePathSize;

  DEBUG ((DEBUG_INFO, "%a() - enter\n", __FUNCTION__));

  Variable = NULL;
  VariableSize = 0;

  Status = GetVariable2 (
             OPAL_REQUEST_VARIABLE_NAME,
             &gHiiSetupVariableGuid,
             (VOID **) &Variable,
             &VariableSize
             );
  if (EFI_ERROR (Status) || (Variable == NULL)) {
    return;
  }

  TempVariable = Variable;
  while ((VariableSize > sizeof (OPAL_REQUEST_VARIABLE)) &&
         (VariableSize >= TempVariable->Length) &&
         (TempVariable->Length > sizeof (OPAL_REQUEST_VARIABLE))) {
    DevicePathInVariable = (EFI_DEVICE_PATH_PROTOCOL *) ((UINTN) TempVariable + sizeof (OPAL_REQUEST_VARIABLE));
    DevicePathSizeInVariable = GetDevicePathSize (DevicePathInVariable);
    DevicePath = OpalDisk->OpalDevicePath;
    DevicePathSize = GetDevicePathSize (DevicePath);
    if ((DevicePathSize == DevicePathSizeInVariable) &&
        (CompareMem (DevicePath, DevicePathInVariable, DevicePathSize) == 0)) {
      //
      // Found the node for the OPAL device.
      // Get the OPAL request.
      //
      CopyMem (OpalRequest, &TempVariable->OpalRequest, sizeof (OPAL_REQUEST));
      DEBUG ((
        DEBUG_INFO,
        "OpalRequest got: 0x%x\n",
        *OpalRequest
        ));
      break;
    }
    VariableSize -= TempVariable->Length;
    TempVariable = (OPAL_REQUEST_VARIABLE *) ((UINTN) TempVariable + TempVariable->Length);
  }

  FreePool (Variable);

  DEBUG ((DEBUG_INFO, "%a() - exit\n", __FUNCTION__));
}

/**
  Save OPAL request.

  @param[in] OpalDisk       The disk has OPAL request to save.
  @param[in] OpalRequest    OPAL request to save.

**/
VOID
SaveOpalRequest (
  IN OPAL_DISK              *OpalDisk,
  IN OPAL_REQUEST           OpalRequest
  )
{
  EFI_STATUS                Status;
  OPAL_REQUEST_VARIABLE     *TempVariable;
  UINTN                     TempVariableSize;
  OPAL_REQUEST_VARIABLE     *Variable;
  UINTN                     VariableSize;
  OPAL_REQUEST_VARIABLE     *NewVariable;
  UINTN                     NewVariableSize;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePathInVariable;
  UINTN                     DevicePathSizeInVariable;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
  UINTN                     DevicePathSize;

  DEBUG ((DEBUG_INFO, "%a() - enter\n", __FUNCTION__));

  DEBUG ((
    DEBUG_INFO,
    "OpalRequest to save: 0x%x\n",
    OpalRequest
    ));

  Variable = NULL;
  VariableSize = 0;
  NewVariable = NULL;
  NewVariableSize = 0;

  Status = GetVariable2 (
             OPAL_REQUEST_VARIABLE_NAME,
             &gHiiSetupVariableGuid,
             (VOID **) &Variable,
             &VariableSize
             );
  if (!EFI_ERROR (Status) && (Variable != NULL)) {
    TempVariable = Variable;
    TempVariableSize = VariableSize;
    while ((TempVariableSize > sizeof (OPAL_REQUEST_VARIABLE)) &&
           (TempVariableSize >= TempVariable->Length) &&
           (TempVariable->Length > sizeof (OPAL_REQUEST_VARIABLE))) {
      DevicePathInVariable = (EFI_DEVICE_PATH_PROTOCOL *) ((UINTN) TempVariable + sizeof (OPAL_REQUEST_VARIABLE));
      DevicePathSizeInVariable = GetDevicePathSize (DevicePathInVariable);
      DevicePath = OpalDisk->OpalDevicePath;
      DevicePathSize = GetDevicePathSize (DevicePath);
      if ((DevicePathSize == DevicePathSizeInVariable) &&
          (CompareMem (DevicePath, DevicePathInVariable, DevicePathSize) == 0)) {
        //
        // Found the node for the OPAL device.
        // Update the OPAL request.
        //
        CopyMem (&TempVariable->OpalRequest, &OpalRequest, sizeof (OPAL_REQUEST));
        NewVariable = Variable;
        NewVariableSize = VariableSize;
        break;
      }
      TempVariableSize -= TempVariable->Length;
      TempVariable = (OPAL_REQUEST_VARIABLE *) ((UINTN) TempVariable + TempVariable->Length);
    }
    if (NewVariable == NULL) {
      //
      // The node for the OPAL device is not found.
      // Create node for the OPAL device.
      //
      DevicePath = OpalDisk->OpalDevicePath;
      DevicePathSize = GetDevicePathSize (DevicePath);
      NewVariableSize = VariableSize + sizeof (OPAL_REQUEST_VARIABLE) + DevicePathSize;
      NewVariable = AllocatePool (NewVariableSize);
      ASSERT (NewVariable != NULL);
      CopyMem (NewVariable, Variable, VariableSize);
      TempVariable = (OPAL_REQUEST_VARIABLE *) ((UINTN) NewVariable + VariableSize);
      TempVariable->Length = (UINT32) (sizeof (OPAL_REQUEST_VARIABLE) + DevicePathSize);
      CopyMem (&TempVariable->OpalRequest, &OpalRequest, sizeof (OPAL_REQUEST));
      DevicePathInVariable = (EFI_DEVICE_PATH_PROTOCOL *) ((UINTN) TempVariable + sizeof (OPAL_REQUEST_VARIABLE));
      CopyMem (DevicePathInVariable, DevicePath, DevicePathSize);
    }
  } else {
    DevicePath = OpalDisk->OpalDevicePath;
    DevicePathSize = GetDevicePathSize (DevicePath);
    NewVariableSize = sizeof (OPAL_REQUEST_VARIABLE) + DevicePathSize;
    NewVariable = AllocatePool (NewVariableSize);
    ASSERT (NewVariable != NULL);
    NewVariable->Length = (UINT32) (sizeof (OPAL_REQUEST_VARIABLE) + DevicePathSize);
    CopyMem (&NewVariable->OpalRequest, &OpalRequest, sizeof (OPAL_REQUEST));
    DevicePathInVariable = (EFI_DEVICE_PATH_PROTOCOL *) ((UINTN) NewVariable + sizeof (OPAL_REQUEST_VARIABLE));
    CopyMem (DevicePathInVariable, DevicePath, DevicePathSize);
  }
  Status = gRT->SetVariable (
                  OPAL_REQUEST_VARIABLE_NAME,
                  (EFI_GUID *) &gHiiSetupVariableGuid,
                  EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS,
                  NewVariableSize,
                  NewVariable
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "OpalRequest variable set failed (%r)\n", Status));
  }
  if (NewVariable != Variable) {
    FreePool (NewVariable);
  }
  if (Variable != NULL) {
    FreePool (Variable);
  }

  DEBUG ((DEBUG_INFO, "%a() - exit\n", __FUNCTION__));
}

/**
  Sets the current system state of global config variables.

**/
VOID
HiiSetCurrentConfiguration(
  VOID
  )
{
  UINT32                                       PpStorageFlag;
  EFI_STRING                                   NewString;

  gHiiConfiguration.NumDisks = GetDeviceCount();

  //
  // Update the BlockSID status string.
  //
  PpStorageFlag = Tcg2PhysicalPresenceLibGetManagementFlags ();

  if ((PpStorageFlag & TCG2_BIOS_STORAGE_MANAGEMENT_FLAG_ENABLE_BLOCK_SID) != 0) {
    NewString = HiiGetString (gHiiPackageListHandle, STRING_TOKEN(STR_ENABLED), NULL);
    if (NewString == NULL) {
      DEBUG ((DEBUG_INFO,  "HiiSetCurrentConfiguration: HiiGetString( ) failed\n"));
      return;
    }
  } else {
    NewString = HiiGetString (gHiiPackageListHandle, STRING_TOKEN(STR_DISABLED), NULL);
    if (NewString == NULL) {
      DEBUG ((DEBUG_INFO,  "HiiSetCurrentConfiguration: HiiGetString( ) failed\n"));
      return;
    }
  }
  HiiSetString(gHiiPackageListHandle, STRING_TOKEN(STR_BLOCKSID_STATUS1), NewString, NULL);
  FreePool (NewString);

  if ((PpStorageFlag & TCG2_BIOS_STORAGE_MANAGEMENT_FLAG_PP_REQUIRED_FOR_ENABLE_BLOCK_SID) != 0) {
    NewString = HiiGetString (gHiiPackageListHandle, STRING_TOKEN(STR_DISK_INFO_ENABLE_BLOCKSID_TRUE), NULL);
    if (NewString == NULL) {
      DEBUG ((DEBUG_INFO,  "HiiSetCurrentConfiguration: HiiGetString( ) failed\n"));
      return;
    }
  } else {
    NewString = HiiGetString (gHiiPackageListHandle, STRING_TOKEN(STR_DISK_INFO_ENABLE_BLOCKSID_FALSE), NULL);
    if (NewString == NULL) {
      DEBUG ((DEBUG_INFO,  "HiiSetCurrentConfiguration: HiiGetString( ) failed\n"));
      return;
    }
  }
  HiiSetString(gHiiPackageListHandle, STRING_TOKEN(STR_BLOCKSID_STATUS2), NewString, NULL);
  FreePool (NewString);

  if ((PpStorageFlag & TCG2_BIOS_STORAGE_MANAGEMENT_FLAG_PP_REQUIRED_FOR_DISABLE_BLOCK_SID) != 0) {
    NewString = HiiGetString (gHiiPackageListHandle, STRING_TOKEN(STR_DISK_INFO_DISABLE_BLOCKSID_TRUE), NULL);
    if (NewString == NULL) {
      DEBUG ((DEBUG_INFO,  "HiiSetCurrentConfiguration: HiiGetString( ) failed\n"));
      return;
    }
  } else {
    NewString = HiiGetString (gHiiPackageListHandle, STRING_TOKEN(STR_DISK_INFO_DISABLE_BLOCKSID_FALSE), NULL);
    if (NewString == NULL) {
      DEBUG ((DEBUG_INFO,  "HiiSetCurrentConfiguration: HiiGetString( ) failed\n"));
      return;
    }
  }
  HiiSetString(gHiiPackageListHandle, STRING_TOKEN(STR_BLOCKSID_STATUS3), NewString, NULL);
  FreePool (NewString);
}

/**
  Install the HII related resources.

  @retval  EFI_SUCCESS        Install all the resources success.
  @retval  other              Error occur when install the resources.
**/
EFI_STATUS
HiiInstall(
  VOID
  )
{
  EFI_STATUS                   Status;
  EFI_HANDLE                   DriverHandle;

  //
  // Clear the global configuration.
  //
  ZeroMem(&gHiiConfiguration, sizeof(gHiiConfiguration));

  //
  // Obtain the driver handle that the BIOS assigned us
  //
  DriverHandle = HiiGetDriverImageHandleCB();

  //
  // Populate the config access protocol with the three functions we are publishing
  //
  gHiiConfigAccessProtocol.ExtractConfig = ExtractConfig;
  gHiiConfigAccessProtocol.RouteConfig = RouteConfig;
  gHiiConfigAccessProtocol.Callback = DriverCallback;

  //
  // Associate the required protocols with our driver handle
  //
  Status = gBS->InstallMultipleProtocolInterfaces(
               &DriverHandle,
               &gEfiHiiConfigAccessProtocolGuid,
               &gHiiConfigAccessProtocol,      // HII callback
               &gEfiDevicePathProtocolGuid,
               &gHiiVendorDevicePath,        // required for HII callback allow all disks to be shown in same hii
               NULL
           );

  if (EFI_ERROR(Status)) {
    return Status;
  }

  return OpalHiiAddPackages();
}

/**
  Install the HII form and string packages.

  @retval  EFI_SUCCESS           Install all the resources success.
  @retval  EFI_OUT_OF_RESOURCES  Out of resource error.
**/
EFI_STATUS
OpalHiiAddPackages(
  VOID
  )
{
  EFI_HANDLE                   DriverHandle;
  CHAR16                       *NewString;

  DriverHandle = HiiGetDriverImageHandleCB();

  //
  // Publish the HII form and HII string packages
  //
  gHiiPackageListHandle = HiiAddPackages(
                                &gHiiPackageListGuid,
                                DriverHandle,
                                OpalPasswordDxeStrings,
                                OpalPasswordFormBin,
                                (VOID*)NULL
                                );

  //
  // Make sure the packages installed successfully
  //
  if (gHiiPackageListHandle == NULL) {
    DEBUG ((DEBUG_INFO, "OpalHiiAddPackages failed\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Update Version String in main window
  //
  NewString = HiiGetDriverNameCB ();
  if (HiiSetString(gHiiPackageListHandle, STRING_TOKEN(STR_MAIN_OPAL_VERSION), NewString, NULL) == 0) {
    DEBUG ((DEBUG_INFO,  "OpalHiiAddPackages: HiiSetString( ) failed\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}

/**
  Uninstall the HII capability.

  @retval  EFI_SUCCESS           Uninstall all the resources success.
  @retval  others                Other errors occur when unistall the hii resource.
**/
EFI_STATUS
HiiUninstall(
  VOID
  )
{
  EFI_STATUS                   Status;

  //
  // Remove the packages we've provided to the BIOS
  //
  HiiRemovePackages(gHiiPackageListHandle);

  //
  // Remove the protocols from our driver handle
  //
  Status = gBS->UninstallMultipleProtocolInterfaces(
                          HiiGetDriverImageHandleCB(),
                          &gEfiHiiConfigAccessProtocolGuid,
                          &gHiiConfigAccessProtocol,        // HII callback
                          &gEfiDevicePathProtocolGuid,
                          &gHiiVendorDevicePath,            // required for HII callback
                          NULL
                      );
  if (EFI_ERROR(Status)) {
    DEBUG ((DEBUG_INFO, "Cannot uninstall Hii Protocols: %r\n", Status));
  }

  return Status;
}

/**
  Updates the main menu form.

  @retval  EFI_SUCCESS           update the main form success.
**/
EFI_STATUS
HiiPopulateMainMenuForm (
  VOID
  )
{
  UINT8         Index;
  CHAR8         *DiskName;
  EFI_STRING_ID DiskNameId;
  OPAL_DISK     *OpalDisk;

  HiiSetCurrentConfiguration();

  gHiiConfiguration.SupportedDisks = 0;

  for (Index = 0; Index < gHiiConfiguration.NumDisks; Index++) {
    OpalDisk = HiiGetOpalDiskCB (Index);
    if ((OpalDisk != NULL) && OpalFeatureSupported (&OpalDisk->SupportedAttributes)) {
      gHiiConfiguration.SupportedDisks |= (1 << Index);
      DiskNameId = GetDiskNameStringId (Index);
      DiskName = HiiDiskGetNameCB (Index);
      if ((DiskName == NULL) || (DiskNameId == 0)) {
        return EFI_UNSUPPORTED;
      }
      HiiSetFormString(DiskNameId, DiskName);
    }
  }

  OpalHiiSetBrowserData ();
  return EFI_SUCCESS;
}

/**
  Get disk name string id.

  @param   DiskIndex             The input disk index info.

  @retval  The disk name string id.

**/
EFI_STRING_ID
GetDiskNameStringId(
  UINT8 DiskIndex
  )
{
  switch (DiskIndex) {
    case 0: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_0);
    case 1: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_1);
    case 2: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_2);
    case 3: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_3);
    case 4: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_4);
    case 5: return STRING_TOKEN(STR_MAIN_GOTO_DISK_INFO_5);
  }
  return 0;
}

/**
  Confirm whether user truly want to do the revert action.

  @param     OpalDisk            The device which need to do the revert action.

  @retval  EFI_SUCCESS           Confirmed user want to do the revert action.
**/
EFI_STATUS
HiiConfirmRevertAction (
  IN OPAL_DISK                  *OpalDisk

  )
{
  CHAR16                        Unicode[512];
  EFI_INPUT_KEY                 Key;
  CHAR16                        ApproveResponse;
  CHAR16                        RejectResponse;

  //
  // When the estimate cost time bigger than MAX_ACCEPTABLE_REVERTING_TIME, pop up dialog to let user confirm
  // the revert action.
  //
  if (OpalDisk->EstimateTimeCost < MAX_ACCEPTABLE_REVERTING_TIME) {
    return EFI_SUCCESS;
  }

  ApproveResponse = L'Y';
  RejectResponse  = L'N';

  UnicodeSPrint(Unicode, StrSize(L"WARNING: Revert device needs about ####### seconds"), L"WARNING: Revert device needs about %d seconds", OpalDisk->EstimateTimeCost);

  do {
    CreatePopUp(
        EFI_LIGHTGRAY | EFI_BACKGROUND_BLUE,
        &Key,
        Unicode,
        L" System should not be powered off until revert completion ",
        L" ",
        L" Press 'Y/y' to continue, press 'N/n' to cancal ",
        NULL
    );
  } while (
      ((Key.UnicodeChar | UPPER_LOWER_CASE_OFFSET) != (ApproveResponse | UPPER_LOWER_CASE_OFFSET)) &&
      ((Key.UnicodeChar | UPPER_LOWER_CASE_OFFSET) != (RejectResponse | UPPER_LOWER_CASE_OFFSET))
    );

  if ((Key.UnicodeChar | UPPER_LOWER_CASE_OFFSET) == (RejectResponse | UPPER_LOWER_CASE_OFFSET)) {
    return EFI_ABORTED;
  }

  return EFI_SUCCESS;
}

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

  @param  This                   Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param  Action                 Specifies the type of action taken by the browser.
  @param  QuestionId             A unique value which is sent to the original
                                 exporting driver so that it can identify the type
                                 of data to expect.
  @param  Type                   The type of value for the question.
  @param  Value                  A pointer to the data being sent to the original
                                 exporting driver.
  @param  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
DriverCallback(
  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL    *This,
  EFI_BROWSER_ACTION                      Action,
  EFI_QUESTION_ID                         QuestionId,
  UINT8                                   Type,
  EFI_IFR_TYPE_VALUE                      *Value,
  EFI_BROWSER_ACTION_REQUEST              *ActionRequest
  )
{
  HII_KEY    HiiKey;
  UINT8      HiiKeyId;
  UINT32     PpRequest;
  OPAL_DISK  *OpalDisk;

  if (ActionRequest != NULL) {
    *ActionRequest = EFI_BROWSER_ACTION_REQUEST_NONE;
  } else {
    return EFI_INVALID_PARAMETER;
  }

  //
  // If QuestionId is an auto-generated key (label, empty line, etc.), ignore it.
  //
  if ((QuestionId & HII_KEY_FLAG) == 0) {
    return EFI_SUCCESS;
  }

  HiiKey.Raw = QuestionId;
  HiiKeyId   = (UINT8) HiiKey.KeyBits.Id;

  if (Action == EFI_BROWSER_ACTION_FORM_OPEN) {
    switch (HiiKeyId) {
      case HII_KEY_ID_VAR_SUPPORTED_DISKS:
        DEBUG ((DEBUG_INFO,  "HII_KEY_ID_VAR_SUPPORTED_DISKS\n"));
        return HiiPopulateMainMenuForm ();

      case HII_KEY_ID_VAR_SELECTED_DISK_AVAILABLE_ACTIONS:
        DEBUG ((DEBUG_INFO,  "HII_KEY_ID_VAR_SELECTED_DISK_AVAILABLE_ACTIONS\n"));
        return HiiPopulateDiskInfoForm();
    }
  } else if (Action == EFI_BROWSER_ACTION_CHANGING) {
    switch (HiiKeyId) {
      case HII_KEY_ID_GOTO_DISK_INFO:
        return HiiSelectDisk((UINT8)HiiKey.KeyBits.Index);

      case HII_KEY_ID_REVERT:
      case HII_KEY_ID_PSID_REVERT:
        OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);
        if (OpalDisk != NULL) {
          return HiiConfirmRevertAction (OpalDisk);
        } else {
          ASSERT (FALSE);
          return EFI_SUCCESS;
        }

    }
  } else if (Action == EFI_BROWSER_ACTION_CHANGED) {
    switch (HiiKeyId) {
      case HII_KEY_ID_BLOCKSID:
        switch (Value->u8) {
          case 0:
            PpRequest = TCG2_PHYSICAL_PRESENCE_NO_ACTION;
            break;

          case 1:
            PpRequest = TCG2_PHYSICAL_PRESENCE_ENABLE_BLOCK_SID;
            break;

          case 2:
            PpRequest = TCG2_PHYSICAL_PRESENCE_DISABLE_BLOCK_SID;
            break;

          case 3:
            PpRequest = TCG2_PHYSICAL_PRESENCE_SET_PP_REQUIRED_FOR_ENABLE_BLOCK_SID_FUNC_TRUE;
            break;

          case 4:
            PpRequest = TCG2_PHYSICAL_PRESENCE_SET_PP_REQUIRED_FOR_ENABLE_BLOCK_SID_FUNC_FALSE;
            break;

          case 5:
            PpRequest = TCG2_PHYSICAL_PRESENCE_SET_PP_REQUIRED_FOR_DISABLE_BLOCK_SID_FUNC_TRUE;
            break;

          case 6:
            PpRequest = TCG2_PHYSICAL_PRESENCE_SET_PP_REQUIRED_FOR_DISABLE_BLOCK_SID_FUNC_FALSE;
            break;

          default:
            PpRequest = TCG2_PHYSICAL_PRESENCE_NO_ACTION;
            DEBUG ((DEBUG_ERROR, "Invalid value input!\n"));
            break;
        }
        HiiSetBlockSidAction(PpRequest);

        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        return EFI_SUCCESS;

      case HII_KEY_ID_SET_ADMIN_PWD:
        DEBUG ((DEBUG_INFO,  "HII_KEY_ID_SET_ADMIN_PWD\n"));
        gHiiConfiguration.OpalRequest.SetAdminPwd = Value->b;
        OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);
        if (OpalDisk != NULL) {
          SaveOpalRequest (OpalDisk, gHiiConfiguration.OpalRequest);
        }
        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        return EFI_SUCCESS;

      case HII_KEY_ID_SET_USER_PWD:
        DEBUG ((DEBUG_INFO,  "HII_KEY_ID_SET_USER_PWD\n"));
        gHiiConfiguration.OpalRequest.SetUserPwd = Value->b;
        OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);
        if (OpalDisk != NULL) {
          SaveOpalRequest (OpalDisk, gHiiConfiguration.OpalRequest);
        }
        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        return EFI_SUCCESS;

      case HII_KEY_ID_SECURE_ERASE:
        DEBUG ((DEBUG_INFO,  "HII_KEY_ID_SECURE_ERASE\n"));
        gHiiConfiguration.OpalRequest.SecureErase = Value->b;
        OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);
        if (OpalDisk != NULL) {
          SaveOpalRequest (OpalDisk, gHiiConfiguration.OpalRequest);
        }
        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        return EFI_SUCCESS;

      case HII_KEY_ID_REVERT:
        DEBUG ((DEBUG_INFO,  "HII_KEY_ID_REVERT\n"));
        gHiiConfiguration.OpalRequest.Revert = Value->b;
        OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);
        if (OpalDisk != NULL) {
          SaveOpalRequest (OpalDisk, gHiiConfiguration.OpalRequest);
        }
        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        return EFI_SUCCESS;
      case HII_KEY_ID_KEEP_USER_DATA:
        DEBUG ((DEBUG_INFO,  "HII_KEY_ID_KEEP_USER_DATA\n"));
        gHiiConfiguration.OpalRequest.KeepUserData = Value->b;
        OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);
        if (OpalDisk != NULL) {
          SaveOpalRequest (OpalDisk, gHiiConfiguration.OpalRequest);
        }
        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        return EFI_SUCCESS;

      case HII_KEY_ID_PSID_REVERT:
        DEBUG ((DEBUG_INFO,  "HII_KEY_ID_PSID_REVERT\n"));
        gHiiConfiguration.OpalRequest.PsidRevert = Value->b;
        OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);
        if (OpalDisk != NULL) {
          SaveOpalRequest (OpalDisk, gHiiConfiguration.OpalRequest);
        }
        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        return EFI_SUCCESS;

      case HII_KEY_ID_DISABLE_USER:
        DEBUG ((DEBUG_INFO,  "HII_KEY_ID_DISABLE_USER\n"));
        gHiiConfiguration.OpalRequest.DisableUser = Value->b;
        OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);
        if (OpalDisk != NULL) {
          SaveOpalRequest (OpalDisk, gHiiConfiguration.OpalRequest);
        }
        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        return EFI_SUCCESS;

      case HII_KEY_ID_ENABLE_FEATURE:
        DEBUG ((DEBUG_INFO,  "HII_KEY_ID_ENABLE_FEATURE\n"));
        gHiiConfiguration.OpalRequest.EnableFeature = Value->b;
        OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);
        if (OpalDisk != NULL) {
          SaveOpalRequest (OpalDisk, gHiiConfiguration.OpalRequest);
        }
        *ActionRequest = EFI_BROWSER_ACTION_REQUEST_FORM_APPLY;
        return EFI_SUCCESS;

      default:
        break;
    }
  }

  return EFI_UNSUPPORTED;
}

/**
  Update the global Disk index info.

  @param   Index             The input disk index info.

  @retval  EFI_SUCCESS       Update the disk index info success.

**/
EFI_STATUS
HiiSelectDisk(
  UINT8 Index
  )
{
  OpalHiiGetBrowserData();
  gHiiConfiguration.SelectedDiskIndex = Index;
  OpalHiiSetBrowserData ();

  return EFI_SUCCESS;
}

/**
  Draws the disk info form.

  @retval  EFI_SUCCESS       Draw the disk info success.

**/
EFI_STATUS
HiiPopulateDiskInfoForm(
  VOID
  )
{
  OPAL_DISK*                    OpalDisk;
  OPAL_DISK_ACTIONS             AvailActions;
  TCG_RESULT                    Ret;
  CHAR8                         *DiskName;

  OpalHiiGetBrowserData();

  DiskName = HiiDiskGetNameCB (gHiiConfiguration.SelectedDiskIndex);
  if (DiskName == NULL) {
    return EFI_UNSUPPORTED;
  }
  HiiSetFormString(STRING_TOKEN(STR_DISK_INFO_SELECTED_DISK_NAME), DiskName);

  gHiiConfiguration.SelectedDiskAvailableActions = HII_ACTION_NONE;
  ZeroMem (&gHiiConfiguration.OpalRequest, sizeof (OPAL_REQUEST));
  gHiiConfiguration.KeepUserDataForced = FALSE;

  OpalDisk = HiiGetOpalDiskCB(gHiiConfiguration.SelectedDiskIndex);

  if (OpalDisk != NULL) {
    OpalDiskUpdateStatus (OpalDisk);
    Ret = OpalSupportGetAvailableActions(&OpalDisk->SupportedAttributes, &OpalDisk->LockingFeature, OpalDisk->Owner, &AvailActions);
    if (Ret == TcgResultSuccess) {
      //
      // Update actions, always allow PSID Revert
      //
      gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.PsidRevert == 1) ? HII_ACTION_PSID_REVERT : HII_ACTION_NONE;

      //
      // Always allow unlock to handle device migration
      //
      gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.Unlock == 1) ? HII_ACTION_UNLOCK : HII_ACTION_NONE;

      if (!OpalFeatureEnabled (&OpalDisk->SupportedAttributes, &OpalDisk->LockingFeature)) {
        if (OpalDisk->Owner == OpalOwnershipNobody) {
          gHiiConfiguration.SelectedDiskAvailableActions |= HII_ACTION_ENABLE_FEATURE;

          //
          // Update strings
          //
          HiiSetFormString( STRING_TOKEN(STR_DISK_INFO_PSID_REVERT), "PSID Revert to factory default");
        } else {
          DEBUG ((DEBUG_INFO, "Feature disabled but ownership != nobody\n"));
        }
      } else {
        gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.Revert == 1) ? HII_ACTION_REVERT : HII_ACTION_NONE;
        gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.AdminPass == 1) ? HII_ACTION_SET_ADMIN_PWD : HII_ACTION_NONE;
        gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.UserPass == 1) ? HII_ACTION_SET_USER_PWD : HII_ACTION_NONE;
        gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.SecureErase == 1) ? HII_ACTION_SECURE_ERASE : HII_ACTION_NONE;
        gHiiConfiguration.SelectedDiskAvailableActions |= (AvailActions.DisableUser == 1) ? HII_ACTION_DISABLE_USER : HII_ACTION_NONE;

        HiiSetFormString (STRING_TOKEN(STR_DISK_INFO_PSID_REVERT), "PSID Revert to factory default and Disable");

        //
        // Determine revert options for disk
        // Default initialize keep user Data to be true
        //
        gHiiConfiguration.OpalRequest.KeepUserData = 1;
        if (AvailActions.RevertKeepDataForced) {
          gHiiConfiguration.KeepUserDataForced = TRUE;
        }
      }
    }

    GetSavedOpalRequest (OpalDisk, &gHiiConfiguration.OpalRequest);
  }

  //
  // Pass the current configuration to the BIOS
  //
  OpalHiiSetBrowserData ();

  return EFI_SUCCESS;
}

/**
  Send BlockSid request through TPM physical presence module.

  @param   PpRequest         TPM physical presence operation request.

  @retval  EFI_SUCCESS       Do the required action success.
  @retval  Others            Other error occur.

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

  ReturnCode = Tcg2PhysicalPresenceLibSubmitRequestToPreOSFunction (PpRequest, 0);
  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  This                   Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param  Configuration          A null-terminated Unicode string in <ConfigResp>
                                 format.
  @param  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
RouteConfig(
  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL    *This,
  CONST EFI_STRING                        Configuration,
  EFI_STRING                              *Progress
  )
{
  if (Configuration == NULL || Progress == NULL) {
    return (EFI_INVALID_PARAMETER);
  }

  *Progress = Configuration;
  if (!HiiIsConfigHdrMatch (Configuration, &gHiiSetupVariableGuid, OpalPasswordStorageName)) {
    return EFI_NOT_FOUND;
  }

  *Progress = Configuration + StrLen (Configuration);

  return EFI_SUCCESS;
}

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

  @param  This                   Points to the EFI_HII_CONFIG_ACCESS_PROTOCOL.
  @param  Request                A null-terminated Unicode string in
                                 <ConfigRequest> format.
  @param  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  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
ExtractConfig(
  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL    *This,
  CONST EFI_STRING                        Request,
  EFI_STRING                              *Progress,
  EFI_STRING                              *Results
  )
{
  EFI_STATUS                              Status;
  EFI_STRING                              ConfigRequest;
  EFI_STRING                              ConfigRequestHdr;
  UINTN                                   BufferSize;
  UINTN                                   Size;
  BOOLEAN                                 AllocatedRequest;
  EFI_HANDLE                              DriverHandle;

  //
  // Check for valid parameters
  //
  if (Progress == NULL || Results == NULL) {
    return (EFI_INVALID_PARAMETER);
  }

  *Progress = Request;
  if ((Request != NULL) &&
    !HiiIsConfigHdrMatch (Request, &gHiiSetupVariableGuid, OpalPasswordStorageName)) {
    return EFI_NOT_FOUND;
  }

  AllocatedRequest = FALSE;
  BufferSize = sizeof (OPAL_HII_CONFIGURATION);
  ConfigRequest = Request;
  if ((Request == NULL) || (StrStr (Request, L"OFFSET") == NULL)) {
    //
    // Request has no request element, construct full request string.
    // Allocate and fill a buffer large enough to hold the <ConfigHdr> template
    // followed by "&OFFSET=0&WIDTH=WWWWWWWWWWWWWWWW" followed by a Null-terminator
    //
    DriverHandle = HiiGetDriverImageHandleCB();
    ConfigRequestHdr = HiiConstructConfigHdr (&gHiiSetupVariableGuid, OpalPasswordStorageName, DriverHandle);
    Size = (StrLen (ConfigRequestHdr) + 32 + 1) * sizeof (CHAR16);
    ConfigRequest = AllocateZeroPool (Size);
    if (ConfigRequest == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
    AllocatedRequest = TRUE;
    UnicodeSPrint (ConfigRequest, Size, L"%s&OFFSET=0&WIDTH=%016LX", ConfigRequestHdr, (UINT64)BufferSize);
    FreePool (ConfigRequestHdr);
  }

  //
  // Convert Buffer Data to <ConfigResp> by helper function BlockToConfig( )
  //
  Status = gHiiConfigRouting->BlockToConfig(
               gHiiConfigRouting,
               ConfigRequest,
               (UINT8*)&gHiiConfiguration,
               sizeof(OPAL_HII_CONFIGURATION),
               Results,
               Progress
           );

  //
  // Free the allocated config request string.
  //
  if (AllocatedRequest) {
    FreePool (ConfigRequest);
    ConfigRequest = NULL;
  }

  //
  // Set Progress string to the original request string.
  //
  if (Request == NULL) {
    *Progress = NULL;
  } else if (StrStr (Request, L"OFFSET") == NULL) {
    *Progress = Request + StrLen (Request);
  }

  return (Status);
}


/**

  Pass the current system state to the bios via the hii_G_Configuration.

**/
VOID
OpalHiiSetBrowserData (
  VOID
  )
{
  HiiSetBrowserData(
      &gHiiSetupVariableGuid,
      (CHAR16*)L"OpalHiiConfig",
      sizeof(gHiiConfiguration),
      (UINT8*)&gHiiConfiguration,
      NULL
  );
}


/**

  Populate the hii_g_Configuraton with the browser Data.

**/
VOID
OpalHiiGetBrowserData (
  VOID
  )
{
  HiiGetBrowserData(
      &gHiiSetupVariableGuid,
      (CHAR16*)L"OpalHiiConfig",
      sizeof(gHiiConfiguration),
      (UINT8*)&gHiiConfiguration
  );
}

/**
  Set a string Value in a form.

  @param      DestStringId   The stringid which need to update.
  @param      SrcAsciiStr    The string nned to update.

  @retval  EFI_SUCCESS       Do the required action success.
  @retval  Others            Other error occur.

**/
EFI_STATUS
HiiSetFormString(
  EFI_STRING_ID       DestStringId,
  CHAR8               *SrcAsciiStr
  )
{
  UINT32                    Len;
  UINT32                    UniSize;
  CHAR16*                   UniStr;

  //
  // Determine the Length of the sting
  //
  Len = ( UINT32 )AsciiStrLen( SrcAsciiStr );

  //
  // Allocate space for the unicode string, including terminator
  //
  UniSize = (Len + 1) * sizeof(CHAR16);
  UniStr = (CHAR16*)AllocateZeroPool(UniSize);

  //
  // Copy into unicode string, then copy into string id
  //
  AsciiStrToUnicodeStrS ( SrcAsciiStr, UniStr, Len + 1);

  //
  // Update the string in the form
  //
  if (HiiSetString(gHiiPackageListHandle, DestStringId, UniStr, NULL) == 0) {
    DEBUG ((DEBUG_INFO,  "HiiSetFormString( ) failed\n"));
    FreePool(UniStr);
    return (EFI_OUT_OF_RESOURCES);
  }

  //
  // Free the memory
  //
  FreePool(UniStr);

  return (EFI_SUCCESS);
}

/**
  Initialize the Opal disk base on the hardware info get from device.

  @param Dev                  The Opal device.

  @retval EFI_SUCESS          Initialize the device success.
  @retval EFI_DEVICE_ERROR    Get info from device failed.

**/
EFI_STATUS
OpalDiskInitialize (
  IN OPAL_DRIVER_DEVICE          *Dev
  )
{
  TCG_RESULT                  TcgResult;
  OPAL_SESSION                Session;
  UINT8                       ActiveDataRemovalMechanism;
  UINT32                      RemovalMechanishLists[ResearvedMechanism];

  ZeroMem(&Dev->OpalDisk, sizeof(OPAL_DISK));
  Dev->OpalDisk.Sscp = Dev->Sscp;
  Dev->OpalDisk.MediaId = Dev->MediaId;
  Dev->OpalDisk.OpalDevicePath = Dev->OpalDevicePath;

  ZeroMem(&Session, sizeof(Session));
  Session.Sscp = Dev->Sscp;
  Session.MediaId = Dev->MediaId;

  TcgResult = OpalGetSupportedAttributesInfo (&Session, &Dev->OpalDisk.SupportedAttributes, &Dev->OpalDisk.OpalBaseComId);
  if (TcgResult != TcgResultSuccess) {
    return EFI_DEVICE_ERROR;
  }
  Session.OpalBaseComId = Dev->OpalDisk.OpalBaseComId;

  TcgResult = OpalUtilGetMsid (&Session, Dev->OpalDisk.Msid, OPAL_MSID_LENGHT, &Dev->OpalDisk.MsidLength);
  if (TcgResult != TcgResultSuccess) {
    return EFI_DEVICE_ERROR;
  }

  if (Dev->OpalDisk.SupportedAttributes.DataRemoval) {
    TcgResult = OpalUtilGetDataRemovalMechanismLists (&Session, RemovalMechanishLists);
    if (TcgResult != TcgResultSuccess) {
      return EFI_DEVICE_ERROR;
    }

    TcgResult = OpalUtilGetActiveDataRemovalMechanism (&Session, Dev->OpalDisk.Msid, Dev->OpalDisk.MsidLength, &ActiveDataRemovalMechanism);
    if (TcgResult != TcgResultSuccess) {
      return EFI_DEVICE_ERROR;
    }

    Dev->OpalDisk.EstimateTimeCost = RemovalMechanishLists[ActiveDataRemovalMechanism];
  }

  return OpalDiskUpdateStatus (&Dev->OpalDisk);
}

/**
  Update the device info.

  @param OpalDisk                The Opal device.

  @retval EFI_SUCESS             Initialize the device success.
  @retval EFI_DEVICE_ERROR       Get info from device failed.
  @retval EFI_INVALID_PARAMETER  Not get Msid info before get ownership info.

**/
EFI_STATUS
OpalDiskUpdateStatus (
  OPAL_DISK        *OpalDisk
  )
{
  TCG_RESULT                  TcgResult;
  OPAL_SESSION                Session;

  ZeroMem(&Session, sizeof(Session));
  Session.Sscp = OpalDisk->Sscp;
  Session.MediaId = OpalDisk->MediaId;
  Session.OpalBaseComId = OpalDisk->OpalBaseComId;

  TcgResult = OpalGetLockingInfo(&Session, &OpalDisk->LockingFeature);
  if (TcgResult != TcgResultSuccess) {
    return EFI_DEVICE_ERROR;
  }

  if (OpalDisk->MsidLength == 0) {
    return EFI_INVALID_PARAMETER;
  } else {
    //
    // Base on the Msid info to get the ownership, so Msid info must get first.
    //
    OpalDisk->Owner = OpalUtilDetermineOwnership(&Session, OpalDisk->Msid, OpalDisk->MsidLength);
  }

  return EFI_SUCCESS;
}

