/** @file
BOT Transportation implementation.

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

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

**/

#include "UsbBotPeim.h"
#include "BotPeim.h"
#include "PeiUsbLib.h"

/**
  Reset the given usb device.

  @param  PeiServices            The pointer of EFI_PEI_SERVICES.
  @param  PeiBotDev              The instance to PEI_BOT_DEVICE.

  @retval EFI_INVALID_PARAMETER  Can not get usb io ppi.
  @retval EFI_SUCCESS            Failed to reset the given usb device.

**/
EFI_STATUS
BotRecoveryReset (
  IN  EFI_PEI_SERVICES          **PeiServices,
  IN  PEI_BOT_DEVICE            *PeiBotDev
  )
{
  EFI_USB_DEVICE_REQUEST  DevReq;
  UINT32                  Timeout;
  PEI_USB_IO_PPI          *UsbIoPpi;
  UINT8                   EndpointAddr;
  EFI_STATUS              Status;

  UsbIoPpi = PeiBotDev->UsbIoPpi;

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

  ZeroMem (&DevReq, sizeof (EFI_USB_DEVICE_REQUEST));

  DevReq.RequestType  = 0x21;
  DevReq.Request      = 0xFF;
  DevReq.Value        = 0;
  DevReq.Index        = 0;
  DevReq.Length       = 0;

  Timeout             = 3000;

  Status = UsbIoPpi->UsbControlTransfer (
                      PeiServices,
                      UsbIoPpi,
                      &DevReq,
                      EfiUsbNoData,
                      Timeout,
                      NULL,
                      0
                      );

  //
  // clear bulk in endpoint stall feature
  //
  EndpointAddr = (PeiBotDev->BulkInEndpoint)->EndpointAddress;
  PeiUsbClearEndpointHalt (PeiServices, UsbIoPpi, EndpointAddr);

  //
  // clear bulk out endpoint stall feature
  //
  EndpointAddr = (PeiBotDev->BulkOutEndpoint)->EndpointAddress;
  PeiUsbClearEndpointHalt (PeiServices, UsbIoPpi, EndpointAddr);

  return Status;
}

