/*!
 * @file        usbd_composite_msc_scsi.c
 *
 * @brief       usb device msc scsi handler
 *
 * @version     V1.0.0
 *
 * @date        2025-02-15
 *
 * @attention
 *
 *  Copyright (C) 2025 Geehy Semiconductor
 *
 *  You may not use this file except in compliance with the
 *  GEEHY COPYRIGHT NOTICE (GEEHY SOFTWARE PACKAGE LICENSE).
 *
 *  The program is only for reference, which is distributed in the hope
 *  that it will be useful and instructional for customers to develop
 *  their software. Unless required by applicable law or agreed to in
 *  writing, the program is distributed on an "AS IS" BASIS, WITHOUT
 *  ANY WARRANTY OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the GEEHY SOFTWARE PACKAGE LICENSE for the governing permissions
 *  and limitations under the License.
 */

/* Includes ***************************************************************/
#include "usbd_composite_msc_scsi.h"

/* Private includes *******************************************************/
#include "usbd_composite.h"
#include "usbd_composite_msc_bot.h"
#include "usbd_dataXfer.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Private macro **********************************************************/

/* Private typedef ********************************************************/

/* Private variables ******************************************************/

/* USB mass storage page 00 inquiry data */
uint8_t page00InquiryData[USBD_LEN_INQUIRY_PAGE00] =
{
    0x00,
    0x00,
    0x00,
    (USBD_LEN_INQUIRY_PAGE00 - 4U),
    0x00,
    0x80
};

/* USB mass storage page 80 inquiry data */
uint8_t page80InquiryData[USBD_LEN_INQUIRY_PAGE80] =
{
    0x00,
    0x80,
    0x00,
    USBD_LEN_INQUIRY_PAGE80,
    0x20,
    0x20,
    0x20,
    0x20
};

/* USB mass storage sense 6 data */
uint8_t modeSense6data[USBD_LEN_STD_MODE_SENSE6] =
{
    0x22,
    0x00,
    0x00,
    0x00,
    0x08,
    0x12,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00
};

/* USB Mass storage sense 10  Data */
uint8_t modeSense10data[USBD_LEN_STD_MODE_SENSE10] =
{
    0x00,
    0x26,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x08,
    0x12,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00
};

/* Private function prototypes ********************************************/

/* External variables *****************************************************/

/* External functions *****************************************************/

