/** @file
  This function deal with the legacy boot option, it create, delete
  and manage the legacy boot option, all legacy boot option is getting from
  the legacy BBS table.

Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "InternalLegacyBm.h"

#define  LEGACY_BM_BOOT_DESCRIPTION_LENGTH  32

/**
  Initialize legacy boot manager library by call EfiBootManagerRegisterLegacyBootSupport
  function to export two function pointer.

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

  @retval EFI_SUCCESS    The legacy boot manager library is initialized correctly.
  @return Other value if failed to initialize the legacy boot manager library.
**/
EFI_STATUS
EFIAPI
LegacyBootManagerLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EfiBootManagerRegisterLegacyBootSupport (
    LegacyBmRefreshAllBootOption,
    LegacyBmBoot
    );
  return EFI_SUCCESS;
}

/**
  Get the device type from the input legacy device path.

  @param DevicePath     The legacy device path.

  @retval               The legacy device type.
**/
UINT16
LegacyBmDeviceType (
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath
  )
{
  ASSERT (
    (DevicePathType (DevicePath) == BBS_DEVICE_PATH) &&
    (DevicePathSubType (DevicePath) == BBS_BBS_DP)
    );
  return ((BBS_BBS_DEVICE_PATH *)DevicePath)->DeviceType;
}

/**
  Validate the BbsEntry base on the Boot Priority info in the BbsEntry.

  @param BbsEntry       The input bbs entry info.

  @retval TRUE          The BbsEntry is valid.
  @retval FALSE         The BbsEntry is invalid.
**/
BOOLEAN
LegacyBmValidBbsEntry (
  IN BBS_TABLE  *BbsEntry
  )
{
  switch (BbsEntry->BootPriority) {
    case BBS_IGNORE_ENTRY:
    case BBS_DO_NOT_BOOT_FROM:
    case BBS_LOWEST_PRIORITY:
      return FALSE;
    default:
      return TRUE;
  }
}

/**
  Build Legacy Device Name String according.

  @param CurBBSEntry     BBS Table.
  @param Index           Index.
  @param BufSize         The buffer size.
  @param BootString      The output string.

**/
VOID
LegacyBmBuildLegacyDevNameString (
  IN  BBS_TABLE  *CurBBSEntry,
  IN  UINTN      Index,
  IN  UINTN      BufSize,
  OUT CHAR16     *BootString
  )
{
  CHAR16  *Fmt;
  CHAR16  *Type;
  CHAR8   *StringDesc;
  CHAR8   StringBufferA[LEGACY_BM_BOOT_DESCRIPTION_LENGTH + 1];
  CHAR16  StringBufferU[LEGACY_BM_BOOT_DESCRIPTION_LENGTH + 1];

  switch (Index) {
    //
    // Primary Master
    //
    case 1:
      Fmt = L"Primary Master %s";
      break;

    //
    // Primary Slave
    //
    case 2:
      Fmt = L"Primary Slave %s";
      break;

    //
    // Secondary Master
    //
    case 3:
      Fmt = L"Secondary Master %s";
      break;

    //
    // Secondary Slave
    //
    case 4:
      Fmt = L"Secondary Slave %s";
      break;

    default:
      Fmt = L"%s";
      break;
  }

  switch (CurBBSEntry->DeviceType) {
    case BBS_FLOPPY:
      Type = L"Floppy";
      break;

    case BBS_HARDDISK:
      Type = L"Harddisk";
      break;

    case BBS_CDROM:
      Type = L"CDROM";
      break;

    case BBS_PCMCIA:
      Type = L"PCMCIAe";
      break;

    case BBS_USB:
      Type = L"USB";
      break;

    case BBS_EMBED_NETWORK:
      Type = L"Network";
      break;

    case BBS_BEV_DEVICE:
      Type = L"BEVe";
      break;

    case BBS_UNKNOWN:
    default:
      Type = L"Unknown";
      break;
  }

  //
  // If current BBS entry has its description then use it.
  //
  StringDesc = (CHAR8 *)(((UINTN)CurBBSEntry->DescStringSegment << 4) + CurBBSEntry->DescStringOffset);
  if (NULL != StringDesc) {
    //
    // Only get first 32 characters, this is suggested by BBS spec
    //
    CopyMem (StringBufferA, StringDesc, LEGACY_BM_BOOT_DESCRIPTION_LENGTH);
    StringBufferA[LEGACY_BM_BOOT_DESCRIPTION_LENGTH] = 0;
    AsciiStrToUnicodeStrS (StringBufferA, StringBufferU, ARRAY_SIZE (StringBufferU));
    Fmt  = L"%s";
    Type = StringBufferU;
  }

  //
  // BbsTable 16 entries are for onboard IDE.
  // Set description string for SATA harddisks, Harddisk 0 ~ Harddisk 11
  //
  if ((Index >= 5) && (Index <= 16) && ((CurBBSEntry->DeviceType == BBS_HARDDISK) || (CurBBSEntry->DeviceType == BBS_CDROM))) {
    Fmt = L"%s %d";
    UnicodeSPrint (BootString, BufSize, Fmt, Type, Index - 5);
  } else {
    UnicodeSPrint (BootString, BufSize, Fmt, Type);
  }
}

/**
  Get the Bbs index for the input boot option.

  @param BootOption     The input boot option info.
  @param BbsTable       The input Bbs table.
  @param BbsCount       The input total bbs entry number.
  @param BbsIndexUsed   The array shows how many BBS table indexs have been used.

  @retval The index for the input boot option.
**/
UINT16
LegacyBmFuzzyMatch (
  EFI_BOOT_MANAGER_LOAD_OPTION  *BootOption,
  BBS_TABLE                     *BbsTable,
  UINT16                        BbsCount,
  BOOLEAN                       *BbsIndexUsed
  )
{
  UINT16                          Index;
  LEGACY_BM_BOOT_OPTION_BBS_DATA  *BbsData;
  CHAR16                          Description[LEGACY_BM_BOOT_DESCRIPTION_LENGTH + 1];

  BbsData = (LEGACY_BM_BOOT_OPTION_BBS_DATA *)BootOption->OptionalData;

  //
  // Directly check the BBS index stored in BootOption
  //
  if ((BbsData->BbsIndex < BbsCount) &&
      (LegacyBmDeviceType (BootOption->FilePath) == BbsTable[BbsData->BbsIndex].DeviceType))
  {
    LegacyBmBuildLegacyDevNameString (
      &BbsTable[BbsData->BbsIndex],
      BbsData->BbsIndex,
      sizeof (Description),
      Description
      );
    if ((StrCmp (Description, BootOption->Description) == 0) && !BbsIndexUsed[BbsData->BbsIndex]) {
      //
      // If devices with the same description string are connected,
      // the BbsIndex of the first device is returned for the other device also.
      // So, check if the BbsIndex is already being used, before assigning the BbsIndex.
      //
      BbsIndexUsed[BbsData->BbsIndex] = TRUE;
      return BbsData->BbsIndex;
    }
  }

  //
  // BBS table could be changed (entry removed/moved)
  // find the correct BBS index
  //
  for (Index = 0; Index < BbsCount; Index++) {
    if (!LegacyBmValidBbsEntry (&BbsTable[Index]) ||
        (BbsTable[Index].DeviceType != LegacyBmDeviceType (BootOption->FilePath)))
    {
      continue;
    }

    LegacyBmBuildLegacyDevNameString (
      &BbsTable[Index],
      Index,
      sizeof (Description),
      Description
      );
    if ((StrCmp (Description, BootOption->Description) == 0) && !BbsIndexUsed[Index]) {
      //
      // If devices with the same description string are connected,
      // the BbsIndex of the first device is assigned for the other device also.
      // So, check if the BbsIndex is already being used, before assigning the corrected BbsIndex.
      //
      break;
    }
  }

  //
  // Add the corrected BbsIndex in the UsedBbsIndex Buffer
  //
  if (Index != BbsCount) {
    BbsIndexUsed[Index] = TRUE;
  }

  return Index;
}

