/*
 * usbhsfs_scsi.c
 *
 * Copyright (c) 2020-2021, DarkMatterCore <pabloacurielz@gmail.com>.
 * Copyright (c) 2020-2021, XorTroll.
 *
 * This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs).
 */

#include "usbhsfs_utils.h"
#include "usbhsfs_request.h"
#include "usbhsfs_scsi.h"

#define SCSI_CBW_SIGNATURE                      0x55534243      /* "USBC". */
#define SCSI_CSW_SIGNATURE                      0x55534253      /* "USBS". */

#define SCSI_ASC_MEDIUM_NOT_PRESENT             0x3A

#define SCSI_MODE_PAGE_CODE_ALL                 0x3F
#define SCSI_MODE_SUBPAGE_CODE_ALL_NO_SUBPAGES  0x00

#define SCSI_READ_CAPACITY_10_MAX_LBA           UINT32_MAX

#define SCSI_RW10_MAX_BLOCK_COUNT               UINT16_MAX

#define SCSI_SERVICE_ACTION_IN_READ_CAPACITY_16 0x10

/* Type definitions. */

/// Reference: https://www.usb.org/sites/default/files/usbmassbulk_10.pdf (page 13).
#pragma pack(push, 1)
typedef struct {
    u32 dCBWSignature;
    u32 dCBWTag;
    u32 dCBWDataTransferLength;
    u8 bmCBWFlags;
    u8 bCBWLUN;
    u8 bCBWCBLength;
    u8 CBWCB[0x10];
} ScsiCommandBlockWrapper;
#pragma pack(pop)

typedef enum {
    ScsiCommandOperationCode_TestUnitReady             = 0x00,
    ScsiCommandOperationCode_RequestSense              = 0x03,
    ScsiCommandOperationCode_Inquiry                   = 0x12,
    ScsiCommandOperationCode_ModeSense6                = 0x1A,
    ScsiCommandOperationCode_StartStopUnit             = 0x1B,
    ScsiCommandOperationCode_PreventAllowMediumRemoval = 0x1E,
    ScsiCommandOperationCode_ReadCapacity10            = 0x25,
    ScsiCommandOperationCode_Read10                    = 0x28,
    ScsiCommandOperationCode_Write10                   = 0x2A,
    ScsiCommandOperationCode_SynchronizeCache10        = 0x35,
    ScsiCommandOperationCode_ModeSense10               = 0x5A,
    ScsiCommandOperationCode_Read16                    = 0x88,
    ScsiCommandOperationCode_Write16                   = 0x8A,
    ScsiCommandOperationCode_SynchronizeCache16        = 0x91,
    ScsiCommandOperationCode_ServiceActionIn           = 0x9E
} ScsiCommandOperationCode;

/// Reference: https://www.usb.org/sites/default/files/usbmassbulk_10.pdf (page 14).
#pragma pack(push, 1)
typedef struct {
    u32 dCSWSignature;
    u32 dCSWTag;
    u32 dCSWDataResidue;
    u8 bCSWStatus;
} ScsiCommandStatusWrapper;
#pragma pack(pop)

/// Reference: https://www.usb.org/sites/default/files/usbmassbulk_10.pdf (page 15).
typedef enum {
    ScsiCommandStatus_Passed     = 0x00,
    ScsiCommandStatus_Failed     = 0x01,
    ScsiCommandStatus_PhaseError = 0x02
} ScsiCommandStatus;

/// Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 56).
/// Followed by additional sense data (not requested).
typedef struct {
    u8 response_code;                       ///< Must either be 0x70 or 0x71.
    u8 segment_number;
    struct {
        u8 sense_key  : 4;
        u8 reserved_1 : 1;
        u8 ili        : 1;                  ///< Incorrect length indicator.
        u8 eom        : 1;                  ///< End-of-medium.
        u8 file_mark  : 1;
    };
    u8 information[0x4];
    u8 additional_sense_length;
    u8 cmd_specific_info[0x4];
    u8 additional_sense_code;
    u8 additional_sense_code_qualifier;
    u8 field_replaceable_unit_code;
    u8 sense_key_specific[0x3];
} ScsiRequestSenseDataFixedFormat;

/// Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 59).
/// Reference: https://www.stix.id.au/wiki/SCSI_Sense_Data.
typedef enum {
    ScsiSenseKey_NoSense        = 0x00,
    ScsiSenseKey_RecoveredError = 0x01,
    ScsiSenseKey_NotReady       = 0x02,
    ScsiSenseKey_MediumError    = 0x03,
    ScsiSenseKey_HardwareError  = 0x04,
    ScsiSenseKey_IllegalRequest = 0x05,
    ScsiSenseKey_UnitAttention  = 0x06,
    ScsiSenseKey_DataProtect    = 0x07,
    ScsiSenseKey_BlankCheck     = 0x08,
    ScsiSenseKey_VendorSpecific = 0x09,
    ScsiSenseKey_CopyAborted    = 0x0A,
    ScsiSenseKey_AbortedCommand = 0x0B,
    ScsiSenseKey_Reserved       = 0x0C,
    ScsiSenseKey_VolumeOverflow = 0x0D,
    ScsiSenseKey_Miscompare     = 0x0E,
    ScsiSenseKey_Completed      = 0x0F
} ScsiSenseKey;

/// Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 95).
typedef enum {
    ScsiPeripheralQualifier_Connected       = 0,
    ScsiPeripheralQualifier_NotConnected    = 1,
    ScsiPeripheralQualifier_Reserved        = 2,
    ScsiPeripheralQualifier_Unsupported     = 3,
    ScsiPeripheralQualifier_VendorSpecific1 = 4,
    ScsiPeripheralQualifier_VendorSpecific2 = 5,
    ScsiPeripheralQualifier_VendorSpecific3 = 6,
    ScsiPeripheralQualifier_VendorSpecific4 = 7
} ScsiPeripheralQualifier;

/// Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 96).
typedef enum {
    ScsiPeripheralDeviceType_DirectAccessBlock       = 0x00,
    ScsiPeripheralDeviceType_SequentialAccess        = 0x01,
    ScsiPeripheralDeviceType_Printer                 = 0x02,
    ScsiPeripheralDeviceType_Processor               = 0x03,
    ScsiPeripheralDeviceType_WriteOnce               = 0x04,
    ScsiPeripheralDeviceType_CdDvd                   = 0x05,
    ScsiPeripheralDeviceType_Obsolete1               = 0x06,
    ScsiPeripheralDeviceType_OpticalMemory           = 0x07,
    ScsiPeripheralDeviceType_MediumChanger           = 0x08,
    ScsiPeripheralDeviceType_Obsolete2               = 0x09,
    ScsiPeripheralDeviceType_Obsolete3               = 0x0A,
    ScsiPeripheralDeviceType_Obsolete4               = 0x0B,
    ScsiPeripheralDeviceType_StorageArrayController  = 0x0C,
    ScsiPeripheralDeviceType_EnclosureServices       = 0x0D,
    ScsiPeripheralDeviceType_SimplifiedDirectAccess  = 0x0E,
    ScsiPeripheralDeviceType_OpticalCardReaderWriter = 0x0F,
    ScsiPeripheralDeviceType_BridgeControllerCommands = 0x10,
    ScsiPeripheralDeviceType_ObjectBasedStorage       = 0x11,
    ScsiPeripheralDeviceType_AutomationDriveInterface = 0x12,
    ScsiPeripheralDeviceType_Reserved1                = 0x13,
    ScsiPeripheralDeviceType_Reserved2                = 0x14,
    ScsiPeripheralDeviceType_Reserved3                = 0x15,
    ScsiPeripheralDeviceType_Reserved4                = 0x16,
    ScsiPeripheralDeviceType_Reserved5                = 0x17,
    ScsiPeripheralDeviceType_Reserved6                = 0x18,
    ScsiPeripheralDeviceType_Reserved7                = 0x19,
    ScsiPeripheralDeviceType_Reserved8                = 0x1A,
    ScsiPeripheralDeviceType_Reserved9                = 0x1B,
    ScsiPeripheralDeviceType_Reserved10               = 0x1C,
    ScsiPeripheralDeviceType_Reserved11               = 0x1D,
    ScsiPeripheralDeviceType_WellKnownLogicalUnit     = 0x1E,
    ScsiPeripheralDeviceType_Unknown                  = 0x1F
} ScsiPeripheralDeviceType;

/// Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 97).
typedef enum {
    ScsiInquirySPCVersion_SPC  = 0x03,
    ScsiInquirySPCVersion_SPC2 = 0x04,
    ScsiInquirySPCVersion_SPC3 = 0x05,
    ScsiInquirySPCVersion_SPC4 = 0x06,
    ScsiInquirySPCVersion_SPC5 = 0x07
} ScsiInquirySPCVersion;

/// Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 94).
/// Truncated at the product revision level field to just request the bare minimum - we don't need anything else past that point.
typedef struct {
    struct {
        u8 peripheral_device_type : 5;  ///< ScsiPeripheralDeviceType.
        u8 peripheral_qualifier   : 3;  ///< ScsiPeripheralQualifier.
    };
    struct {
        u8 reserved_1 : 7;
        u8 rmb        : 1;              ///< Removable Media Bit.
    };
    u8 version;                         ///< ScsiInquirySPCVersion.
    struct {
        u8 response_data_format : 4;
        u8 hisup                : 1;    ///< Hierarchical Addressing Support.
        u8 naca                 : 1;    ///< Normal Auto Contingent Allegiance.
        u8 reserved_2           : 2;
    };
    u8 additional_length;
    struct {
        u8 protect    : 1;
        u8 reserved_3 : 2;
        u8 _3pc       : 1;              ///< Third-Party Copy Support.
        u8 tpgs       : 2;              ///< Target Port Group Support.
        u8 acc        : 1;              ///< Access Controls Coordinator.
        u8 sccs       : 1;              ///< Embedded storage array controller component.
    };
    struct {
        u8 reserved_4 : 4;
        u8 multip     : 1;              ///< Multi Port.
        u8 vs_1       : 1;              ///< Vendor Specific field #1.
        u8 encserv    : 1;              ///< Enclosure Services.
        u8 reserved_5 : 1;
    };
    struct {
        u8 vs_2       : 1;              ///< Vendor Specific field #2.
        u8 cmdque     : 1;              ///< Command Queuing.
        u8 reserved_6 : 6;
    };
    char vendor_id[0x8];
    char product_id[0x10];
    char product_revision[0x4];
} ScsiInquiryStandardData;

/// Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 111).
typedef enum {
    ScsiModePageControl_CurrentValues    = 0,
    ScsiModePageControl_ChangeableValues = 1,
    ScsiModePageControl_DefaultValues    = 2,
    ScsiModePageControl_SavedValues      = 3
} ScsiModePageControl;

/// Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 378).
typedef struct {
    u8 mode_data_length;    ///< Length of the rest of the data available to be transferred (excluding this field).
    u8 medium_type;
    struct {
        u8 reserved_1 : 4;
        u8 dpofua     : 1;  ///< DPO and FUA support.
        u8 reserved_2 : 2;
        u8 wp         : 1;  ///< Write Protect.
    };
    u8 block_desc_length;   ///< Length in bytes of all of the block descriptors.
} ScsiModeParameterHeader6;

/// Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 378).
typedef struct {
    u16 mode_data_length;   ///< Length of the rest of the data available to be transferred (excluding this field). Stored using big endian byte ordering.
    u8 medium_type;
    struct {
        u8 reserved_1 : 4;
        u8 dpofua     : 1;  ///< DPO and FUA support.
        u8 reserved_2 : 2;
        u8 wp         : 1;  ///< Write Protect.
    };
    struct {
        u8 longlba    : 1;  ///< Long Block Descriptor.
        u8 reserved_3 : 7;
    };
    u8 reserved_4;
    u16 block_desc_length;  ///< Length in bytes of all of the block descriptors. Stored using big endian byte ordering.
} ScsiModeParameterHeader10;

/// Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 156).
typedef struct {
    u32 block_count;    ///< Stored using big endian byte ordering.
    u32 block_length;   ///< Stored using big endian byte ordering.
} ScsiReadCapacity10Data;

/// Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (pages 158 and 159).
typedef struct {
    u64 block_count;            ///< Stored using big endian byte ordering.
    u32 block_length;           ///< Stored using big endian byte ordering.
    struct {
        u8 prot_en    : 1;      ///< Protection enabled.
        u8 p_type     : 3;      ///< Protection type.
        u8 rc_basis   : 2;      ///< Read Capacity Basis.
        u8 reserved_1 : 2;
    };
    struct {
        u8 lb_per_pb_exp : 4;   ///< Logical blocks per physical blocks exponent.
        u8 p_i_exp       : 4;   ///< Protection Information Exponent.
    };
    struct {
        u16 lowest_lba : 14;    ///< Lowest aligned LBA. Stored using big endian byte ordering.
        u16 lbprz      : 1;     ///< Logical Block Provisioning Read Zeros.
        u16 lbpme      : 1;     ///< Logical Block Provisioning Management Enabled.
    };
    u8 reserved_2[0x10];
} ScsiReadCapacity16Data;

static_assert(sizeof(ScsiCommandBlockWrapper) == 0x1F, "Bad ScsiCommandBlockWrapper size! Expected 0x1F.");
static_assert(sizeof(ScsiCommandStatusWrapper) == 0xD, "Bad ScsiCommandStatusWrapper size! Expected 0xD.");
static_assert(sizeof(ScsiRequestSenseDataFixedFormat) == 0x12, "Bad ScsiRequestSenseDataFixedFormat size! Expected 0x12.");
static_assert(sizeof(ScsiInquiryStandardData) == 0x24, "Bad ScsiInquiryStandardData size! Expected 0x24.");
static_assert(sizeof(ScsiModeParameterHeader6) == 0x4, "Bad ScsiModeParameterHeader6 size! Expected 0x4.");
static_assert(sizeof(ScsiModeParameterHeader10) == 0x8, "Bad ScsiModeParameterHeader10 size! Expected 0x8.");
static_assert(sizeof(ScsiReadCapacity10Data) == 0x8, "Bad ScsiReadCapacity10Data size! Expected 0x8.");
static_assert(sizeof(ScsiReadCapacity16Data) == 0x20, "Bad ScsiReadCapacity16Data size! Expected 0x20.");

/* Global variables. */

static __thread bool g_mediumPresent = true;

/* Function prototypes. */