/*!
 * @brief     Put the sense code to array
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     key: sense Key
 *
 * @param     asc: Additional Sense Code
 *
 * @param     ascq: Additional Sense Code Qualifier
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_CodeSense(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t key, uint8_t asc, uint8_t ascq)
{
    USBD_STA_T  usbStatus = USBD_OK;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    UNUSED(lun);

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    usbDevComposite->mscInfo.usbDevSCSI.sense[usbDevComposite->mscInfo.usbDevSCSI.senseEnd].Key = key;
    usbDevComposite->mscInfo.usbDevSCSI.sense[usbDevComposite->mscInfo.usbDevSCSI.senseEnd].ASC = asc;
    usbDevComposite->mscInfo.usbDevSCSI.sense[usbDevComposite->mscInfo.usbDevSCSI.senseEnd].ASCQ = ascq;
    usbDevComposite->mscInfo.usbDevSCSI.senseEnd++;

    if (usbDevComposite->mscInfo.usbDevSCSI.senseEnd == USBD_SCSI_SENSE_LIST_NUMBER)
    {
        usbDevComposite->mscInfo.usbDevSCSI.senseEnd = 0;
    }

    return usbStatus;
}

/*!
 * @brief     USB device SCSI configure BOT data
 *
 * @param     usbInfo : usb handler information
 *
 * @param     buffer : data buffer
 *
 * @param     length: data length
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_ConfigBotData(USBD_INFO_T* usbInfo, uint8_t* buffer, uint16_t length)
{
    USBD_STA_T  usbStatus = USBD_BUSY;

    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;
    uint16_t i;

    if (usbDevComposite == NULL)
    {
        return USBD_OK;
    }

    usbDevComposite->mscInfo.usbDevBOT.dataLen = length;

    for (i = 0; i < length; i++)
    {
        usbDevComposite->mscInfo.usbDevBOT.data[i] = buffer[i];
    }

    return usbStatus;
}

/*!
 * @brief     USB device SCSI read capacity 16 command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_ReadCapacity16(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    uint8_t reqStatus = USBD_BUSY;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    UNUSED(command);

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryReadCapacity(lun, \
                &usbDevComposite->mscInfo.usbDevSCSI.blockNum, &usbDevComposite->mscInfo.usbDevSCSI.blockSize);

    if ((reqStatus != USBD_OK) || (usbDevComposite->mscInfo.usbDevSCSI.mediumState == USBD_SCSI_MEDIUM_EJECT))
    {
        USBD_SCSI_CodeSense(usbInfo, lun, \
                            USBD_SCSI_SENSE_KEY_NOT_READY, \
                            USBD_SCSI_ASC_MEDIUM_NOT_PRESENT, \
                            0);

        return USBD_FAIL;
    }

    usbDevComposite->mscInfo.usbDevBOT.dataLen = (uint32_t)((command[10] << 24) | \
                                   (command[11] << 16) | \
                                   (command[12] << 8) | \
                                   (command[13]));

    memset(usbDevComposite->mscInfo.usbDevBOT.data, 0, usbDevComposite->mscInfo.usbDevBOT.dataLen);

    usbDevComposite->mscInfo.usbDevBOT.data[4] = (uint8_t)((usbDevComposite->mscInfo.usbDevSCSI.blockNum - 1) >> 24);
    usbDevComposite->mscInfo.usbDevBOT.data[5] = (uint8_t)((usbDevComposite->mscInfo.usbDevSCSI.blockNum - 1) >> 16);
    usbDevComposite->mscInfo.usbDevBOT.data[6] = (uint8_t)((usbDevComposite->mscInfo.usbDevSCSI.blockNum - 1) >> 8);
    usbDevComposite->mscInfo.usbDevBOT.data[7] = (uint8_t)((usbDevComposite->mscInfo.usbDevSCSI.blockNum - 1));

    usbDevComposite->mscInfo.usbDevBOT.data[8] = (uint8_t)((usbDevComposite->mscInfo.usbDevSCSI.blockSize) >> 24);
    usbDevComposite->mscInfo.usbDevBOT.data[9] = (uint8_t)(usbDevComposite->mscInfo.usbDevSCSI.blockSize >> 16);
    usbDevComposite->mscInfo.usbDevBOT.data[10] = (uint8_t)(usbDevComposite->mscInfo.usbDevSCSI.blockSize >> 8);
    usbDevComposite->mscInfo.usbDevBOT.data[11] = (uint8_t)(usbDevComposite->mscInfo.usbDevSCSI.blockSize);

    return usbStatus;
}

/*!
 * @brief     USB device SCSI read capacity command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_ReadCapacity(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    uint8_t reqStatus = USBD_BUSY;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    UNUSED(command);

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryReadCapacity(lun, \
                &usbDevComposite->mscInfo.usbDevSCSI.blockNum, &usbDevComposite->mscInfo.usbDevSCSI.blockSize);

    if ((reqStatus != USBD_OK) || (usbDevComposite->mscInfo.usbDevSCSI.mediumState == USBD_SCSI_MEDIUM_EJECT))
    {
        USBD_SCSI_CodeSense(usbInfo, lun, \
                            USBD_SCSI_SENSE_KEY_NOT_READY, \
                            USBD_SCSI_ASC_MEDIUM_NOT_PRESENT, \
                            0);

        return USBD_FAIL;
    }

    memset(usbDevComposite->mscInfo.usbDevBOT.data, 0, 8);

    usbDevComposite->mscInfo.usbDevBOT.data[0] = (uint8_t)((usbDevComposite->mscInfo.usbDevSCSI.blockNum - 1) >> 24);
    usbDevComposite->mscInfo.usbDevBOT.data[1] = (uint8_t)((usbDevComposite->mscInfo.usbDevSCSI.blockNum - 1) >> 16);
    usbDevComposite->mscInfo.usbDevBOT.data[2] = (uint8_t)((usbDevComposite->mscInfo.usbDevSCSI.blockNum - 1) >> 8);
    usbDevComposite->mscInfo.usbDevBOT.data[3] = (uint8_t)((usbDevComposite->mscInfo.usbDevSCSI.blockNum - 1));

    usbDevComposite->mscInfo.usbDevBOT.data[4] = (uint8_t)((usbDevComposite->mscInfo.usbDevSCSI.blockSize) >> 24);
    usbDevComposite->mscInfo.usbDevBOT.data[5] = (uint8_t)(usbDevComposite->mscInfo.usbDevSCSI.blockSize >> 16);
    usbDevComposite->mscInfo.usbDevBOT.data[6] = (uint8_t)(usbDevComposite->mscInfo.usbDevSCSI.blockSize >> 8);
    usbDevComposite->mscInfo.usbDevBOT.data[7] = (uint8_t)(usbDevComposite->mscInfo.usbDevSCSI.blockSize);

    usbDevComposite->mscInfo.usbDevBOT.dataLen = 8;

    return usbStatus;
}

/*!
 * @brief     USB device SCSI read format capacity command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_ReadFormatCapacity(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    uint8_t reqStatus = USBD_BUSY;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;
    uint16_t blockSize;
    uint32_t blockNum;

    UNUSED(command);

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryReadCapacity(lun, &blockNum, &blockSize);

    if ((reqStatus != USBD_OK) || (usbDevComposite->mscInfo.usbDevSCSI.mediumState == USBD_SCSI_MEDIUM_EJECT))
    {
        USBD_SCSI_CodeSense(usbInfo, lun, \
                            USBD_SCSI_SENSE_KEY_NOT_READY, \
                            USBD_SCSI_ASC_MEDIUM_NOT_PRESENT, \
                            0);

        return USBD_FAIL;
    }

    memset(usbDevComposite->mscInfo.usbDevBOT.data, 0, 12);

    blockNum -= 1;

    usbDevComposite->mscInfo.usbDevBOT.data[3] = 0x08;
    usbDevComposite->mscInfo.usbDevBOT.data[4] = (uint8_t)(blockNum >> 24);
    usbDevComposite->mscInfo.usbDevBOT.data[5] = (uint8_t)(blockNum >> 16);
    usbDevComposite->mscInfo.usbDevBOT.data[6] = (uint8_t)(blockNum >> 8);
    usbDevComposite->mscInfo.usbDevBOT.data[7] = (uint8_t)(blockNum);

    usbDevComposite->mscInfo.usbDevBOT.data[8] = 0x02;
    usbDevComposite->mscInfo.usbDevBOT.data[9] = (uint8_t)(blockSize >> 16);
    usbDevComposite->mscInfo.usbDevBOT.data[10] = (uint8_t)(blockSize >> 8);
    usbDevComposite->mscInfo.usbDevBOT.data[11] = (uint8_t)(blockSize);

    usbDevComposite->mscInfo.usbDevBOT.dataLen = 12;

    return usbStatus;
}

/*!
 * @brief     USB device SCSI inquiry command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_Inquiry(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;
    uint8_t epvd = command[1] & 0x01;
    uint8_t addLen = command[4];
    uint8_t* buffer;
    uint16_t bufferLen;

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    if (usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.dDataXferLen == 0)
    {
        USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                            USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                            USBD_SCSI_ASC_INVALID_CDB, 0);
        return USBD_FAIL;
    }

    /* EPVD is set */
    if (epvd != 0)
    {
        if (command[2] == 0)
        {
            USBD_SCSI_ConfigBotData(usbInfo, page00InquiryData, USBD_LEN_INQUIRY_PAGE00);
        }
        else if (command[2] == 0x80)
        {
            USBD_SCSI_ConfigBotData(usbInfo, page80InquiryData, USBD_LEN_INQUIRY_PAGE80);
        }
        else
        {
            USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                USBD_SCSI_ASC_INVALID_FIELED_IN_COMMAND, 0);
            return USBD_FAIL;
        }
    }
    else
    {
        buffer = (uint8_t*) & ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->inquiryData[lun * USBD_LEN_STD_INQUIRY];
        bufferLen = buffer[4] + 5;

        if (addLen <= bufferLen)
        {
            bufferLen = addLen;
        }

        USBD_SCSI_ConfigBotData(usbInfo, buffer, bufferLen);
    }

    return usbStatus;
}

