/** @file
  Produces Simple Text Input Protocol, Simple Text Input Extended Protocol and
  Simple Text Output Protocol upon Serial IO Protocol.

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

**/


#include "Terminal.h"

//
// Globals
//
EFI_DRIVER_BINDING_PROTOCOL gTerminalDriverBinding = {
  TerminalDriverBindingSupported,
  TerminalDriverBindingStart,
  TerminalDriverBindingStop,
  0xa,
  NULL,
  NULL
};


EFI_GUID  *mTerminalType[] = {
  &gEfiPcAnsiGuid,
  &gEfiVT100Guid,
  &gEfiVT100PlusGuid,
  &gEfiVTUTF8Guid,
  &gEfiTtyTermGuid,
  &gEdkiiLinuxTermGuid,
  &gEdkiiXtermR6Guid,
  &gEdkiiVT400Guid,
  &gEdkiiSCOTermGuid
};


CHAR16 *mSerialConsoleNames[] = {
  L"PC-ANSI Serial Console",
  L"VT-100 Serial Console",
  L"VT-100+ Serial Console",
  L"VT-UTF8 Serial Console",
  L"Tty Terminal Serial Console",
  L"Linux Terminal Serial Console",
  L"Xterm R6 Serial Console",
  L"VT-400 Serial Console",
  L"SCO Terminal Serial Console"
};

TERMINAL_DEV  mTerminalDevTemplate = {
  TERMINAL_DEV_SIGNATURE,
  NULL,
  0,
  NULL,
  NULL,
  {   // SimpleTextInput
    TerminalConInReset,
    TerminalConInReadKeyStroke,
    NULL
  },
  {   // SimpleTextOutput
    TerminalConOutReset,
    TerminalConOutOutputString,
    TerminalConOutTestString,
    TerminalConOutQueryMode,
    TerminalConOutSetMode,
    TerminalConOutSetAttribute,
    TerminalConOutClearScreen,
    TerminalConOutSetCursorPosition,
    TerminalConOutEnableCursor,
    NULL
  },
  {   // SimpleTextOutputMode
    1,                                           // MaxMode
    0,                                           // Mode
    EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK),    // Attribute
    0,                                           // CursorColumn
    0,                                           // CursorRow
    TRUE                                         // CursorVisible
  },
  NULL, // TerminalConsoleModeData
  0,  // SerialInTimeOut

  NULL, // RawFifo
  NULL, // UnicodeFiFo
  NULL, // EfiKeyFiFo
  NULL, // EfiKeyFiFoForNotify

  NULL, // ControllerNameTable
  NULL, // TimerEvent
  NULL, // TwoSecondTimeOut
  INPUT_STATE_DEFAULT,
  RESET_STATE_DEFAULT,
  {
      0,
      0,
      0
  },
  0,
  FALSE,
  {   // SimpleTextInputEx
    TerminalConInResetEx,
    TerminalConInReadKeyStrokeEx,
    NULL,
    TerminalConInSetState,
    TerminalConInRegisterKeyNotify,
    TerminalConInUnregisterKeyNotify,
  },
  {   // NotifyList
    NULL,
    NULL,
  },
  NULL // KeyNotifyProcessEvent
};

TERMINAL_CONSOLE_MODE_DATA mTerminalConsoleModeData[] = {
  {80,  25},
  {80,  50},
  {100, 31},
  //
  // New modes can be added here.
  //
};

/**
  Convert the GUID representation of terminal type to enum type.

  @param Guid  The GUID representation of terminal type.

  @return  The terminal type in enum type.
**/
TERMINAL_TYPE
TerminalTypeFromGuid (
  IN EFI_GUID                     *Guid
)
{
  TERMINAL_TYPE                   Type;

  for (Type = 0; Type < ARRAY_SIZE (mTerminalType); Type++) {
    if (CompareGuid (Guid, mTerminalType[Type])) {
      break;
    }
  }
  return Type;
}

