/** @file
  Legacy Boot Maintenance UI implementation.

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

**/


#include "LegacyBootMaintUi.h"

LEGACY_BOOT_OPTION_CALLBACK_DATA  *mLegacyBootOptionPrivate = NULL;
EFI_GUID  mLegacyBootOptionGuid     = LEGACY_BOOT_OPTION_FORMSET_GUID;
CHAR16    mLegacyBootStorageName[]  = L"LegacyBootData";
BBS_TYPE  mBbsType[] = {BBS_FLOPPY, BBS_HARDDISK, BBS_CDROM, BBS_EMBED_NETWORK, BBS_BEV_DEVICE, BBS_UNKNOWN};
BOOLEAN   mFirstEnterLegacyForm = FALSE;


///
/// Legacy FD Info from LegacyBios.GetBbsInfo()
///
LEGACY_MENU_OPTION      LegacyFDMenu = {
  LEGACY_MENU_OPTION_SIGNATURE,
  {NULL},
  0
};

///
/// Legacy HD Info from LegacyBios.GetBbsInfo()
///
LEGACY_MENU_OPTION      LegacyHDMenu = {
  LEGACY_MENU_OPTION_SIGNATURE,
  {NULL},
  0
};

///
/// Legacy CD Info from LegacyBios.GetBbsInfo()
///
LEGACY_MENU_OPTION      LegacyCDMenu = {
  LEGACY_MENU_OPTION_SIGNATURE,
  {NULL},
  0
};

///
/// Legacy NET Info from LegacyBios.GetBbsInfo()
///
LEGACY_MENU_OPTION      LegacyNETMenu = {
  LEGACY_MENU_OPTION_SIGNATURE,
  {NULL},
  0
};

///
/// Legacy NET Info from LegacyBios.GetBbsInfo()
///
LEGACY_MENU_OPTION      LegacyBEVMenu = {
  LEGACY_MENU_OPTION_SIGNATURE,
  {NULL},
  0
};


VOID                *mLegacyStartOpCodeHandle = NULL;
VOID                *mLegacyEndOpCodeHandle = NULL;
EFI_IFR_GUID_LABEL  *mLegacyStartLabel = NULL;
EFI_IFR_GUID_LABEL  *mLegacyEndLabel = NULL;


HII_VENDOR_DEVICE_PATH  mLegacyBootOptionHiiVendorDevicePath = {
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_VENDOR_DP,
      {
        (UINT8) (sizeof (VENDOR_DEVICE_PATH)),
        (UINT8) ((sizeof (VENDOR_DEVICE_PATH)) >> 8)
      }
    },
    { 0x6bc75598, 0x89b4, 0x483d, { 0x91, 0x60, 0x7f, 0x46, 0x9a, 0x96, 0x35, 0x31 } }
  },
  {
    END_DEVICE_PATH_TYPE,
    END_ENTIRE_DEVICE_PATH_SUBTYPE,
    {
      (UINT8) (END_DEVICE_PATH_LENGTH),
      (UINT8) ((END_DEVICE_PATH_LENGTH) >> 8)
    }
  }
};

/**

  Build the LegacyFDMenu LegacyHDMenu LegacyCDMenu according to LegacyBios.GetBbsInfo().

**/
VOID
GetLegacyOptions (
  VOID
  );


/**

  Base on the L"LegacyDevOrder" variable to build the current order data.

**/
VOID
GetLegacyOptionsOrder (
  VOID
  );

/**
  Re-order the Boot Option according to the DevOrder.

  The routine re-orders the Boot Option in BootOption array according to
  the order specified by DevOrder.

  @param DevOrder           Pointer to buffer containing the BBS Index,
                            high 8-bit value 0xFF indicating a disabled boot option
  @param DevOrderCount      Count of the BBS Index
  @param EnBootOption       Callee allocated buffer containing the enabled Boot Option Numbers
  @param EnBootOptionCount  Count of the enabled Boot Option Numbers
  @param DisBootOption      Callee allocated buffer containing the disabled Boot Option Numbers
  @param DisBootOptionCount Count of the disabled Boot Option Numbers

  @return EFI_SUCCESS       The function completed successfully.
  @retval other             Contain some error, details see  the status return by gRT->SetVariable.
**/
EFI_STATUS
OrderLegacyBootOption4SameType (
  UINT16                   *DevOrder,
  UINTN                    DevOrderCount,
  UINT16                   **EnBootOption,
  UINTN                    *EnBootOptionCount,
  UINT16                   **DisBootOption,
  UINTN                    *DisBootOptionCount
  )
{
  EFI_STATUS               Status;
  UINT16                   *NewBootOption;
  UINT16                   *BootOrder;
  UINTN                    BootOrderSize;
  UINTN                    Index;
  UINTN                    StartPosition;

  EFI_BOOT_MANAGER_LOAD_OPTION    BootOption;

  CHAR16                           OptionName[sizeof ("Boot####")];
  UINT16                   *BbsIndexArray;
  UINT16                   *DeviceTypeArray;

  GetEfiGlobalVariable2 (L"BootOrder", (VOID **) &BootOrder, &BootOrderSize);
  ASSERT (BootOrder != NULL);

  BbsIndexArray       = AllocatePool (BootOrderSize);
  DeviceTypeArray     = AllocatePool (BootOrderSize);
  *EnBootOption       = AllocatePool (BootOrderSize);
  *DisBootOption      = AllocatePool (BootOrderSize);
  *DisBootOptionCount = 0;
  *EnBootOptionCount  = 0;
  Index               = 0;
  Status              = EFI_SUCCESS;

  ASSERT (BbsIndexArray != NULL);
  ASSERT (DeviceTypeArray != NULL);
  ASSERT (*EnBootOption != NULL);
  ASSERT (*DisBootOption != NULL);

  for (Index = 0; Index < BootOrderSize / sizeof (UINT16); Index++) {

    UnicodeSPrint (OptionName, sizeof (OptionName), L"Boot%04x", BootOrder[Index]);
    Status = EfiBootManagerVariableToLoadOption (OptionName, &BootOption);
    ASSERT_EFI_ERROR (Status);

    if ((DevicePathType (BootOption.FilePath) == BBS_DEVICE_PATH) &&
        (DevicePathSubType (BootOption.FilePath) == BBS_BBS_DP)) {
      //
      // Legacy Boot Option
      //
      ASSERT (BootOption.OptionalDataSize == sizeof (LEGACY_BOOT_OPTION_BBS_DATA));

      DeviceTypeArray[Index] = ((BBS_BBS_DEVICE_PATH *) BootOption.FilePath)->DeviceType;
      BbsIndexArray  [Index] = ((LEGACY_BOOT_OPTION_BBS_DATA *) BootOption.OptionalData)->BbsIndex;
    } else {
      DeviceTypeArray[Index] = BBS_TYPE_UNKNOWN;
      BbsIndexArray  [Index] = 0xFFFF;
    }
    EfiBootManagerFreeLoadOption (&BootOption);
  }

  //
  // Record the corresponding Boot Option Numbers according to the DevOrder
  // Record the EnBootOption and DisBootOption according to the DevOrder
  //
  StartPosition = BootOrderSize / sizeof (UINT16);
  NewBootOption = AllocatePool (DevOrderCount * sizeof (UINT16));
  ASSERT (NewBootOption != NULL);
  while (DevOrderCount-- != 0) {
    for (Index = 0; Index < BootOrderSize / sizeof (UINT16); Index++) {
      if (BbsIndexArray[Index] == (DevOrder[DevOrderCount] & 0xFF)) {
        StartPosition = MIN (StartPosition, Index);
        NewBootOption[DevOrderCount] = BootOrder[Index];

        if ((DevOrder[DevOrderCount] & 0xFF00) == 0xFF00) {
          (*DisBootOption)[*DisBootOptionCount] = BootOrder[Index];
          (*DisBootOptionCount)++;
        } else {
          (*EnBootOption)[*EnBootOptionCount] = BootOrder[Index];
          (*EnBootOptionCount)++;
        }
        break;
      }
    }
  }

  //
  // Overwrite the old BootOption
  //
  CopyMem (&BootOrder[StartPosition], NewBootOption, (*DisBootOptionCount + *EnBootOptionCount) * sizeof (UINT16));
  Status = gRT->SetVariable (
                  L"BootOrder",
                  &gEfiGlobalVariableGuid,
                  VAR_FLAG,
                  BootOrderSize,
                  BootOrder
                  );

  FreePool (NewBootOption);
  FreePool (DeviceTypeArray);
  FreePool (BbsIndexArray);

  return Status;
}

