/** @file
handles console redirection from boot manager

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

**/

#include "BootMaintenanceManager.h"

/**
  Function compares a device path data structure to that of all the nodes of a
  second device path instance.

  @param  Multi                 A pointer to a multi-instance device path data
                                structure.
  @param  Single                A pointer to a single-instance device path data
                                structure.

  @retval TRUE                  If the Single device path is contained within Multi device path.
  @retval FALSE                 The Single device path is not match within Multi device path.

**/
BOOLEAN
MatchDevicePaths (
  IN  EFI_DEVICE_PATH_PROTOCOL  *Multi,
  IN  EFI_DEVICE_PATH_PROTOCOL  *Single
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePathInst;
  UINTN                     Size;

  if (Multi == NULL || Single  == NULL) {
    return FALSE;
  }

  DevicePath     = Multi;
  DevicePathInst = GetNextDevicePathInstance (&DevicePath, &Size);

  //
  // Search for the match of 'Single' in 'Multi'
  //
  while (DevicePathInst != NULL) {
    //
    // If the single device path is found in multiple device paths,
    // return success
    //
    if (CompareMem (Single, DevicePathInst, Size) == 0) {
      FreePool (DevicePathInst);
      return TRUE;
    }

    FreePool (DevicePathInst);
    DevicePathInst = GetNextDevicePathInstance (&DevicePath, &Size);
  }

  return FALSE;
}

/**
  Check whether the device path node is ISA Serial Node.

  @param Acpi           Device path node to be checked

  @retval TRUE          It's ISA Serial Node.
  @retval FALSE         It's NOT ISA Serial Node.

**/
BOOLEAN
IsIsaSerialNode (
  IN ACPI_HID_DEVICE_PATH *Acpi
  )
{
  return (BOOLEAN) (
      (DevicePathType (Acpi) == ACPI_DEVICE_PATH) &&
      (DevicePathSubType (Acpi) == ACPI_DP) &&
      (ReadUnaligned32 (&Acpi->HID) == EISA_PNP_ID (0x0501))
      );
}

/**
  Update Com Ports attributes from DevicePath

  @param DevicePath      DevicePath that contains Com ports

  @retval EFI_SUCCESS   The update is successful.

**/
EFI_STATUS
UpdateComAttributeFromVariable (
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath
  );

/**
  Update the multi-instance device path of Terminal Device based on
  the global TerminalMenu. If ChangeTernimal is TRUE, the terminal
  device path in the Terminal Device in TerminalMenu is also updated.

  @param DevicePath      The multi-instance device path.
  @param ChangeTerminal  TRUE, then device path in the Terminal Device
                         in TerminalMenu is also updated; FALSE, no update.

  @return EFI_SUCCESS    The function completes successfully.

**/
EFI_STATUS
ChangeTerminalDevicePath (
  IN OUT    EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  IN        BOOLEAN                   ChangeTerminal
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *Node;
  EFI_DEVICE_PATH_PROTOCOL  *Node1;
  ACPI_HID_DEVICE_PATH      *Acpi;
  UART_DEVICE_PATH          *Uart;
  UART_DEVICE_PATH          *Uart1;
  UINTN                     Com;
  BM_TERMINAL_CONTEXT       *NewTerminalContext;
  BM_MENU_ENTRY             *NewMenuEntry;

  Node  = DevicePath;
  Node  = NextDevicePathNode (Node);
  Com   = 0;
  while (!IsDevicePathEnd (Node)) {
    Acpi = (ACPI_HID_DEVICE_PATH *) Node;
    if (IsIsaSerialNode (Acpi)) {
      CopyMem (&Com, &Acpi->UID, sizeof (UINT32));
    }

    NewMenuEntry = BOpt_GetMenuEntry (&TerminalMenu, Com);

    NewTerminalContext = (BM_TERMINAL_CONTEXT *) NewMenuEntry->VariableContext;
    if ((DevicePathType (Node) == MESSAGING_DEVICE_PATH) && (DevicePathSubType (Node) == MSG_UART_DP)) {
      Uart = (UART_DEVICE_PATH *) Node;
      CopyMem (
        &Uart->BaudRate,
        &NewTerminalContext->BaudRate,
        sizeof (UINT64)
        );

      CopyMem (
        &Uart->DataBits,
        &NewTerminalContext->DataBits,
        sizeof (UINT8)
        );

      CopyMem (
        &Uart->Parity,
        &NewTerminalContext->Parity,
        sizeof (UINT8)
        );

      CopyMem (
        &Uart->StopBits,
        &NewTerminalContext->StopBits,
        sizeof (UINT8)
        );
      //
      // Change the device path in the ComPort
      //
      if (ChangeTerminal) {
        Node1 = NewTerminalContext->DevicePath;
        Node1 = NextDevicePathNode (Node1);
        while (!IsDevicePathEnd (Node1)) {
          if ((DevicePathType (Node1) == MESSAGING_DEVICE_PATH) && (DevicePathSubType (Node1) == MSG_UART_DP)) {
            Uart1 = (UART_DEVICE_PATH *) Node1;
            CopyMem (
              &Uart1->BaudRate,
              &NewTerminalContext->BaudRate,
              sizeof (UINT64)
              );

            CopyMem (
              &Uart1->DataBits,
              &NewTerminalContext->DataBits,
              sizeof (UINT8)
              );

            CopyMem (
              &Uart1->Parity,
              &NewTerminalContext->Parity,
              sizeof (UINT8)
              );

            CopyMem (
              &Uart1->StopBits,
              &NewTerminalContext->StopBits,
              sizeof (UINT8)
              );
            break;
          }
          //
          // end if
          //
          Node1 = NextDevicePathNode (Node1);
        }
        //
        // end while
        //
        break;
      }
    }

    Node = NextDevicePathNode (Node);
  }

  return EFI_SUCCESS;

}

