/** @file
  Legacy Boot Maintainence UI implementation.

Copyright (c) 2004 - 2018, Intel Corporation. All rights reserved.<BR>
(C) Copyright 2018 Hewlett Packard Enterprise Development LP<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 "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 udpated with the new Legacy Boot order. The EFI Variable of "Boot####" and gEfiGlobalVariableGuid
  is also updated.

  @param NVMapData   The data for egacy 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 spefies 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 ((EFI_D_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 leagcyBootMaintUiLib depends on the LegacyBootManagerLib to realize its functionality.
        // We need to do the leagcy 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_SUCEESS  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;
}