/**
  Update the legacy BBS boot option. L"LegacyDevOrder" and gEfiLegacyDevOrderVariableGuid EFI Variable
  is updated with the new Legacy Boot order. The EFI Variable of "Boot####" and gEfiGlobalVariableGuid
  is also updated.

  @param NVMapData   The data for legacy BBS boot.

  @return EFI_SUCCESS           The function completed successfully.
  @retval EFI_NOT_FOUND         If L"LegacyDevOrder" and gEfiLegacyDevOrderVariableGuid EFI Variable can not be found.
  @retval EFI_OUT_OF_RESOURCES  Fail to allocate memory resource
  @retval other                 Contain some error, details see  the status return by gRT->SetVariable.
**/
EFI_STATUS
UpdateBBSOption (
  IN LEGACY_BOOT_NV_DATA            *NVMapData
  )
{
  UINTN                       Index;
  UINTN                       Index2;
  UINTN                       CurrentType;
  VOID                        *BootOptionVar;
  CHAR16                      VarName[100];
  UINTN                       OptionSize;
  EFI_STATUS                  Status;
  UINT32                      *Attribute;
  LEGACY_MENU_OPTION          *OptionMenu;
  UINT16                      *LegacyDev;
  UINT16                      *InitialLegacyDev;
  UINT8                       *VarData;
  UINTN                       VarSize;
  LEGACY_DEV_ORDER_ENTRY      *DevOrder;
  UINT8                       *OriginalPtr;
  UINT8                       *DisMap;
  UINTN                       Pos;
  UINTN                       Bit;
  UINT16                      *NewOrder;
  UINT16                      Tmp;
  UINT16                      *EnBootOption;
  UINTN                       EnBootOptionCount;
  UINT16                      *DisBootOption;
  UINTN                       DisBootOptionCount;
  UINTN                       BufferSize;


  DisMap              = NULL;
  NewOrder            = NULL;
  CurrentType         = 0;
  EnBootOption        = NULL;
  DisBootOption       = NULL;


  DisMap  = mLegacyBootOptionPrivate->MaintainMapData->DisableMap;
  Status  = EFI_SUCCESS;

  //
  // Update the Variable "LegacyDevOrder"
  //
  GetVariable2 (VAR_LEGACY_DEV_ORDER, &gEfiLegacyDevOrderVariableGuid, (VOID **) &VarData, &VarSize);
  if (VarData == NULL) {
    return EFI_NOT_FOUND;
  }
  OriginalPtr = VarData;

  while (mBbsType[CurrentType] != BBS_UNKNOWN) {
    switch (mBbsType[CurrentType]) {
    case BBS_FLOPPY:
      OptionMenu            = (LEGACY_MENU_OPTION *) &LegacyFDMenu;
      LegacyDev             = NVMapData->LegacyFD;
      InitialLegacyDev     = mLegacyBootOptionPrivate->MaintainMapData->InitialNvData.LegacyFD;
      BufferSize            = sizeof (NVMapData->LegacyFD);
      break;

    case BBS_HARDDISK:
      OptionMenu            = (LEGACY_MENU_OPTION *) &LegacyHDMenu;
      LegacyDev             = NVMapData->LegacyHD;
      InitialLegacyDev     = mLegacyBootOptionPrivate->MaintainMapData->InitialNvData.LegacyHD;

      BufferSize            = sizeof (NVMapData->LegacyHD);
      break;

    case BBS_CDROM:
      OptionMenu            = (LEGACY_MENU_OPTION *) &LegacyCDMenu;
      LegacyDev             = NVMapData->LegacyCD;
      InitialLegacyDev     = mLegacyBootOptionPrivate->MaintainMapData->InitialNvData.LegacyCD;
      BufferSize            = sizeof (NVMapData->LegacyCD);
      break;

    case BBS_EMBED_NETWORK:
      OptionMenu            = (LEGACY_MENU_OPTION *) &LegacyNETMenu;
      LegacyDev             = NVMapData->LegacyNET;
      InitialLegacyDev     = mLegacyBootOptionPrivate->MaintainMapData->InitialNvData.LegacyNET;
      BufferSize            = sizeof (NVMapData->LegacyNET);
      break;

    default:
      ASSERT (mBbsType[CurrentType] == BBS_BEV_DEVICE);
      OptionMenu            = (LEGACY_MENU_OPTION *) &LegacyBEVMenu;
      LegacyDev             = NVMapData->LegacyBEV;
      InitialLegacyDev     = mLegacyBootOptionPrivate->MaintainMapData->InitialNvData.LegacyBEV;
      BufferSize            = sizeof (NVMapData->LegacyBEV);
      break;
    }

    //
    // Check whether has value changed.
    //
    if (CompareMem (LegacyDev, InitialLegacyDev, BufferSize) == 0) {
      CurrentType++;
      continue;
    }

    DevOrder    = (LEGACY_DEV_ORDER_ENTRY *) OriginalPtr;
    while (VarData < OriginalPtr + VarSize) {
      if (DevOrder->BbsType == mBbsType[CurrentType]) {
        break;
      }

      VarData += sizeof (BBS_TYPE) + DevOrder->Length;
      DevOrder = (LEGACY_DEV_ORDER_ENTRY *) VarData;
    }

    if (VarData >= OriginalPtr + VarSize) {
      FreePool (OriginalPtr);
      return EFI_NOT_FOUND;
    }

    NewOrder = AllocateZeroPool (DevOrder->Length - sizeof (DevOrder->Length));
    if (NewOrder == NULL) {
      FreePool (OriginalPtr);
      return EFI_OUT_OF_RESOURCES;
    }

    for (Index = 0; Index < OptionMenu->MenuNumber; Index++) {
      if (0xFF == LegacyDev[Index]) {
        break;
      }

      NewOrder[Index] = LegacyDev[Index];
    }

    //
    // Only the enable/disable state of each boot device with same device type can be changed,
    // so we can count on the index information in DevOrder.
    // DisMap bit array is the only reliable source to check a device's en/dis state,
    // so we use DisMap to set en/dis state of each item in NewOrder array
    //
    for (Index2 = 0; Index2 < OptionMenu->MenuNumber; Index2++) {
      Tmp = (UINT16) (DevOrder->Data[Index2] & 0xFF);
      Pos = Tmp / 8;
      Bit = 7 - (Tmp % 8);
      if ((DisMap[Pos] & (1 << Bit)) != 0) {
        NewOrder[Index] = (UINT16) (0xFF00 | Tmp);
        Index++;
      }
    }

    CopyMem (
      DevOrder->Data,
      NewOrder,
      DevOrder->Length - sizeof (DevOrder->Length)
      );
    FreePool (NewOrder);

    //
    // Update BootOrder and Boot####.Attribute
    //
    // 1. Re-order the Option Number in BootOrder according to Legacy Dev Order
    //
    ASSERT (OptionMenu->MenuNumber == DevOrder->Length / sizeof (UINT16) - 1);

    Status = OrderLegacyBootOption4SameType (
      DevOrder->Data,
      DevOrder->Length / sizeof (UINT16) - 1,
      &EnBootOption,
      &EnBootOptionCount,
      &DisBootOption,
      &DisBootOptionCount
      );
     if (EFI_ERROR(Status)) {
       goto Fail;
     }

    //
    // 2. Deactivate the DisBootOption and activate the EnBootOption
    //
    for (Index = 0; Index < DisBootOptionCount; Index++) {
      UnicodeSPrint (VarName, sizeof (VarName), L"Boot%04x", DisBootOption[Index]);
      GetEfiGlobalVariable2 (VarName, (VOID **) &BootOptionVar, &OptionSize);
      if (BootOptionVar != NULL) {
        Attribute   = (UINT32 *) BootOptionVar;
        *Attribute &= ~LOAD_OPTION_ACTIVE;

        Status = gRT->SetVariable (
                        VarName,
                        &gEfiGlobalVariableGuid,
                        VAR_FLAG,
                        OptionSize,
                        BootOptionVar
                        );

        FreePool (BootOptionVar);
      }
    }

    for (Index = 0; Index < EnBootOptionCount; Index++) {
      UnicodeSPrint (VarName, sizeof (VarName), L"Boot%04x", EnBootOption[Index]);
      GetEfiGlobalVariable2 (VarName, (VOID **) &BootOptionVar, &OptionSize);
      if (BootOptionVar != NULL) {
        Attribute   = (UINT32 *) BootOptionVar;
        *Attribute |= LOAD_OPTION_ACTIVE;

        Status = gRT->SetVariable (
                        VarName,
                        &gEfiGlobalVariableGuid,
                        VAR_FLAG,
                        OptionSize,
                        BootOptionVar
                        );

        FreePool (BootOptionVar);
      }
    }


    FreePool (EnBootOption);
    FreePool (DisBootOption);

    CurrentType++;
  }

  Status = gRT->SetVariable (
                  VAR_LEGACY_DEV_ORDER,
                  &gEfiLegacyDevOrderVariableGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                  VarSize,
                  OriginalPtr
                  );

Fail:
  if (EnBootOption != NULL) {
    FreePool (EnBootOption);
  }

  if (DisBootOption != NULL) {
    FreePool (DisBootOption);
  }

  FreePool (OriginalPtr);
  return Status;
}

