/** @file
  Debug Port Library implementation based on usb3 debug port.

  Copyright (c) 2014 - 2018, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/
#include "DebugCommunicationLibUsb3Internal.h"

/**
  Synchronize the specified transfer ring to update the enqueue and dequeue pointer.

  @param  Handle      Debug port handle.
  @param  TrsRing     The transfer ring to sync.

  @retval EFI_SUCCESS The transfer ring is synchronized successfully.

**/
EFI_STATUS
EFIAPI
XhcSyncTrsRing (
  IN USB3_DEBUG_PORT_HANDLE  *Handle,
  IN TRANSFER_RING           *TrsRing
  )
{
  UINTN         Index;
  TRB_TEMPLATE  *TrsTrb;
  UINT32        CycleBit;

  ASSERT (TrsRing != NULL);

  //
  // Calculate the latest RingEnqueue and RingPCS
  //
  TrsTrb = (TRB_TEMPLATE *)(UINTN)TrsRing->RingEnqueue;

  ASSERT (TrsTrb != NULL);

  for (Index = 0; Index < TrsRing->TrbNumber; Index++) {
    if (TrsTrb->CycleBit != (TrsRing->RingPCS & BIT0)) {
      break;
    }

    TrsTrb++;
    if ((UINT8)TrsTrb->Type == TRB_TYPE_LINK) {
      ASSERT (((LINK_TRB *)TrsTrb)->TC != 0);
      //
      // set cycle bit in Link TRB as normal
      //
      ((LINK_TRB *)TrsTrb)->CycleBit = TrsRing->RingPCS & BIT0;
      //
      // Toggle PCS maintained by software
      //
      TrsRing->RingPCS = (TrsRing->RingPCS & BIT0) ? 0 : 1;
      TrsTrb           = (TRB_TEMPLATE *)(UINTN)((TrsTrb->Parameter1 | LShiftU64 ((UINT64)TrsTrb->Parameter2, 32)) & ~0x0F);
    }
  }

  ASSERT (Index != TrsRing->TrbNumber);

  if ((EFI_PHYSICAL_ADDRESS)(UINTN)TrsTrb != TrsRing->RingEnqueue) {
    TrsRing->RingEnqueue = (EFI_PHYSICAL_ADDRESS)(UINTN)TrsTrb;
  }

  //
  // Clear the Trb context for enqueue, but reserve the PCS bit which indicates free Trb.
  //
  CycleBit = TrsTrb->CycleBit;
  ZeroMem (TrsTrb, sizeof (TRB_TEMPLATE));
  TrsTrb->CycleBit = CycleBit;

  return EFI_SUCCESS;
}

/**
  Synchronize the specified event ring to update the enqueue and dequeue pointer.

  @param  Handle      Debug port handle.
  @param  EvtRing     The event ring to sync.

  @retval EFI_SUCCESS The event ring is synchronized successfully.

**/
EFI_STATUS
EFIAPI
XhcSyncEventRing (
  IN USB3_DEBUG_PORT_HANDLE  *Handle,
  IN EVENT_RING              *EvtRing
  )
{
  UINTN         Index;
  TRB_TEMPLATE  *EvtTrb1;

  ASSERT (EvtRing != NULL);

  //
  // Calculate the EventRingEnqueue and EventRingCCS.
  // Note: only support single Segment
  //
  EvtTrb1 = (TRB_TEMPLATE *)(UINTN)EvtRing->EventRingDequeue;

  for (Index = 0; Index < EvtRing->TrbNumber; Index++) {
    if (EvtTrb1->CycleBit != EvtRing->EventRingCCS) {
      break;
    }

    EvtTrb1++;

    if ((UINTN)EvtTrb1 >= ((UINTN)EvtRing->EventRingSeg0 + sizeof (TRB_TEMPLATE) * EvtRing->TrbNumber)) {
      EvtTrb1               = (TRB_TEMPLATE *)(UINTN)EvtRing->EventRingSeg0;
      EvtRing->EventRingCCS = (EvtRing->EventRingCCS) ? 0 : 1;
    }
  }

  if (Index < EvtRing->TrbNumber) {
    EvtRing->EventRingEnqueue = (EFI_PHYSICAL_ADDRESS)(UINTN)EvtTrb1;
  } else {
    ASSERT (FALSE);
  }

  return EFI_SUCCESS;
}

