/** @file
This file contains the implementation of Usb Hc Protocol.

Copyright (c) 2013-2015 Intel Corporation.

SPDX-License-Identifier: BSD-2-Clause-Patent

**/


#include "OhcPeim.h"

/**
  Submits control transfer to a target USB device.

  @param  PeiServices            The pointer of EFI_PEI_SERVICES.
  @param  This                   The pointer of PEI_USB_HOST_CONTROLLER_PPI.
  @param  DeviceAddress          The target device address.
  @param  DeviceSpeed            Target device speed.
  @param  MaximumPacketLength    Maximum packet size the default control transfer
                                 endpoint is capable of sending or receiving.
  @param  Request                USB device request to send.
  @param  TransferDirection      Specifies the data direction for the data stage.
  @param  Data                   Data buffer to be transmitted or received from USB device.
  @param  DataLength             The size (in bytes) of the data buffer.
  @param  TimeOut                Indicates the maximum timeout, in millisecond.
  @param  TransferResult         Return the result of this control transfer.

  @retval EFI_SUCCESS            Transfer was completed successfully.
  @retval EFI_OUT_OF_RESOURCES   The transfer failed due to lack of resources.
  @retval EFI_INVALID_PARAMETER  Some parameters are invalid.
  @retval EFI_TIMEOUT            Transfer failed due to timeout.
  @retval EFI_DEVICE_ERROR       Transfer failed due to host controller or device error.

**/
EFI_STATUS
EFIAPI
OhciControlTransfer (
  IN  EFI_PEI_SERVICES             **PeiServices,
  IN  PEI_USB_HOST_CONTROLLER_PPI  *This,
  IN  UINT8                        DeviceAddress,
  IN  UINT8                        DeviceSpeed,
  IN  UINT8                        MaxPacketLength,
  IN  EFI_USB_DEVICE_REQUEST       *Request,
  IN  EFI_USB_DATA_DIRECTION       TransferDirection,
  IN  OUT VOID                     *Data,
  IN  OUT UINTN                    *DataLength,
  IN  UINTN                        TimeOut,
  OUT UINT32                       *TransferResult
  )
{
  USB_OHCI_HC_DEV               *Ohc;
  ED_DESCRIPTOR                 *Ed;
  TD_DESCRIPTOR                 *HeadTd;
  TD_DESCRIPTOR                 *SetupTd;
  TD_DESCRIPTOR                 *DataTd;
  TD_DESCRIPTOR                 *StatusTd;
  TD_DESCRIPTOR                 *EmptyTd;
  EFI_STATUS                    Status;
  UINT32                        DataPidDir;
  UINT32                        StatusPidDir;
  UINTN                         TimeCount;
  UINT32                        ErrorCode;

  UINTN                         ActualSendLength;
  UINTN                         LeftLength;
  UINT8                         DataToggle;

  EFI_PHYSICAL_ADDRESS          ReqMapPhyAddr = 0;

  UINTN                         DataMapLength = 0;
  EFI_PHYSICAL_ADDRESS          DataMapPhyAddr = 0;

  HeadTd = NULL;
  DataTd = NULL;

  if ((TransferDirection != EfiUsbDataOut && TransferDirection != EfiUsbDataIn &&
       TransferDirection != EfiUsbNoData) ||
      Request == NULL || DataLength == NULL || TransferResult == NULL ||
      (TransferDirection == EfiUsbNoData && (*DataLength != 0 || Data != NULL)) ||
      (TransferDirection != EfiUsbNoData && (*DataLength == 0 || Data == NULL)) ||
      (DeviceSpeed != EFI_USB_SPEED_LOW && DeviceSpeed != EFI_USB_SPEED_FULL) ||
      (MaxPacketLength != 8 && MaxPacketLength != 16 &&
       MaxPacketLength != 32 && MaxPacketLength != 64)) {
    DEBUG ((EFI_D_INFO, "OhciControlTransfer: EFI_INVALID_PARAMETER\n"));
    return EFI_INVALID_PARAMETER;
  }

  if (*DataLength > MAX_BYTES_PER_TD) {
    DEBUG ((EFI_D_ERROR, "OhciControlTransfer: Request data size is too large\n"));
    return EFI_INVALID_PARAMETER;
  }

  Ohc = PEI_RECOVERY_USB_OHC_DEV_FROM_EHCI_THIS(This);

  if (TransferDirection == EfiUsbDataIn) {
    DataPidDir = TD_IN_PID;
    StatusPidDir = TD_OUT_PID;
  } else {
    DataPidDir = TD_OUT_PID;
    StatusPidDir = TD_IN_PID;
  }

  OhciSetHcControl (Ohc, CONTROL_ENABLE, 0);
  if (OhciGetHcControl (Ohc, CONTROL_ENABLE) != 0) {
    MicroSecondDelay (HC_1_MILLISECOND);
    if (OhciGetHcControl (Ohc, CONTROL_ENABLE) != 0) {
      *TransferResult = EFI_USB_ERR_SYSTEM;
      DEBUG ((EFI_D_INFO, "OhciControlTransfer: Fail to disable CONTROL transfer\n"));
      return EFI_DEVICE_ERROR;
    }
  }
  OhciSetMemoryPointer (Ohc, HC_CONTROL_HEAD, NULL);
  Ed = OhciCreateED (Ohc);
  if (Ed == NULL) {
    DEBUG ((EFI_D_INFO, "OhciControlTransfer: Fail to allocate ED buffer\n"));
    return EFI_OUT_OF_RESOURCES;
  }
  OhciSetEDField (Ed, ED_SKIP, 1);
  OhciSetEDField (Ed, ED_FUNC_ADD, DeviceAddress);
  OhciSetEDField (Ed, ED_ENDPT_NUM, 0);
  OhciSetEDField (Ed, ED_DIR, ED_FROM_TD_DIR);
  OhciSetEDField (Ed, ED_SPEED, DeviceSpeed);
  OhciSetEDField (Ed, ED_FORMAT | ED_HALTED | ED_DTTOGGLE, 0);
  OhciSetEDField (Ed, ED_MAX_PACKET, MaxPacketLength);
  OhciSetEDField (Ed, ED_PDATA, 0);
  OhciSetEDField (Ed, ED_ZERO, 0);
  OhciSetEDField (Ed, ED_TDHEAD_PTR, (UINT32) NULL);
  OhciSetEDField (Ed, ED_TDTAIL_PTR, (UINT32) NULL);
  OhciSetEDField (Ed, ED_NEXT_EDPTR, (UINT32) NULL);
  OhciAttachEDToList (Ohc, CONTROL_LIST, Ed, NULL);
  //
  // Setup Stage
  //
  if(Request != NULL) {
    ReqMapPhyAddr = (EFI_PHYSICAL_ADDRESS)(UINTN)Request;
  }
  SetupTd = OhciCreateTD (Ohc);
  if (SetupTd == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    DEBUG ((EFI_D_INFO, "OhciControlTransfer: Fail to allocate Setup TD buffer\n"));
    goto FREE_ED_BUFF;
  }
  HeadTd = SetupTd;
  OhciSetTDField (SetupTd, TD_PDATA, 0);
  OhciSetTDField (SetupTd, TD_BUFFER_ROUND, 1);
  OhciSetTDField (SetupTd, TD_DIR_PID, TD_SETUP_PID);
  OhciSetTDField (SetupTd, TD_DELAY_INT, TD_NO_DELAY);
  OhciSetTDField (SetupTd, TD_DT_TOGGLE, 2);
  OhciSetTDField (SetupTd, TD_ERROR_CNT, 0);
  OhciSetTDField (SetupTd, TD_COND_CODE, TD_TOBE_PROCESSED);
  OhciSetTDField (SetupTd, TD_CURR_BUFFER_PTR, (UINTN)ReqMapPhyAddr);
  OhciSetTDField (SetupTd, TD_NEXT_PTR, (UINT32) NULL);
  OhciSetTDField (SetupTd, TD_BUFFER_END_PTR, (UINTN)ReqMapPhyAddr + sizeof (EFI_USB_DEVICE_REQUEST) - 1);
  SetupTd->ActualSendLength = 0;
  SetupTd->DataBuffer = NULL;
  SetupTd->NextTDPointer = NULL;

  DataMapLength = *DataLength;
  if ((Data != NULL) && (DataMapLength != 0)) {
    DataMapPhyAddr = (EFI_PHYSICAL_ADDRESS)(UINTN)Data;
  }
  //
  //Data Stage
  //
  LeftLength = DataMapLength;
  ActualSendLength = DataMapLength;
  DataToggle = 1;
  while (LeftLength > 0) {
    ActualSendLength = LeftLength;
    if (LeftLength > MaxPacketLength) {
      ActualSendLength = MaxPacketLength;
    }
    DataTd = OhciCreateTD (Ohc);
    if (DataTd == NULL) {
      DEBUG ((EFI_D_INFO, "OhciControlTransfer: Fail to allocate Data TD buffer\n"));
      Status = EFI_OUT_OF_RESOURCES;
      goto FREE_TD_BUFF;
    }
    OhciSetTDField (DataTd, TD_PDATA, 0);
    OhciSetTDField (DataTd, TD_BUFFER_ROUND, 1);
    OhciSetTDField (DataTd, TD_DIR_PID, DataPidDir);
    OhciSetTDField (DataTd, TD_DELAY_INT, TD_NO_DELAY);
    OhciSetTDField (DataTd, TD_DT_TOGGLE, DataToggle);
    OhciSetTDField (DataTd, TD_ERROR_CNT, 0);
    OhciSetTDField (DataTd, TD_COND_CODE, TD_TOBE_PROCESSED);
    OhciSetTDField (DataTd, TD_CURR_BUFFER_PTR, (UINT32) DataMapPhyAddr);
    OhciSetTDField (DataTd, TD_BUFFER_END_PTR, (UINT32) DataMapPhyAddr + ActualSendLength - 1);
    OhciSetTDField (DataTd, TD_NEXT_PTR, (UINT32) NULL);
    DataTd->ActualSendLength = ActualSendLength;
    DataTd->DataBuffer = (UINT8 *)(UINTN)DataMapPhyAddr;
    DataTd->NextTDPointer = 0;
    OhciLinkTD (HeadTd, DataTd);
    DataToggle ^= 1;
    DataMapPhyAddr += ActualSendLength;
    LeftLength -= ActualSendLength;
  }
  //
  // Status Stage
  //
  StatusTd = OhciCreateTD (Ohc);
  if (StatusTd == NULL) {
    DEBUG ((EFI_D_INFO, "OhciControlTransfer: Fail to allocate Status TD buffer\n"));
    Status = EFI_OUT_OF_RESOURCES;
    goto FREE_TD_BUFF;
  }
  OhciSetTDField (StatusTd, TD_PDATA, 0);
  OhciSetTDField (StatusTd, TD_BUFFER_ROUND, 1);
  OhciSetTDField (StatusTd, TD_DIR_PID, StatusPidDir);
  OhciSetTDField (StatusTd, TD_DELAY_INT, 7);
  OhciSetTDField (StatusTd, TD_DT_TOGGLE, 3);
  OhciSetTDField (StatusTd, TD_ERROR_CNT, 0);
  OhciSetTDField (StatusTd, TD_COND_CODE, TD_TOBE_PROCESSED);
  OhciSetTDField (StatusTd, TD_CURR_BUFFER_PTR, (UINT32) NULL);
  OhciSetTDField (StatusTd, TD_NEXT_PTR, (UINT32) NULL);
  OhciSetTDField (StatusTd, TD_BUFFER_END_PTR, (UINT32) NULL);
  StatusTd->ActualSendLength = 0;
  StatusTd->DataBuffer = NULL;
  StatusTd->NextTDPointer = NULL;
  OhciLinkTD (HeadTd, StatusTd);
  //
  // Empty Stage
  //
  EmptyTd = OhciCreateTD (Ohc);
  if (EmptyTd == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    DEBUG ((EFI_D_INFO, "OhciControlTransfer: Fail to allocate Empty TD buffer\n"));
    goto FREE_TD_BUFF;
  }
  OhciSetTDField (EmptyTd, TD_PDATA, 0);
  OhciSetTDField (EmptyTd, TD_BUFFER_ROUND, 0);
  OhciSetTDField (EmptyTd, TD_DIR_PID, 0);
  OhciSetTDField (EmptyTd, TD_DELAY_INT, 0);
  //OhciSetTDField (EmptyTd, TD_DT_TOGGLE, CurrentToggle);
  EmptyTd->Word0.DataToggle = 0;
  OhciSetTDField (EmptyTd, TD_ERROR_CNT, 0);
  OhciSetTDField (EmptyTd, TD_COND_CODE, 0);
  OhciSetTDField (EmptyTd, TD_CURR_BUFFER_PTR, 0);
  OhciSetTDField (EmptyTd, TD_BUFFER_END_PTR, 0);
  OhciSetTDField (EmptyTd, TD_NEXT_PTR, 0);
  EmptyTd->ActualSendLength = 0;
  EmptyTd->DataBuffer = NULL;
  EmptyTd->NextTDPointer = NULL;
  OhciLinkTD (HeadTd, EmptyTd);
  Ed->TdTailPointer = EmptyTd;
  OhciAttachTDListToED (Ed, HeadTd);
  //
  OhciSetEDField (Ed, ED_SKIP, 0);
  MicroSecondDelay (20 * HC_1_MILLISECOND);
  OhciSetHcCommandStatus (Ohc, CONTROL_LIST_FILLED, 1);
  OhciSetHcControl (Ohc, CONTROL_ENABLE, 1);
  MicroSecondDelay (20 * HC_1_MILLISECOND);
  if (OhciGetHcControl (Ohc, CONTROL_ENABLE) != 1) {
  MicroSecondDelay (HC_1_MILLISECOND);
    if (OhciGetHcControl (Ohc, CONTROL_ENABLE) != 1) {
      *TransferResult = EFI_USB_ERR_SYSTEM;
      Status = EFI_DEVICE_ERROR;
      DEBUG ((EFI_D_INFO, "OhciControlTransfer: Fail to enable CONTROL transfer\n"));
      goto FREE_TD_BUFF;
    }
  }

  TimeCount = 0;
  Status = CheckIfDone (Ohc, CONTROL_LIST, Ed, HeadTd, &ErrorCode);

  while (Status == EFI_NOT_READY && TimeCount <= TimeOut) {
    MicroSecondDelay (HC_1_MILLISECOND);
    TimeCount++;
    Status = CheckIfDone (Ohc, CONTROL_LIST, Ed, HeadTd, &ErrorCode);
  }
  //
  *TransferResult = ConvertErrorCode (ErrorCode);

  if (ErrorCode != TD_NO_ERROR) {
    if (ErrorCode == TD_TOBE_PROCESSED) {
      DEBUG ((EFI_D_INFO, "Control pipe timeout, > %d mS\r\n", TimeOut));
    } else {
      DEBUG ((EFI_D_INFO, "Control pipe broken\r\n"));
    }

    *DataLength = 0;
  }

  OhciSetHcControl (Ohc, CONTROL_ENABLE, 0);
  if (OhciGetHcControl (Ohc, CONTROL_ENABLE) != 0) {
  MicroSecondDelay (HC_1_MILLISECOND);
    if (OhciGetHcControl (Ohc, CONTROL_ENABLE) != 0) {
      *TransferResult = EFI_USB_ERR_SYSTEM;
      DEBUG ((EFI_D_INFO, "OhciControlTransfer: Cannot disable CONTROL_ENABLE transfer\n"));
      goto FREE_TD_BUFF;
    }
  }

FREE_TD_BUFF:
  while (HeadTd) {
    DataTd = HeadTd;
    HeadTd = HeadTd->NextTDPointer;
    UsbHcFreeMem(Ohc->MemPool, DataTd, sizeof(TD_DESCRIPTOR));
  }

FREE_ED_BUFF:
  UsbHcFreeMem(Ohc->MemPool, Ed, sizeof(ED_DESCRIPTOR));

  return Status;
}