/**

  Update legacy device order base on the input info.

  @param   LegacyDevOrder     Legacy device order data buffer.
  @param   LegacyDevOrderSize Legacy device order data buffer size.
  @param   DeviceType         Device type which need to check.
  @param   OldBbsIndex        Old Bds Index.
  @param   NewBbsIndex        New Bds Index, if it is -1,means remove this option.

**/
VOID
LegacyBmUpdateBbsIndex (
  LEGACY_DEV_ORDER_ENTRY  *LegacyDevOrder,
  UINTN                   *LegacyDevOrderSize,
  UINT16                  DeviceType,
  UINT16                  OldBbsIndex,
  UINT16                  NewBbsIndex  // Delete entry if -1
  )
{
  LEGACY_DEV_ORDER_ENTRY  *Entry;
  UINTN                   Index;

  ASSERT (
    ((LegacyDevOrder == NULL) && (*LegacyDevOrderSize == 0)) ||
    ((LegacyDevOrder != NULL) && (*LegacyDevOrderSize != 0))
    );

  for (Entry = LegacyDevOrder;
       Entry < (LEGACY_DEV_ORDER_ENTRY *)((UINT8 *)LegacyDevOrder + *LegacyDevOrderSize);
       Entry = (LEGACY_DEV_ORDER_ENTRY *)((UINTN)Entry + sizeof (BBS_TYPE) + Entry->Length)
       )
  {
    if (Entry->BbsType == DeviceType) {
      for (Index = 0; Index < Entry->Length / sizeof (UINT16) - 1; Index++) {
        if (Entry->Data[Index] == OldBbsIndex) {
          if (NewBbsIndex == (UINT16)-1) {
            //
            // Delete the old entry
            //
            CopyMem (
              &Entry->Data[Index],
              &Entry->Data[Index + 1],
              (UINT8 *)LegacyDevOrder + *LegacyDevOrderSize - (UINT8 *)&Entry->Data[Index + 1]
              );
            Entry->Length       -= sizeof (UINT16);
            *LegacyDevOrderSize -= sizeof (UINT16);
          } else {
            Entry->Data[Index] = NewBbsIndex;
          }

          break;
        }
      }

      break;
    }
  }
}

/**
  Delete all the legacy boot options.

  @retval EFI_SUCCESS            All legacy boot options are deleted.
**/
EFI_STATUS
LegacyBmDeleteAllBootOptions (
  VOID
  )
{
  EFI_STATUS                    Status;
  UINTN                         Index;
  EFI_BOOT_MANAGER_LOAD_OPTION  *BootOption;
  UINTN                         BootOptionCount;

  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))
    {
      Status = EfiBootManagerDeleteLoadOptionVariable (BootOption[Index].OptionNumber, BootOption[Index].OptionType);
      //
      // Deleting variable with current variable implementation shouldn't fail.
      //
      ASSERT_EFI_ERROR (Status);
    }
  }

  Status = gRT->SetVariable (
                  VAR_LEGACY_DEV_ORDER,
                  &gEfiLegacyDevOrderVariableGuid,
                  0,
                  0,
                  NULL
                  );
  //
  // Deleting variable with current variable implementation shouldn't fail.
  //
  ASSERT (Status == EFI_SUCCESS || Status == EFI_NOT_FOUND);

  return EFI_SUCCESS;
}

