text: /** @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_BEGIN ();
    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
        ));
    }
  
    DEBUG_CODE_END ();
    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;
  }
  