/** @file
Pei USB ATAPI command implementations.

Copyright (c) 1999 - 2018, Intel Corporation. All rights reserved.<BR>
Copyright (C) 2022 Advanced Micro Devices, Inc. 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 ((DEBUG_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 ((DEBUG_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;
  UINT32                BlocksRemaining;
  UINT32                SectorCount;
  UINT32                Lba32;
  UINT32                BlockSize;
  UINT32                ByteCount;
  VOID                  *PtrBuffer;
  EFI_STATUS            Status;
  UINT32                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)(MAX_UINT16 / BlockSize);
  ASSERT (NumberOfBlocks < MAX_UINT32);
  BlocksRemaining = (UINT32)NumberOfBlocks;

  Status = EFI_SUCCESS;
  while (BlocksRemaining > 0) {
    SectorCount = MIN (BlocksRemaining, 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 = SectorCount * 2000;

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

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

    ASSERT (Lba32 <= (MAX_UINT32-SectorCount));
    Lba32          += SectorCount;
    PtrBuffer       = (UINT8 *)PtrBuffer + SectorCount * BlockSize;
    BlocksRemaining = 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;
}
