/** @file
  Library functions which relate with boot option description.

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

**/

#include "InternalBm.h"

#define VENDOR_IDENTIFICATION_OFFSET     3
#define VENDOR_IDENTIFICATION_LENGTH     8
#define PRODUCT_IDENTIFICATION_OFFSET    11
#define PRODUCT_IDENTIFICATION_LENGTH    16

CONST UINT16 mBmUsbLangId    = 0x0409; // English
CHAR16       mBmUefiPrefix[] = L"UEFI ";

LIST_ENTRY mPlatformBootDescriptionHandlers = INITIALIZE_LIST_HEAD_VARIABLE (mPlatformBootDescriptionHandlers);

/**
  For a bootable Device path, return its boot type.

  @param  DevicePath        The bootable device Path to check

  @retval AcpiFloppyBoot    If given device path contains ACPI_DEVICE_PATH type device path node
                            which HID is floppy device.
  @retval MessageAtapiBoot  If given device path contains MESSAGING_DEVICE_PATH type device path node
                            and its last device path node's subtype is MSG_ATAPI_DP.
  @retval MessageSataBoot   If given device path contains MESSAGING_DEVICE_PATH type device path node
                            and its last device path node's subtype is MSG_SATA_DP.
  @retval MessageScsiBoot   If given device path contains MESSAGING_DEVICE_PATH type device path node
                            and its last device path node's subtype is MSG_SCSI_DP.
  @retval MessageUsbBoot    If given device path contains MESSAGING_DEVICE_PATH type device path node
                            and its last device path node's subtype is MSG_USB_DP.
  @retval BmMiscBoot        If tiven device path doesn't match the above condition.

**/
BM_BOOT_TYPE
BmDevicePathType (
  IN  EFI_DEVICE_PATH_PROTOCOL     *DevicePath
  )
{
  EFI_DEVICE_PATH_PROTOCOL      *Node;
  EFI_DEVICE_PATH_PROTOCOL      *NextNode;

  ASSERT (DevicePath != NULL);

  for (Node = DevicePath; !IsDevicePathEndType (Node); Node = NextDevicePathNode (Node)) {
    switch (DevicePathType (Node)) {

      case ACPI_DEVICE_PATH:
        if (EISA_ID_TO_NUM (((ACPI_HID_DEVICE_PATH *) Node)->HID) == 0x0604) {
          return BmAcpiFloppyBoot;
        }
        break;

      case HARDWARE_DEVICE_PATH:
        if (DevicePathSubType (Node) == HW_CONTROLLER_DP) {
          return BmHardwareDeviceBoot;
        }
        break;

      case MESSAGING_DEVICE_PATH:
        //
        // Skip LUN device node
        //
        NextNode = Node;
        do {
          NextNode = NextDevicePathNode (NextNode);
        } while (
            (DevicePathType (NextNode) == MESSAGING_DEVICE_PATH) &&
            (DevicePathSubType(NextNode) == MSG_DEVICE_LOGICAL_UNIT_DP)
            );

        //
        // If the device path not only point to driver device, it is not a messaging device path,
        //
        if (!IsDevicePathEndType (NextNode)) {
          continue;
        }

        switch (DevicePathSubType (Node)) {
        case MSG_ATAPI_DP:
          return BmMessageAtapiBoot;
          break;

        case MSG_SATA_DP:
          return BmMessageSataBoot;
          break;

        case MSG_USB_DP:
          return BmMessageUsbBoot;
          break;

        case MSG_SCSI_DP:
          return BmMessageScsiBoot;
          break;
        }
    }
  }

  return BmMiscBoot;
}