/**
  Update the device path that describing a terminal device
  based on the new BaudRate, Data Bits, parity and Stop Bits
  set.

  @param DevicePath terminal device's path

**/
VOID
ChangeVariableDevicePath (
  IN OUT EFI_DEVICE_PATH_PROTOCOL  *DevicePath
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *Node;
  ACPI_HID_DEVICE_PATH      *Acpi;
  UART_DEVICE_PATH          *Uart;
  UINTN                     Com;
  BM_TERMINAL_CONTEXT       *NewTerminalContext;
  BM_MENU_ENTRY             *NewMenuEntry;

  Node  = DevicePath;
  Node  = NextDevicePathNode (Node);
  Com   = 0;
  while (!IsDevicePathEnd (Node)) {
    Acpi = (ACPI_HID_DEVICE_PATH *) Node;
    if (IsIsaSerialNode (Acpi)) {
      CopyMem (&Com, &Acpi->UID, sizeof (UINT32));
    }

    if ((DevicePathType (Node) == MESSAGING_DEVICE_PATH) && (DevicePathSubType (Node) == MSG_UART_DP)) {
      NewMenuEntry = BOpt_GetMenuEntry (
                      &TerminalMenu,
                      Com
                      );
      ASSERT (NewMenuEntry != NULL);
      NewTerminalContext  = (BM_TERMINAL_CONTEXT *) NewMenuEntry->VariableContext;
      Uart                = (UART_DEVICE_PATH *) Node;
      CopyMem (
        &Uart->BaudRate,
        &NewTerminalContext->BaudRate,
        sizeof (UINT64)
        );

      CopyMem (
        &Uart->DataBits,
        &NewTerminalContext->DataBits,
        sizeof (UINT8)
        );

      CopyMem (
        &Uart->Parity,
        &NewTerminalContext->Parity,
        sizeof (UINT8)
        );

      CopyMem (
        &Uart->StopBits,
        &NewTerminalContext->StopBits,
        sizeof (UINT8)
        );
    }

    Node = NextDevicePathNode (Node);
  }
}

/**
  Retrieve ACPI UID of UART from device path

  @param Handle          The handle for the UART device.
  @param AcpiUid         The ACPI UID on output.

  @retval  TRUE   Find valid UID from device path
  @retval  FALSE  Can't find

**/
BOOLEAN
RetrieveUartUid (
  IN EFI_HANDLE   Handle,
  IN OUT UINT32   *AcpiUid
  )
{
  EFI_STATUS                Status;
  ACPI_HID_DEVICE_PATH      *Acpi;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;

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

  Acpi = NULL;
  for (; !IsDevicePathEnd (DevicePath); DevicePath = NextDevicePathNode (DevicePath)) {
    if ((DevicePathType (DevicePath) == MESSAGING_DEVICE_PATH) && (DevicePathSubType (DevicePath) == MSG_UART_DP)) {
      break;
    }
    //
    // Acpi points to the node before the Uart node
    //
    Acpi = (ACPI_HID_DEVICE_PATH *) DevicePath;
  }

  if ((Acpi != NULL) && IsIsaSerialNode (Acpi)) {
    if (AcpiUid != NULL) {
      CopyMem (AcpiUid, &Acpi->UID, sizeof (UINT32));
    }
    return TRUE;
  } else {
    return FALSE;
  }
}

/**
  Sort Uart handles array with Acpi->UID from low to high.

  @param Handles         EFI_SERIAL_IO_PROTOCOL handle buffer
  @param NoHandles       EFI_SERIAL_IO_PROTOCOL handle count
**/
VOID
SortedUartHandle (
  IN  EFI_HANDLE *Handles,
  IN  UINTN      NoHandles
  )
{
  UINTN       Index1;
  UINTN       Index2;
  UINTN       Position;
  UINT32      AcpiUid1;
  UINT32      AcpiUid2;
  UINT32      TempAcpiUid;
  EFI_HANDLE  TempHandle;

  for (Index1 = 0; Index1 < NoHandles-1; Index1++) {
    if (!RetrieveUartUid (Handles[Index1], &AcpiUid1)) {
      continue;
    }
    TempHandle  = Handles[Index1];
    Position    = Index1;
    TempAcpiUid = AcpiUid1;

    for (Index2 = Index1+1; Index2 < NoHandles; Index2++) {
      if (!RetrieveUartUid (Handles[Index2], &AcpiUid2)) {
        continue;
      }
      if (AcpiUid2 < TempAcpiUid) {
        TempAcpiUid = AcpiUid2;
        TempHandle  = Handles[Index2];
        Position    = Index2;
      }
    }
    Handles[Position] = Handles[Index1];
    Handles[Index1]   = TempHandle;
  }
}

