/**
  ******************************************************************************
  * @file    usbh_msc_scsi.c
  * @author  MCD Application Team
  * @brief   This file implements the SCSI commands
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2015 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */

/* BSPDependencies
- "stm32xxxxx_{eval}{discovery}{nucleo_144}.c"
- "stm32xxxxx_{eval}{discovery}_io.c"
- "stm32xxxxx_{eval}{discovery}{adafruit}_lcd.c"
- "stm32xxxxx_{eval}{discovery}_sdram.c"
EndBSPDependencies */

/* Includes ------------------------------------------------------------------*/
#include "usbh_msc.h"
#include "usbh_msc_scsi.h"
#include "usbh_msc_bot.h"


/** @addtogroup USBH_LIB
  * @{
  */

/** @addtogroup USBH_CLASS
  * @{
  */

/** @addtogroup USBH_MSC_CLASS
  * @{
  */

/** @defgroup USBH_MSC_SCSI
  * @brief    This file includes the mass storage related functions
  * @{
  */


/** @defgroup USBH_MSC_SCSI_Private_TypesDefinitions
  * @{
  */

/**
  * @}
  */

/** @defgroup USBH_MSC_SCSI_Private_Defines
  * @{
  */
/**
  * @}
  */

/** @defgroup USBH_MSC_SCSI_Private_Macros
  * @{
  */
/**
  * @}
  */


/** @defgroup USBH_MSC_SCSI_Private_FunctionPrototypes
  * @{
  */
/**
  * @}
  */


/** @defgroup USBH_MSC_SCSI_Exported_Variables
  * @{
  */

/**
  * @}
  */


/** @defgroup USBH_MSC_SCSI_Private_Functions
  * @{
  */


/**
  * @brief  USBH_MSC_SCSI_TestUnitReady
  *         Issue TestUnitReady command.
  * @param  phost: Host handle
  * @param  lun: Logical Unit Number
  * @retval USBH Status
  */
USBH_StatusTypeDef USBH_MSC_SCSI_TestUnitReady(USBH_HandleTypeDef *phost,
                                               uint8_t lun)
{
  USBH_StatusTypeDef error = USBH_FAIL;
  MSC_HandleTypeDef *MSC_Handle = (MSC_HandleTypeDef *) phost->pActiveClass->pData;

  switch (MSC_Handle->hbot.cmd_state)
  {
    case BOT_CMD_SEND:

      /* Prepare the CBW and relevant field */
      MSC_Handle->hbot.cbw.field.DataTransferLength = DATA_LEN_MODE_TEST_UNIT_READY;
      MSC_Handle->hbot.cbw.field.Flags = USB_EP_DIR_OUT;
      MSC_Handle->hbot.cbw.field.CBLength = CBW_LENGTH;

      (void)USBH_memset(MSC_Handle->hbot.cbw.field.CB, 0, CBW_CB_LENGTH);
      MSC_Handle->hbot.cbw.field.CB[0] = OPCODE_TEST_UNIT_READY;

      MSC_Handle->hbot.state = BOT_SEND_CBW;
      MSC_Handle->hbot.cmd_state = BOT_CMD_WAIT;
      error = USBH_BUSY;
      break;

    case BOT_CMD_WAIT:
      error = USBH_MSC_BOT_Process(phost, lun);
      break;

    default:
      break;
  }

  return error;
}

/**
  * @brief  USBH_MSC_SCSI_ReadCapacity
  *         Issue Read Capacity command.
  * @param  phost: Host handle
  * @param  lun: Logical Unit Number
  * @param  capacity: pointer to the capacity structure
  * @retval USBH Status
  */