/**
  Eliminate the extra spaces in the Str to one space.

  @param    Str     Input string info.
**/
VOID
BmEliminateExtraSpaces (
  IN CHAR16                    *Str
  )
{
  UINTN                        Index;
  UINTN                        ActualIndex;

  for (Index = 0, ActualIndex = 0; Str[Index] != L'\0'; Index++) {
    if ((Str[Index] != L' ') || ((ActualIndex > 0) && (Str[ActualIndex - 1] != L' '))) {
      Str[ActualIndex++] = Str[Index];
    }
  }
  Str[ActualIndex] = L'\0';
}

/**
  Try to get the controller's ATA/ATAPI description.

  @param Handle                Controller handle.

  @return  The description string.
**/
CHAR16 *
BmGetDescriptionFromDiskInfo (
  IN EFI_HANDLE                Handle
  )
{
  UINTN                        Index;
  EFI_STATUS                   Status;
  EFI_DISK_INFO_PROTOCOL       *DiskInfo;
  UINT32                       BufferSize;
  EFI_ATAPI_IDENTIFY_DATA      IdentifyData;
  EFI_SCSI_INQUIRY_DATA        InquiryData;
  CHAR16                       *Description;
  UINTN                        Length;
  CONST UINTN                  ModelNameLength    = 40;
  CONST UINTN                  SerialNumberLength = 20;
  CHAR8                        *StrPtr;
  UINT8                        Temp;
  EFI_DEVICE_PATH_PROTOCOL     *DevicePath;

  Description  = NULL;

  Status = gBS->HandleProtocol (
                  Handle,
                  &gEfiDiskInfoProtocolGuid,
                  (VOID **) &DiskInfo
                  );
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  if (CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoAhciInterfaceGuid) ||
      CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoIdeInterfaceGuid)) {
    BufferSize   = sizeof (EFI_ATAPI_IDENTIFY_DATA);
    Status = DiskInfo->Identify (
                         DiskInfo,
                         &IdentifyData,
                         &BufferSize
                         );
    if (!EFI_ERROR (Status)) {
      Description = AllocateZeroPool ((ModelNameLength + SerialNumberLength + 2) * sizeof (CHAR16));
      ASSERT (Description != NULL);
      for (Index = 0; Index + 1 < ModelNameLength; Index += 2) {
        Description[Index]     = (CHAR16) IdentifyData.ModelName[Index + 1];
        Description[Index + 1] = (CHAR16) IdentifyData.ModelName[Index];
      }

      Length = Index;
      Description[Length++] = L' ';

      for (Index = 0; Index + 1 < SerialNumberLength; Index += 2) {
        Description[Length + Index]     = (CHAR16) IdentifyData.SerialNo[Index + 1];
        Description[Length + Index + 1] = (CHAR16) IdentifyData.SerialNo[Index];
      }
      Length += Index;
      Description[Length++] = L'\0';
      ASSERT (Length == ModelNameLength + SerialNumberLength + 2);

      BmEliminateExtraSpaces (Description);
    }
  } else if (CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoScsiInterfaceGuid)) {
    BufferSize   = sizeof (EFI_SCSI_INQUIRY_DATA);
    Status = DiskInfo->Inquiry (
                         DiskInfo,
                         &InquiryData,
                         &BufferSize
                         );
    if (!EFI_ERROR (Status)) {
      Description = AllocateZeroPool ((VENDOR_IDENTIFICATION_LENGTH + PRODUCT_IDENTIFICATION_LENGTH + 2) * sizeof (CHAR16));
      ASSERT (Description != NULL);

      //
      // Per SCSI spec, EFI_SCSI_INQUIRY_DATA.Reserved_5_95[3 - 10] save the Verdor identification
      // EFI_SCSI_INQUIRY_DATA.Reserved_5_95[11 - 26] save the product identification,
      // Here combine the vendor identification and product identification to the description.
      //
      StrPtr = (CHAR8 *) (&InquiryData.Reserved_5_95[VENDOR_IDENTIFICATION_OFFSET]);
      Temp = StrPtr[VENDOR_IDENTIFICATION_LENGTH];
      StrPtr[VENDOR_IDENTIFICATION_LENGTH] = '\0';
      AsciiStrToUnicodeStrS (StrPtr, Description, VENDOR_IDENTIFICATION_LENGTH + 1);
      StrPtr[VENDOR_IDENTIFICATION_LENGTH] = Temp;

      //
      // Add one space at the middle of vendor information and product information.
      //
      Description[VENDOR_IDENTIFICATION_LENGTH] = L' ';

      StrPtr = (CHAR8 *) (&InquiryData.Reserved_5_95[PRODUCT_IDENTIFICATION_OFFSET]);
      StrPtr[PRODUCT_IDENTIFICATION_LENGTH] = '\0';
      AsciiStrToUnicodeStrS (StrPtr, Description + VENDOR_IDENTIFICATION_LENGTH + 1, PRODUCT_IDENTIFICATION_LENGTH + 1);

      BmEliminateExtraSpaces (Description);
    }
  } else if (CompareGuid (&DiskInfo->Interface, &gEfiDiskInfoSdMmcInterfaceGuid)) {
    DevicePath = DevicePathFromHandle (Handle);
    if (DevicePath == NULL) {
      return NULL;
    }

    while (!IsDevicePathEnd (DevicePath) && (DevicePathType (DevicePath) != MESSAGING_DEVICE_PATH)) {
      DevicePath = NextDevicePathNode (DevicePath);
    }
    if (IsDevicePathEnd (DevicePath)) {
      return NULL;
    }

    if (DevicePathSubType (DevicePath) == MSG_SD_DP) {
      Description = L"SD Device";
    } else if (DevicePathSubType (DevicePath) == MSG_EMMC_DP) {
      Description = L"eMMC Device";
    } else {
      return NULL;
    }

    Description = AllocateCopyPool (StrSize (Description), Description);
  }

  return Description;
}