/**
  Test whether DevicePath is a valid Terminal


  @param DevicePath      DevicePath to be checked
  @param Termi           If DevicePath is valid Terminal, terminal type is returned.
  @param Com             If DevicePath is valid Terminal, Com Port type is returned.

  @retval  TRUE         If DevicePath point to a Terminal.
  @retval  FALSE        If DevicePath does not point to a Terminal.

**/
BOOLEAN
IsTerminalDevicePath (
  IN  EFI_DEVICE_PATH_PROTOCOL *DevicePath,
  OUT TYPE_OF_TERMINAL         *Termi,
  OUT UINTN                    *Com
  );

/**
  Build a list containing all serial devices.


  @retval EFI_SUCCESS The function complete successfully.
  @retval EFI_UNSUPPORTED No serial ports present.

**/
EFI_STATUS
LocateSerialIo (
  VOID
  )
{
  UINTN                     Index;
  UINTN                     Index2;
  UINTN                     NoHandles;
  EFI_HANDLE                *Handles;
  EFI_STATUS                Status;
  ACPI_HID_DEVICE_PATH      *Acpi;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
  EFI_SERIAL_IO_PROTOCOL    *SerialIo;
  EFI_DEVICE_PATH_PROTOCOL  *Node;
  EFI_DEVICE_PATH_PROTOCOL  *OutDevicePath;
  EFI_DEVICE_PATH_PROTOCOL  *InpDevicePath;
  EFI_DEVICE_PATH_PROTOCOL  *ErrDevicePath;
  BM_MENU_ENTRY             *NewMenuEntry;
  BM_TERMINAL_CONTEXT       *NewTerminalContext;
  EFI_DEVICE_PATH_PROTOCOL  *NewDevicePath;
  VENDOR_DEVICE_PATH        Vendor;

  //
  // Get all handles that have SerialIo protocol installed
  //
  InitializeListHead (&TerminalMenu.Head);
  TerminalMenu.MenuNumber = 0;
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiSerialIoProtocolGuid,
                  NULL,
                  &NoHandles,
                  &Handles
                  );
  if (EFI_ERROR (Status)) {
    //
    // No serial ports present
    //
    return EFI_UNSUPPORTED;
  }

  //
  // Sort Uart handles array with Acpi->UID from low to high
  // then Terminal menu can be built from low Acpi->UID to high Acpi->UID
  //
  SortedUartHandle (Handles, NoHandles);

  for (Index = 0; Index < NoHandles; Index++) {
    //
    // Check to see whether the handle has DevicePath Protocol installed
    //
    gBS->HandleProtocol (
          Handles[Index],
          &gEfiDevicePathProtocolGuid,
          (VOID **) &DevicePath
          );

    Acpi = NULL;
    for (Node = DevicePath; !IsDevicePathEnd (Node); Node = NextDevicePathNode (Node)) {
      if ((DevicePathType (Node) == MESSAGING_DEVICE_PATH) && (DevicePathSubType (Node) == MSG_UART_DP)) {
        break;
      }
      //
      // Acpi points to the node before Uart node
      //
      Acpi = (ACPI_HID_DEVICE_PATH *) Node;
    }

    if ((Acpi != NULL) && IsIsaSerialNode (Acpi)) {
      NewMenuEntry = BOpt_CreateMenuEntry (BM_TERMINAL_CONTEXT_SELECT);
      if (NewMenuEntry == NULL) {
        FreePool (Handles);
        return EFI_OUT_OF_RESOURCES;
      }

      NewTerminalContext = (BM_TERMINAL_CONTEXT *) NewMenuEntry->VariableContext;
      CopyMem (&NewMenuEntry->OptionNumber, &Acpi->UID, sizeof (UINT32));
      NewTerminalContext->DevicePath = DuplicateDevicePath (DevicePath);
      //
      // BugBug: I have no choice, calling EfiLibStrFromDatahub will hang the system!
      // coz' the misc data for each platform is not correct, actually it's the device path stored in
      // datahub which is not completed, so a searching for end of device path will enter a
      // dead-loop.
      //
      NewMenuEntry->DisplayString = EfiLibStrFromDatahub (DevicePath);
      if (NULL == NewMenuEntry->DisplayString) {
        NewMenuEntry->DisplayString = UiDevicePathToStr (DevicePath);
      }

      NewMenuEntry->HelpString = NULL;

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

      NewMenuEntry->HelpStringToken = NewMenuEntry->DisplayStringToken;

      gBS->HandleProtocol (
            Handles[Index],
            &gEfiSerialIoProtocolGuid,
            (VOID **) &SerialIo
            );

      CopyMem (
        &NewTerminalContext->BaudRate,
        &SerialIo->Mode->BaudRate,
        sizeof (UINT64)
        );

      CopyMem (
        &NewTerminalContext->DataBits,
        &SerialIo->Mode->DataBits,
        sizeof (UINT8)
        );

      CopyMem (
        &NewTerminalContext->Parity,
        &SerialIo->Mode->Parity,
        sizeof (UINT8)
        );

      CopyMem (
        &NewTerminalContext->StopBits,
        &SerialIo->Mode->StopBits,
        sizeof (UINT8)
        );
      InsertTailList (&TerminalMenu.Head, &NewMenuEntry->Link);
      TerminalMenu.MenuNumber++;
    }
  }
  if (Handles != NULL) {
    FreePool (Handles);
  }

  //
  // Get L"ConOut", L"ConIn" and L"ErrOut" from the Var
  //
  GetEfiGlobalVariable2 (L"ConOut", (VOID**)&OutDevicePath, NULL);
  GetEfiGlobalVariable2 (L"ConIn", (VOID**)&InpDevicePath, NULL);
  GetEfiGlobalVariable2 (L"ErrOut", (VOID**)&ErrDevicePath, NULL);
  if (OutDevicePath != NULL) {
    UpdateComAttributeFromVariable (OutDevicePath);
  }

  if (InpDevicePath != NULL) {
    UpdateComAttributeFromVariable (InpDevicePath);
  }

  if (ErrDevicePath != NULL) {
    UpdateComAttributeFromVariable (ErrDevicePath);
  }

  for (Index = 0; Index < TerminalMenu.MenuNumber; Index++) {
    NewMenuEntry = BOpt_GetMenuEntry (&TerminalMenu, Index);
    if (NULL == NewMenuEntry) {
      return EFI_NOT_FOUND;
    }

    NewTerminalContext                = (BM_TERMINAL_CONTEXT *) NewMenuEntry->VariableContext;

    NewTerminalContext->TerminalType  = 0;
    NewTerminalContext->IsConIn       = FALSE;
    NewTerminalContext->IsConOut      = FALSE;
    NewTerminalContext->IsStdErr      = FALSE;

    Vendor.Header.Type                = MESSAGING_DEVICE_PATH;
    Vendor.Header.SubType             = MSG_VENDOR_DP;

    for (Index2 = 0; Index2 < (ARRAY_SIZE (TerminalTypeGuid)); Index2++) {
      CopyMem (&Vendor.Guid, &TerminalTypeGuid[Index2], sizeof (EFI_GUID));
      SetDevicePathNodeLength (&Vendor.Header, sizeof (VENDOR_DEVICE_PATH));
      NewDevicePath = AppendDevicePathNode (
                        NewTerminalContext->DevicePath,
                        (EFI_DEVICE_PATH_PROTOCOL *) &Vendor
                        );
      if (NewMenuEntry->HelpString != NULL) {
        FreePool (NewMenuEntry->HelpString);
      }
      //
      // NewMenuEntry->HelpString = UiDevicePathToStr (NewDevicePath);
      // NewMenuEntry->DisplayString = NewMenuEntry->HelpString;
      //
      NewMenuEntry->HelpString = NULL;

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

      NewMenuEntry->HelpStringToken = NewMenuEntry->DisplayStringToken;

      if (MatchDevicePaths (OutDevicePath, NewDevicePath)) {
        NewTerminalContext->IsConOut      = TRUE;
        NewTerminalContext->TerminalType  = (UINT8) Index2;
      }

      if (MatchDevicePaths (InpDevicePath, NewDevicePath)) {
        NewTerminalContext->IsConIn       = TRUE;
        NewTerminalContext->TerminalType  = (UINT8) Index2;
      }

      if (MatchDevicePaths (ErrDevicePath, NewDevicePath)) {
        NewTerminalContext->IsStdErr      = TRUE;
        NewTerminalContext->TerminalType  = (UINT8) Index2;
      }
    }
  }

  return EFI_SUCCESS;
}