/**
  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
LegacyBootOptionExtractConfig (
  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;
}

/**
  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
LegacyBootOptionRouteConfig (
  IN  CONST EFI_HII_CONFIG_ACCESS_PROTOCOL   *This,
  IN  CONST EFI_STRING                       Configuration,
  OUT       EFI_STRING                       *Progress
  )
{
  EFI_STATUS                      Status;
  EFI_HII_CONFIG_ROUTING_PROTOCOL *ConfigRouting;
  LEGACY_BOOT_NV_DATA             *CurrentNVMapData;
  UINTN                           BufferSize;


  if (Configuration == NULL || Progress == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  *Progress = Configuration;

  //
  // Check routing data in <ConfigHdr>.
  // Note: there is no name for Name/Value storage, only GUID will be checked
  //
  if (!HiiIsConfigHdrMatch (Configuration, &mLegacyBootOptionGuid, mLegacyBootStorageName)) {
    return EFI_NOT_FOUND;
  }

  Status = gBS->LocateProtocol (
                  &gEfiHiiConfigRoutingProtocolGuid,
                  NULL,
                  (VOID **) &ConfigRouting
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Convert <ConfigResp> to buffer data by helper function ConfigToBlock()
  //
  CurrentNVMapData = &mLegacyBootOptionPrivate->MaintainMapData->CurrentNvData;
  Status = ConfigRouting->ConfigToBlock (
                            ConfigRouting,
                            Configuration,
                            (UINT8 *) CurrentNVMapData,
                            &BufferSize,
                            Progress
                            );
  ASSERT_EFI_ERROR (Status);

  Status = UpdateBBSOption (CurrentNVMapData);

  return Status;
}

/**
  Refresh the global UpdateData structure.

**/
VOID
RefreshLegacyUpdateData (
  VOID
  )
{
  //
  // Free current updated date
  //
  if (mLegacyStartOpCodeHandle != NULL) {
    HiiFreeOpCodeHandle (mLegacyStartOpCodeHandle);
  }
  if (mLegacyEndOpCodeHandle != NULL) {
    HiiFreeOpCodeHandle (mLegacyEndOpCodeHandle);
  }

  //
  // Create new OpCode Handle
  //
  mLegacyStartOpCodeHandle = HiiAllocateOpCodeHandle ();
  mLegacyEndOpCodeHandle = HiiAllocateOpCodeHandle ();

  //
  // Create Hii Extend Label OpCode as the start opcode
  //
  mLegacyStartLabel = (EFI_IFR_GUID_LABEL *) HiiCreateGuidOpCode (
                                         mLegacyStartOpCodeHandle,
                                         &gEfiIfrTianoGuid,
                                         NULL,
                                         sizeof (EFI_IFR_GUID_LABEL)
                                         );
  mLegacyStartLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;

  mLegacyStartLabel->Number = FORM_BOOT_LEGACY_DEVICE_ID;

  //
  // Create Hii Extend Label OpCode as the start opcode
  //
  mLegacyEndLabel = (EFI_IFR_GUID_LABEL *) HiiCreateGuidOpCode (
                                         mLegacyEndOpCodeHandle,
                                         &gEfiIfrTianoGuid,
                                         NULL,
                                         sizeof (EFI_IFR_GUID_LABEL)
                                         );
  mLegacyEndLabel->ExtendOpCode = EFI_IFR_EXTEND_OP_LABEL;

  mLegacyEndLabel->Number = FORM_BOOT_LEGACY_LABEL_END;

}