/**
  Submits bulk transfer to a bulk endpoint of a USB device.

  @param  PeiServices           The pointer of EFI_PEI_SERVICES.
  @param  This                  The pointer of PEI_USB_HOST_CONTROLLER_PPI.
  @param  DeviceAddress         Target device address.
  @param  EndPointAddress       Endpoint number and its direction in bit 7.
  @param  MaxiPacketLength      Maximum packet size the endpoint is capable of
                                sending or receiving.
  @param  Data                  A pointers to the buffers of data to transmit
                                from or receive into.
  @param  DataLength            The lenght of the data buffer.
  @param  DataToggle            On input, the initial data toggle for the transfer;
                                On output, it is updated to to next data toggle to use of
                                the subsequent bulk transfer.
  @param  TimeOut               Indicates the maximum time, in millisecond, which the
                                transfer is allowed to complete.
  @param  TransferResult        A pointer to the detailed result information of the
                                bulk transfer.

  @retval EFI_SUCCESS           The transfer was completed successfully.
  @retval EFI_OUT_OF_RESOURCES  The transfer failed due to lack of resource.
  @retval EFI_INVALID_PARAMETER Parameters are invalid.
  @retval EFI_TIMEOUT           The transfer failed due to timeout.
  @retval EFI_DEVICE_ERROR      The transfer failed due to host controller error.

**/
EFI_STATUS
EFIAPI
OhciBulkTransfer (
  IN EFI_PEI_SERVICES             **PeiServices,
  IN PEI_USB_HOST_CONTROLLER_PPI  *This,
  IN  UINT8                       DeviceAddress,
  IN  UINT8                       EndPointAddress,
  IN  UINT8                       MaxPacketLength,
  IN  OUT VOID                    *Data,
  IN  OUT UINTN                   *DataLength,
  IN  OUT UINT8                   *DataToggle,
  IN  UINTN                       TimeOut,
  OUT UINT32                      *TransferResult
  )
{
  USB_OHCI_HC_DEV                *Ohc;
  ED_DESCRIPTOR                  *Ed;
  UINT32                         DataPidDir;
  TD_DESCRIPTOR                  *HeadTd;
  TD_DESCRIPTOR                  *DataTd;
  TD_DESCRIPTOR                  *EmptyTd;
  EFI_STATUS                     Status;
  UINT8                          EndPointNum;
  UINTN                          TimeCount;
  UINT32                         ErrorCode;

  UINT8                          CurrentToggle;
  UINTN                          MapLength;
  EFI_PHYSICAL_ADDRESS           MapPyhAddr;
  UINTN                          LeftLength;
  UINTN                          ActualSendLength;
  BOOLEAN                        FirstTD;

  MapLength = 0;
  MapPyhAddr = 0;
  LeftLength = 0;
  Status = EFI_SUCCESS;

  if (Data == NULL || DataLength == NULL || DataToggle == NULL || TransferResult == NULL ||
      *DataLength == 0 || (*DataToggle != 0 && *DataToggle != 1) ||
      (MaxPacketLength != 8 && MaxPacketLength != 16 &&
       MaxPacketLength != 32 && MaxPacketLength != 64)) {
    return EFI_INVALID_PARAMETER;
  }

  Ohc = PEI_RECOVERY_USB_OHC_DEV_FROM_EHCI_THIS (This);

  if ((EndPointAddress & 0x80) != 0) {
    DataPidDir = TD_IN_PID;
  } else {
    DataPidDir = TD_OUT_PID;
  }

  EndPointNum = (EndPointAddress & 0xF);

  OhciSetHcControl (Ohc, BULK_ENABLE, 0);
  if (OhciGetHcControl (Ohc, BULK_ENABLE) != 0) {
    MicroSecondDelay (HC_1_MILLISECOND);
    if (OhciGetHcControl (Ohc, BULK_ENABLE) != 0) {
      *TransferResult = EFI_USB_ERR_SYSTEM;
      return EFI_DEVICE_ERROR;
    }
  }

  OhciSetMemoryPointer (Ohc, HC_BULK_HEAD, NULL);

  Ed = OhciCreateED (Ohc);
  if (Ed == NULL) {
    DEBUG ((EFI_D_INFO, "OhcBulkTransfer: Fail to allocate ED buffer\r\n"));
    return EFI_OUT_OF_RESOURCES;
  }
  OhciSetEDField (Ed, ED_SKIP, 1);
  OhciSetEDField (Ed, ED_FUNC_ADD, DeviceAddress);
  OhciSetEDField (Ed, ED_ENDPT_NUM, EndPointNum);
  OhciSetEDField (Ed, ED_DIR, ED_FROM_TD_DIR);
  OhciSetEDField (Ed, ED_SPEED, HI_SPEED);
  OhciSetEDField (Ed, ED_FORMAT | ED_HALTED | ED_DTTOGGLE, 0);
  OhciSetEDField (Ed, ED_MAX_PACKET, MaxPacketLength);
  OhciSetEDField (Ed, ED_PDATA, 0);
  OhciSetEDField (Ed, ED_ZERO, 0);
  OhciSetEDField (Ed, ED_TDHEAD_PTR, (UINT32) NULL);
  OhciSetEDField (Ed, ED_TDTAIL_PTR, (UINT32) NULL);
  OhciSetEDField (Ed, ED_NEXT_EDPTR, (UINT32) NULL);
  OhciAttachEDToList (Ohc, BULK_LIST, Ed, NULL);

  if(Data != NULL) {
    MapLength = *DataLength;
    MapPyhAddr = (EFI_PHYSICAL_ADDRESS)(UINTN)Data;
  }
  //
  //Data Stage
  //
  LeftLength = MapLength;
  ActualSendLength = MapLength;
  CurrentToggle = *DataToggle;
  HeadTd = NULL;
  FirstTD = TRUE;
  while (LeftLength > 0) {
    ActualSendLength = LeftLength;
    if (LeftLength > MaxPacketLength) {
      ActualSendLength = MaxPacketLength;
    }
    DataTd = OhciCreateTD (Ohc);
    if (DataTd == NULL) {
      DEBUG ((EFI_D_INFO, "OhcBulkTransfer: Fail to allocate Data TD buffer\r\n"));
      Status = EFI_OUT_OF_RESOURCES;
      goto FREE_TD_BUFF;
    }
    OhciSetTDField (DataTd, TD_PDATA, 0);
    OhciSetTDField (DataTd, TD_BUFFER_ROUND, 1);
    OhciSetTDField (DataTd, TD_DIR_PID, DataPidDir);
    OhciSetTDField (DataTd, TD_DELAY_INT, TD_NO_DELAY);
    OhciSetTDField (DataTd, TD_DT_TOGGLE, CurrentToggle);
    OhciSetTDField (DataTd, TD_ERROR_CNT, 0);
    OhciSetTDField (DataTd, TD_COND_CODE, TD_TOBE_PROCESSED);
    OhciSetTDField (DataTd, TD_CURR_BUFFER_PTR, (UINT32) MapPyhAddr);
    OhciSetTDField (DataTd, TD_BUFFER_END_PTR, (UINT32) MapPyhAddr + ActualSendLength - 1);
    OhciSetTDField (DataTd, TD_NEXT_PTR, (UINT32) NULL);
    DataTd->ActualSendLength = ActualSendLength;
    DataTd->DataBuffer = (UINT8 *)(UINTN)MapPyhAddr;
    DataTd->NextTDPointer = 0;
    if (FirstTD) {
      HeadTd = DataTd;
      FirstTD = FALSE;
    } else {
      OhciLinkTD (HeadTd, DataTd);
    }
    CurrentToggle ^= 1;
    MapPyhAddr += ActualSendLength;
    LeftLength -= ActualSendLength;
  }
  //
  // Empty Stage
  //
  EmptyTd = OhciCreateTD (Ohc);
  if (EmptyTd == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
      DEBUG ((EFI_D_INFO, "OhcBulkTransfer: Fail to allocate Empty TD buffer\r\n"));
    goto FREE_TD_BUFF;
  }
  OhciSetTDField (EmptyTd, TD_PDATA, 0);
  OhciSetTDField (EmptyTd, TD_BUFFER_ROUND, 0);
  OhciSetTDField (EmptyTd, TD_DIR_PID, 0);
  OhciSetTDField (EmptyTd, TD_DELAY_INT, 0);
  //OhciSetTDField (EmptyTd, TD_DT_TOGGLE, CurrentToggle);
  EmptyTd->Word0.DataToggle = 0;
  OhciSetTDField (EmptyTd, TD_ERROR_CNT, 0);
  OhciSetTDField (EmptyTd, TD_COND_CODE, 0);
  OhciSetTDField (EmptyTd, TD_CURR_BUFFER_PTR, 0);
  OhciSetTDField (EmptyTd, TD_BUFFER_END_PTR, 0);
  OhciSetTDField (EmptyTd, TD_NEXT_PTR, 0);
  EmptyTd->ActualSendLength = 0;
  EmptyTd->DataBuffer = NULL;
  EmptyTd->NextTDPointer = NULL;
  OhciLinkTD (HeadTd, EmptyTd);
  Ed->TdTailPointer = EmptyTd;
  OhciAttachTDListToED (Ed, HeadTd);

  OhciSetEDField (Ed, ED_SKIP, 0);
  OhciSetHcCommandStatus (Ohc, BULK_LIST_FILLED, 1);
  OhciSetHcControl (Ohc, BULK_ENABLE, 1);
  if (OhciGetHcControl (Ohc, BULK_ENABLE) != 1) {
    MicroSecondDelay (HC_1_MILLISECOND);
    if (OhciGetHcControl (Ohc, BULK_ENABLE) != 1) {
      *TransferResult = EFI_USB_ERR_SYSTEM;
      goto FREE_TD_BUFF;
    }
  }

  TimeCount = 0;
  Status = CheckIfDone (Ohc, BULK_LIST, Ed, HeadTd, &ErrorCode);

  while (Status == EFI_NOT_READY && TimeCount <= TimeOut) {
    MicroSecondDelay (HC_1_MILLISECOND);
    TimeCount++;
    Status = CheckIfDone (Ohc, BULK_LIST, Ed, HeadTd, &ErrorCode);
  }

  *TransferResult = ConvertErrorCode (ErrorCode);

  if (ErrorCode != TD_NO_ERROR) {
    if (ErrorCode == TD_TOBE_PROCESSED) {
      DEBUG ((EFI_D_INFO, "Bulk pipe timeout, > %d mS\r\n", TimeOut));
    } else {
      DEBUG ((EFI_D_INFO, "Bulk pipe broken\r\n"));
    }
    *DataLength = 0;
  }
    *DataToggle = (UINT8) OhciGetEDField (Ed, ED_DTTOGGLE);

FREE_TD_BUFF:
  while (HeadTd) {
    DataTd = HeadTd;
    HeadTd = HeadTd->NextTDPointer;
    UsbHcFreeMem(Ohc->MemPool, DataTd, sizeof(TD_DESCRIPTOR));
  }
  UsbHcFreeMem(Ohc->MemPool, Ed, sizeof(ED_DESCRIPTOR));

  return Status;
}
/**
  Retrieves the number of root hub ports.

  @param[in]  PeiServices       The pointer to the PEI Services Table.
  @param[in]  This              The pointer to this instance of the
                                PEI_USB_HOST_CONTROLLER_PPI.
  @param[out] NumOfPorts        The pointer to the number of the root hub ports.

  @retval EFI_SUCCESS           The port number was retrieved successfully.
  @retval EFI_INVALID_PARAMETER PortNumber is NULL.

**/