/**
  Try to get the controller's USB description.

  @param Handle                Controller handle.

  @return  The description string.
**/
CHAR16 *
BmGetUsbDescription (
  IN EFI_HANDLE                Handle
  )
{
  EFI_STATUS                   Status;
  EFI_USB_IO_PROTOCOL          *UsbIo;
  CHAR16                       NullChar;
  CHAR16                       *Manufacturer;
  CHAR16                       *Product;
  CHAR16                       *SerialNumber;
  CHAR16                       *Description;
  EFI_USB_DEVICE_DESCRIPTOR    DevDesc;
  UINTN                        DescMaxSize;

  Status = gBS->HandleProtocol (
                  Handle,
                  &gEfiUsbIoProtocolGuid,
                  (VOID **) &UsbIo
                  );
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  NullChar = L'\0';

  Status = UsbIo->UsbGetDeviceDescriptor (UsbIo, &DevDesc);
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  Status = UsbIo->UsbGetStringDescriptor (
                    UsbIo,
                    mBmUsbLangId,
                    DevDesc.StrManufacturer,
                    &Manufacturer
                    );
  if (EFI_ERROR (Status)) {
    Manufacturer = &NullChar;
  }

  Status = UsbIo->UsbGetStringDescriptor (
                    UsbIo,
                    mBmUsbLangId,
                    DevDesc.StrProduct,
                    &Product
                    );
  if (EFI_ERROR (Status)) {
    Product = &NullChar;
  }

  Status = UsbIo->UsbGetStringDescriptor (
                    UsbIo,
                    mBmUsbLangId,
                    DevDesc.StrSerialNumber,
                    &SerialNumber
                    );
  if (EFI_ERROR (Status)) {
    SerialNumber = &NullChar;
  }

  if ((Manufacturer == &NullChar) &&
      (Product == &NullChar) &&
      (SerialNumber == &NullChar)
      ) {
    return NULL;
  }

  DescMaxSize = StrSize (Manufacturer) + StrSize (Product) + StrSize (SerialNumber);
  Description = AllocateZeroPool (DescMaxSize);
  ASSERT (Description != NULL);
  StrCatS (Description, DescMaxSize/sizeof(CHAR16), Manufacturer);
  StrCatS (Description, DescMaxSize/sizeof(CHAR16), L" ");

  StrCatS (Description, DescMaxSize/sizeof(CHAR16), Product);
  StrCatS (Description, DescMaxSize/sizeof(CHAR16), L" ");

  StrCatS (Description, DescMaxSize/sizeof(CHAR16), SerialNumber);

  if (Manufacturer != &NullChar) {
    FreePool (Manufacturer);
  }
  if (Product != &NullChar) {
    FreePool (Product);
  }
  if (SerialNumber != &NullChar) {
    FreePool (SerialNumber);
  }

  BmEliminateExtraSpaces (Description);

  return Description;
}