/**
  Test to see if this driver supports Controller.

  @param  This                Protocol instance pointer.
  @param  Controller          Handle of device to test
  @param  RemainingDevicePath Optional parameter use to pick a specific child
                              device to start.

  @retval EFI_SUCCESS         This driver supports this device.
  @retval EFI_ALREADY_STARTED This driver is already running on this device.
  @retval other               This driver does not support this device.

**/
EFI_STATUS
EFIAPI
TerminalDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
{
  EFI_STATUS                Status;
  EFI_DEVICE_PATH_PROTOCOL  *ParentDevicePath;
  EFI_SERIAL_IO_PROTOCOL    *SerialIo;
  VENDOR_DEVICE_PATH        *Node;

  //
  // If remaining device path is not NULL, then make sure it is a
  // device path that describes a terminal communications protocol.
  //
  if (RemainingDevicePath != NULL) {
    //
    // Check if RemainingDevicePath is the End of Device Path Node,
    // if yes, go on checking other conditions
    //
    if (!IsDevicePathEnd (RemainingDevicePath)) {
      //
      // If RemainingDevicePath isn't the End of Device Path Node,
      // check its validation
      //
      Node = (VENDOR_DEVICE_PATH *) RemainingDevicePath;

      if (Node->Header.Type != MESSAGING_DEVICE_PATH ||
          Node->Header.SubType != MSG_VENDOR_DP ||
          DevicePathNodeLength(&Node->Header) != sizeof(VENDOR_DEVICE_PATH)) {

        return EFI_UNSUPPORTED;

      }
      //
      // only supports PC ANSI, VT100, VT100+, VT-UTF8, TtyTerm
      // Linux, XtermR6, VT400 and SCO terminal types
      //
      if (TerminalTypeFromGuid (&Node->Guid) == ARRAY_SIZE (mTerminalType)) {
        return EFI_UNSUPPORTED;
      }
    }
  }
  //
  // Open the IO Abstraction(s) needed to perform the supported test
  // The Controller must support the Serial I/O Protocol.
  // This driver is a bus driver with at most 1 child device, so it is
  // ok for it to be already started.
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiSerialIoProtocolGuid,
                  (VOID **) &SerialIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (Status == EFI_ALREADY_STARTED) {
    return EFI_SUCCESS;
  }

  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Close the I/O Abstraction(s) used to perform the supported test
  //
  gBS->CloseProtocol (
        Controller,
        &gEfiSerialIoProtocolGuid,
        This->DriverBindingHandle,
        Controller
        );

  //
  // Open the EFI Device Path protocol needed to perform the supported test
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &ParentDevicePath,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (Status == EFI_ALREADY_STARTED) {
    return EFI_SUCCESS;
  }

  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Close protocol, don't use device path protocol in the Support() function
  //
  gBS->CloseProtocol (
        Controller,
        &gEfiDevicePathProtocolGuid,
        This->DriverBindingHandle,
        Controller
        );

  return Status;
}


