/** @file
Pei USB ATATPI command implementations.

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

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

**/

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

#define MAXSENSEKEY 5

/**
  Sends out ATAPI Inquiry Packet Command to the specified device. This command will
  return INQUIRY data of the device.

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

  @retval EFI_SUCCESS       Inquiry command completes successfully.
  @retval EFI_DEVICE_ERROR  Inquiry command failed.

**/
EFI_STATUS
PeiUsbInquiry (
  IN  EFI_PEI_SERVICES  **PeiServices,
  IN  PEI_BOT_DEVICE    *PeiBotDevice
  )
{
  ATAPI_PACKET_COMMAND  Packet;
  EFI_STATUS            Status;
  ATAPI_INQUIRY_DATA          Idata;

  //
  // fill command packet
  //
  ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
  ZeroMem (&Idata, sizeof (ATAPI_INQUIRY_DATA));

  Packet.Inquiry.opcode             = ATA_CMD_INQUIRY;
  Packet.Inquiry.page_code          = 0;
  Packet.Inquiry.allocation_length  = 36;

  //
  // Send scsi INQUIRY command packet.
  // According to SCSI Primary Commands-2 spec, host only needs to
  // retrieve the first 36 bytes for standard INQUIRY data.
  //
  Status = PeiAtapiCommand (
            PeiServices,
            PeiBotDevice,
            &Packet,
            (UINT8) sizeof (ATAPI_PACKET_COMMAND),
            &Idata,
            36,
            EfiUsbDataIn,
            2000
            );

  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }

  if ((Idata.peripheral_type & 0x1f) == 0x05) {
    PeiBotDevice->DeviceType      = USBCDROM;
    PeiBotDevice->Media.BlockSize = 0x800;
    PeiBotDevice->Media2.ReadOnly       = TRUE;
    PeiBotDevice->Media2.RemovableMedia = TRUE;
    PeiBotDevice->Media2.BlockSize      = 0x800;
  } else {
    PeiBotDevice->DeviceType      = USBFLOPPY;
    PeiBotDevice->Media.BlockSize = 0x200;
    PeiBotDevice->Media2.ReadOnly       = FALSE;
    PeiBotDevice->Media2.RemovableMedia = TRUE;
    PeiBotDevice->Media2.BlockSize      = 0x200;
  }

  return EFI_SUCCESS;
}

