/** @file
  USB Serial Driver that manages USB to Serial and produces Serial IO Protocol.

Copyright (c) 2004 - 2013, Intel Corporation. All rights reserved.
Portions Copyright 2012 Ashley DeSimone
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

//

// Tested with VEND_ID 0x0403, DEVICE_ID 0x6001
//
// Driver starts the device with the following values:
// 115200, No parity, 8 data bits, 1 stop bit, No Flow control
//

#include "FtdiUsbSerialDriver.h"

//
// Table of supported devices. This is the device information that this
// driver was developed with. Add other FTDI devices as needed.
//
USB_DEVICE gUSBDeviceList[] = {
  {VID_FTDI, DID_FTDI_FT232},
  {0,0}
};

//
// USB Serial Driver Global Variables
//
EFI_DRIVER_BINDING_PROTOCOL  gUsbSerialDriverBinding = {
  UsbSerialDriverBindingSupported,
  UsbSerialDriverBindingStart,
  UsbSerialDriverBindingStop,
  0xa,
  NULL,
  NULL
};

//
// Table with the nearest power of 2 for the numbers 0-15
//
UINT8 gRoundedPowersOf2[16] = { 0, 2, 2, 4, 4, 4, 8, 8, 8, 8, 8, 8, 16, 16, 16, 16 };

/**
  Check to see if the device path node is the Flow control node

  @param[in] FlowControl    The device path node to be checked

  @retval    TRUE           It is the flow control node
  @retval    FALSE          It is not the flow control node

**/
BOOLEAN
IsUartFlowControlNode (
  IN UART_FLOW_CONTROL_DEVICE_PATH *FlowControl
  )
{
  return (BOOLEAN) (
           (DevicePathType (FlowControl) == MESSAGING_DEVICE_PATH) &&
           (DevicePathSubType (FlowControl) == MSG_VENDOR_DP) &&
           (CompareGuid (&FlowControl->Guid, &gEfiUartDevicePathGuid))
           );
}

/**
  Checks the device path to see if it contains flow control.

  @param[in] DevicePath    The device path to be checked

  @retval    TRUE          It contains flow control
  @retval    FALSE         It does not contain flow control

**/
BOOLEAN
ContainsFlowControl (
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath
  )
{
  while (!IsDevicePathEnd (DevicePath)) {
    if (IsUartFlowControlNode ((UART_FLOW_CONTROL_DEVICE_PATH *) DevicePath)) {
      return TRUE;
    }
    DevicePath = NextDevicePathNode (DevicePath);
  }
  return FALSE;
}

/**
  Transfer the data between the device and host.

  This function transfers the data between the device and host.
  BOT transfer is composed of three phases: Command, Data, and Status.
  This is the Data phase.

  @param  UsbBot[in]                     The USB BOT device
  @param  DataDir[in]                    The direction of the data
  @param  Data[in, out]                  The buffer to hold data
  @param  TransLen[in, out]              The expected length of the data
  @param  Timeout[in]                    The time to wait the command to complete

  @retval EFI_SUCCESS                    The data is transferred
  @retval EFI_SUCCESS                    No data to transfer
  @retval EFI_NOT_READY                  The device return NAK to the transfer
  @retval Others                         Failed to transfer data

**/
EFI_STATUS
UsbSerialDataTransfer (
  IN USB_SER_DEV             *UsbBot,
  IN EFI_USB_DATA_DIRECTION  DataDir,
  IN OUT VOID                *Data,
  IN OUT UINTN               *TransLen,
  IN UINT32                  Timeout
  )
{
  EFI_USB_ENDPOINT_DESCRIPTOR  *Endpoint;
  EFI_STATUS                   Status;
  UINT32                       Result;

  //
  // If no data to transfer, just return EFI_SUCCESS.
  //
  if ((DataDir == EfiUsbNoData) || (*TransLen == 0)) {
    return EFI_SUCCESS;
  }

  //
  // Select the endpoint then issue the transfer
  //
  if (DataDir == EfiUsbDataIn) {
    Endpoint = &UsbBot->InEndpointDescriptor;
  } else {
    Endpoint = &UsbBot->OutEndpointDescriptor;
  }

  Result = 0;
  Status = UsbBot->UsbIo->UsbBulkTransfer (
                            UsbBot->UsbIo,
                            Endpoint->EndpointAddress,
                            Data,
                            TransLen,
                            Timeout,
                            &Result
                            );
  if (EFI_ERROR (Status)) {
    if (USB_IS_ERROR (Result, EFI_USB_ERR_NAK)) {
      Status = EFI_NOT_READY;
    } else {
      UsbBot->Shutdown = TRUE; // Fixes infinite loop in older EFI
    }
    return Status;
  }
  return Status;
}

/**
  Sets the status values of the Usb Serial Device.

  @param  UsbSerialDevice[in]  Handle to the Usb Serial Device to set the status
                               for
  @param  StatusBuffer[in]     Buffer holding the status values

  @retval EFI_SUCCESS          The status values were read and set correctly

**/
EFI_STATUS
EFIAPI
SetStatusInternal (
  IN USB_SER_DEV  *UsbSerialDevice,
  IN UINT8        *StatusBuffer
  )
{
  UINT8  Msr;

  Msr = (StatusBuffer[0] & MSR_MASK);

  //
  // set the Status values to disabled
  //
  UsbSerialDevice->StatusValues.CtsState = FALSE;
  UsbSerialDevice->StatusValues.DsrState = FALSE;
  UsbSerialDevice->StatusValues.RiState  = FALSE;
  UsbSerialDevice->StatusValues.SdState  = FALSE;

  //
  // Check the values from the status buffer and set the appropriate status
  // values to enabled
  //
  if ((Msr & CTS_MASK) == CTS_MASK) {
    UsbSerialDevice->StatusValues.CtsState = TRUE;
  }
  if ((Msr & DSR_MASK) == DSR_MASK) {
    UsbSerialDevice->StatusValues.DsrState = TRUE;
  }
  if ((Msr & RI_MASK) == RI_MASK) {
    UsbSerialDevice->StatusValues.RiState = TRUE;
  }
  if ((Msr & SD_MASK) == SD_MASK) {
    UsbSerialDevice->StatusValues.SdState = TRUE;
  }
  return EFI_SUCCESS;
}

/**
  Initiates a read operation on the Usb Serial Device.

  @param  UsbSerialDevice[in]        Handle to the USB device to read
  @param  BufferSize[in, out]        On input, the size of the Buffer. On output,
                                     the amount of data returned in Buffer.
                                     Setting this to zero will initiate a read
                                     and store all data returned in the internal
                                     buffer.
  @param  Buffer [out]               The buffer to return the data into.

  @retval EFI_SUCCESS                The data was read.
  @retval EFI_DEVICE_ERROR           The device reported an error.
  @retval EFI_TIMEOUT                The data write was stopped due to a timeout.

**/
EFI_STATUS
EFIAPI
ReadDataFromUsb (
  IN USB_SER_DEV  *UsbSerialDevice,
  IN OUT UINTN    *BufferSize,
  OUT VOID        *Buffer
  )
{
  EFI_STATUS  Status;
  UINTN       ReadBufferSize;
  UINT8       *ReadBuffer;
  UINTN       Index;
  EFI_TPL     Tpl;
  UINT8       StatusBuffer[2]; // buffer to store the status bytes

  ReadBufferSize = 512;
  ReadBuffer     = &(UsbSerialDevice->ReadBuffer[0]);

  if (UsbSerialDevice->Shutdown) {
    return EFI_DEVICE_ERROR;
  }

  Tpl = gBS->RaiseTPL (TPL_NOTIFY);

  Status = UsbSerialDataTransfer (
             UsbSerialDevice,
             EfiUsbDataIn,
             ReadBuffer,
             &ReadBufferSize,
             FTDI_TIMEOUT*2  //Padded because timers won't be exactly aligned
             );
  if (EFI_ERROR (Status)) {
    gBS->RestoreTPL (Tpl);
    if (Status == EFI_TIMEOUT) {
      return EFI_TIMEOUT;
    } else {
      return EFI_DEVICE_ERROR;
    }
  }

  //
  // Store the status bytes in the status buffer
  //
  for (Index = 0; Index < 2; Index++) {//only the first 2 bytes are status bytes
    StatusBuffer[Index] = ReadBuffer[Index];
  }
  //
  // update the statusvalue field of the usbserialdevice
  //
  Status = SetStatusInternal (UsbSerialDevice, StatusBuffer);
  if (Status != EFI_SUCCESS) {
  }

  //
  // Store the read data in the read buffer, start at 2 to ignore status bytes
  //
  for (Index = 2; Index < ReadBufferSize; Index++) {
    if (((UsbSerialDevice->DataBufferTail + 1) % SW_FIFO_DEPTH) == UsbSerialDevice->DataBufferHead) {
      break;
    }
    if (ReadBuffer[Index] == 0x00) {
      //
      // This is null, do not add
      //
    } else {
      UsbSerialDevice->DataBuffer[UsbSerialDevice->DataBufferTail] = ReadBuffer[Index];
      UsbSerialDevice->DataBufferTail = (UsbSerialDevice->DataBufferTail + 1) % SW_FIFO_DEPTH;
    }
  }

  //
  // Read characters out of the buffer to satisfy caller's request.
  //
  for (Index = 0; Index < *BufferSize; Index++) {
    if (UsbSerialDevice->DataBufferHead == UsbSerialDevice->DataBufferTail) {
      break;
    }
    //
    // Still have characters in the buffer to return
    //
    ((UINT8 *)Buffer)[Index]        = UsbSerialDevice->DataBuffer[UsbSerialDevice->DataBufferHead];
    UsbSerialDevice->DataBufferHead = (UsbSerialDevice->DataBufferHead + 1) % SW_FIFO_DEPTH;
  }
  //
  // Return actual number of bytes returned.
  //
  *BufferSize = Index;
  gBS->RestoreTPL (Tpl);
  return EFI_SUCCESS;
}