/**
  Get the Menu Entry from the list in Menu Entry List.

  If MenuNumber is great or equal to the number of Menu
  Entry in the list, then ASSERT.

  @param MenuOption      The Menu Entry List to read the menu entry.
  @param MenuNumber      The index of Menu Entry.

  @return The Menu Entry.

**/
LEGACY_MENU_ENTRY *
GetMenuEntry (
  LEGACY_MENU_OPTION      *MenuOption,
  UINTN                   MenuNumber
  )
{
  LEGACY_MENU_ENTRY   *NewMenuEntry;
  UINTN               Index;
  LIST_ENTRY          *List;

  ASSERT (MenuNumber < MenuOption->MenuNumber);

  List = MenuOption->Head.ForwardLink;
  for (Index = 0; Index < MenuNumber; Index++) {
    List = List->ForwardLink;
  }

  NewMenuEntry = CR (List, LEGACY_MENU_ENTRY, Link, LEGACY_MENU_ENTRY_SIGNATURE);

  return NewMenuEntry;
}

/**
  Create string tokens for a menu from its help strings and display strings

  @param HiiHandle          Hii Handle of the package to be updated.
  @param MenuOption         The Menu whose string tokens need to be created

**/
VOID
CreateLegacyMenuStringToken (
  IN EFI_HII_HANDLE                   HiiHandle,
  IN LEGACY_MENU_OPTION               *MenuOption
  )
{
  LEGACY_MENU_ENTRY *NewMenuEntry;
  UINTN             Index;

  for (Index = 0; Index < MenuOption->MenuNumber; Index++) {
    NewMenuEntry = GetMenuEntry (MenuOption, Index);

    NewMenuEntry->DisplayStringToken = HiiSetString (
                                         HiiHandle,
                                         0,
                                         NewMenuEntry->DisplayString,
                                         NULL
                                         );

    if (NULL == NewMenuEntry->HelpString) {
      NewMenuEntry->HelpStringToken = NewMenuEntry->DisplayStringToken;
    } else {
      NewMenuEntry->HelpStringToken = HiiSetString (
                                        HiiHandle,
                                        0,
                                        NewMenuEntry->HelpString,
                                        NULL
                                        );
    }
  }
}