EFI_STATUS
EFIAPI
OhciGetRootHubNumOfPorts (
  IN EFI_PEI_SERVICES             **PeiServices,
  IN PEI_USB_HOST_CONTROLLER_PPI  *This,
  OUT UINT8                       *NumOfPorts
  )
{
  USB_OHCI_HC_DEV                *Ohc;
  if (NumOfPorts == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  Ohc = PEI_RECOVERY_USB_OHC_DEV_FROM_EHCI_THIS (This);
  *NumOfPorts = (UINT8)OhciGetRootHubDescriptor(Ohc, RH_NUM_DS_PORTS);

  return EFI_SUCCESS;
}
/**
  Retrieves the current status of a USB root hub port.

  @param  PeiServices            The pointer of EFI_PEI_SERVICES.
  @param  This                   The pointer of PEI_USB_HOST_CONTROLLER_PPI.
  @param  PortNumber             The root hub port to retrieve the state from.
  @param  PortStatus             Variable to receive the port state.

  @retval EFI_SUCCESS            The status of the USB root hub port specified.
                                 by PortNumber was returned in PortStatus.
  @retval EFI_INVALID_PARAMETER  PortNumber is invalid.

**/

EFI_STATUS
EFIAPI
OhciGetRootHubPortStatus (
  IN  EFI_PEI_SERVICES             **PeiServices,
  IN  PEI_USB_HOST_CONTROLLER_PPI  *This,
  IN  UINT8                        PortNumber,
  OUT EFI_USB_PORT_STATUS          *PortStatus
  )
{
  USB_OHCI_HC_DEV  *Ohc;
  UINT8            NumOfPorts;

  Ohc = PEI_RECOVERY_USB_OHC_DEV_FROM_EHCI_THIS (This);

  OhciGetRootHubNumOfPorts (PeiServices, This, &NumOfPorts);
  if (PortNumber >= NumOfPorts) {
    return EFI_INVALID_PARAMETER;
  }
  PortStatus->PortStatus = 0;
  PortStatus->PortChangeStatus = 0;

  if (OhciReadRootHubPortStatus (Ohc,PortNumber, RH_CURR_CONNECT_STAT)) {
    PortStatus->PortStatus |= USB_PORT_STAT_CONNECTION;
  }
  if (OhciReadRootHubPortStatus (Ohc,PortNumber, RH_PORT_ENABLE_STAT)) {
    PortStatus->PortStatus |= USB_PORT_STAT_ENABLE;
  }
  if (OhciReadRootHubPortStatus (Ohc,PortNumber, RH_PORT_SUSPEND_STAT)) {
    PortStatus->PortStatus |= USB_PORT_STAT_SUSPEND;
  }
  if (OhciReadRootHubPortStatus (Ohc,PortNumber, RH_PORT_OC_INDICATOR)) {
    PortStatus->PortStatus |= USB_PORT_STAT_OVERCURRENT;
  }
  if (OhciReadRootHubPortStatus (Ohc,PortNumber, RH_PORT_RESET_STAT)) {
    PortStatus->PortStatus |= USB_PORT_STAT_RESET;
  }
  if (OhciReadRootHubPortStatus (Ohc,PortNumber, RH_PORT_POWER_STAT)) {
    PortStatus->PortStatus |= USB_PORT_STAT_POWER;
  }
  if (OhciReadRootHubPortStatus (Ohc,PortNumber, RH_LSDEVICE_ATTACHED)) {
    PortStatus->PortStatus |= USB_PORT_STAT_LOW_SPEED;
  }
  if (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_ENABLE_STAT_CHANGE)) {
    PortStatus->PortChangeStatus |= USB_PORT_STAT_C_ENABLE;
  }
  if (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_CONNECT_STATUS_CHANGE)) {
    PortStatus->PortChangeStatus |= USB_PORT_STAT_C_CONNECTION;
  }
  if (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_SUSPEND_STAT_CHANGE)) {
    PortStatus->PortChangeStatus |= USB_PORT_STAT_C_SUSPEND;
  }
  if (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_OC_INDICATOR_CHANGE)) {
    PortStatus->PortChangeStatus |= USB_PORT_STAT_C_OVERCURRENT;
  }
  if (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_RESET_STAT_CHANGE)) {
    PortStatus->PortChangeStatus |= USB_PORT_STAT_C_RESET;
  }

  return EFI_SUCCESS;
}
/**
  Sets a feature for the specified root hub port.

  @param  PeiServices           The pointer of EFI_PEI_SERVICES
  @param  This                  The pointer of PEI_USB_HOST_CONTROLLER_PPI
  @param  PortNumber            Root hub port to set.
  @param  PortFeature           Feature to set.

  @retval EFI_SUCCESS            The feature specified by PortFeature was set.
  @retval EFI_INVALID_PARAMETER  PortNumber is invalid or PortFeature is invalid.
  @retval EFI_TIMEOUT            The time out occurred.

**/