/**
  Return the description for network boot device.

  @param Handle                Controller handle.

  @return  The description string.
**/
CHAR16 *
BmGetNetworkDescription (
  IN EFI_HANDLE                  Handle
  )
{
  EFI_STATUS                     Status;
  EFI_DEVICE_PATH_PROTOCOL       *DevicePath;
  MAC_ADDR_DEVICE_PATH           *Mac;
  VLAN_DEVICE_PATH               *Vlan;
  EFI_DEVICE_PATH_PROTOCOL       *Ip;
  EFI_DEVICE_PATH_PROTOCOL       *Uri;
  CHAR16                         *Description;
  UINTN                          DescriptionSize;

  Status = gBS->OpenProtocol (
                  Handle,
                  &gEfiLoadFileProtocolGuid,
                  NULL,
                  gImageHandle,
                  Handle,
                  EFI_OPEN_PROTOCOL_TEST_PROTOCOL
                  );
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  Status = gBS->OpenProtocol (
                  Handle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &DevicePath,
                  gImageHandle,
                  Handle,
                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                  );
  if (EFI_ERROR (Status) || (DevicePath == NULL)) {
    return NULL;
  }

  //
  // The PXE device path is like:
  //   ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)]
  //   ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)]/IPv4(...)
  //   ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)]/IPv6(...)
  //
  // The HTTP device path is like:
  //   ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)]/IPv4(...)[/Dns(...)]/Uri(...)
  //   ....../Mac(...)[/Vlan(...)][/Wi-Fi(...)]/IPv6(...)[/Dns(...)]/Uri(...)
  //
  while (!IsDevicePathEnd (DevicePath) &&
         ((DevicePathType (DevicePath) != MESSAGING_DEVICE_PATH) ||
          (DevicePathSubType (DevicePath) != MSG_MAC_ADDR_DP))
         ) {
    DevicePath = NextDevicePathNode (DevicePath);
  }

  if (IsDevicePathEnd (DevicePath)) {
    return NULL;
  }

  Mac = (MAC_ADDR_DEVICE_PATH *) DevicePath;
  DevicePath = NextDevicePathNode (DevicePath);

  //
  // Locate the optional Vlan node
  //
  if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) &&
      (DevicePathSubType (DevicePath) == MSG_VLAN_DP)
      ) {
    Vlan = (VLAN_DEVICE_PATH *) DevicePath;
    DevicePath = NextDevicePathNode (DevicePath);
  } else {
    Vlan = NULL;
  }

  //
  // Skip the optional Wi-Fi node
  //
  if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) &&
      (DevicePathSubType (DevicePath) == MSG_WIFI_DP)
      ) {
    DevicePath = NextDevicePathNode (DevicePath);
  }

  //
  // Locate the IP node
  //
  if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) &&
      ((DevicePathSubType (DevicePath) == MSG_IPv4_DP) ||
       (DevicePathSubType (DevicePath) == MSG_IPv6_DP))
      ) {
    Ip = DevicePath;
    DevicePath = NextDevicePathNode (DevicePath);
  } else {
    Ip = NULL;
  }

  //
  // Skip the optional DNS node
  //
  if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) &&
      (DevicePathSubType (DevicePath) == MSG_DNS_DP)
      ) {
    DevicePath = NextDevicePathNode (DevicePath);
  }

  //
  // Locate the URI node
  //
  if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) &&
      (DevicePathSubType (DevicePath) == MSG_URI_DP)
      ) {
    Uri = DevicePath;
    DevicePath = NextDevicePathNode (DevicePath);
  } else {
    Uri = NULL;
  }

  //
  // Build description like below:
  //   "PXEv6 (MAC:112233445566 VLAN1)"
  //   "HTTPv4 (MAC:112233445566)"
  //
  DescriptionSize = sizeof (L"HTTPv6 (MAC:112233445566 VLAN65535)");
  Description     = AllocatePool (DescriptionSize);
  ASSERT (Description != NULL);
  UnicodeSPrint (
    Description, DescriptionSize,
    (Vlan == NULL) ?
    L"%sv%d (MAC:%02x%02x%02x%02x%02x%02x)" :
    L"%sv%d (MAC:%02x%02x%02x%02x%02x%02x VLAN%d)",
    (Uri == NULL) ? L"PXE" : L"HTTP",
    ((Ip == NULL) || (DevicePathSubType (Ip) == MSG_IPv4_DP)) ? 4 : 6,
    Mac->MacAddress.Addr[0], Mac->MacAddress.Addr[1], Mac->MacAddress.Addr[2],
    Mac->MacAddress.Addr[3], Mac->MacAddress.Addr[4], Mac->MacAddress.Addr[5],
    (Vlan == NULL) ? 0 : Vlan->VlanId
    );
  return Description;
}