/*!
 * @brief     USB device SCSI allow medium removable command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_AllowMediumRemoval(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    UNUSED(lun);

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    if (command[4])
    {
        usbDevComposite->mscInfo.usbDevSCSI.mediumState = USBD_SCSI_MEDIUM_LOCK;
    }
    else
    {
        usbDevComposite->mscInfo.usbDevSCSI.mediumState = USBD_SCSI_MEDIUM_UNLOCK;
    }

    usbDevComposite->mscInfo.usbDevBOT.dataLen = 0;

    return usbStatus;
}

/*!
 * @brief     USB device SCSI mode sense 6 command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_ModeSense6(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;
    uint16_t length;

    UNUSED(lun);

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    length = USBD_LEN_STD_MODE_SENSE6;

    if (length >= command[4])
    {
        length = command[4];
    }

    USBD_SCSI_ConfigBotData(usbInfo, modeSense6data, length);

    return usbStatus;
}

/*!
 * @brief     USB device SCSI mode sense 10 command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_ModeSense10(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;
    uint16_t length;

    UNUSED(lun);

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    length = USBD_LEN_STD_MODE_SENSE10;

    if (length >= command[8])
    {
        length = command[8];
    }

    USBD_SCSI_ConfigBotData(usbInfo, modeSense10data, length);

    return usbStatus;
}

/*!
 * @brief     USB device SCSI request sense command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_RequestSense(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    UNUSED(lun);

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    if (usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.dDataXferLen == 0)
    {
        USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                            USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                            USBD_SCSI_ASC_INVALID_CDB, \
                            0);

        return USBD_FAIL;
    }

    memset(usbDevComposite->mscInfo.usbDevBOT.data, 0, USBD_LEN_STD_REQ_SENSE);

    usbDevComposite->mscInfo.usbDevBOT.data[0] = 0x70;
    usbDevComposite->mscInfo.usbDevBOT.data[7] = USBD_LEN_STD_REQ_SENSE - 6;

    if ((usbDevComposite->mscInfo.usbDevSCSI.senseHead != usbDevComposite->mscInfo.usbDevSCSI.senseEnd))
    {
        usbDevComposite->mscInfo.usbDevBOT.data[2] = usbDevComposite->mscInfo.usbDevSCSI.sense[usbDevComposite->mscInfo.usbDevSCSI.senseHead].Key;
        usbDevComposite->mscInfo.usbDevBOT.data[12] = usbDevComposite->mscInfo.usbDevSCSI.sense[usbDevComposite->mscInfo.usbDevSCSI.senseHead].ASC;
        usbDevComposite->mscInfo.usbDevBOT.data[13] = usbDevComposite->mscInfo.usbDevSCSI.sense[usbDevComposite->mscInfo.usbDevSCSI.senseHead].ASCQ;

        if (++usbDevComposite->mscInfo.usbDevSCSI.senseHead == USBD_SCSI_SENSE_LIST_NUMBER)
        {
            usbDevComposite->mscInfo.usbDevSCSI.senseHead = 0;
        }
    }

    usbDevComposite->mscInfo.usbDevBOT.dataLen = (command[4] <= USBD_LEN_STD_REQ_SENSE) ? \
                                   command[4] : USBD_LEN_STD_REQ_SENSE;

    return usbStatus;
}

/*!
 * @brief     USB device SCSI start or stop unit command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_StartStopUnit(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    uint8_t temp = command[4] & 0x03;

    UNUSED(lun);

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    if ((usbDevComposite->mscInfo.usbDevSCSI.mediumState == USBD_SCSI_MEDIUM_LOCK) && \
            (temp == 0x02))
    {
        USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                            USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                            USBD_SCSI_ASC_INVALID_FIELED_IN_COMMAND, \
                            0);

        return USBD_FAIL;
    }

    switch (temp)
    {
        /* START=1 */
        case 0x01:
            usbDevComposite->mscInfo.usbDevSCSI.mediumState = USBD_SCSI_MEDIUM_UNLOCK;
            break;

        /* START=0 and LOEJ Load Eject=1 */
        case 0x02:
            usbDevComposite->mscInfo.usbDevSCSI.mediumState = USBD_SCSI_MEDIUM_EJECT;
            break;

        /* START=1 and LOEJ Load Eject=1 */
        case 0x03:
            usbDevComposite->mscInfo.usbDevSCSI.mediumState = USBD_SCSI_MEDIUM_UNLOCK;
            break;

        default:
            break;
    }

    usbDevComposite->mscInfo.usbDevBOT.dataLen = 0;

    return usbStatus;
}