/**
  Sets the initial status values of the Usb Serial Device by reading the status
  bytes from the device.

  @param  UsbSerialDevice[in]  Handle to the Usb Serial Device that needs its
                               initial status values set

  @retval EFI_SUCCESS          The status bytes were read successfully and the
                               initial status values were set correctly
  @retval EFI_TIMEOUT          The read of the status bytes was stopped due to a
                               timeout
  @retval EFI_DEVICE_ERROR     The device reported an error during the read of
                               the status bytes

**/
EFI_STATUS
EFIAPI
SetInitialStatus (
  IN USB_SER_DEV          *UsbSerialDevice
  )
{
  EFI_STATUS      Status;
  UINTN           BufferSize;
  EFI_TPL         Tpl;
  UINT8           StatusBuffer[2];

  Status          = EFI_UNSUPPORTED;
  BufferSize      = sizeof (StatusBuffer);

  if (UsbSerialDevice->Shutdown) {
    return EFI_DEVICE_ERROR;
  }

  Tpl = gBS->RaiseTPL (TPL_NOTIFY);

  Status = UsbSerialDataTransfer (
             UsbSerialDevice,
             EfiUsbDataIn,
             StatusBuffer,
             &BufferSize,
             40    //Slightly more than 2x the FTDI polling frequency to make sure that data will be returned
             );

  Status = SetStatusInternal (UsbSerialDevice, StatusBuffer);

  gBS->RestoreTPL (Tpl);

  return Status;
}

/**
  UsbSerialDriverCheckInput.
  attempts to read data in from the device periodically, stores any read data
  and updates the control attributes.

  @param  Event[in]
  @param  Context[in]....The current instance of the USB serial device

**/
VOID
EFIAPI
UsbSerialDriverCheckInput (
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
  UINTN        BufferSize;
  USB_SER_DEV  *UsbSerialDevice;

  UsbSerialDevice = (USB_SER_DEV*)Context;

  if (UsbSerialDevice->DataBufferHead == UsbSerialDevice->DataBufferTail) {
    //
    // Data buffer is empty, try to read from device
    //
    BufferSize = 0;
    ReadDataFromUsb (UsbSerialDevice, &BufferSize, NULL);
    if (UsbSerialDevice->DataBufferHead == UsbSerialDevice->DataBufferTail) {
      //
      // Data buffer still has no data, set the EFI_SERIAL_INPUT_BUFFER_EMPTY
      // flag
      //
      UsbSerialDevice->ControlBits |= EFI_SERIAL_INPUT_BUFFER_EMPTY;
    } else {
      //
      // Read has returned some data, clear the EFI_SERIAL_INPUT_BUFFER_EMPTY
      // flag
      //
      UsbSerialDevice->ControlBits &= ~(EFI_SERIAL_INPUT_BUFFER_EMPTY);
    }
  } else {
    //
    // Data buffer has data, no read attempt required
    //
    UsbSerialDevice->ControlBits &= ~(EFI_SERIAL_INPUT_BUFFER_EMPTY);
  }
}