USBH_StatusTypeDef USBH_MSC_SCSI_ReadCapacity(USBH_HandleTypeDef *phost,
                                              uint8_t lun,
                                              SCSI_CapacityTypeDef *capacity)
{
  USBH_StatusTypeDef error = USBH_BUSY;
  MSC_HandleTypeDef *MSC_Handle = (MSC_HandleTypeDef *) phost->pActiveClass->pData;

  switch (MSC_Handle->hbot.cmd_state)
  {
    case BOT_CMD_SEND:

      /* Prepare the CBW and relevant field */
      MSC_Handle->hbot.cbw.field.DataTransferLength = DATA_LEN_READ_CAPACITY10;
      MSC_Handle->hbot.cbw.field.Flags = USB_EP_DIR_IN;
      MSC_Handle->hbot.cbw.field.CBLength = CBW_LENGTH;

      (void)USBH_memset(MSC_Handle->hbot.cbw.field.CB, 0, CBW_CB_LENGTH);
      MSC_Handle->hbot.cbw.field.CB[0]  = OPCODE_READ_CAPACITY10;

      MSC_Handle->hbot.state = BOT_SEND_CBW;

      MSC_Handle->hbot.cmd_state = BOT_CMD_WAIT;
      MSC_Handle->hbot.pbuf = (uint8_t *)(void *)MSC_Handle->hbot.data;
      error = USBH_BUSY;
      break;

    case BOT_CMD_WAIT:

      error = USBH_MSC_BOT_Process(phost, lun);

      if (error == USBH_OK)
      {
        /* Assign the capacity */
        capacity->block_nbr = MSC_Handle->hbot.pbuf[3] | ((uint32_t)MSC_Handle->hbot.pbuf[2] << 8U) | \
                              ((uint32_t)MSC_Handle->hbot.pbuf[1] << 16U) | ((uint32_t)MSC_Handle->hbot.pbuf[0] << 24U);

        /* Assign the page length */
        capacity->block_size = (uint16_t)(MSC_Handle->hbot.pbuf[7] | ((uint32_t)MSC_Handle->hbot.pbuf[6] << 8U));
      }
      break;

    default:
      break;
  }

  return error;
}

/**
  * @brief  USBH_MSC_SCSI_Inquiry
  *         Issue Inquiry command.
  * @param  phost: Host handle
  * @param  lun: Logical Unit Number
  * @param  capacity: pointer to the inquiry structure
  * @retval USBH Status
  */
USBH_StatusTypeDef USBH_MSC_SCSI_Inquiry(USBH_HandleTypeDef *phost, uint8_t lun,
                                         SCSI_StdInquiryDataTypeDef *inquiry)
{
  USBH_StatusTypeDef error = USBH_FAIL;
  MSC_HandleTypeDef *MSC_Handle = (MSC_HandleTypeDef *) phost->pActiveClass->pData;

  switch (MSC_Handle->hbot.cmd_state)
  {
    case BOT_CMD_SEND:

      /* Prepare the CBW and relevant field */
      MSC_Handle->hbot.cbw.field.DataTransferLength = DATA_LEN_INQUIRY;
      MSC_Handle->hbot.cbw.field.Flags = USB_EP_DIR_IN;
      MSC_Handle->hbot.cbw.field.CBLength = CBW_LENGTH;

      (void)USBH_memset(MSC_Handle->hbot.cbw.field.CB, 0, CBW_LENGTH);
      MSC_Handle->hbot.cbw.field.CB[0] = OPCODE_INQUIRY;
      MSC_Handle->hbot.cbw.field.CB[1] = (lun << 5);
      MSC_Handle->hbot.cbw.field.CB[2] = 0U;
      MSC_Handle->hbot.cbw.field.CB[3] = 0U;
      MSC_Handle->hbot.cbw.field.CB[4] = 0x24U;
      MSC_Handle->hbot.cbw.field.CB[5] = 0U;

      MSC_Handle->hbot.state = BOT_SEND_CBW;

      MSC_Handle->hbot.cmd_state = BOT_CMD_WAIT;
      MSC_Handle->hbot.pbuf = (uint8_t *)(void *)MSC_Handle->hbot.data;
      error = USBH_BUSY;
      break;

    case BOT_CMD_WAIT:

      error = USBH_MSC_BOT_Process(phost, lun);

      if (error == USBH_OK)
      {
        (void)USBH_memset(inquiry, 0, sizeof(SCSI_StdInquiryDataTypeDef));
        /* Assign Inquiry Data */
        inquiry->DeviceType = MSC_Handle->hbot.pbuf[0] & 0x1FU;
        inquiry->PeripheralQualifier = MSC_Handle->hbot.pbuf[0] >> 5U;

        if (((uint32_t)MSC_Handle->hbot.pbuf[1] & 0x80U) == 0x80U)
        {
          inquiry->RemovableMedia = 1U;
        }
        else
        {
          inquiry->RemovableMedia = 0U;
        }

        (void)USBH_memcpy(inquiry->vendor_id, &MSC_Handle->hbot.pbuf[8], 8U);
        (void)USBH_memcpy(inquiry->product_id, &MSC_Handle->hbot.pbuf[16], 16U);
        (void)USBH_memcpy(inquiry->revision_id, &MSC_Handle->hbot.pbuf[32], 4U);
      }
      break;

    default:
      break;
  }

  return error;
}