/**
  Return the boot description for LoadFile

  @param Handle                Controller handle.

  @return  The description string.
**/
CHAR16 *
BmGetLoadFileDescription (
  IN EFI_HANDLE                  Handle
  )
{
  EFI_STATUS                            Status;
  EFI_DEVICE_PATH_PROTOCOL              *FilePath;
  EFI_DEVICE_PATH_PROTOCOL              *DevicePathNode;
  CHAR16                                *Description;
  EFI_LOAD_FILE_PROTOCOL                *LoadFile;

  Status = gBS->HandleProtocol (Handle, &gEfiLoadFileProtocolGuid, (VOID **)&LoadFile);
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  //
  // Get the file name
  //
  Description = NULL;
  Status = gBS->HandleProtocol (Handle, &gEfiDevicePathProtocolGuid, (VOID **)&FilePath);
  if (!EFI_ERROR (Status)) {
    DevicePathNode = FilePath;
    while (!IsDevicePathEnd (DevicePathNode)) {
      if (DevicePathNode->Type == MEDIA_DEVICE_PATH && DevicePathNode->SubType == MEDIA_FILEPATH_DP) {
        Description = (CHAR16 *)(DevicePathNode + 1);
        break;
      }
      DevicePathNode = NextDevicePathNode (DevicePathNode);
    }
  }

  if (Description != NULL) {
    return AllocateCopyPool (StrSize (Description), Description);
  }

  return NULL;
}