/**
  Check if there is a new generated event.

  @param  Handle        Debug port handle.
  @param  EvtRing       The event ring to check.
  @param  NewEvtTrb     The new event TRB found.

  @retval EFI_SUCCESS   Found a new event TRB at the event ring.
  @retval EFI_NOT_READY The event ring has no new event.

**/
EFI_STATUS
EFIAPI
XhcCheckNewEvent (
  IN  USB3_DEBUG_PORT_HANDLE  *Handle,
  IN  EVENT_RING              *EvtRing,
  OUT TRB_TEMPLATE            **NewEvtTrb
  )
{
  EFI_STATUS  Status;

  ASSERT (EvtRing != NULL);

  *NewEvtTrb = (TRB_TEMPLATE *)(UINTN)EvtRing->EventRingDequeue;

  if (EvtRing->EventRingDequeue == EvtRing->EventRingEnqueue) {
    return EFI_NOT_READY;
  }

  Status = EFI_SUCCESS;

  EvtRing->EventRingDequeue += sizeof (TRB_TEMPLATE);
  //
  // If the dequeue pointer is beyond the ring, then roll-back it to the beginning of the ring.
  //
  if ((UINTN)EvtRing->EventRingDequeue >= ((UINTN)EvtRing->EventRingSeg0 + sizeof (TRB_TEMPLATE) * EvtRing->TrbNumber)) {
    EvtRing->EventRingDequeue = EvtRing->EventRingSeg0;
  }

  return Status;
}

/**
  Check if the Trb is a transaction of the URB.

  @param Ring   The transfer ring to be checked.
  @param Trb    The TRB to be checked.

  @retval TRUE  It is a transaction of the URB.
  @retval FALSE It is not any transaction of the URB.

**/
BOOLEAN
IsTrbInTrsRing (
  IN  TRANSFER_RING  *Ring,
  IN  TRB_TEMPLATE   *Trb
  )
{
  TRB_TEMPLATE  *CheckedTrb;
  UINTN         Index;

  CheckedTrb = (TRB_TEMPLATE *)(UINTN)Ring->RingSeg0;

  ASSERT (Ring->TrbNumber == TR_RING_TRB_NUMBER);

  for (Index = 0; Index < Ring->TrbNumber; Index++) {
    if (Trb == CheckedTrb) {
      return TRUE;
    }

    CheckedTrb++;
  }

  return FALSE;
}

/**
  Check the URB's execution result and update the URB's
  result accordingly.

  @param  Handle          Debug port handle.
  @param  Urb             The URB to check result.

**/
VOID
XhcCheckUrbResult (
  IN  USB3_DEBUG_PORT_HANDLE  *Handle,
  IN  URB                     *Urb
  )
{
  EVT_TRB_TRANSFER  *EvtTrb;
  TRB_TEMPLATE      *TRBPtr;
  UINTN             Index;
  EFI_STATUS        Status;
  URB               *CheckedUrb;
  UINT64            XhcDequeue;
  UINT32            High;
  UINT32            Low;

  ASSERT ((Handle != NULL) && (Urb != NULL));

  if (Urb->Finished) {
    goto EXIT;
  }

  EvtTrb = NULL;

  //
  // Traverse the event ring to find out all new events from the previous check.
  //
  XhcSyncEventRing (Handle, &Handle->EventRing);

  for (Index = 0; Index < Handle->EventRing.TrbNumber; Index++) {
    Status = XhcCheckNewEvent (Handle, &Handle->EventRing, ((TRB_TEMPLATE **)&EvtTrb));
    if (Status == EFI_NOT_READY) {
      //
      // All new events are handled, return directly.
      //
      goto EXIT;
    }

    if ((EvtTrb->Type != TRB_TYPE_COMMAND_COMPLT_EVENT) && (EvtTrb->Type != TRB_TYPE_TRANS_EVENT)) {
      continue;
    }

    TRBPtr = (TRB_TEMPLATE *)(UINTN)(EvtTrb->TRBPtrLo | LShiftU64 ((UINT64)EvtTrb->TRBPtrHi, 32));

    if (IsTrbInTrsRing ((TRANSFER_RING *)(UINTN)(Urb->Ring), TRBPtr)) {
      CheckedUrb = Urb;
    } else if (IsTrbInTrsRing ((TRANSFER_RING *)(UINTN)(Handle->UrbIn.Ring), TRBPtr)) {
      //
      // If it is read event and it should be generated by poll, and current operation is write, we need save data into internal buffer.
      // Internal buffer is used by next read.
      //
      Handle->DataCount = (UINT8)(Handle->UrbIn.DataLen - EvtTrb->Length);
      CopyMem ((VOID *)(UINTN)Handle->Data, (VOID *)(UINTN)Handle->UrbIn.Data, Handle->DataCount);
      //
      // Fill this TRB complete with CycleBit, otherwise next read will fail with old TRB.
      //
      TRBPtr->CycleBit = (TRBPtr->CycleBit & BIT0) ? 0 : 1;
      continue;
    } else {
      continue;
    }

    if ((EvtTrb->Completecode == TRB_COMPLETION_SHORT_PACKET) ||
        (EvtTrb->Completecode == TRB_COMPLETION_SUCCESS))
    {
      //
      // The length of data which were transferred.
      //
      CheckedUrb->Completed += (((TRANSFER_TRB_NORMAL *)TRBPtr)->Length - EvtTrb->Length);
    } else {
      CheckedUrb->Result |= EFI_USB_ERR_TIMEOUT;
    }

    //
    // This Urb has been processed
    //
    CheckedUrb->Finished = TRUE;
  }

EXIT:
  //
  // Advance event ring to last available entry
  //
  // Some 3rd party XHCI external cards don't support single 64-bytes width register access,
  // So divide it to two 32-bytes width register access.
  //
  Low        = XhcReadDebugReg (Handle, XHC_DC_DCERDP);
  High       = XhcReadDebugReg (Handle, XHC_DC_DCERDP + 4);
  XhcDequeue = (UINT64)(LShiftU64 ((UINT64)High, 32) | Low);

  if ((XhcDequeue & (~0x0F)) != ((UINT64)(UINTN)Handle->EventRing.EventRingDequeue & (~0x0F))) {
    //
    // Some 3rd party XHCI external cards don't support single 64-bytes width register access,
    // So divide it to two 32-bytes width register access.
    //
    XhcWriteDebugReg (Handle, XHC_DC_DCERDP, XHC_LOW_32BIT (Handle->EventRing.EventRingDequeue));
    XhcWriteDebugReg (Handle, XHC_DC_DCERDP + 4, XHC_HIGH_32BIT (Handle->EventRing.EventRingDequeue));
  }
}