/**
  Create a dynamic page so that Legacy Device boot order
  can be set for specified device type.

  @param UpdatePageId    The form ID. It also specifies the legacy device type.


**/
VOID
UpdateLegacyDeviceOrderPage (
  IN UINT16                           UpdatePageId
  )
{
  LEGACY_MENU_OPTION          *OptionMenu;
  LEGACY_MENU_ENTRY           *NewMenuEntry;
  EFI_STRING_ID               StrRef;
  EFI_STRING_ID               StrRefHelp;
  UINT16                      *Default;
  UINT16                      Index;
  UINT16                      Key;
  CHAR16                      String[100];
  CHAR16                      *TypeStr;
  CHAR16                      *TypeStrHelp;
  CHAR16                      *FormTitle;
  VOID                        *OptionsOpCodeHandle;
  VOID                        *DefaultOpCodeHandle;

  Key         = 0;
  StrRef      = 0;
  StrRefHelp  = 0;
  OptionMenu  = NULL;
  TypeStr     = NULL;
  TypeStrHelp = NULL;
  Default     = NULL;

  RefreshLegacyUpdateData();

  //
  // Create oneof option list
  //
  switch (UpdatePageId) {
  case FORM_FLOPPY_BOOT_ID:
    OptionMenu  = (LEGACY_MENU_OPTION *) &LegacyFDMenu;
    Key         = (UINT16) LEGACY_FD_QUESTION_ID;
    TypeStr     = STR_FLOPPY;
    TypeStrHelp = STR_FLOPPY_HELP;
    FormTitle   = STR_FLOPPY_TITLE;
    Default     = mLegacyBootOptionPrivate->MaintainMapData->CurrentNvData.LegacyFD;
    break;

  case FORM_HARDDISK_BOOT_ID:
    OptionMenu  = (LEGACY_MENU_OPTION *) &LegacyHDMenu;
    Key         = (UINT16) LEGACY_HD_QUESTION_ID;
    TypeStr     = STR_HARDDISK;
    TypeStrHelp = STR_HARDDISK_HELP;
    FormTitle   = STR_HARDDISK_TITLE;
    Default     = mLegacyBootOptionPrivate->MaintainMapData->CurrentNvData.LegacyHD;
    break;

  case FORM_CDROM_BOOT_ID:
    OptionMenu  = (LEGACY_MENU_OPTION *) &LegacyCDMenu;
    Key         = (UINT16) LEGACY_CD_QUESTION_ID;
    TypeStr     = STR_CDROM;
    TypeStrHelp = STR_CDROM_HELP;
    FormTitle   = STR_CDROM_TITLE;
    Default     = mLegacyBootOptionPrivate->MaintainMapData->CurrentNvData.LegacyCD;
    break;

  case FORM_NET_BOOT_ID:
    OptionMenu  = (LEGACY_MENU_OPTION *) &LegacyNETMenu;
    Key         = (UINT16) LEGACY_NET_QUESTION_ID;
    TypeStr     = STR_NET;
    TypeStrHelp = STR_NET_HELP;
    FormTitle   = STR_NET_TITLE;
    Default     = mLegacyBootOptionPrivate->MaintainMapData->CurrentNvData.LegacyNET;
    break;

  case FORM_BEV_BOOT_ID:
    OptionMenu  = (LEGACY_MENU_OPTION *) &LegacyBEVMenu;
    Key         = (UINT16) LEGACY_BEV_QUESTION_ID;
    TypeStr     = STR_BEV;
    TypeStrHelp = STR_BEV_HELP;
    FormTitle   = STR_BEV_TITLE;
    Default     = mLegacyBootOptionPrivate->MaintainMapData->CurrentNvData.LegacyBEV;
    break;

  default:
    DEBUG ((DEBUG_ERROR, "Invalid command ID for updating page!\n"));
    return;
  }

  HiiSetString (mLegacyBootOptionPrivate->HiiHandle, STRING_TOKEN(STR_ORDER_CHANGE_PROMPT), FormTitle, NULL);

  CreateLegacyMenuStringToken (mLegacyBootOptionPrivate->HiiHandle, OptionMenu);

  OptionsOpCodeHandle = HiiAllocateOpCodeHandle ();
  ASSERT (OptionsOpCodeHandle != NULL);


  for (Index = 0; Index < OptionMenu->MenuNumber; Index++) {
    NewMenuEntry = GetMenuEntry (OptionMenu, Index);
    //
    // Create OneOf for each legacy device
    //
    HiiCreateOneOfOptionOpCode (
      OptionsOpCodeHandle,
      NewMenuEntry->DisplayStringToken,
      0,
      EFI_IFR_TYPE_NUM_SIZE_16,
      ((LEGACY_DEVICE_CONTEXT *) NewMenuEntry->VariableContext)->BbsIndex
      );
  }

  //
  // Create OneOf for item "Disabled"
  //
  HiiCreateOneOfOptionOpCode (
    OptionsOpCodeHandle,
    STRING_TOKEN (STR_DISABLE_LEGACY_DEVICE),
    0,
    EFI_IFR_TYPE_NUM_SIZE_16,
    0xFF
    );

  //
  // Create oneof tag here for FD/HD/CD #1 #2
  //
  for (Index = 0; Index < OptionMenu->MenuNumber; Index++) {
    DefaultOpCodeHandle = HiiAllocateOpCodeHandle ();
    ASSERT (DefaultOpCodeHandle != NULL);

    HiiCreateDefaultOpCode (
      DefaultOpCodeHandle,
      EFI_HII_DEFAULT_CLASS_STANDARD,
      EFI_IFR_TYPE_NUM_SIZE_16,
      *Default++
      );

    //
    // Create the string for oneof tag
    //
    UnicodeSPrint (String, sizeof (String), TypeStr, Index);
    StrRef = HiiSetString (mLegacyBootOptionPrivate->HiiHandle, 0, String, NULL);

    UnicodeSPrint (String, sizeof (String), TypeStrHelp, Index);
    StrRefHelp = HiiSetString (mLegacyBootOptionPrivate->HiiHandle, 0, String, NULL);

    HiiCreateOneOfOpCode (
      mLegacyStartOpCodeHandle,
      (EFI_QUESTION_ID) (Key + Index),
      VARSTORE_ID_LEGACY_BOOT,
      (UINT16) (Key + Index * 2 - CONFIG_OPTION_OFFSET),
      StrRef,
      StrRefHelp,
      EFI_IFR_FLAG_CALLBACK,
      EFI_IFR_NUMERIC_SIZE_2,
      OptionsOpCodeHandle,
      DefaultOpCodeHandle //NULL //
      );

    HiiFreeOpCodeHandle (DefaultOpCodeHandle);
  }

  HiiUpdateForm (
    mLegacyBootOptionPrivate->HiiHandle,
    &mLegacyBootOptionGuid,
    LEGACY_ORDER_CHANGE_FORM_ID,
    mLegacyStartOpCodeHandle,
    mLegacyEndOpCodeHandle
    );

  HiiFreeOpCodeHandle (OptionsOpCodeHandle);
}