/**
  Update Com Ports attributes from DevicePath

  @param DevicePath      DevicePath that contains Com ports

  @retval EFI_SUCCESS   The update is successful.
  @retval EFI_NOT_FOUND Can not find specific menu entry
**/
EFI_STATUS
UpdateComAttributeFromVariable (
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *Node;
  EFI_DEVICE_PATH_PROTOCOL  *SerialNode;
  ACPI_HID_DEVICE_PATH      *Acpi;
  UART_DEVICE_PATH          *Uart;
  UART_DEVICE_PATH          *Uart1;
  UINTN                     TerminalNumber;
  BM_MENU_ENTRY             *NewMenuEntry;
  BM_TERMINAL_CONTEXT       *NewTerminalContext;
  UINTN                     Index;

  Node            = DevicePath;
  Node            = NextDevicePathNode (Node);
  TerminalNumber  = 0;
  for (Index = 0; Index < TerminalMenu.MenuNumber; Index++) {
    while (!IsDevicePathEnd (Node)) {
      Acpi = (ACPI_HID_DEVICE_PATH *) Node;
      if (IsIsaSerialNode (Acpi)) {
        CopyMem (&TerminalNumber, &Acpi->UID, sizeof (UINT32));
      }

      if ((DevicePathType (Node) == MESSAGING_DEVICE_PATH) && (DevicePathSubType (Node) == MSG_UART_DP)) {
        Uart          = (UART_DEVICE_PATH *) Node;
        NewMenuEntry  = BOpt_GetMenuEntry (&TerminalMenu, TerminalNumber);
        if (NULL == NewMenuEntry) {
          return EFI_NOT_FOUND;
        }

        NewTerminalContext = (BM_TERMINAL_CONTEXT *) NewMenuEntry->VariableContext;
        CopyMem (
          &NewTerminalContext->BaudRate,
          &Uart->BaudRate,
          sizeof (UINT64)
          );

        CopyMem (
          &NewTerminalContext->DataBits,
          &Uart->DataBits,
          sizeof (UINT8)
          );

        CopyMem (
          &NewTerminalContext->Parity,
          &Uart->Parity,
          sizeof (UINT8)
          );

        CopyMem (
          &NewTerminalContext->StopBits,
          &Uart->StopBits,
          sizeof (UINT8)
          );

        SerialNode  = NewTerminalContext->DevicePath;
        SerialNode  = NextDevicePathNode (SerialNode);
        while (!IsDevicePathEnd (SerialNode)) {
          if ((DevicePathType (SerialNode) == MESSAGING_DEVICE_PATH) && (DevicePathSubType (SerialNode) == MSG_UART_DP)) {
            //
            // Update following device paths according to
            // previous acquired uart attributes
            //
            Uart1 = (UART_DEVICE_PATH *) SerialNode;
            CopyMem (
              &Uart1->BaudRate,
              &NewTerminalContext->BaudRate,
              sizeof (UINT64)
              );

            CopyMem (
              &Uart1->DataBits,
              &NewTerminalContext->DataBits,
              sizeof (UINT8)
              );
            CopyMem (
              &Uart1->Parity,
              &NewTerminalContext->Parity,
              sizeof (UINT8)
              );
            CopyMem (
              &Uart1->StopBits,
              &NewTerminalContext->StopBits,
              sizeof (UINT8)
              );

            break;
          }

          SerialNode = NextDevicePathNode (SerialNode);
        }
        //
        // end while
        //
      }

      Node = NextDevicePathNode (Node);
    }
    //
    // end while
    //
  }

  return EFI_SUCCESS;
}