/*!
 * @brief     USB device SCSI test unit ready command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_TestUnitReady(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    uint8_t reqStatus = USBD_BUSY;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    UNUSED(command);

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    if (usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.dDataXferLen != 0)
    {
        USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                            USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                            USBD_SCSI_ASC_INVALID_CDB, \
                            0);

        return USBD_FAIL;
    }

    if (usbDevComposite->mscInfo.usbDevSCSI.mediumState == USBD_SCSI_MEDIUM_EJECT)
    {
        USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                            USBD_SCSI_SENSE_KEY_NOT_READY, \
                            USBD_SCSI_ASC_MEDIUM_NOT_PRESENT, \
                            0);

        usbDevComposite->mscInfo.usbDevBOT.state = USBD_BOT_NO_DATA;

        return USBD_FAIL;
    }

    reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryCheckReady(lun);

    if (reqStatus != USBD_OK)
    {
        USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                            USBD_SCSI_SENSE_KEY_NOT_READY, \
                            USBD_SCSI_ASC_MEDIUM_NOT_PRESENT, \
                            0);

        usbDevComposite->mscInfo.usbDevBOT.state = USBD_BOT_NO_DATA;

        return USBD_FAIL;
    }

    usbDevComposite->mscInfo.usbDevBOT.dataLen = 0;

    return usbStatus;
}

/*!
 * @brief     USB device SCSI write data handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_TxData(USBD_INFO_T* usbInfo, uint8_t lun)
{
    USBD_STA_T  usbStatus = USBD_OK;
    uint8_t reqStatus = USBD_BUSY;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;
    uint32_t length;
    uint32_t blockSize = usbDevComposite->mscInfo.usbDevSCSI.blockSize;
    uint32_t blockLen = usbDevComposite->mscInfo.usbDevSCSI.blockLen;
    uint32_t blockAddr = usbDevComposite->mscInfo.usbDevSCSI.blockAddr;

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    length = (blockLen * blockSize) < USBD_SUP_MSC_MEDIA_PACKET ? \
             (blockLen * blockSize) : USBD_SUP_MSC_MEDIA_PACKET;

    reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryWriteData(lun, \
                usbDevComposite->mscInfo.usbDevBOT.data, \
                blockAddr, \
                (length / blockSize));

    if (reqStatus != USBD_OK)
    {
        USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                            USBD_SCSI_SENSE_KEY_HARDWARE_ERROR, \
                            USBD_SCSI_ASC_WRITE_FAULT, \
                            0);

        return USBD_FAIL;
    }

    usbDevComposite->mscInfo.usbDevSCSI.blockAddr += (length / blockSize);
    usbDevComposite->mscInfo.usbDevSCSI.blockLen -= (length / blockSize);

    usbDevComposite->mscInfo.usbDevBOT.cmdPack.CSW.DATA_FIELD.dDataResidue -= length;

    if (usbDevComposite->mscInfo.usbDevSCSI.blockLen)
    {
        length = (usbDevComposite->mscInfo.usbDevSCSI.blockLen * usbDevComposite->mscInfo.usbDevSCSI.blockSize) < USBD_SUP_MSC_MEDIA_PACKET ? \
                 (usbDevComposite->mscInfo.usbDevSCSI.blockLen * usbDevComposite->mscInfo.usbDevSCSI.blockSize) : USBD_SUP_MSC_MEDIA_PACKET;

        USBD_EP_ReceiveCallback(usbInfo, usbDevComposite->mscInfo.epOutAddr, \
                                usbDevComposite->mscInfo.usbDevBOT.data, \
                                length);
    }
    else
    {
        USBD_MSC_BOT_SendCSW(usbInfo, USBD_BOT_CSW_OK);
    }

    return usbStatus;
}

/*!
 * @brief     USB device SCSI read data handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_RxData(USBD_INFO_T* usbInfo, uint8_t lun)
{
    USBD_STA_T  usbStatus = USBD_OK;
    uint8_t reqStatus = USBD_BUSY;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;
    uint32_t length;

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    length = (usbDevComposite->mscInfo.usbDevSCSI.blockLen * usbDevComposite->mscInfo.usbDevSCSI.blockSize) < USBD_SUP_MSC_MEDIA_PACKET ? \
             (usbDevComposite->mscInfo.usbDevSCSI.blockLen * usbDevComposite->mscInfo.usbDevSCSI.blockSize) : USBD_SUP_MSC_MEDIA_PACKET;

    reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryReadData(lun, \
                usbDevComposite->mscInfo.usbDevBOT.data, \
                usbDevComposite->mscInfo.usbDevSCSI.blockAddr, \
                (length / usbDevComposite->mscInfo.usbDevSCSI.blockSize));

    if (reqStatus != USBD_OK)
    {
        USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                            USBD_SCSI_SENSE_KEY_HARDWARE_ERROR, \
                            USBD_SCSI_ASC_UNRECOVERED_READ_ERROR, \
                            0);

        return USBD_FAIL;
    }

    USBD_EP_TransferCallback(usbInfo, usbDevComposite->mscInfo.epInAddr, \
                             usbDevComposite->mscInfo.usbDevBOT.data, \
                             length);

    usbDevComposite->mscInfo.usbDevSCSI.blockAddr += (length / usbDevComposite->mscInfo.usbDevSCSI.blockSize);
    usbDevComposite->mscInfo.usbDevSCSI.blockLen -= (length / usbDevComposite->mscInfo.usbDevSCSI.blockSize);

    usbDevComposite->mscInfo.usbDevBOT.cmdPack.CSW.DATA_FIELD.dDataResidue -= length;

    if (usbDevComposite->mscInfo.usbDevSCSI.blockLen == 0)
    {
        usbDevComposite->mscInfo.usbDevBOT.state = USBD_BOT_DATAIN_LAST;
    }

    return usbStatus;
}

/*!
 * @brief     USB device SCSI write 10 command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_Write10(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    uint8_t reqStatus = USBD_BUSY;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    uint32_t length;

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    switch (usbDevComposite->mscInfo.usbDevBOT.state)
    {
        case USBD_BOT_IDLE:
            if (usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.dDataXferLen == 0)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                    USBD_SCSI_ASC_INVALID_CDB, \
                                    0);

                return USBD_FAIL;
            }

            if ((usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bmFlags.CBW_Flag & 0x80) == 0x80)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                    USBD_SCSI_ASC_INVALID_CDB, \
                                    0);

                return USBD_FAIL;
            }

            reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryCheckReady(lun);

            if (reqStatus != USBD_OK)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_NOT_READY, \
                                    USBD_SCSI_ASC_MEDIUM_NOT_PRESENT, \
                                    0);

                return USBD_FAIL;
            }

            reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryCheckWPR(lun);

            if (reqStatus != USBD_OK)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_NOT_READY, \
                                    USBD_SCSI_ASC_WRITE_PROTECTED, \
                                    0);

                return USBD_FAIL;
            }

            /* Store write block information */
            usbDevComposite->mscInfo.usbDevSCSI.blockAddr = (uint32_t)((command[2] << 24) | \
                                              (command[3] << 16) | \
                                              (command[4] << 8) | \
                                              (command[5]));

            usbDevComposite->mscInfo.usbDevSCSI.blockLen = (uint32_t)((command[7] << 8) | \
                                             (command[8]));

            /* check if LBA address is in the right range */
            if ((usbDevComposite->mscInfo.usbDevSCSI.blockAddr + usbDevComposite->mscInfo.usbDevSCSI.blockLen) > \
                    usbDevComposite->mscInfo.usbDevSCSI.blockNum)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                    USBD_SCSI_ASC_ADDRESS_OUT_OF_RANGE, \
                                    0);

                return USBD_FAIL;
            }

            length = usbDevComposite->mscInfo.usbDevSCSI.blockLen * usbDevComposite->mscInfo.usbDevSCSI.blockSize;

            if (usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.dDataXferLen != length)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                    USBD_SCSI_ASC_INVALID_CDB, \
                                    0);

                return USBD_FAIL;
            }


            length =  length < USBD_SUP_MSC_MEDIA_PACKET ? \
                      length : USBD_SUP_MSC_MEDIA_PACKET;

            usbDevComposite->mscInfo.usbDevBOT.state = USBD_BOT_DATAOUT;

            USBD_EP_ReceiveCallback(usbInfo, usbDevComposite->mscInfo.epOutAddr, \
                                    usbDevComposite->mscInfo.usbDevBOT.data, \
                                    length);

            break;

        default:
            USBD_SCSI_TxData(usbInfo, lun);
            break;
    }

    return usbStatus;
}