/**
  Adjust question value when one question value has been changed.

  @param QuestionId    The question id for the value changed question.
  @param Value         The value for the changed question.

**/
VOID
AdjustOptionValue (
  IN  UINT16                                 QuestionId,
  IN  EFI_IFR_TYPE_VALUE                     *Value
  )
{
  UINTN                       Number;
  UINT16                      *Default;
  LEGACY_BOOT_NV_DATA         *CurrentNVMap;
  UINT16                      *CurrentVal;
  UINTN                       Index;
  UINTN                       Index2;
  UINTN                       Index3;
  UINTN                       NewValuePos;
  UINTN                       OldValue;
  UINTN                       NewValue;
  UINT8                       *DisMap;
  UINTN                       Pos;
  UINTN                       Bit;

  Number = 0;
  CurrentVal = 0;
  Default = NULL;
  NewValue = 0;
  NewValuePos = 0;
  OldValue = 0;

  //
  // Update Select FD/HD/CD/NET/BEV Order Form
  //
  ASSERT ((QuestionId >= LEGACY_FD_QUESTION_ID) && (QuestionId < LEGACY_BEV_QUESTION_ID + MAX_MENU_NUMBER));

  CurrentNVMap = &mLegacyBootOptionPrivate->MaintainMapData->CurrentNvData;
  HiiGetBrowserData (&mLegacyBootOptionGuid, mLegacyBootStorageName, sizeof (LEGACY_BOOT_NV_DATA), (UINT8 *) CurrentNVMap);
  DisMap  = mLegacyBootOptionPrivate->MaintainMapData->DisableMap;

  if (QuestionId >= LEGACY_FD_QUESTION_ID && QuestionId < LEGACY_FD_QUESTION_ID + MAX_MENU_NUMBER) {
    Number      = (UINT16) LegacyFDMenu.MenuNumber;
    CurrentVal  = CurrentNVMap->LegacyFD;
    Default     = mLegacyBootOptionPrivate->MaintainMapData->LastTimeNvData.LegacyFD;
  } else if (QuestionId >= LEGACY_HD_QUESTION_ID && QuestionId < LEGACY_HD_QUESTION_ID + MAX_MENU_NUMBER) {
    Number      = (UINT16) LegacyHDMenu.MenuNumber;
    CurrentVal  = CurrentNVMap->LegacyHD;
    Default     = mLegacyBootOptionPrivate->MaintainMapData->LastTimeNvData.LegacyHD;
  } else if (QuestionId >= LEGACY_CD_QUESTION_ID && QuestionId < LEGACY_CD_QUESTION_ID + MAX_MENU_NUMBER) {
    Number      = (UINT16) LegacyCDMenu.MenuNumber;
    CurrentVal  = CurrentNVMap->LegacyCD;
    Default     = mLegacyBootOptionPrivate->MaintainMapData->LastTimeNvData.LegacyCD;
  } else if (QuestionId >= LEGACY_NET_QUESTION_ID && QuestionId < LEGACY_NET_QUESTION_ID + MAX_MENU_NUMBER) {
    Number      = (UINT16) LegacyNETMenu.MenuNumber;
    CurrentVal  = CurrentNVMap->LegacyNET;
    Default     = mLegacyBootOptionPrivate->MaintainMapData->LastTimeNvData.LegacyNET;
  } else if (QuestionId >= LEGACY_BEV_QUESTION_ID && QuestionId < LEGACY_BEV_QUESTION_ID + MAX_MENU_NUMBER) {
    Number      = (UINT16) LegacyBEVMenu.MenuNumber;
    CurrentVal  = CurrentNVMap->LegacyBEV;
    Default     = mLegacyBootOptionPrivate->MaintainMapData->LastTimeNvData.LegacyBEV;
  }

  //
  //  First, find the different position
  //  if there is change, it should be only one
  //
  for (Index = 0; Index < Number; Index++) {
    if (CurrentVal[Index] != Default[Index]) {
      OldValue  = Default[Index];
      NewValue  = CurrentVal[Index];
      break;
    }
  }

  if (Index != Number) {
    //
    // there is change, now process
    //
    if (0xFF == NewValue) {
      //
      // This item will be disable
      // Just move the items behind this forward to overlap it
      //
      Pos = OldValue / 8;
      Bit = 7 - (OldValue % 8);
      DisMap[Pos] = (UINT8) (DisMap[Pos] | (UINT8) (1 << Bit));
      for (Index2 = Index; Index2 < Number - 1; Index2++) {
        CurrentVal[Index2] = CurrentVal[Index2 + 1];
      }

      CurrentVal[Index2] = 0xFF;
    } else {
      for (Index2 = 0; Index2 < Number; Index2++) {
        if (Index2 == Index) {
          continue;
        }

        if (Default[Index2] == NewValue) {
          //
          // If NewValue is in OldLegacyDev array
          // remember its old position
          //
          NewValuePos = Index2;
          break;
        }
      }

      if (Index2 != Number) {
        //
        // We will change current item to an existing item
        // (It's hard to describe here, please read code, it's like a cycle-moving)
        //
        for (Index2 = NewValuePos; Index2 != Index;) {
          if (NewValuePos < Index) {
            CurrentVal[Index2] = Default[Index2 + 1];
            Index2++;
          } else {
            CurrentVal[Index2] = Default[Index2 - 1];
            Index2--;
          }
        }
      } else {
        //
        // If NewValue is not in OldlegacyDev array, we are changing to a disabled item
        // so we should modify DisMap to reflect the change
        //
        Pos = NewValue / 8;
        Bit = 7 - (NewValue % 8);
        DisMap[Pos] = (UINT8) (DisMap[Pos] & (~ (UINT8) (1 << Bit)));
        if (0xFF != OldValue) {
          //
          // Because NewValue is a item that was disabled before
          // so after changing the OldValue should be disabled
          // actually we are doing a swap of enable-disable states of two items
          //
          Pos = OldValue / 8;
          Bit = 7 - (OldValue % 8);
          DisMap[Pos] = (UINT8) (DisMap[Pos] | (UINT8) (1 << Bit));
        }
      }
    }
    //
    // To prevent DISABLE appears in the middle of the list
    // we should perform a re-ordering
    //
    Index3 = Index;
    Index = 0;
    while (Index < Number) {
      if (0xFF != CurrentVal[Index]) {
        Index++;
        continue;
      }

      Index2 = Index;
      Index2++;
      while (Index2 < Number) {
        if (0xFF != CurrentVal[Index2]) {
          break;
        }

        Index2++;
      }

      if (Index2 < Number) {
        CurrentVal[Index]   = CurrentVal[Index2];
        CurrentVal[Index2]  = 0xFF;
      }

      Index++;
    }

    //
    // Return correct question value.
    //
    Value->u16 = CurrentVal[Index3];
    CopyMem (Default, CurrentVal, sizeof (UINT16) * Number);
  }

  //
  // Pass changed uncommitted data back to Form Browser
  //
  HiiSetBrowserData (&mLegacyBootOptionGuid, mLegacyBootStorageName, sizeof (LEGACY_BOOT_NV_DATA), (UINT8 *) CurrentNVMap, NULL);
}

/**
  This call back function is registered with Boot Manager formset.
  When user selects a boot option, this call back function will
  be triggered. The boot option is saved for later processing.


  @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_INVALID_PARAMETER The setup browser call this function with invalid parameters.

**/
EFI_STATUS
EFIAPI
LegacyBootOptionCallback (
  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
  )
{
  if (Action != EFI_BROWSER_ACTION_CHANGED && Action != EFI_BROWSER_ACTION_CHANGING && Action != EFI_BROWSER_ACTION_FORM_OPEN) {
    //
    // Do nothing for other UEFI Action. Only do call back when data is changed or the form is open.
    //
    return EFI_UNSUPPORTED;
  }

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

  if (Action == EFI_BROWSER_ACTION_FORM_OPEN) {
    if (QuestionId == FORM_FLOPPY_BOOT_ID) {
      if (!mFirstEnterLegacyForm) {
        //
        // The legacyBootMaintUiLib depends on the LegacyBootManagerLib to realize its functionality.
        // We need to do the legacy boot options related actions after the LegacyBootManagerLib has been initialized.
        // Opening the legacy menus is the appropriate time that the LegacyBootManagerLib has already been initialized.
        //
        mFirstEnterLegacyForm = TRUE;
        GetLegacyOptions ();
        GetLegacyOptionsOrder ();
      }
    }
  }

  if (Action == EFI_BROWSER_ACTION_CHANGING) {
    switch (QuestionId) {
    case FORM_FLOPPY_BOOT_ID:
    case FORM_HARDDISK_BOOT_ID:
    case FORM_CDROM_BOOT_ID:
    case FORM_NET_BOOT_ID:
    case FORM_BEV_BOOT_ID:
      UpdateLegacyDeviceOrderPage (QuestionId);
      break;

    default:
      break;
    }
  } else if (Action == EFI_BROWSER_ACTION_CHANGED) {
    if ((Value == NULL) || (ActionRequest == NULL)) {
      return EFI_INVALID_PARAMETER;
    }

    if ((QuestionId >= LEGACY_FD_QUESTION_ID) && (QuestionId < LEGACY_BEV_QUESTION_ID + MAX_MENU_NUMBER)) {
      AdjustOptionValue(QuestionId, Value);
    }
  }
  return EFI_SUCCESS;
}