/**
  Build up Console Menu based on types passed in. The type can
  be BM_CONSOLE_IN_CONTEXT_SELECT, BM_CONSOLE_OUT_CONTEXT_SELECT
  and BM_CONSOLE_ERR_CONTEXT_SELECT.

  @param ConsoleMenuType Can be BM_CONSOLE_IN_CONTEXT_SELECT, BM_CONSOLE_OUT_CONTEXT_SELECT
                         and BM_CONSOLE_ERR_CONTEXT_SELECT.

  @retval EFI_UNSUPPORTED The type passed in is not in the 3 types defined.
  @retval EFI_NOT_FOUND   If the EFI Variable defined in UEFI spec with name "ConOutDev",
                          "ConInDev" or "ConErrDev" doesn't exists.
  @retval EFI_OUT_OF_RESOURCES Not enough resource to complete the operations.
  @retval EFI_SUCCESS          Function completes successfully.

**/
EFI_STATUS
GetConsoleMenu (
  IN UINTN              ConsoleMenuType
  )
{
  EFI_DEVICE_PATH_PROTOCOL  *DevicePath;
  EFI_DEVICE_PATH_PROTOCOL  *AllDevicePath;
  EFI_DEVICE_PATH_PROTOCOL  *MultiDevicePath;
  EFI_DEVICE_PATH_PROTOCOL  *DevicePathInst;
  UINTN                     Size;
  UINTN                     AllCount;
  UINTN                     Index;
  UINTN                     Index2;
  BM_MENU_ENTRY             *NewMenuEntry;
  BM_CONSOLE_CONTEXT        *NewConsoleContext;
  TYPE_OF_TERMINAL          Terminal;
  UINTN                     Com;
  BM_MENU_OPTION            *ConsoleMenu;

  DevicePath    = NULL;
  AllDevicePath = NULL;
  AllCount      = 0;
  switch (ConsoleMenuType) {
  case BM_CONSOLE_IN_CONTEXT_SELECT:
    ConsoleMenu   = &ConsoleInpMenu;
    GetEfiGlobalVariable2 (L"ConIn", (VOID**)&DevicePath, NULL);
    GetEfiGlobalVariable2 (L"ConInDev", (VOID**)&AllDevicePath, NULL);
    break;

  case BM_CONSOLE_OUT_CONTEXT_SELECT:
    ConsoleMenu   = &ConsoleOutMenu;
    GetEfiGlobalVariable2 (L"ConOut", (VOID**)&DevicePath, NULL);
    GetEfiGlobalVariable2 (L"ConOutDev", (VOID**)&AllDevicePath, NULL);
    break;

  case BM_CONSOLE_ERR_CONTEXT_SELECT:
    ConsoleMenu   = &ConsoleErrMenu;
    GetEfiGlobalVariable2 (L"ErrOut", (VOID**)&DevicePath, NULL);
    GetEfiGlobalVariable2 (L"ErrOutDev", (VOID**)&AllDevicePath, NULL);
    break;

  default:
    return EFI_UNSUPPORTED;
  }

  if (NULL == AllDevicePath) {
    return EFI_NOT_FOUND;
  }

  InitializeListHead (&ConsoleMenu->Head);

  AllCount                = EfiDevicePathInstanceCount (AllDevicePath);
  ConsoleMenu->MenuNumber = 0;
  //
  // Following is menu building up for Console Devices selected.
  //
  MultiDevicePath = AllDevicePath;
  Index2          = 0;
  for (Index = 0; Index < AllCount; Index++) {
    DevicePathInst  = GetNextDevicePathInstance (&MultiDevicePath, &Size);

    NewMenuEntry    = BOpt_CreateMenuEntry (BM_CONSOLE_CONTEXT_SELECT);
    if (NULL == NewMenuEntry) {
      return EFI_OUT_OF_RESOURCES;
    }

    NewConsoleContext             = (BM_CONSOLE_CONTEXT *) NewMenuEntry->VariableContext;
    NewMenuEntry->OptionNumber    = Index2;

    NewConsoleContext->DevicePath = DuplicateDevicePath (DevicePathInst);
    ASSERT (NewConsoleContext->DevicePath != NULL);
    NewMenuEntry->DisplayString   = EfiLibStrFromDatahub (NewConsoleContext->DevicePath);
    if (NULL == NewMenuEntry->DisplayString) {
      NewMenuEntry->DisplayString = UiDevicePathToStr (NewConsoleContext->DevicePath);
    }

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

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

    NewConsoleContext->IsTerminal = IsTerminalDevicePath (
                                      NewConsoleContext->DevicePath,
                                      &Terminal,
                                      &Com
                                      );

    NewConsoleContext->IsActive = MatchDevicePaths (
                                    DevicePath,
                                    NewConsoleContext->DevicePath
                                    );

    if (NewConsoleContext->IsTerminal) {
      BOpt_DestroyMenuEntry (NewMenuEntry);
    } else {
      Index2++;
      ConsoleMenu->MenuNumber++;
      InsertTailList (&ConsoleMenu->Head, &NewMenuEntry->Link);
    }
  }

  return EFI_SUCCESS;
}

