/** @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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_ERROR, "XhcPeiBulkTransfer: XhcPeiRecoverHaltedEndpoint failed\n"));
      }

      Status = EFI_DEVICE_ERROR;
    }
  }

  XhcPeiFreeUrb (Xhc, Urb);

ON_EXIT:

  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_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 ((DEBUG_INFO, "XhciPei: UsbHostControllerBaseAddress: %x\n", XhcDev->UsbHostControllerBaseAddress));
    DEBUG ((DEBUG_INFO, "XhciPei: CapLength:                    %x\n", XhcDev->CapLength));
    DEBUG ((DEBUG_INFO, "XhciPei: HcSParams1:                   %x\n", XhcDev->HcSParams1.Dword));
    DEBUG ((DEBUG_INFO, "XhciPei: HcSParams2:                   %x\n", XhcDev->HcSParams2.Dword));
    DEBUG ((DEBUG_INFO, "XhciPei: HcCParams:                    %x\n", XhcDev->HcCParams.Dword));
    DEBUG ((DEBUG_INFO, "XhciPei: DBOff:                        %x\n", XhcDev->DBOff));
    DEBUG ((DEBUG_INFO, "XhciPei: RTSOff:                       %x\n", XhcDev->RTSOff));
    DEBUG ((DEBUG_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;
}