/**
  Ring the door bell to notify XHCI there is a transaction to be executed.

  @param  Handle        Debug port handle.
  @param  Urb           The pointer to URB.

  @retval EFI_SUCCESS   Successfully ring the door bell.

**/
EFI_STATUS
EFIAPI
XhcRingDoorBell (
  IN USB3_DEBUG_PORT_HANDLE  *Handle,
  IN URB                     *Urb
  )
{
  UINT32  Dcdb;

  //
  // 7.6.8.2 DCDB Register
  //
  Dcdb = (Urb->Direction == EfiUsbDataIn) ? 0x100 : 0x0;

  XhcWriteDebugReg (
    Handle,
    XHC_DC_DCDB,
    Dcdb
    );

  return EFI_SUCCESS;
}

/**
  Execute the transfer by polling the URB. This is a synchronous operation.

  @param  Handle            Debug port handle.
  @param  Urb               The URB to execute.
  @param  Timeout           The time to wait before abort, in microsecond.

**/
VOID
XhcExecTransfer (
  IN  USB3_DEBUG_PORT_HANDLE  *Handle,
  IN  URB                     *Urb,
  IN  UINTN                   Timeout
  )
{
  TRANSFER_RING  *Ring;
  TRB_TEMPLATE   *Trb;
  UINTN          Loop;
  UINTN          Index;

  Loop = Timeout / XHC_DEBUG_PORT_1_MILLISECOND;
  if (Timeout == 0) {
    Loop = 0xFFFFFFFF;
  }

  XhcRingDoorBell (Handle, Urb);
  //
  // Event Ring Not Empty bit can only be set to 1 by XHC after ringing door bell with some delay.
  //
  for (Index = 0; Index < Loop; Index++) {
    XhcCheckUrbResult (Handle, Urb);
    if (Urb->Finished) {
      break;
    }

    MicroSecondDelay (XHC_DEBUG_PORT_1_MILLISECOND);
  }

  if (Index == Loop) {
    //
    // If time out occurs.
    //
    Urb->Result |= EFI_USB_ERR_TIMEOUT;
  }

  //
  // If URB transfer is error, restore transfer ring to original value before URB transfer
  // This will make the current transfer TRB is always at the latest unused one in transfer ring.
  //
  Ring = (TRANSFER_RING *)(UINTN)Urb->Ring;
  if ((Urb->Result != EFI_USB_NOERROR) && (Urb->Direction == EfiUsbDataIn)) {
    //
    // Adjust Enqueue pointer
    //
    Ring->RingEnqueue = Urb->Trb;
    //
    // Clear CCS flag for next use
    //
    Trb           = (TRB_TEMPLATE *)(UINTN)Urb->Trb;
    Trb->CycleBit = ((~Ring->RingPCS) & BIT0);
  } else {
    //
    // Update transfer ring for next transfer.
    //
    XhcSyncTrsRing (Handle, Ring);
  }
}