/**
  Delete all the invalid legacy boot options.

  @retval EFI_SUCCESS             All invalid legacy boot options are deleted.
  @retval EFI_OUT_OF_RESOURCES    Fail to allocate necessary memory.
  @retval EFI_NOT_FOUND           Fail to retrieve variable of boot order.
**/
EFI_STATUS
LegacyBmDeleteAllInvalidBootOptions (
  VOID
  )
{
  EFI_STATUS                    Status;
  UINT16                        HddCount;
  UINT16                        BbsCount;
  HDD_INFO                      *HddInfo;
  BBS_TABLE                     *BbsTable;
  UINT16                        BbsIndex;
  EFI_LEGACY_BIOS_PROTOCOL      *LegacyBios;
  UINTN                         Index;
  EFI_BOOT_MANAGER_LOAD_OPTION  *BootOption;
  UINTN                         BootOptionCount;
  LEGACY_DEV_ORDER_ENTRY        *LegacyDevOrder;
  UINTN                         LegacyDevOrderSize;
  BOOLEAN                       *BbsIndexUsed;

  HddCount = 0;
  BbsCount = 0;
  HddInfo  = NULL;
  BbsTable = NULL;

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

  Status = LegacyBios->GetBbsInfo (
                         LegacyBios,
                         &HddCount,
                         &HddInfo,
                         &BbsCount,
                         &BbsTable
                         );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  GetVariable2 (VAR_LEGACY_DEV_ORDER, &gEfiLegacyDevOrderVariableGuid, (VOID **)&LegacyDevOrder, &LegacyDevOrderSize);

  BootOption = EfiBootManagerGetLoadOptions (&BootOptionCount, LoadOptionTypeBoot);

  BbsIndexUsed = AllocateZeroPool (BbsCount * sizeof (BOOLEAN));
  ASSERT (BbsIndexUsed != NULL);

  for (Index = 0; Index < BootOptionCount; Index++) {
    //
    // Skip non legacy boot option
    //
    if ((DevicePathType (BootOption[Index].FilePath) != BBS_DEVICE_PATH) ||
        (DevicePathSubType (BootOption[Index].FilePath) != BBS_BBS_DP))
    {
      continue;
    }

    BbsIndex = LegacyBmFuzzyMatch (&BootOption[Index], BbsTable, BbsCount, BbsIndexUsed);
    if (BbsIndex == BbsCount) {
      DEBUG ((DEBUG_INFO, "[LegacyBds] Delete Boot Option Boot%04x: %s\n", (UINTN)BootOption[Index].OptionNumber, BootOption[Index].Description));
      //
      // Delete entry from LegacyDevOrder
      //
      LegacyBmUpdateBbsIndex (
        LegacyDevOrder,
        &LegacyDevOrderSize,
        LegacyBmDeviceType (BootOption[Index].FilePath),
        ((LEGACY_BM_BOOT_OPTION_BBS_DATA *)BootOption[Index].OptionalData)->BbsIndex,
        (UINT16)-1
        );
      EfiBootManagerDeleteLoadOptionVariable (BootOption[Index].OptionNumber, BootOption[Index].OptionType);
    } else {
      if (((LEGACY_BM_BOOT_OPTION_BBS_DATA *)BootOption[Index].OptionalData)->BbsIndex != BbsIndex) {
        DEBUG ((
          DEBUG_INFO,
          "[LegacyBds] Update Boot Option Boot%04x: %s Bbs0x%04x->Bbs0x%04x\n",
          (UINTN)BootOption[Index].OptionNumber,
          BootOption[Index].Description,
          (UINTN)((LEGACY_BM_BOOT_OPTION_BBS_DATA *)BootOption[Index].OptionalData)->BbsIndex,
          (UINTN)BbsIndex
          ));
        //
        // Update the BBS index in LegacyDevOrder
        //
        LegacyBmUpdateBbsIndex (
          LegacyDevOrder,
          &LegacyDevOrderSize,
          LegacyBmDeviceType (BootOption[Index].FilePath),
          ((LEGACY_BM_BOOT_OPTION_BBS_DATA *)BootOption[Index].OptionalData)->BbsIndex,
          BbsIndex
          );

        //
        // Update the OptionalData in the Boot#### variable
        //
        ((LEGACY_BM_BOOT_OPTION_BBS_DATA *)BootOption[Index].OptionalData)->BbsIndex = BbsIndex;
        EfiBootManagerLoadOptionToVariable (&BootOption[Index]);
      }
    }
  }

  EfiBootManagerFreeLoadOptions (BootOption, BootOptionCount);

  if (LegacyDevOrder != NULL) {
    Status = gRT->SetVariable (
                    VAR_LEGACY_DEV_ORDER,
                    &gEfiLegacyDevOrderVariableGuid,
                    EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                    LegacyDevOrderSize,
                    LegacyDevOrder
                    );
    //
    // Shrink variable with current variable implementation shouldn't fail.
    //
    ASSERT_EFI_ERROR (Status);

    FreePool (LegacyDevOrder);
  }

  FreePool (BbsIndexUsed);
  return Status;
}