static bool usbHsFsScsiSendTestUnitReadyCommand(UsbHsFsDriveContext *drive_ctx, u8 lun);
static bool usbHsFsScsiSendRequestSenseCommand(UsbHsFsDriveContext *drive_ctx, u8 lun, ScsiRequestSenseDataFixedFormat *sense_data);
static bool usbHsFsScsiSendInquiryCommand(UsbHsFsDriveContext *drive_ctx, u8 lun, ScsiInquiryStandardData *inquiry_data);
static bool usbHsFsScsiSendModeSense6Command(UsbHsFsDriveContext *drive_ctx, u8 lun, u8 page_control, u8 page_code, u8 subpage_code, u8 allocation_length, void *buf);
static bool usbHsFsScsiSendStartStopUnitCommand(UsbHsFsDriveContext *drive_ctx, u8 lun, bool start);
static bool usbHsFsScsiSendPreventAllowMediumRemovalCommand(UsbHsFsDriveContext *drive_ctx, u8 lun, bool prevent);
static bool usbHsFsScsiSendReadCapacity10Command(UsbHsFsDriveContext *drive_ctx, u8 lun, ScsiReadCapacity10Data *read_capacity_10_data);
static bool usbHsFsScsiSendRead10Command(UsbHsFsDriveContext *drive_ctx, u8 lun, void *buf, u32 block_addr, u16 block_count, u32 block_length, bool fua);
static bool usbHsFsScsiSendWrite10Command(UsbHsFsDriveContext *drive_ctx, u8 lun, void *buf, u32 block_addr, u16 block_count, u32 block_length, bool fua);
static bool usbHsFsScsiSendSynchronizeCache10Command(UsbHsFsDriveContext *drive_ctx, u8 lun, u32 block_addr, u16 block_count);
static bool usbHsFsScsiSendModeSense10Command(UsbHsFsDriveContext *drive_ctx, u8 lun, bool long_lba, u8 page_control, u8 page_code, u8 subpage_code, u16 allocation_length, void *buf);
static bool usbHsFsScsiSendRead16Command(UsbHsFsDriveContext *drive_ctx, u8 lun, void *buf, u64 block_addr, u32 block_count, u32 block_length, bool fua);
static bool usbHsFsScsiSendWrite16Command(UsbHsFsDriveContext *drive_ctx, u8 lun, void *buf, u64 block_addr, u32 block_count, u32 block_length, bool fua);
static bool usbHsFsScsiSendSynchronizeCache16Command(UsbHsFsDriveContext *drive_ctx, u8 lun, u64 block_addr, u32 block_count);
static bool usbHsFsScsiSendReadCapacity16Command(UsbHsFsDriveContext *drive_ctx, u8 lun, ScsiReadCapacity16Data *read_capacity_16_data);

static void usbHsFsScsiPrepareCommandBlockWrapper(ScsiCommandBlockWrapper *cbw, u32 data_size, bool data_in, u8 lun, u8 cb_size);
static bool usbHsFsScsiTransferCommand(UsbHsFsDriveContext *drive_ctx, ScsiCommandBlockWrapper *cbw, void *buf);

static bool usbHsFsScsiSendCommandBlockWrapper(UsbHsFsDriveContext *drive_ctx, ScsiCommandBlockWrapper *cbw);
static bool usbHsFsScsiReceiveCommandStatusWrapper(UsbHsFsDriveContext *drive_ctx, ScsiCommandBlockWrapper *cbw, ScsiCommandStatusWrapper *out_csw);

static void usbHsFsScsiResetRecovery(UsbHsFsDriveContext *drive_ctx);

