/** @file
PEIM to produce gPeiUsb2HostControllerPpiGuid based on gPeiUsbControllerPpiGuid
which is used to enable recovery function from USB Drivers.

Copyright (c) 2014 - 2018, Intel Corporation. All rights reserved.<BR>

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

**/

#include "XhcPeim.h"

//
// Two arrays used to translate the XHCI port state (change)
// to the UEFI protocol's port state (change).
//
USB_PORT_STATE_MAP  mUsbPortStateMap[] = {
  {XHC_PORTSC_CCS,   USB_PORT_STAT_CONNECTION},
  {XHC_PORTSC_PED,   USB_PORT_STAT_ENABLE},
  {XHC_PORTSC_OCA,   USB_PORT_STAT_OVERCURRENT},
  {XHC_PORTSC_PP,    USB_PORT_STAT_POWER},
  {XHC_PORTSC_RESET, USB_PORT_STAT_RESET}
};

USB_PORT_STATE_MAP  mUsbPortChangeMap[] = {
  {XHC_PORTSC_CSC, USB_PORT_STAT_C_CONNECTION},
  {XHC_PORTSC_PEC, USB_PORT_STAT_C_ENABLE},
  {XHC_PORTSC_OCC, USB_PORT_STAT_C_OVERCURRENT},
  {XHC_PORTSC_PRC, USB_PORT_STAT_C_RESET}
};

USB_CLEAR_PORT_MAP mUsbClearPortChangeMap[] = {
  {XHC_PORTSC_CSC, EfiUsbPortConnectChange},
  {XHC_PORTSC_PEC, EfiUsbPortEnableChange},
  {XHC_PORTSC_OCC, EfiUsbPortOverCurrentChange},
  {XHC_PORTSC_PRC, EfiUsbPortResetChange}
};

USB_PORT_STATE_MAP  mUsbHubPortStateMap[] = {
  {XHC_HUB_PORTSC_CCS,   USB_PORT_STAT_CONNECTION},
  {XHC_HUB_PORTSC_PED,   USB_PORT_STAT_ENABLE},
  {XHC_HUB_PORTSC_OCA,   USB_PORT_STAT_OVERCURRENT},
  {XHC_HUB_PORTSC_PP,    USB_PORT_STAT_POWER},
  {XHC_HUB_PORTSC_RESET, USB_PORT_STAT_RESET}
};

USB_PORT_STATE_MAP  mUsbHubPortChangeMap[] = {
  {XHC_HUB_PORTSC_CSC, USB_PORT_STAT_C_CONNECTION},
  {XHC_HUB_PORTSC_PEC, USB_PORT_STAT_C_ENABLE},
  {XHC_HUB_PORTSC_OCC, USB_PORT_STAT_C_OVERCURRENT},
  {XHC_HUB_PORTSC_PRC, USB_PORT_STAT_C_RESET}
};

USB_CLEAR_PORT_MAP mUsbHubClearPortChangeMap[] = {
  {XHC_HUB_PORTSC_CSC, EfiUsbPortConnectChange},
  {XHC_HUB_PORTSC_PEC, EfiUsbPortEnableChange},
  {XHC_HUB_PORTSC_OCC, EfiUsbPortOverCurrentChange},
  {XHC_HUB_PORTSC_PRC, EfiUsbPortResetChange},
  {XHC_HUB_PORTSC_BHRC, Usb3PortBHPortResetChange}
};

/**
  Read XHCI Operation register.

  @param Xhc            The XHCI device.
  @param Offset         The operation register offset.

  @retval the register content read.

**/
UINT32
XhcPeiReadOpReg (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Offset
  )
{
  UINT32                Data;

  ASSERT (Xhc->CapLength != 0);

  Data = MmioRead32 (Xhc->UsbHostControllerBaseAddress + Xhc->CapLength + Offset);
  return Data;
}

/**
  Write the data to the XHCI operation register.

  @param Xhc            The XHCI device.
  @param Offset         The operation register offset.
  @param Data           The data to write.

**/
VOID
XhcPeiWriteOpReg (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Offset,
  IN UINT32             Data
  )
{
  ASSERT (Xhc->CapLength != 0);

  MmioWrite32 (Xhc->UsbHostControllerBaseAddress + Xhc->CapLength + Offset, Data);
}

/**
  Set one bit of the operational register while keeping other bits.

  @param  Xhc           The XHCI device.
  @param  Offset        The offset of the operational register.
  @param  Bit           The bit mask of the register to set.

**/
VOID
XhcPeiSetOpRegBit (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Offset,
  IN UINT32             Bit
  )
{
  UINT32                Data;

  Data  = XhcPeiReadOpReg (Xhc, Offset);
  Data |= Bit;
  XhcPeiWriteOpReg (Xhc, Offset, Data);
}

/**
  Clear one bit of the operational register while keeping other bits.

  @param  Xhc           The XHCI device.
  @param  Offset        The offset of the operational register.
  @param  Bit           The bit mask of the register to clear.

**/
VOID
XhcPeiClearOpRegBit (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Offset,
  IN UINT32             Bit
  )
{
  UINT32                Data;

  Data  = XhcPeiReadOpReg (Xhc, Offset);
  Data &= ~Bit;
  XhcPeiWriteOpReg (Xhc, Offset, Data);
}

/**
  Wait the operation register's bit as specified by Bit
  to become set (or clear).

  @param  Xhc           The XHCI device.
  @param  Offset        The offset of the operational register.
  @param  Bit           The bit mask of the register to wait for.
  @param  WaitToSet     Wait the bit to set or clear.
  @param  Timeout       The time to wait before abort (in millisecond, ms).

  @retval EFI_SUCCESS   The bit successfully changed by host controller.
  @retval EFI_TIMEOUT   The time out occurred.

**/
EFI_STATUS
XhcPeiWaitOpRegBit (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Offset,
  IN UINT32             Bit,
  IN BOOLEAN            WaitToSet,
  IN UINT32             Timeout
  )
{
  UINT64                Index;

  for (Index = 0; Index < Timeout * XHC_1_MILLISECOND; Index++) {
    if (XHC_REG_BIT_IS_SET (Xhc, Offset, Bit) == WaitToSet) {
      return EFI_SUCCESS;
    }

    MicroSecondDelay (XHC_1_MICROSECOND);
  }

  return EFI_TIMEOUT;
}

/**
  Read XHCI capability register.

  @param Xhc        The XHCI device.
  @param Offset     Capability register address.

  @retval the register content read.

**/
UINT32
XhcPeiReadCapRegister (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Offset
  )
{
  UINT32                Data;

  Data = MmioRead32 (Xhc->UsbHostControllerBaseAddress + Offset);

  return Data;
}



/**
  Write the data to the XHCI door bell register.

  @param  Xhc           The XHCI device.
  @param  Offset        The offset of the door bell register.
  @param  Data          The data to write.

**/
VOID
XhcPeiWriteDoorBellReg (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Offset,
  IN UINT32             Data
  )
{
  ASSERT (Xhc->DBOff != 0);

  MmioWrite32 (Xhc->UsbHostControllerBaseAddress + Xhc->DBOff + Offset, Data);
}