/**
  Free notify functions list.

  @param  ListHead               The list head

  @retval EFI_SUCCESS            Free the notify list successfully.
  @retval EFI_INVALID_PARAMETER  ListHead is NULL.

**/
EFI_STATUS
TerminalFreeNotifyList (
  IN OUT LIST_ENTRY           *ListHead
  )
{
  TERMINAL_CONSOLE_IN_EX_NOTIFY *NotifyNode;

  if (ListHead == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  while (!IsListEmpty (ListHead)) {
    NotifyNode = CR (
                   ListHead->ForwardLink,
                   TERMINAL_CONSOLE_IN_EX_NOTIFY,
                   NotifyEntry,
                   TERMINAL_CONSOLE_IN_EX_NOTIFY_SIGNATURE
                   );
    RemoveEntryList (ListHead->ForwardLink);
    FreePool (NotifyNode);
  }

  return EFI_SUCCESS;
}

/**
  Initialize all the text modes which the terminal console supports.

  It returns information for available text modes that the terminal can support.

  @param[out] TextModeCount      The total number of text modes that terminal console supports.

  @return   The buffer to the text modes column and row information.
            Caller is responsible to free it when it's non-NULL.

**/
TERMINAL_CONSOLE_MODE_DATA *
InitializeTerminalConsoleTextMode (
  OUT INT32                         *TextModeCount
)
{
  TERMINAL_CONSOLE_MODE_DATA  *TextModeData;

  ASSERT (TextModeCount != NULL);

  TextModeData = AllocateCopyPool (sizeof (mTerminalConsoleModeData), mTerminalConsoleModeData);
  if (TextModeData == NULL) {
    return NULL;
  }
  *TextModeCount = ARRAY_SIZE (mTerminalConsoleModeData);

  DEBUG_CODE (
    INT32 Index;
    for (Index = 0; Index < *TextModeCount; Index++) {
      DEBUG ((DEBUG_INFO, "Terminal - Mode %d, Column = %d, Row = %d\n",
              Index, TextModeData[Index].Columns, TextModeData[Index].Rows));
    }
  );
  return TextModeData;
}

/**
  Stop the terminal state machine.

  @param TerminalDevice    The terminal device.
**/
VOID
StopTerminalStateMachine (
  TERMINAL_DEV             *TerminalDevice
  )
{
  EFI_TPL                  OriginalTpl;

  OriginalTpl = gBS->RaiseTPL (TPL_NOTIFY);

  gBS->CloseEvent (TerminalDevice->TimerEvent);
  gBS->CloseEvent (TerminalDevice->TwoSecondTimeOut);

  gBS->RestoreTPL (OriginalTpl);
}

/**
  Start the terminal state machine.

  @param TerminalDevice    The terminal device.
**/
VOID
StartTerminalStateMachine (
  TERMINAL_DEV             *TerminalDevice
  )
{
  EFI_STATUS               Status;
  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  TerminalConInTimerHandler,
                  TerminalDevice,
                  &TerminalDevice->TimerEvent
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->SetTimer (
                  TerminalDevice->TimerEvent,
                  TimerPeriodic,
                  KEYBOARD_TIMER_INTERVAL
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->CreateEvent (
                  EVT_TIMER,
                  TPL_CALLBACK,
                  NULL,
                  NULL,
                  &TerminalDevice->TwoSecondTimeOut
                  );
  ASSERT_EFI_ERROR (Status);
}

/**
  Initialize the controller name table.

  @param TerminalType        The terminal type.
  @param ControllerNameTable The controller name table.

  @retval EFI_SUCCESS  The controller name table is initialized successfully.
  @retval others       Return status of AddUnicodeString2 ().
**/
EFI_STATUS
InitializeControllerNameTable (
  TERMINAL_TYPE             TerminalType,
  EFI_UNICODE_STRING_TABLE  **ControllerNameTable
)
{
  EFI_STATUS                Status;
  EFI_UNICODE_STRING_TABLE  *Table;

  ASSERT (TerminalType < ARRAY_SIZE (mTerminalType));
  Table = NULL;
  Status = AddUnicodeString2 (
             "eng",
             gTerminalComponentName.SupportedLanguages,
             &Table,
             mSerialConsoleNames[TerminalType],
             TRUE
             );
  if (!EFI_ERROR (Status)) {
    Status = AddUnicodeString2 (
               "en",
               gTerminalComponentName2.SupportedLanguages,
               &Table,
               mSerialConsoleNames[TerminalType],
               FALSE
               );
    if (EFI_ERROR (Status)) {
      FreeUnicodeStringTable (Table);
    }
  }
  if (!EFI_ERROR (Status)) {
    *ControllerNameTable = Table;
  }
  return Status;
}

/**
  Start this driver on Controller by opening a Serial IO protocol,
  reading Device Path, and creating a child handle with a Simple Text In,
  Simple Text In Ex and Simple Text Out protocol, and device path protocol.
  And store Console Device Environment Variables.

  @param  This                 Protocol instance pointer.
  @param  Controller           Handle of device to bind driver to
  @param  RemainingDevicePath  Optional parameter use to pick a specific child
                               device to start.

  @retval EFI_SUCCESS          This driver is added to Controller.
  @retval EFI_ALREADY_STARTED  This driver is already running on Controller.
  @retval other                This driver does not support this device.

**/
EFI_STATUS
EFIAPI
TerminalDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL    *This,
  IN EFI_HANDLE                     Controller,
  IN EFI_DEVICE_PATH_PROTOCOL       *RemainingDevicePath
  )
{
  EFI_STATUS                          Status;
  EFI_SERIAL_IO_PROTOCOL              *SerialIo;
  EFI_DEVICE_PATH_PROTOCOL            *ParentDevicePath;
  EFI_DEVICE_PATH_PROTOCOL            *Vendor;
  EFI_HANDLE                          SerialIoHandle;
  EFI_SERIAL_IO_MODE                  *Mode;
  UINTN                               SerialInTimeOut;
  TERMINAL_DEV                        *TerminalDevice;
  UINT8                               TerminalType;
  EFI_OPEN_PROTOCOL_INFORMATION_ENTRY *OpenInfoBuffer;
  UINTN                               EntryCount;
  UINTN                               Index;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL     *SimpleTextOutput;
  EFI_SIMPLE_TEXT_INPUT_PROTOCOL      *SimpleTextInput;
  EFI_UNICODE_STRING_TABLE            *ControllerNameTable;

  //
  // Get the Device Path Protocol to build the device path of the child device
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &ParentDevicePath,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  ASSERT ((Status == EFI_SUCCESS) || (Status == EFI_ALREADY_STARTED));
  if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
    return Status;
  }

  //
  // Open the Serial I/O Protocol BY_DRIVER.  It might already be started.
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiSerialIoProtocolGuid,
                  (VOID **) &SerialIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  ASSERT ((Status == EFI_SUCCESS) || (Status == EFI_ALREADY_STARTED));
  if (EFI_ERROR (Status) && (Status != EFI_ALREADY_STARTED)) {
    return Status;
  }

  if (!IsHotPlugDevice (ParentDevicePath)) {
    //
    // if the serial device is a hot plug device, do not update the
    // ConInDev, ConOutDev, and StdErrDev variables.
    //
    TerminalUpdateConsoleDevVariable (EFI_CON_IN_DEV_VARIABLE_NAME, ParentDevicePath);
    TerminalUpdateConsoleDevVariable (EFI_CON_OUT_DEV_VARIABLE_NAME, ParentDevicePath);
    TerminalUpdateConsoleDevVariable (EFI_ERR_OUT_DEV_VARIABLE_NAME, ParentDevicePath);
  }

  //
  // Do not create any child for END remaining device path.
  //
  if ((RemainingDevicePath != NULL) && IsDevicePathEnd (RemainingDevicePath)) {
    return EFI_SUCCESS;
  }

  if (Status == EFI_ALREADY_STARTED) {

    if (RemainingDevicePath == NULL) {
      //
      // If RemainingDevicePath is NULL or is the End of Device Path Node
      //
      return EFI_SUCCESS;
    }

    //
    // This driver can only produce one child per serial port.
    // Change its terminal type as remaining device path requests.
    //
    Status = gBS->OpenProtocolInformation (
                    Controller,
                    &gEfiSerialIoProtocolGuid,
                    &OpenInfoBuffer,
                    &EntryCount
                    );
    if (!EFI_ERROR (Status)) {
      Status = EFI_NOT_FOUND;
      for (Index = 0; Index < EntryCount; Index++) {
        if ((OpenInfoBuffer[Index].Attributes & EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) != 0) {
          Status = gBS->OpenProtocol (
                          OpenInfoBuffer[Index].ControllerHandle,
                          &gEfiSimpleTextInProtocolGuid,
                          (VOID **) &SimpleTextInput,
                          This->DriverBindingHandle,
                          Controller,
                          EFI_OPEN_PROTOCOL_GET_PROTOCOL
                          );
          if (!EFI_ERROR (Status)) {
            TerminalDevice = TERMINAL_CON_IN_DEV_FROM_THIS (SimpleTextInput);
            TerminalType = TerminalTypeFromGuid (&((VENDOR_DEVICE_PATH *) RemainingDevicePath)->Guid);
            ASSERT (TerminalType < ARRAY_SIZE (mTerminalType));
            if (TerminalDevice->TerminalType != TerminalType) {
              Status = InitializeControllerNameTable (TerminalType, &ControllerNameTable);
              if (!EFI_ERROR (Status)) {
                StopTerminalStateMachine (TerminalDevice);
                //
                // Update the device path
                //
                Vendor = TerminalDevice->DevicePath;
                Status = gBS->LocateDevicePath (&gEfiSerialIoProtocolGuid, &Vendor, &SerialIoHandle);
                ASSERT_EFI_ERROR (Status);
                CopyGuid (&((VENDOR_DEVICE_PATH *) Vendor)->Guid, mTerminalType[TerminalType]);
                Status = gBS->ReinstallProtocolInterface (
                                TerminalDevice->Handle,
                                &gEfiDevicePathProtocolGuid,
                                TerminalDevice->DevicePath,
                                TerminalDevice->DevicePath
                                );
                if (!EFI_ERROR (Status)) {
                  TerminalDevice->TerminalType = TerminalType;
                  StartTerminalStateMachine (TerminalDevice);
                  FreeUnicodeStringTable (TerminalDevice->ControllerNameTable);
                  TerminalDevice->ControllerNameTable = ControllerNameTable;
                } else {
                  //
                  // Restore the device path on failure
                  //
                  CopyGuid (&((VENDOR_DEVICE_PATH *) Vendor)->Guid, mTerminalType[TerminalDevice->TerminalType]);
                  FreeUnicodeStringTable (ControllerNameTable);
                }
              }
            }
          }
          break;
        }
      }
      FreePool (OpenInfoBuffer);
    }
    return Status;
  }

  //
  // Initialize the Terminal Dev
  //
  TerminalDevice = AllocateCopyPool (sizeof (TERMINAL_DEV), &mTerminalDevTemplate);
  if (TerminalDevice == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto CloseProtocols;
  }

  if (RemainingDevicePath == NULL) {
    //
    // If RemainingDevicePath is NULL, use default terminal type
    //
    TerminalDevice->TerminalType = PcdGet8 (PcdDefaultTerminalType);
  } else {
    //
    // End of Device Path Node is handled in above.
    //
    ASSERT (!IsDevicePathEnd (RemainingDevicePath));
    //
    // If RemainingDevicePath isn't the End of Device Path Node,
    // Use the RemainingDevicePath to determine the terminal type
    //
    TerminalDevice->TerminalType = TerminalTypeFromGuid (&((VENDOR_DEVICE_PATH *) RemainingDevicePath)->Guid);
  }
  ASSERT (TerminalDevice->TerminalType < ARRAY_SIZE (mTerminalType));
  TerminalDevice->SerialIo = SerialIo;

  //
  // Build the component name for the child device
  //
  Status = InitializeControllerNameTable (TerminalDevice->TerminalType, &TerminalDevice->ControllerNameTable);
  if (EFI_ERROR (Status)) {
    goto FreeResources;
  }

  //
  // Build the device path for the child device
  //
  Status = SetTerminalDevicePath (TerminalDevice->TerminalType, ParentDevicePath, &TerminalDevice->DevicePath);
  if (EFI_ERROR (Status)) {
    goto FreeResources;
  }

  InitializeListHead (&TerminalDevice->NotifyList);
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_WAIT,
                  TPL_NOTIFY,
                  TerminalConInWaitForKeyEx,
                  TerminalDevice,
                  &TerminalDevice->SimpleInputEx.WaitForKeyEx
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_WAIT,
                  TPL_NOTIFY,
                  TerminalConInWaitForKey,
                  TerminalDevice,
                  &TerminalDevice->SimpleInput.WaitForKey
                  );
  ASSERT_EFI_ERROR (Status);
  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  KeyNotifyProcessHandler,
                  TerminalDevice,
                  &TerminalDevice->KeyNotifyProcessEvent
                  );
  ASSERT_EFI_ERROR (Status);

  //
  // Allocates and initializes the FIFO buffer to be zero, used for accommodating
  // the pre-read pending characters.
  //
  TerminalDevice->RawFiFo = AllocateZeroPool (sizeof (RAW_DATA_FIFO));
  if (TerminalDevice->RawFiFo == NULL) {
    goto FreeResources;
  }
  TerminalDevice->UnicodeFiFo = AllocateZeroPool (sizeof (UNICODE_FIFO));
  if (TerminalDevice->UnicodeFiFo == NULL) {
    goto FreeResources;
  }
  TerminalDevice->EfiKeyFiFo = AllocateZeroPool (sizeof (EFI_KEY_FIFO));
  if (TerminalDevice->EfiKeyFiFo == NULL) {
    goto FreeResources;
  }
  TerminalDevice->EfiKeyFiFoForNotify = AllocateZeroPool (sizeof (EFI_KEY_FIFO));
  if (TerminalDevice->EfiKeyFiFoForNotify == NULL) {
    goto FreeResources;
  }

  //
  // Set the timeout value of serial buffer for keystroke response performance issue
  //
  Mode = TerminalDevice->SerialIo->Mode;

  SerialInTimeOut = 0;
  if (Mode->BaudRate != 0) {
    SerialInTimeOut = (1 + Mode->DataBits + Mode->StopBits) * 2 * 1000000 / (UINTN) Mode->BaudRate;
  }

  Status = TerminalDevice->SerialIo->SetAttributes (
                                       TerminalDevice->SerialIo,
                                       Mode->BaudRate,
                                       Mode->ReceiveFifoDepth,
                                       (UINT32) SerialInTimeOut,
                                       (EFI_PARITY_TYPE) (Mode->Parity),
                                       (UINT8) Mode->DataBits,
                                       (EFI_STOP_BITS_TYPE) (Mode->StopBits)
                                       );
  if (EFI_ERROR (Status)) {
    //
    // if set attributes operation fails, invalidate
    // the value of SerialInTimeOut,thus make it
    // inconsistent with the default timeout value
    // of serial buffer. This will invoke the recalculation
    // in the readkeystroke routine.
    //
    TerminalDevice->SerialInTimeOut = 0;
  } else {
    TerminalDevice->SerialInTimeOut = SerialInTimeOut;
  }

  SimpleTextOutput = &TerminalDevice->SimpleTextOutput;
  SimpleTextInput = &TerminalDevice->SimpleInput;

  //
  // Initialize SimpleTextOut instance
  //
  SimpleTextOutput->Mode = &TerminalDevice->SimpleTextOutputMode;
  TerminalDevice->TerminalConsoleModeData = InitializeTerminalConsoleTextMode (
    &SimpleTextOutput->Mode->MaxMode
  );
  if (TerminalDevice->TerminalConsoleModeData == NULL) {
    goto FreeResources;
  }
  //
  // For terminal devices, cursor is always visible
  //
  SimpleTextOutput->Mode->CursorVisible = TRUE;
  Status = SimpleTextOutput->SetAttribute (SimpleTextOutput, EFI_TEXT_ATTR (EFI_LIGHTGRAY, EFI_BLACK));
  if (!EFI_ERROR (Status)) {
    Status = SimpleTextOutput->Reset (SimpleTextOutput, FALSE);
  }
  if (EFI_ERROR (Status)) {
    goto ReportError;
  }

  //
  // Initialize SimpleTextInput instance
  //
  Status = SimpleTextInput->Reset (SimpleTextInput, FALSE);
  if (EFI_ERROR (Status)) {
    goto ReportError;
  }

  Status = gBS->InstallMultipleProtocolInterfaces (
                  &TerminalDevice->Handle,
                  &gEfiSimpleTextInProtocolGuid,      &TerminalDevice->SimpleInput,
                  &gEfiSimpleTextInputExProtocolGuid, &TerminalDevice->SimpleInputEx,
                  &gEfiSimpleTextOutProtocolGuid,     &TerminalDevice->SimpleTextOutput,
                  &gEfiDevicePathProtocolGuid,        TerminalDevice->DevicePath,
                  NULL
                  );
  if (!EFI_ERROR (Status)) {
    Status = gBS->OpenProtocol (
                    Controller,
                    &gEfiSerialIoProtocolGuid,
                    (VOID **) &TerminalDevice->SerialIo,
                    This->DriverBindingHandle,
                    TerminalDevice->Handle,
                    EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                    );
    ASSERT_EFI_ERROR (Status);
    StartTerminalStateMachine (TerminalDevice);
    return Status;
  }