/**
  Send the command to the device using Bulk-Out endpoint.

  This function sends the command to the device using Bulk-Out endpoint.
  BOT transfer is composed of three phases: Command, Data, and Status.
  This is the Command phase.

  @param  PeiServices            The pointer of EFI_PEI_SERVICES.
  @param  PeiBotDev              The instance to PEI_BOT_DEVICE.
  @param  Command                The command to transfer to device.
  @param  CommandSize            The length of the command.
  @param  DataTransferLength     The expected length of the data.
  @param  Direction              The direction of the data.
  @param  Timeout                Indicates the maximum time, in millisecond, which the
                                 transfer is allowed to complete.

  @retval EFI_DEVICE_ERROR       Successful to send the command to device.
  @retval EFI_SUCCESS            Failed to send the command to device.

**/
EFI_STATUS
BotCommandPhase (
  IN  EFI_PEI_SERVICES          **PeiServices,
  IN  PEI_BOT_DEVICE            *PeiBotDev,
  IN  VOID                      *Command,
  IN  UINT8                     CommandSize,
  IN  UINT32                    DataTransferLength,
  IN  EFI_USB_DATA_DIRECTION    Direction,
  IN  UINT16                    Timeout
  )
{
  CBW             Cbw;
  EFI_STATUS      Status;
  PEI_USB_IO_PPI  *UsbIoPpi;
  UINTN           DataSize;

  UsbIoPpi = PeiBotDev->UsbIoPpi;

  ZeroMem (&Cbw, sizeof (CBW));

  //
  // Fill the command block, detailed see BOT spec
  //
  Cbw.Signature           = CBWSIG;
  Cbw.Tag                 = 0x01;
  Cbw.DataTransferLength  = DataTransferLength;
  Cbw.Flags               = (UINT8) ((Direction == EfiUsbDataIn) ? 0x80 : 0);
  Cbw.Lun                 = 0;
  Cbw.CmdLen              = CommandSize;

  CopyMem (Cbw.CmdBlock, Command, CommandSize);

  DataSize = sizeof (CBW);

  Status = UsbIoPpi->UsbBulkTransfer (
                      PeiServices,
                      UsbIoPpi,
                      (PeiBotDev->BulkOutEndpoint)->EndpointAddress,
                      (UINT8 *) &Cbw,
                      &DataSize,
                      Timeout
                      );
  if (EFI_ERROR (Status)) {
    //
    // Command phase fail, we need to recovery reset this device
    //
    BotRecoveryReset (PeiServices, PeiBotDev);
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

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

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

  @param  PeiServices            The pointer of EFI_PEI_SERVICES.
  @param  PeiBotDev              The instance to PEI_BOT_DEVICE.
  @param  DataSize               The length of the data.
  @param  DataBuffer             The pointer to the data.
  @param  Direction              The direction of the data.
  @param  Timeout                Indicates the maximum time, in millisecond, which the
                                 transfer is allowed to complete.

  @retval EFI_DEVICE_ERROR       Successful to send the data to device.
  @retval EFI_SUCCESS            Failed to send the data to device.

**/
EFI_STATUS
BotDataPhase (
  IN  EFI_PEI_SERVICES          **PeiServices,
  IN  PEI_BOT_DEVICE            *PeiBotDev,
  IN  UINT32                    *DataSize,
  IN  OUT VOID                  *DataBuffer,
  IN  EFI_USB_DATA_DIRECTION    Direction,
  IN  UINT16                    Timeout
  )
{
  EFI_STATUS      Status;
  PEI_USB_IO_PPI  *UsbIoPpi;
  UINT8           EndpointAddr;
  UINTN           Remain;
  UINTN           Increment;
  UINT32          MaxPacketLen;
  UINT8           *BufferPtr;
  UINTN           TransferredSize;

  UsbIoPpi        = PeiBotDev->UsbIoPpi;

  Remain          = *DataSize;
  BufferPtr       = (UINT8 *) DataBuffer;
  TransferredSize = 0;

  //
  // retrieve the the max packet length of the given endpoint
  //
  if (Direction == EfiUsbDataIn) {
    MaxPacketLen  = (PeiBotDev->BulkInEndpoint)->MaxPacketSize;
    EndpointAddr  = (PeiBotDev->BulkInEndpoint)->EndpointAddress;
  } else {
    MaxPacketLen  = (PeiBotDev->BulkOutEndpoint)->MaxPacketSize;
    EndpointAddr  = (PeiBotDev->BulkOutEndpoint)->EndpointAddress;
  }

  while (Remain > 0) {
    //
    // Using 15 packets to avoid Bitstuff error
    //
    if (Remain > 16 * MaxPacketLen) {
      Increment = 16 * MaxPacketLen;
    } else {
      Increment = Remain;
    }

    Status = UsbIoPpi->UsbBulkTransfer (
                        PeiServices,
                        UsbIoPpi,
                        EndpointAddr,
                        BufferPtr,
                        &Increment,
                        Timeout
                        );

    TransferredSize += Increment;

    if (EFI_ERROR (Status)) {
      PeiUsbClearEndpointHalt (PeiServices, UsbIoPpi, EndpointAddr);
      return Status;
    }

    BufferPtr += Increment;
    Remain -= Increment;
  }

  *DataSize = (UINT32) TransferredSize;

  return EFI_SUCCESS;
}

/**
  Get the command execution status from device.

  This function gets the command execution status from device.
  BOT transfer is composed of three phases: Command, Data, and Status.
  This is the Status phase.

  @param  PeiServices            The pointer of EFI_PEI_SERVICES.
  @param  PeiBotDev              The instance to PEI_BOT_DEVICE.
  @param  TransferStatus         The status of the transaction.
  @param  Timeout                Indicates the maximum time, in millisecond, which the
                                 transfer is allowed to complete.

  @retval EFI_DEVICE_ERROR       Successful to get the status of device.
  @retval EFI_SUCCESS            Failed to get the status of device.

**/
EFI_STATUS
BotStatusPhase (
  IN  EFI_PEI_SERVICES          **PeiServices,
  IN  PEI_BOT_DEVICE            *PeiBotDev,
  OUT UINT8                     *TransferStatus,
  IN  UINT16                    Timeout
  )
{
  CSW             Csw;
  EFI_STATUS      Status;
  PEI_USB_IO_PPI  *UsbIoPpi;
  UINT8           EndpointAddr;
  UINTN           DataSize;

  UsbIoPpi = PeiBotDev->UsbIoPpi;

  ZeroMem (&Csw, sizeof (CSW));

  EndpointAddr  = (PeiBotDev->BulkInEndpoint)->EndpointAddress;

  DataSize      = sizeof (CSW);

  //
  // Get the status field from bulk transfer
  //
  Status = UsbIoPpi->UsbBulkTransfer (
                      PeiServices,
                      UsbIoPpi,
                      EndpointAddr,
                      &Csw,
                      &DataSize,
                      Timeout
                      );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (Csw.Signature == CSWSIG) {
    *TransferStatus = Csw.Status;
  } else {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

/**
  Send ATAPI command using BOT protocol.

  @param  PeiServices            The pointer of EFI_PEI_SERVICES.
  @param  PeiBotDev              The instance to PEI_BOT_DEVICE.
  @param  Command                The command to be sent to ATAPI device.
  @param  CommandSize            The length of the data to be sent.
  @param  DataBuffer             The pointer to the data.
  @param  BufferLength           The length of the data.
  @param  Direction              The direction of the data.
  @param  TimeOutInMilliSeconds  Indicates the maximum time, in millisecond, which the
                                 transfer is allowed to complete.

  @retval EFI_DEVICE_ERROR       Successful to get the status of device.
  @retval EFI_SUCCESS            Failed to get the status of device.

**/
EFI_STATUS
PeiAtapiCommand (
  IN  EFI_PEI_SERVICES            **PeiServices,
  IN  PEI_BOT_DEVICE              *PeiBotDev,
  IN  VOID                        *Command,
  IN  UINT8                       CommandSize,
  IN  VOID                        *DataBuffer,
  IN  UINT32                      BufferLength,
  IN  EFI_USB_DATA_DIRECTION      Direction,
  IN  UINT16                      TimeOutInMilliSeconds
  )
{
  EFI_STATUS  Status;
  EFI_STATUS  BotDataStatus;
  UINT8       TransferStatus;
  UINT32      BufferSize;

  BotDataStatus = EFI_SUCCESS;
  //
  // First send ATAPI command through Bot
  //
  Status = BotCommandPhase (
            PeiServices,
            PeiBotDev,
            Command,
            CommandSize,
            BufferLength,
            Direction,
            TimeOutInMilliSeconds
            );

  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }
  //
  // Send/Get Data if there is a Data Stage
  //
  switch (Direction) {
  case EfiUsbDataIn:
  case EfiUsbDataOut:
    BufferSize = BufferLength;

    BotDataStatus = BotDataPhase (
                      PeiServices,
                      PeiBotDev,
                      &BufferSize,
                      DataBuffer,
                      Direction,
                      TimeOutInMilliSeconds
                      );
    break;

  case EfiUsbNoData:
    break;
  }
  //
  // Status Phase
  //
  Status = BotStatusPhase (
            PeiServices,
            PeiBotDev,
            &TransferStatus,
            TimeOutInMilliSeconds
            );
  if (EFI_ERROR (Status)) {
    BotRecoveryReset (PeiServices, PeiBotDev);
    return EFI_DEVICE_ERROR;
  }

  if (TransferStatus == 0x01) {
    return EFI_DEVICE_ERROR;
  }

  return BotDataStatus;
}