/**
  Create a menu entry by given menu type.

  @param MenuType        The Menu type to be created.

  @retval NULL           If failed to create the menu.
  @return the new menu entry.

**/
LEGACY_MENU_ENTRY *
CreateMenuEntry (
  VOID
  )
{
  LEGACY_MENU_ENTRY *MenuEntry;

  //
  // Create new menu entry
  //
  MenuEntry = AllocateZeroPool (sizeof (LEGACY_MENU_ENTRY));
  if (MenuEntry == NULL) {
    return NULL;
  }

  MenuEntry->VariableContext = AllocateZeroPool (sizeof (LEGACY_DEVICE_CONTEXT));
  if (MenuEntry->VariableContext == NULL) {
    FreePool (MenuEntry);
    return NULL;
  }

  MenuEntry->Signature        = LEGACY_MENU_ENTRY_SIGNATURE;
  return MenuEntry;
}

/**

  Base on the L"LegacyDevOrder" variable to build the current order data.

**/
VOID
GetLegacyOptionsOrder (
  VOID
  )
{
  UINTN                       VarSize;
  UINT8                       *VarData;
  UINT8                       *VarTmp;
  LEGACY_DEV_ORDER_ENTRY      *DevOrder;
  UINT16                      *LegacyDev;
  UINTN                       Index;
  LEGACY_MENU_OPTION          *OptionMenu;
  UINT16                      VarDevOrder;
  UINTN                       Pos;
  UINTN                       Bit;
  UINT8                       *DisMap;
  UINTN                       TotalLength;

  LegacyDev = NULL;
  OptionMenu = NULL;

  DisMap = ZeroMem (mLegacyBootOptionPrivate->MaintainMapData->DisableMap, sizeof (mLegacyBootOptionPrivate->MaintainMapData->DisableMap));

  //
  // Get Device Order from variable
  //
  GetVariable2 (VAR_LEGACY_DEV_ORDER, &gEfiLegacyDevOrderVariableGuid, (VOID **) &VarData, &VarSize);
  VarTmp = VarData;
  if (NULL != VarData) {
    DevOrder    = (LEGACY_DEV_ORDER_ENTRY *) VarData;
    while (VarData < VarTmp + VarSize) {
      switch (DevOrder->BbsType) {
      case BBS_FLOPPY:
        LegacyDev = mLegacyBootOptionPrivate->MaintainMapData->InitialNvData.LegacyFD;
        OptionMenu = &LegacyFDMenu;
        break;

      case BBS_HARDDISK:
        LegacyDev = mLegacyBootOptionPrivate->MaintainMapData->InitialNvData.LegacyHD;
        OptionMenu = &LegacyHDMenu;
        break;

      case BBS_CDROM:
        LegacyDev = mLegacyBootOptionPrivate->MaintainMapData->InitialNvData.LegacyCD;
        OptionMenu = &LegacyCDMenu;
        break;

      case BBS_EMBED_NETWORK:
        LegacyDev = mLegacyBootOptionPrivate->MaintainMapData->InitialNvData.LegacyNET;
        OptionMenu = &LegacyNETMenu;
        break;

      case BBS_BEV_DEVICE:
        LegacyDev = mLegacyBootOptionPrivate->MaintainMapData->InitialNvData.LegacyBEV;
        OptionMenu = &LegacyBEVMenu;
        break;

      case BBS_UNKNOWN:
      default:
        ASSERT (FALSE);
        DEBUG ((DEBUG_ERROR, "Unsupported device type found!\n"));
        break;
      }

      //
      // Create oneof tag here for FD/HD/CD #1 #2
      //
      for (Index = 0; Index < OptionMenu->MenuNumber; Index++) {
        TotalLength = sizeof (BBS_TYPE) + sizeof (UINT16) + Index * sizeof (UINT16);
        VarDevOrder = *(UINT16 *) ((UINT8 *) DevOrder + TotalLength);

        if (0xFF00 == (VarDevOrder & 0xFF00)) {
          LegacyDev[Index]  = 0xFF;
          Pos               = (VarDevOrder & 0xFF) / 8;
          Bit               = 7 - ((VarDevOrder & 0xFF) % 8);
          DisMap[Pos]       = (UINT8) (DisMap[Pos] | (UINT8) (1 << Bit));
        } else {
          LegacyDev[Index] = VarDevOrder & 0xFF;
        }
      }

      VarData ++;
      VarData += *(UINT16 *) VarData;
      DevOrder = (LEGACY_DEV_ORDER_ENTRY *) VarData;
    }
  }

  CopyMem (&mLegacyBootOptionPrivate->MaintainMapData->LastTimeNvData, &mLegacyBootOptionPrivate->MaintainMapData->InitialNvData, sizeof (LEGACY_BOOT_NV_DATA));
  CopyMem (&mLegacyBootOptionPrivate->MaintainMapData->CurrentNvData, &mLegacyBootOptionPrivate->MaintainMapData->InitialNvData, sizeof (LEGACY_BOOT_NV_DATA));
}