/**
  Build up ConsoleOutMenu, ConsoleInpMenu and ConsoleErrMenu

  @retval EFI_SUCCESS    The function always complete successfully.

**/
EFI_STATUS
GetAllConsoles (
  VOID
  )
{
  GetConsoleMenu (BM_CONSOLE_IN_CONTEXT_SELECT);
  GetConsoleMenu (BM_CONSOLE_OUT_CONTEXT_SELECT);
  GetConsoleMenu (BM_CONSOLE_ERR_CONTEXT_SELECT);
  return EFI_SUCCESS;
}

/**
  Free ConsoleOutMenu, ConsoleInpMenu and ConsoleErrMenu

  @retval EFI_SUCCESS    The function always complete successfully.
**/
EFI_STATUS
FreeAllConsoles (
  VOID
  )
{
  BOpt_FreeMenu (&ConsoleOutMenu);
  BOpt_FreeMenu (&ConsoleInpMenu);
  BOpt_FreeMenu (&ConsoleErrMenu);
  BOpt_FreeMenu (&TerminalMenu);
  return EFI_SUCCESS;
}

/**
  Test whether DevicePath is a valid Terminal


  @param DevicePath      DevicePath to be checked
  @param Termi           If DevicePath is valid Terminal, terminal type is returned.
  @param Com             If DevicePath is valid Terminal, Com Port type is returned.

  @retval  TRUE         If DevicePath point to a Terminal.
  @retval  FALSE        If DevicePath does not point to a Terminal.

**/
BOOLEAN
IsTerminalDevicePath (
  IN  EFI_DEVICE_PATH_PROTOCOL *DevicePath,
  OUT TYPE_OF_TERMINAL         *Termi,
  OUT UINTN                    *Com
  )
{
  BOOLEAN                   IsTerminal;
  EFI_DEVICE_PATH_PROTOCOL  *Node;
  VENDOR_DEVICE_PATH        *Vendor;
  UART_DEVICE_PATH          *Uart;
  ACPI_HID_DEVICE_PATH      *Acpi;

  IsTerminal = FALSE;

  Uart   = NULL;
  Vendor = NULL;
  Acpi   = NULL;
  for (Node = DevicePath; !IsDevicePathEnd (Node); Node = NextDevicePathNode (Node)) {
    //
    // Vendor points to the node before the End node
    //
    Vendor = (VENDOR_DEVICE_PATH *) Node;

    if ((DevicePathType (Node) == MESSAGING_DEVICE_PATH) && (DevicePathSubType (Node) == MSG_UART_DP)) {
      Uart = (UART_DEVICE_PATH *) Node;
    }

    if (Uart == NULL) {
      //
      // Acpi points to the node before the UART node
      //
      Acpi = (ACPI_HID_DEVICE_PATH *) Node;
    }
  }

  if (Vendor == NULL ||
      DevicePathType (Vendor) != MESSAGING_DEVICE_PATH ||
      DevicePathSubType (Vendor) != MSG_VENDOR_DP ||
      Uart == NULL) {
    return FALSE;
  }

  //
  // There are four kinds of Terminal types
  // check to see whether this devicepath
  // is one of that type
  //
  if (CompareGuid (&Vendor->Guid, &TerminalTypeGuid[0])) {
    *Termi      = TerminalTypePcAnsi;
    IsTerminal  = TRUE;
  } else {
    if (CompareGuid (&Vendor->Guid, &TerminalTypeGuid[1])) {
      *Termi      = TerminalTypeVt100;
      IsTerminal  = TRUE;
    } else {
      if (CompareGuid (&Vendor->Guid, &TerminalTypeGuid[2])) {
        *Termi      = TerminalTypeVt100Plus;
        IsTerminal  = TRUE;
      } else {
        if (CompareGuid (&Vendor->Guid, &TerminalTypeGuid[3])) {
          *Termi      = TerminalTypeVtUtf8;
          IsTerminal  = TRUE;
        } else {
          if (CompareGuid (&Vendor->Guid, &TerminalTypeGuid[4])) {
            *Termi      = TerminalTypeTtyTerm;
            IsTerminal  = TRUE;
          } else {
            IsTerminal = FALSE;
          }
        }
      }
    }
  }

  if (!IsTerminal) {
    return FALSE;
  }

  if ((Acpi != NULL) && IsIsaSerialNode (Acpi)) {
    CopyMem (Com, &Acpi->UID, sizeof (UINT32));
  } else {
    return FALSE;
  }

  return TRUE;
}