bool usbHsFsScsiStartDriveLogicalUnit(UsbHsFsDriveLogicalUnitContext *lun_ctx)
{
    UsbHsFsDriveContext *drive_ctx = NULL;
    u8 lun = 0;
    
    if (!lun_ctx || !usbHsFsDriveIsValidContext((drive_ctx = (UsbHsFsDriveContext*)lun_ctx->drive_ctx)) || (lun = lun_ctx->lun) >= UMS_MAX_LUN)
    {
        USBHSFS_LOG_MSG("Invalid parameters!");
        return false;
    }
    
    ScsiInquiryStandardData inquiry_data = {0};
    
    ScsiModeParameterHeader6 mode_parameter_header_6 = {0};
    ScsiModeParameterHeader10 mode_parameter_header_10 = {0};
    
    ScsiReadCapacity10Data read_capacity_10_data = {0};
    ScsiReadCapacity16Data read_capacity_16_data = {0};
    u64 block_count = 0, block_length = 0, capacity = 0;
    
    bool ret = false, eject_supported = false, write_protect = false, fua_supported = false, long_lba = false;
    
    USBHSFS_LOG_MSG("Starting LUN #%u from drive with interface ID %d.", lun, drive_ctx->usb_if_id);
    
    /* Reset medium present flag. */
    g_mediumPresent = true;
    
    /* Send Inquiry SCSI command. */
    if (!usbHsFsScsiSendInquiryCommand(drive_ctx, lun, &inquiry_data))
    {
        USBHSFS_LOG_MSG("Inquiry failed! (interface %d, LUN %d).", drive_ctx->usb_if_id, lun);
        goto end;
    }
    
    USBHSFS_LOG_DATA(&inquiry_data, sizeof(ScsiInquiryStandardData), "Inquiry data (interface %d, LUN %u):", drive_ctx->usb_if_id, lun);
    
    /* Check if we're dealing with an available Direct Access Block device. */
    if (inquiry_data.peripheral_qualifier != ScsiPeripheralQualifier_Connected || inquiry_data.peripheral_device_type != ScsiPeripheralDeviceType_DirectAccessBlock)
    {
        USBHSFS_LOG_MSG("Unsupported peripheral qualifier and/or device type! (0x%02X) (interface %d, LUN %d).", *((u8*)&inquiry_data), drive_ctx->usb_if_id, lun);
        goto end;
    }
    
    /* Check if the SPC standard version is valid. */
    if (inquiry_data.version > ScsiInquirySPCVersion_SPC5)
    {
        USBHSFS_LOG_MSG("Invalid SPC standard version value! (0x%02X) (interface %d, LUN %d).", inquiry_data.version, drive_ctx->usb_if_id, lun);
        goto end;
    }
    
    /* Perform necessary steps for removable LUNs. */
    /* Reference: https://t10.org/ftp/t10/document.05/05-344r0.pdf (page 26). */
    if (inquiry_data.rmb)
    {
        /* Send Prevent/Allow Medium Removal SCSI command. Not supported by all devices. We're OK if it fails. */
        if (usbHsFsScsiSendPreventAllowMediumRemovalCommand(drive_ctx, lun, true))
        {
            /* Send Start Stop Unit SCSI command. */
            if (!usbHsFsScsiSendStartStopUnitCommand(drive_ctx, lun, true))
            {
                USBHSFS_LOG_MSG("Start Stop Unit failed! (interface %d, LUN %d).", drive_ctx->usb_if_id, lun);
                goto end;
            }
            
            /* Update eject supported flag. */
            eject_supported = true;
        } else {
            USBHSFS_LOG_MSG("Prevent/Allow Medium Removal failed! (interface %d, LUN %d).", drive_ctx->usb_if_id, lun);
            if (!g_mediumPresent) goto end;
        }
    }
    
    /* Send Mode Sense (6) SCSI command. */
    /* We'll only request the mode parameter header to determine if there's write protection in place and if the FUA feature is supported. */
    if (usbHsFsScsiSendModeSense6Command(drive_ctx, lun, ScsiModePageControl_CurrentValues, SCSI_MODE_PAGE_CODE_ALL, SCSI_MODE_SUBPAGE_CODE_ALL_NO_SUBPAGES, sizeof(ScsiModeParameterHeader6), \
                                         &mode_parameter_header_6))
    {
        USBHSFS_LOG_DATA(&mode_parameter_header_6, sizeof(ScsiModeParameterHeader6), "Mode Sense (6) data (interface %d, LUN %u):", drive_ctx->usb_if_id, lun);
        
        /* Update Write Protect and FUA supported flags. */
        write_protect = (mode_parameter_header_6.wp == 1);
        fua_supported = (mode_parameter_header_6.dpofua == 1);
    } else {
        USBHSFS_LOG_MSG("Mode Sense (6) failed! (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
        
        /* Send Mode Sense (10) SCSI command. */
        /* Odds are we're dealing with a device that doesn't support Mode Sense (6). */
        if (usbHsFsScsiSendModeSense10Command(drive_ctx, lun, false, ScsiModePageControl_CurrentValues, SCSI_MODE_PAGE_CODE_ALL, SCSI_MODE_SUBPAGE_CODE_ALL_NO_SUBPAGES, \
                                               sizeof(ScsiModeParameterHeader10), &mode_parameter_header_10))
        {
            USBHSFS_LOG_DATA(&mode_parameter_header_10, sizeof(ScsiModeParameterHeader10), "Mode Sense (10) data (interface %d, LUN %u):", drive_ctx->usb_if_id, lun);
            
            /* Update Write Protect and FUA supported flags. */
            write_protect = (mode_parameter_header_10.wp == 1);
            fua_supported = (mode_parameter_header_10.dpofua == 1);
        } else {
            /* Nothing else to do - Mode Sense commands most likely aren't supported at all. */
            USBHSFS_LOG_MSG("Mode Sense (10) failed! (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
        }
    }
    
    /* Send Test Unit Ready SCSI command. */
    if (!usbHsFsScsiSendTestUnitReadyCommand(drive_ctx, lun))
    {
        USBHSFS_LOG_MSG("Test Unit Ready failed! (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
        goto end;
    }
    
    /* Send Read Capacity (10) SCSI command. */
    if (!usbHsFsScsiSendReadCapacity10Command(drive_ctx, lun, &read_capacity_10_data))
    {
        USBHSFS_LOG_MSG("Read Capacity (10) failed! (interface %d, LUN %d).", drive_ctx->usb_if_id, lun);
        goto end;
    }
    
    USBHSFS_LOG_DATA(&read_capacity_10_data, sizeof(ScsiReadCapacity10Data), "Read Capacity (10) data (interface %d, LUN %u):", drive_ctx->usb_if_id, lun);
    
    if (read_capacity_10_data.block_count == SCSI_READ_CAPACITY_10_MAX_LBA)
    {
        /* Send Read Capacity (16) SCSI command. */
        if (!usbHsFsScsiSendReadCapacity16Command(drive_ctx, lun, &read_capacity_16_data))
        {
            USBHSFS_LOG_MSG("Read Capacity (16) failed! (interface %d, LUN %d).", drive_ctx->usb_if_id, lun);
            goto end;
        }
        
        USBHSFS_LOG_DATA(&read_capacity_16_data, sizeof(ScsiReadCapacity16Data), "Read Capacity (16) data (interface %d, LUN %u):", drive_ctx->usb_if_id, lun);
        
        /* Store block count and length. */
        block_count = __builtin_bswap64(read_capacity_16_data.block_count);
        block_length = __builtin_bswap32(read_capacity_16_data.block_length);
        
        /* Update long LBA flag. */
        long_lba = true;
    } else {
        /* Store block count and length. */
        block_count = __builtin_bswap32(read_capacity_10_data.block_count);
        block_length = __builtin_bswap32(read_capacity_10_data.block_length);
    }
    
    /* Verify block length. */
    if (!block_length || (block_length % BLKDEV_MIN_BLOCK_SIZE) != 0 || block_length > BLKDEV_MAX_BLOCK_SIZE)
    {
        USBHSFS_LOG_MSG("Invalid block length! (0x%lX) (interface %d, LUN %u).", block_length, drive_ctx->usb_if_id, lun);
        goto end;
    }
    
    /* Calculate LUN capacity. */
    capacity = (block_count * block_length);
    if (!capacity)
    {
        USBHSFS_LOG_MSG("Capacity is zero! (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
        goto end;
    }
    
    USBHSFS_LOG_MSG("Capacity (interface %d, LUN %u): 0x%lX byte(s).", drive_ctx->usb_if_id, lun, capacity);
    
    /* Fill LUN context. */
    lun_ctx->removable = inquiry_data.rmb;
    lun_ctx->eject_supported = eject_supported;
    lun_ctx->write_protect = write_protect;
    lun_ctx->fua_supported = fua_supported;
    
    memcpy(lun_ctx->vendor_id, inquiry_data.vendor_id, sizeof(inquiry_data.vendor_id));
    usbHsFsUtilsTrimString(lun_ctx->vendor_id);
    
    memcpy(lun_ctx->product_id, inquiry_data.product_id, sizeof(inquiry_data.product_id));
    usbHsFsUtilsTrimString(lun_ctx->product_id);
    
    lun_ctx->long_lba = long_lba;
    lun_ctx->block_count = block_count;
    lun_ctx->block_length = block_length;
    lun_ctx->capacity = capacity;
    
    /* Update return value. */
    ret = true;
    
    USBHSFS_LOG_MSG("Successfully started LUN #%u from drive with interface ID %d.", lun, drive_ctx->usb_if_id);
    
end:
    /* Stop removable LUN if we successfully started it but the overall process failed. */
    /* Send Prevent/Allow Medium Removal SCSI command first. */
    /* Reference: https://t10.org/ftp/t10/document.05/05-344r0.pdf (page 26). */
    if (!ret && inquiry_data.rmb && eject_supported && usbHsFsScsiSendPreventAllowMediumRemovalCommand(drive_ctx, lun, false))
    {
        /* Send Start Stop Unit SCSI command. */
        usbHsFsScsiSendStartStopUnitCommand(drive_ctx, lun, false);
    }
    
    return ret;
}

/* Reference: https://t10.org/ftp/t10/document.05/05-344r0.pdf (page 26). */
void usbHsFsScsiStopDriveLogicalUnit(UsbHsFsDriveLogicalUnitContext *lun_ctx)
{
    /* Only perform these steps on valid LUNs that are removable and support ejection. */
    if (!usbHsFsDriveIsValidLogicalUnitContext(lun_ctx) || !lun_ctx->removable || !lun_ctx->eject_supported) return;
    
    /* Retrieve LUN context. */
    UsbHsFsDriveContext *drive_ctx = (UsbHsFsDriveContext*)lun_ctx->drive_ctx;
    
    /* Send Prevent/Allow Medium Removal SCSI command. */
    if (usbHsFsScsiSendPreventAllowMediumRemovalCommand(drive_ctx, lun_ctx->lun, false))
    {
        /* Send Start Stop Unit SCSI command. */
        usbHsFsScsiSendStartStopUnitCommand(drive_ctx, lun_ctx->lun, false);
    }
}

bool usbHsFsScsiReadLogicalUnitBlocks(UsbHsFsDriveLogicalUnitContext *lun_ctx, void *buf, u64 block_addr, u32 block_count)
{
    UsbHsFsDriveContext *drive_ctx = (UsbHsFsDriveContext*)lun_ctx->drive_ctx;
    u8 lun = lun_ctx->lun, *data_buf = (u8*)buf;
    u64 cur_block_addr = block_addr, data_transferred = 0;
    u32 block_length = lun_ctx->block_length, cmd_max_block_count = 0, buf_block_count = (USB_XFER_BUF_SIZE / block_length), max_block_count_per_loop = 0;
    bool fua = lun_ctx->fua_supported, long_lba = lun_ctx->long_lba, cmd = false;
    
    /* Set max block count per Read command. */
    /* Short LBA LUNs: this is just SCSI_RW10_MAX_BLOCK_COUNT. */
    /* Long LBA LUNs: up to UINT32_MAX blocks should be supported, but some tests with 4 TB Seagate drives show that only up to SCSI_RW10_MAX_BLOCK_COUNT + 1 blocks can be read at once. */
    cmd_max_block_count = (long_lba ? (SCSI_RW10_MAX_BLOCK_COUNT + 1) : SCSI_RW10_MAX_BLOCK_COUNT);
    
    /* Optimize reads by issuing commands with block counts aligned to the transfer buffer size. Reserve short packets for the last Read command (if needed). */
    max_block_count_per_loop = ALIGN_DOWN(cmd_max_block_count, buf_block_count);
    
    /* Read data using a loop. */
    while(block_count)
    {
        /* Determine number of blocks to read based on our limit. */
        u32 xfer_block_count = (block_count > max_block_count_per_loop ? max_block_count_per_loop : block_count);
        u64 xfer_size = ((u64)xfer_block_count * (u64)block_length);
        
        /* Read blocks. */
        USBHSFS_LOG_MSG("Reading 0x%X block(s) from LBA 0x%lX (0x%lX byte[s]) (interface %d, LUN %u).", xfer_block_count, cur_block_addr, xfer_size, lun_ctx->usb_if_id, lun);
        cmd = (long_lba ? usbHsFsScsiSendRead16Command(drive_ctx, lun, data_buf + data_transferred, cur_block_addr, xfer_block_count, block_length, fua) : \
                          usbHsFsScsiSendRead10Command(drive_ctx, lun, data_buf + data_transferred, (u32)cur_block_addr, (u16)xfer_block_count, block_length, fua));
        if (!cmd) break;
        
        /* Update data. */
        data_transferred += xfer_size;
        cur_block_addr += xfer_block_count;
        block_count -= xfer_block_count;
    }
    
    return (block_count == 0);
}

bool usbHsFsScsiWriteLogicalUnitBlocks(UsbHsFsDriveLogicalUnitContext *lun_ctx, const void *buf, u64 block_addr, u32 block_count)
{
    UsbHsFsDriveContext *drive_ctx = (UsbHsFsDriveContext*)lun_ctx->drive_ctx;
    u8 lun = lun_ctx->lun, *data_buf = (u8*)buf;
    u64 cur_block_addr = block_addr, data_transferred = 0;
    u32 block_length = lun_ctx->block_length, cmd_max_block_count = 0, buf_block_count = (USB_XFER_BUF_SIZE / block_length), max_block_count_per_loop = 0;
    bool fua = lun_ctx->fua_supported, long_lba = lun_ctx->long_lba, cmd = false;
    
    /* Make sure write protection is disabled. */
    if (lun_ctx->write_protect)
    {
        USBHSFS_LOG_MSG("Error: write protection enabled! (interface %d, LUN %u).", lun_ctx->usb_if_id, lun);
        return false;
    }
    
    /* Set max block count per Write command. */
    /* Short LBA LUNs: this is just SCSI_RW10_MAX_BLOCK_COUNT. */
    /* Long LBA LUNs: up to UINT32_MAX blocks should be supported, but some tests with 4 TB Seagate drives show that only up to SCSI_RW10_MAX_BLOCK_COUNT + 1 blocks can be written at once. */
    cmd_max_block_count = (long_lba ? (SCSI_RW10_MAX_BLOCK_COUNT + 1) : SCSI_RW10_MAX_BLOCK_COUNT);
    
    /* Optimize writes by issuing commands with block counts aligned to the transfer buffer size. Reserve short packets for the last Write command (if needed). */
    max_block_count_per_loop = ALIGN_DOWN(cmd_max_block_count, buf_block_count);
    
    /* Write data using a loop. */
    while(block_count)
    {
        /* Determine number of blocks to write based on our limit. */
        u32 xfer_block_count = (block_count > max_block_count_per_loop ? max_block_count_per_loop : block_count);
        u64 xfer_size = ((u64)xfer_block_count * (u64)block_length);
        
        /* Write blocks. */
        USBHSFS_LOG_MSG("Writing 0x%X block(s) to LBA 0x%lX (0x%lX byte[s]) (interface %d, LUN %u).", xfer_block_count, cur_block_addr, xfer_size, lun_ctx->usb_if_id, lun);
        cmd = (long_lba ? usbHsFsScsiSendWrite16Command(drive_ctx, lun, data_buf + data_transferred, cur_block_addr, xfer_block_count, block_length, fua) : \
                          usbHsFsScsiSendWrite10Command(drive_ctx, lun, data_buf + data_transferred, (u32)cur_block_addr, (u16)xfer_block_count, block_length, fua));
        if (!cmd) break;
        
        /* Update data. */
        data_transferred += xfer_size;
        cur_block_addr += xfer_block_count;
        block_count -= xfer_block_count;
    }
    
    return (block_count == 0);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 230). */
static bool usbHsFsScsiSendTestUnitReadyCommand(UsbHsFsDriveContext *drive_ctx, u8 lun)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, 0, false, lun, 6);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_TestUnitReady;  /* Operation code. */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, NULL);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (pages 47 and 195). */
static bool usbHsFsScsiSendRequestSenseCommand(UsbHsFsDriveContext *drive_ctx, u8 lun, ScsiRequestSenseDataFixedFormat *sense_data)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, (u32)sizeof(ScsiRequestSenseDataFixedFormat), true, lun, 6);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_RequestSense;   /* Operation code. */
    cbw.CBWCB[1] = 0;                                       /* Use fixed format sense data. */
    cbw.CBWCB[4] = (u8)cbw.dCBWDataTransferLength;          /* Set allocation length. */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, sense_data);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 92). */