/**
  Return the boot description for NVME boot device.

  @param Handle                Controller handle.

  @return  The description string.
**/
CHAR16 *
BmGetNvmeDescription (
  IN EFI_HANDLE                      Handle
  )
{
  EFI_STATUS                               Status;
  EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL       *NvmePassthru;
  EFI_DEV_PATH_PTR                         DevicePath;
  EFI_NVM_EXPRESS_PASS_THRU_COMMAND_PACKET CommandPacket;
  EFI_NVM_EXPRESS_COMMAND                  Command;
  EFI_NVM_EXPRESS_COMPLETION               Completion;
  NVME_ADMIN_CONTROLLER_DATA               ControllerData;
  CHAR16                                   *Description;
  CHAR16                                   *Char;
  UINTN                                    Index;

  Status = gBS->HandleProtocol (Handle, &gEfiDevicePathProtocolGuid, (VOID **) &DevicePath.DevPath);
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  Status = gBS->LocateDevicePath (&gEfiNvmExpressPassThruProtocolGuid, &DevicePath.DevPath, &Handle);
  if (EFI_ERROR (Status) ||
      (DevicePathType (DevicePath.DevPath) != MESSAGING_DEVICE_PATH) ||
      (DevicePathSubType (DevicePath.DevPath) != MSG_NVME_NAMESPACE_DP)) {
    //
    // Do not return description when the Handle is not a child of NVME controller.
    //
    return NULL;
  }

  //
  // Send ADMIN_IDENTIFY command to NVME controller to get the model and serial number.
  //
  Status = gBS->HandleProtocol (Handle, &gEfiNvmExpressPassThruProtocolGuid, (VOID **) &NvmePassthru);
  ASSERT_EFI_ERROR (Status);

  ZeroMem (&CommandPacket, sizeof(EFI_NVM_EXPRESS_PASS_THRU_COMMAND_PACKET));
  ZeroMem (&Command, sizeof(EFI_NVM_EXPRESS_COMMAND));
  ZeroMem (&Completion, sizeof(EFI_NVM_EXPRESS_COMPLETION));

  Command.Cdw0.Opcode = NVME_ADMIN_IDENTIFY_CMD;
  //
  // According to Nvm Express 1.1 spec Figure 38, When not used, the field shall be cleared to 0h.
  // For the Identify command, the Namespace Identifier is only used for the Namespace data structure.
  //
  Command.Nsid        = 0;
  CommandPacket.NvmeCmd        = &Command;
  CommandPacket.NvmeCompletion = &Completion;
  CommandPacket.TransferBuffer = &ControllerData;
  CommandPacket.TransferLength = sizeof (ControllerData);
  CommandPacket.CommandTimeout = EFI_TIMER_PERIOD_SECONDS (5);
  CommandPacket.QueueType      = NVME_ADMIN_QUEUE;
  //
  // Set bit 0 (Cns bit) to 1 to identify a controller
  //
  Command.Cdw10                = 1;
  Command.Flags                = CDW10_VALID;

  Status = NvmePassthru->PassThru (
                               NvmePassthru,
                               0,
                               &CommandPacket,
                               NULL
                               );
  if (EFI_ERROR (Status)) {
    return NULL;
  }

  Description = AllocateZeroPool (
                  (ARRAY_SIZE (ControllerData.Mn) + 1
                   + ARRAY_SIZE (ControllerData.Sn) + 1
                   + MAXIMUM_VALUE_CHARACTERS + 1
                   ) * sizeof (CHAR16));
  if (Description != NULL) {
    Char = Description;
    for (Index = 0; Index < ARRAY_SIZE (ControllerData.Mn); Index++) {
      *(Char++) = (CHAR16) ControllerData.Mn[Index];
    }
    *(Char++) = L' ';
    for (Index = 0; Index < ARRAY_SIZE (ControllerData.Sn); Index++) {
      *(Char++) = (CHAR16) ControllerData.Sn[Index];
    }
    *(Char++) = L' ';
    UnicodeValueToStringS (
      Char, sizeof (CHAR16) * (MAXIMUM_VALUE_CHARACTERS + 1),
      0, DevicePath.NvmeNamespace->NamespaceId, 0
      );
    BmEliminateExtraSpaces (Description);
  }

  return Description;
}