/**
  Get mode number according to column and row

  @param CallbackData    The BMM context data.
**/
VOID
GetConsoleOutMode (
  IN  BMM_CALLBACK_DATA    *CallbackData
  )
{
  UINTN                         Col;
  UINTN                         Row;
  UINTN                         CurrentCol;
  UINTN                         CurrentRow;
  UINTN                         Mode;
  UINTN                         MaxMode;
  EFI_STATUS                    Status;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *ConOut;

  ConOut   = gST->ConOut;
  MaxMode  = (UINTN) (ConOut->Mode->MaxMode);

  CurrentCol = PcdGet32 (PcdSetupConOutColumn);
  CurrentRow = PcdGet32 (PcdSetupConOutRow);
  for (Mode = 0; Mode < MaxMode; Mode++) {
    Status = ConOut->QueryMode (ConOut, Mode, &Col, &Row);
    if (!EFI_ERROR(Status)) {
      if (CurrentCol == Col && CurrentRow == Row) {
        CallbackData->BmmFakeNvData.ConsoleOutMode = (UINT16) Mode;
        break;
      }
    }
  }
}

/**

  Initialize console input device check box to ConsoleInCheck[MAX_MENU_NUMBER]
  in BMM_FAKE_NV_DATA structure.

  @param CallbackData    The BMM context data.

**/
VOID
GetConsoleInCheck (
  IN  BMM_CALLBACK_DATA    *CallbackData
  )
{
  UINT16              Index;
  BM_MENU_ENTRY       *NewMenuEntry;
  UINT8               *ConInCheck;
  BM_CONSOLE_CONTEXT  *NewConsoleContext;
  BM_TERMINAL_CONTEXT *NewTerminalContext;

  ASSERT (CallbackData != NULL);

  ConInCheck = &CallbackData->BmmFakeNvData.ConsoleInCheck[0];
  for (Index = 0; ((Index < ConsoleInpMenu.MenuNumber) && \
       (Index < MAX_MENU_NUMBER)) ; Index++) {
    NewMenuEntry      = BOpt_GetMenuEntry (&ConsoleInpMenu, Index);
    NewConsoleContext = (BM_CONSOLE_CONTEXT *) NewMenuEntry->VariableContext;
    ConInCheck[Index] = NewConsoleContext->IsActive;
  }

  for (Index = 0; Index < TerminalMenu.MenuNumber; Index++) {
    NewMenuEntry                = BOpt_GetMenuEntry (&TerminalMenu, Index);
    NewTerminalContext          = (BM_TERMINAL_CONTEXT *) NewMenuEntry->VariableContext;
    ASSERT (Index + ConsoleInpMenu.MenuNumber < MAX_MENU_NUMBER);
    ConInCheck[Index + ConsoleInpMenu.MenuNumber] = NewTerminalContext->IsConIn;
  }
}

/**

  Initialize console output device check box to ConsoleOutCheck[MAX_MENU_NUMBER]
  in BMM_FAKE_NV_DATA structure.

  @param CallbackData    The BMM context data.

**/
VOID
GetConsoleOutCheck (
  IN  BMM_CALLBACK_DATA    *CallbackData
  )
{
  UINT16              Index;
  BM_MENU_ENTRY       *NewMenuEntry;
  UINT8               *ConOutCheck;
  BM_CONSOLE_CONTEXT  *NewConsoleContext;
  BM_TERMINAL_CONTEXT *NewTerminalContext;

  ASSERT (CallbackData != NULL);
  ConOutCheck = &CallbackData->BmmFakeNvData.ConsoleOutCheck[0];
  for (Index = 0; ((Index < ConsoleOutMenu.MenuNumber) && \
       (Index < MAX_MENU_NUMBER)) ; Index++) {
    NewMenuEntry      = BOpt_GetMenuEntry (&ConsoleOutMenu, Index);
    NewConsoleContext = (BM_CONSOLE_CONTEXT *) NewMenuEntry->VariableContext;
    ConOutCheck[Index] = NewConsoleContext->IsActive;
  }

  for (Index = 0; Index < TerminalMenu.MenuNumber; Index++) {
    NewMenuEntry                = BOpt_GetMenuEntry (&TerminalMenu, Index);
    NewTerminalContext          = (BM_TERMINAL_CONTEXT *) NewMenuEntry->VariableContext;
    ASSERT (Index + ConsoleOutMenu.MenuNumber < MAX_MENU_NUMBER);
    ConOutCheck[Index + ConsoleOutMenu.MenuNumber] = NewTerminalContext->IsConOut;
  }
}