static bool usbHsFsScsiSendInquiryCommand(UsbHsFsDriveContext *drive_ctx, u8 lun, ScsiInquiryStandardData *inquiry_data)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, (u32)sizeof(ScsiInquiryStandardData), true, lun, 6);
    
    /* Byteswap data. */
    u16 allocation_length = __builtin_bswap16((u16)cbw.dCBWDataTransferLength);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_Inquiry;            /* Operation code. */
    cbw.CBWCB[1] = 0;                                           /* Request standard inquiry data. */
    cbw.CBWCB[2] = 0;                                           /* Mandatory for standard inquiry data request. */
    memcpy(&(cbw.CBWCB[3]), &allocation_length, sizeof(u16));   /* Set allocation length. */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, inquiry_data);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 111). */
static bool usbHsFsScsiSendModeSense6Command(UsbHsFsDriveContext *drive_ctx, u8 lun, u8 page_control, u8 page_code, u8 subpage_code, u8 allocation_length, void *buf)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, allocation_length, true, lun, 6);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_ModeSense6;                 /* Operation code. */
    cbw.CBWCB[1] = 0;                                                   /* Always clear DBD bit. */
    cbw.CBWCB[2] = (((page_control << 6) & 0xC0) | (page_code & 0x3F)); /* Mask Page Control and Page Code values. */
    cbw.CBWCB[3] = subpage_code;                                        /* Set Subpage Code. */
    cbw.CBWCB[4] = allocation_length;                                   /* Set allocation length. */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, buf);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (pages 223 and 224). */
static bool usbHsFsScsiSendStartStopUnitCommand(UsbHsFsDriveContext *drive_ctx, u8 lun, bool start)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, 0, false, lun, 6);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_StartStopUnit;  /* Operation code. */
    cbw.CBWCB[1] = 0;                                       /* Return status after the whole operation is completed. */
    cbw.CBWCB[2] = 0;                                       /* Reserved. */
    cbw.CBWCB[3] = 0;                                       /* Unused for our configuration. */
    cbw.CBWCB[4] = (start ? 1 : 2);                         /* Start: LOEJ cleared, START set. Stop: LOEJ set, START cleared. */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, NULL);
}

/* Reference: https://web.archive.org/web/20201109051603if_/https://docs.oracle.com/en/storage/tape-storage/storagetek-sl150-modular-tape-library/slorm/preventallow-medium-removal-1eh.html. */
/* Reference: https://web.archive.org/web/20201109051603if_/https://docs.oracle.com/en/storage/tape-storage/storagetek-sl150-modular-tape-library/slorm/img_text/slk_100.html. */
static bool usbHsFsScsiSendPreventAllowMediumRemovalCommand(UsbHsFsDriveContext *drive_ctx, u8 lun, bool prevent)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, 0, false, lun, 6);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_PreventAllowMediumRemoval;  /* Operation code. */
    cbw.CBWCB[4] = (prevent ? 1 : 0);                                   /* Prevent or allow medium removal. */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, NULL);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 155). */