/**
  * @brief  USBH_MSC_SCSI_RequestSense
  *         Issue RequestSense command.
  * @param  phost: Host handle
  * @param  lun: Logical Unit Number
  * @param  capacity: pointer to the sense data structure
  * @retval USBH Status
  */
USBH_StatusTypeDef USBH_MSC_SCSI_RequestSense(USBH_HandleTypeDef *phost,
                                              uint8_t lun,
                                              SCSI_SenseTypeDef *sense_data)
{
  USBH_StatusTypeDef error = USBH_FAIL;
  MSC_HandleTypeDef *MSC_Handle = (MSC_HandleTypeDef *) phost->pActiveClass->pData;

  switch (MSC_Handle->hbot.cmd_state)
  {
    case BOT_CMD_SEND:

      /* Prepare the CBW and relevant field */
      MSC_Handle->hbot.cbw.field.DataTransferLength = DATA_LEN_REQUEST_SENSE;
      MSC_Handle->hbot.cbw.field.Flags = USB_EP_DIR_IN;
      MSC_Handle->hbot.cbw.field.CBLength = CBW_LENGTH;

      (void)USBH_memset(MSC_Handle->hbot.cbw.field.CB, 0, CBW_CB_LENGTH);
      MSC_Handle->hbot.cbw.field.CB[0] = OPCODE_REQUEST_SENSE;
      MSC_Handle->hbot.cbw.field.CB[1] = (lun << 5);
      MSC_Handle->hbot.cbw.field.CB[2] = 0U;
      MSC_Handle->hbot.cbw.field.CB[3] = 0U;
      MSC_Handle->hbot.cbw.field.CB[4] = DATA_LEN_REQUEST_SENSE;
      MSC_Handle->hbot.cbw.field.CB[5] = 0U;

      MSC_Handle->hbot.state = BOT_SEND_CBW;
      MSC_Handle->hbot.cmd_state = BOT_CMD_WAIT;
      MSC_Handle->hbot.pbuf = (uint8_t *)(void *)MSC_Handle->hbot.data;
      error = USBH_BUSY;
      break;

    case BOT_CMD_WAIT:

      error = USBH_MSC_BOT_Process(phost, lun);

      if (error == USBH_OK)
      {
        sense_data->key  = MSC_Handle->hbot.pbuf[2] & 0x0FU;
        sense_data->asc  = MSC_Handle->hbot.pbuf[12];
        sense_data->ascq = MSC_Handle->hbot.pbuf[13];

        if (sense_data->asc == SCSI_ASC_MEDIUM_NOT_PRESENT)
        {
          USBH_UsrLog("MSC Device MEDIUM not present");
          error = USBH_UNRECOVERED_ERROR;
        }
      }
      break;

    default:
      break;
  }

  return error;
}

/**
  * @brief  USBH_MSC_SCSI_Write
  *         Issue write10 command.
  * @param  phost: Host handle
  * @param  lun: Logical Unit Number
  * @param  address: sector address
  * @param  pbuf: pointer to data
  * @param  length: number of sector to write
  * @retval USBH Status
  */
