status_t usb_disk_operation(device_lun *lun, uint8 operation, uint8 opLength, uint32 logicalBlockAddress, uint16 transferLength, void *data, uint32 *dataLength, bool directionIn) { TRACE("operation: lun: %u; op: %u; oplen: %u; lba: %lu; tlen: %u; data: %p; dlen: %p (%lu); in: %c\n", lun->logical_unit_number, operation, opLength, logicalBlockAddress, transferLength, data, dataLength, dataLength ? *dataLength : 0, directionIn ? 'y' : 'n'); disk_device *device = lun->device; command_block_wrapper command; command.signature = CBW_SIGNATURE; command.tag = device->current_tag++; command.data_transfer_length = (dataLength != NULL ? *dataLength : 0); command.flags = (directionIn ? CBW_DATA_INPUT : CBW_DATA_OUTPUT); command.lun = lun->logical_unit_number; command.command_block_length = opLength; memset(command.command_block, 0, sizeof(command.command_block)); switch (opLength) { case 6: { scsi_command_6 *commandBlock = (scsi_command_6 *)command.command_block; commandBlock->operation = operation; commandBlock->lun = lun->logical_unit_number << 5; commandBlock->allocation_length = (uint8)transferLength; if (operation == SCSI_MODE_SENSE_6) { // we hijack the lba argument to transport the desired page commandBlock->reserved[1] = (uint8)logicalBlockAddress; } break; } case 10: { scsi_command_10 *commandBlock = (scsi_command_10 *)command.command_block; commandBlock->operation = operation; commandBlock->lun_flags = lun->logical_unit_number << 5; commandBlock->logical_block_address = htonl(logicalBlockAddress); commandBlock->transfer_length = htons(transferLength); break; } default: TRACE_ALWAYS("unsupported operation length %d\n", opLength); return B_BAD_VALUE; } status_t result = usb_disk_transfer_data(device, false, &command, sizeof(command_block_wrapper)); if (result != B_OK) return result; if (device->status != B_OK || device->actual_length != sizeof(command_block_wrapper)) { // sending the command block wrapper failed TRACE_ALWAYS("sending the command block wrapper failed\n"); usb_disk_reset_recovery(device); return B_ERROR; } size_t transferedData = 0; if (data != NULL && dataLength != NULL && *dataLength > 0) { // we have data to transfer in a data stage result = usb_disk_transfer_data(device, directionIn, data, *dataLength); if (result != B_OK) return result; transferedData = device->actual_length; if (device->status != B_OK || transferedData != *dataLength) { // sending or receiving of the data failed if (device->status == B_DEV_STALLED) { TRACE("stall while transfering data\n"); gUSBModule->clear_feature(directionIn ? device->bulk_in : device->bulk_out, USB_FEATURE_ENDPOINT_HALT); } else { TRACE_ALWAYS("sending or receiving of the data failed\n"); usb_disk_reset_recovery(device); return B_ERROR; } } } command_status_wrapper status; result = usb_disk_receive_csw(device, &status); if (result != B_OK) { // in case of a stall or error clear the stall and try again gUSBModule->clear_feature(device->bulk_in, USB_FEATURE_ENDPOINT_HALT); result = usb_disk_receive_csw(device, &status); } if (result != B_OK) { TRACE_ALWAYS("receiving the command status wrapper failed\n"); usb_disk_reset_recovery(device); return result; } if (status.signature != CSW_SIGNATURE || status.tag != command.tag) { // the command status wrapper is not valid TRACE_ALWAYS("command status wrapper is not valid\n"); usb_disk_reset_recovery(device); return B_ERROR; } switch (status.status) { case CSW_STATUS_COMMAND_PASSED: case CSW_STATUS_COMMAND_FAILED: { if (status.data_residue > command.data_transfer_length) { // command status wrapper is not meaningful TRACE_ALWAYS("command status wrapper has invalid residue\n"); usb_disk_reset_recovery(device); return B_ERROR; } if (dataLength != NULL) { *dataLength -= status.data_residue; if (transferedData < *dataLength) { TRACE_ALWAYS("less data transfered than indicated\n"); *dataLength = transferedData; } } if (status.status == CSW_STATUS_COMMAND_PASSED) { // the operation is complete and has succeeded return B_OK; } else { // the operation is complete but has failed at the SCSI level if (operation != SCSI_TEST_UNIT_READY_6) { TRACE_ALWAYS("operation 0x%02x failed at the SCSI level\n", operation); } result = usb_disk_request_sense(lun); return result == B_OK ? B_ERROR : result; } } case CSW_STATUS_PHASE_ERROR: { // a protocol or device error occured TRACE_ALWAYS("phase error in operation 0x%02x\n", operation); usb_disk_reset_recovery(device); return B_ERROR; } default: { // command status wrapper is not meaningful TRACE_ALWAYS("command status wrapper has invalid status\n"); usb_disk_reset_recovery(device); return B_ERROR; } } }
status_t usb_disk_operation(device_lun *lun, uint8* operation, void *data, uint32 *dataLength, bool directionIn) { // TODO: remove transferLength TRACE("operation: lun: %u; op: 0x%x; data: %p; dlen: %p (%lu); in: %c\n", lun->logical_unit_number, operation[0], data, dataLength, dataLength ? *dataLength : 0, directionIn ? 'y' : 'n'); disk_device* device = lun->device; // Step 1 : send the SCSI operation as a class specific request size_t actualLength = 12; status_t result = gUSBModule->send_request(device->device, USB_REQTYPE_CLASS | USB_REQTYPE_INTERFACE_OUT, 0 /*request*/, 0/*value*/, device->interface/*index*/, 12, operation, &actualLength); if (result != B_OK || actualLength != 12) { TRACE("Command stage: wrote %ld bytes (error: %s)\n", actualLength, strerror(result)); // There was an error, we have to do a request sense to reset the device if (operation[0] != SCSI_REQUEST_SENSE_6) { usb_disk_request_sense(lun); } return result; } // Step 2 : data phase : send or receive data size_t transferedData = 0; if (data != NULL && dataLength != NULL && *dataLength > 0) { // we have data to transfer in a data stage result = usb_disk_transfer_data(device, directionIn, data, *dataLength); if (result != B_OK) { TRACE("Error %s in data phase\n", strerror(result)); return result; } transferedData = device->actual_length; if (device->status != B_OK || transferedData != *dataLength) { // sending or receiving of the data failed if (device->status == B_DEV_STALLED) { TRACE("stall while transfering data\n"); gUSBModule->clear_feature(directionIn ? device->bulk_in : device->bulk_out, USB_FEATURE_ENDPOINT_HALT); } else { TRACE_ALWAYS("sending or receiving of the data failed\n"); usb_disk_reset_recovery(device); return B_ERROR; } } } // step 3 : wait for the device to send the interrupt ACK if (operation[0] != SCSI_REQUEST_SENSE_6) { command_status_wrapper status; result = usb_disk_receive_csw(device, &status); if (result != B_OK) { // in case of a stall or error clear the stall and try again TRACE("Error receiving interrupt: %s. Retrying...\n", strerror(result)); gUSBModule->clear_feature(device->bulk_in, USB_FEATURE_ENDPOINT_HALT); result = usb_disk_receive_csw(device, &status); } if (result != B_OK) { TRACE_ALWAYS("receiving the command status interrupt failed\n"); usb_disk_reset_recovery(device); return result; } // wait for the device to finish the operation. result = usb_disk_request_sense(lun); } return result; }