/**
  Read XHCI runtime register.

  @param  Xhc           The XHCI device.
  @param  Offset        The offset of the runtime register.

  @return The register content read

**/
UINT32
XhcPeiReadRuntimeReg (
  IN  PEI_XHC_DEV       *Xhc,
  IN  UINT32            Offset
  )
{
  UINT32                Data;

  ASSERT (Xhc->RTSOff != 0);

  Data = MmioRead32 (Xhc->UsbHostControllerBaseAddress + Xhc->RTSOff + Offset);

  return Data;
}

/**
  Write the data to the XHCI runtime register.

  @param  Xhc       The XHCI device.
  @param  Offset    The offset of the runtime register.
  @param  Data      The data to write.

**/
VOID
XhcPeiWriteRuntimeReg (
  IN PEI_XHC_DEV          *Xhc,
  IN UINT32               Offset,
  IN UINT32               Data
  )
{
  ASSERT (Xhc->RTSOff != 0);

  MmioWrite32 (Xhc->UsbHostControllerBaseAddress + Xhc->RTSOff + Offset, Data);
}

/**
  Set one bit of the runtime register while keeping other bits.

  @param  Xhc          The XHCI device.
  @param  Offset       The offset of the runtime register.
  @param  Bit          The bit mask of the register to set.

**/
VOID
XhcPeiSetRuntimeRegBit (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Offset,
  IN UINT32             Bit
  )
{
  UINT32                Data;

  Data  = XhcPeiReadRuntimeReg (Xhc, Offset);
  Data |= Bit;
  XhcPeiWriteRuntimeReg (Xhc, Offset, Data);
}

/**
  Clear one bit of the runtime register while keeping other bits.

  @param  Xhc          The XHCI device.
  @param  Offset       The offset of the runtime register.
  @param  Bit          The bit mask of the register to set.

**/
VOID
XhcPeiClearRuntimeRegBit (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Offset,
  IN UINT32             Bit
  )
{
  UINT32                Data;

  Data  = XhcPeiReadRuntimeReg (Xhc, Offset);
  Data &= ~Bit;
  XhcPeiWriteRuntimeReg (Xhc, Offset, Data);
}

/**
  Check whether Xhc is halted.

  @param  Xhc           The XHCI device.

  @retval TRUE          The controller is halted.
  @retval FALSE         The controller isn't halted.

**/
BOOLEAN
XhcPeiIsHalt (
  IN PEI_XHC_DEV        *Xhc
  )
{
  return XHC_REG_BIT_IS_SET (Xhc, XHC_USBSTS_OFFSET, XHC_USBSTS_HALT);
}

/**
  Check whether system error occurred.

  @param  Xhc           The XHCI device.

  @retval TRUE          System error happened.
  @retval FALSE         No system error.

**/
BOOLEAN
XhcPeiIsSysError (
  IN PEI_XHC_DEV        *Xhc
  )
{
  return XHC_REG_BIT_IS_SET (Xhc, XHC_USBSTS_OFFSET, XHC_USBSTS_HSE);
}

/**
  Reset the host controller.

  @param  Xhc           The XHCI device.
  @param  Timeout       Time to wait before abort (in millisecond, ms).

  @retval EFI_TIMEOUT   The transfer failed due to time out.
  @retval Others        Failed to reset the host.

**/
EFI_STATUS
XhcPeiResetHC (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Timeout
  )
{
  EFI_STATUS            Status;

  //
  // Host can only be reset when it is halt. If not so, halt it
  //
  if (!XhcPeiIsHalt (Xhc)) {
    Status = XhcPeiHaltHC (Xhc, Timeout);

    if (EFI_ERROR (Status)) {
      goto ON_EXIT;
    }
  }

  XhcPeiSetOpRegBit (Xhc, XHC_USBCMD_OFFSET, XHC_USBCMD_RESET);
  //
  // Some XHCI host controllers require to have extra 1ms delay before accessing any MMIO register during reset.
  // Otherwise there may have the timeout case happened.
  // The below is a workaround to solve such problem.
  //
  MicroSecondDelay (1000);
  Status = XhcPeiWaitOpRegBit (Xhc, XHC_USBCMD_OFFSET, XHC_USBCMD_RESET, FALSE, Timeout);
ON_EXIT:
  DEBUG ((EFI_D_INFO, "XhcPeiResetHC: %r\n", Status));
  return Status;
}

/**
  Halt the host controller.

  @param  Xhc           The XHCI device.
  @param  Timeout       Time to wait before abort.

  @retval EFI_TIMEOUT   Failed to halt the controller before Timeout.
  @retval EFI_SUCCESS   The XHCI is halt.

**/
EFI_STATUS
XhcPeiHaltHC (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Timeout
  )
{
  EFI_STATUS            Status;

  XhcPeiClearOpRegBit (Xhc, XHC_USBCMD_OFFSET, XHC_USBCMD_RUN);
  Status = XhcPeiWaitOpRegBit (Xhc, XHC_USBSTS_OFFSET, XHC_USBSTS_HALT, TRUE, Timeout);
  DEBUG ((EFI_D_INFO, "XhcPeiHaltHC: %r\n", Status));
  return Status;
}

/**
  Set the XHCI to run.

  @param  Xhc           The XHCI device.
  @param  Timeout       Time to wait before abort.

  @retval EFI_SUCCESS   The XHCI is running.
  @retval Others        Failed to set the XHCI to run.

**/
EFI_STATUS
XhcPeiRunHC (
  IN PEI_XHC_DEV        *Xhc,
  IN UINT32             Timeout
  )
{
  EFI_STATUS            Status;

  XhcPeiSetOpRegBit (Xhc, XHC_USBCMD_OFFSET, XHC_USBCMD_RUN);
  Status = XhcPeiWaitOpRegBit (Xhc, XHC_USBSTS_OFFSET, XHC_USBSTS_HALT, FALSE, Timeout);
  DEBUG ((EFI_D_INFO, "XhcPeiRunHC: %r\n", Status));
  return Status;
}

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

  @param  PeiServices               The pointer of EFI_PEI_SERVICES.
  @param  This                      The pointer of PEI_USB2_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.
                                    If Timeout is 0, then the caller must wait for the function
                                    to be completed until EFI_SUCCESS or EFI_DEVICE_ERROR is returned.
  @param  Translator                Transaction translator to be used by this device.
  @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