EFI_STATUS
EFIAPI
OhciSetRootHubPortFeature (
  IN EFI_PEI_SERVICES             **PeiServices,
  IN PEI_USB_HOST_CONTROLLER_PPI  *This,
  IN UINT8                        PortNumber,
  IN EFI_USB_PORT_FEATURE         PortFeature
  )
{
  USB_OHCI_HC_DEV         *Ohc;
  EFI_STATUS              Status;
  UINT8                   NumOfPorts;
  UINTN                   RetryTimes;

  OhciGetRootHubNumOfPorts (PeiServices, This, &NumOfPorts);
  if (PortNumber >= NumOfPorts) {
    return EFI_INVALID_PARAMETER;
  }

  Ohc = PEI_RECOVERY_USB_OHC_DEV_FROM_EHCI_THIS (This);

  Status = EFI_SUCCESS;


  switch (PortFeature) {
    case EfiUsbPortPower:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_SET_PORT_POWER);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);
        RetryTimes++;
      } while (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_POWER_STAT) == 0 &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }
      break;

    case EfiUsbPortReset:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_SET_PORT_RESET);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);
        RetryTimes++;
      } while ((OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_RESET_STAT_CHANGE) == 0 ||
                OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_RESET_STAT) == 1) &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }

      OhciSetRootHubPortStatus (Ohc, PortNumber, RH_PORT_RESET_STAT_CHANGE);
      break;

    case EfiUsbPortEnable:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_SET_PORT_ENABLE);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);;
        RetryTimes++;
      } while (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_ENABLE_STAT) == 0 &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }
      break;


    case EfiUsbPortSuspend:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_SET_PORT_SUSPEND);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);;
        RetryTimes++;
      } while (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_SUSPEND_STAT) == 0 &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }
      break;

    default:
      return EFI_INVALID_PARAMETER;
  }

  return Status;
}