/**
  Return the boot description for the controller based on the type.

  @param Handle                Controller handle.

  @return  The description string.
**/
CHAR16 *
BmGetMiscDescription (
  IN EFI_HANDLE                  Handle
  )
{
  EFI_STATUS                      Status;
  CHAR16                          *Description;
  EFI_BLOCK_IO_PROTOCOL           *BlockIo;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs;

  switch (BmDevicePathType (DevicePathFromHandle (Handle))) {
  case BmAcpiFloppyBoot:
    Description = L"Floppy";
    break;

  case BmMessageAtapiBoot:
  case BmMessageSataBoot:
    Status = gBS->HandleProtocol (Handle, &gEfiBlockIoProtocolGuid, (VOID **) &BlockIo);
    ASSERT_EFI_ERROR (Status);
    //
    // Assume a removable SATA device should be the DVD/CD device
    //
    Description = BlockIo->Media->RemovableMedia ? L"DVD/CDROM" : L"Hard Drive";
    break;

  case BmMessageUsbBoot:
    Description = L"USB Device";
    break;

  case BmMessageScsiBoot:
    Description = L"SCSI Device";
    break;

  case BmHardwareDeviceBoot:
    Status = gBS->HandleProtocol (Handle, &gEfiBlockIoProtocolGuid, (VOID **) &BlockIo);
    if (!EFI_ERROR (Status)) {
      Description = BlockIo->Media->RemovableMedia ? L"Removable Disk" : L"Hard Drive";
    } else {
      Description = L"Misc Device";
    }
    break;

  default:
    Status = gBS->HandleProtocol (Handle, &gEfiSimpleFileSystemProtocolGuid, (VOID **) &Fs);
    if (!EFI_ERROR (Status)) {
      Description = L"Non-Block Boot Device";
    } else {
      Description = L"Misc Device";
    }
    break;
  }

  return AllocateCopyPool (StrSize (Description), Description);
}

