/* * usbhsfs_scsi.c * * Copyright (c) 2020-2021, DarkMatterCore . * 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); }