/**
  Create a transfer TRB.

  @param  Handle  Debug port handle.
  @param  Urb     The urb used to construct the transfer TRB.

  @return Created TRB or NULL

**/
EFI_STATUS
XhcCreateTransferTrb (
  IN USB3_DEBUG_PORT_HANDLE  *Handle,
  IN URB                     *Urb
  )
{
  TRANSFER_RING  *EPRing;
  TRB            *Trb;

  if (Urb->Direction == EfiUsbDataIn) {
    EPRing = &Handle->TransferRingIn;
  } else {
    EPRing = &Handle->TransferRingOut;
  }

  Urb->Ring = (EFI_PHYSICAL_ADDRESS)(UINTN)EPRing;
  XhcSyncTrsRing (Handle, EPRing);

  Urb->Trb                 = EPRing->RingEnqueue;
  Trb                      = (TRB *)(UINTN)EPRing->RingEnqueue;
  Trb->TrbNormal.TRBPtrLo  = XHC_LOW_32BIT (Urb->Data);
  Trb->TrbNormal.TRBPtrHi  = XHC_HIGH_32BIT (Urb->Data);
  Trb->TrbNormal.Length    = Urb->DataLen;
  Trb->TrbNormal.TDSize    = 0;
  Trb->TrbNormal.IntTarget = 0;
  Trb->TrbNormal.ISP       = 1;
  Trb->TrbNormal.IOC       = 1;
  Trb->TrbNormal.Type      = TRB_TYPE_NORMAL;

  //
  // Update the cycle bit to indicate this TRB has been consumed.
  //
  Trb->TrbNormal.CycleBit = EPRing->RingPCS & BIT0;

  return EFI_SUCCESS;
}

/**
  Create a new URB for a new transaction.

  @param  Handle     Debug port handle.
  @param  Direction  The direction of data flow.
  @param  Data       The user data to transfer
  @param  DataLen    The length of data buffer

  @return Created URB or NULL

**/
URB *
XhcCreateUrb (
  IN USB3_DEBUG_PORT_HANDLE  *Handle,
  IN EFI_USB_DATA_DIRECTION  Direction,
  IN VOID                    *Data,
  IN UINTN                   DataLen
  )
{
  EFI_STATUS            Status;
  URB                   *Urb;
  EFI_PHYSICAL_ADDRESS  UrbData;

  if (Direction == EfiUsbDataIn) {
    Urb = &Handle->UrbIn;
  } else {
    Urb = &Handle->UrbOut;
  }

  UrbData = Urb->Data;

  ZeroMem (Urb, sizeof (URB));
  Urb->Direction = Direction;

  //
  // Allocate memory to move data from CAR or SMRAM to normal memory
  // to make XHCI DMA successfully
  // re-use the pre-allocate buffer in PEI to avoid DXE memory service or gBS are not ready
  //
  Urb->Data = UrbData;

  if (Direction == EfiUsbDataIn) {
    //
    // Do not break URB data in buffer as it may contain the data which were just put in via DMA by XHC
    //
    Urb->DataLen = (UINT32)DataLen;
  } else {
    //
    // Put data into URB data out buffer which will create TRBs
    //
    ZeroMem ((VOID *)(UINTN)Urb->Data, DataLen);
    CopyMem ((VOID *)(UINTN)Urb->Data, Data, DataLen);
    Urb->DataLen = (UINT32)DataLen;
  }

  Status = XhcCreateTransferTrb (Handle, Urb);
  ASSERT_EFI_ERROR (Status);

  return Urb;
}

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

  @param  Handle                Debug port handle.
  @param  Direction             The direction of data transfer.
  @param  Data                  Array of pointers to the buffers of data to transmit
                                from or receive into.
  @param  DataLength            The length of the data buffer.
  @param  Timeout               Indicates the maximum time, in microsecond, which
                                the transfer is allowed to complete.

  @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 Some 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
XhcDataTransfer (
  IN     USB3_DEBUG_PORT_HANDLE  *Handle,
  IN     EFI_USB_DATA_DIRECTION  Direction,
  IN OUT VOID                    *Data,
  IN OUT UINTN                   *DataLength,
  IN     UINTN                   Timeout
  )
{
  URB         *Urb;
  EFI_STATUS  Status;

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

  //
  // Create a new URB, insert it into the asynchronous
  // schedule list, then poll the execution status.
  //
  Urb = XhcCreateUrb (Handle, Direction, Data, *DataLength);
  ASSERT (Urb != NULL);

  XhcExecTransfer (Handle, Urb, Timeout);

  //
  // Make sure the data received from HW can fit in the received buffer.
  //
  if (Urb->Completed > *DataLength) {
    return EFI_DEVICE_ERROR;
  }

  *DataLength = Urb->Completed;

  Status = EFI_TIMEOUT;
  if (Urb->Result == EFI_USB_NOERROR) {
    Status = EFI_SUCCESS;
  }

  if (Direction == EfiUsbDataIn) {
    //
    // Move data from internal buffer to outside buffer (outside buffer may be in SMRAM...)
    // SMRAM does not allow to do DMA, so we create an internal buffer.
    //
    CopyMem (Data, (VOID *)(UINTN)Urb->Data, *DataLength);
  }

  return Status;
}
