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

Copyright (c) 2006 - 2017, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/


#include "Terminal.h"

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


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


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

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, and TtyTerm 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));

  //
  // 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 (!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;
}