/*!
 * @brief     USB device SCSI write 12 command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_Write12(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    uint8_t reqStatus = USBD_BUSY;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    uint32_t length;

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    switch (usbDevComposite->mscInfo.usbDevBOT.state)
    {
        case USBD_BOT_IDLE:
            if (usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.dDataXferLen == 0)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                    USBD_SCSI_ASC_INVALID_CDB, \
                                    0);

                return USBD_FAIL;
            }

            if ((usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bmFlags.CBW_Flag & 0x80) == 0x80)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                    USBD_SCSI_ASC_INVALID_CDB, \
                                    0);

                return USBD_FAIL;
            }

            reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryCheckReady(lun);

            if (reqStatus != USBD_OK)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_NOT_READY, \
                                    USBD_SCSI_ASC_MEDIUM_NOT_PRESENT, \
                                    0);

                usbDevComposite->mscInfo.usbDevBOT.state = USBD_BOT_NO_DATA;

                return USBD_FAIL;
            }

            reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryCheckWPR(lun);

            if (reqStatus != USBD_OK)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_NOT_READY, \
                                    USBD_SCSI_ASC_WRITE_PROTECTED, \
                                    0);

                usbDevComposite->mscInfo.usbDevBOT.state = USBD_BOT_NO_DATA;

                return USBD_FAIL;
            }

            /* Store write block information */
            usbDevComposite->mscInfo.usbDevSCSI.blockAddr = (uint32_t)((command[2] << 24) | \
                                              (command[3] << 16) | \
                                              (command[4] << 8) | \
                                              (command[5]));

            usbDevComposite->mscInfo.usbDevSCSI.blockLen = (uint32_t)((command[6] << 24) | \
                                             (command[7] << 16) | \
                                             (command[8] << 8) | \
                                             (command[9]));

            /* check if LBA address is in the right range */
            if ((usbDevComposite->mscInfo.usbDevSCSI.blockAddr + usbDevComposite->mscInfo.usbDevSCSI.blockLen) > \
                    usbDevComposite->mscInfo.usbDevSCSI.blockNum)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                    USBD_SCSI_ASC_ADDRESS_OUT_OF_RANGE, \
                                    0);

                return USBD_FAIL;
            }

            length = usbDevComposite->mscInfo.usbDevSCSI.blockLen * usbDevComposite->mscInfo.usbDevSCSI.blockSize;

            if (usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.dDataXferLen != length)
            {
                USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                    USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                    USBD_SCSI_ASC_INVALID_CDB, \
                                    0);

                return USBD_FAIL;
            }

            length =  length < USBD_SUP_MSC_MEDIA_PACKET ? \
                      length : USBD_SUP_MSC_MEDIA_PACKET;

            usbDevComposite->mscInfo.usbDevBOT.state = USBD_BOT_DATAOUT;

            USBD_EP_ReceiveCallback(usbInfo, usbDevComposite->mscInfo.epOutAddr, \
                                    usbDevComposite->mscInfo.usbDevBOT.data, \
                                    length);

            break;

        default:
            USBD_SCSI_TxData(usbInfo, lun);
            break;
    }

    return usbStatus;
}