static bool usbHsFsScsiSendReadCapacity10Command(UsbHsFsDriveContext *drive_ctx, u8 lun, ScsiReadCapacity10Data *read_capacity_10_data)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, (u32)sizeof(ScsiReadCapacity10Data), true, lun, 10);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_ReadCapacity10; /* Operation code. Everything else is ignored/deprecated. */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, read_capacity_10_data);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 136). */
static bool usbHsFsScsiSendRead10Command(UsbHsFsDriveContext *drive_ctx, u8 lun, void *buf, u32 block_addr, u16 block_count, u32 block_length, bool fua)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, (u32)block_count * block_length, true, lun, 10);
    
    /* Byteswap data. */
    block_addr = __builtin_bswap32(block_addr);
    block_count = __builtin_bswap16(block_count);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_Read10;     /* Operation code. */
    cbw.CBWCB[1] = (fua ? (1 << 3) : 0);                /* Enable Force Unit Access (if needed). */
    memcpy(&(cbw.CBWCB[2]), &block_addr, sizeof(u32));  /* LBA (big endian). */
    memcpy(&(cbw.CBWCB[7]), &block_count, sizeof(u16)); /* Transfer length (big endian). */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, buf);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 249). */
static bool usbHsFsScsiSendWrite10Command(UsbHsFsDriveContext *drive_ctx, u8 lun, void *buf, u32 block_addr, u16 block_count, u32 block_length, bool fua)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, (u32)block_count * block_length, false, lun, 10);
    
    /* Byteswap data. */
    block_addr = __builtin_bswap32(block_addr);
    block_count = __builtin_bswap16(block_count);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_Write10;    /* Operation code. */
    cbw.CBWCB[1] = (fua ? (1 << 3) : 0);                /* Enable Force Unit Access (if needed). */
    memcpy(&(cbw.CBWCB[2]), &block_addr, sizeof(u32));  /* LBA (big endian). */
    memcpy(&(cbw.CBWCB[7]), &block_count, sizeof(u16)); /* Transfer length (big endian). */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, buf);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 227). */
static bool usbHsFsScsiSendSynchronizeCache10Command(UsbHsFsDriveContext *drive_ctx, u8 lun, u32 block_addr, u16 block_count)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, 0, false, lun, 10);
    
    /* Byteswap data. */
    block_addr = __builtin_bswap32(block_addr);
    block_count = __builtin_bswap16(block_count);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_SynchronizeCache10; /* Operation code. */
    cbw.CBWCB[1] = 0;                                           /* Always clear Immediate bit. */
    memcpy(&(cbw.CBWCB[2]), &block_addr, sizeof(u32));          /* LBA (big endian). */
    memcpy(&(cbw.CBWCB[7]), &block_count, sizeof(u16));         /* Transfer length (big endian). */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, NULL);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 114). */
static bool usbHsFsScsiSendModeSense10Command(UsbHsFsDriveContext *drive_ctx, u8 lun, bool long_lba, u8 page_control, u8 page_code, u8 subpage_code, u16 allocation_length, void *buf)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, allocation_length, true, lun, 10);
    
    /* Byteswap data. */
    allocation_length = __builtin_bswap16(allocation_length);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_ModeSense10;                /* Operation code. */
    cbw.CBWCB[1] = (long_lba ? (1 << 4) : 0);                           /* Set LLBAA bit (if needed), always clear DBD bit. */
    cbw.CBWCB[2] = (((page_control << 6) & 0xC0) | (page_code & 0x3F)); /* Mask Page Control and Page Code values. */
    cbw.CBWCB[3] = subpage_code;                                        /* Set Subpage Code. */
    memcpy(&(cbw.CBWCB[7]), &allocation_length, sizeof(u16));           /* Set allocation length. */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, buf);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 141). */
static bool usbHsFsScsiSendRead16Command(UsbHsFsDriveContext *drive_ctx, u8 lun, void *buf, u64 block_addr, u32 block_count, u32 block_length, bool fua)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, block_count * block_length, true, lun, 16);
    
    /* Byteswap data. */
    block_addr = __builtin_bswap64(block_addr);
    block_count = __builtin_bswap32(block_count);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_Read16;         /* Operation code. */
    cbw.CBWCB[1] = (fua ? (1 << 3) : 0);                    /* Enable Force Unit Access (if needed). */
    memcpy(&(cbw.CBWCB[2]), &block_addr, sizeof(u64));      /* LBA (big endian). */
    memcpy(&(cbw.CBWCB[10]), &block_count, sizeof(u32));    /* Transfer length (big endian). */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, buf);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 254). */
static bool usbHsFsScsiSendWrite16Command(UsbHsFsDriveContext *drive_ctx, u8 lun, void *buf, u64 block_addr, u32 block_count, u32 block_length, bool fua)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, block_count * block_length, false, lun, 16);
    
    /* Byteswap data. */
    block_addr = __builtin_bswap64(block_addr);
    block_count = __builtin_bswap32(block_count);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_Write16;        /* Operation code. */
    cbw.CBWCB[1] = (fua ? (1 << 3) : 0);                    /* Enable Force Unit Access (if needed). */
    memcpy(&(cbw.CBWCB[2]), &block_addr, sizeof(u64));      /* LBA (big endian). */
    memcpy(&(cbw.CBWCB[10]), &block_count, sizeof(u32));    /* Transfer length (big endian). */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, buf);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 229). */
static bool usbHsFsScsiSendSynchronizeCache16Command(UsbHsFsDriveContext *drive_ctx, u8 lun, u64 block_addr, u32 block_count)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, 0, false, lun, 16);
    
    /* Byteswap data. */
    block_addr = __builtin_bswap64(block_addr);
    block_count = __builtin_bswap32(block_count);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_SynchronizeCache16; /* Operation code. */
    cbw.CBWCB[1] = 0;                                           /* Always clear Immediate bit. */
    memcpy(&(cbw.CBWCB[2]), &block_addr, sizeof(u64));          /* LBA (big endian). */
    memcpy(&(cbw.CBWCB[10]), &block_count, sizeof(u32));        /* Transfer length (big endian). */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, NULL);
}

/* Reference: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf (page 157). */
static bool usbHsFsScsiSendReadCapacity16Command(UsbHsFsDriveContext *drive_ctx, u8 lun, ScsiReadCapacity16Data *read_capacity_16_data)
{
    /* Prepare CBW. */
    ScsiCommandBlockWrapper cbw = {0};
    usbHsFsScsiPrepareCommandBlockWrapper(&cbw, (u32)sizeof(ScsiReadCapacity16Data), true, lun, 16);
    
    /* Byteswap data. */
    u32 allocation_length = __builtin_bswap32(cbw.dCBWDataTransferLength);
    
    /* Prepare CB. */
    cbw.CBWCB[0] = ScsiCommandOperationCode_ServiceActionIn;    /* Operation code. */
    cbw.CBWCB[1] = SCSI_SERVICE_ACTION_IN_READ_CAPACITY_16;     /* Service action. */
    memcpy(&(cbw.CBWCB[10]), &allocation_length, sizeof(u32));  /* Set allocation length. */
    
    /* Send command. */
    USBHSFS_LOG_MSG("Sending command (interface %d, LUN %u).", drive_ctx->usb_if_id, lun);
    return usbHsFsScsiTransferCommand(drive_ctx, &cbw, read_capacity_16_data);
}