/**
  Register the platform provided boot description handler.

  @param Handler  The platform provided boot description handler

  @retval EFI_SUCCESS          The handler was registered successfully.
  @retval EFI_ALREADY_STARTED  The handler was already registered.
  @retval EFI_OUT_OF_RESOURCES There is not enough resource to perform the registration.
**/
EFI_STATUS
EFIAPI
EfiBootManagerRegisterBootDescriptionHandler (
  IN EFI_BOOT_MANAGER_BOOT_DESCRIPTION_HANDLER  Handler
  )
{
  LIST_ENTRY                                    *Link;
  BM_BOOT_DESCRIPTION_ENTRY                    *Entry;

  for ( Link = GetFirstNode (&mPlatformBootDescriptionHandlers)
      ; !IsNull (&mPlatformBootDescriptionHandlers, Link)
      ; Link = GetNextNode (&mPlatformBootDescriptionHandlers, Link)
      ) {
    Entry = CR (Link, BM_BOOT_DESCRIPTION_ENTRY, Link, BM_BOOT_DESCRIPTION_ENTRY_SIGNATURE);
    if (Entry->Handler == Handler) {
      return EFI_ALREADY_STARTED;
    }
  }

  Entry = AllocatePool (sizeof (BM_BOOT_DESCRIPTION_ENTRY));
  if (Entry == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Entry->Signature = BM_BOOT_DESCRIPTION_ENTRY_SIGNATURE;
  Entry->Handler   = Handler;
  InsertTailList (&mPlatformBootDescriptionHandlers, &Entry->Link);
  return EFI_SUCCESS;
}

BM_GET_BOOT_DESCRIPTION mBmBootDescriptionHandlers[] = {
  BmGetUsbDescription,
  BmGetDescriptionFromDiskInfo,
  BmGetNetworkDescription,
  BmGetLoadFileDescription,
  BmGetNvmeDescription,
  BmGetMiscDescription
};

/**
  Return the boot description for the controller.

  @param Handle                Controller handle.

  @return  The description string.
**/
CHAR16 *
BmGetBootDescription (
  IN EFI_HANDLE                  Handle
  )
{
  LIST_ENTRY                     *Link;
  BM_BOOT_DESCRIPTION_ENTRY      *Entry;
  CHAR16                         *Description;
  CHAR16                         *DefaultDescription;
  CHAR16                         *Temp;
  UINTN                          Index;

  //
  // Firstly get the default boot description
  //
  DefaultDescription = NULL;
  for (Index = 0; Index < ARRAY_SIZE (mBmBootDescriptionHandlers); Index++) {
    DefaultDescription = mBmBootDescriptionHandlers[Index] (Handle);
    if (DefaultDescription != NULL) {
      //
      // Avoid description confusion between UEFI & Legacy boot option by adding "UEFI " prefix
      // ONLY for core provided boot description handler.
      //
      Temp = AllocatePool (StrSize (DefaultDescription) + sizeof (mBmUefiPrefix));
      ASSERT (Temp != NULL);
      StrCpyS (Temp, (StrSize (DefaultDescription) + sizeof (mBmUefiPrefix)) / sizeof (CHAR16), mBmUefiPrefix);
      StrCatS (Temp, (StrSize (DefaultDescription) + sizeof (mBmUefiPrefix)) / sizeof (CHAR16), DefaultDescription);
      FreePool (DefaultDescription);
      DefaultDescription = Temp;
      break;
    }
  }
  ASSERT (DefaultDescription != NULL);

  //
  // Secondly query platform for the better boot description
  //
  for ( Link = GetFirstNode (&mPlatformBootDescriptionHandlers)
      ; !IsNull (&mPlatformBootDescriptionHandlers, Link)
      ; Link = GetNextNode (&mPlatformBootDescriptionHandlers, Link)
      ) {
    Entry = CR (Link, BM_BOOT_DESCRIPTION_ENTRY, Link, BM_BOOT_DESCRIPTION_ENTRY_SIGNATURE);
    Description = Entry->Handler (Handle, DefaultDescription);
    if (Description != NULL) {
      FreePool (DefaultDescription);
      return Description;
    }
  }

  return DefaultDescription;
}

/**
  Enumerate all boot option descriptions and append " 2"/" 3"/... to make
  unique description.

  @param BootOptions            Array of boot options.
  @param BootOptionCount        Count of boot options.
**/
VOID
BmMakeBootOptionDescriptionUnique (
  EFI_BOOT_MANAGER_LOAD_OPTION         *BootOptions,
  UINTN                                BootOptionCount
  )
{
  UINTN                                Base;
  UINTN                                Index;
  UINTN                                DescriptionSize;
  UINTN                                MaxSuffixSize;
  BOOLEAN                              *Visited;
  UINTN                                MatchCount;

  if (BootOptionCount == 0) {
    return;
  }

  //
  // Calculate the maximum buffer size for the number suffix.
  // The initial sizeof (CHAR16) is for the blank space before the number.
  //
  MaxSuffixSize = sizeof (CHAR16);
  for (Index = BootOptionCount; Index != 0; Index = Index / 10) {
    MaxSuffixSize += sizeof (CHAR16);
  }

  Visited = AllocateZeroPool (sizeof (BOOLEAN) * BootOptionCount);
  ASSERT (Visited != NULL);

  for (Base = 0; Base < BootOptionCount; Base++) {
    if (!Visited[Base]) {
      MatchCount      = 1;
      Visited[Base]   = TRUE;
      DescriptionSize = StrSize (BootOptions[Base].Description);
      for (Index = Base + 1; Index < BootOptionCount; Index++) {
        if (!Visited[Index] && StrCmp (BootOptions[Base].Description, BootOptions[Index].Description) == 0) {
          Visited[Index] = TRUE;
          MatchCount++;
          FreePool (BootOptions[Index].Description);
          BootOptions[Index].Description = AllocatePool (DescriptionSize + MaxSuffixSize);
          UnicodeSPrint (
            BootOptions[Index].Description, DescriptionSize + MaxSuffixSize,
            L"%s %d",
            BootOptions[Base].Description, MatchCount
            );
        }
      }
    }
  }

  FreePool (Visited);
}