/**
  Sends out ATAPI Test Unit Ready Packet Command to the specified device
  to find out whether device is accessible.

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

  @retval EFI_SUCCESS        TestUnit command executed successfully.
  @retval EFI_DEVICE_ERROR   Device cannot be executed TestUnit command successfully.

**/
EFI_STATUS
PeiUsbTestUnitReady (
  IN  EFI_PEI_SERVICES  **PeiServices,
  IN  PEI_BOT_DEVICE    *PeiBotDevice
  )
{
  ATAPI_PACKET_COMMAND  Packet;
  EFI_STATUS            Status;

  //
  // fill command packet
  //
  ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
  Packet.TestUnitReady.opcode = ATA_CMD_TEST_UNIT_READY;

  //
  // send command packet
  //
  Status = PeiAtapiCommand (
            PeiServices,
            PeiBotDevice,
            &Packet,
            (UINT8) sizeof (ATAPI_PACKET_COMMAND),
            NULL,
            0,
            EfiUsbNoData,
            2000
            );

  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

/**
  Sends out ATAPI Request Sense Packet Command to the specified device.

  @param PeiServices    The pointer of EFI_PEI_SERVICES.
  @param PeiBotDevice   The pointer to PEI_BOT_DEVICE instance.
  @param SenseCounts    Length of sense buffer.
  @param SenseKeyBuffer Pointer to sense buffer.

  @retval EFI_SUCCESS           Command executed successfully.
  @retval EFI_DEVICE_ERROR      Some device errors happen.

**/
EFI_STATUS
PeiUsbRequestSense (
  IN  EFI_PEI_SERVICES  **PeiServices,
  IN  PEI_BOT_DEVICE    *PeiBotDevice,
  OUT UINTN             *SenseCounts,
  IN  UINT8             *SenseKeyBuffer
  )
{
  EFI_STATUS                  Status;
  ATAPI_PACKET_COMMAND        Packet;
  UINT8                       *Ptr;
  BOOLEAN                     SenseReq;
  ATAPI_REQUEST_SENSE_DATA    *Sense;

  *SenseCounts = 0;

  //
  // fill command packet for Request Sense Packet Command
  //
  ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
  Packet.RequestSence.opcode            = ATA_CMD_REQUEST_SENSE;
  Packet.RequestSence.allocation_length = (UINT8) sizeof (ATAPI_REQUEST_SENSE_DATA);

  Ptr = SenseKeyBuffer;

  SenseReq = TRUE;

  //
  //  request sense data from device continuously
  //  until no sense data exists in the device.
  //
  while (SenseReq) {
    Sense = (ATAPI_REQUEST_SENSE_DATA *) Ptr;

    //
    // send out Request Sense Packet Command and get one Sense
    // data form device.
    //
    Status = PeiAtapiCommand (
              PeiServices,
              PeiBotDevice,
              &Packet,
              (UINT8) sizeof (ATAPI_PACKET_COMMAND),
              (VOID *) Ptr,
              sizeof (ATAPI_REQUEST_SENSE_DATA),
              EfiUsbDataIn,
              2000
              );

    //
    // failed to get Sense data
    //
    if (EFI_ERROR (Status)) {
      if (*SenseCounts == 0) {
        return EFI_DEVICE_ERROR;
      } else {
        return EFI_SUCCESS;
      }
    }

    if (Sense->sense_key != ATA_SK_NO_SENSE) {

      Ptr += sizeof (ATAPI_REQUEST_SENSE_DATA);
      //
      // Ptr is byte based pointer
      //
      (*SenseCounts)++;

      if (*SenseCounts == MAXSENSEKEY) {
        break;
      }

    } else {
      //
      // when no sense key, skip out the loop
      //
      SenseReq = FALSE;
    }
  }

  return EFI_SUCCESS;
}

/**
  Sends out ATAPI Read Capacity Packet Command to the specified device.
  This command will return the information regarding the capacity of the
  media in the device.

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

  @retval EFI_SUCCESS           Command executed successfully.
  @retval EFI_DEVICE_ERROR      Some device errors happen.

**/
EFI_STATUS
PeiUsbReadCapacity (
  IN  EFI_PEI_SERVICES  **PeiServices,
  IN  PEI_BOT_DEVICE    *PeiBotDevice
  )
{
  EFI_STATUS                  Status;
  ATAPI_PACKET_COMMAND        Packet;
  ATAPI_READ_CAPACITY_DATA    Data;
  UINT32                      LastBlock;

  ZeroMem (&Data, sizeof (ATAPI_READ_CAPACITY_DATA));
  ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));

  Packet.Inquiry.opcode = ATA_CMD_READ_CAPACITY;

  //
  // send command packet
  //
  Status = PeiAtapiCommand (
            PeiServices,
            PeiBotDevice,
            &Packet,
            (UINT8) sizeof (ATAPI_PACKET_COMMAND),
            (VOID *) &Data,
            sizeof (ATAPI_READ_CAPACITY_DATA),
            EfiUsbDataIn,
            2000
            );

  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }
  LastBlock = ((UINT32) Data.LastLba3 << 24) | (Data.LastLba2 << 16) | (Data.LastLba1 << 8) | Data.LastLba0;

  if (LastBlock == 0xFFFFFFFF) {
    DEBUG ((EFI_D_INFO, "The usb device LBA count is larger than 0xFFFFFFFF!\n"));
  }

  PeiBotDevice->Media.LastBlock    = LastBlock;
  PeiBotDevice->Media.MediaPresent = TRUE;

  PeiBotDevice->Media2.LastBlock    = LastBlock;
  PeiBotDevice->Media2.MediaPresent = TRUE;

  return EFI_SUCCESS;
}