ReportError:
  REPORT_STATUS_CODE_WITH_DEVICE_PATH (
    EFI_ERROR_CODE | EFI_ERROR_MINOR,
    (EFI_PERIPHERAL_REMOTE_CONSOLE | EFI_P_EC_CONTROLLER_ERROR),
    ParentDevicePath
    );

FreeResources:
  ASSERT (TerminalDevice != NULL);

  if (TerminalDevice->SimpleInput.WaitForKey != NULL) {
    gBS->CloseEvent (TerminalDevice->SimpleInput.WaitForKey);
  }
  if (TerminalDevice->SimpleInputEx.WaitForKeyEx != NULL) {
    gBS->CloseEvent (TerminalDevice->SimpleInputEx.WaitForKeyEx);
  }
  if (TerminalDevice->KeyNotifyProcessEvent != NULL) {
    gBS->CloseEvent (TerminalDevice->KeyNotifyProcessEvent);
  }

  if (TerminalDevice->RawFiFo != NULL) {
    FreePool (TerminalDevice->RawFiFo);
  }
  if (TerminalDevice->UnicodeFiFo != NULL) {
    FreePool (TerminalDevice->UnicodeFiFo);
  }
  if (TerminalDevice->EfiKeyFiFo != NULL) {
    FreePool (TerminalDevice->EfiKeyFiFo);
  }
  if (TerminalDevice->EfiKeyFiFoForNotify != NULL) {
    FreePool (TerminalDevice->EfiKeyFiFoForNotify);
  }

  if (TerminalDevice->ControllerNameTable != NULL) {
    FreeUnicodeStringTable (TerminalDevice->ControllerNameTable);
  }

  if (TerminalDevice->DevicePath != NULL) {
    FreePool (TerminalDevice->DevicePath);
  }

  if (TerminalDevice->TerminalConsoleModeData != NULL) {
    FreePool (TerminalDevice->TerminalConsoleModeData);
  }

  FreePool (TerminalDevice);

