// // usbmassdevice.c // // USPi - An USB driver for Raspberry Pi written in C // Copyright (C) 2014 R. Stange // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // #include #include #include #include #include #include #include // USB Mass Storage Bulk-Only Transport // Command Block Wrapper typedef struct TCBW { unsigned int dCWBSignature, #define CBWSIGNATURE 0x43425355 dCWBTag, dCBWDataTransferLength; // number of bytes unsigned char bmCBWFlags, #define CBWFLAGS_DATA_IN 0x80 bCBWLUN : 4, #define CBWLUN 0 Reserved1 : 4, bCBWCBLength : 5, // valid length of the CBWCB in bytes Reserved2 : 3; unsigned char CBWCB[16]; } PACKED TCBW; // Command Status Wrapper typedef struct TCSW { unsigned int dCSWSignature, #define CSWSIGNATURE 0x53425355 dCSWTag, dCSWDataResidue; // difference in amount of data processed unsigned char bCSWStatus; #define CSWSTATUS_PASSED 0x00 #define CSWSTATUS_FAILED 0x01 #define CSWSTATUS_PHASE_ERROR 0x02 } PACKED TCSW; // SCSI Transparent Command Set #define SCSI_CONTROL 0x00 typedef struct TSCSIInquiry { unsigned char OperationCode, #define SCSI_OP_INQUIRY 0x12 LogicalUnitNumberEVPD, PageCode, Reserved, AllocationLength, Control; } PACKED TSCSIInquiry; typedef struct TSCSIInquiryResponse { unsigned char PeripheralDeviceType : 5, #define SCSI_PDT_DIRECT_ACCESS_BLOCK 0x00 // SBC-2 command set (or above) #define SCSI_PDT_DIRECT_ACCESS_RBC 0x0E // RBC command set PeripheralQualifier : 3, // 0: device is connected to this LUN DeviceTypeModifier : 7, RMB : 1, // 1: removable media ANSIApprovedVersion : 3, ECMAVersion : 3, ISOVersion : 2, Reserved1, AdditionalLength, Reserved2[3], VendorIdentification[8], ProductIdentification[16], ProductRevisionLevel[4]; } PACKED TSCSIInquiryResponse; typedef struct TSCSITestUnitReady { unsigned char OperationCode; #define SCSI_OP_TEST_UNIT_READY 0x00 unsigned int Reserved; unsigned char Control; } PACKED TSCSITestUnitReady; typedef struct TSCSIRequestSense { unsigned char OperationCode; #define SCSI_REQUEST_SENSE 0x03 unsigned char DescriptorFormat : 1, // set to 0 Reserved1 : 7; unsigned short Reserved2; unsigned char AllocationLength; unsigned char Control; } PACKED TSCSIRequestSense; typedef struct TSCSIRequestSenseResponse7x { unsigned char ResponseCode : 7, Valid : 1; unsigned char Obsolete; unsigned char SenseKey : 4, Reserved : 1, ILI : 1, EOM : 1, FileMark : 1; unsigned int Information; // big endian unsigned char AdditionalSenseLength; unsigned int CommandSpecificInformation; // big endian unsigned char AdditionalSenseCode; unsigned char AdditionalSenseCodeQualifier; unsigned char FieldReplaceableUnitCode; unsigned char SenseKeySpecificHigh : 7, SKSV : 1; unsigned short SenseKeySpecificLow; } PACKED TSCSIRequestSenseResponse7x; typedef struct TSCSIReadCapacity10 { unsigned char OperationCode; #define SCSI_OP_READ_CAPACITY10 0x25 unsigned char Obsolete : 1, Reserved1 : 7; unsigned int LogicalBlockAddress; // set to 0 unsigned short Reserved2; unsigned char PartialMediumIndicator : 1, // set to 0 Reserved3 : 7; unsigned char Control; } PACKED TSCSIReadCapacity10; typedef struct TSCSIReadCapacityResponse { unsigned int ReturnedLogicalBlockAddress; // big endian unsigned int BlockLengthInBytes; // big endian } PACKED TSCSIReadCapacityResponse; typedef struct TSCSIRead10 { unsigned char OperationCode, #define SCSI_OP_READ 0x28 Reserved1; unsigned int LogicalBlockAddress; // big endian unsigned char Reserved2; unsigned short TransferLength; // block count, big endian unsigned char Control; } PACKED TSCSIRead10; typedef struct TSCSIWrite10 { unsigned char OperationCode, #define SCSI_OP_WRITE 0x2A Flags; #define SCSI_WRITE_FUA 0x08 unsigned int LogicalBlockAddress; // big endian unsigned char Reserved; unsigned short TransferLength; // block count, big endian unsigned char Control; } PACKED TSCSIWrite10; static unsigned s_nDeviceNumber = 1; static const char FromUmsd[] = "umsd"; int USBBulkOnlyMassStorageDeviceTryRead (TUSBBulkOnlyMassStorageDevice *pThis, void *pBuffer, unsigned nCount); int USBBulkOnlyMassStorageDeviceTryWrite (TUSBBulkOnlyMassStorageDevice *pThis, const void *pBuffer, unsigned nCount); int USBBulkOnlyMassStorageDeviceCommand (TUSBBulkOnlyMassStorageDevice *pThis, void *pCmdBlk, unsigned nCmdBlkLen, void *pBuffer, unsigned nBufLen, boolean bIn); int USBBulkOnlyMassStorageDeviceReset (TUSBBulkOnlyMassStorageDevice *pThis); void USBBulkOnlyMassStorageDevice (TUSBBulkOnlyMassStorageDevice *pThis, TUSBDevice *pDevice) { assert (pThis != 0); USBDeviceCopy (&pThis->m_USBDevice, pDevice); pThis->m_USBDevice.Configure = USBBulkOnlyMassStorageDeviceConfigure; pThis->m_pEndpointIn = 0; pThis->m_pEndpointOut = 0; pThis->m_nCWBTag = 0; pThis->m_nBlockCount = 0; pThis->m_ullOffset = 0; } void _USBBulkOnlyMassStorageDevice (TUSBBulkOnlyMassStorageDevice *pThis) { assert (pThis != 0); if (pThis->m_pEndpointOut != 0) { _USBEndpoint (pThis->m_pEndpointOut); free (pThis->m_pEndpointOut); pThis->m_pEndpointOut = 0; } if (pThis->m_pEndpointIn != 0) { _USBEndpoint (pThis->m_pEndpointIn); free (pThis->m_pEndpointIn); pThis->m_pEndpointIn = 0; } _USBDevice (&pThis->m_USBDevice); } boolean USBBulkOnlyMassStorageDeviceConfigure (TUSBDevice *pUSBDevice) { TUSBBulkOnlyMassStorageDevice *pThis = (TUSBBulkOnlyMassStorageDevice *) pUSBDevice; assert (pThis != 0); TUSBConfigurationDescriptor *pConfDesc = (TUSBConfigurationDescriptor *) USBDeviceGetDescriptor (&pThis->m_USBDevice, DESCRIPTOR_CONFIGURATION); if ( pConfDesc == 0 || pConfDesc->bNumInterfaces < 1) { USBDeviceConfigurationError (&pThis->m_USBDevice, FromUmsd); return FALSE; } TUSBInterfaceDescriptor *pInterfaceDesc = (TUSBInterfaceDescriptor *) USBDeviceGetDescriptor (&pThis->m_USBDevice, DESCRIPTOR_INTERFACE); if ( pInterfaceDesc == 0 || pInterfaceDesc->bInterfaceNumber != 0x00 || pInterfaceDesc->bAlternateSetting != 0x00 || pInterfaceDesc->bNumEndpoints < 2 || pInterfaceDesc->bInterfaceClass != 0x08 // Mass Storage Class || pInterfaceDesc->bInterfaceSubClass != 0x06 // SCSI Transparent Command Set || pInterfaceDesc->bInterfaceProtocol != 0x50) // Bulk-Only Transport { USBDeviceConfigurationError (&pThis->m_USBDevice, FromUmsd); return FALSE; } const TUSBEndpointDescriptor *pEndpointDesc; while ((pEndpointDesc = (TUSBEndpointDescriptor *) USBDeviceGetDescriptor (&pThis->m_USBDevice, DESCRIPTOR_ENDPOINT)) != 0) { if ((pEndpointDesc->bmAttributes & 0x3F) == 0x02) // Bulk { if ((pEndpointDesc->bEndpointAddress & 0x80) == 0x80) // Input { if (pThis->m_pEndpointIn != 0) { USBDeviceConfigurationError (&pThis->m_USBDevice, FromUmsd); return FALSE; } pThis->m_pEndpointIn = (TUSBEndpoint *) malloc (sizeof (TUSBEndpoint)); assert (pThis->m_pEndpointIn != 0); USBEndpoint2 (pThis->m_pEndpointIn, &pThis->m_USBDevice, pEndpointDesc); } else // Output { if (pThis->m_pEndpointOut != 0) { USBDeviceConfigurationError (&pThis->m_USBDevice, FromUmsd); return FALSE; } pThis->m_pEndpointOut = (TUSBEndpoint *) malloc (sizeof (TUSBEndpoint)); assert (pThis->m_pEndpointOut != 0); USBEndpoint2 (pThis->m_pEndpointOut, &pThis->m_USBDevice, pEndpointDesc); } } } if ( pThis->m_pEndpointIn == 0 || pThis->m_pEndpointOut == 0) { USBDeviceConfigurationError (&pThis->m_USBDevice, FromUmsd); return FALSE; } if (!USBDeviceConfigure (&pThis->m_USBDevice)) { LogWrite (FromUmsd, LOG_ERROR, "Cannot set configuration"); return FALSE; } TSCSIInquiry SCSIInquiry; SCSIInquiry.OperationCode = SCSI_OP_INQUIRY; SCSIInquiry.LogicalUnitNumberEVPD = 0; SCSIInquiry.PageCode = 0; SCSIInquiry.Reserved = 0; SCSIInquiry.AllocationLength = sizeof (TSCSIInquiryResponse); SCSIInquiry.Control = SCSI_CONTROL; TSCSIInquiryResponse SCSIInquiryResponse; if (USBBulkOnlyMassStorageDeviceCommand (pThis, &SCSIInquiry, sizeof SCSIInquiry, &SCSIInquiryResponse, sizeof SCSIInquiryResponse, TRUE) != (int) sizeof SCSIInquiryResponse) { LogWrite (FromUmsd, LOG_ERROR, "Device does not respond"); return FALSE; } if (SCSIInquiryResponse.PeripheralDeviceType != SCSI_PDT_DIRECT_ACCESS_BLOCK) { LogWrite (FromUmsd, LOG_ERROR, "Unsupported device type: 0x%02X", (unsigned) SCSIInquiryResponse.PeripheralDeviceType); return FALSE; } unsigned nTries = 100; while (--nTries) { TSCSITestUnitReady SCSITestUnitReady; SCSITestUnitReady.OperationCode = SCSI_OP_TEST_UNIT_READY; SCSITestUnitReady.Reserved = 0; SCSITestUnitReady.Control = SCSI_CONTROL; if (USBBulkOnlyMassStorageDeviceCommand (pThis, &SCSITestUnitReady, sizeof SCSITestUnitReady, 0, 0, FALSE) >= 0) { break; } TSCSIRequestSense SCSIRequestSense; SCSIRequestSense.OperationCode = SCSI_REQUEST_SENSE; SCSIRequestSense.DescriptorFormat = 0; SCSIRequestSense.Reserved1 = 0; SCSIRequestSense.Reserved2 = 0; SCSIRequestSense.AllocationLength = sizeof (TSCSIRequestSenseResponse7x); SCSIRequestSense.Control = SCSI_CONTROL; TSCSIRequestSenseResponse7x SCSIRequestSenseResponse7x; if (USBBulkOnlyMassStorageDeviceCommand (pThis, &SCSIRequestSense, sizeof SCSIRequestSense, &SCSIRequestSenseResponse7x, sizeof SCSIRequestSenseResponse7x, TRUE) < 0) { LogWrite (FromUmsd, LOG_ERROR, "Request sense failed"); return FALSE; } MsDelay (100); } if (nTries == 0) { LogWrite (FromUmsd, LOG_ERROR, "Unit is not ready"); return FALSE; } TSCSIReadCapacity10 SCSIReadCapacity; SCSIReadCapacity.OperationCode = SCSI_OP_READ_CAPACITY10; SCSIReadCapacity.Obsolete = 0; SCSIReadCapacity.Reserved1 = 0; SCSIReadCapacity.LogicalBlockAddress = 0; SCSIReadCapacity.Reserved2 = 0; SCSIReadCapacity.PartialMediumIndicator = 0; SCSIReadCapacity.Reserved3 = 0; SCSIReadCapacity.Control = SCSI_CONTROL; TSCSIReadCapacityResponse SCSIReadCapacityResponse; if (USBBulkOnlyMassStorageDeviceCommand (pThis, &SCSIReadCapacity, sizeof SCSIReadCapacity, &SCSIReadCapacityResponse, sizeof SCSIReadCapacityResponse, TRUE) != (int) sizeof SCSIReadCapacityResponse) { LogWrite (FromUmsd, LOG_ERROR, "Read capacity failed"); return FALSE; } unsigned nBlockSize = uspi_le2be32 (SCSIReadCapacityResponse.BlockLengthInBytes); if (nBlockSize != UMSD_BLOCK_SIZE) { LogWrite (FromUmsd, LOG_ERROR, "Unsupported block size: %u", nBlockSize); return FALSE; } pThis->m_nBlockCount = uspi_le2be32 (SCSIReadCapacityResponse.ReturnedLogicalBlockAddress); if (pThis->m_nBlockCount == (u32) -1) { LogWrite (FromUmsd, LOG_ERROR, "Unsupported disk size > 2TB"); return FALSE; } pThis->m_nBlockCount++; LogWrite (FromUmsd, LOG_DEBUG, "Capacity is %u MByte", pThis->m_nBlockCount / (0x100000 / UMSD_BLOCK_SIZE)); TString DeviceName; String (&DeviceName); StringFormat (&DeviceName, "umsd%u", s_nDeviceNumber++); DeviceNameServiceAddDevice (DeviceNameServiceGet (), StringGet (&DeviceName), pThis, TRUE); _String (&DeviceName); return TRUE; } int USBBulkOnlyMassStorageDeviceRead (TUSBBulkOnlyMassStorageDevice *pThis, void *pBuffer, unsigned nCount) { assert (pThis != 0); unsigned nTries = 4; int nResult; do { nResult = USBBulkOnlyMassStorageDeviceTryRead (pThis, pBuffer, nCount); if (nResult != (int) nCount) { int nStatus = USBBulkOnlyMassStorageDeviceReset (pThis); if (nStatus != 0) { return nStatus; } } } while ( nResult != (int) nCount && --nTries > 0); return nResult; } int USBBulkOnlyMassStorageDeviceWrite (TUSBBulkOnlyMassStorageDevice *pThis, const void *pBuffer, unsigned nCount) { assert (pThis != 0); unsigned nTries = 4; int nResult; do { nResult = USBBulkOnlyMassStorageDeviceTryWrite (pThis, pBuffer, nCount); if (nResult != (int) nCount) { int nStatus = USBBulkOnlyMassStorageDeviceReset (pThis); if (nStatus != 0) { return nStatus; } } } while ( nResult != (int) nCount && --nTries > 0); return nResult; } unsigned long long USBBulkOnlyMassStorageDeviceSeek (TUSBBulkOnlyMassStorageDevice *pThis, unsigned long long ullOffset) { assert (pThis != 0); pThis->m_ullOffset = ullOffset; return pThis->m_ullOffset; } unsigned USBBulkOnlyMassStorageDeviceGetCapacity (TUSBBulkOnlyMassStorageDevice *pThis) { assert (pThis != 0); return pThis->m_nBlockCount; } int USBBulkOnlyMassStorageDeviceTryRead (TUSBBulkOnlyMassStorageDevice *pThis, void *pBuffer, unsigned nCount) { assert (pThis != 0); assert (pBuffer != 0); if ( (pThis->m_ullOffset & UMSD_BLOCK_MASK) != 0 || pThis->m_ullOffset > UMSD_MAX_OFFSET) { return -1; } unsigned nBlockAddress = (unsigned) (pThis->m_ullOffset >> UMSD_BLOCK_SHIFT); if ((nCount & UMSD_BLOCK_MASK) != 0) { return -1; } unsigned short usTransferLength = (unsigned short) (nCount >> UMSD_BLOCK_SHIFT); //LogWrite (FromUmsd, LOG_DEBUG, "TryRead %u/0x%X/%u", nBlockAddress, (unsigned) pBuffer, (unsigned) usTransferLength); TSCSIRead10 SCSIRead; SCSIRead.OperationCode = SCSI_OP_READ; SCSIRead.Reserved1 = 0; SCSIRead.LogicalBlockAddress = uspi_le2be32 (nBlockAddress); SCSIRead.Reserved2 = 0; SCSIRead.TransferLength = uspi_le2be16 (usTransferLength); SCSIRead.Control = SCSI_CONTROL; if (USBBulkOnlyMassStorageDeviceCommand (pThis, &SCSIRead, sizeof SCSIRead, pBuffer, nCount, TRUE) != (int) nCount) { LogWrite (FromUmsd, LOG_ERROR, "TryRead failed"); return -1; } return nCount; } int USBBulkOnlyMassStorageDeviceTryWrite (TUSBBulkOnlyMassStorageDevice *pThis, const void *pBuffer, unsigned nCount) { assert (pThis != 0); assert (pBuffer != 0); if ( (pThis->m_ullOffset & UMSD_BLOCK_MASK) != 0 || pThis->m_ullOffset > UMSD_MAX_OFFSET) { return -1; } unsigned nBlockAddress = (unsigned) (pThis->m_ullOffset >> UMSD_BLOCK_SHIFT); if ((nCount & UMSD_BLOCK_MASK) != 0) { return -1; } unsigned short usTransferLength = (unsigned short) (nCount >> UMSD_BLOCK_SHIFT); //LogWrite (FromUmsd, LOG_DEBUG, "TryWrite %u/0x%X/%u", nBlockAddress, (unsigned) pBuffer, (unsigned) usTransferLength); TSCSIWrite10 SCSIWrite; SCSIWrite.OperationCode = SCSI_OP_WRITE; SCSIWrite.Flags = SCSI_WRITE_FUA; SCSIWrite.LogicalBlockAddress = uspi_le2be32 (nBlockAddress); SCSIWrite.Reserved = 0; SCSIWrite.TransferLength = uspi_le2be16 (usTransferLength); SCSIWrite.Control = SCSI_CONTROL; if (USBBulkOnlyMassStorageDeviceCommand (pThis, &SCSIWrite, sizeof SCSIWrite, (void *) pBuffer, nCount, FALSE) < 0) { LogWrite (FromUmsd, LOG_ERROR, "TryWrite failed"); return -1; } return nCount; } int USBBulkOnlyMassStorageDeviceCommand (TUSBBulkOnlyMassStorageDevice *pThis, void *pCmdBlk, unsigned nCmdBlkLen, void *pBuffer, unsigned nBufLen, boolean bIn) { assert (pThis != 0); assert (pCmdBlk != 0); assert (6 <= nCmdBlkLen && nCmdBlkLen <= 16); assert (nBufLen == 0 || pBuffer != 0); TCBW CBW; memset (&CBW, 0, sizeof CBW); CBW.dCWBSignature = CBWSIGNATURE; CBW.dCWBTag = ++pThis->m_nCWBTag; CBW.dCBWDataTransferLength = nBufLen; CBW.bmCBWFlags = bIn ? CBWFLAGS_DATA_IN : 0; CBW.bCBWLUN = CBWLUN; CBW.bCBWCBLength = (u8) nCmdBlkLen; memcpy (CBW.CBWCB, pCmdBlk, nCmdBlkLen); TUSBHostController *pHost = USBDeviceGetHost (&pThis->m_USBDevice); assert (pHost != 0); if (DWHCIDeviceTransfer (pHost, pThis->m_pEndpointOut, &CBW, sizeof CBW) < 0) { LogWrite (FromUmsd, LOG_ERROR, "CBW transfer failed"); return -1; } int nResult = 0; if (nBufLen > 0) { nResult = DWHCIDeviceTransfer (pHost, bIn ? pThis->m_pEndpointIn : pThis->m_pEndpointOut, pBuffer, nBufLen); if (nResult < 0) { LogWrite (FromUmsd, LOG_ERROR, "Data transfer failed"); return -1; } } TCSW CSW; if (DWHCIDeviceTransfer (pHost, pThis->m_pEndpointIn, &CSW, sizeof CSW) != (int) sizeof CSW) { LogWrite (FromUmsd, LOG_ERROR, "CSW transfer failed"); return -1; } if (CSW.dCSWSignature != CSWSIGNATURE) { LogWrite (FromUmsd, LOG_ERROR, "CSW signature is wrong"); return -1; } if (CSW.dCSWTag != pThis->m_nCWBTag) { LogWrite (FromUmsd, LOG_ERROR, "CSW tag is wrong"); return -1; } if (CSW.bCSWStatus != CSWSTATUS_PASSED) { return -1; } if (CSW.dCSWDataResidue != 0) { LogWrite (FromUmsd, LOG_ERROR, "Data residue is not 0"); return -1; } return nResult; } int USBBulkOnlyMassStorageDeviceReset (TUSBBulkOnlyMassStorageDevice *pThis) { assert (pThis != 0); TUSBHostController *pHost = USBDeviceGetHost (&pThis->m_USBDevice); assert (pHost != 0); if (DWHCIDeviceControlMessage (pHost, USBDeviceGetEndpoint0 (&pThis->m_USBDevice), 0x21, 0xFF, 0, 0x00, 0, 0) < 0) { LogWrite (FromUmsd, LOG_DEBUG, "Cannot reset device"); return -1; } if (DWHCIDeviceControlMessage (pHost, USBDeviceGetEndpoint0 (&pThis->m_USBDevice), 0x02, 1, 0, 1, 0, 0) < 0) { LogWrite (FromUmsd, LOG_DEBUG, "Cannot clear halt on endpoint 1"); return -1; } if (DWHCIDeviceControlMessage (pHost, USBDeviceGetEndpoint0 (&pThis->m_USBDevice), 0x02, 1, 0, 2, 0, 0) < 0) { LogWrite (FromUmsd, LOG_DEBUG, "Cannot clear halt on endpoint 2"); return -1; } USBEndpointResetPID (pThis->m_pEndpointIn); USBEndpointResetPID (pThis->m_pEndpointOut); return 0; }