/*!
 * @brief     USB device SCSI verify 10 command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_Verify10(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;

    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    UNUSED(lun);

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    if ((command[1] & 0x02) == 0x02)
    {
        USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                            USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                            USBD_SCSI_ASC_INVALID_FIELED_IN_COMMAND, \
                            0);

        return USBD_FAIL;
    }

    /* check if LBA address is in the right range */
    if ((usbDevComposite->mscInfo.usbDevSCSI.blockAddr + usbDevComposite->mscInfo.usbDevSCSI.blockLen) > \
            usbDevComposite->mscInfo.usbDevSCSI.blockNum)
    {
        USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                            USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                            USBD_SCSI_ASC_ADDRESS_OUT_OF_RANGE, \
                            0);

        return USBD_FAIL;
    }

    usbDevComposite->mscInfo.usbDevBOT.dataLen = 0;

    return usbStatus;
}

/*!
 * @brief     USB device SCSI read 10 command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_Read10(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    uint8_t reqStatus = USBD_BUSY;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    if (usbDevComposite->mscInfo.usbDevBOT.state == USBD_BOT_IDLE)
    {
        if ((usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bmFlags.CBW_Flag & 0x80) != 0x80)
        {
            USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                USBD_SCSI_ASC_INVALID_CDB, \
                                0);

            return USBD_FAIL;
        }

        if (usbDevComposite->mscInfo.usbDevSCSI.mediumState == USBD_SCSI_MEDIUM_EJECT)
        {
            USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                USBD_SCSI_SENSE_KEY_NOT_READY, \
                                USBD_SCSI_ASC_MEDIUM_NOT_PRESENT, \
                                0);

            return USBD_FAIL;
        }

        reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryCheckReady(lun);

        if (reqStatus != USBD_OK)
        {
            USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                USBD_SCSI_SENSE_KEY_NOT_READY, \
                                USBD_SCSI_ASC_MEDIUM_NOT_PRESENT, \
                                0);

            return USBD_FAIL;
        }

        /* Store write block information */
        usbDevComposite->mscInfo.usbDevSCSI.blockAddr = (uint32_t)((command[2] << 24) | \
                                          (command[3] << 16) | \
                                          (command[4] << 8) | \
                                          (command[5]));

        usbDevComposite->mscInfo.usbDevSCSI.blockLen = (uint32_t)((command[7] << 8) | \
                                         (command[8]));

        /* check if LBA address is in the right range */
        if ((usbDevComposite->mscInfo.usbDevSCSI.blockAddr + usbDevComposite->mscInfo.usbDevSCSI.blockLen) > \
                usbDevComposite->mscInfo.usbDevSCSI.blockNum)
        {
            USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                USBD_SCSI_ASC_ADDRESS_OUT_OF_RANGE, \
                                0);

            return USBD_FAIL;
        }

        if (usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.dDataXferLen != \
                (usbDevComposite->mscInfo.usbDevSCSI.blockLen * usbDevComposite->mscInfo.usbDevSCSI.blockSize))
        {
            USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                USBD_SCSI_ASC_INVALID_CDB, \
                                0);

            return USBD_FAIL;
        }

        usbDevComposite->mscInfo.usbDevBOT.state = USBD_BOT_DATAIN;
    }

    usbDevComposite->mscInfo.usbDevBOT.dataLen = USBD_SUP_MSC_MEDIA_PACKET;

    usbStatus = USBD_SCSI_RxData(usbInfo, lun);

    return usbStatus;
}