/**
  Encodes the baud rate into the format expected by the Ftdi device.

  @param  BaudRate[in]                The baudrate to be set on the device
  @param  EncodedBaudRate[out]        The baud rate encoded in the format
                                      expected by the Ftdi device

  @return EFI_SUCCESS                 Baudrate encoding was calculated
                                      successfully
  @return EFI_INVALID_PARAMETER       An invalid value of BaudRate was received

**/
EFI_STATUS
EFIAPI
EncodeBaudRateForFtdi (
  IN  UINT64  BaudRate,
  OUT UINT16  *EncodedBaudRate
  )
{
  UINT32 Divisor;
  UINT32 AdjustedFrequency;
  UINT16 Result;

  //
  // Check to make sure we won't get an integer overflow
  //
  if ((BaudRate < 178) || ( BaudRate > ((FTDI_UART_FREQUENCY * 100) / 97))) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Baud Rates of 2000000 and 3000000 are special cases
  //
  if ((BaudRate >= FTDI_SPECIAL_CASE_300_MIN) && (BaudRate <= FTDI_SPECIAL_CASE_300_MAX)) {
    *EncodedBaudRate = 0;
    return EFI_SUCCESS;
  }
  if ((BaudRate >= FTDI_SPECIAL_CASE_200_MIN) && (BaudRate <= FTDI_SPECIAL_CASE_200_MAX)) {
    *EncodedBaudRate = 1;
    return EFI_SUCCESS;
  }

  //
  // Compute divisor
  //
  Divisor = (FTDI_UART_FREQUENCY << 4) / (UINT32)BaudRate;

  //
  // Round the last 4 bits to the nearest power of 2
  //
  Divisor = (Divisor & ~(0xF)) + (gRoundedPowersOf2[Divisor & 0xF]);

  //
  // Check to make sure computed divisor is within 
  // the min and max that FTDI controller will accept
  //
  if (Divisor < FTDI_MIN_DIVISOR) {
    Divisor = FTDI_MIN_DIVISOR;
  } else if (Divisor > FTDI_MAX_DIVISOR) {
    Divisor = FTDI_MAX_DIVISOR;
  }

  //
  // Check to make sure the frequency that the FTDI chip will need to
  // generate to attain the requested Baud Rate is within 3% of the
  // 3MHz clock frequency that the FTDI chip runs at.
  //
  // (3MHz * 1600) / 103 = 46601941
  // (3MHz * 1600) / 97  = 49484536
  //
  AdjustedFrequency = (((UINT32)BaudRate) * Divisor);
  if ((AdjustedFrequency < FTDI_MIN_FREQUENCY) || (AdjustedFrequency > FTDI_MAX_FREQUENCY)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Encode the Divisor into the format FTDI expects
  //
  Result = (UINT16)(Divisor >> 4);
  if ((Divisor & 0x8) != 0) {
    Result |= 0x4000;
  } else if ((Divisor & 0x4) != 0) {
    Result |= 0x8000;
  } else if ((Divisor & 0x2) != 0) {
    Result |= 0xC000;
  }

  *EncodedBaudRate = Result;
  return EFI_SUCCESS;
}

/**
  Uses USB I/O to check whether the device is a USB Serial device.

  @param  UsbIo[in]    Pointer to a USB I/O protocol instance.

  @retval TRUE         Device is a USB Serial device.
  @retval FALSE        Device is a not USB Serial device.

**/
BOOLEAN
IsUsbSerial (
  IN  EFI_USB_IO_PROTOCOL  *UsbIo
  )
{
  EFI_STATUS                 Status;
  EFI_USB_DEVICE_DESCRIPTOR  DeviceDescriptor;
  CHAR16                     *StrMfg;
  BOOLEAN                    Found;
  UINT32                     Index;

  //
  // Get the default device descriptor
  //
  Status = UsbIo->UsbGetDeviceDescriptor (
                    UsbIo,
                    &DeviceDescriptor
                    );
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  Found = FALSE;
  Index = 0;
  while (gUSBDeviceList[Index].VendorId != 0 &&
         gUSBDeviceList[Index].DeviceId != 0 &&
         !Found                                  ) {
    if (DeviceDescriptor.IdProduct == gUSBDeviceList[Index].DeviceId &&
        DeviceDescriptor.IdVendor  == gUSBDeviceList[Index].VendorId      ){
        //
        // Checks to see if a string descriptor can be pulled from the device in
        // the selected language. If not False is returned indicating that this
        // is not a Usb Serial Device that can be managegd by this driver
        //
        StrMfg = NULL;
        Status = UsbIo->UsbGetStringDescriptor (
                          UsbIo,
                          USB_US_LANG_ID, // LANGID selector, should make this
                                          // more robust to verify lang support
                                          // for device
                          DeviceDescriptor.StrManufacturer,
                          &StrMfg
                          );
        if (StrMfg != NULL) {
          FreePool (StrMfg);
        }
        if (EFI_ERROR (Status)) {
          return FALSE;
        }
        return TRUE;
    }
    Index++;
  }
  return FALSE;
}

/**
  Internal function that sets the Data Bits, Stop Bits and Parity values on the
  Usb Serial Device with a single usb control transfer.

  @param  UsbIo[in]                  Usb Io Protocol instance pointer
  @param  DataBits[in]               The data bits value to be set on the Usb
                                     Serial Device
  @param  Parity[in]                 The parity type that will be set on the Usb
                                     Serial Device
  @param  StopBits[in]               The stop bits type that will be set on the
                                     Usb Serial Device
  @param  LastSettings[in]           A pointer to the Usb Serial Device's
                                     PREVIOUS_ATTRIBUTES item

  @retval EFI_SUCCESS                The data items were correctly set on the
                                     USB Serial Device
  @retval EFI_INVALID_PARAMETER      An invalid data parameter or an invalid
                                     combination or parameters was used
  @retval EFI_DEVICE_ERROR           The device is not functioning correctly and
                                     the data values were unable to be set

**/
EFI_STATUS
EFIAPI
SetDataInternal (
  IN EFI_USB_IO_PROTOCOL  *UsbIo,
  IN UINT8                DataBits,
  IN EFI_PARITY_TYPE      Parity,
  IN EFI_STOP_BITS_TYPE   StopBits,
  IN PREVIOUS_ATTRIBUTES  *LastSettings
  )
{
  EFI_STATUS              Status;
  EFI_USB_DEVICE_REQUEST  DevReq;
  UINT32                  ReturnValue;
  UINT8                   ConfigurationValue;

  //
  // Since data bits settings of 6,7,8 cannot be set with a stop bits setting of
  // 1.5 check to see if this happens when the values of last settings are used
  //
  if ((DataBits == 0) && (StopBits == OneFiveStopBits)) {
    if ((LastSettings->DataBits == 6) || (LastSettings->DataBits == 7) || (LastSettings->DataBits == 8)) {
      return EFI_INVALID_PARAMETER;
    }
  } else if ((StopBits == DefaultStopBits) && ((DataBits == 6) || (DataBits == 7) || (DataBits == 8))) {
    if (LastSettings->StopBits == OneFiveStopBits) {
      return EFI_INVALID_PARAMETER;
    }
  } else if ((DataBits == 0) && (StopBits == DefaultStopBits)) {
    if (LastSettings->StopBits == OneFiveStopBits) {
      if ((LastSettings->DataBits == 6) || (LastSettings->DataBits == 7) || (LastSettings->DataBits == 8)) {
        return EFI_INVALID_PARAMETER;
      }
    }
  }

  //
  // set the DevReq.Value for the usb control transfer to the correct value
  // based on the seleceted number of data bits if there is an invalid number of
  // data bits requested return EFI_INVALID_PARAMETER
  //
  if (((DataBits < 5 ) || (DataBits > 8)) && (DataBits != 0)) {
    return EFI_INVALID_PARAMETER;
  }
  if (DataBits == 0) {
    //
    // use the value of LastDataBits
    //
    DevReq.Value = SET_DATA_BITS (LastSettings->DataBits);
  } else {
    //
    // use the value of DataBits
    //
    DevReq.Value = SET_DATA_BITS (DataBits);
  }

  //
  // Set Parity
  //
  if (Parity == DefaultParity) {
    Parity = LastSettings->Parity;
  }

  if (Parity == NoParity) {
    DevReq.Value |= SET_PARITY_NONE;
  } else if (Parity == EvenParity) {
    DevReq.Value |= SET_PARITY_EVEN;
  } else if (Parity == OddParity){
    DevReq.Value |= SET_PARITY_ODD;
  } else if (Parity == MarkParity) {
    DevReq.Value |= SET_PARITY_MARK;
  } else if (Parity == SpaceParity) {
    DevReq.Value |= SET_PARITY_SPACE;
  }

  //
  // Set Stop Bits
  //
  if (StopBits == DefaultStopBits) {
    StopBits = LastSettings->StopBits;
  }

  if (StopBits == OneStopBit) {
    DevReq.Value |= SET_STOP_BITS_1;
  } else if (StopBits == OneFiveStopBits) {
    DevReq.Value |= SET_STOP_BITS_15;
  } else if (StopBits == TwoStopBits) {
    DevReq.Value |= SET_STOP_BITS_2;
  }

  //
  // set the rest of the DevReq parameters and perform the usb control transfer
  // to set the data bits on the device
  //
  DevReq.Request     = FTDI_COMMAND_SET_DATA;
  DevReq.RequestType = USB_REQ_TYPE_VENDOR;
  DevReq.Index       = FTDI_PORT_IDENTIFIER;
  DevReq.Length      = 0; // indicates that there is no data phase in this request

  Status = UsbIo->UsbControlTransfer (
                    UsbIo,
                    &DevReq,
                    EfiUsbDataOut,
                    WDR_SHORT_TIMEOUT,
                    &ConfigurationValue,
                    1,
                    &ReturnValue
                    );
  if (EFI_ERROR (Status)) {
    goto StatusError;
  }
  return Status;

StatusError:
  if ((Status != EFI_INVALID_PARAMETER) || (Status != EFI_DEVICE_ERROR)) {
    return EFI_DEVICE_ERROR;
  } else {
    return Status;
  }
}

/**
  Internal function that sets the baudrate on the Usb Serial Device.

  @param  UsbIo[in]                  Usb Io Protocol instance pointer
  @param  BaudRate[in]               The baudrate value to be set on the device.
                                     If this value is 0 the value of LastBaudRate
                                     will be used instead
  @param  LastBaudRate[in]           The baud rate value that was previously set
                                     on the Usb Serial Device

  @retval EFI_SUCCESS                The baudrate was set succesfully
  @retval EFI_INVALID_PARAMETER      An invalid baudrate was used
  @retval EFI_DEVICE_ERROR           The device is not functioning correctly and
                                     the baudrate was unable to be set

**/
EFI_STATUS
EFIAPI
SetBaudRateInternal (
  IN EFI_USB_IO_PROTOCOL  *UsbIo,
  IN UINT64               BaudRate,
  IN UINT64               LastBaudRate
  )
{
  EFI_STATUS              Status;
  EFI_USB_DEVICE_REQUEST  DevReq;
  UINT32                  ReturnValue;
  UINT8                   ConfigurationValue;
  UINT16                  EncodedBaudRate;
  EFI_TPL                 Tpl;

  Tpl    = gBS->RaiseTPL(TPL_NOTIFY);

  //
  // set the value of DevReq.Value based on the value of BaudRate
  // if 0 is selected as baud rate use the value of LastBaudRate
  //
  if (BaudRate == 0) {
    Status = EncodeBaudRateForFtdi (LastBaudRate, &EncodedBaudRate);
    if (EFI_ERROR (Status)) {
      gBS->RestoreTPL (Tpl);
      //
      // EncodeBaudRateForFtdi returns EFI_INVALID_PARAMETER when not
      // succesfull
      //
      return Status;
    }
    DevReq.Value = EncodedBaudRate;
  } else {
    Status = EncodeBaudRateForFtdi (BaudRate, &EncodedBaudRate);
    if (EFI_ERROR (Status)) {
      gBS->RestoreTPL (Tpl);
      //
      // EncodeBaudRateForFtdi returns EFI_INVALID_PARAMETER when not
      // successfull
      //
      return Status;
    }
    DevReq.Value = EncodedBaudRate;
  }

  //
  // set the remaining parameters of DevReq and perform the usb control transfer
  // to set the device
  //
  DevReq.Request     = FTDI_COMMAND_SET_BAUDRATE;
  DevReq.RequestType = USB_REQ_TYPE_VENDOR;
  DevReq.Index       = FTDI_PORT_IDENTIFIER;
  DevReq.Length      = 0; // indicates that there is no data phase in this request

  Status = UsbIo->UsbControlTransfer (
                    UsbIo,
                    &DevReq,
                    EfiUsbDataOut,
                    WDR_SHORT_TIMEOUT,
                    &ConfigurationValue,
                    1,
                    &ReturnValue
                    );
  if (EFI_ERROR (Status)) {
    goto StatusError;
  }
  gBS->RestoreTPL (Tpl);
  return Status;

StatusError:
  gBS->RestoreTPL (Tpl);
  if ((Status != EFI_INVALID_PARAMETER) || (Status != EFI_DEVICE_ERROR)) {
    return EFI_DEVICE_ERROR;
  } else {
    return Status;
  }
}

/**
  Sets the baud rate, receive FIFO depth, transmit/receice time out, parity,
  data bits, and stop bits on a serial device.

  @param  UsbSerialDevice[in]  Pointer to the current instance of the USB Serial
                               Device.
  @param  BaudRate[in]         The requested baud rate. A BaudRate value of 0
                               will use the device's default interface speed.
  @param  ReveiveFifoDepth[in] The requested depth of the FIFO on the receive
                               side of the serial interface. A ReceiveFifoDepth
                               value of 0 will use the device's default FIFO
                               depth.
  @param  Timeout[in]          The requested time out for a single character in
                               microseconds.This timeout applies to both the
                               transmit and receive side of the interface.A
                               Timeout value of 0 will use the device's default
                               time out value.
  @param  Parity[in]           The type of parity to use on this serial device.
                               A Parity value of DefaultParity will use the
                               device's default parity value.
  @param  DataBits[in]         The number of data bits to use on the serial
                               device. A DataBits value of 0 will use the
                               device's default data bit setting.
  @param  StopBits[in]         The number of stop bits to use on this serial
                               device. A StopBits value of DefaultStopBits will
                               use the device's default number of stop bits.

  @retval EFI_SUCCESS          The attributes were set
  @retval EFI_DEVICE_ERROR     The attributes were not able to be set

**/
EFI_STATUS
EFIAPI
SetAttributesInternal (
  IN USB_SER_DEV         *UsbSerialDevice,
  IN UINT64              BaudRate,
  IN UINT32              ReceiveFifoDepth,
  IN UINT32              Timeout,
  IN EFI_PARITY_TYPE     Parity,
  IN UINT8               DataBits,
  IN EFI_STOP_BITS_TYPE  StopBits
  )
{
  EFI_STATUS                Status;
  EFI_TPL                   Tpl;
  UART_DEVICE_PATH          *Uart;
  EFI_DEVICE_PATH_PROTOCOL  *RemainingDevicePath;

  Status = EFI_UNSUPPORTED;
  Tpl    = gBS->RaiseTPL(TPL_NOTIFY);
  Uart   = NULL;

  //
  // check for invalid combinations of parameters
  //
  if (((DataBits >= 6) && (DataBits <= 8)) && (StopBits == OneFiveStopBits)) {
    return  EFI_INVALID_PARAMETER;
  }

  //
  // set data bits, parity and stop bits
  //
  Status = SetDataInternal (
             UsbSerialDevice->UsbIo,
             DataBits,
             Parity,
             StopBits,
             &(UsbSerialDevice->LastSettings)
             );
  if (EFI_ERROR (Status)) {
    goto StatusError;
  }
  //
  // set baudrate
  //
  Status = SetBaudRateInternal (
             UsbSerialDevice->UsbIo,
             BaudRate,
             UsbSerialDevice->LastSettings.BaudRate
             );
  if (EFI_ERROR (Status)){
    goto StatusError;
  }

  //
  // update the values of UsbSerialDevice->LastSettings and UsbSerialDevice->SerialIo.Mode
  //
  if (BaudRate == 0) {
    UsbSerialDevice->LastSettings.BaudRate   = UsbSerialDevice->LastSettings.BaudRate;
    UsbSerialDevice->SerialIo.Mode->BaudRate = UsbSerialDevice->LastSettings.BaudRate;
  } else {
    UsbSerialDevice->LastSettings.BaudRate   = BaudRate;
    UsbSerialDevice->SerialIo.Mode->BaudRate = BaudRate;
  }

  UsbSerialDevice->LastSettings.Timeout          = FTDI_TIMEOUT;
  UsbSerialDevice->LastSettings.ReceiveFifoDepth = FTDI_MAX_RECEIVE_FIFO_DEPTH;

  if (Parity == DefaultParity) {
    UsbSerialDevice->LastSettings.Parity   = UsbSerialDevice->LastSettings.Parity;
    UsbSerialDevice->SerialIo.Mode->Parity = UsbSerialDevice->LastSettings.Parity;
  } else {
    UsbSerialDevice->LastSettings.Parity   = Parity;
    UsbSerialDevice->SerialIo.Mode->Parity = Parity;
  }
  if (DataBits == 0) {
    UsbSerialDevice->LastSettings.DataBits   = UsbSerialDevice->LastSettings.DataBits;
    UsbSerialDevice->SerialIo.Mode->DataBits = UsbSerialDevice->LastSettings.DataBits;
  } else {
    UsbSerialDevice->LastSettings.DataBits   = DataBits;
    UsbSerialDevice->SerialIo.Mode->DataBits = DataBits;
  }
  if (StopBits == DefaultStopBits) {
    UsbSerialDevice->LastSettings.StopBits   = UsbSerialDevice->LastSettings.StopBits;
    UsbSerialDevice->SerialIo.Mode->StopBits = UsbSerialDevice->LastSettings.StopBits;
  } else {
    UsbSerialDevice->LastSettings.StopBits   = StopBits;
    UsbSerialDevice->SerialIo.Mode->StopBits = StopBits;
  }

  //
  // See if the device path node has changed
  //
  if (UsbSerialDevice->UartDevicePath.BaudRate == BaudRate &&
      UsbSerialDevice->UartDevicePath.DataBits == DataBits &&
      UsbSerialDevice->UartDevicePath.StopBits == StopBits &&
      UsbSerialDevice->UartDevicePath.Parity == Parity
      ) {
    gBS->RestoreTPL (Tpl);
    return EFI_SUCCESS;
  }

  //
  // Update the device path
  //
  UsbSerialDevice->UartDevicePath.BaudRate = BaudRate;
  UsbSerialDevice->UartDevicePath.DataBits = DataBits;
  UsbSerialDevice->UartDevicePath.StopBits = (UINT8) StopBits;
  UsbSerialDevice->UartDevicePath.Parity   = (UINT8) Parity;

  Status = EFI_SUCCESS;
  if (UsbSerialDevice->ControllerHandle != NULL) {
    RemainingDevicePath = UsbSerialDevice->DevicePath;
    while (!IsDevicePathEnd (RemainingDevicePath)) {
      Uart = (UART_DEVICE_PATH *) NextDevicePathNode (RemainingDevicePath);
      if (Uart->Header.Type == MESSAGING_DEVICE_PATH &&
          Uart->Header.SubType == MSG_UART_DP &&
          sizeof (UART_DEVICE_PATH) == DevicePathNodeLength ((EFI_DEVICE_PATH *) Uart)) {
        Uart->BaudRate = BaudRate;
        Uart->DataBits = DataBits;
        Uart->StopBits = (UINT8)StopBits;
        Uart->Parity   = (UINT8) Parity;
        break;
        }
        RemainingDevicePath = NextDevicePathNode (RemainingDevicePath);
    }
  }

  gBS->RestoreTPL (Tpl);
  return Status;

StatusError:
  gBS->RestoreTPL (Tpl);
  if ((Status != EFI_INVALID_PARAMETER) || (Status != EFI_DEVICE_ERROR)) {
    return EFI_DEVICE_ERROR;
  } else {
    return Status;
  }
}

/**
  Internal function that performs a Usb Control Transfer to set the flow control
  on the Usb Serial Device.

  @param  UsbIo[in]                  Usb Io Protocol instance pointer
  @param  FlowControlEnable[in]      Data on the Enable/Disable status of Flow
                                     Control on the Usb Serial Device

  @retval EFI_SUCCESS                The flow control was set on the Usb Serial
                                     device
  @retval EFI_INVALID_PARAMETER      An invalid flow control value was used
  @retval EFI_EFI_UNSUPPORTED        The operation is not supported
  @retval EFI_DEVICE_ERROR           The device is not functioning correctly

**/
EFI_STATUS
EFIAPI
SetFlowControlInternal (
  IN EFI_USB_IO_PROTOCOL  *UsbIo,
  IN BOOLEAN              FlowControlEnable
  )
{
  EFI_STATUS               Status;
  EFI_USB_DEVICE_REQUEST   DevReq;
  UINT32                   ReturnValue;
  UINT8                    ConfigurationValue;

  //
  // set DevReq.Value based on the value of FlowControlEnable
  //
  if (!FlowControlEnable) {
    DevReq.Value = NO_FLOW_CTRL;
  }
  if (FlowControlEnable) {
    DevReq.Value = XON_XOFF_CTRL;
  }
  //
  // set the remaining DevReq parameters and perform the usb control transfer to
  // set the flow control on the device
  //
  DevReq.Request      = FTDI_COMMAND_SET_FLOW_CTRL;
  DevReq.RequestType  = USB_REQ_TYPE_VENDOR;
  DevReq.Index        = FTDI_PORT_IDENTIFIER;
  DevReq.Length       = 0; // indicates that this transfer has no data phase
  Status              = UsbIo->UsbControlTransfer (
                                 UsbIo,
                                 &DevReq,
                                 EfiUsbDataOut,
                                 WDR_TIMEOUT,
                                 &ConfigurationValue,
                                 1,
                                 &ReturnValue
                                 );
  if (EFI_ERROR (Status)) {
    goto StatusError;
  }

  return Status;

StatusError:
  if ((Status != EFI_INVALID_PARAMETER) ||
      (Status != EFI_DEVICE_ERROR)      ||
      (Status != EFI_UNSUPPORTED)          ) {
    return EFI_DEVICE_ERROR;
  } else {
    return Status;
  }
}

/**
  Internal function that performs a Usb Control Transfer to set the Dtr value on
  the Usb Serial Device.

  @param  UsbIo[in]                  Usb Io Protocol instance pointer
  @param  DtrEnable[in]              Data on the Enable/Disable status of the
                                     Dtr for the Usb Serial Device

  @retval EFI_SUCCESS                The Dtr value was set on the Usb Serial
                                     Device
  @retval EFI_INVALID_PARAMETER      An invalid Dtr value was used
  @retval EFI_UNSUPPORTED            The operation is not supported
  @retval EFI_DEVICE_ERROR           The device is not functioning correctly

**/
EFI_STATUS
EFIAPI
SetDtrInternal (
  IN EFI_USB_IO_PROTOCOL  *UsbIo,
  IN BOOLEAN              DtrEnable
  )
{
  EFI_STATUS              Status;
  EFI_USB_DEVICE_REQUEST  DevReq;
  UINT32                  ReturnValue;
  UINT8                   ConfigurationValue;

  //
  // set the value of DevReq.Value based on the value of DtrEnable
  //
  if (!DtrEnable) {
    DevReq.Value = SET_DTR_LOW;
  }
  if (DtrEnable) {
    DevReq.Value = SET_DTR_HIGH;
  }
  //
  // set the remaining attributes of DevReq and perform the usb control transfer
  // to set the device
  //
  DevReq.Request      = FTDI_COMMAND_MODEM_CTRL;
  DevReq.RequestType  = USB_REQ_TYPE_VENDOR;
  DevReq.Index        = FTDI_PORT_IDENTIFIER;
  DevReq.Length       = 0; // indicates that there is no data phase in this transfer

  Status = UsbIo->UsbControlTransfer (
                    UsbIo,
                    &DevReq,
                    EfiUsbDataOut,
                    WDR_TIMEOUT,
                    &ConfigurationValue,
                    1,
                    &ReturnValue
                    );
  if (EFI_ERROR (Status)) {
    goto StatusError;
  }
  return Status;

StatusError:
  if ((Status != EFI_INVALID_PARAMETER) ||
      (Status != EFI_DEVICE_ERROR)      ||
      (Status != EFI_UNSUPPORTED)          ) {
    return EFI_DEVICE_ERROR;
  } else {
    return Status;
  }
}

/**
  Internal function that performs a Usb Control Transfer to set the Dtr value on
  the Usb Serial Device.
  
  @param  UsbIo[in]                  Usb Io Protocol instance pointer
  @param  RtsEnable[in]              Data on the Enable/Disable status of the
                                     Rts for the Usb Serial Device

  @retval EFI_SUCCESS                The Rts value was set on the Usb Serial
                                     Device
  @retval EFI_INVALID_PARAMETER      An invalid Rts value was used
  @retval EFI_UNSUPPORTED            The operation is not supported
  @retval EFI_DEVICE_ERROR           The device is not functioning correctly

**/
EFI_STATUS
EFIAPI
SetRtsInternal (
  IN EFI_USB_IO_PROTOCOL  *UsbIo,
  IN BOOLEAN              RtsEnable
  )
{
  EFI_STATUS              Status;
  EFI_USB_DEVICE_REQUEST  DevReq;
  UINT32                  ReturnValue;
  UINT8                   ConfigurationValue;

  //
  // set DevReq.Value based on the value of RtsEnable
  //
  if (!RtsEnable) {
    DevReq.Value = SET_RTS_LOW;
  }
  if (RtsEnable) {
    DevReq.Value = SET_RTS_HIGH;
  }

  //
  // set the remaining parameters of DevReq and perform the usb control transfer
  // to set the values on the device
  //
  DevReq.Request     = FTDI_COMMAND_MODEM_CTRL;
  DevReq.RequestType = USB_REQ_TYPE_VENDOR;
  DevReq.Index       = FTDI_PORT_IDENTIFIER;
  DevReq.Length      = 0; // indicates that there is no data phase in this request

  Status = UsbIo->UsbControlTransfer (
                    UsbIo,
                    &DevReq,
                    EfiUsbDataOut,
                    WDR_TIMEOUT,
                    &ConfigurationValue,
                    1,
                    &ReturnValue
                    );
  if (EFI_ERROR (Status)) {
    goto StatusError;
  }

  return Status;

StatusError:
  if ((Status != EFI_INVALID_PARAMETER) ||
      (Status != EFI_DEVICE_ERROR)      ||
      (Status != EFI_UNSUPPORTED)          ) {
    return EFI_DEVICE_ERROR;
  } else {
    return Status;
  }
}

/**
  Internal function that checks for valid control values and sets the control
  bits on the Usb Serial Device.

  @param  UsbSerialDevice[in]        Handle to the Usb Serial Device whose
                                     control bits are being set
  @param  Control[in]                The control value passed to the function
                                     that contains the values of the control
                                     bits that are being set

  @retval EFI_SUCCESS                The control bits were set on the Usb Serial
                                     Device
  @retval EFI_INVALID_PARAMETER      An invalid control value was encountered
  @retval EFI_EFI_UNSUPPORTED        The operation is not supported
  @retval EFI_DEVICE_ERROR           The device is not functioning correctly

**/
EFI_STATUS
EFIAPI
SetControlBitsInternal (
  IN USB_SER_DEV   *UsbSerialDevice,
  IN CONTROL_BITS  *Control
  )
{
  EFI_STATUS                    Status;
  UART_FLOW_CONTROL_DEVICE_PATH *FlowControl;
  EFI_DEVICE_PATH_PROTOCOL      *RemainingDevicePath;

  //
  // check for invalid control parameters hardware and software loopback enabled
  // must always be set to FALSE
  //
  Control->HardwareLoopBack = FALSE;
  Control->SoftwareLoopBack = FALSE;

  //
  // set hardware flow control
  //
  Status  = SetFlowControlInternal (
              UsbSerialDevice->UsbIo,
              Control->HardwareFlowControl
              );
  if (EFI_ERROR (Status)) {
    goto StatusError;
  }

  //
  // set Dtr state
  //
  Status = SetDtrInternal (UsbSerialDevice->UsbIo, Control->DtrState);
  if (EFI_ERROR (Status)) {
    goto StatusError;
  }

  //
  // set Rts state
  //
  Status = SetRtsInternal (UsbSerialDevice->UsbIo, Control->RtsState);
  if (EFI_ERROR (Status)){
    goto StatusError;
  }

  //
  // update the remaining control values for UsbSerialDevice->ControlValues
  //
  UsbSerialDevice->ControlValues.DtrState            = Control->DtrState;
  UsbSerialDevice->ControlValues.RtsState            = Control->RtsState;
  UsbSerialDevice->ControlValues.HardwareFlowControl = Control->HardwareFlowControl;
  UsbSerialDevice->ControlValues.HardwareLoopBack    = FALSE;
  UsbSerialDevice->ControlValues.SoftwareLoopBack    = FALSE;

  Status = EFI_SUCCESS;
  //
  // Update the device path to have the correct flow control values
  //
  if (UsbSerialDevice->ControllerHandle != NULL) {
    RemainingDevicePath = UsbSerialDevice->DevicePath;
    while (!IsDevicePathEnd (RemainingDevicePath)) {
      FlowControl = (UART_FLOW_CONTROL_DEVICE_PATH *) NextDevicePathNode (RemainingDevicePath);
      if (FlowControl->Header.Type == MESSAGING_DEVICE_PATH &&
          FlowControl->Header.SubType == MSG_VENDOR_DP &&
          sizeof (UART_FLOW_CONTROL_DEVICE_PATH) == DevicePathNodeLength ((EFI_DEVICE_PATH *) FlowControl)){
        if (UsbSerialDevice->ControlValues.HardwareFlowControl == TRUE) {
          FlowControl->FlowControlMap = UART_FLOW_CONTROL_HARDWARE;
        } else if (UsbSerialDevice->ControlValues.HardwareFlowControl == FALSE) {
          FlowControl->FlowControlMap = 0;
        }
        break;
      }
      RemainingDevicePath = NextDevicePathNode (RemainingDevicePath);
    }
  }

  return Status;

StatusError:
  if ((Status != EFI_INVALID_PARAMETER) ||
      (Status != EFI_DEVICE_ERROR)      ||
      (Status != EFI_UNSUPPORTED)          ) {
    return EFI_DEVICE_ERROR;
  } else {
    return Status;
  }
}

/**
  Internal function that calculates the Control value used by GetControlBits()
  based on the status and control values of the Usb Serial Device.

  @param  UsbSerialDevice[in]        Handle to the Usb Serial Devie whose status
                                     and control values are being used to set
                                     Control
  @param  Control[out]               On output the formated value of Control
                                     that has been calculated based on the
                                     control and status values of the Usb Serial
                                     Device

  @retval EFI_SUCCESS                The value of Control was successfully
                                     calculated

**/
EFI_STATUS
EFIAPI
GetControlBitsInternal (
  IN USB_SER_DEV  *UsbSerialDevice,
  OUT UINT32      *Control
  )
{
  *Control = 0;

  //
  // Check the values of UsbSerialDevice->Status Values and modify control
  // accordingly these values correspond to the modem status register
  //
  if (UsbSerialDevice->StatusValues.CtsState) {
    *Control |= EFI_SERIAL_CLEAR_TO_SEND;
  }
  if (UsbSerialDevice->StatusValues.DsrState) {
    *Control |= EFI_SERIAL_DATA_SET_READY;
  }
  if (UsbSerialDevice->StatusValues.RiState) {
    *Control |= EFI_SERIAL_RING_INDICATE;
  }
  if (UsbSerialDevice->StatusValues.SdState) {
    *Control |= EFI_SERIAL_CARRIER_DETECT;
  }

  //
  // check the values of UsbSerialDevice->ControlValues and modify control
  // accordingly these values correspond to the values of the Modem Control
  // Register
  //
  if (UsbSerialDevice->ControlValues.DtrState) {
    *Control |= EFI_SERIAL_DATA_TERMINAL_READY;
  }
  if (UsbSerialDevice->ControlValues.RtsState) {
    *Control |= EFI_SERIAL_REQUEST_TO_SEND;
  }
  if (UsbSerialDevice->ControlValues.HardwareLoopBack) {
    *Control |= EFI_SERIAL_HARDWARE_LOOPBACK_ENABLE;
  }
  if (UsbSerialDevice->ControlValues.HardwareFlowControl) {
    *Control |= EFI_SERIAL_HARDWARE_FLOW_CONTROL_ENABLE;
  }
  //
  // check if the buffer is empty since only one is being used if it is empty
  // set both the receive and transmit buffers to empty
  //
  if (UsbSerialDevice->DataBufferHead == UsbSerialDevice->DataBufferTail) {
    *Control |= EFI_SERIAL_OUTPUT_BUFFER_EMPTY;
    *Control |= EFI_SERIAL_INPUT_BUFFER_EMPTY;
  }
  //
  // check for software loopback enable in UsbSerialDevice->ControlValues
  //
  if (UsbSerialDevice->ControlValues.SoftwareLoopBack) {
    *Control |= EFI_SERIAL_SOFTWARE_LOOPBACK_ENABLE;
  }

  return EFI_SUCCESS;
}

/**
  Resets the USB Serial Device

  This function is the internal method for resetting the device and is called by
  SerialReset()

  @param  UsbSerialDevice[in]  A pointer to the USB Serial device

  @retval EFI_SUCCESS          The device was reset
  @retval EFI_DEVICE_ERROR     The device could not be reset

**/
EFI_STATUS
EFIAPI
ResetInternal (
  IN USB_SER_DEV  *UsbSerialDevice
  )
{
  EFI_STATUS              Status;
  EFI_USB_DEVICE_REQUEST  DevReq;
  UINT8                   ConfigurationValue;
  UINT32                  ReturnValue;

  DevReq.Request     = FTDI_COMMAND_RESET_PORT;
  DevReq.RequestType = USB_REQ_TYPE_VENDOR;
  DevReq.Value       = RESET_PORT_PURGE_RX;
  DevReq.Index       = FTDI_PORT_IDENTIFIER;
  DevReq.Length      = 0; //indicates that there is not data phase in this request

  Status = UsbSerialDevice->UsbIo->UsbControlTransfer (
                                     UsbSerialDevice->UsbIo,
                                     &DevReq,
                                     EfiUsbDataIn,
                                     WDR_TIMEOUT,
                                     &ConfigurationValue,
                                     1,
                                     &ReturnValue
                                     );
  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }

  DevReq.Request     = FTDI_COMMAND_RESET_PORT;
  DevReq.RequestType = USB_REQ_TYPE_VENDOR;
  DevReq.Value       = RESET_PORT_PURGE_TX;
  DevReq.Index       = FTDI_PORT_IDENTIFIER;
  DevReq.Length      = 0; //indicates that there is no data phase in this request

  Status = UsbSerialDevice->UsbIo->UsbControlTransfer (
                                     UsbSerialDevice->UsbIo,
                                     &DevReq,
                                     EfiUsbDataIn,
                                     WDR_TIMEOUT,
                                     &ConfigurationValue,
                                     1,
                                     &ReturnValue
                                     );
  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }
  return Status;
}