CloseProtocols:

  //
  // Remove Parent Device Path from
  // the Console Device Environment Variables
  //
  TerminalRemoveConsoleDevVariable (EFI_CON_IN_DEV_VARIABLE_NAME, ParentDevicePath);
  TerminalRemoveConsoleDevVariable (EFI_CON_OUT_DEV_VARIABLE_NAME, ParentDevicePath);
  TerminalRemoveConsoleDevVariable (EFI_ERR_OUT_DEV_VARIABLE_NAME, ParentDevicePath);

  Status = gBS->CloseProtocol (
                  Controller,
                  &gEfiSerialIoProtocolGuid,
                  This->DriverBindingHandle,
                  Controller
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->CloseProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  This->DriverBindingHandle,
                  Controller
                  );
  ASSERT_EFI_ERROR (Status);
  return Status;
}

/**
  Stop this driver on Controller by closing Simple Text In, Simple Text
  In Ex, Simple Text Out protocol, and removing parent device path from
  Console Device Environment Variables.

  @param  This              Protocol instance pointer.
  @param  Controller        Handle of device to stop driver on
  @param  NumberOfChildren  Number of Handles in ChildHandleBuffer. If number of
                            children is zero stop the entire bus driver.
  @param  ChildHandleBuffer List of Child Handles to Stop.

  @retval EFI_SUCCESS       This driver is removed Controller.
  @retval other             This driver could not be removed from this device.

**/
EFI_STATUS
EFIAPI
TerminalDriverBindingStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL   *This,
  IN  EFI_HANDLE                    Controller,
  IN  UINTN                         NumberOfChildren,
  IN  EFI_HANDLE                    *ChildHandleBuffer
  )
{
  EFI_STATUS                       Status;
  UINTN                            Index;
  BOOLEAN                          AllChildrenStopped;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL  *SimpleTextOutput;
  TERMINAL_DEV                     *TerminalDevice;
  EFI_DEVICE_PATH_PROTOCOL         *ParentDevicePath;
  EFI_SERIAL_IO_PROTOCOL           *SerialIo;

  //
  // Complete all outstanding transactions to Controller.
  // Don't allow any new transaction to Controller to be started.
  //
  if (NumberOfChildren == 0) {
    //
    // Close the bus driver
    //
    Status = gBS->OpenProtocol (
                    Controller,
                    &gEfiDevicePathProtocolGuid,
                    (VOID **) &ParentDevicePath,
                    This->DriverBindingHandle,
                    Controller,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    ASSERT_EFI_ERROR (Status);

    //
    // Remove Parent Device Path from
    // the Console Device Environment Variables
    //
    TerminalRemoveConsoleDevVariable (EFI_CON_IN_DEV_VARIABLE_NAME, ParentDevicePath);
    TerminalRemoveConsoleDevVariable (EFI_CON_OUT_DEV_VARIABLE_NAME, ParentDevicePath);
    TerminalRemoveConsoleDevVariable (EFI_ERR_OUT_DEV_VARIABLE_NAME, ParentDevicePath);

    gBS->CloseProtocol (
          Controller,
          &gEfiSerialIoProtocolGuid,
          This->DriverBindingHandle,
          Controller
          );

    gBS->CloseProtocol (
          Controller,
          &gEfiDevicePathProtocolGuid,
          This->DriverBindingHandle,
          Controller
          );

    return EFI_SUCCESS;
  }

  AllChildrenStopped = TRUE;

  for (Index = 0; Index < NumberOfChildren; Index++) {

    Status = gBS->OpenProtocol (
                    ChildHandleBuffer[Index],
                    &gEfiSimpleTextOutProtocolGuid,
                    (VOID **) &SimpleTextOutput,
                    This->DriverBindingHandle,
                    ChildHandleBuffer[Index],
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (!EFI_ERROR (Status)) {

      TerminalDevice = TERMINAL_CON_OUT_DEV_FROM_THIS (SimpleTextOutput);

      gBS->CloseProtocol (
            Controller,
            &gEfiSerialIoProtocolGuid,
            This->DriverBindingHandle,
            ChildHandleBuffer[Index]
            );

      Status = gBS->UninstallMultipleProtocolInterfaces (
                      ChildHandleBuffer[Index],
                      &gEfiSimpleTextInProtocolGuid,
                      &TerminalDevice->SimpleInput,
                      &gEfiSimpleTextInputExProtocolGuid,
                      &TerminalDevice->SimpleInputEx,
                      &gEfiSimpleTextOutProtocolGuid,
                      &TerminalDevice->SimpleTextOutput,
                      &gEfiDevicePathProtocolGuid,
                      TerminalDevice->DevicePath,
                      NULL
                      );
      if (EFI_ERROR (Status)) {
        gBS->OpenProtocol (
              Controller,
              &gEfiSerialIoProtocolGuid,
              (VOID **) &SerialIo,
              This->DriverBindingHandle,
              ChildHandleBuffer[Index],
              EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
              );
      } else {

        FreeUnicodeStringTable (TerminalDevice->ControllerNameTable);
        StopTerminalStateMachine (TerminalDevice);
        gBS->CloseEvent (TerminalDevice->SimpleInput.WaitForKey);
        gBS->CloseEvent (TerminalDevice->SimpleInputEx.WaitForKeyEx);
        gBS->CloseEvent (TerminalDevice->KeyNotifyProcessEvent);
        TerminalFreeNotifyList (&TerminalDevice->NotifyList);
        FreePool (TerminalDevice->DevicePath);
        FreePool (TerminalDevice->TerminalConsoleModeData);
        FreePool (TerminalDevice);
      }
    }

    if (EFI_ERROR (Status)) {
      AllChildrenStopped = FALSE;
    }
  }

  if (!AllChildrenStopped) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

/**
  Compare 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 is contained within Multi.
  @retval FALSE          The Single is not match within Multi.

**/
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;

  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;
}

/**
  Update terminal device path in Console Device Environment Variables.

  @param  VariableName           The Console Device Environment Variable.
  @param  ParentDevicePath       The terminal device path to be updated.

**/
VOID
TerminalUpdateConsoleDevVariable (
  IN CHAR16                    *VariableName,
  IN EFI_DEVICE_PATH_PROTOCOL  *ParentDevicePath
  )
{
  EFI_STATUS                Status;
  UINTN                     NameSize;
  UINTN                     VariableSize;
  TERMINAL_TYPE             TerminalType;
  EFI_DEVICE_PATH_PROTOCOL  *Variable;
  EFI_DEVICE_PATH_PROTOCOL  *NewVariable;
  EFI_DEVICE_PATH_PROTOCOL  *TempDevicePath;
  EDKII_SET_VARIABLE_STATUS *SetVariableStatus;

  //
  // Get global variable and its size according to the name given.
  //
  Status = GetEfiGlobalVariable2 (VariableName, (VOID**)&Variable, NULL);
  if (Status == EFI_NOT_FOUND) {
    Status   = EFI_SUCCESS;
    Variable = NULL;
  }
  if (EFI_ERROR (Status)) {
    return;
  }

  //
  // Append terminal device path onto the variable.
  //
  for (TerminalType = 0; TerminalType < ARRAY_SIZE (mTerminalType); TerminalType++) {
    SetTerminalDevicePath (TerminalType, ParentDevicePath, &TempDevicePath);

    if (TempDevicePath != NULL) {
      if (!MatchDevicePaths (Variable, TempDevicePath)) {
        NewVariable = AppendDevicePathInstance (Variable, TempDevicePath);
        if (NewVariable != NULL) {
          if (Variable != NULL) {
            FreePool (Variable);
          }
          Variable = NewVariable;
        }
      }

      FreePool (TempDevicePath);
    }

  }

  VariableSize = GetDevicePathSize (Variable);

  Status = gRT->SetVariable (
                  VariableName,
                  &gEfiGlobalVariableGuid,
                  EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
                  VariableSize,
                  Variable
                  );

  if (EFI_ERROR (Status)) {
    NameSize = StrSize (VariableName);
    SetVariableStatus = AllocatePool (sizeof (EDKII_SET_VARIABLE_STATUS) + NameSize + VariableSize);
    if (SetVariableStatus != NULL) {
      CopyGuid (&SetVariableStatus->Guid, &gEfiGlobalVariableGuid);
      SetVariableStatus->NameSize   = NameSize;
      SetVariableStatus->DataSize   = VariableSize;
      SetVariableStatus->SetStatus  = Status;
      SetVariableStatus->Attributes = EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS;
      CopyMem (SetVariableStatus + 1,                          VariableName, NameSize);
      CopyMem (((UINT8 *) (SetVariableStatus + 1)) + NameSize, Variable,     VariableSize);

      REPORT_STATUS_CODE_EX (
        EFI_ERROR_CODE,
        PcdGet32 (PcdErrorCodeSetVariable),
        0,
        NULL,
        &gEdkiiStatusCodeDataTypeVariableGuid,
        SetVariableStatus,
        sizeof (EDKII_SET_VARIABLE_STATUS) + NameSize + VariableSize
        );

      FreePool (SetVariableStatus);
    }
  }

  FreePool (Variable);

  return ;
}


/**
  Remove terminal device path from Console Device Environment Variables.

  @param  VariableName           Console Device Environment Variables.
  @param  ParentDevicePath       The terminal device path to be updated.

**/
VOID
TerminalRemoveConsoleDevVariable (
  IN CHAR16                    *VariableName,
  IN EFI_DEVICE_PATH_PROTOCOL  *ParentDevicePath
  )
{
  EFI_STATUS                Status;
  BOOLEAN                   FoundOne;
  BOOLEAN                   Match;
  UINTN                     VariableSize;
  UINTN                     InstanceSize;
  TERMINAL_TYPE             TerminalType;
  EFI_DEVICE_PATH_PROTOCOL  *Instance;
  EFI_DEVICE_PATH_PROTOCOL  *Variable;
  EFI_DEVICE_PATH_PROTOCOL  *OriginalVariable;
  EFI_DEVICE_PATH_PROTOCOL  *NewVariable;
  EFI_DEVICE_PATH_PROTOCOL  *SavedNewVariable;
  EFI_DEVICE_PATH_PROTOCOL  *TempDevicePath;

  Instance  = NULL;

  //
  // Get global variable and its size according to the name given.
  //
  GetEfiGlobalVariable2 (VariableName, (VOID**)&Variable, NULL);
  if (Variable == NULL) {
    return ;
  }

  FoundOne          = FALSE;
  OriginalVariable  = Variable;
  NewVariable       = NULL;

  //
  // Get first device path instance from Variable
  //
  Instance = GetNextDevicePathInstance (&Variable, &InstanceSize);
  if (Instance == NULL) {
    FreePool (OriginalVariable);
    return ;
  }
  //
  // Loop through all the device path instances of Variable
  //
  do {
    //
    // Loop through all the terminal types that this driver supports
    //
    Match = FALSE;
    for (TerminalType = 0; TerminalType < ARRAY_SIZE (mTerminalType); TerminalType++) {

      SetTerminalDevicePath (TerminalType, ParentDevicePath, &TempDevicePath);

      //
      // Compare the generated device path to the current device path instance
      //
      if (TempDevicePath != NULL) {
        if (CompareMem (Instance, TempDevicePath, InstanceSize) == 0) {
          Match     = TRUE;
          FoundOne  = TRUE;
        }

        FreePool (TempDevicePath);
      }
    }
    //
    // If a match was not found, then keep the current device path instance
    //
    if (!Match) {
      SavedNewVariable  = NewVariable;
      NewVariable       = AppendDevicePathInstance (NewVariable, Instance);
      if (SavedNewVariable != NULL) {
        FreePool (SavedNewVariable);
      }
    }
    //
    // Get next device path instance from Variable
    //
    FreePool (Instance);
    Instance = GetNextDevicePathInstance (&Variable, &InstanceSize);
  } while (Instance != NULL);

  FreePool (OriginalVariable);

  if (FoundOne) {
    VariableSize = GetDevicePathSize (NewVariable);

    Status = gRT->SetVariable (
                    VariableName,
                    &gEfiGlobalVariableGuid,
                    EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS,
                    VariableSize,
                    NewVariable
                    );
    //
    // Shrinking variable with existing variable driver implementation shouldn't fail.
    //
    ASSERT_EFI_ERROR (Status);
  }

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

  return ;
}

/**
  Build terminal device path according to terminal type.

  @param  TerminalType           The terminal type is PC ANSI, VT100, VT100+ or VT-UTF8.
  @param  ParentDevicePath       Parent device path.
  @param  TerminalDevicePath     Returned terminal device path, if building successfully.

  @retval EFI_UNSUPPORTED        Terminal does not belong to the supported type.
  @retval EFI_OUT_OF_RESOURCES   Generate terminal device path failed.
  @retval EFI_SUCCESS            Build terminal device path successfully.

**/
EFI_STATUS
SetTerminalDevicePath (
  IN  TERMINAL_TYPE               TerminalType,
  IN  EFI_DEVICE_PATH_PROTOCOL    *ParentDevicePath,
  OUT EFI_DEVICE_PATH_PROTOCOL    **TerminalDevicePath
  )
{
  VENDOR_DEVICE_PATH  Node;

  ASSERT (TerminalType < ARRAY_SIZE (mTerminalType));
  Node.Header.Type    = MESSAGING_DEVICE_PATH;
  Node.Header.SubType = MSG_VENDOR_DP;
  SetDevicePathNodeLength (&Node.Header, sizeof (VENDOR_DEVICE_PATH));
  CopyGuid (&Node.Guid, mTerminalType[TerminalType]);

  //
  // Append the terminal node onto parent device path
  // to generate a complete terminal device path.
  //
  *TerminalDevicePath = AppendDevicePathNode (
                          ParentDevicePath,
                          (EFI_DEVICE_PATH_PROTOCOL *) &Node
                          );
  if (*TerminalDevicePath == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  return EFI_SUCCESS;
}

/**
  The user Entry Point for module Terminal. The user code starts with this function.

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

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

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

  //
  // Install driver model protocol(s).
  //
  Status = EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             SystemTable,
             &gTerminalDriverBinding,
             ImageHandle,
             &gTerminalComponentName,
             &gTerminalComponentName2
             );
  ASSERT_EFI_ERROR (Status);

  return Status;
}

/**
  Check if the device supports hot-plug through its device path.

  This function could be updated to check more types of Hot Plug devices.
  Currently, it checks USB and PCCard device.

  @param  DevicePath            Pointer to device's device path.

  @retval TRUE                  The devcie is a hot-plug device
  @retval FALSE                 The devcie is not a hot-plug device.

**/
BOOLEAN
IsHotPlugDevice (
  IN  EFI_DEVICE_PATH_PROTOCOL    *DevicePath
  )
{
  EFI_DEVICE_PATH_PROTOCOL     *CheckDevicePath;

  CheckDevicePath = DevicePath;
  while (!IsDevicePathEnd (CheckDevicePath)) {
    //
    // Check device whether is hot plug device or not throught Device Path
    //
    if ((DevicePathType (CheckDevicePath) == MESSAGING_DEVICE_PATH) &&
        (DevicePathSubType (CheckDevicePath) == MSG_USB_DP ||
         DevicePathSubType (CheckDevicePath) == MSG_USB_CLASS_DP ||
         DevicePathSubType (CheckDevicePath) == MSG_USB_WWID_DP)) {
      //
      // If Device is USB device
      //
      return TRUE;
    }
    if ((DevicePathType (CheckDevicePath) == HARDWARE_DEVICE_PATH) &&
        (DevicePathSubType (CheckDevicePath) == HW_PCCARD_DP)) {
      //
      // If Device is PCCard
      //
      return TRUE;
    }

    CheckDevicePath = NextDevicePathNode (CheckDevicePath);
  }

  return FALSE;
}