static void usbHsFsScsiPrepareCommandBlockWrapper(ScsiCommandBlockWrapper *cbw, u32 data_size, bool data_in, u8 lun, u8 cb_size)
{
    if (!cbw) return;
    cbw->dCBWSignature = __builtin_bswap32(SCSI_CBW_SIGNATURE);
    randomGet(&(cbw->dCBWTag), sizeof(cbw->dCBWTag));
    cbw->dCBWDataTransferLength = data_size;
    cbw->bmCBWFlags = (data_in ? USB_ENDPOINT_IN : USB_ENDPOINT_OUT);
    cbw->bCBWLUN = lun;
    cbw->bCBWCBLength = cb_size;
}

static bool usbHsFsScsiTransferCommand(UsbHsFsDriveContext *drive_ctx, ScsiCommandBlockWrapper *cbw, void *buf)
{
    if (!drive_ctx || !cbw || (cbw->dCBWDataTransferLength && !buf))
    {
        USBHSFS_LOG_MSG("Invalid parameters!");
        return false;
    }
    
    Result rc = 0;
    u8 *data_buf = (u8*)buf;
    u32 blksize = USB_XFER_BUF_SIZE;
    u32 data_size = cbw->dCBWDataTransferLength, data_transferred = 0;
    
    ScsiCommandStatusWrapper csw = {0};
    ScsiRequestSenseDataFixedFormat sense_data = {0};
    
    bool ret = false, receive = (cbw->bmCBWFlags == USB_ENDPOINT_IN), unexpected_csw = false;
    
    u8 *xfer_buf = drive_ctx->xfer_buf;
    UsbHsClientIfSession *usb_if_session = &(drive_ctx->usb_if_session);
    UsbHsClientEpSession *usb_ep_session = (receive ? &(drive_ctx->usb_in_ep_session[0]) : &(drive_ctx->usb_out_ep_session[0]));
    
    /* Send CBW. */
    if (!usbHsFsScsiSendCommandBlockWrapper(drive_ctx, cbw)) goto end;
    
    /* Enter data transfer stage. */
    while(data_transferred < data_size)
    {
        u32 rest_size = (data_size - data_transferred);
        u32 xfer_size = (rest_size > blksize ? blksize : rest_size);
        
        /* If we're sending data, copy it to the USB transfer buffer. */
        if (!receive) memcpy(xfer_buf, data_buf + data_transferred, xfer_size);
        
        /* Transfer data. */
        rc = usbHsFsRequestPostBuffer(usb_if_session, usb_ep_session, xfer_buf, xfer_size, &rest_size, false);
        if (R_FAILED(rc))
        {
            USBHSFS_LOG_MSG("usbHsFsRequestPostBuffer failed to %s 0x%X byte-long block! (0x%08X) (interface %d, LUN %u).", receive ? "receive" : "send", xfer_size, rc, drive_ctx->usb_if_id, cbw->bCBWLUN);
            
            /* Try to receive a CSW. */
            if (usbHsFsScsiReceiveCommandStatusWrapper(drive_ctx, cbw, &csw))
            {
                /* Update unexpected CSW flag and jump straight to the Request Sense section. */
                unexpected_csw = true;
                goto req_sense;
            }
            
            /* Nothing else to do. */
            goto end;
        }
        
        /* Check transferred data size. */
        if (rest_size != xfer_size)
        {
            USBHSFS_LOG_MSG("usbHsFsRequestPostBuffer transferred 0x%X byte(s), expected 0x%X! (interface %d, LUN %u).", rest_size, xfer_size, drive_ctx->usb_if_id, cbw->bCBWLUN);
            
            /* Check if we received an unexpected CSW. */
            if (receive && rest_size == sizeof(ScsiCommandStatusWrapper))
            {
                memcpy(&csw, xfer_buf, sizeof(ScsiCommandStatusWrapper));
                if ((csw.dCSWSignature == SCSI_CSW_SIGNATURE || csw.dCSWSignature == __builtin_bswap32(SCSI_CSW_SIGNATURE)) && csw.dCSWTag == cbw->dCBWTag)
                {
                    USBHSFS_LOG_DATA(&csw, sizeof(ScsiCommandStatusWrapper), "Data from unexpected CSW (interface %d, LUN %u):", drive_ctx->usb_if_id, cbw->bCBWLUN);
                    
                    /* Check if we got a Phase Error status. */
                    if (csw.bCSWStatus == ScsiCommandStatus_PhaseError)
                    {
                        USBHSFS_LOG_MSG("Phase error status in unexpected CSW! (interface %d, LUN %u). Performing BOT mass storage reset.", drive_ctx->usb_if_id, cbw->bCBWLUN);
                        usbHsFsScsiResetRecovery(drive_ctx);
                    }
                    
                    /* Update unexpected CSW flag and jump straight to the Request Sense section. */
                    unexpected_csw = true;
                    goto req_sense;
                }
            }
            
            goto end;
        }
        
        /* If we're receiving data, copy it to the provided buffer. */
        if (receive) memcpy(data_buf + data_transferred, xfer_buf, xfer_size);
        
        /* Update transferred data size. */
        data_transferred += xfer_size;
    }
    
    /* Receive CSW. */
    ret = usbHsFsScsiReceiveCommandStatusWrapper(drive_ctx, cbw, &csw);
    
req_sense:
    if (((ret && csw.bCSWStatus != ScsiCommandStatus_Passed) || unexpected_csw) && cbw->CBWCB[0] != ScsiCommandOperationCode_RequestSense)
    {
        /* Send Request Sense SCSI command. */
        if (!usbHsFsScsiSendRequestSenseCommand(drive_ctx, cbw->bCBWLUN, &sense_data))
        {
            USBHSFS_LOG_MSG("Request Sense failed! (interface %d, LUN %u).", drive_ctx->usb_if_id, cbw->bCBWLUN);
            ret = false;
            goto end;
        }
        
        USBHSFS_LOG_DATA(&sense_data, sizeof(ScsiRequestSenseDataFixedFormat), "Request Sense data (interface %d, LUN %u):", drive_ctx->usb_if_id, cbw->bCBWLUN);
        
        /* Reference: https://www.stix.id.au/wiki/SCSI_Sense_Data. */
        switch(sense_data.sense_key)
        {
            case ScsiSenseKey_NoSense:
            case ScsiSenseKey_RecoveredError:
            case ScsiSenseKey_UnitAttention:
            case ScsiSenseKey_Completed:
                /* Proceed normally. */
                USBHSFS_LOG_MSG("Proceeding normally (0x%X) (interface %d, LUN %u).", sense_data.sense_key, drive_ctx->usb_if_id, cbw->bCBWLUN);
                
                /* Retry Inquiry command if we're dealing with an unexpected CSW with no sense data. */
                if (unexpected_csw && cbw->CBWCB[0] == ScsiCommandOperationCode_Inquiry) ret = usbHsFsScsiTransferCommand(drive_ctx, cbw, buf);
                
                break;
            case ScsiSenseKey_NotReady:
                /* Check if we're dealing with a medium not present. */
                if (sense_data.additional_sense_code == SCSI_ASC_MEDIUM_NOT_PRESENT)
                {
                    USBHSFS_LOG_MSG("Error: medium not present! (0x%02X / 0x%02X) (interface %d, LUN %u).", sense_data.sense_key, sense_data.additional_sense_code, drive_ctx->usb_if_id, cbw->bCBWLUN);
                    ret = false;
                    g_mediumPresent = false;    /* Update medium present flag. */
                    break;
                }
                
                /* Wait some time (1s). */
                usbHsFsUtilsSleep(1);
            case ScsiSenseKey_AbortedCommand:
                /* Retry command once more. */
                USBHSFS_LOG_MSG("Retrying command 0x%02X (0x%X) (interface %d, LUN %u).", cbw->CBWCB[0], sense_data.sense_key, drive_ctx->usb_if_id, cbw->bCBWLUN);
                ret = usbHsFsScsiTransferCommand(drive_ctx, cbw, buf);
                break;
            default:
                /* Unrecoverable error. */
                USBHSFS_LOG_MSG("Unrecoverable error (0x%X) (interface %d, LUN %u).", sense_data.sense_key, drive_ctx->usb_if_id, cbw->bCBWLUN);
                ret = false;
                break;
        }
    }
    
end:
    return ret;
}