XhcPeiControlTransfer (
  IN EFI_PEI_SERVICES                       **PeiServices,
  IN PEI_USB2_HOST_CONTROLLER_PPI           *This,
  IN UINT8                                  DeviceAddress,
  IN UINT8                                  DeviceSpeed,
  IN UINTN                                  MaximumPacketLength,
  IN EFI_USB_DEVICE_REQUEST                 *Request,
  IN EFI_USB_DATA_DIRECTION                 TransferDirection,
  IN OUT VOID                               *Data,
  IN OUT UINTN                              *DataLength,
  IN UINTN                                  TimeOut,
  IN EFI_USB2_HC_TRANSACTION_TRANSLATOR     *Translator,
  OUT UINT32                                *TransferResult
  )
{
  PEI_XHC_DEV                   *Xhc;
  URB                           *Urb;
  UINT8                         Endpoint;
  UINT8                         Index;
  UINT8                         DescriptorType;
  UINT8                         SlotId;
  UINT8                         TTT;
  UINT8                         MTT;
  UINT32                        MaxPacket0;
  EFI_USB_HUB_DESCRIPTOR        *HubDesc;
  EFI_STATUS                    Status;
  EFI_STATUS                    RecoveryStatus;
  UINTN                         MapSize;
  EFI_USB_PORT_STATUS           PortStatus;
  UINT32                        State;
  EFI_USB_DEVICE_REQUEST        ClearPortRequest;
  UINTN                         Len;

  //
  // Validate parameters
  //
  if ((Request == NULL) || (TransferResult == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((TransferDirection != EfiUsbDataIn) &&
      (TransferDirection != EfiUsbDataOut) &&
      (TransferDirection != EfiUsbNoData)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((TransferDirection == EfiUsbNoData) &&
      ((Data != NULL) || (*DataLength != 0))) {
    return EFI_INVALID_PARAMETER;
  }

  if ((TransferDirection != EfiUsbNoData) &&
     ((Data == NULL) || (*DataLength == 0))) {
    return EFI_INVALID_PARAMETER;
  }

  if ((MaximumPacketLength != 8)  && (MaximumPacketLength != 16) &&
      (MaximumPacketLength != 32) && (MaximumPacketLength != 64) &&
      (MaximumPacketLength != 512)
      ) {
    return EFI_INVALID_PARAMETER;
  }

  if ((DeviceSpeed == EFI_USB_SPEED_LOW) && (MaximumPacketLength != 8)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((DeviceSpeed == EFI_USB_SPEED_SUPER) && (MaximumPacketLength != 512)) {
    return EFI_INVALID_PARAMETER;
  }

  Xhc             = PEI_RECOVERY_USB_XHC_DEV_FROM_THIS (This);

  Status          = EFI_DEVICE_ERROR;
  *TransferResult = EFI_USB_ERR_SYSTEM;
  Len             = 0;

  if (XhcPeiIsHalt (Xhc) || XhcPeiIsSysError (Xhc)) {
    DEBUG ((EFI_D_ERROR, "XhcPeiControlTransfer: HC is halted or has system error\n"));
    goto ON_EXIT;
  }

  //
  // Check if the device is still enabled before every transaction.
  //
  SlotId = XhcPeiBusDevAddrToSlotId (Xhc, DeviceAddress);
  if (SlotId == 0) {
    goto ON_EXIT;
  }

  //
  // Hook the Set_Address request from UsbBus.
  // According to XHCI 1.0 spec, the Set_Address request is replaced by XHCI's Address_Device cmd.
  //
  if ((Request->Request     == USB_REQ_SET_ADDRESS) &&
      (Request->RequestType == USB_REQUEST_TYPE (EfiUsbNoData, USB_REQ_TYPE_STANDARD, USB_TARGET_DEVICE))) {
    //
    // Reset the BusDevAddr field of all disabled entries in UsbDevContext array firstly.
    // This way is used to clean the history to avoid using wrong device address afterwards.
    //
    for (Index = 0; Index < 255; Index++) {
      if (!Xhc->UsbDevContext[Index + 1].Enabled &&
          (Xhc->UsbDevContext[Index + 1].SlotId == 0) &&
          (Xhc->UsbDevContext[Index + 1].BusDevAddr == (UINT8) Request->Value)) {
        Xhc->UsbDevContext[Index + 1].BusDevAddr = 0;
      }
    }

    if (Xhc->UsbDevContext[SlotId].XhciDevAddr == 0) {
      goto ON_EXIT;
    }
    //
    // The actual device address has been assigned by XHCI during initializing the device slot.
    // So we just need establish the mapping relationship between the device address requested from UsbBus
    // and the actual device address assigned by XHCI. The following invocations through EFI_USB2_HC_PROTOCOL interface
    // can find out the actual device address by it.
    //
    Xhc->UsbDevContext[SlotId].BusDevAddr = (UINT8) Request->Value;
    Status = EFI_SUCCESS;
    goto ON_EXIT;
  }

  //
  // Create a new URB, insert it into the asynchronous
  // schedule list, then poll the execution status.
  // Note that we encode the direction in address although default control
  // endpoint is bidirectional. XhcPeiCreateUrb expects this
  // combination of Ep addr and its direction.
  //
  Endpoint = (UINT8) (0 | ((TransferDirection == EfiUsbDataIn) ? 0x80 : 0));
  Urb = XhcPeiCreateUrb (
          Xhc,
          DeviceAddress,
          Endpoint,
          DeviceSpeed,
          MaximumPacketLength,
          XHC_CTRL_TRANSFER,
          Request,
          Data,
          *DataLength,
          NULL,
          NULL
          );

  if (Urb == NULL) {
    DEBUG ((EFI_D_ERROR, "XhcPeiControlTransfer: failed to create URB"));
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  Status = XhcPeiExecTransfer (Xhc, FALSE, Urb, TimeOut);

  //
  // Get the status from URB. The result is updated in XhcPeiCheckUrbResult
  // which is called by XhcPeiExecTransfer
  //
  *TransferResult = Urb->Result;
  *DataLength     = Urb->Completed;

  if (Status == EFI_TIMEOUT) {
    //
    // The transfer timed out. Abort the transfer by dequeueing of the TD.
    //
    RecoveryStatus = XhcPeiDequeueTrbFromEndpoint(Xhc, Urb);
    if (EFI_ERROR(RecoveryStatus)) {
      DEBUG((EFI_D_ERROR, "XhcPeiControlTransfer: XhcPeiDequeueTrbFromEndpoint failed\n"));
    }
    XhcPeiFreeUrb (Xhc, Urb);
    goto ON_EXIT;
  } else {
    if (*TransferResult == EFI_USB_NOERROR) {
      Status = EFI_SUCCESS;
    } else if ((*TransferResult == EFI_USB_ERR_STALL) || (*TransferResult == EFI_USB_ERR_BABBLE)) {
      RecoveryStatus = XhcPeiRecoverHaltedEndpoint(Xhc, Urb);
      if (EFI_ERROR (RecoveryStatus)) {
        DEBUG ((EFI_D_ERROR, "XhcPeiControlTransfer: XhcPeiRecoverHaltedEndpoint failed\n"));
      }
      Status = EFI_DEVICE_ERROR;
      XhcPeiFreeUrb (Xhc, Urb);
      goto ON_EXIT;
    } else {
      XhcPeiFreeUrb (Xhc, Urb);
      goto ON_EXIT;
    }
  }
  //
  // Unmap data before consume.
  //
  XhcPeiFreeUrb (Xhc, Urb);

  //
  // Hook Get_Descriptor request from UsbBus as we need evaluate context and configure endpoint.
  // Hook Get_Status request form UsbBus as we need trace device attach/detach event happened at hub.
  // Hook Set_Config request from UsbBus as we need configure device endpoint.
  //
  if ((Request->Request     == USB_REQ_GET_DESCRIPTOR) &&
      ((Request->RequestType == USB_REQUEST_TYPE (EfiUsbDataIn, USB_REQ_TYPE_STANDARD, USB_TARGET_DEVICE)) ||
      ((Request->RequestType == USB_REQUEST_TYPE (EfiUsbDataIn, USB_REQ_TYPE_CLASS, USB_TARGET_DEVICE))))) {
    DescriptorType = (UINT8) (Request->Value >> 8);
    if ((DescriptorType == USB_DESC_TYPE_DEVICE) && ((*DataLength == sizeof (EFI_USB_DEVICE_DESCRIPTOR)) || ((DeviceSpeed == EFI_USB_SPEED_FULL) && (*DataLength == 8)))) {
      ASSERT (Data != NULL);
      //
      // Store a copy of device scriptor as hub device need this info to configure endpoint.
      //
      CopyMem (&Xhc->UsbDevContext[SlotId].DevDesc, Data, *DataLength);
      if (Xhc->UsbDevContext[SlotId].DevDesc.BcdUSB >= 0x0300) {
        //
        // If it's a usb3.0 device, then its max packet size is a 2^n.
        //
        MaxPacket0 = 1 << Xhc->UsbDevContext[SlotId].DevDesc.MaxPacketSize0;
      } else {
        MaxPacket0 = Xhc->UsbDevContext[SlotId].DevDesc.MaxPacketSize0;
      }
      Xhc->UsbDevContext[SlotId].ConfDesc = AllocateZeroPool (Xhc->UsbDevContext[SlotId].DevDesc.NumConfigurations * sizeof (EFI_USB_CONFIG_DESCRIPTOR *));
      if (Xhc->UsbDevContext[SlotId].ConfDesc == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto ON_EXIT;
      }
      if (Xhc->HcCParams.Data.Csz == 0) {
        Status = XhcPeiEvaluateContext (Xhc, SlotId, MaxPacket0);
      } else {
        Status = XhcPeiEvaluateContext64 (Xhc, SlotId, MaxPacket0);
      }
    } else if (DescriptorType == USB_DESC_TYPE_CONFIG) {
      ASSERT (Data != NULL);
      if (*DataLength == ((UINT16 *) Data)[1]) {
        //
        // Get configuration value from request, store the configuration descriptor for Configure_Endpoint cmd.
        //
        Index = (UINT8) Request->Value;
        ASSERT (Index < Xhc->UsbDevContext[SlotId].DevDesc.NumConfigurations);
        Xhc->UsbDevContext[SlotId].ConfDesc[Index] = AllocateZeroPool (*DataLength);
        if (Xhc->UsbDevContext[SlotId].ConfDesc[Index] == NULL) {
          Status = EFI_OUT_OF_RESOURCES;
          goto ON_EXIT;
        }
        CopyMem (Xhc->UsbDevContext[SlotId].ConfDesc[Index], Data, *DataLength);
      }
    } else if (((DescriptorType == USB_DESC_TYPE_HUB) ||
               (DescriptorType == USB_DESC_TYPE_HUB_SUPER_SPEED)) && (*DataLength > 2)) {
      ASSERT (Data != NULL);
      HubDesc = (EFI_USB_HUB_DESCRIPTOR *) Data;
      ASSERT (HubDesc->NumPorts <= 15);
      //
      // The bit 5,6 of HubCharacter field of Hub Descriptor is TTT.
      //
      TTT = (UINT8) ((HubDesc->HubCharacter & (BIT5 | BIT6)) >> 5);
      if (Xhc->UsbDevContext[SlotId].DevDesc.DeviceProtocol == 2) {
        //
        // Don't support multi-TT feature for super speed hub now.
        //
        MTT = 0;
        DEBUG ((EFI_D_ERROR, "XHCI: Don't support multi-TT feature for Hub now. (force to disable MTT)\n"));
      } else {
        MTT = 0;
      }

      if (Xhc->HcCParams.Data.Csz == 0) {
        Status = XhcPeiConfigHubContext (Xhc, SlotId, HubDesc->NumPorts, TTT, MTT);
      } else {
        Status = XhcPeiConfigHubContext64 (Xhc, SlotId, HubDesc->NumPorts, TTT, MTT);
      }
    }
  } else if ((Request->Request     == USB_REQ_SET_CONFIG) &&
             (Request->RequestType == USB_REQUEST_TYPE (EfiUsbNoData, USB_REQ_TYPE_STANDARD, USB_TARGET_DEVICE))) {
    //
    // Hook Set_Config request from UsbBus as we need configure device endpoint.
    //
    for (Index = 0; Index < Xhc->UsbDevContext[SlotId].DevDesc.NumConfigurations; Index++) {
      if (Xhc->UsbDevContext[SlotId].ConfDesc[Index]->ConfigurationValue == (UINT8)Request->Value) {
        if (Xhc->HcCParams.Data.Csz == 0) {
          Status = XhcPeiSetConfigCmd (Xhc, SlotId, DeviceSpeed, Xhc->UsbDevContext[SlotId].ConfDesc[Index]);
        } else {
          Status = XhcPeiSetConfigCmd64 (Xhc, SlotId, DeviceSpeed, Xhc->UsbDevContext[SlotId].ConfDesc[Index]);
        }
        break;
      }
    }
  } else if ((Request->Request     == USB_REQ_GET_STATUS) &&
             (Request->RequestType == USB_REQUEST_TYPE (EfiUsbDataIn, USB_REQ_TYPE_CLASS, USB_TARGET_OTHER))) {
    ASSERT (Data != NULL);
    //
    // Hook Get_Status request from UsbBus to keep track of the port status change.
    //
    State                       = *(UINT32 *) Data;
    PortStatus.PortStatus       = 0;
    PortStatus.PortChangeStatus = 0;

    if (DeviceSpeed == EFI_USB_SPEED_SUPER) {
      //
      // For super speed hub, its bit10~12 presents the attached device speed.
      //
      if ((State & XHC_PORTSC_PS) >> 10 == 0) {
        PortStatus.PortStatus |= USB_PORT_STAT_SUPER_SPEED;
      }
    } else {
      //
      // For high or full/low speed hub, its bit9~10 presents the attached device speed.
      //
      if (XHC_BIT_IS_SET (State, BIT9)) {
        PortStatus.PortStatus |= USB_PORT_STAT_LOW_SPEED;
      } else if (XHC_BIT_IS_SET (State, BIT10)) {
        PortStatus.PortStatus |= USB_PORT_STAT_HIGH_SPEED;
      }
    }

    //
    // Convert the XHCI port/port change state to UEFI status
    //
    MapSize = sizeof (mUsbHubPortStateMap) / sizeof (USB_PORT_STATE_MAP);
    for (Index = 0; Index < MapSize; Index++) {
      if (XHC_BIT_IS_SET (State, mUsbHubPortStateMap[Index].HwState)) {
        PortStatus.PortStatus = (UINT16) (PortStatus.PortStatus | mUsbHubPortStateMap[Index].UefiState);
      }
    }

    MapSize = sizeof (mUsbHubPortChangeMap) / sizeof (USB_PORT_STATE_MAP);
    for (Index = 0; Index < MapSize; Index++) {
      if (XHC_BIT_IS_SET (State, mUsbHubPortChangeMap[Index].HwState)) {
        PortStatus.PortChangeStatus = (UINT16) (PortStatus.PortChangeStatus | mUsbHubPortChangeMap[Index].UefiState);
      }
    }

    MapSize = sizeof (mUsbHubClearPortChangeMap) / sizeof (USB_CLEAR_PORT_MAP);

    for (Index = 0; Index < MapSize; Index++) {
      if (XHC_BIT_IS_SET (State, mUsbHubClearPortChangeMap[Index].HwState)) {
        ZeroMem (&ClearPortRequest, sizeof (EFI_USB_DEVICE_REQUEST));
        ClearPortRequest.RequestType  = USB_REQUEST_TYPE (EfiUsbNoData, USB_REQ_TYPE_CLASS, USB_TARGET_OTHER);
        ClearPortRequest.Request      = (UINT8) USB_REQ_CLEAR_FEATURE;
        ClearPortRequest.Value        = mUsbHubClearPortChangeMap[Index].Selector;
        ClearPortRequest.Index        = Request->Index;
        ClearPortRequest.Length       = 0;

        XhcPeiControlTransfer (
          PeiServices,
          This,
          DeviceAddress,
          DeviceSpeed,
          MaximumPacketLength,
          &ClearPortRequest,
          EfiUsbNoData,
          NULL,
          &Len,
          TimeOut,
          Translator,
          TransferResult
          );
      }
    }

    XhcPeiPollPortStatusChange (Xhc, Xhc->UsbDevContext[SlotId].RouteString, (UINT8)Request->Index, &PortStatus);

    *(UINT32 *) Data = *(UINT32 *) &PortStatus;
  }

ON_EXIT:

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "XhcPeiControlTransfer: error - %r, transfer - %x\n", Status, *TransferResult));
  }

  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_USB2_HOST_CONTROLLER_PPI.
  @param  DeviceAddress         Target device address.
  @param  EndPointAddress       Endpoint number and its direction in bit 7.
  @param  DeviceSpeed           Device speed, Low speed device doesn't support
                                bulk transfer.
  @param  MaximumPacketLength   Maximum packet size the endpoint is capable of
                                sending or receiving.
  @param  Data                  Array of 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.
                                If Timeout is 0, then the caller must wait for the function
                                to be completed until EFI_SUCCESS or EFI_DEVICE_ERROR is returned.
  @param  Translator            A pointr to the transaction translator data.
  @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
XhcPeiBulkTransfer (
  IN EFI_PEI_SERVICES                       **PeiServices,
  IN PEI_USB2_HOST_CONTROLLER_PPI           *This,
  IN UINT8                                  DeviceAddress,
  IN UINT8                                  EndPointAddress,
  IN UINT8                                  DeviceSpeed,
  IN UINTN                                  MaximumPacketLength,
  IN OUT VOID                               *Data[EFI_USB_MAX_BULK_BUFFER_NUM],
  IN OUT UINTN                              *DataLength,
  IN OUT UINT8                              *DataToggle,
  IN UINTN                                  TimeOut,
  IN EFI_USB2_HC_TRANSACTION_TRANSLATOR     *Translator,
  OUT UINT32                                *TransferResult
  )
{
  PEI_XHC_DEV                   *Xhc;
  URB                           *Urb;
  UINT8                         SlotId;
  EFI_STATUS                    Status;
  EFI_STATUS                    RecoveryStatus;

  //
  // Validate the parameters
  //
  if ((DataLength == NULL) || (*DataLength == 0) ||
      (Data == NULL) || (Data[0] == NULL) || (TransferResult == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((*DataToggle != 0) && (*DataToggle != 1)) {
    return EFI_INVALID_PARAMETER;
  }

  if ((DeviceSpeed == EFI_USB_SPEED_LOW) ||
      ((DeviceSpeed == EFI_USB_SPEED_FULL) && (MaximumPacketLength > 64)) ||
      ((DeviceSpeed == EFI_USB_SPEED_HIGH) && (MaximumPacketLength > 512)) ||
      ((DeviceSpeed == EFI_USB_SPEED_SUPER) && (MaximumPacketLength > 1024))) {
    return EFI_INVALID_PARAMETER;
  }

  Xhc             = PEI_RECOVERY_USB_XHC_DEV_FROM_THIS (This);

  *TransferResult = EFI_USB_ERR_SYSTEM;
  Status          = EFI_DEVICE_ERROR;

  if (XhcPeiIsHalt (Xhc) || XhcPeiIsSysError (Xhc)) {
    DEBUG ((EFI_D_ERROR, "XhcPeiBulkTransfer: HC is halted or has system error\n"));
    goto ON_EXIT;
  }

  //
  // Check if the device is still enabled before every transaction.
  //
  SlotId = XhcPeiBusDevAddrToSlotId (Xhc, DeviceAddress);
  if (SlotId == 0) {
    goto ON_EXIT;
  }

  //
  // Create a new URB, insert it into the asynchronous
  // schedule list, then poll the execution status.
  //
  Urb = XhcPeiCreateUrb (
          Xhc,
          DeviceAddress,
          EndPointAddress,
          DeviceSpeed,
          MaximumPacketLength,
          XHC_BULK_TRANSFER,
          NULL,
          Data[0],
          *DataLength,
          NULL,
          NULL
          );

  if (Urb == NULL) {
    DEBUG ((EFI_D_ERROR, "XhcPeiBulkTransfer: failed to create URB\n"));
    Status = EFI_OUT_OF_RESOURCES;
    goto ON_EXIT;
  }

  Status = XhcPeiExecTransfer (Xhc, FALSE, Urb, TimeOut);

  *TransferResult = Urb->Result;
  *DataLength     = Urb->Completed;

  if (Status == EFI_TIMEOUT) {
    //
    // The transfer timed out. Abort the transfer by dequeueing of the TD.
    //
    RecoveryStatus = XhcPeiDequeueTrbFromEndpoint(Xhc, Urb);
    if (EFI_ERROR(RecoveryStatus)) {
      DEBUG((EFI_D_ERROR, "XhcPeiBulkTransfer: XhcPeiDequeueTrbFromEndpoint failed\n"));
    }
  } else {
    if (*TransferResult == EFI_USB_NOERROR) {
      Status = EFI_SUCCESS;
    } else if ((*TransferResult == EFI_USB_ERR_STALL) || (*TransferResult == EFI_USB_ERR_BABBLE)) {
      RecoveryStatus = XhcPeiRecoverHaltedEndpoint(Xhc, Urb);
      if (EFI_ERROR (RecoveryStatus)) {
        DEBUG ((EFI_D_ERROR, "XhcPeiBulkTransfer: XhcPeiRecoverHaltedEndpoint failed\n"));
      }
      Status = EFI_DEVICE_ERROR;
    }
  }

  XhcPeiFreeUrb (Xhc, Urb);

ON_EXIT:

  if (EFI_ERROR (Status)) {
    DEBUG ((EFI_D_ERROR, "XhcPeiBulkTransfer: error - %r, transfer - %x\n", Status, *TransferResult));
  }

  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_USB2_HOST_CONTROLLER_PPI.
  @param[out] PortNumber            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
XhcPeiGetRootHubPortNumber (
  IN EFI_PEI_SERVICES               **PeiServices,
  IN PEI_USB2_HOST_CONTROLLER_PPI   *This,
  OUT UINT8                         *PortNumber
  )
{
  PEI_XHC_DEV           *XhcDev;
  XhcDev = PEI_RECOVERY_USB_XHC_DEV_FROM_THIS (This);

  if (PortNumber == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  *PortNumber = XhcDev->HcSParams1.Data.MaxPorts;
  DEBUG ((EFI_D_INFO, "XhcPeiGetRootHubPortNumber: PortNumber = %x\n", *PortNumber));
  return EFI_SUCCESS;
}

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

  @param  PeiServices               The pointer of EFI_PEI_SERVICES.
  @param  This                      The pointer of PEI_USB2_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
XhcPeiClearRootHubPortFeature (
  IN EFI_PEI_SERVICES               **PeiServices,
  IN PEI_USB2_HOST_CONTROLLER_PPI   *This,
  IN UINT8                          PortNumber,
  IN EFI_USB_PORT_FEATURE           PortFeature
  )
{
  PEI_XHC_DEV           *Xhc;
  UINT32                Offset;
  UINT32                State;
  EFI_STATUS            Status;

  Xhc = PEI_RECOVERY_USB_XHC_DEV_FROM_THIS (This);
  Status = EFI_SUCCESS;

  if (PortNumber >= Xhc->HcSParams1.Data.MaxPorts) {
    Status = EFI_INVALID_PARAMETER;
    goto ON_EXIT;
  }

  Offset = (UINT32) (XHC_PORTSC_OFFSET + (0x10 * PortNumber));
  State = XhcPeiReadOpReg (Xhc, Offset);
  DEBUG ((EFI_D_INFO, "XhcPeiClearRootHubPortFeature: Port: %x State: %x\n", PortNumber, State));

  //
  // Mask off the port status change bits, these bits are
  // write clean bits
  //
  State &= ~ (BIT1 | BIT17 | BIT18 | BIT19 | BIT20 | BIT21 | BIT22 | BIT23);

  switch (PortFeature) {
    case EfiUsbPortEnable:
      //
      // Ports may only be enabled by the xHC. Software cannot enable a port by writing a '1' to this flag.
      // A port may be disabled by software writing a '1' to this flag.
      //
      State |= XHC_PORTSC_PED;
      State &= ~XHC_PORTSC_RESET;
      XhcPeiWriteOpReg (Xhc, Offset, State);
      break;

    case EfiUsbPortSuspend:
      State |= XHC_PORTSC_LWS;
      XhcPeiWriteOpReg (Xhc, Offset, State);
      State &= ~XHC_PORTSC_PLS;
      XhcPeiWriteOpReg (Xhc, Offset, State);
      break;

    case EfiUsbPortReset:
      //
      // PORTSC_RESET BIT(4) bit is RW1S attribute, which means Write-1-to-set status:
      // Register bits indicate status when read, a clear bit may be set by
      // writing a '1'. Writing a '0' to RW1S bits has no effect.
      //
      break;

    case EfiUsbPortPower:
      if (Xhc->HcCParams.Data.Ppc) {
        //
        // Port Power Control supported
        //
        State &= ~XHC_PORTSC_PP;
        XhcPeiWriteOpReg (Xhc, Offset, State);
      }
      break;

    case EfiUsbPortOwner:
      //
      // XHCI root hub port don't has the owner bit, ignore the operation
      //
      break;

    case EfiUsbPortConnectChange:
      //
      // Clear connect status change
      //
      State |= XHC_PORTSC_CSC;
      XhcPeiWriteOpReg (Xhc, Offset, State);
      break;

    case EfiUsbPortEnableChange:
      //
      // Clear enable status change
      //
      State |= XHC_PORTSC_PEC;
      XhcPeiWriteOpReg (Xhc, Offset, State);
      break;

    case EfiUsbPortOverCurrentChange:
      //
      // Clear PortOverCurrent change
      //
      State |= XHC_PORTSC_OCC;
      XhcPeiWriteOpReg (Xhc, Offset, State);
      break;

    case EfiUsbPortResetChange:
      //
      // Clear Port Reset change
      //
      State |= XHC_PORTSC_PRC;
      XhcPeiWriteOpReg (Xhc, Offset, State);
      break;

    case EfiUsbPortSuspendChange:
      //
      // Not supported or not related operation
      //
      break;

    default:
      Status = EFI_INVALID_PARAMETER;
      break;
  }

ON_EXIT:
  DEBUG ((EFI_D_INFO, "XhcPeiClearRootHubPortFeature: PortFeature: %x Status = %r\n", PortFeature, Status));
  return Status;
}

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

  @param  PeiServices               The pointer of EFI_PEI_SERVICES
  @param  This                      The pointer of PEI_USB2_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
XhcPeiSetRootHubPortFeature (
  IN EFI_PEI_SERVICES               **PeiServices,
  IN PEI_USB2_HOST_CONTROLLER_PPI   *This,
  IN UINT8                          PortNumber,
  IN EFI_USB_PORT_FEATURE           PortFeature
  )
{
  PEI_XHC_DEV           *Xhc;
  UINT32                Offset;
  UINT32                State;
  EFI_STATUS            Status;

  Xhc = PEI_RECOVERY_USB_XHC_DEV_FROM_THIS (This);
  Status = EFI_SUCCESS;

  if (PortNumber >= Xhc->HcSParams1.Data.MaxPorts) {
    Status = EFI_INVALID_PARAMETER;
    goto ON_EXIT;
  }

  Offset = (UINT32) (XHC_PORTSC_OFFSET + (0x10 * PortNumber));
  State = XhcPeiReadOpReg (Xhc, Offset);
  DEBUG ((EFI_D_INFO, "XhcPeiSetRootHubPortFeature: Port: %x State: %x\n", PortNumber, State));

  //
  // Mask off the port status change bits, these bits are
  // write clean bits
  //
  State &= ~ (BIT1 | BIT17 | BIT18 | BIT19 | BIT20 | BIT21 | BIT22 | BIT23);

  switch (PortFeature) {
    case EfiUsbPortEnable:
      //
      // Ports may only be enabled by the xHC. Software cannot enable a port by writing a '1' to this flag.
      // A port may be disabled by software writing a '1' to this flag.
      //
      break;

    case EfiUsbPortSuspend:
      State |= XHC_PORTSC_LWS;
      XhcPeiWriteOpReg (Xhc, Offset, State);
      State &= ~XHC_PORTSC_PLS;
      State |= (3 << 5) ;
      XhcPeiWriteOpReg (Xhc, Offset, State);
      break;

    case EfiUsbPortReset:
      //
      // Make sure Host Controller not halt before reset it
      //
      if (XhcPeiIsHalt (Xhc)) {
        Status = XhcPeiRunHC (Xhc, XHC_GENERIC_TIMEOUT);
        if (EFI_ERROR (Status)) {
          break;
        }
      }

      //
      // 4.3.1 Resetting a Root Hub Port
      // 1) Write the PORTSC register with the Port Reset (PR) bit set to '1'.
      // 2) Wait for a successful Port Status Change Event for the port, where the Port Reset Change (PRC)
      //    bit in the PORTSC field is set to '1'.
      //
      State |= XHC_PORTSC_RESET;
      XhcPeiWriteOpReg (Xhc, Offset, State);
      XhcPeiWaitOpRegBit(Xhc, Offset, XHC_PORTSC_PRC, TRUE, XHC_GENERIC_TIMEOUT);
      break;

    case EfiUsbPortPower:
      if (Xhc->HcCParams.Data.Ppc) {
        //
        // Port Power Control supported
        //
        State |= XHC_PORTSC_PP;
        XhcPeiWriteOpReg (Xhc, Offset, State);
      }
      break;

    case EfiUsbPortOwner:
      //
      // XHCI root hub port don't has the owner bit, ignore the operation
      //
      break;

    default:
      Status = EFI_INVALID_PARAMETER;
  }

ON_EXIT:
  DEBUG ((EFI_D_INFO, "XhcPeiSetRootHubPortFeature: PortFeature: %x Status = %r\n", PortFeature, Status));
  return Status;
}

/**
  Retrieves the current status of a USB root hub port.

  @param  PeiServices               The pointer of EFI_PEI_SERVICES.
  @param  This                      The pointer of PEI_USB2_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
XhcPeiGetRootHubPortStatus (
  IN EFI_PEI_SERVICES               **PeiServices,
  IN PEI_USB2_HOST_CONTROLLER_PPI   *This,
  IN UINT8                          PortNumber,
  OUT EFI_USB_PORT_STATUS           *PortStatus
  )
{
  PEI_XHC_DEV               *Xhc;
  UINT32                    Offset;
  UINT32                    State;
  UINTN                     Index;
  UINTN                     MapSize;
  USB_DEV_ROUTE             ParentRouteChart;

  if (PortStatus == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Xhc = PEI_RECOVERY_USB_XHC_DEV_FROM_THIS (This);

  if (PortNumber >= Xhc->HcSParams1.Data.MaxPorts) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Clear port status.
  //
  PortStatus->PortStatus        = 0;
  PortStatus->PortChangeStatus  = 0;

  Offset                        = (UINT32) (XHC_PORTSC_OFFSET + (0x10 * PortNumber));
  State                         = XhcPeiReadOpReg (Xhc, Offset);
  DEBUG ((EFI_D_INFO, "XhcPeiGetRootHubPortStatus: Port: %x State: %x\n", PortNumber, State));

  //
  // According to XHCI 1.1 spec November 2017,
  // bit 10~13 of the root port status register identifies the speed of the attached device.
  //
  switch ((State & XHC_PORTSC_PS) >> 10) {
    case 2:
      PortStatus->PortStatus |= USB_PORT_STAT_LOW_SPEED;
      break;

    case 3:
      PortStatus->PortStatus |= USB_PORT_STAT_HIGH_SPEED;
      break;

    case 4:
    case 5:
      PortStatus->PortStatus |= USB_PORT_STAT_SUPER_SPEED;
      break;

    default:
      break;
  }

  //
  // Convert the XHCI port/port change state to UEFI status
  //
  MapSize = sizeof (mUsbPortStateMap) / sizeof (USB_PORT_STATE_MAP);

  for (Index = 0; Index < MapSize; Index++) {
    if (XHC_BIT_IS_SET (State, mUsbPortStateMap[Index].HwState)) {
      PortStatus->PortStatus = (UINT16) (PortStatus->PortStatus | mUsbPortStateMap[Index].UefiState);
    }
  }
  //
  // Bit5~8 reflects its current link state.
  //
  if ((State & XHC_PORTSC_PLS) >> 5 == 3) {
    PortStatus->PortStatus |= USB_PORT_STAT_SUSPEND;
  }

  MapSize = sizeof (mUsbPortChangeMap) / sizeof (USB_PORT_STATE_MAP);

  for (Index = 0; Index < MapSize; Index++) {
    if (XHC_BIT_IS_SET (State, mUsbPortChangeMap[Index].HwState)) {
      PortStatus->PortChangeStatus = (UINT16) (PortStatus->PortChangeStatus | mUsbPortChangeMap[Index].UefiState);
    }
  }

  MapSize = sizeof (mUsbClearPortChangeMap) / sizeof (USB_CLEAR_PORT_MAP);

  for (Index = 0; Index < MapSize; Index++) {
    if (XHC_BIT_IS_SET (State, mUsbClearPortChangeMap[Index].HwState)) {
      XhcPeiClearRootHubPortFeature (PeiServices, This, PortNumber, (EFI_USB_PORT_FEATURE)mUsbClearPortChangeMap[Index].Selector);
    }
  }

  //
  // Poll the root port status register to enable/disable corresponding device slot if there is a device attached/detached.
  // For those devices behind hub, we get its attach/detach event by hooking Get_Port_Status request at control transfer for those hub.
  //
  ParentRouteChart.Dword = 0;
  XhcPeiPollPortStatusChange (Xhc, ParentRouteChart, PortNumber, PortStatus);

  DEBUG ((EFI_D_INFO, "XhcPeiGetRootHubPortStatus: PortChangeStatus: %x PortStatus: %x\n", PortStatus->PortChangeStatus, PortStatus->PortStatus));
  return EFI_SUCCESS;
}

/**
  One notified function to stop the Host Controller at the end of PEI

  @param[in]  PeiServices        Pointer to PEI Services Table.
  @param[in]  NotifyDescriptor   Pointer to the descriptor for the Notification event that
                                 caused this function to execute.
  @param[in]  Ppi                Pointer to the PPI data associated with this function.

  @retval     EFI_SUCCESS  The function completes successfully
  @retval     others
**/
EFI_STATUS
EFIAPI
XhcEndOfPei (
  IN EFI_PEI_SERVICES           **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyDescriptor,
  IN VOID                       *Ppi
  )
{
  PEI_XHC_DEV    *Xhc;

  Xhc = PEI_RECOVERY_USB_XHC_DEV_FROM_THIS_NOTIFY(NotifyDescriptor);

  XhcPeiHaltHC (Xhc, XHC_GENERIC_TIMEOUT);

  XhcPeiFreeSched (Xhc);

  return EFI_SUCCESS;
}

/**
  @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
EFIAPI
XhcPeimEntry (
  IN EFI_PEI_FILE_HANDLE    FileHandle,
  IN CONST EFI_PEI_SERVICES **PeiServices
  )
{
  PEI_USB_CONTROLLER_PPI      *UsbControllerPpi;
  EFI_STATUS                  Status;
  UINT8                       Index;
  UINTN                       ControllerType;
  UINTN                       BaseAddress;
  UINTN                       MemPages;
  PEI_XHC_DEV                 *XhcDev;
  EFI_PHYSICAL_ADDRESS        TempPtr;
  UINT32                      PageSize;

  //
  // Shadow this PEIM to run from memory.
  //
  if (!EFI_ERROR (PeiServicesRegisterForShadow (FileHandle))) {
    return EFI_SUCCESS;
  }

  Status = PeiServicesLocatePpi (
             &gPeiUsbControllerPpiGuid,
             0,
             NULL,
             (VOID **) &UsbControllerPpi
             );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  IoMmuInit ();

  Index = 0;
  while (TRUE) {
    Status = UsbControllerPpi->GetUsbController (
                                 (EFI_PEI_SERVICES **) PeiServices,
                                 UsbControllerPpi,
                                 Index,
                                 &ControllerType,
                                 &BaseAddress
                                 );
    //
    // When status is error, it means no controller is found.
    //
    if (EFI_ERROR (Status)) {
      break;
    }

    //
    // This PEIM is for XHC type controller.
    //
    if (ControllerType != PEI_XHCI_CONTROLLER) {
      Index++;
      continue;
    }

    MemPages = EFI_SIZE_TO_PAGES (sizeof (PEI_XHC_DEV));
    Status = PeiServicesAllocatePages (
               EfiBootServicesData,
               MemPages,
               &TempPtr
               );
    if (EFI_ERROR (Status)) {
      return EFI_OUT_OF_RESOURCES;
    }
    ZeroMem ((VOID *) (UINTN) TempPtr, EFI_PAGES_TO_SIZE (MemPages));
    XhcDev = (PEI_XHC_DEV *) ((UINTN) TempPtr);

    XhcDev->Signature = USB_XHC_DEV_SIGNATURE;
    XhcDev->UsbHostControllerBaseAddress = (UINT32) BaseAddress;
    XhcDev->CapLength           = (UINT8) (XhcPeiReadCapRegister (XhcDev, XHC_CAPLENGTH_OFFSET) & 0x0FF);
    XhcDev->HcSParams1.Dword    = XhcPeiReadCapRegister (XhcDev, XHC_HCSPARAMS1_OFFSET);
    XhcDev->HcSParams2.Dword    = XhcPeiReadCapRegister (XhcDev, XHC_HCSPARAMS2_OFFSET);
    XhcDev->HcCParams.Dword     = XhcPeiReadCapRegister (XhcDev, XHC_HCCPARAMS_OFFSET);
    XhcDev->DBOff               = XhcPeiReadCapRegister (XhcDev, XHC_DBOFF_OFFSET);
    XhcDev->RTSOff              = XhcPeiReadCapRegister (XhcDev, XHC_RTSOFF_OFFSET);

    //
    // This PageSize field defines the page size supported by the xHC implementation.
    // This xHC supports a page size of 2^(n+12) if bit n is Set. For example,
    // if bit 0 is Set, the xHC supports 4k byte page sizes.
    //
    PageSize         = XhcPeiReadOpReg (XhcDev, XHC_PAGESIZE_OFFSET) & XHC_PAGESIZE_MASK;
    XhcDev->PageSize = 1 << (HighBitSet32 (PageSize) + 12);

    DEBUG ((EFI_D_INFO, "XhciPei: UsbHostControllerBaseAddress: %x\n", XhcDev->UsbHostControllerBaseAddress));
    DEBUG ((EFI_D_INFO, "XhciPei: CapLength:                    %x\n", XhcDev->CapLength));
    DEBUG ((EFI_D_INFO, "XhciPei: HcSParams1:                   %x\n", XhcDev->HcSParams1.Dword));
    DEBUG ((EFI_D_INFO, "XhciPei: HcSParams2:                   %x\n", XhcDev->HcSParams2.Dword));
    DEBUG ((EFI_D_INFO, "XhciPei: HcCParams:                    %x\n", XhcDev->HcCParams.Dword));
    DEBUG ((EFI_D_INFO, "XhciPei: DBOff:                        %x\n", XhcDev->DBOff));
    DEBUG ((EFI_D_INFO, "XhciPei: RTSOff:                       %x\n", XhcDev->RTSOff));
    DEBUG ((EFI_D_INFO, "XhciPei: PageSize:                     %x\n", XhcDev->PageSize));

    XhcPeiResetHC (XhcDev, XHC_RESET_TIMEOUT);
    ASSERT (XhcPeiIsHalt (XhcDev));

    //
    // Initialize the schedule
    //
    XhcPeiInitSched (XhcDev);

    //
    // Start the Host Controller
    //
    XhcPeiRunHC (XhcDev, XHC_GENERIC_TIMEOUT);

    //
    // Wait for root port state stable
    //
    MicroSecondDelay (XHC_ROOT_PORT_STATE_STABLE);

    XhcDev->Usb2HostControllerPpi.ControlTransfer           = XhcPeiControlTransfer;
    XhcDev->Usb2HostControllerPpi.BulkTransfer              = XhcPeiBulkTransfer;
    XhcDev->Usb2HostControllerPpi.GetRootHubPortNumber      = XhcPeiGetRootHubPortNumber;
    XhcDev->Usb2HostControllerPpi.GetRootHubPortStatus      = XhcPeiGetRootHubPortStatus;
    XhcDev->Usb2HostControllerPpi.SetRootHubPortFeature     = XhcPeiSetRootHubPortFeature;
    XhcDev->Usb2HostControllerPpi.ClearRootHubPortFeature   = XhcPeiClearRootHubPortFeature;

    XhcDev->PpiDescriptor.Flags = (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST);
    XhcDev->PpiDescriptor.Guid = &gPeiUsb2HostControllerPpiGuid;
    XhcDev->PpiDescriptor.Ppi = &XhcDev->Usb2HostControllerPpi;

    XhcDev->EndOfPeiNotifyList.Flags = (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST);
    XhcDev->EndOfPeiNotifyList.Guid = &gEfiEndOfPeiSignalPpiGuid;
    XhcDev->EndOfPeiNotifyList.Notify = XhcEndOfPei;

    PeiServicesInstallPpi (&XhcDev->PpiDescriptor);
    PeiServicesNotifyPpi (&XhcDev->EndOfPeiNotifyList);

    Index++;
  }

  return EFI_SUCCESS;
}