USBH_StatusTypeDef USBH_MSC_SCSI_Write(USBH_HandleTypeDef *phost,
                                       uint8_t lun,
                                       uint32_t address,
                                       uint8_t *pbuf,
                                       uint32_t length)
{
  USBH_StatusTypeDef    error = USBH_FAIL;

  MSC_HandleTypeDef *MSC_Handle = (MSC_HandleTypeDef *) phost->pActiveClass->pData;

  switch (MSC_Handle->hbot.cmd_state)
  {
    case BOT_CMD_SEND:

      /* Prepare the CBW and relevant field */
      MSC_Handle->hbot.cbw.field.DataTransferLength = length * MSC_Handle->unit[lun].capacity.block_size;
      MSC_Handle->hbot.cbw.field.Flags = USB_EP_DIR_OUT;
      MSC_Handle->hbot.cbw.field.CBLength = CBW_LENGTH;

      (void)USBH_memset(MSC_Handle->hbot.cbw.field.CB, 0, CBW_CB_LENGTH);
      MSC_Handle->hbot.cbw.field.CB[0] = OPCODE_WRITE10;

      /* Logical block address */
      MSC_Handle->hbot.cbw.field.CB[2] = (((uint8_t *)(void *)&address)[3]);
      MSC_Handle->hbot.cbw.field.CB[3] = (((uint8_t *)(void *)&address)[2]);
      MSC_Handle->hbot.cbw.field.CB[4] = (((uint8_t *)(void *)&address)[1]);
      MSC_Handle->hbot.cbw.field.CB[5] = (((uint8_t *)(void *)&address)[0]);


      /*Transfer length */
      MSC_Handle->hbot.cbw.field.CB[7] = (((uint8_t *)(void *)&length)[1]);
      MSC_Handle->hbot.cbw.field.CB[8] = (((uint8_t *)(void *)&length)[0]);


      MSC_Handle->hbot.state = BOT_SEND_CBW;
      MSC_Handle->hbot.cmd_state = BOT_CMD_WAIT;
      MSC_Handle->hbot.pbuf = pbuf;
      error = USBH_BUSY;
      break;

    case BOT_CMD_WAIT:
      error = USBH_MSC_BOT_Process(phost, lun);
      break;

    default:
      break;
  }

  return error;
}

/**
  * @brief  USBH_MSC_SCSI_Read
  *         Issue Read10 command.
  * @param  phost: Host handle
  * @param  lun: Logical Unit Number
  * @param  address: sector address
  * @param  pbuf: pointer to data
  * @param  length: number of sector to read
  * @retval USBH Status
  */
USBH_StatusTypeDef USBH_MSC_SCSI_Read(USBH_HandleTypeDef *phost,
                                      uint8_t lun,
                                      uint32_t address,
                                      uint8_t *pbuf,
                                      uint32_t length)
{
  USBH_StatusTypeDef error = USBH_FAIL;
  MSC_HandleTypeDef *MSC_Handle = (MSC_HandleTypeDef *) phost->pActiveClass->pData;

  switch (MSC_Handle->hbot.cmd_state)
  {
    case BOT_CMD_SEND:

      /* Prepare the CBW and relevant field */
      MSC_Handle->hbot.cbw.field.DataTransferLength = length * MSC_Handle->unit[lun].capacity.block_size;
      MSC_Handle->hbot.cbw.field.Flags = USB_EP_DIR_IN;
      MSC_Handle->hbot.cbw.field.CBLength = CBW_LENGTH;

      (void)USBH_memset(MSC_Handle->hbot.cbw.field.CB, 0, CBW_CB_LENGTH);
      MSC_Handle->hbot.cbw.field.CB[0] = OPCODE_READ10;

      /* Logical block address */
      MSC_Handle->hbot.cbw.field.CB[2] = (((uint8_t *)(void *)&address)[3]);
      MSC_Handle->hbot.cbw.field.CB[3] = (((uint8_t *)(void *)&address)[2]);
      MSC_Handle->hbot.cbw.field.CB[4] = (((uint8_t *)(void *)&address)[1]);
      MSC_Handle->hbot.cbw.field.CB[5] = (((uint8_t *)(void *)&address)[0]);


      /* Transfer length */
      MSC_Handle->hbot.cbw.field.CB[7] = (((uint8_t *)(void *)&length)[1]);
      MSC_Handle->hbot.cbw.field.CB[8] = (((uint8_t *)(void *)&length)[0]);


      MSC_Handle->hbot.state = BOT_SEND_CBW;
      MSC_Handle->hbot.cmd_state = BOT_CMD_WAIT;
      MSC_Handle->hbot.pbuf = pbuf;
      error = USBH_BUSY;
      break;

    case BOT_CMD_WAIT:
      error = USBH_MSC_BOT_Process(phost, lun);
      break;

    default:
      break;
  }

  return error;
}


/**
  * @}
  */

/**
  * @}
  */

/**
  * @}
  */

/**
  * @}
  */

/**
  * @}
  */