/* Reference: https://www.usb.org/sites/default/files/usbmassbulk_10.pdf (page 17). */
static bool usbHsFsScsiSendCommandBlockWrapper(UsbHsFsDriveContext *drive_ctx, ScsiCommandBlockWrapper *cbw)
{
    Result rc = 0;
    u32 xfer_size = 0;
    bool ret = false, status = false;
    
    USBHSFS_LOG_DATA(cbw, sizeof(ScsiCommandBlockWrapper), "Data from CBW to send (interface %d, LUN %u):", drive_ctx->usb_if_id, cbw->bCBWLUN);
    
    /* Copy current CBW to the USB transfer buffer. */
    memcpy(drive_ctx->xfer_buf, cbw, sizeof(ScsiCommandBlockWrapper));
    
    /* Send CBW. */
    /* usbHsFsRequestPostBuffer() isn't used here because CBW transfers are not handled exactly the same as CSW or data stage transfers. */
    /* A reset recovery must be performed if something goes wrong and the output endpoint is STALLed by the device. */
    rc = usbHsEpPostBuffer(&(drive_ctx->usb_out_ep_session[0]), drive_ctx->xfer_buf, sizeof(ScsiCommandBlockWrapper), &xfer_size);
    if (R_FAILED(rc))
    {
        USBHSFS_LOG_MSG("usbHsEpPostBuffer failed! (0x%08X) (interface %d, LUN %u).", rc, drive_ctx->usb_if_id, cbw->bCBWLUN);
        goto ep_chk;
    }
    
    /* Check transfer size. */
    if (xfer_size != sizeof(ScsiCommandBlockWrapper))
    {
        USBHSFS_LOG_MSG("usbHsEpPostBuffer transferred 0x%X byte(s), expected 0x%lX! (interface %d, LUN %u).", xfer_size, sizeof(ScsiCommandBlockWrapper), drive_ctx->usb_if_id, cbw->bCBWLUN);
        goto ep_chk;
    }
    
    /* Update return value. */
    ret = true;
    goto end;
    
ep_chk:
    /* Check if the output endpoint was STALLed by the device. */
    rc = usbHsFsRequestGetEndpointStatus(&(drive_ctx->usb_if_session), &(drive_ctx->usb_out_ep_session[0]), &status);
    if (R_FAILED(rc))
    {
        USBHSFS_LOG_MSG("Failed to get output endpoint status! (0x%08X) (interface %d, LUN %u).", rc, drive_ctx->usb_if_id, cbw->bCBWLUN);
        goto end;
    }
    
    /* If the endpoint was STALLed, something went wrong. Let's perform a reset recovery. */
    if (status)
    {
        USBHSFS_LOG_MSG("Output endpoint STALLed (interface %d, LUN %u). Performing BOT mass storage reset.", drive_ctx->usb_if_id, cbw->bCBWLUN);
        usbHsFsScsiResetRecovery(drive_ctx);
    }
    
end:
    return ret;
}

/* Reference: https://www.usb.org/sites/default/files/usbmassbulk_10.pdf (page 17). */
static bool usbHsFsScsiReceiveCommandStatusWrapper(UsbHsFsDriveContext *drive_ctx, ScsiCommandBlockWrapper *cbw, ScsiCommandStatusWrapper *out_csw)
{
    Result rc = 0;
    u32 xfer_size = 0;
    bool ret = false, valid_csw = false;
    ScsiCommandStatusWrapper *csw = (ScsiCommandStatusWrapper*)drive_ctx->xfer_buf;
    
    /* Receive CSW. */
    rc = usbHsFsRequestPostBuffer(&(drive_ctx->usb_if_session), &(drive_ctx->usb_in_ep_session[0]), csw, sizeof(ScsiCommandStatusWrapper), &xfer_size, true);
    if (R_FAILED(rc))
    {
        USBHSFS_LOG_MSG("usbHsFsRequestPostBuffer failed! (0x%08X) (interface %d, LUN %u).", rc, drive_ctx->usb_if_id, cbw->bCBWLUN);
        goto end;
    }
    
    /* Check transfer size. */
    if (xfer_size != sizeof(ScsiCommandStatusWrapper))
    {
        USBHSFS_LOG_MSG("usbHsFsRequestPostBuffer transferred 0x%X byte(s), expected 0x%lX! (interface %d, LUN %u).", xfer_size, sizeof(ScsiCommandStatusWrapper), drive_ctx->usb_if_id, cbw->bCBWLUN);
        goto end;
    }
    
    USBHSFS_LOG_DATA(csw, sizeof(ScsiCommandStatusWrapper), "Data from received CSW (interface %d, LUN %u):", drive_ctx->usb_if_id, cbw->bCBWLUN);
    
    /* Check CSW signature. */
    if (csw->dCSWSignature != SCSI_CSW_SIGNATURE && csw->dCSWSignature != __builtin_bswap32(SCSI_CSW_SIGNATURE))
    {
        USBHSFS_LOG_MSG("Invalid CSW signature! (0x%08X) (interface %d, LUN %u).", __builtin_bswap32(csw->dCSWSignature), drive_ctx->usb_if_id, cbw->bCBWLUN);
        goto end;
    }
    
    /* Check CSW tag. */
    if (csw->dCSWTag != cbw->dCBWTag)
    {
        USBHSFS_LOG_MSG("Invalid CSW tag! (0x%08X != 0x%08X) (interface %d, LUN %u).", csw->dCSWTag, cbw->dCBWTag, drive_ctx->usb_if_id, cbw->bCBWLUN);
        goto end;
    }
    
    /* Copy CSW from the USB transfer buffer. */
    memcpy(out_csw, csw, sizeof(ScsiCommandStatusWrapper));
    
    /* Update return value. */
    ret = true;
    
    /* Check if we got a Phase Error status. */
    if (csw->bCSWStatus == ScsiCommandStatus_PhaseError)
    {
        USBHSFS_LOG_MSG("Phase error status in CSW! (interface %d, LUN %u).", drive_ctx->usb_if_id, cbw->bCBWLUN);
        goto end;
    }
    
    /* Update valid CSW flag. */
    valid_csw = true;
    
end:
    if (R_SUCCEEDED(rc) && !valid_csw)
    {
        USBHSFS_LOG_MSG("Invalid CSW detected (interface %d, LUN %u). Performing BOT mass storage reset.", drive_ctx->usb_if_id, cbw->bCBWLUN);
        usbHsFsScsiResetRecovery(drive_ctx);
    }
    
    return ret;
}

static void usbHsFsScsiResetRecovery(UsbHsFsDriveContext *drive_ctx)
{
    /* Perform BOT mass storage reset. */
    if (R_FAILED(usbHsFsRequestMassStorageReset(&(drive_ctx->usb_if_session)))) USBHSFS_LOG_MSG("BOT mass storage reset failed! (interface %d).", drive_ctx->usb_if_id);
    
    /* Clear STALL status from both endpoints. */
    usbHsFsDriveClearStallStatus(drive_ctx);
}