/*!
 * @brief     USB device SCSI read 12 command handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_Read12(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_OK;
    uint8_t reqStatus = USBD_BUSY;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    if (usbDevComposite == NULL)
    {
        return USBD_FAIL;
    }

    if (usbDevComposite->mscInfo.usbDevBOT.state == USBD_BOT_IDLE)
    {
        if ((usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bmFlags.CBW_Flag & 0x80) != 0x80)
        {
            USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                USBD_SCSI_ASC_INVALID_CDB, \
                                0);

            return USBD_FAIL;
        }

        if (usbDevComposite->mscInfo.usbDevSCSI.mediumState == USBD_SCSI_MEDIUM_EJECT)
        {
            USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                USBD_SCSI_SENSE_KEY_NOT_READY, \
                                USBD_SCSI_ASC_MEDIUM_NOT_PRESENT, \
                                0);

            return USBD_FAIL;
        }

        reqStatus = ((USBD_MSC_MEMORY_T*)usbDevComposite->mscInfo.pUserData)->MemoryCheckReady(lun);

        if (reqStatus != USBD_OK)
        {
            USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                USBD_SCSI_SENSE_KEY_NOT_READY, \
                                USBD_SCSI_ASC_MEDIUM_NOT_PRESENT, \
                                0);

            return USBD_FAIL;
        }

        /* Store write block information */
        usbDevComposite->mscInfo.usbDevSCSI.blockAddr = (uint32_t)((command[2] << 24) | \
                                          (command[3] << 16) | \
                                          (command[4] << 8) | \
                                          (command[5]));

        usbDevComposite->mscInfo.usbDevSCSI.blockLen = (uint32_t)((command[6] << 24) | \
                                         (command[7] << 16) | \
                                         (command[8] << 8) | \
                                         (command[9]));

        /* check if LBA address is in the right range */
        if ((usbDevComposite->mscInfo.usbDevSCSI.blockAddr + usbDevComposite->mscInfo.usbDevSCSI.blockLen) > \
                usbDevComposite->mscInfo.usbDevSCSI.blockNum)
        {
            USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                USBD_SCSI_ASC_ADDRESS_OUT_OF_RANGE, \
                                0);

            return USBD_FAIL;
        }

        if (usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.dDataXferLen != \
                (usbDevComposite->mscInfo.usbDevSCSI.blockLen * usbDevComposite->mscInfo.usbDevSCSI.blockSize))
        {
            USBD_SCSI_CodeSense(usbInfo, usbDevComposite->mscInfo.usbDevBOT.cmdPack.CBW.DATA_FIELD.bLUN, \
                                USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                USBD_SCSI_ASC_INVALID_CDB, \
                                0);

            return USBD_FAIL;
        }

        usbDevComposite->mscInfo.usbDevBOT.state = USBD_BOT_DATAIN;
    }

    usbDevComposite->mscInfo.usbDevBOT.dataLen = USBD_SUP_MSC_MEDIA_PACKET;

    usbStatus = USBD_SCSI_RxData(usbInfo, lun);

    return usbStatus;
}