/**
  Sends out ATAPI Read Format Capacity Data Command to the specified device.
  This command will return the information regarding the capacity of the
  media in the device.

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

  @retval EFI_SUCCESS           Command executed successfully.
  @retval EFI_DEVICE_ERROR      Some device errors happen.

**/
EFI_STATUS
PeiUsbReadFormattedCapacity (
  IN  EFI_PEI_SERVICES  **PeiServices,
  IN  PEI_BOT_DEVICE    *PeiBotDevice
  )
{
  EFI_STATUS                      Status;
  ATAPI_PACKET_COMMAND            Packet;
  ATAPI_READ_FORMAT_CAPACITY_DATA FormatData;
  UINT32                          LastBlock;

  ZeroMem (&FormatData, sizeof (ATAPI_READ_FORMAT_CAPACITY_DATA));
  ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));

  Packet.ReadFormatCapacity.opcode                = ATA_CMD_READ_FORMAT_CAPACITY;
  Packet.ReadFormatCapacity.allocation_length_lo  = 12;

  //
  // send command packet
  //
  Status = PeiAtapiCommand (
            PeiServices,
            PeiBotDevice,
            &Packet,
            (UINT8) sizeof (ATAPI_PACKET_COMMAND),
            (VOID *) &FormatData,
            sizeof (ATAPI_READ_FORMAT_CAPACITY_DATA),
            EfiUsbDataIn,
            2000
            );

  if (EFI_ERROR (Status)) {
    return EFI_DEVICE_ERROR;
  }

  if (FormatData.DesCode == 3) {
    //
    // Media is not present
    //
    PeiBotDevice->Media.MediaPresent  = FALSE;
    PeiBotDevice->Media.LastBlock     = 0;
    PeiBotDevice->Media2.MediaPresent  = FALSE;
    PeiBotDevice->Media2.LastBlock     = 0;

  } else {
    LastBlock = ((UINT32) FormatData.LastLba3 << 24) | (FormatData.LastLba2 << 16) | (FormatData.LastLba1 << 8) | FormatData.LastLba0;
    if (LastBlock == 0xFFFFFFFF) {
      DEBUG ((EFI_D_INFO, "The usb device LBA count is larger than 0xFFFFFFFF!\n"));
    }

    PeiBotDevice->Media.LastBlock = LastBlock;

    PeiBotDevice->Media.LastBlock--;

    PeiBotDevice->Media.MediaPresent = TRUE;

    PeiBotDevice->Media2.MediaPresent = TRUE;
    PeiBotDevice->Media2.LastBlock    = PeiBotDevice->Media.LastBlock;
  }

  return EFI_SUCCESS;
}

/**
  Execute Read(10) ATAPI command on a specific SCSI target.

  Executes the ATAPI Read(10) command on the ATAPI target specified by PeiBotDevice.

  @param PeiServices       The pointer of EFI_PEI_SERVICES.
  @param PeiBotDevice      The pointer to PEI_BOT_DEVICE instance.
  @param Buffer            The pointer to data buffer.
  @param Lba               The start logic block address of reading.
  @param NumberOfBlocks    The block number of reading.

  @retval EFI_SUCCESS           Command executed successfully.
  @retval EFI_DEVICE_ERROR      Some device errors happen.

**/
EFI_STATUS
PeiUsbRead10 (
  IN  EFI_PEI_SERVICES  **PeiServices,
  IN  PEI_BOT_DEVICE    *PeiBotDevice,
  IN  VOID              *Buffer,
  IN  EFI_PEI_LBA       Lba,
  IN  UINTN             NumberOfBlocks
  )
{
  ATAPI_PACKET_COMMAND  Packet;
  ATAPI_READ10_CMD      *Read10Packet;
  UINT16                MaxBlock;
  UINT16                BlocksRemaining;
  UINT16                SectorCount;
  UINT32                Lba32;
  UINT32                BlockSize;
  UINT32                ByteCount;
  VOID                  *PtrBuffer;
  EFI_STATUS            Status;
  UINT16                TimeOut;

  //
  // prepare command packet for the Inquiry Packet Command.
  //
  ZeroMem (&Packet, sizeof (ATAPI_PACKET_COMMAND));
  Read10Packet    = &Packet.Read10;
  Lba32           = (UINT32) Lba;
  PtrBuffer       = Buffer;

  BlockSize       = (UINT32) PeiBotDevice->Media.BlockSize;

  MaxBlock        = (UINT16) (65535 / BlockSize);
  BlocksRemaining = (UINT16) NumberOfBlocks;

  Status          = EFI_SUCCESS;
  while (BlocksRemaining > 0) {

    if (BlocksRemaining <= MaxBlock) {

      SectorCount = BlocksRemaining;

    } else {

      SectorCount = MaxBlock;
    }
    //
    // fill the Packet data structure
    //
    Read10Packet->opcode = ATA_CMD_READ_10;

    //
    // Lba0 ~ Lba3 specify the start logical block address of the data transfer.
    // Lba0 is MSB, Lba3 is LSB
    //
    Read10Packet->Lba3  = (UINT8) (Lba32 & 0xff);
    Read10Packet->Lba2  = (UINT8) (Lba32 >> 8);
    Read10Packet->Lba1  = (UINT8) (Lba32 >> 16);
    Read10Packet->Lba0  = (UINT8) (Lba32 >> 24);

    //
    // TranLen0 ~ TranLen1 specify the transfer length in block unit.
    // TranLen0 is MSB, TranLen is LSB
    //
    Read10Packet->TranLen1  = (UINT8) (SectorCount & 0xff);
    Read10Packet->TranLen0  = (UINT8) (SectorCount >> 8);

    ByteCount               = SectorCount * BlockSize;

    TimeOut                 = (UINT16) (SectorCount * 2000);

    //
    // send command packet
    //
    Status = PeiAtapiCommand (
              PeiServices,
              PeiBotDevice,
              &Packet,
              (UINT8) sizeof (ATAPI_PACKET_COMMAND),
              (VOID *) PtrBuffer,
              ByteCount,
              EfiUsbDataIn,
              TimeOut
              );

    if (Status != EFI_SUCCESS) {
      return Status;
    }

    Lba32 += SectorCount;
    PtrBuffer       = (UINT8 *) PtrBuffer + SectorCount * BlockSize;
    BlocksRemaining = (UINT16) (BlocksRemaining - SectorCount);
  }

  return Status;
}