/**
  Entrypoint of USB Serial Driver.

  This function is the entrypoint of USB Serial Driver. It installs
  Driver Binding Protocols together with Component Name Protocols.

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

  @retval EFI_SUCCESS           The entry point is executed successfully.

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

  Status = EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             SystemTable,
             &gUsbSerialDriverBinding,
             ImageHandle,
             &gUsbSerialComponentName,
             &gUsbSerialComponentName2
             );
  ASSERT_EFI_ERROR (Status);
  return EFI_SUCCESS;
}

/**
  Unload function for the Usb Serial Driver.

  @param  ImageHandle[in]    The allocated handle for the EFI image

  @retval EFI_SUCCESS        The driver was unloaded successfully
**/
EFI_STATUS
EFIAPI
FtdiUsbSerialUnload (
  IN EFI_HANDLE  ImageHandle
  )
{
  EFI_STATUS  Status;
  EFI_HANDLE  *HandleBuffer;
  UINTN       HandleCount;
  UINTN       Index;

  //
  // Retrieve all handles in the handle database
  //
  Status = gBS->LocateHandleBuffer (
                  AllHandles,
                  NULL,
                  NULL,
                  &HandleCount,
                  &HandleBuffer
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Disconnect the driver from the handles in the handle database
  //
  for (Index = 0; Index < HandleCount; Index++) {
    Status = gBS->DisconnectController (
                    HandleBuffer[Index],
                    gImageHandle,
                    NULL
                    );
  }

  //
  // Free the handle array
  //
  FreePool (HandleBuffer);

  //
  // Uninstall protocols installed by the driver in its entrypoint
  //
  Status = gBS->UninstallMultipleProtocolInterfaces (
                  ImageHandle,
                  &gEfiDriverBindingProtocolGuid,
                  &gUsbSerialDriverBinding,
                  &gEfiComponentNameProtocolGuid,
                  &gUsbSerialComponentName,
                  &gEfiComponentName2ProtocolGuid,
                  &gUsbSerialComponentName2,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return EFI_SUCCESS;
}

/**
  Check whether USB Serial driver supports this device.

  @param  This[in]                   The USB Serial driver binding protocol.
  @param  Controller[in]             The controller handle to check.
  @param  RemainingDevicePath[in]    The remaining device path.

  @retval EFI_SUCCESS                The driver supports this controller.
  @retval other                      This device isn't supported.

**/
EFI_STATUS
EFIAPI
UsbSerialDriverBindingSupported (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   Controller,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS           Status;
  EFI_USB_IO_PROTOCOL  *UsbIo;
  UART_DEVICE_PATH     *UartNode;
  UART_FLOW_CONTROL_DEVICE_PATH        *FlowControlNode;
  UINTN                                Index;
  UINTN                                EntryCount;
  EFI_OPEN_PROTOCOL_INFORMATION_ENTRY  *OpenInfoBuffer;
  BOOLEAN                              HasFlowControl;
  EFI_DEVICE_PATH_PROTOCOL             *DevicePath;
  EFI_DEVICE_PATH_PROTOCOL             *ParentDevicePath;

  if (RemainingDevicePath != NULL) {
    if (!IsDevicePathEnd (RemainingDevicePath)) {
      Status = EFI_UNSUPPORTED;
      UartNode = (UART_DEVICE_PATH *) NextDevicePathNode (RemainingDevicePath);
      if (UartNode->Header.Type != MESSAGING_DEVICE_PATH ||
          UartNode->Header.SubType != MSG_UART_DP ||
          sizeof (UART_DEVICE_PATH) != DevicePathNodeLength ((EFI_DEVICE_PATH *) UartNode)) {
        goto Error;
      }
      FlowControlNode = (UART_FLOW_CONTROL_DEVICE_PATH *) NextDevicePathNode (UartNode);
      if ((ReadUnaligned32 (&FlowControlNode->FlowControlMap) & ~UART_FLOW_CONTROL_HARDWARE) != 0) {
        goto Error;
      }
    }
  }

  //
  // Check if USB I/O Protocol is attached on the controller handle.
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiUsbIoProtocolGuid,
                  (VOID **) &UsbIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (Status == EFI_ALREADY_STARTED) {
    if (RemainingDevicePath == NULL || IsDevicePathEnd (RemainingDevicePath)) {
      return EFI_SUCCESS;
    }
    Status = gBS->OpenProtocolInformation (
                    Controller,
                    &gEfiUsbIoProtocolGuid,
                    &OpenInfoBuffer,
                    &EntryCount
                    );
    if (EFI_ERROR (Status)) {
      return Status;
    }
    for (Index = 0; Index < EntryCount; Index++) {
      if ((OpenInfoBuffer[Index].Attributes & EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) != 0) {
        Status = gBS->OpenProtocol (
                        OpenInfoBuffer[Index].ControllerHandle,
                        &gEfiDevicePathProtocolGuid,
                        (VOID **) &DevicePath,
                        This->DriverBindingHandle,
                        Controller,
                        EFI_OPEN_PROTOCOL_GET_PROTOCOL
                        );
        if (!EFI_ERROR (Status)) {
          HasFlowControl = ContainsFlowControl (RemainingDevicePath);
          if (HasFlowControl ^ ContainsFlowControl (DevicePath)) {
            Status = EFI_UNSUPPORTED;
          }
        }
        break;
      }
    }
    FreePool (OpenInfoBuffer);
    return Status;
  }

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

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

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

  //
  // Use the USB I/O Protocol interface to check whether Controller is
  // a USB Serial device that can be managed by this driver.
  //
  Status = EFI_SUCCESS;

  if (!IsUsbSerial (UsbIo)) {
    Status = EFI_UNSUPPORTED;
    goto Error;
  }

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

/**
  Starts the USB Serial device with this driver.

  This function produces initializes the USB Serial device and
  produces the Serial IO Protocol.

  @param  This[in]                   The USB Serial driver binding instance.
  @param  Controller[in]             Handle of device to bind driver to.
  @param  RemainingDevicePath[in]    Optional parameter use to pick a specific
                                     child device to start.

  @retval EFI_SUCCESS                The controller is controlled by the usb USB
                                     Serial driver.
  @retval EFI_UNSUPPORTED            No interrupt endpoint can be found.
  @retval Other                      This controller cannot be started.

**/
EFI_STATUS
EFIAPI
UsbSerialDriverBindingStart (
  IN EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN EFI_HANDLE                   Controller,
  IN EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
{
  EFI_STATUS                          Status;
  EFI_USB_IO_PROTOCOL                 *UsbIo;
  USB_SER_DEV                         *UsbSerialDevice;
  UINT8                               EndpointNumber;
  EFI_USB_ENDPOINT_DESCRIPTOR         EndpointDescriptor;
  UINT8                               Index;
  BOOLEAN                             FoundIn;
  BOOLEAN                             FoundOut;
  EFI_DEVICE_PATH_PROTOCOL            *ParentDevicePath;
  EFI_OPEN_PROTOCOL_INFORMATION_ENTRY *OpenInfoBuffer;
  UINTN                               EntryCount;
  EFI_SERIAL_IO_PROTOCOL              *SerialIo;
  UART_DEVICE_PATH                    *Uart;
  UART_FLOW_CONTROL_DEVICE_PATH       *FlowControl;
  UINT32                              Control;
  EFI_DEVICE_PATH_PROTOCOL            *TempDevicePath;

  UsbSerialDevice = AllocateZeroPool (sizeof (USB_SER_DEV));
  ASSERT (UsbSerialDevice != NULL);

  //
  // Get the Parent Device path
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &ParentDevicePath,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status) && Status != EFI_ALREADY_STARTED) {
    goto ErrorExit1;
  }

  //
  // Open USB I/O Protocol
  //
  Status = gBS->OpenProtocol (
                  Controller,
                  &gEfiUsbIoProtocolGuid,
                  (VOID **) &UsbIo,
                  This->DriverBindingHandle,
                  Controller,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status) && Status != EFI_ALREADY_STARTED) {
    goto ErrorExit1;
  }

  if (Status == EFI_ALREADY_STARTED) {
    if (RemainingDevicePath == NULL || IsDevicePathEnd (RemainingDevicePath)) {
      FreePool (UsbSerialDevice);
      return EFI_SUCCESS;
    }

    //
    // Check to see if a child handle exists
    //
    Status = gBS->OpenProtocolInformation (
                    Controller,
                    &gEfiSerialIoProtocolGuid,
                    &OpenInfoBuffer,
                    &EntryCount
                    );
    if (EFI_ERROR (Status)) {
      goto ErrorExit1;
    }

    Status = EFI_ALREADY_STARTED;
    for (Index = 0; Index < EntryCount; Index++) {
      if ((OpenInfoBuffer[Index].Attributes & EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER) != 0) {
        Status = gBS->OpenProtocol (
                        OpenInfoBuffer[Index].ControllerHandle,
                        &gEfiSerialIoProtocolGuid,
                        (VOID **) &SerialIo,
                        This->DriverBindingHandle,
                        Controller,
                        EFI_OPEN_PROTOCOL_GET_PROTOCOL
                        );
        if (EFI_ERROR (Status)) {
        }
        if (!EFI_ERROR (Status)) {
          Uart = (UART_DEVICE_PATH *) RemainingDevicePath;
          Status = SerialIo->SetAttributes (
                               SerialIo,
                               Uart->BaudRate,
                               SerialIo->Mode->ReceiveFifoDepth,
                               SerialIo->Mode->Timeout,
                               (EFI_PARITY_TYPE) Uart->Parity,
                               Uart->DataBits,
                               (EFI_STOP_BITS_TYPE) Uart->StopBits
                               );
          FlowControl = (UART_FLOW_CONTROL_DEVICE_PATH *) NextDevicePathNode (Uart);
          if (!EFI_ERROR (Status) && IsUartFlowControlNode (FlowControl)) {
            Status = SerialIo->GetControl (
                                 SerialIo,
                                 &Control
                                 );
            if (!EFI_ERROR (Status)) {
              if (ReadUnaligned32 (&FlowControl->FlowControlMap) == UART_FLOW_CONTROL_HARDWARE) {
                Control |= EFI_SERIAL_HARDWARE_FLOW_CONTROL_ENABLE;
              } else {
                Control &= ~EFI_SERIAL_HARDWARE_FLOW_CONTROL_ENABLE;
              }
              //
              // Clear bits that are not allowed to be passed to SetControl
              //
              Control &= (EFI_SERIAL_REQUEST_TO_SEND | 
                          EFI_SERIAL_DATA_TERMINAL_READY |
                          EFI_SERIAL_HARDWARE_LOOPBACK_ENABLE |
                          EFI_SERIAL_SOFTWARE_LOOPBACK_ENABLE |
                          EFI_SERIAL_HARDWARE_FLOW_CONTROL_ENABLE);
              Status = SerialIo->SetControl (SerialIo, Control);
            }
          }
        }
        break;
      }
    }
    FreePool (OpenInfoBuffer);
    return Status;
  }

  if (RemainingDevicePath != NULL) {
    if (IsDevicePathEnd (RemainingDevicePath)) {
      return EFI_SUCCESS;
    }
  }

  UsbSerialDevice->UsbIo = UsbIo;

  //
  // Get interface & endpoint descriptor
  //
  UsbIo->UsbGetInterfaceDescriptor (
           UsbIo,
           &UsbSerialDevice->InterfaceDescriptor
           );

  EndpointNumber = UsbSerialDevice->InterfaceDescriptor.NumEndpoints;

  //
  // Traverse endpoints to find the IN and OUT endpoints that will send and
  // receive data.
  //
  FoundIn = FALSE;
  FoundOut = FALSE;
  for (Index = 0; Index < EndpointNumber; Index++) {

    Status = UsbIo->UsbGetEndpointDescriptor (
                      UsbIo,
                      Index,
                      &EndpointDescriptor
                      );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    if (EndpointDescriptor.EndpointAddress == FTDI_ENDPOINT_ADDRESS_OUT) {
      //
      // Set the Out endpoint device
      //
      CopyMem (
        &UsbSerialDevice->OutEndpointDescriptor,
        &EndpointDescriptor,
        sizeof(EndpointDescriptor)
        );
      FoundOut = TRUE;
    }

    if (EndpointDescriptor.EndpointAddress == FTDI_ENDPOINT_ADDRESS_IN) {
      //
      // Set the In endpoint device
      //
      CopyMem (
        &UsbSerialDevice->InEndpointDescriptor,
        &EndpointDescriptor,
        sizeof(EndpointDescriptor)
        );
      FoundIn = TRUE;
    }
  }

  if (!FoundIn || !FoundOut) {
    //
    // No interrupt endpoint found, then return unsupported.
    //
    Status = EFI_UNSUPPORTED;
    goto ErrorExit;
  }
  //
  // set the initial values of UsbSerialDevice->LastSettings to the default
  // values
  //
  UsbSerialDevice->LastSettings.BaudRate         = 115200;
  UsbSerialDevice->LastSettings.DataBits         = 8;
  UsbSerialDevice->LastSettings.Parity           = NoParity;
  UsbSerialDevice->LastSettings.ReceiveFifoDepth = FTDI_MAX_RECEIVE_FIFO_DEPTH;
  UsbSerialDevice->LastSettings.StopBits         = OneStopBit;
  UsbSerialDevice->LastSettings.Timeout          = FTDI_TIMEOUT;

  //
  // set the initial values of UsbSerialDevice->ControlValues
  //
  UsbSerialDevice->ControlValues.DtrState            = FALSE;
  UsbSerialDevice->ControlValues.RtsState            = FALSE;
  UsbSerialDevice->ControlValues.HardwareFlowControl = FALSE;
  UsbSerialDevice->ControlValues.HardwareLoopBack    = FALSE;
  UsbSerialDevice->ControlValues.SoftwareLoopBack    = FALSE;

  //
  // set the values of UsbSerialDevice->UartDevicePath
  //
  UsbSerialDevice->UartDevicePath.Header.Type    = MESSAGING_DEVICE_PATH;
  UsbSerialDevice->UartDevicePath.Header.SubType = MSG_UART_DP;
  UsbSerialDevice->UartDevicePath.Header.Length[0] = (UINT8) (sizeof (UART_DEVICE_PATH));
  UsbSerialDevice->UartDevicePath.Header.Length[1] = (UINT8) ((sizeof (UART_DEVICE_PATH)) >> 8);

  //
  // set the values of UsbSerialDevice->FlowControlDevicePath
  UsbSerialDevice->FlowControlDevicePath.Header.Type = MESSAGING_DEVICE_PATH;
  UsbSerialDevice->FlowControlDevicePath.Header.SubType = MSG_VENDOR_DP;
  UsbSerialDevice->FlowControlDevicePath.Header.Length[0] = (UINT8) (sizeof (UART_FLOW_CONTROL_DEVICE_PATH));
  UsbSerialDevice->FlowControlDevicePath.Header.Length[1] = (UINT8) ((sizeof (UART_FLOW_CONTROL_DEVICE_PATH)) >> 8);
  UsbSerialDevice->FlowControlDevicePath.FlowControlMap = 0;

  Status = SetAttributesInternal (
             UsbSerialDevice, 
             UsbSerialDevice->LastSettings.BaudRate,
             UsbSerialDevice->LastSettings.ReceiveFifoDepth, 
             UsbSerialDevice->LastSettings.Timeout,
             UsbSerialDevice->LastSettings.Parity, 
             UsbSerialDevice->LastSettings.DataBits,
             UsbSerialDevice->LastSettings.StopBits
             );

  ASSERT_EFI_ERROR (Status);

  Status = SetControlBitsInternal (
             UsbSerialDevice,
             &(UsbSerialDevice->ControlValues)
             );

  ASSERT_EFI_ERROR (Status);

  //
  // Publish Serial GUID and protocol
  //

  UsbSerialDevice->Signature              = USB_SER_DEV_SIGNATURE;
  UsbSerialDevice->SerialIo.Reset         = SerialReset;
  UsbSerialDevice->SerialIo.SetControl    = SetControlBits;
  UsbSerialDevice->SerialIo.SetAttributes = SetAttributes;
  UsbSerialDevice->SerialIo.GetControl    = GetControlBits;
  UsbSerialDevice->SerialIo.Read          = ReadSerialIo;
  UsbSerialDevice->SerialIo.Write         = WriteSerialIo;

  //
  // Set the static Serial IO modes that will display when running
  // "sermode" within the UEFI shell.
  //

  UsbSerialDevice->SerialIo.Mode->Timeout  = 0;
  UsbSerialDevice->SerialIo.Mode->BaudRate = 115200;
  UsbSerialDevice->SerialIo.Mode->DataBits = 8;
  UsbSerialDevice->SerialIo.Mode->Parity   = 1;
  UsbSerialDevice->SerialIo.Mode->StopBits = 1;

  UsbSerialDevice->ParentDevicePath = ParentDevicePath;
  UsbSerialDevice->ControllerHandle = NULL;
  FlowControl                       = NULL;

  //
  // Allocate space for the receive buffer
  //
  UsbSerialDevice->DataBuffer = AllocateZeroPool (SW_FIFO_DEPTH);

  //
  // Initialize data buffer pointers.
  // Head==Tail = true means buffer is empty.
  //
  UsbSerialDevice->DataBufferHead = 0;
  UsbSerialDevice->DataBufferTail = 0;

  UsbSerialDevice->ControllerNameTable = NULL;
  AddUnicodeString2 (
    "eng",
    gUsbSerialComponentName.SupportedLanguages,
    &UsbSerialDevice->ControllerNameTable,
    L"FTDI USB Serial Adapter",
    TRUE
    );
  AddUnicodeString2 (
    "en",
    gUsbSerialComponentName2.SupportedLanguages,
    &UsbSerialDevice->ControllerNameTable,
    L"FTDI USB Serial Adapter",
    FALSE
    );

  Status = SetInitialStatus (UsbSerialDevice);
  ASSERT_EFI_ERROR (Status);

  //
  // Create a polling loop to check for input
  //

  gBS->CreateEvent (
         EVT_TIMER | EVT_NOTIFY_SIGNAL,
         TPL_CALLBACK,
         UsbSerialDriverCheckInput,
         UsbSerialDevice,
         &(UsbSerialDevice->PollingLoop)
         );
  //
  // add code to set trigger time based on baud rate
  // setting to 0.5s for now
  //
  gBS->SetTimer (
         UsbSerialDevice->PollingLoop,
         TimerPeriodic,
         EFI_TIMER_PERIOD_MILLISECONDS (500)
         );

  //
  // Check if the remaining device path is null. If it is not null change the settings
  // of the device to match those on the device path
  //
  if (RemainingDevicePath != NULL) {
    CopyMem (
      &UsbSerialDevice->UartDevicePath,
      RemainingDevicePath,
      sizeof (UART_DEVICE_PATH)
      );
    FlowControl = (UART_FLOW_CONTROL_DEVICE_PATH *) NextDevicePathNode (RemainingDevicePath);
    if (IsUartFlowControlNode (FlowControl)) {
      UsbSerialDevice->FlowControlDevicePath.FlowControlMap = ReadUnaligned32 (&FlowControl->FlowControlMap);
    } else {
      FlowControl = NULL;
    }
  }

  //
  // Build the device path by appending the UART node to the parent device path
  //
  UsbSerialDevice->DevicePath = AppendDevicePathNode (
                                  ParentDevicePath,
                                  (EFI_DEVICE_PATH_PROTOCOL *) &UsbSerialDevice->UartDevicePath
                                  );
  //
  // Continue building the device path by appending the flow control node
  //
  TempDevicePath = UsbSerialDevice->DevicePath;
  UsbSerialDevice->DevicePath = AppendDevicePathNode (
                                  TempDevicePath,
                                  (EFI_DEVICE_PATH_PROTOCOL *) &UsbSerialDevice->FlowControlDevicePath
                                  );
  FreePool (TempDevicePath);

  if (UsbSerialDevice->DevicePath == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto ErrorExit;
  }

  //
  // Install protocol interfaces for the device
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &UsbSerialDevice->ControllerHandle,
                  &gEfiDevicePathProtocolGuid,
                  UsbSerialDevice->DevicePath,
                  &gEfiSerialIoProtocolGuid,
                  &UsbSerialDevice->SerialIo,
                  NULL
                  );
  if (EFI_ERROR (Status)){
    goto ErrorExit;
  }

  //
  // Open for child device
  //
  Status = gBS->OpenProtocol (
                 Controller,
                 &gEfiUsbIoProtocolGuid,
                 (VOID **) &UsbIo,
                 This->DriverBindingHandle,
                 UsbSerialDevice->ControllerHandle,
                 EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                 );

  UsbSerialDevice->Shutdown = FALSE;

  return EFI_SUCCESS;

ErrorExit:
  //
  // Error handler
  //

  Status = gBS->UninstallMultipleProtocolInterfaces (
                  Controller,
                  &gEfiSerialIoProtocolGuid,
                  &UsbSerialDevice->SerialIo,
                  NULL
                  );
  if (EFI_ERROR (Status)) {
    goto ErrorExit1;
  }

  FreePool (UsbSerialDevice->DataBuffer);
  FreePool (UsbSerialDevice);

  UsbSerialDevice = NULL;
  gBS->CloseProtocol (
         Controller,
         &gEfiUsbIoProtocolGuid,
         This->DriverBindingHandle,
         Controller
         );

ErrorExit1:
  return Status;
}