/**
  Create legacy boot option.

  @param BootOption        Pointer to the boot option which will be crated.
  @param BbsEntry          The input bbs entry info.
  @param BbsIndex          The BBS index.

  @retval EFI_SUCCESS            Create legacy boot option successfully.
  @retval EFI_INVALID_PARAMETER  Invalid input parameter.

**/
EFI_STATUS
LegacyBmCreateLegacyBootOption (
  IN OUT EFI_BOOT_MANAGER_LOAD_OPTION  *BootOption,
  IN BBS_TABLE                         *BbsEntry,
  IN UINT16                            BbsIndex
  )
{
  EFI_STATUS                      Status;
  EFI_DEVICE_PATH_PROTOCOL        *DevicePath;
  CHAR16                          Description[LEGACY_BM_BOOT_DESCRIPTION_LENGTH + 1];
  CHAR8                           HelpString[LEGACY_BM_BOOT_DESCRIPTION_LENGTH + 1];
  UINTN                           StringLen;
  LEGACY_BM_BOOT_OPTION_BBS_DATA  *OptionalData;
  BBS_BBS_DEVICE_PATH             *BbsNode;

  if ((BootOption == NULL) || (BbsEntry == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  LegacyBmBuildLegacyDevNameString (BbsEntry, BbsIndex, sizeof (Description), Description);

  //
  // Create the BBS device path with description string
  //
  UnicodeStrToAsciiStrS (Description, HelpString, sizeof (HelpString));
  StringLen  = AsciiStrLen (HelpString);
  DevicePath = AllocatePool (sizeof (BBS_BBS_DEVICE_PATH) + StringLen + END_DEVICE_PATH_LENGTH);
  ASSERT (DevicePath != NULL);

  BbsNode = (BBS_BBS_DEVICE_PATH *)DevicePath;
  SetDevicePathNodeLength (BbsNode, sizeof (BBS_BBS_DEVICE_PATH) + StringLen);
  BbsNode->Header.Type    = BBS_DEVICE_PATH;
  BbsNode->Header.SubType = BBS_BBS_DP;
  BbsNode->DeviceType     = BbsEntry->DeviceType;
  CopyMem (&BbsNode->StatusFlag, &BbsEntry->StatusFlags, sizeof (BBS_STATUS_FLAGS));
  CopyMem (BbsNode->String, HelpString, StringLen + 1);

  SetDevicePathEndNode (NextDevicePathNode (BbsNode));

  //
  // Create the OptionalData
  //
  OptionalData = AllocatePool (sizeof (LEGACY_BM_BOOT_OPTION_BBS_DATA));
  ASSERT (OptionalData != NULL);
  OptionalData->BbsIndex = BbsIndex;

  //
  // Create the BootOption
  //
  Status = EfiBootManagerInitializeLoadOption (
             BootOption,
             LoadOptionNumberUnassigned,
             LoadOptionTypeBoot,
             LOAD_OPTION_ACTIVE,
             Description,
             DevicePath,
             (UINT8 *)OptionalData,
             sizeof (LEGACY_BM_BOOT_OPTION_BBS_DATA)
             );
  FreePool (DevicePath);
  FreePool (OptionalData);

  return Status;
}

/**
  Fill the device order buffer.

  @param BbsTable        The BBS table.
  @param BbsType         The BBS Type.
  @param BbsCount        The BBS Count.
  @param Buf             device order buffer.

  @return The device order buffer.

**/
UINT16 *
LegacyBmFillDevOrderBuf (
  IN BBS_TABLE  *BbsTable,
  IN BBS_TYPE   BbsType,
  IN UINTN      BbsCount,
  OUT UINT16    *Buf
  )
{
  UINTN  Index;

  for (Index = 0; Index < BbsCount; Index++) {
    if (!LegacyBmValidBbsEntry (&BbsTable[Index])) {
      continue;
    }

    if (BbsTable[Index].DeviceType != BbsType) {
      continue;
    }

    *Buf = (UINT16)(Index & 0xFF);
    Buf++;
  }

  return Buf;
}

/**
  Create the device order buffer.

  @param BbsTable        The BBS table.
  @param BbsCount        The BBS Count.

  @retval EFI_SUCCESS            The buffer is created and the EFI variable named
                                 VAR_LEGACY_DEV_ORDER and EfiLegacyDevOrderGuid is
                                 set correctly.
  @retval EFI_OUT_OF_RESOURCES   Memory or storage is not enough.
  @retval EFI_DEVICE_ERROR       Fail to add the device order into EFI variable fail
                                 because of hardware error.
**/
EFI_STATUS
LegacyBmCreateDevOrder (
  IN BBS_TABLE  *BbsTable,
  IN UINT16     BbsCount
  )
{
  UINTN                   Index;
  UINTN                   FDCount;
  UINTN                   HDCount;
  UINTN                   CDCount;
  UINTN                   NETCount;
  UINTN                   BEVCount;
  UINTN                   TotalSize;
  UINTN                   HeaderSize;
  LEGACY_DEV_ORDER_ENTRY  *DevOrder;
  LEGACY_DEV_ORDER_ENTRY  *DevOrderPtr;
  EFI_STATUS              Status;

  FDCount    = 0;
  HDCount    = 0;
  CDCount    = 0;
  NETCount   = 0;
  BEVCount   = 0;
  TotalSize  = 0;
  HeaderSize = sizeof (BBS_TYPE) + sizeof (UINT16);
  DevOrder   = NULL;
  Status     = EFI_SUCCESS;

  //
  // Count all boot devices
  //
  for (Index = 0; Index < BbsCount; Index++) {
    if (!LegacyBmValidBbsEntry (&BbsTable[Index])) {
      continue;
    }

    switch (BbsTable[Index].DeviceType) {
      case BBS_FLOPPY:
        FDCount++;
        break;

      case BBS_HARDDISK:
        HDCount++;
        break;

      case BBS_CDROM:
        CDCount++;
        break;

      case BBS_EMBED_NETWORK:
        NETCount++;
        break;

      case BBS_BEV_DEVICE:
        BEVCount++;
        break;

      default:
        break;
    }
  }

  TotalSize += (HeaderSize + sizeof (UINT16) * FDCount);
  TotalSize += (HeaderSize + sizeof (UINT16) * HDCount);
  TotalSize += (HeaderSize + sizeof (UINT16) * CDCount);
  TotalSize += (HeaderSize + sizeof (UINT16) * NETCount);
  TotalSize += (HeaderSize + sizeof (UINT16) * BEVCount);

  //
  // Create buffer to hold all boot device order
  //
  DevOrder = AllocateZeroPool (TotalSize);
  if (NULL == DevOrder) {
    return EFI_OUT_OF_RESOURCES;
  }

  DevOrderPtr = DevOrder;

  DevOrderPtr->BbsType = BBS_FLOPPY;
  DevOrderPtr->Length  = (UINT16)(sizeof (DevOrderPtr->Length) + FDCount * sizeof (UINT16));
  DevOrderPtr          = (LEGACY_DEV_ORDER_ENTRY *)LegacyBmFillDevOrderBuf (BbsTable, BBS_FLOPPY, BbsCount, DevOrderPtr->Data);

  DevOrderPtr->BbsType = BBS_HARDDISK;
  DevOrderPtr->Length  = (UINT16)(sizeof (UINT16) + HDCount * sizeof (UINT16));
  DevOrderPtr          = (LEGACY_DEV_ORDER_ENTRY *)LegacyBmFillDevOrderBuf (BbsTable, BBS_HARDDISK, BbsCount, DevOrderPtr->Data);

  DevOrderPtr->BbsType = BBS_CDROM;
  DevOrderPtr->Length  = (UINT16)(sizeof (UINT16) + CDCount * sizeof (UINT16));
  DevOrderPtr          = (LEGACY_DEV_ORDER_ENTRY *)LegacyBmFillDevOrderBuf (BbsTable, BBS_CDROM, BbsCount, DevOrderPtr->Data);

  DevOrderPtr->BbsType = BBS_EMBED_NETWORK;
  DevOrderPtr->Length  = (UINT16)(sizeof (UINT16) + NETCount * sizeof (UINT16));
  DevOrderPtr          = (LEGACY_DEV_ORDER_ENTRY *)LegacyBmFillDevOrderBuf (BbsTable, BBS_EMBED_NETWORK, BbsCount, DevOrderPtr->Data);

  DevOrderPtr->BbsType = BBS_BEV_DEVICE;
  DevOrderPtr->Length  = (UINT16)(sizeof (UINT16) + BEVCount * sizeof (UINT16));
  DevOrderPtr          = (LEGACY_DEV_ORDER_ENTRY *)LegacyBmFillDevOrderBuf (BbsTable, BBS_BEV_DEVICE, BbsCount, DevOrderPtr->Data);

  ASSERT (TotalSize == ((UINTN)DevOrderPtr - (UINTN)DevOrder));

  //
  // Save device order for legacy boot device to variable.
  //
  Status = gRT->SetVariable (
                  VAR_LEGACY_DEV_ORDER,
                  &gEfiLegacyDevOrderVariableGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                  TotalSize,
                  DevOrder
                  );
  FreePool (DevOrder);

  return Status;
}

/**
  Add the legacy boot devices from BBS table into
  the legacy device boot order.

  @retval EFI_SUCCESS           The boot devices are added successfully.
  @retval EFI_NOT_FOUND         The legacy boot devices are not found.
  @retval EFI_OUT_OF_RESOURCES  Memory or storage is not enough.
  @retval EFI_DEVICE_ERROR      Fail to add the legacy device boot order into EFI variable
                                because of hardware error.
**/
EFI_STATUS
LegacyBmUpdateDevOrder (
  VOID
  )
{
  LEGACY_DEV_ORDER_ENTRY    *DevOrder;
  LEGACY_DEV_ORDER_ENTRY    *NewDevOrder;
  LEGACY_DEV_ORDER_ENTRY    *Ptr;
  LEGACY_DEV_ORDER_ENTRY    *NewPtr;
  EFI_LEGACY_BIOS_PROTOCOL  *LegacyBios;
  EFI_STATUS                Status;
  UINT16                    HddCount;
  UINT16                    BbsCount;
  HDD_INFO                  *LocalHddInfo;
  BBS_TABLE                 *LocalBbsTable;
  UINTN                     Index;
  UINTN                     Index2;
  UINTN                     *Idx;
  UINTN                     FDCount;
  UINTN                     HDCount;
  UINTN                     CDCount;
  UINTN                     NETCount;
  UINTN                     BEVCount;
  UINTN                     TotalSize;
  UINTN                     HeaderSize;
  UINT16                    *NewFDPtr;
  UINT16                    *NewHDPtr;
  UINT16                    *NewCDPtr;
  UINT16                    *NewNETPtr;
  UINT16                    *NewBEVPtr;
  UINT16                    *NewDevPtr;
  UINTN                     FDIndex;
  UINTN                     HDIndex;
  UINTN                     CDIndex;
  UINTN                     NETIndex;
  UINTN                     BEVIndex;

  Idx        = NULL;
  FDCount    = 0;
  HDCount    = 0;
  CDCount    = 0;
  NETCount   = 0;
  BEVCount   = 0;
  TotalSize  = 0;
  HeaderSize = sizeof (BBS_TYPE) + sizeof (UINT16);
  FDIndex    = 0;
  HDIndex    = 0;
  CDIndex    = 0;
  NETIndex   = 0;
  BEVIndex   = 0;
  NewDevPtr  = NULL;

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

  Status = LegacyBios->GetBbsInfo (
                         LegacyBios,
                         &HddCount,
                         &LocalHddInfo,
                         &BbsCount,
                         &LocalBbsTable
                         );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  GetVariable2 (VAR_LEGACY_DEV_ORDER, &gEfiLegacyDevOrderVariableGuid, (VOID **)&DevOrder, NULL);
  if (NULL == DevOrder) {
    return LegacyBmCreateDevOrder (LocalBbsTable, BbsCount);
  }

  //
  // First we figure out how many boot devices with same device type respectively
  //
  for (Index = 0; Index < BbsCount; Index++) {
    if (!LegacyBmValidBbsEntry (&LocalBbsTable[Index])) {
      continue;
    }

    switch (LocalBbsTable[Index].DeviceType) {
      case BBS_FLOPPY:
        FDCount++;
        break;

      case BBS_HARDDISK:
        HDCount++;
        break;

      case BBS_CDROM:
        CDCount++;
        break;

      case BBS_EMBED_NETWORK:
        NETCount++;
        break;

      case BBS_BEV_DEVICE:
        BEVCount++;
        break;

      default:
        break;
    }
  }

  TotalSize += (HeaderSize + FDCount * sizeof (UINT16));
  TotalSize += (HeaderSize + HDCount * sizeof (UINT16));
  TotalSize += (HeaderSize + CDCount * sizeof (UINT16));
  TotalSize += (HeaderSize + NETCount * sizeof (UINT16));
  TotalSize += (HeaderSize + BEVCount * sizeof (UINT16));

  NewDevOrder = AllocateZeroPool (TotalSize);
  if (NULL == NewDevOrder) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // copy FD
  //
  Ptr             = DevOrder;
  NewPtr          = NewDevOrder;
  NewPtr->BbsType = Ptr->BbsType;
  NewPtr->Length  = (UINT16)(sizeof (UINT16) + FDCount * sizeof (UINT16));
  for (Index = 0; Index < Ptr->Length / sizeof (UINT16) - 1; Index++) {
    if (!LegacyBmValidBbsEntry (&LocalBbsTable[Ptr->Data[Index] & 0xFF]) ||
        (LocalBbsTable[Ptr->Data[Index] & 0xFF].DeviceType != BBS_FLOPPY)
        )
    {
      continue;
    }

    NewPtr->Data[FDIndex] = Ptr->Data[Index];
    FDIndex++;
  }

  NewFDPtr = NewPtr->Data;

  //
  // copy HD
  //
  Ptr             = (LEGACY_DEV_ORDER_ENTRY *)(&Ptr->Data[Ptr->Length / sizeof (UINT16) - 1]);
  NewPtr          = (LEGACY_DEV_ORDER_ENTRY *)(&NewPtr->Data[NewPtr->Length / sizeof (UINT16) -1]);
  NewPtr->BbsType = Ptr->BbsType;
  NewPtr->Length  = (UINT16)(sizeof (UINT16) + HDCount * sizeof (UINT16));
  for (Index = 0; Index < Ptr->Length / sizeof (UINT16) - 1; Index++) {
    if (!LegacyBmValidBbsEntry (&LocalBbsTable[Ptr->Data[Index] & 0xFF]) ||
        (LocalBbsTable[Ptr->Data[Index] & 0xFF].DeviceType != BBS_HARDDISK)
        )
    {
      continue;
    }

    NewPtr->Data[HDIndex] = Ptr->Data[Index];
    HDIndex++;
  }

  NewHDPtr = NewPtr->Data;

  //
  // copy CD
  //
  Ptr             = (LEGACY_DEV_ORDER_ENTRY *)(&Ptr->Data[Ptr->Length / sizeof (UINT16) - 1]);
  NewPtr          = (LEGACY_DEV_ORDER_ENTRY *)(&NewPtr->Data[NewPtr->Length / sizeof (UINT16) -1]);
  NewPtr->BbsType = Ptr->BbsType;
  NewPtr->Length  = (UINT16)(sizeof (UINT16) + CDCount * sizeof (UINT16));
  for (Index = 0; Index < Ptr->Length / sizeof (UINT16) - 1; Index++) {
    if (!LegacyBmValidBbsEntry (&LocalBbsTable[Ptr->Data[Index] & 0xFF]) ||
        (LocalBbsTable[Ptr->Data[Index] & 0xFF].DeviceType != BBS_CDROM)
        )
    {
      continue;
    }

    NewPtr->Data[CDIndex] = Ptr->Data[Index];
    CDIndex++;
  }

  NewCDPtr = NewPtr->Data;

  //
  // copy NET
  //
  Ptr             = (LEGACY_DEV_ORDER_ENTRY *)(&Ptr->Data[Ptr->Length / sizeof (UINT16) - 1]);
  NewPtr          = (LEGACY_DEV_ORDER_ENTRY *)(&NewPtr->Data[NewPtr->Length / sizeof (UINT16) -1]);
  NewPtr->BbsType = Ptr->BbsType;
  NewPtr->Length  = (UINT16)(sizeof (UINT16) + NETCount * sizeof (UINT16));
  for (Index = 0; Index < Ptr->Length / sizeof (UINT16) - 1; Index++) {
    if (!LegacyBmValidBbsEntry (&LocalBbsTable[Ptr->Data[Index] & 0xFF]) ||
        (LocalBbsTable[Ptr->Data[Index] & 0xFF].DeviceType != BBS_EMBED_NETWORK)
        )
    {
      continue;
    }

    NewPtr->Data[NETIndex] = Ptr->Data[Index];
    NETIndex++;
  }

  NewNETPtr = NewPtr->Data;

  //
  // copy BEV
  //
  Ptr             = (LEGACY_DEV_ORDER_ENTRY *)(&Ptr->Data[Ptr->Length / sizeof (UINT16) - 1]);
  NewPtr          = (LEGACY_DEV_ORDER_ENTRY *)(&NewPtr->Data[NewPtr->Length / sizeof (UINT16) -1]);
  NewPtr->BbsType = Ptr->BbsType;
  NewPtr->Length  = (UINT16)(sizeof (UINT16) + BEVCount * sizeof (UINT16));
  for (Index = 0; Index < Ptr->Length / sizeof (UINT16) - 1; Index++) {
    if (!LegacyBmValidBbsEntry (&LocalBbsTable[Ptr->Data[Index] & 0xFF]) ||
        (LocalBbsTable[Ptr->Data[Index] & 0xFF].DeviceType != BBS_BEV_DEVICE)
        )
    {
      continue;
    }

    NewPtr->Data[BEVIndex] = Ptr->Data[Index];
    BEVIndex++;
  }

  NewBEVPtr = NewPtr->Data;

  for (Index = 0; Index < BbsCount; Index++) {
    if (!LegacyBmValidBbsEntry (&LocalBbsTable[Index])) {
      continue;
    }

    switch (LocalBbsTable[Index].DeviceType) {
      case BBS_FLOPPY:
        Idx       = &FDIndex;
        NewDevPtr = NewFDPtr;
        break;

      case BBS_HARDDISK:
        Idx       = &HDIndex;
        NewDevPtr = NewHDPtr;
        break;

      case BBS_CDROM:
        Idx       = &CDIndex;
        NewDevPtr = NewCDPtr;
        break;

      case BBS_EMBED_NETWORK:
        Idx       = &NETIndex;
        NewDevPtr = NewNETPtr;
        break;

      case BBS_BEV_DEVICE:
        Idx       = &BEVIndex;
        NewDevPtr = NewBEVPtr;
        break;

      default:
        Idx = NULL;
        break;
    }

    //
    // at this point we have copied those valid indexes to new buffer
    // and we should check if there is any new appeared boot device
    //
    if (Idx != NULL) {
      for (Index2 = 0; Index2 < *Idx; Index2++) {
        if ((NewDevPtr[Index2] & 0xFF) == (UINT16)Index) {
          break;
        }
      }

      if (Index2 == *Idx) {
        //
        // Index2 == *Idx means we didn't find Index
        // so Index is a new appeared device's index in BBS table
        // insert it before disabled indexes.
        //
        for (Index2 = 0; Index2 < *Idx; Index2++) {
          if ((NewDevPtr[Index2] & 0xFF00) == 0xFF00) {
            break;
          }
        }

        CopyMem (&NewDevPtr[Index2 + 1], &NewDevPtr[Index2], (*Idx - Index2) * sizeof (UINT16));
        NewDevPtr[Index2] = (UINT16)(Index & 0xFF);
        (*Idx)++;
      }
    }
  }

  FreePool (DevOrder);

  Status = gRT->SetVariable (
                  VAR_LEGACY_DEV_ORDER,
                  &gEfiLegacyDevOrderVariableGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_NON_VOLATILE,
                  TotalSize,
                  NewDevOrder
                  );
  FreePool (NewDevOrder);

  return Status;
}

/**
  Set Boot Priority for specified device type.

  @param DeviceType      The device type.
  @param BbsIndex        The BBS index to set the highest priority. Ignore when -1.
  @param LocalBbsTable   The BBS table.
  @param Priority        The priority table.

  @retval EFI_SUCCESS           The function completes successfully.
  @retval EFI_NOT_FOUND         Failed to find device.
  @retval EFI_OUT_OF_RESOURCES  Failed to get the efi variable of device order.

**/
EFI_STATUS
LegacyBmSetPriorityForSameTypeDev (
  IN UINT16         DeviceType,
  IN UINTN          BbsIndex,
  IN OUT BBS_TABLE  *LocalBbsTable,
  IN OUT UINT16     *Priority
  )
{
  LEGACY_DEV_ORDER_ENTRY  *DevOrder;
  LEGACY_DEV_ORDER_ENTRY  *DevOrderPtr;
  UINTN                   DevOrderSize;
  UINTN                   Index;

  GetVariable2 (VAR_LEGACY_DEV_ORDER, &gEfiLegacyDevOrderVariableGuid, (VOID **)&DevOrder, &DevOrderSize);
  if (NULL == DevOrder) {
    return EFI_OUT_OF_RESOURCES;
  }

  DevOrderPtr = DevOrder;
  while ((UINT8 *)DevOrderPtr < (UINT8 *)DevOrder + DevOrderSize) {
    if (DevOrderPtr->BbsType == DeviceType) {
      break;
    }

    DevOrderPtr = (LEGACY_DEV_ORDER_ENTRY *)((UINTN)DevOrderPtr + sizeof (BBS_TYPE) + DevOrderPtr->Length);
  }

  if ((UINT8 *)DevOrderPtr >= (UINT8 *)DevOrder + DevOrderSize) {
    FreePool (DevOrder);
    return EFI_NOT_FOUND;
  }

  if (BbsIndex != (UINTN)-1) {
    //
    // In case the BBS entry isn't valid because devices were plugged or removed.
    //
    if (!LegacyBmValidBbsEntry (&LocalBbsTable[BbsIndex]) || (LocalBbsTable[BbsIndex].DeviceType != DeviceType)) {
      FreePool (DevOrder);
      return EFI_NOT_FOUND;
    }

    LocalBbsTable[BbsIndex].BootPriority = *Priority;
    (*Priority)++;
  }

  //
  // If the high byte of the DevIndex is 0xFF, it indicates that this device has been disabled.
  //
  for (Index = 0; Index < DevOrderPtr->Length / sizeof (UINT16) - 1; Index++) {
    if ((DevOrderPtr->Data[Index] & 0xFF00) == 0xFF00) {
      //
      // LocalBbsTable[DevIndex[Index] & 0xFF].BootPriority = BBS_DISABLED_ENTRY;
      //
    } else if (DevOrderPtr->Data[Index] != BbsIndex) {
      LocalBbsTable[DevOrderPtr->Data[Index]].BootPriority = *Priority;
      (*Priority)++;
    }
  }

  FreePool (DevOrder);
  return EFI_SUCCESS;
}

/**
  Print the BBS Table.

  @param LocalBbsTable   The BBS table.
  @param BbsCount        The count of entry in BBS table.
**/
VOID
LegacyBmPrintBbsTable (
  IN BBS_TABLE  *LocalBbsTable,
  IN UINT16     BbsCount
  )
{
  UINT16  Index;

  DEBUG ((DEBUG_INFO, "\n"));
  DEBUG ((DEBUG_INFO, " NO  Prio bb/dd/ff cl/sc Type Stat segm:offs mseg dseg\n"));
  DEBUG ((DEBUG_INFO, "======================================================\n"));
  for (Index = 0; Index < BbsCount; Index++) {
    if (!LegacyBmValidBbsEntry (&LocalBbsTable[Index])) {
      continue;
    }

    DEBUG (
      (DEBUG_INFO,
       " %02x: %04x %02x/%02x/%02x %02x/%02x %04x %04x %04x:%04x %04x %04x\n",
       (UINTN)Index,
       (UINTN)LocalBbsTable[Index].BootPriority,
       (UINTN)LocalBbsTable[Index].Bus,
       (UINTN)LocalBbsTable[Index].Device,
       (UINTN)LocalBbsTable[Index].Function,
       (UINTN)LocalBbsTable[Index].Class,
       (UINTN)LocalBbsTable[Index].SubClass,
       (UINTN)LocalBbsTable[Index].DeviceType,
       (UINTN)*(UINT16 *)&LocalBbsTable[Index].StatusFlags,
       (UINTN)LocalBbsTable[Index].BootHandlerSegment,
       (UINTN)LocalBbsTable[Index].BootHandlerOffset,
       (UINTN)((LocalBbsTable[Index].MfgStringSegment << 4) + LocalBbsTable[Index].MfgStringOffset),
       (UINTN)((LocalBbsTable[Index].DescStringSegment << 4) + LocalBbsTable[Index].DescStringOffset))
      );
  }

  DEBUG ((DEBUG_INFO, "\n"));
}

/**
  Set the boot priority for BBS entries based on boot option entry and boot order.

  @param  BootOption            The boot option is to be checked for refresh BBS table.

  @retval EFI_SUCCESS           The boot priority for BBS entries is refreshed successfully.
  @retval EFI_NOT_FOUND         BBS entries can't be found.
  @retval EFI_OUT_OF_RESOURCES  Failed to get the legacy device boot order.
**/
EFI_STATUS
LegacyBmRefreshBbsTableForBoot (
  IN EFI_BOOT_MANAGER_LOAD_OPTION  *BootOption
  )
{
  EFI_STATUS                    Status;
  UINT16                        BbsIndex;
  UINT16                        HddCount;
  UINT16                        BbsCount;
  HDD_INFO                      *LocalHddInfo;
  BBS_TABLE                     *LocalBbsTable;
  UINT16                        DevType;
  EFI_LEGACY_BIOS_PROTOCOL      *LegacyBios;
  UINTN                         Index;
  UINT16                        Priority;
  UINT16                        *DeviceType;
  UINTN                         DeviceTypeCount;
  UINTN                         DeviceTypeIndex;
  EFI_BOOT_MANAGER_LOAD_OPTION  *Option;
  UINTN                         OptionCount;

  HddCount      = 0;
  BbsCount      = 0;
  LocalHddInfo  = NULL;
  LocalBbsTable = NULL;
  DevType       = BBS_UNKNOWN;

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

  Status = LegacyBios->GetBbsInfo (
                         LegacyBios,
                         &HddCount,
                         &LocalHddInfo,
                         &BbsCount,
                         &LocalBbsTable
                         );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // First, set all the present devices' boot priority to BBS_UNPRIORITIZED_ENTRY
  // We will set them according to the settings setup by user
  //
  for (Index = 0; Index < BbsCount; Index++) {
    if (LegacyBmValidBbsEntry (&LocalBbsTable[Index])) {
      LocalBbsTable[Index].BootPriority = BBS_UNPRIORITIZED_ENTRY;
    }
  }

  //
  // boot priority always starts at 0
  //
  Priority = 0;
  if ((DevicePathType (BootOption->FilePath) == BBS_DEVICE_PATH) &&
      (DevicePathSubType (BootOption->FilePath) == BBS_BBS_DP))
  {
    //
    // If BootOption stands for a legacy boot option, we prioritize the devices with the same type first.
    //
    DevType  = LegacyBmDeviceType (BootOption->FilePath);
    BbsIndex = ((LEGACY_BM_BOOT_OPTION_BBS_DATA *)BootOption->OptionalData)->BbsIndex;
    Status   = LegacyBmSetPriorityForSameTypeDev (
                 DevType,
                 BbsIndex,
                 LocalBbsTable,
                 &Priority
                 );
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }

  //
  // we have to set the boot priority for other BBS entries with different device types
  //
  Option     = EfiBootManagerGetLoadOptions (&OptionCount, LoadOptionTypeBoot);
  DeviceType = AllocatePool (sizeof (UINT16) * OptionCount);
  ASSERT (DeviceType != NULL);
  DeviceType[0]   = DevType;
  DeviceTypeCount = 1;
  for (Index = 0; Index < OptionCount; Index++) {
    if ((DevicePathType (Option[Index].FilePath) != BBS_DEVICE_PATH) ||
        (DevicePathSubType (Option[Index].FilePath) != BBS_BBS_DP))
    {
      continue;
    }

    DevType = LegacyBmDeviceType (Option[Index].FilePath);
    for (DeviceTypeIndex = 0; DeviceTypeIndex < DeviceTypeCount; DeviceTypeIndex++) {
      if (DeviceType[DeviceTypeIndex] == DevType) {
        break;
      }
    }

    if (DeviceTypeIndex < DeviceTypeCount) {
      //
      // We don't want to process twice for a device type
      //
      continue;
    }

    DeviceType[DeviceTypeCount] = DevType;
    DeviceTypeCount++;

    Status = LegacyBmSetPriorityForSameTypeDev (
               DevType,
               (UINTN)-1,
               LocalBbsTable,
               &Priority
               );
  }

  EfiBootManagerFreeLoadOptions (Option, OptionCount);

  DEBUG_CODE_BEGIN ();
  LegacyBmPrintBbsTable (LocalBbsTable, BbsCount);
  DEBUG_CODE_END ();

  return Status;
}

/**
  Boot the legacy system with the boot option.

  @param  BootOption The legacy boot option which have BBS device path
                     On return, BootOption->Status contains the boot status.
                     EFI_UNSUPPORTED    There is no legacybios protocol, do not support
                                        legacy boot.
                     EFI_STATUS         The status of LegacyBios->LegacyBoot ().
**/
VOID
EFIAPI
LegacyBmBoot (
  IN  EFI_BOOT_MANAGER_LOAD_OPTION  *BootOption
  )
{
  EFI_STATUS                Status;
  EFI_LEGACY_BIOS_PROTOCOL  *LegacyBios;

  Status = gBS->LocateProtocol (&gEfiLegacyBiosProtocolGuid, NULL, (VOID **)&LegacyBios);
  if (EFI_ERROR (Status)) {
    //
    // If no LegacyBios protocol we do not support legacy boot
    //
    BootOption->Status = EFI_UNSUPPORTED;
    return;
  }

  //
  // Notes: if we separate the int 19, then we don't need to refresh BBS
  //
  Status = LegacyBmRefreshBbsTableForBoot (BootOption);
  if (EFI_ERROR (Status)) {
    BootOption->Status = Status;
    return;
  }

  BootOption->Status = LegacyBios->LegacyBoot (
                                     LegacyBios,
                                     (BBS_BBS_DEVICE_PATH *)BootOption->FilePath,
                                     BootOption->OptionalDataSize,
                                     BootOption->OptionalData
                                     );
}

/**
  This function enumerates all the legacy boot options.

  @param BootOptionCount   Return the legacy boot option count.

  @retval    Pointer to the legacy boot option buffer.
**/
EFI_BOOT_MANAGER_LOAD_OPTION *
LegacyBmEnumerateAllBootOptions (
  UINTN  *BootOptionCount
  )
{
  EFI_STATUS                    Status;
  UINT16                        HddCount;
  UINT16                        BbsCount;
  HDD_INFO                      *HddInfo;
  BBS_TABLE                     *BbsTable;
  EFI_LEGACY_BIOS_PROTOCOL      *LegacyBios;
  UINT16                        Index;
  EFI_BOOT_MANAGER_LOAD_OPTION  *BootOptions;

  ASSERT (BootOptionCount != NULL);

  BootOptions      = NULL;
  *BootOptionCount = 0;
  BbsCount         = 0;

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

  Status = LegacyBios->GetBbsInfo (
                         LegacyBios,
                         &HddCount,
                         &HddInfo,
                         &BbsCount,
                         &BbsTable
                         );
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  for (Index = 0; Index < BbsCount; Index++) {
    if (!LegacyBmValidBbsEntry (&BbsTable[Index])) {
      continue;
    }

    BootOptions = ReallocatePool (
                    sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount),
                    sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * (*BootOptionCount + 1),
                    BootOptions
                    );
    ASSERT (BootOptions != NULL);

    Status = LegacyBmCreateLegacyBootOption (&BootOptions[(*BootOptionCount)++], &BbsTable[Index], Index);
    ASSERT_EFI_ERROR (Status);
  }

  return BootOptions;
}

/**
  Return the index of the boot option in the boot option array.

  The function compares the Description, FilePath, OptionalData.

  @param Key         The input boot option which is compared with.
  @param Array       The input boot option array.
  @param Count       The count of the input boot options.

  @retval  The index of the input boot option in the array.

**/
INTN
LegacyBmFindBootOption (
  IN CONST EFI_BOOT_MANAGER_LOAD_OPTION  *Key,
  IN CONST EFI_BOOT_MANAGER_LOAD_OPTION  *Array,
  IN UINTN                               Count
  )
{
  UINTN  Index;

  for (Index = 0; Index < Count; Index++) {
    if ((StrCmp (Key->Description, Array[Index].Description) == 0) &&
        (CompareMem (Key->FilePath, Array[Index].FilePath, GetDevicePathSize (Key->FilePath)) == 0) &&
        (Key->OptionalDataSize == Array[Index].OptionalDataSize) &&
        (CompareMem (Key->OptionalData, Array[Index].OptionalData, Key->OptionalDataSize) == 0))
    {
      return (INTN)Index;
    }
  }

  return -1;
}

/**
  Refresh all legacy boot options.

**/
VOID
EFIAPI
LegacyBmRefreshAllBootOption (
  VOID
  )
{
  EFI_STATUS                    Status;
  EFI_LEGACY_BIOS_PROTOCOL      *LegacyBios;
  UINTN                         RootBridgeHandleCount;
  EFI_HANDLE                    *RootBridgeHandleBuffer;
  UINTN                         HandleCount;
  EFI_HANDLE                    *HandleBuffer;
  UINTN                         RootBridgeIndex;
  UINTN                         Index;
  UINTN                         Flags;
  EFI_BOOT_MANAGER_LOAD_OPTION  *BootOptions;
  UINTN                         BootOptionCount;
  EFI_BOOT_MANAGER_LOAD_OPTION  *ExistingBootOptions;
  UINTN                         ExistingBootOptionCount;

  Status = gBS->LocateProtocol (&gEfiLegacyBiosProtocolGuid, NULL, (VOID **)&LegacyBios);
  if (EFI_ERROR (Status)) {
    LegacyBmDeleteAllBootOptions ();
    return;
  }

  PERF_START (NULL, "LegacyBootOptionEnum", "BDS", 0);

  //
  // Before enumerating the legacy boot option, we need to dispatch all the legacy option roms
  // to ensure the GetBbsInfo() counts all the legacy devices.
  //
  gBS->LocateHandleBuffer (
         ByProtocol,
         &gEfiPciRootBridgeIoProtocolGuid,
         NULL,
         &RootBridgeHandleCount,
         &RootBridgeHandleBuffer
         );
  for (RootBridgeIndex = 0; RootBridgeIndex < RootBridgeHandleCount; RootBridgeIndex++) {
    gBS->ConnectController (RootBridgeHandleBuffer[RootBridgeIndex], NULL, NULL, FALSE);
    gBS->LocateHandleBuffer (
           ByProtocol,
           &gEfiPciIoProtocolGuid,
           NULL,
           &HandleCount,
           &HandleBuffer
           );
    for (Index = 0; Index < HandleCount; Index++) {
      //
      // Start the thunk driver so that the legacy option rom gets dispatched.
      // Note: We don't directly call InstallPciRom because some thunk drivers
      // (e.g. BlockIo thunk driver) depend on the immediate result after dispatching
      //
      Status = LegacyBios->CheckPciRom (
                             LegacyBios,
                             HandleBuffer[Index],
                             NULL,
                             NULL,
                             &Flags
                             );
      if (!EFI_ERROR (Status)) {
        gBS->ConnectController (HandleBuffer[Index], NULL, NULL, FALSE);
      }
    }
  }

  //
  // Same algorithm pattern as the EfiBootManagerRefreshAllBootOption
  // Firstly delete the invalid legacy boot options,
  // then enumerate and save the newly appeared legacy boot options
  // the last step is legacy boot option special action to refresh the LegacyDevOrder variable
  //
  LegacyBmDeleteAllInvalidBootOptions ();

  ExistingBootOptions = EfiBootManagerGetLoadOptions (&ExistingBootOptionCount, LoadOptionTypeBoot);
  BootOptions         = LegacyBmEnumerateAllBootOptions (&BootOptionCount);

  for (Index = 0; Index < BootOptionCount; Index++) {
    if (LegacyBmFindBootOption (&BootOptions[Index], ExistingBootOptions, ExistingBootOptionCount) == -1) {
      Status = EfiBootManagerAddLoadOptionVariable (&BootOptions[Index], (UINTN)-1);
      DEBUG ((
        DEBUG_INFO,
        "[LegacyBds] New Boot Option: Boot%04x Bbs0x%04x %s %r\n",
        (UINTN)BootOptions[Index].OptionNumber,
        (UINTN)((LEGACY_BM_BOOT_OPTION_BBS_DATA *)BootOptions[Index].OptionalData)->BbsIndex,
        BootOptions[Index].Description,
        Status
        ));
      //
      // Continue upon failure to add boot option.
      //
    }
  }

  EfiBootManagerFreeLoadOptions (ExistingBootOptions, ExistingBootOptionCount);
  EfiBootManagerFreeLoadOptions (BootOptions, BootOptionCount);

  //
  // Failure to create LegacyDevOrder variable only impacts the boot order.
  //
  LegacyBmUpdateDevOrder ();

  PERF_END (NULL, "LegacyBootOptionEnum", "BDS", 0);
}