/**
  Check if there is media according to sense data.

  @param  SenseData   Pointer to sense data.
  @param  SenseCounts Count of sense data.

  @retval TRUE    No media
  @retval FALSE   Media exists

**/
BOOLEAN
IsNoMedia (
  IN  ATAPI_REQUEST_SENSE_DATA *SenseData,
  IN  UINTN                    SenseCounts
  )
{
  ATAPI_REQUEST_SENSE_DATA  *SensePtr;
  UINTN                     Index;
  BOOLEAN                   NoMedia;

  NoMedia   = FALSE;
  SensePtr  = SenseData;

  for (Index = 0; Index < SenseCounts; Index++) {

    switch (SensePtr->sense_key) {

    case ATA_SK_NOT_READY:
      switch (SensePtr->addnl_sense_code) {
      //
      // if no media, fill IdeDev parameter with specific info.
      //
      case ATA_ASC_NO_MEDIA:
        NoMedia = TRUE;
        break;

      default:
        break;
      }
      break;

    default:
      break;
    }

    SensePtr++;
  }

  return NoMedia;
}

/**
  Check if there is media error according to sense data.

  @param  SenseData   Pointer to sense data.
  @param  SenseCounts Count of sense data.

  @retval TRUE    Media error
  @retval FALSE   No media error

**/
BOOLEAN
IsMediaError (
  IN  ATAPI_REQUEST_SENSE_DATA    *SenseData,
  IN  UINTN                       SenseCounts
  )
{
  ATAPI_REQUEST_SENSE_DATA  *SensePtr;
  UINTN                     Index;
  BOOLEAN                   Error;

  SensePtr  = SenseData;
  Error     = FALSE;

  for (Index = 0; Index < SenseCounts; Index++) {

    switch (SensePtr->sense_key) {
    //
    // Medium error case
    //
    case ATA_SK_MEDIUM_ERROR:
      switch (SensePtr->addnl_sense_code) {
      case ATA_ASC_MEDIA_ERR1:
        //
        // fall through
        //
      case ATA_ASC_MEDIA_ERR2:
        //
        // fall through
        //
      case ATA_ASC_MEDIA_ERR3:
        //
        // fall through
        //
      case ATA_ASC_MEDIA_ERR4:
        Error = TRUE;
        break;

      default:
        break;
      }

      break;

    //
    // Medium upside-down case
    //
    case ATA_SK_NOT_READY:
      switch (SensePtr->addnl_sense_code) {
      case ATA_ASC_MEDIA_UPSIDE_DOWN:
        Error = TRUE;
        break;

      default:
        break;
      }
      break;

    default:
      break;
    }

    SensePtr++;
  }

  return Error;
}

/**
  Check if media is changed according to sense data.

  @param  SenseData   Pointer to sense data.
  @param  SenseCounts Count of sense data.

  @retval TRUE    There is media change event.
  @retval FALSE   media is NOT changed.

**/
BOOLEAN
IsMediaChange (
  IN  ATAPI_REQUEST_SENSE_DATA *SenseData,
  IN  UINTN                    SenseCounts
  )
{
  ATAPI_REQUEST_SENSE_DATA  *SensePtr;
  UINTN                     Index;
  BOOLEAN                   MediaChange;

  MediaChange = FALSE;

  SensePtr    = SenseData;

  for (Index = 0; Index < SenseCounts; Index++) {
    //
    // catch media change sense key and addition sense data
    //
    switch (SensePtr->sense_key) {
    case ATA_SK_UNIT_ATTENTION:
      switch (SensePtr->addnl_sense_code) {
      case ATA_ASC_MEDIA_CHANGE:
        MediaChange = TRUE;
        break;

      default:
        break;
      }
      break;

    default:
      break;
    }

    SensePtr++;
  }

  return MediaChange;
}