/**
  Clears a feature for the specified root hub port.

  @param  PeiServices           The pointer of EFI_PEI_SERVICES.
  @param  This                  The pointer of PEI_USB_HOST_CONTROLLER_PPI.
  @param  PortNumber            Specifies the root hub port whose feature
                                is requested to be cleared.
  @param  PortFeature           Indicates the feature selector associated with the
                                feature clear request.

  @retval EFI_SUCCESS            The feature specified by PortFeature was cleared
                                 for the USB root hub port specified by PortNumber.
  @retval EFI_INVALID_PARAMETER  PortNumber is invalid or PortFeature is invalid.

**/

EFI_STATUS
EFIAPI
OhciClearRootHubPortFeature (
  IN EFI_PEI_SERVICES             **PeiServices,
  IN PEI_USB_HOST_CONTROLLER_PPI  *This,
  IN UINT8                        PortNumber,
  IN EFI_USB_PORT_FEATURE         PortFeature
  )
{
  USB_OHCI_HC_DEV         *Ohc;
  EFI_STATUS              Status;
  UINT8                   NumOfPorts;
  UINTN                   RetryTimes;


  OhciGetRootHubNumOfPorts (PeiServices, This, &NumOfPorts);
  if (PortNumber >= NumOfPorts) {
    return EFI_INVALID_PARAMETER;
  }

  Ohc = PEI_RECOVERY_USB_OHC_DEV_FROM_EHCI_THIS (This);

  Status = EFI_SUCCESS;

  switch (PortFeature) {
    case EfiUsbPortEnable:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_CLEAR_PORT_ENABLE);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);
        RetryTimes++;
      } while (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_ENABLE_STAT) == 1 &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }
      break;

    case EfiUsbPortSuspend:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_CLEAR_SUSPEND_STATUS);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);
        RetryTimes++;
      } while (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_SUSPEND_STAT) == 1 &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }
      break;

    case EfiUsbPortReset:
      break;

    case EfiUsbPortPower:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_CLEAR_PORT_POWER);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);
        RetryTimes++;
      } while (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_POWER_STAT) == 1 &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }
      break;

    case EfiUsbPortConnectChange:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_CONNECT_STATUS_CHANGE);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);
        RetryTimes++;
      } while (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_CONNECT_STATUS_CHANGE) == 1 &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }
      break;

    case EfiUsbPortResetChange:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_PORT_RESET_STAT_CHANGE);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);
        RetryTimes++;
      } while (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_RESET_STAT_CHANGE) == 1 &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }
      break;


    case EfiUsbPortEnableChange:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_PORT_ENABLE_STAT_CHANGE);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);
        RetryTimes++;
      } while (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_ENABLE_STAT_CHANGE) == 1 &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }
      break;

    case EfiUsbPortSuspendChange:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_PORT_SUSPEND_STAT_CHANGE);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);
        RetryTimes++;
      } while (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_PORT_SUSPEND_STAT_CHANGE) == 1 &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }
      break;

    case EfiUsbPortOverCurrentChange:
      Status = OhciSetRootHubPortStatus (Ohc, PortNumber, RH_OC_INDICATOR_CHANGE);

      //
      // Verify the state
      //
      RetryTimes = 0;
      do {
        MicroSecondDelay (HC_1_MILLISECOND);
        RetryTimes++;
      } while (OhciReadRootHubPortStatus (Ohc, PortNumber, RH_OC_INDICATOR_CHANGE) == 1 &&
               RetryTimes < MAX_RETRY_TIMES);

      if (RetryTimes >= MAX_RETRY_TIMES) {
        return EFI_DEVICE_ERROR;
      }
      break;

    default:
      return EFI_INVALID_PARAMETER;
  }

  return Status;
}
/**
  Provides software reset for the USB host controller.

  @param  This                  This EFI_USB_HC_PROTOCOL instance.
  @param  Attributes            A bit mask of the reset operation to perform.

  @retval EFI_SUCCESS           The reset operation succeeded.
  @retval EFI_INVALID_PARAMETER Attributes is not valid.
  @retval EFI_UNSUPPOURTED      The type of reset specified by Attributes is
                                not currently supported by the host controller.
  @retval EFI_DEVICE_ERROR      Host controller isn't halted to reset.

**/
EFI_STATUS
InitializeUsbHC (
  IN EFI_PEI_SERVICES           **PeiServices,
  IN USB_OHCI_HC_DEV            *Ohc,
  IN UINT16                     Attributes
  )
{
  EFI_STATUS              Status;
  UINT8                   Index;
  UINT8                   NumOfPorts;
  UINT32                  PowerOnGoodTime;
  UINT32                  Data32;
  BOOLEAN                 Flag = FALSE;

  if ((Attributes & ~(EFI_USB_HC_RESET_GLOBAL | EFI_USB_HC_RESET_HOST_CONTROLLER)) != 0) {
    return EFI_INVALID_PARAMETER;
  }
  Status = EFI_SUCCESS;

  if ((Attributes & EFI_USB_HC_RESET_HOST_CONTROLLER) != 0) {
    MicroSecondDelay (50 * HC_1_MILLISECOND);
    Status = OhciSetHcCommandStatus (Ohc, HC_RESET, HC_RESET);
    if (EFI_ERROR (Status)) {
      return EFI_DEVICE_ERROR;
    }
    MicroSecondDelay (50 * HC_1_MILLISECOND);
    //
    // Wait for host controller reset.
    //
    PowerOnGoodTime = 50;
    do {
      MicroSecondDelay (HC_1_MILLISECOND);
      Data32 = OhciGetOperationalReg (Ohc, HC_COMMAND_STATUS );
      if ((Data32 & HC_RESET) == 0) {
        Flag = TRUE;
        break;
      }
    }while(PowerOnGoodTime--);
    if (!Flag){
      return EFI_DEVICE_ERROR;
    }
  }

  OhciSetFrameInterval (Ohc, FRAME_INTERVAL, 0x2edf);
  if ((Attributes &  EFI_USB_HC_RESET_GLOBAL) != 0) {
    Status = OhciSetHcControl (Ohc, HC_FUNCTIONAL_STATE, HC_STATE_RESET);
    if (EFI_ERROR (Status)) {
      return EFI_DEVICE_ERROR;
    }
    MicroSecondDelay (50 * HC_1_MILLISECOND);
  }
  //
  // Initialize host controller operational registers
  //
  OhciSetFrameInterval (Ohc, FS_LARGEST_DATA_PACKET, 0x2778);
  OhciSetFrameInterval (Ohc, FRAME_INTERVAL, 0x2edf);
  OhciSetPeriodicStart (Ohc, 0x2a2f);
  OhciSetHcControl (Ohc, CONTROL_BULK_RATIO, 0x0);
  OhciSetHcCommandStatus (Ohc, CONTROL_LIST_FILLED | BULK_LIST_FILLED, 0);
  OhciSetRootHubDescriptor (Ohc, RH_PSWITCH_MODE, 0);
  OhciSetRootHubDescriptor (Ohc, RH_NO_PSWITCH | RH_NOC_PROT, 1);
  //OhciSetRootHubDescriptor (Hc, RH_PSWITCH_MODE | RH_NO_PSWITCH, 0);
  //OhciSetRootHubDescriptor (Hc, RH_PSWITCH_MODE | RH_NOC_PROT, 1);

  OhciSetRootHubDescriptor (Ohc, RH_DEV_REMOVABLE, 0);
  OhciSetRootHubDescriptor (Ohc, RH_PORT_PWR_CTRL_MASK, 0xffff);
  OhciSetRootHubStatus (Ohc, RH_LOCAL_PSTAT_CHANGE);
  OhciSetRootHubPortStatus (Ohc, 0, RH_SET_PORT_POWER);
  OhciGetRootHubNumOfPorts (PeiServices, &Ohc->UsbHostControllerPpi, &NumOfPorts);
  for (Index = 0; Index < NumOfPorts; Index++) {
    if (!EFI_ERROR (OhciSetRootHubPortFeature (PeiServices, &Ohc->UsbHostControllerPpi, Index, EfiUsbPortReset))) {
      MicroSecondDelay (200 * HC_1_MILLISECOND);
      OhciClearRootHubPortFeature (PeiServices, &Ohc->UsbHostControllerPpi, Index, EfiUsbPortReset);
      MicroSecondDelay (HC_1_MILLISECOND);
      OhciSetRootHubPortFeature (PeiServices, &Ohc->UsbHostControllerPpi, Index, EfiUsbPortEnable);
      MicroSecondDelay (HC_1_MILLISECOND);
    }
  }

  Ohc->MemPool = UsbHcInitMemPool(TRUE, 0);
  if(Ohc->MemPool == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  OhciSetMemoryPointer (Ohc, HC_CONTROL_HEAD, NULL);
  OhciSetMemoryPointer (Ohc, HC_BULK_HEAD, NULL);
  OhciSetHcControl (Ohc, CONTROL_ENABLE | BULK_ENABLE, 1);
  OhciSetHcControl (Ohc, HC_FUNCTIONAL_STATE, HC_STATE_OPERATIONAL);
  MicroSecondDelay (50 * HC_1_MILLISECOND);
  //
  // Wait till first SOF occurs, and then clear it
  //
  while (OhciGetHcInterruptStatus (Ohc, START_OF_FRAME) == 0);
  OhciClearInterruptStatus (Ohc, START_OF_FRAME);
  MicroSecondDelay (HC_1_MILLISECOND);

  return EFI_SUCCESS;
}

/**
  Submits control transfer to a target USB device.

  Calls underlying OhciControlTransfer to do work. This wrapper routine required
  on Quark so that USB DMA transfers do not cause an IMR violation.

  @param  PeiServices            The pointer of EFI_PEI_SERVICES.
  @param  This                   The pointer of PEI_USB_HOST_CONTROLLER_PPI.
  @param  DeviceAddress          The target device address.
  @param  DeviceSpeed            Target device speed.
  @param  MaximumPacketLength    Maximum packet size the default control transfer
                                 endpoint is capable of sending or receiving.
  @param  Request                USB device request to send.
  @param  TransferDirection      Specifies the data direction for the data stage.
  @param  Data                   Data buffer to be transmitted or received from USB device.
  @param  DataLength             The size (in bytes) of the data buffer.
  @param  TimeOut                Indicates the maximum timeout, in millisecond.
  @param  TransferResult         Return the result of this control transfer.

  @retval EFI_SUCCESS            Transfer was completed successfully.
  @retval EFI_OUT_OF_RESOURCES   The transfer failed due to lack of resources.
  @retval EFI_INVALID_PARAMETER  Some parameters are invalid.
  @retval EFI_TIMEOUT            Transfer failed due to timeout.
  @retval EFI_DEVICE_ERROR       Transfer failed due to host controller or device error.

**/
EFI_STATUS
EFIAPI
RedirectOhciControlTransfer (
  IN  EFI_PEI_SERVICES             **PeiServices,
  IN  PEI_USB_HOST_CONTROLLER_PPI  *This,
  IN  UINT8                        DeviceAddress,
  IN  UINT8                        DeviceSpeed,
  IN  UINT8                        MaxPacketLength,
  IN  EFI_USB_DEVICE_REQUEST       *Request,
  IN  EFI_USB_DATA_DIRECTION       TransferDirection,
  IN  OUT VOID                     *Data,
  IN  OUT UINTN                    *DataLength,
  IN  UINTN                        TimeOut,
  OUT UINT32                       *TransferResult
  )
{
  EFI_STATUS              Status;
  EFI_USB_DEVICE_REQUEST  *NewRequest;
  VOID                    *NewData;
  UINT8                   *Alloc;

  //
  // Allocate memory external to IMR protected region for transfer data.
  //
  Status = PeiServicesAllocatePool (
                             sizeof(EFI_USB_DEVICE_REQUEST) + *DataLength,
                             (VOID **) &Alloc
                             );
  ASSERT_EFI_ERROR (Status);

  //
  // Setup pointers to transfer buffers.
  //
  NewRequest = (EFI_USB_DEVICE_REQUEST *) Alloc;
  Alloc += sizeof(EFI_USB_DEVICE_REQUEST);
  NewData = (VOID *) Alloc;

  //
  // Copy callers request packet into transfer request packet.
  //
  if (Request != NULL) {
    CopyMem (NewRequest,Request,sizeof(EFI_USB_DEVICE_REQUEST));
  } else {
    NewRequest = NULL;
  }
  //
  // Copy callers data into transfer data buffer.
  //
  if (Data != NULL) {
    if (DataLength > 0) {
      CopyMem (NewData,Data,*DataLength);
    }
  } else {
    NewData = NULL;
  }

  //
  // Call underlying OhciControlTransfer to do work.
  //
  Status = OhciControlTransfer (
             PeiServices,
             This,
             DeviceAddress,
             DeviceSpeed,
             MaxPacketLength,
             NewRequest,
             TransferDirection,
             NewData,
             DataLength,
             TimeOut,
             TransferResult
             );

  //
  // Copy transfer buffer back into callers buffer.
  //
  if (Data != NULL && *DataLength > 0) {
    CopyMem (Data, NewData, *DataLength);
  }

  return Status;
}

/**
  Submits bulk transfer to a bulk endpoint of a USB device.

  Calls underlying OhciBulkTransfer to do work. This wrapper routine required
  on Quark so that USB DMA transfers do not cause an IMR violation.

  @param  PeiServices           The pointer of EFI_PEI_SERVICES.
  @param  This                  The pointer of PEI_USB_HOST_CONTROLLER_PPI.
  @param  DeviceAddress         Target device address.
  @param  EndPointAddress       Endpoint number and its direction in bit 7.
  @param  MaxiPacketLength      Maximum packet size the endpoint is capable of
                                sending or receiving.
  @param  Data                  A pointers to the buffers of data to transmit
                                from or receive into.
  @param  DataLength            The lenght of the data buffer.
  @param  DataToggle            On input, the initial data toggle for the transfer;
                                On output, it is updated to to next data toggle to use of
                                the subsequent bulk transfer.
  @param  TimeOut               Indicates the maximum time, in millisecond, which the
                                transfer is allowed to complete.
  @param  TransferResult        A pointer to the detailed result information of the
                                bulk transfer.

  @retval EFI_SUCCESS           The transfer was completed successfully.
  @retval EFI_OUT_OF_RESOURCES  The transfer failed due to lack of resource.
  @retval EFI_INVALID_PARAMETER Parameters are invalid.
  @retval EFI_TIMEOUT           The transfer failed due to timeout.
  @retval EFI_DEVICE_ERROR      The transfer failed due to host controller error.

**/
EFI_STATUS
EFIAPI
RedirectOhciBulkTransfer (
  IN EFI_PEI_SERVICES             **PeiServices,
  IN PEI_USB_HOST_CONTROLLER_PPI  *This,
  IN  UINT8                       DeviceAddress,
  IN  UINT8                       EndPointAddress,
  IN  UINT8                       MaxPacketLength,
  IN  OUT VOID                    *Data,
  IN  OUT UINTN                   *DataLength,
  IN  OUT UINT8                   *DataToggle,
  IN  UINTN                       TimeOut,
  OUT UINT32                      *TransferResult
  )
{
  EFI_STATUS              Status;
  UINT8                   *NewData;

  //
  // Allocate memory external to IMR protected region for transfer data.
  //
  Status = PeiServicesAllocatePool (
                             *DataLength,
                             (VOID **) &NewData
                             );
  ASSERT_EFI_ERROR (Status);

  //
  // Copy callers data into transfer buffer.
  //
  if (Data != NULL) {
    if (DataLength > 0) {
      CopyMem (NewData,Data,*DataLength);
    }
  } else {
    NewData = NULL;
  }

  //
  // Call underlying OhciBulkTransfer to do work.
  //
  Status = OhciBulkTransfer (
             PeiServices,
             This,
             DeviceAddress,
             EndPointAddress,
             MaxPacketLength,
             NewData,
             DataLength,
             DataToggle,
             TimeOut,
             TransferResult
             );

  //
  // Copy transfer buffer back into callers buffer.
  //
  if (Data != NULL && *DataLength > 0) {
    CopyMem (Data, NewData, *DataLength);
  }

  return Status;
}

/**
  @param  FileHandle  Handle of the file being invoked.
  @param  PeiServices Describes the list of possible PEI Services.

  @retval EFI_SUCCESS            PPI successfully installed.

**/
EFI_STATUS
OhcPeimEntry (
  IN EFI_PEI_FILE_HANDLE        FileHandle,
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{

  PEI_USB_CONTROLLER_PPI  *ChipSetUsbControllerPpi;
  EFI_STATUS              Status;
  UINT8                   Index;
  UINTN                   ControllerType;
  UINTN                   BaseAddress;
  UINTN                   MemPages;
  USB_OHCI_HC_DEV         *Ohc;
  EFI_PHYSICAL_ADDRESS    TempPtr;


  //
  // Shadow this PEIM to run from memory
  //
  if (!EFI_ERROR (PeiServicesRegisterForShadow (FileHandle))) {
    return EFI_SUCCESS;
  }
  Status = PeiServicesLocatePpi (
             &gPeiUsbControllerPpiGuid,
             0,
             NULL,
             (VOID **) &ChipSetUsbControllerPpi
             );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  Index = 0;
  while (TRUE) {
    Status = ChipSetUsbControllerPpi->GetUsbController (
                                        (EFI_PEI_SERVICES **) PeiServices,
                                        ChipSetUsbControllerPpi,
                                        Index,
                                        &ControllerType,
                                        &BaseAddress
                                        );
    //
    // When status is error, meant no controller is found
    //
    if (EFI_ERROR (Status)) {
      break;
    }
    //
    // This PEIM is for OHC type controller.
    //
    if (ControllerType != PEI_OHCI_CONTROLLER) {
      Index++;
      continue;
    }

    MemPages = sizeof (USB_OHCI_HC_DEV) / PAGESIZE + 1;
    Status = PeiServicesAllocatePages (
               EfiBootServicesCode,
               MemPages,
               &TempPtr
               );
    if (EFI_ERROR (Status)) {
      DEBUG ((EFI_D_INFO, "OhcPeimEntry: Fail to allocate buffer for the %dth OHCI ControllerPpi\n", Index));
      return EFI_OUT_OF_RESOURCES;
    }
    ZeroMem((VOID *)(UINTN)TempPtr, MemPages*PAGESIZE);
    Ohc = (USB_OHCI_HC_DEV *) ((UINTN) TempPtr);

    Ohc->Signature = USB_OHCI_HC_DEV_SIGNATURE;

    Ohc->UsbHostControllerBaseAddress = (UINT32) BaseAddress;

    //
    // Initialize Uhc's hardware
    //
    Status = InitializeUsbHC (
               (EFI_PEI_SERVICES **)PeiServices,
               Ohc,
               EFI_USB_HC_RESET_GLOBAL
               );
    if (EFI_ERROR (Status)) {
      DEBUG ((EFI_D_INFO, "OhcPeimEntry: Fail to init %dth OHCI ControllerPpi\n", Index));
      return Status;
    }
    //
    // Control & Bulk transfer services are accessed via their Redirect
    // routine versions on Quark so that USB DMA transfers do not cause an
    // IMR violation.
    //
    Ohc->UsbHostControllerPpi.ControlTransfer          = RedirectOhciControlTransfer;
    Ohc->UsbHostControllerPpi.BulkTransfer             = RedirectOhciBulkTransfer;
    Ohc->UsbHostControllerPpi.GetRootHubPortNumber     = OhciGetRootHubNumOfPorts;
    Ohc->UsbHostControllerPpi.GetRootHubPortStatus     = OhciGetRootHubPortStatus;
    Ohc->UsbHostControllerPpi.SetRootHubPortFeature    = OhciSetRootHubPortFeature;
    Ohc->UsbHostControllerPpi.ClearRootHubPortFeature  = OhciClearRootHubPortFeature;

    Ohc->PpiDescriptor.Flags = (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST);
    Ohc->PpiDescriptor.Guid  = &gPeiUsbHostControllerPpiGuid;
    Ohc->PpiDescriptor.Ppi   = &Ohc->UsbHostControllerPpi;

    Status = PeiServicesInstallPpi (&Ohc->PpiDescriptor);
    if (EFI_ERROR (Status)) {
      Index++;
      continue;
    }
    Index++;
  }
  return EFI_SUCCESS;
}