/*!
 * @brief     USB device SCSI handler
 *
 * @param     usbInfo : usb handler information
 *
 * @param     lun : LUN
 *
 * @param     command: SCSI command
 *
 * @retval    USB device operation status
 */
USBD_STA_T USBD_SCSI_Handle(USBD_INFO_T* usbInfo, uint8_t lun, uint8_t* command)
{
    USBD_STA_T  usbStatus = USBD_FAIL;
    USBD_COMPOSITE_INFO_T* usbDevComposite = (USBD_COMPOSITE_INFO_T*)usbInfo->devClass[usbInfo->classID]->classData;

    if (usbDevComposite == NULL)
    {
        return usbStatus;
    }

    switch (command[0])
    {
        case USBD_SCSI_CMD_INQUIRY:
            usbStatus = USBD_SCSI_Inquiry(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_ALLOW_MEDIUM_REMOVAL:
            usbStatus = USBD_SCSI_AllowMediumRemoval(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_MODE_SENSE_6:
            usbStatus = USBD_SCSI_ModeSense6(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_MODE_SENSE_10:
            usbStatus = USBD_SCSI_ModeSense10(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_READ_FORMAT_CAPACITIES:
            usbStatus = USBD_SCSI_ReadFormatCapacity(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_READ_CAPACITY:
            usbStatus = USBD_SCSI_ReadCapacity(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_READ_CAPACITY_16:
            usbStatus = USBD_SCSI_ReadCapacity16(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_REQUEST_SENSE:
            usbStatus = USBD_SCSI_RequestSense(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_START_STOP_UNIT:
            usbStatus = USBD_SCSI_StartStopUnit(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_TEST_UNIT_READY:
            usbStatus = USBD_SCSI_TestUnitReady(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_WRITE10:
            usbStatus = USBD_SCSI_Write10(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_WRITE12:
            usbStatus = USBD_SCSI_Write12(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_VERIFY_10:
            usbStatus = USBD_SCSI_Verify10(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_READ_10:
            usbStatus = USBD_SCSI_Read10(usbInfo, lun, command);
            break;

        case USBD_SCSI_CMD_READ_12:
            usbStatus = USBD_SCSI_Read12(usbInfo, lun, command);
            break;

        default:
            USBD_SCSI_CodeSense(usbInfo, lun, USBD_SCSI_SENSE_KEY_ILLEGAL_REQUEST, \
                                USBD_SCSI_ASC_INVALID_CDB, 0);
            usbDevComposite->mscInfo.usbDevBOT.status = USBD_BOT_ERR;
            usbStatus = USBD_FAIL;
            break;
    }

    return usbStatus;
}