/**

  Build the LegacyFDMenu LegacyHDMenu LegacyCDMenu according to LegacyBios.GetBbsInfo().

**/
VOID
GetLegacyOptions (
  VOID
  )
{
  LEGACY_MENU_ENTRY             *NewMenuEntry;
  LEGACY_DEVICE_CONTEXT         *NewLegacyDevContext;
  EFI_BOOT_MANAGER_LOAD_OPTION  *BootOption;
  UINTN                         BootOptionCount;
  UINT16                        Index;
  UINTN                         FDNum;
  UINTN                         HDNum;
  UINTN                         CDNum;
  UINTN                         NETNum;
  UINTN                         BEVNum;

  //
  // Initialize Bbs Table Context from BBS info data
  //
  InitializeListHead (&LegacyFDMenu.Head);
  InitializeListHead (&LegacyHDMenu.Head);
  InitializeListHead (&LegacyCDMenu.Head);
  InitializeListHead (&LegacyNETMenu.Head);
  InitializeListHead (&LegacyBEVMenu.Head);

  FDNum   = 0;
  HDNum   = 0;
  CDNum   = 0;
  NETNum  = 0;
  BEVNum  = 0;

  EfiBootManagerConnectAll ();

  //
  // for better user experience
  // 1. User changes HD configuration (e.g.: unplug HDD), here we have a chance to remove the HDD boot option
  // 2. User enables/disables UEFI PXE, here we have a chance to add/remove EFI Network boot option
  //
  EfiBootManagerRefreshAllBootOption ();

  BootOption = EfiBootManagerGetLoadOptions (&BootOptionCount, LoadOptionTypeBoot);
  for (Index = 0; Index < BootOptionCount; Index++) {
    if ((DevicePathType (BootOption[Index].FilePath) != BBS_DEVICE_PATH) ||
        (DevicePathSubType (BootOption[Index].FilePath) != BBS_BBS_DP)
       ) {
      continue;
    }
    ASSERT (BootOption[Index].OptionalDataSize == sizeof (LEGACY_BOOT_OPTION_BBS_DATA));
    NewMenuEntry = CreateMenuEntry ();
    ASSERT (NewMenuEntry != NULL);

    NewLegacyDevContext              = (LEGACY_DEVICE_CONTEXT *) NewMenuEntry->VariableContext;
    NewLegacyDevContext->BbsIndex    = ((LEGACY_BOOT_OPTION_BBS_DATA *) BootOption[Index].OptionalData)->BbsIndex;
    NewLegacyDevContext->Description = AllocateCopyPool (StrSize (BootOption[Index].Description), BootOption[Index].Description);
    ASSERT (NewLegacyDevContext->Description != NULL);

    NewMenuEntry->DisplayString = NewLegacyDevContext->Description;
    NewMenuEntry->HelpString    = NULL;

    switch (((BBS_BBS_DEVICE_PATH *) BootOption[Index].FilePath)->DeviceType) {
    case BBS_TYPE_FLOPPY:
      InsertTailList (&LegacyFDMenu.Head, &NewMenuEntry->Link);
      FDNum++;
      break;

    case BBS_TYPE_HARDDRIVE:
      InsertTailList (&LegacyHDMenu.Head, &NewMenuEntry->Link);
      HDNum++;
      break;

    case BBS_TYPE_CDROM:
      InsertTailList (&LegacyCDMenu.Head, &NewMenuEntry->Link);
      CDNum++;
      break;

    case BBS_TYPE_EMBEDDED_NETWORK:
      InsertTailList (&LegacyNETMenu.Head, &NewMenuEntry->Link);
      NETNum++;
      break;

    case BBS_TYPE_BEV:
      InsertTailList (&LegacyBEVMenu.Head, &NewMenuEntry->Link);
      BEVNum++;
      break;
    }
  }

  EfiBootManagerFreeLoadOptions (BootOption, BootOptionCount);

  LegacyFDMenu.MenuNumber   = FDNum;
  LegacyHDMenu.MenuNumber   = HDNum;
  LegacyCDMenu.MenuNumber   = CDNum;
  LegacyNETMenu.MenuNumber  = NETNum;
  LegacyBEVMenu.MenuNumber  = BEVNum;
}


/**

  Install Boot Manager Menu driver.

  @param ImageHandle     The image handle.
  @param SystemTable     The system table.

  @retval  EFI_SUCCESS  Install Boot manager menu success.
  @retval  Other        Return error status.

**/
EFI_STATUS
EFIAPI
LegacyBootMaintUiLibConstructor (
  IN EFI_HANDLE                            ImageHandle,
  IN EFI_SYSTEM_TABLE                      *SystemTable
  )
{
  EFI_STATUS                        Status;
  EFI_LEGACY_BIOS_PROTOCOL          *LegacyBios;
  LEGACY_BOOT_OPTION_CALLBACK_DATA  *LegacyBootOptionData;

  Status = gBS->LocateProtocol (&gEfiLegacyBiosProtocolGuid, NULL, (VOID **) &LegacyBios);
  if (!EFI_ERROR (Status)) {
    //
    // Create LegacyBootOptionData structures for Driver Callback
    //
    LegacyBootOptionData = AllocateZeroPool (sizeof (LEGACY_BOOT_OPTION_CALLBACK_DATA));
    ASSERT (LegacyBootOptionData != NULL);

    LegacyBootOptionData->MaintainMapData = AllocateZeroPool (sizeof (LEGACY_BOOT_MAINTAIN_DATA));
    ASSERT (LegacyBootOptionData->MaintainMapData != NULL);

    LegacyBootOptionData->ConfigAccess.ExtractConfig = LegacyBootOptionExtractConfig;
    LegacyBootOptionData->ConfigAccess.RouteConfig   = LegacyBootOptionRouteConfig;
    LegacyBootOptionData->ConfigAccess.Callback      = LegacyBootOptionCallback;

    //
    // Install Device Path Protocol and Config Access protocol to driver handle
    //
    Status = gBS->InstallMultipleProtocolInterfaces (
                    &LegacyBootOptionData->DriverHandle,
                    &gEfiDevicePathProtocolGuid,
                    &mLegacyBootOptionHiiVendorDevicePath,
                    &gEfiHiiConfigAccessProtocolGuid,
                    &LegacyBootOptionData->ConfigAccess,
                    NULL
                    );
    ASSERT_EFI_ERROR (Status);

    //
    // Publish our HII data
    //
    LegacyBootOptionData->HiiHandle = HiiAddPackages (
                                      &mLegacyBootOptionGuid,
                                      LegacyBootOptionData->DriverHandle,
                                      LegacyBootMaintUiVfrBin,
                                      LegacyBootMaintUiLibStrings,
                                      NULL
                                      );
    ASSERT (LegacyBootOptionData->HiiHandle != NULL);

    mLegacyBootOptionPrivate = LegacyBootOptionData;
  }

  return EFI_SUCCESS;
}

/**
  Destructor of Customized Display Library Instance.

  @param  ImageHandle   The firmware allocated handle for the EFI image.
  @param  SystemTable   A pointer to the EFI System Table.

  @retval EFI_SUCCESS   The destructor completed successfully.
  @retval Other value   The destructor did not complete successfully.

**/
EFI_STATUS
EFIAPI
LegacyBootMaintUiLibDestructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS    Status;

  if (mLegacyBootOptionPrivate != NULL && mLegacyBootOptionPrivate->DriverHandle != NULL) {
    Status = gBS->UninstallMultipleProtocolInterfaces (
                    mLegacyBootOptionPrivate->DriverHandle,
                    &gEfiDevicePathProtocolGuid,
                    &mLegacyBootOptionHiiVendorDevicePath,
                    &gEfiHiiConfigAccessProtocolGuid,
                    &mLegacyBootOptionPrivate->ConfigAccess,
                    NULL
                    );
    ASSERT_EFI_ERROR (Status);

    HiiRemovePackages (mLegacyBootOptionPrivate->HiiHandle);

    FreePool (mLegacyBootOptionPrivate->MaintainMapData);
    FreePool (mLegacyBootOptionPrivate);
  }

  return EFI_SUCCESS;
}