/**
  Stop the USB Serial device handled by this driver.

  @param  This[in]                   The USB Serial driver binding protocol.
  @param  Controller[in]             The controller to release.
  @param  NumberOfChildren[in]       The number of handles in ChildHandleBuffer.
  @param  ChildHandleBuffer[in]      The array of child handle.

  @retval EFI_SUCCESS                The device was stopped.
  @retval EFI_UNSUPPORTED            Serial IO Protocol is not installed on
                                     Controller.
  @retval EFI_DEVICE_ERROR           The device could not be stopped due to a
                                     device error.
  @retval Others                     Fail to uninstall protocols attached on the
                                     device.

**/
EFI_STATUS
EFIAPI
UsbSerialDriverBindingStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                   Controller,
  IN  UINTN                        NumberOfChildren,
  IN  EFI_HANDLE                   *ChildHandleBuffer
  )
{
  EFI_STATUS                Status;
  EFI_SERIAL_IO_PROTOCOL    *SerialIo;
  EFI_USB_IO_PROTOCOL       *UsbIo;
  USB_SER_DEV               *UsbSerialDevice;
  UINTN                     Index;
  BOOLEAN                   AllChildrenStopped;

  Status = EFI_SUCCESS;
  UsbSerialDevice = NULL;

  if (NumberOfChildren == 0) {
    //
    // Close the driver
    //
    Status = gBS->CloseProtocol (
                    Controller,
                    &gEfiUsbIoProtocolGuid,
                    This->DriverBindingHandle,
                    Controller
                    );
    Status = gBS->CloseProtocol (
                    Controller,
                    &gEfiDevicePathProtocolGuid,
                    This->DriverBindingHandle,
                    Controller
                    );
    return Status;
  }

  AllChildrenStopped = TRUE;

  for (Index = 0; Index < NumberOfChildren ;Index++) {
    Status = gBS->OpenProtocol (
                    ChildHandleBuffer[Index],
                    &gEfiSerialIoProtocolGuid,
                    (VOID **) &SerialIo,
                    This->DriverBindingHandle,
                    Controller,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (Status == EFI_SUCCESS) {//!EFI_ERROR (Status)) {
      UsbSerialDevice = USB_SER_DEV_FROM_THIS (SerialIo);
      Status = gBS->CloseProtocol (
                      Controller,
                      &gEfiUsbIoProtocolGuid,
                      This->DriverBindingHandle,
                      ChildHandleBuffer[Index]
                      );
      Status = gBS->UninstallMultipleProtocolInterfaces (
                      ChildHandleBuffer[Index],
                      &gEfiDevicePathProtocolGuid,
                      UsbSerialDevice->DevicePath,
                      &gEfiSerialIoProtocolGuid,
                      &UsbSerialDevice->SerialIo,
                      NULL
                      );

      if (EFI_ERROR (Status)) {
        gBS->OpenProtocol (
               Controller,
               &gEfiUsbIoProtocolGuid,
               (VOID **) &UsbIo,
               This->DriverBindingHandle,
               ChildHandleBuffer[Index],
               EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
               );
      } else {
        if (UsbSerialDevice->DevicePath != NULL) {
          gBS->FreePool (UsbSerialDevice->DevicePath);
        }
        gBS->SetTimer (
               UsbSerialDevice->PollingLoop,
               TimerCancel,
               0
               );
        gBS->CloseEvent (UsbSerialDevice->PollingLoop);
        UsbSerialDevice->Shutdown = TRUE;
        FreeUnicodeStringTable (UsbSerialDevice->ControllerNameTable);
        FreePool (UsbSerialDevice->DataBuffer);
        FreePool (UsbSerialDevice);
      }
    }
    if (EFI_ERROR (Status)) {
      AllChildrenStopped = FALSE;
    }
  }

  if (!AllChildrenStopped) {
    return EFI_DEVICE_ERROR;
  }
  return EFI_SUCCESS;
}

//
// Serial IO Member Functions
//

/**
  Reset the serial device.

  @param  This[in]              Protocol instance pointer.

  @retval EFI_SUCCESS           The device was reset.
  @retval EFI_DEVICE_ERROR      The serial device could not be reset.

**/
EFI_STATUS
EFIAPI
SerialReset (
  IN EFI_SERIAL_IO_PROTOCOL  *This
  )
{
  EFI_STATUS    Status;
  USB_SER_DEV  *UsbSerialDevice;

  UsbSerialDevice = USB_SER_DEV_FROM_THIS (This);
  Status          = ResetInternal (UsbSerialDevice);
  if (EFI_ERROR (Status)){
    return EFI_DEVICE_ERROR;
  }
  return Status;
}

/**
  Set the control bits on a serial device.

  @param  This[in]             Protocol instance pointer.
  @param  Control[in]          Set the bits of Control that are settable.

  @retval EFI_SUCCESS          The new control bits were set on the serial device.
  @retval EFI_UNSUPPORTED      The serial device does not support this operation.
  @retval EFI_DEVICE_ERROR     The serial device is not functioning correctly.

**/
EFI_STATUS
EFIAPI
SetControlBits (
  IN EFI_SERIAL_IO_PROTOCOL  *This,
  IN UINT32                  Control
  )
{
  EFI_STATUS    Status;
  USB_SER_DEV   *UsbSerialDevice;
  CONTROL_BITS  ControlBits;
  
  UsbSerialDevice = USB_SER_DEV_FROM_THIS (This);
  
  //
  // check for invalid control parameters 
  //
  if ((Control & (~(EFI_SERIAL_REQUEST_TO_SEND          |
                    EFI_SERIAL_DATA_TERMINAL_READY      |
                    EFI_SERIAL_HARDWARE_LOOPBACK_ENABLE |
                    EFI_SERIAL_SOFTWARE_LOOPBACK_ENABLE |
                    EFI_SERIAL_HARDWARE_FLOW_CONTROL_ENABLE))) != 0 ) {
    return EFI_UNSUPPORTED;
  }

  //
  // check the control parameters and set the correct setting for
  // the paramerts of ControlBits
  // both loopback enables are always set to FALSE
  //
  ControlBits.HardwareLoopBack = FALSE;
  ControlBits.SoftwareLoopBack = FALSE;
  //
  // check for hardware flow control
  //
  if ((Control & EFI_SERIAL_HARDWARE_FLOW_CONTROL_ENABLE) == EFI_SERIAL_HARDWARE_FLOW_CONTROL_ENABLE) {
    ControlBits.HardwareFlowControl = TRUE;
  } else {
    ControlBits.HardwareFlowControl = FALSE;
  }
  //
  // check for DTR enabled
  //
  if ((Control & EFI_SERIAL_DATA_TERMINAL_READY) == EFI_SERIAL_DATA_TERMINAL_READY) {
    ControlBits.DtrState = TRUE;
  } else {
    ControlBits.DtrState = FALSE;
  }
  //
  // check for RTS enabled
  //
  if ((Control & EFI_SERIAL_REQUEST_TO_SEND) == EFI_SERIAL_REQUEST_TO_SEND) {
    ControlBits.RtsState = TRUE;
  } else {
    ControlBits.RtsState = FALSE;
  }

  //
  // set the control values with a call to SetControlBitsInternal()
  //
  Status = SetControlBitsInternal (UsbSerialDevice, &ControlBits);

  return Status;
}

/**
  calls SetAttributesInternal() to set the baud rate, receive FIFO depth,
  transmit/receive time out, parity, data buts, and stop bits on a serial
  device.

  @param  This[in]             Protocol instance pointer.
  @param  BaudRate[in]         The requested baud rate. A BaudRate value of 0
                               will use the device's default interface speed.
  @param  ReveiveFifoDepth[in] The requested depth of the FIFO on the receive
                               side of the serial interface. A ReceiveFifoDepth
                               value of 0 will use the device's default FIFO
                               depth.
  @param  Timeout[in]          The requested time out for a single character in
                               microseconds.This timeout applies to both the
                               transmit and receive side of the interface. A
                               Timeout value of 0 will use the device's default
                               time out value.
  @param  Parity[in]           The type of parity to use on this serial device.
                               A Parity value of DefaultParity will use the
                               device's default parity value.
  @param  DataBits[in]         The number of data bits to use on the serial
                               device. A DataBit vaule of 0 will use the
                               device's default data bit setting.
  @param  StopBits[in]         The number of stop bits to use on this serial
                               device. A StopBits value of DefaultStopBits will
                               use the device's default number of stop bits.

  @retval EFI_SUCCESS          The attributes were set
  @retval EFI_DEVICE_ERROR     The attributes were not able to be

**/
EFI_STATUS
EFIAPI
SetAttributes (
  IN EFI_SERIAL_IO_PROTOCOL  *This,
  IN UINT64                  BaudRate,
  IN UINT32                  ReceiveFifoDepth,
  IN UINT32                  Timeout,
  IN EFI_PARITY_TYPE         Parity,
  IN UINT8                   DataBits,
  IN EFI_STOP_BITS_TYPE      StopBits
  )
{

  EFI_STATUS   Status;
  USB_SER_DEV  *UsbSerialDevice;

  UsbSerialDevice = USB_SER_DEV_FROM_THIS (This);

  Status = SetAttributesInternal (
             UsbSerialDevice,
             BaudRate,
             ReceiveFifoDepth,
             Timeout,
             Parity,
             DataBits,
             StopBits
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return Status;
}


/**
  Retrieves the status of the control bits on a serial device.

  @param  This[in]               Protocol instance pointer.
  @param  Control[out]           A pointer to return the current Control signals
                                 from the serial device.

  @retval EFI_SUCCESS            The control bits were read from the serial
                                 device.
  @retval EFI_DEVICE_ERROR       The serial device is not functioning correctly.

**/
EFI_STATUS
EFIAPI
GetControlBits (
  IN EFI_SERIAL_IO_PROTOCOL  *This,
  OUT UINT32                 *Control
  )
{
  USB_SER_DEV  *UsbSerialDevice;
  EFI_STATUS   Status;

  UsbSerialDevice = USB_SER_DEV_FROM_THIS (This);

  *Control        = 0;

  Status = GetControlBitsInternal (UsbSerialDevice, Control);

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

/**
  Reads data from a serial device.

  @param  This[in]                   Protocol instance pointer.
  @param  BufferSize[in, out]        On input, the size of the Buffer. On output,
                                     the amount of data returned in Buffer.
  @param  Buffer[out]                The buffer to return the data into.

  @retval EFI_SUCCESS                The data was read.
  @retval EFI_DEVICE_ERROR           The device reported an error.
  @retval EFI_TIMEOUT                The data write was stopped due to a timeout.

**/
EFI_STATUS
EFIAPI
ReadSerialIo (
  IN EFI_SERIAL_IO_PROTOCOL  *This,
  IN OUT UINTN               *BufferSize,
  OUT VOID                   *Buffer
  )
{
  UINTN        Index;
  UINTN        RemainingCallerBufferSize;
  USB_SER_DEV  *UsbSerialDevice;
  EFI_STATUS   Status;


  if (*BufferSize == 0) {
    return EFI_SUCCESS;
  }

  if (Buffer == NULL) {
    return EFI_DEVICE_ERROR;
  }

  Status          = EFI_SUCCESS;
  UsbSerialDevice = USB_SER_DEV_FROM_THIS (This);

  //
  // Clear out any data that we already have in our internal buffer
  //
  for (Index = 0; Index < *BufferSize; Index++) {
    if (UsbSerialDevice->DataBufferHead == UsbSerialDevice->DataBufferTail) {
      break;
    }

    //
    // Still have characters in the buffer to return
    //
    ((UINT8 *)Buffer)[Index] = UsbSerialDevice->DataBuffer[UsbSerialDevice->DataBufferHead];
    UsbSerialDevice->DataBufferHead = (UsbSerialDevice->DataBufferHead + 1) % SW_FIFO_DEPTH;
  }

  //
  // If we haven't filled the caller's buffer using data that we already had on
  // hand We need to generate an additional USB request to try and fill the
  // caller's buffer
  //
  if (Index != *BufferSize) {
    RemainingCallerBufferSize = *BufferSize - Index;
    Status = ReadDataFromUsb (
               UsbSerialDevice,
               &RemainingCallerBufferSize,
               (VOID *)(((CHAR8 *)Buffer) + Index)
               );
    if (!EFI_ERROR (Status)) {
      *BufferSize = RemainingCallerBufferSize + Index;
    } else {
      *BufferSize = Index;
    }
  }

  if (UsbSerialDevice->DataBufferHead == UsbSerialDevice->DataBufferTail) {
    //
    // Data buffer has no data, set the EFI_SERIAL_INPUT_BUFFER_EMPTY flag
    //
    UsbSerialDevice->ControlBits |= EFI_SERIAL_INPUT_BUFFER_EMPTY;
  } else {
    //
    // There is some leftover data, clear EFI_SERIAL_INPUT_BUFFER_EMPTY flag
    //
    UsbSerialDevice->ControlBits &= ~(EFI_SERIAL_INPUT_BUFFER_EMPTY);
  }
  return Status;
}

/**
  Writes data to a serial device.

  @param  This[in]                   Protocol instance pointer.
  @param  BufferSize[in, out]        On input, the size of the Buffer. On output,
                                     the amount of data actually written.
  @param  Buffer[in]                 The buffer of data to write

  @retval EFI_SUCCESS                The data was written.
  @retval EFI_DEVICE_ERROR           The device reported an error.
  @retval EFI_TIMEOUT                The data write was stopped due to a timeout.

**/
EFI_STATUS
EFIAPI
WriteSerialIo (
  IN EFI_SERIAL_IO_PROTOCOL  *This,
  IN OUT UINTN               *BufferSize,
  IN VOID                    *Buffer
  )
{
  EFI_STATUS   Status;
  USB_SER_DEV  *UsbSerialDevice;
  EFI_TPL      Tpl;

  UsbSerialDevice = USB_SER_DEV_FROM_THIS (This);

  if (UsbSerialDevice->Shutdown) {
    return EFI_DEVICE_ERROR;
  }

  Tpl = gBS->RaiseTPL (TPL_NOTIFY);

  Status = UsbSerialDataTransfer (
             UsbSerialDevice,
             EfiUsbDataOut,
             Buffer,
             BufferSize,
             FTDI_TIMEOUT
             );

  gBS->RestoreTPL (Tpl);
  if (EFI_ERROR (Status)) {
    if (Status == EFI_TIMEOUT){
      return Status;
    } else {
      return EFI_DEVICE_ERROR;
    }
  }

  return EFI_SUCCESS;
}