/**

  Initialize standard error output device check box to ConsoleErrCheck[MAX_MENU_NUMBER]
  in BMM_FAKE_NV_DATA structure.

  @param CallbackData    The BMM context data.

**/
VOID
GetConsoleErrCheck (
  IN  BMM_CALLBACK_DATA    *CallbackData
  )
{
  UINT16              Index;
  BM_MENU_ENTRY       *NewMenuEntry;
  UINT8               *ConErrCheck;
  BM_CONSOLE_CONTEXT  *NewConsoleContext;
  BM_TERMINAL_CONTEXT *NewTerminalContext;

  ASSERT (CallbackData != NULL);
  ConErrCheck = &CallbackData->BmmFakeNvData.ConsoleErrCheck[0];
  for (Index = 0; ((Index < ConsoleErrMenu.MenuNumber) && \
       (Index < MAX_MENU_NUMBER)) ; Index++) {
    NewMenuEntry      = BOpt_GetMenuEntry (&ConsoleErrMenu, Index);
    NewConsoleContext = (BM_CONSOLE_CONTEXT *) NewMenuEntry->VariableContext;
    ConErrCheck[Index] = NewConsoleContext->IsActive;
  }

  for (Index = 0; Index < TerminalMenu.MenuNumber; Index++) {
    NewMenuEntry                = BOpt_GetMenuEntry (&TerminalMenu, Index);
    NewTerminalContext          = (BM_TERMINAL_CONTEXT *) NewMenuEntry->VariableContext;
    ASSERT (Index + ConsoleErrMenu.MenuNumber < MAX_MENU_NUMBER);
    ConErrCheck[Index + ConsoleErrMenu.MenuNumber] = NewTerminalContext->IsStdErr;
  }
}

/**

  Initialize terminal attributes (baudrate, data rate, stop bits, parity and terminal type)
  to BMM_FAKE_NV_DATA structure.

  @param CallbackData    The BMM context data.

**/
VOID
GetTerminalAttribute (
  IN  BMM_CALLBACK_DATA    *CallbackData
  )
{
  BMM_FAKE_NV_DATA     *CurrentFakeNVMap;
  BM_MENU_ENTRY        *NewMenuEntry;
  BM_TERMINAL_CONTEXT  *NewTerminalContext;
  UINT16               TerminalIndex;
  UINT8                AttributeIndex;

  ASSERT (CallbackData != NULL);

  CurrentFakeNVMap = &CallbackData->BmmFakeNvData;
  for (TerminalIndex = 0; ((TerminalIndex < TerminalMenu.MenuNumber) && \
       (TerminalIndex < MAX_MENU_NUMBER)); TerminalIndex++) {
    NewMenuEntry        = BOpt_GetMenuEntry (&TerminalMenu, TerminalIndex);
    NewTerminalContext  = (BM_TERMINAL_CONTEXT *) NewMenuEntry->VariableContext;
    for (AttributeIndex = 0; AttributeIndex < sizeof (BaudRateList) / sizeof (BaudRateList [0]); AttributeIndex++) {
      if (NewTerminalContext->BaudRate == (UINT64) (BaudRateList[AttributeIndex].Value)) {
        NewTerminalContext->BaudRateIndex = AttributeIndex;
        break;
      }
    }
    for (AttributeIndex = 0; AttributeIndex < ARRAY_SIZE (DataBitsList); AttributeIndex++) {
      if (NewTerminalContext->DataBits == (UINT64) (DataBitsList[AttributeIndex].Value)) {
        NewTerminalContext->DataBitsIndex = AttributeIndex;
        break;
      }
    }

    for (AttributeIndex = 0; AttributeIndex < ARRAY_SIZE (ParityList); AttributeIndex++) {
      if (NewTerminalContext->Parity == (UINT64) (ParityList[AttributeIndex].Value)) {
        NewTerminalContext->ParityIndex = AttributeIndex;
        break;
      }
    }

    for (AttributeIndex = 0; AttributeIndex < ARRAY_SIZE (StopBitsList); AttributeIndex++) {
      if (NewTerminalContext->StopBits == (UINT64) (StopBitsList[AttributeIndex].Value)) {
        NewTerminalContext->StopBitsIndex = AttributeIndex;
        break;
      }
    }
    CurrentFakeNVMap->COMBaudRate[TerminalIndex]     = NewTerminalContext->BaudRateIndex;
    CurrentFakeNVMap->COMDataRate[TerminalIndex]     = NewTerminalContext->DataBitsIndex;
    CurrentFakeNVMap->COMStopBits[TerminalIndex]     = NewTerminalContext->StopBitsIndex;
    CurrentFakeNVMap->COMParity[TerminalIndex]       = NewTerminalContext->ParityIndex;
    CurrentFakeNVMap->COMTerminalType[TerminalIndex] = NewTerminalContext->TerminalType;
    CurrentFakeNVMap->COMFlowControl[TerminalIndex]  = NewTerminalContext->FlowControl;
  }
}

