mirror of
https://github.com/hax4dazy/TinWoo.git
synced 2025-02-09 19:25:05 +01:00
1200 lines
58 KiB
C
1200 lines
58 KiB
C
/*
|
|
* usbhsfs_scsi.c
|
|
*
|
|
* Copyright (c) 2020-2023, 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);
|
|
}
|