| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/driver.h> |
| #include <ddk/usb/usb.h> |
| #include <usb/usb-request.h> |
| #include <zircon/assert.h> |
| #include <zircon/hw/usb.h> |
| #include <zircon/hw/usb-mass-storage.h> |
| |
| #include <endian.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "usb-mass-storage.h" |
| |
| // comment the next line if you don't want debug messages |
| #define DEBUG 0 |
| #ifdef DEBUG |
| # define DEBUG_PRINT(x) printf x |
| #else |
| # define DEBUG_PRINT(x) do {} while (0) |
| #endif |
| |
| static csw_status_t ums_verify_csw(ums_t* ums, usb_request_t* csw_request, uint32_t* out_residue); |
| |
| static inline void txn_complete(ums_txn_t* txn, zx_status_t status) { |
| zxlogf(TRACE, "UMS DONE %d (%p)\n", status, &txn->op); |
| txn->completion_cb(txn->cookie, status, &txn->op); |
| } |
| |
| static zx_status_t ums_reset(ums_t* ums) { |
| // UMS Reset Recovery. See section 5.3.4 of |
| // "Universal Serial Bus Mass Storage Class Bulk-Only Transport" |
| DEBUG_PRINT(("UMS: performing reset recovery\n")); |
| // Step 1: Send Bulk-Only Mass Storage Reset |
| zx_status_t status = usb_control(&ums->usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
| USB_REQ_RESET, 0, ums->interface_number, NULL, 0, |
| ZX_TIME_INFINITE, NULL); |
| if (status != ZX_OK) { |
| DEBUG_PRINT(("UMS: USB_REQ_RESET failed %d\n", status)); |
| return status; |
| } |
| // Step 2: Clear Feature HALT to the Bulk-In endpoint |
| status = usb_clear_feature(&ums->usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, |
| USB_ENDPOINT_HALT, ums->bulk_in_addr, ZX_TIME_INFINITE); |
| if (status != ZX_OK) { |
| DEBUG_PRINT(("UMS: clear endpoint halt failed %d\n", status)); |
| return status; |
| } |
| // Step 3: Clear Feature HALT to the Bulk-Out endpoint |
| status = usb_clear_feature(&ums->usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, |
| USB_ENDPOINT_HALT, ums->bulk_out_addr, ZX_TIME_INFINITE); |
| if (status != ZX_OK) { |
| DEBUG_PRINT(("UMS: clear endpoint halt failed %d\n", status)); |
| return status; |
| } |
| return ZX_OK; |
| } |
| |
| static void ums_req_complete(usb_request_t* req, void* cookie) { |
| if (cookie) { |
| sync_completion_signal((sync_completion_t *)cookie); |
| } |
| } |
| |
| static void ums_send_cbw(ums_t* ums, uint8_t lun, uint32_t transfer_length, uint8_t flags, |
| uint8_t command_len, void* command) { |
| usb_request_t* req = ums->cbw_req; |
| |
| ums_cbw_t* cbw; |
| zx_status_t status = usb_request_mmap(req, (void **)&cbw); |
| if (status != ZX_OK) { |
| DEBUG_PRINT(("UMS: usb request mmap failed: %d\n", status)); |
| return; |
| } |
| |
| memset(cbw, 0, sizeof(*cbw)); |
| cbw->dCBWSignature = htole32(CBW_SIGNATURE); |
| cbw->dCBWTag = htole32(ums->tag_send++); |
| cbw->dCBWDataTransferLength = htole32(transfer_length); |
| cbw->bmCBWFlags = flags; |
| cbw->bCBWLUN = lun; |
| cbw->bCBWCBLength = command_len; |
| |
| // copy command_len bytes from the command passed in into the command_len |
| memcpy(cbw->CBWCB, command, command_len); |
| |
| sync_completion_t completion = SYNC_COMPLETION_INIT; |
| usb_request_queue(&ums->usb, req, ums_req_complete, &completion); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| } |
| |
| static zx_status_t ums_read_csw(ums_t* ums, uint32_t* out_residue) { |
| sync_completion_t completion = SYNC_COMPLETION_INIT; |
| usb_request_t* csw_request = ums->csw_req; |
| usb_request_queue(&ums->usb, csw_request, ums_req_complete, &completion); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| csw_status_t csw_error = ums_verify_csw(ums, csw_request, out_residue); |
| |
| if (csw_error == CSW_SUCCESS) { |
| return ZX_OK; |
| } else if (csw_error == CSW_FAILED) { |
| return ZX_ERR_BAD_STATE; |
| } else { |
| // FIXME - best way to handle this? |
| // print error and then reset device due to it |
| DEBUG_PRINT(("UMS: CSW verify returned error. Check ums-hw.h csw_status_t for enum = %d\n", csw_error)); |
| ums_reset(ums); |
| return ZX_ERR_INTERNAL; |
| } |
| } |
| |
| static csw_status_t ums_verify_csw(ums_t* ums, usb_request_t* csw_request, uint32_t* out_residue) { |
| ums_csw_t csw; |
| usb_request_copy_from(csw_request, &csw, sizeof(csw), 0); |
| |
| // check signature is "USBS" |
| if (letoh32(csw.dCSWSignature) != CSW_SIGNATURE) { |
| DEBUG_PRINT(("UMS:invalid csw sig: %08x \n", letoh32(csw.dCSWSignature))); |
| return CSW_INVALID; |
| } |
| // check if tag matches the tag of last CBW |
| if (letoh32(csw.dCSWTag) != ums->tag_receive++) { |
| DEBUG_PRINT(("UMS:csw tag mismatch, expected:%08x got in csw:%08x \n", ums->tag_receive - 1, |
| letoh32(csw.dCSWTag))); |
| return CSW_TAG_MISMATCH; |
| } |
| // check if success is true or not? |
| if (csw.bmCSWStatus == CSW_FAILED) { |
| return CSW_FAILED; |
| } else if (csw.bmCSWStatus == CSW_PHASE_ERROR) { |
| return CSW_PHASE_ERROR; |
| } |
| |
| if (out_residue) { |
| *out_residue = letoh32(csw.dCSWDataResidue); |
| } |
| return CSW_SUCCESS; |
| } |
| |
| static void ums_queue_read(ums_t* ums, uint16_t transfer_length) { |
| // read request sense response |
| usb_request_t* read_request = ums->data_req; |
| read_request->header.length = transfer_length; |
| usb_request_queue(&ums->usb, read_request, ums_req_complete, NULL); |
| } |
| |
| static zx_status_t ums_inquiry(ums_t* ums, uint8_t lun, uint8_t* out_data) { |
| // CBW Configuration |
| scsi_command6_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_INQUIRY; |
| command.length = UMS_INQUIRY_TRANSFER_LENGTH; |
| ums_send_cbw(ums, lun, UMS_INQUIRY_TRANSFER_LENGTH, USB_DIR_IN, sizeof(command), &command); |
| |
| // read inquiry response |
| ums_queue_read(ums, UMS_INQUIRY_TRANSFER_LENGTH); |
| |
| // wait for CSW |
| zx_status_t status = ums_read_csw(ums, NULL); |
| if (status == ZX_OK) { |
| usb_request_copy_from(ums->data_req, out_data, UMS_INQUIRY_TRANSFER_LENGTH, 0); |
| } |
| return status; |
| } |
| |
| static zx_status_t ums_test_unit_ready(ums_t* ums, uint8_t lun) { |
| // CBW Configuration |
| scsi_command6_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_TEST_UNIT_READY; |
| ums_send_cbw(ums, lun, 0, USB_DIR_IN, sizeof(command), &command); |
| |
| // wait for CSW |
| return ums_read_csw(ums, NULL); |
| } |
| |
| static zx_status_t ums_request_sense(ums_t* ums, uint8_t lun, uint8_t* out_data) { |
| // CBW Configuration |
| scsi_command6_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_REQUEST_SENSE; |
| command.length = UMS_REQUEST_SENSE_TRANSFER_LENGTH; |
| ums_send_cbw(ums, lun, UMS_REQUEST_SENSE_TRANSFER_LENGTH, USB_DIR_IN, sizeof(command), &command); |
| |
| // read request sense response |
| ums_queue_read(ums, UMS_REQUEST_SENSE_TRANSFER_LENGTH); |
| |
| // wait for CSW |
| zx_status_t status = ums_read_csw(ums, NULL); |
| if (status == ZX_OK) { |
| usb_request_copy_from(ums->data_req, out_data, UMS_REQUEST_SENSE_TRANSFER_LENGTH, 0); |
| } |
| return status; |
| } |
| |
| static zx_status_t ums_read_capacity10(ums_t* ums, uint8_t lun, scsi_read_capacity_10_t* out_data) { |
| // CBW Configuration |
| scsi_command10_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_READ_CAPACITY10; |
| ums_send_cbw(ums, lun, sizeof(*out_data), USB_DIR_IN, sizeof(command), &command); |
| |
| // read capacity10 response |
| ums_queue_read(ums, sizeof(*out_data)); |
| |
| zx_status_t status = ums_read_csw(ums, NULL); |
| if (status == ZX_OK) { |
| usb_request_copy_from(ums->data_req, out_data, sizeof(*out_data), 0); |
| } |
| return status; |
| } |
| |
| static zx_status_t ums_read_capacity16(ums_t* ums, uint8_t lun, scsi_read_capacity_16_t* out_data) { |
| // CBW Configuration |
| scsi_command16_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_READ_CAPACITY16; |
| // service action = 10, not sure what that means |
| command.misc = 0x10; |
| command.length = sizeof(*out_data); |
| ums_send_cbw(ums, lun, sizeof(*out_data), USB_DIR_IN, sizeof(command), &command); |
| |
| // read capacity16 response |
| ums_queue_read(ums, sizeof(*out_data)); |
| |
| zx_status_t status = ums_read_csw(ums, NULL); |
| if (status == ZX_OK) { |
| usb_request_copy_from(ums->data_req, out_data, sizeof(*out_data), 0); |
| } |
| return status; |
| } |
| |
| static zx_status_t ums_mode_sense6(ums_t* ums, uint8_t lun, scsi_mode_sense_6_data_t* out_data) { |
| // CBW Configuration |
| scsi_mode_sense_6_command_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_MODE_SENSE6; |
| command.page = 0x3F; // all pages, current values |
| command.allocation_length = sizeof(*out_data); |
| |
| ums_send_cbw(ums, lun, sizeof(*out_data), USB_DIR_IN, sizeof(command), &command); |
| |
| // read mode sense response |
| ums_queue_read(ums, sizeof(*out_data)); |
| |
| zx_status_t status = ums_read_csw(ums, NULL); |
| if (status == ZX_OK) { |
| usb_request_copy_from(ums->data_req, out_data, sizeof(*out_data), 0); |
| } |
| return status; |
| } |
| |
| static zx_status_t ums_data_transfer(ums_t* ums, ums_txn_t* txn, zx_off_t offset, size_t length, |
| uint8_t ep_address) { |
| usb_request_t* req = ums->data_transfer_req; |
| |
| zx_status_t status = usb_request_init(req, txn->op.rw.vmo, offset, length, ep_address); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| sync_completion_t completion = SYNC_COMPLETION_INIT; |
| usb_request_queue(&ums->usb, req, ums_req_complete, &completion); |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| status = req->response.status; |
| if (status == ZX_OK && req->response.actual != length) { |
| status = ZX_ERR_IO; |
| } |
| |
| usb_request_release(req); |
| return status; |
| } |
| |
| static zx_status_t ums_read(ums_block_t* dev, ums_txn_t* txn) { |
| ums_t* ums = block_to_ums(dev); |
| |
| zx_off_t block_offset = txn->op.rw.offset_dev; |
| uint32_t num_blocks = txn->op.rw.length; |
| if ((block_offset >= dev->total_blocks) || ((dev->total_blocks - block_offset) < num_blocks)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| size_t block_size = dev->block_size; |
| zx_off_t vmo_offset = txn->op.rw.offset_vmo * block_size; |
| size_t max_blocks = ums->max_transfer / block_size; |
| zx_status_t status = ZX_OK; |
| |
| while (status == ZX_OK && num_blocks > 0) { |
| size_t blocks = num_blocks; |
| if (blocks > max_blocks) { |
| blocks = max_blocks; |
| } |
| size_t length = blocks * block_size; |
| |
| // CBW Configuration |
| // Need to use UMS_READ16 if block addresses are greater than 32 bit |
| if (dev->total_blocks > UINT32_MAX) { |
| scsi_command16_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_READ16; |
| command.lba = htobe64(block_offset); |
| command.length = htobe32(blocks); |
| ums_send_cbw(ums, dev->lun, length, USB_DIR_IN, sizeof(command), &command); |
| } else if (blocks <= UINT16_MAX) { |
| scsi_command10_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_READ10; |
| command.lba = htobe32(block_offset); |
| command.length_hi = blocks >> 8; |
| command.length_lo = blocks & 0xFF; |
| ums_send_cbw(ums, dev->lun, length, USB_DIR_IN, sizeof(command), &command); |
| } else { |
| scsi_command12_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_READ12; |
| command.lba = htobe32(block_offset); |
| command.length = htobe32(blocks); |
| ums_send_cbw(ums, dev->lun, length, USB_DIR_IN, sizeof(command), &command); |
| } |
| |
| status = ums_data_transfer(ums, txn, vmo_offset, length, ums->bulk_in_addr); |
| |
| block_offset += blocks; |
| num_blocks -= blocks; |
| vmo_offset += (blocks * block_size); |
| |
| // receive CSW |
| uint32_t residue; |
| status = ums_read_csw(ums, &residue); |
| if (status == ZX_OK && residue) { |
| zxlogf(ERROR, "unexpected residue in ums_read\n"); |
| status = ZX_ERR_IO; |
| } |
| } |
| |
| return status; |
| } |
| |
| static zx_status_t ums_write(ums_block_t* dev, ums_txn_t* txn) { |
| ums_t* ums = block_to_ums(dev); |
| |
| zx_off_t block_offset = txn->op.rw.offset_dev; |
| uint32_t num_blocks = txn->op.rw.length; |
| if ((block_offset >= dev->total_blocks) || ((dev->total_blocks - block_offset) < num_blocks)) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| size_t block_size = dev->block_size; |
| zx_off_t vmo_offset = txn->op.rw.offset_vmo * block_size; |
| size_t max_blocks = ums->max_transfer / block_size; |
| zx_status_t status = ZX_OK; |
| |
| while (status == ZX_OK && num_blocks > 0) { |
| size_t blocks = num_blocks; |
| if (blocks > max_blocks) { |
| blocks = max_blocks; |
| } |
| size_t length = blocks * block_size; |
| |
| // CBW Configuration |
| // Need to use UMS_WRITE16 if block addresses are greater than 32 bit |
| if (dev->total_blocks > UINT32_MAX) { |
| scsi_command16_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_WRITE16; |
| command.lba = htobe64(block_offset); |
| command.length = htobe32(blocks); |
| ums_send_cbw(ums, dev->lun, length, USB_DIR_OUT, sizeof(command), &command); |
| } else if (blocks <= UINT16_MAX) { |
| scsi_command10_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_WRITE10; |
| command.lba = htobe32(block_offset); |
| command.length_hi = blocks >> 8; |
| command.length_lo = blocks & 0xFF; |
| ums_send_cbw(ums, dev->lun, length, USB_DIR_OUT, sizeof(command), &command); |
| } else { |
| scsi_command12_t command; |
| memset(&command, 0, sizeof(command)); |
| command.opcode = UMS_WRITE12; |
| command.lba = htobe32(block_offset); |
| command.length = htobe32(blocks); |
| ums_send_cbw(ums, dev->lun, length, USB_DIR_OUT, sizeof(command), &command); |
| } |
| |
| status = ums_data_transfer(ums, txn, vmo_offset, length, ums->bulk_out_addr); |
| |
| block_offset += blocks; |
| num_blocks -= blocks; |
| vmo_offset += (blocks * block_size); |
| |
| // receive CSW |
| uint32_t residue; |
| status = ums_read_csw(ums, &residue); |
| if (status == ZX_OK && residue) { |
| zxlogf(ERROR, "unexpected residue in ums_write\n"); |
| status = ZX_ERR_IO; |
| } |
| } |
| |
| return status; |
| } |
| |
| static void ums_unbind(void* ctx) { |
| ums_t* ums = ctx; |
| |
| // terminate our worker thread |
| mtx_lock(&ums->txn_lock); |
| ums->dead = true; |
| mtx_unlock(&ums->txn_lock); |
| sync_completion_signal(&ums->txn_completion); |
| |
| // wait for worker thread to finish before removing devices |
| thrd_join(ums->worker_thread, NULL); |
| |
| for (uint8_t lun = 0; lun <= ums->max_lun; lun++) { |
| ums_block_t* dev = &ums->block_devs[lun]; |
| |
| if (dev->device_added) { |
| device_remove(dev->zxdev); |
| } |
| } |
| |
| // remove our root device |
| device_remove(ums->zxdev); |
| } |
| |
| static void ums_release(void* ctx) { |
| ums_t* ums = ctx; |
| |
| if (ums->cbw_req) { |
| usb_request_release(ums->cbw_req); |
| } |
| if (ums->data_req) { |
| usb_request_release(ums->data_req); |
| } |
| if (ums->csw_req) { |
| usb_request_release(ums->csw_req); |
| } |
| if (ums->data_transfer_req) { |
| usb_request_release(ums->data_transfer_req); |
| //The release_cb of data_transfer_req does not free the req. |
| free(ums->data_transfer_req); |
| } |
| |
| free(ums); |
| } |
| |
| static zx_status_t ums_add_block_device(ums_block_t* dev) { |
| ums_t* ums = block_to_ums(dev); |
| uint8_t lun = dev->lun; |
| |
| scsi_read_capacity_10_t data; |
| zx_status_t status = ums_read_capacity10(ums, lun, &data); |
| if (status < 0) { |
| zxlogf(ERROR, "read_capacity10 failed: %d\n", status); |
| return status; |
| } |
| |
| dev->total_blocks = betoh32(data.lba); |
| dev->block_size = betoh32(data.block_length); |
| |
| if (dev->total_blocks == 0xFFFFFFFF) { |
| scsi_read_capacity_16_t data; |
| status = ums_read_capacity16(ums, lun, &data); |
| if (status < 0) { |
| zxlogf(ERROR, "read_capacity16 failed: %d\n", status); |
| return status; |
| } |
| |
| dev->total_blocks = betoh64(data.lba); |
| dev->block_size = betoh32(data.block_length); |
| } |
| if (dev->block_size == 0) { |
| zxlogf(ERROR, "UMS zero block size\n"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // +1 because this returns the address of the final block, and blocks are zero indexed |
| dev->total_blocks++; |
| |
| // determine if LUN is read-only |
| scsi_mode_sense_6_data_t ms_data; |
| status = ums_mode_sense6(ums, lun, &ms_data); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "ums_mode_sense6 failed: %d\n", status); |
| return status; |
| } |
| |
| if (ms_data.device_specific_param & MODE_SENSE_DSP_RO) { |
| dev->flags |= BLOCK_FLAG_READONLY; |
| } else { |
| dev->flags &= ~BLOCK_FLAG_READONLY; |
| } |
| |
| DEBUG_PRINT(("UMS: block size is: 0x%08x\n", dev->block_size)); |
| DEBUG_PRINT(("UMS: total blocks is: %" PRId64 "\n", dev->total_blocks)); |
| DEBUG_PRINT(("UMS: total size is: %" PRId64 "\n", dev->total_blocks * dev->block_size)); |
| DEBUG_PRINT(("UMS: read-only: %d removable: %d\n", !!(dev->flags & BLOCK_FLAG_READONLY), |
| !!(dev->flags & BLOCK_FLAG_REMOVABLE))); |
| |
| return ums_block_add_device(ums, dev); |
| } |
| |
| static zx_status_t ums_check_luns_ready(ums_t* ums) { |
| zx_status_t status = ZX_OK; |
| |
| for (uint8_t lun = 0; lun <= ums->max_lun && status == ZX_OK; lun++) { |
| ums_block_t* dev = &ums->block_devs[lun]; |
| bool ready = false; |
| |
| status = ums_test_unit_ready(ums, lun); |
| if (status == ZX_OK) { |
| ready = true; |
| } if (status == ZX_ERR_BAD_STATE) { |
| ready = false; |
| // command returned CSW_FAILED. device is there but media is not ready. |
| uint8_t request_sense_data[UMS_REQUEST_SENSE_TRANSFER_LENGTH]; |
| status = ums_request_sense(ums, lun, request_sense_data); |
| } |
| if (status != ZX_OK) { |
| break; |
| } |
| |
| if (ready && !dev->device_added) { |
| // this will set ums_block_t.device_added if it succeeds |
| status = ums_add_block_device(dev); |
| if (status == ZX_OK) { |
| dev->device_added = true; |
| } else { |
| zxlogf(ERROR, "UMS: device_add for block device failed %d\n", status); |
| } |
| } else if (!ready && dev->device_added) { |
| device_remove(dev->zxdev); |
| dev->device_added = false; |
| } |
| } |
| |
| return status; |
| } |
| |
| static zx_protocol_device_t ums_device_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .unbind = ums_unbind, |
| .release = ums_release, |
| }; |
| |
| static int ums_worker_thread(void* arg) { |
| ums_t* ums = (ums_t*)arg; |
| zx_status_t status = ZX_OK; |
| |
| for (uint8_t lun = 0; lun <= ums->max_lun; lun++) { |
| uint8_t inquiry_data[UMS_INQUIRY_TRANSFER_LENGTH]; |
| status = ums_inquiry(ums, lun, inquiry_data); |
| if (status < 0) { |
| zxlogf(ERROR, "ums_inquiry failed for lun %d status: %d\n", lun, status); |
| device_remove(ums->zxdev); |
| return status; |
| } |
| uint8_t rmb = inquiry_data[1] & 0x80; // Removable Media Bit |
| if (rmb) { |
| ums->block_devs[lun].flags |= BLOCK_FLAG_REMOVABLE; |
| } |
| } |
| |
| device_make_visible(ums->zxdev); |
| |
| bool wait = true; |
| while (1) { |
| if (wait) { |
| status = sync_completion_wait(&ums->txn_completion, ZX_SEC(1)); |
| if (status == ZX_ERR_TIMED_OUT) { |
| if (ums_check_luns_ready(ums) != ZX_OK) { |
| return status; |
| } |
| continue; |
| } |
| sync_completion_reset(&ums->txn_completion); |
| } |
| |
| mtx_lock(&ums->txn_lock); |
| if (ums->dead) { |
| mtx_unlock(&ums->txn_lock); |
| break; |
| } |
| ums_txn_t* txn = list_remove_head_type(&ums->queued_txns, ums_txn_t, node); |
| if (txn == NULL) { |
| mtx_unlock(&ums->txn_lock); |
| wait = true; |
| continue; |
| } else { |
| wait = false; |
| } |
| |
| mtx_unlock(&ums->txn_lock); |
| zxlogf(TRACE, "UMS PROCESS (%p)\n", &txn->op); |
| |
| ums_block_t* dev = txn->dev; |
| |
| zx_status_t status; |
| switch (txn->op.command & BLOCK_OP_MASK) { |
| case BLOCK_OP_READ: |
| if ((status = ums_read(dev, txn)) != ZX_OK) { |
| zxlogf(ERROR, "ums: read of %u @ %zu failed: %d\n", |
| txn->op.rw.length, txn->op.rw.offset_dev, status); |
| } |
| break; |
| case BLOCK_OP_WRITE: |
| if ((status = ums_write(dev, txn)) != ZX_OK) { |
| zxlogf(ERROR, "ums: write of %u @ %zu failed: %d\n", |
| txn->op.rw.length, txn->op.rw.offset_dev, status); |
| } |
| break; |
| case BLOCK_OP_FLUSH: |
| // nothing to do for flush txns other than complete them |
| status = ZX_OK; |
| break; |
| default: |
| status = ZX_ERR_INVALID_ARGS; |
| break; |
| } |
| |
| txn_complete(txn, status); |
| } |
| |
| // complete any pending txns |
| list_node_t txns = LIST_INITIAL_VALUE(txns); |
| mtx_lock(&ums->txn_lock); |
| list_move(&ums->queued_txns, &txns); |
| mtx_unlock(&ums->txn_lock); |
| |
| ums_txn_t* txn; |
| while ((txn = list_remove_head_type(&ums->queued_txns, ums_txn_t, node)) != NULL) { |
| switch (txn->op.command & BLOCK_OP_MASK) { |
| case BLOCK_OP_READ: |
| zxlogf(ERROR, "ums: read of %u @ %zu discarded during unbind\n", |
| txn->op.rw.length, txn->op.rw.offset_dev); |
| break; |
| case BLOCK_OP_WRITE: |
| zxlogf(ERROR, "ums: write of %u @ %zu discarded during unbind\n", |
| txn->op.rw.length, txn->op.rw.offset_dev); |
| break; |
| } |
| txn_complete(txn, ZX_ERR_IO_NOT_PRESENT); |
| } |
| |
| return ZX_OK; |
| } |
| |
| static zx_status_t ums_bind(void* ctx, zx_device_t* device) { |
| usb_protocol_t usb; |
| if (device_get_protocol(device, ZX_PROTOCOL_USB_OLD, &usb)) { |
| return 0; |
| } |
| |
| // find our endpoints |
| usb_desc_iter_t iter; |
| zx_status_t result = usb_desc_iter_init(&usb, &iter); |
| if (result < 0) return result; |
| |
| usb_interface_descriptor_t* intf = usb_desc_iter_next_interface(&iter, true); |
| if (!intf) { |
| usb_desc_iter_release(&iter); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| if (intf->bNumEndpoints < 2) { |
| DEBUG_PRINT(("UMS:ums_bind wrong number of endpoints: %d\n", intf->bNumEndpoints)); |
| usb_desc_iter_release(&iter); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| uint8_t interface_number = intf->bInterfaceNumber; |
| uint8_t bulk_in_addr = 0; |
| uint8_t bulk_out_addr = 0; |
| size_t bulk_in_max_packet = 0; |
| size_t bulk_out_max_packet = 0; |
| |
| usb_endpoint_descriptor_t* endp = usb_desc_iter_next_endpoint(&iter); |
| while (endp) { |
| if (usb_ep_direction(endp) == USB_ENDPOINT_OUT) { |
| if (usb_ep_type(endp) == USB_ENDPOINT_BULK) { |
| bulk_out_addr = endp->bEndpointAddress; |
| bulk_out_max_packet = usb_ep_max_packet(endp); |
| } |
| } else { |
| if (usb_ep_type(endp) == USB_ENDPOINT_BULK) { |
| bulk_in_addr = endp->bEndpointAddress; |
| bulk_in_max_packet = usb_ep_max_packet(endp); |
| } |
| } |
| endp = usb_desc_iter_next_endpoint(&iter); |
| } |
| usb_desc_iter_release(&iter); |
| |
| if (!bulk_in_addr || !bulk_out_addr) { |
| DEBUG_PRINT(("UMS:ums_bind could not find endpoints\n")); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| uint8_t max_lun; |
| size_t out_length; |
| zx_status_t status = usb_control(&usb, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
| USB_REQ_GET_MAX_LUN, 0x00, 0x00, &max_lun, sizeof(max_lun), |
| ZX_TIME_INFINITE, &out_length); |
| |
| if (status == ZX_ERR_IO_REFUSED) { |
| // Devices that do not support multiple LUNS may stall this command. |
| // See USB Mass Storage Class Spec. 3.2 Get Max LUN. |
| // Clear the stall. |
| usb_reset_endpoint(&usb, 0); |
| zxlogf(INFO, "Device does not support multiple LUNs\n"); |
| max_lun = 0; |
| } else if (status != ZX_OK) { |
| return status; |
| } else if (out_length != sizeof(max_lun)) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| ums_t* ums = calloc(1, sizeof(ums_t) + (max_lun + 1) * sizeof(ums_block_t)); |
| if (!ums) { |
| DEBUG_PRINT(("UMS:Not enough memory for ums_t\n")); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| DEBUG_PRINT(("UMS:Max lun is: %u\n", max_lun)); |
| ums->max_lun = max_lun; |
| |
| for (uint8_t lun = 0; lun <= max_lun; lun++) { |
| ums_block_t* dev = &ums->block_devs[lun]; |
| dev->lun = lun; |
| } |
| |
| list_initialize(&ums->queued_txns); |
| sync_completion_reset(&ums->txn_completion); |
| mtx_init(&ums->txn_lock, mtx_plain); |
| |
| ums->usb_zxdev = device; |
| memcpy(&ums->usb, &usb, sizeof(ums->usb)); |
| ums->bulk_in_addr = bulk_in_addr; |
| ums->bulk_out_addr = bulk_out_addr; |
| ums->bulk_in_max_packet = bulk_in_max_packet; |
| ums->bulk_out_max_packet = bulk_out_max_packet; |
| ums->interface_number = interface_number; |
| |
| size_t max_in = usb_get_max_transfer_size(&usb, bulk_in_addr); |
| size_t max_out = usb_get_max_transfer_size(&usb, bulk_out_addr); |
| ums->max_transfer = (max_in < max_out ? max_in : max_out); |
| |
| ums->parent_req_size = usb_get_request_size(&ums->usb); |
| ZX_DEBUG_ASSERT(ums->parent_req_size != 0); |
| |
| status = usb_request_alloc(&ums->cbw_req, sizeof(ums_cbw_t), bulk_out_addr, |
| ums->parent_req_size); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| status = usb_request_alloc(&ums->data_req, PAGE_SIZE, bulk_in_addr, |
| ums->parent_req_size); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| status = usb_request_alloc(&ums->csw_req, sizeof(ums_csw_t), bulk_in_addr, |
| ums->parent_req_size); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| status = usb_request_alloc(&ums->data_transfer_req, 0, bulk_in_addr, |
| ums->parent_req_size); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| ums->tag_send = ums->tag_receive = 8; |
| |
| // Add root device, which will contain block devices for logical units |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "ums", |
| .ctx = ums, |
| .ops = &ums_device_proto, |
| .flags = DEVICE_ADD_NON_BINDABLE | DEVICE_ADD_INVISIBLE, |
| }; |
| |
| status = device_add(ums->usb_zxdev, &args, &ums->zxdev); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| int ret = thrd_create_with_name(&ums->worker_thread, ums_worker_thread, ums, "ums_worker_thread"); |
| if (ret != thrd_success) { |
| device_remove(ums->zxdev); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| return status; |
| |
| fail: |
| zxlogf(ERROR, "ums_bind failed: %d\n", status); |
| ums_release(ums); |
| return status; |
| } |
| |
| static zx_driver_ops_t usb_mass_storage_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = ums_bind, |
| }; |
| |
| ZIRCON_DRIVER_BEGIN(usb_mass_storage, usb_mass_storage_driver_ops, "zircon", "0.1", 4) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB_OLD), |
| BI_ABORT_IF(NE, BIND_USB_CLASS, USB_CLASS_MSC), |
| BI_ABORT_IF(NE, BIND_USB_SUBCLASS, USB_SUBCLASS_MSC_SCSI), |
| BI_MATCH_IF(EQ, BIND_USB_PROTOCOL, USB_PROTOCOL_MSC_BULK_ONLY), |
| ZIRCON_DRIVER_END(usb_mass_storage) |