| // 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/driver.h> |
| #include <ddk/binding.h> |
| #include <driver/usb.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, iotxn_t* csw_request, uint32_t* out_residue); |
| |
| |
| static zx_status_t ums_reset(ums_t* ums) { |
| // for all these control requests, data is null, length is 0 because nothing is passed back |
| // value and index not used for first command, though index is supposed to be set to interface number |
| // TODO: check interface number, see if index needs to be set |
| DEBUG_PRINT(("UMS: performing reset recovery\n")); |
| zx_status_t status = usb_control(&ums->usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
| USB_REQ_RESET, 0x00, 0x00, NULL, 0, ZX_TIME_INFINITE); |
| status = usb_control(&ums->usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
| USB_REQ_CLEAR_FEATURE, FS_ENDPOINT_HALT, ums->bulk_in_addr, NULL, 0, |
| ZX_TIME_INFINITE); |
| status = usb_control(&ums->usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, |
| USB_REQ_CLEAR_FEATURE, FS_ENDPOINT_HALT, ums->bulk_out_addr, NULL, 0, |
| ZX_TIME_INFINITE); |
| return status; |
| } |
| |
| static void ums_queue_request(ums_t* ums, iotxn_t* txn) { |
| iotxn_queue(ums->usb_mxdev, txn); |
| } |
| |
| static void ums_txn_complete(iotxn_t* txn, void* cookie) { |
| if (cookie) { |
| completion_signal((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) { |
| iotxn_t* txn = ums->cbw_iotxn; |
| |
| ums_cbw_t* cbw; |
| iotxn_mmap(txn, (void **)&cbw); |
| |
| 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); |
| |
| completion_t completion = COMPLETION_INIT; |
| txn->cookie = &completion; |
| ums_queue_request(ums, txn); |
| completion_wait(&completion, ZX_TIME_INFINITE); |
| } |
| |
| static zx_status_t ums_read_csw(ums_t* ums, uint32_t* out_residue) { |
| completion_t completion = COMPLETION_INIT; |
| iotxn_t* csw_request = ums->csw_iotxn; |
| csw_request->cookie = &completion; |
| ums_queue_request(ums, csw_request); |
| 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, iotxn_t* csw_request, uint32_t* out_residue) { |
| ums_csw_t csw; |
| iotxn_copyfrom(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 |
| iotxn_t* read_request = ums->data_iotxn; |
| read_request->length = transfer_length; |
| read_request->cookie = NULL; |
| ums_queue_request(ums, read_request); |
| } |
| |
| 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) { |
| iotxn_copyfrom(ums->data_iotxn, 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) { |
| iotxn_copyfrom(ums->data_iotxn, 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) { |
| iotxn_copyfrom(ums->data_iotxn, 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) { |
| iotxn_copyfrom(ums->data_iotxn, 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) { |
| iotxn_copyfrom(ums->data_iotxn, out_data, sizeof(*out_data), 0); |
| } |
| return status; |
| } |
| |
| static zx_status_t ums_data_transfer(ums_t* ums, iotxn_t* txn, zx_off_t offset, size_t length, |
| uint8_t ep_address) { |
| iotxn_t* clone = NULL; |
| zx_status_t status = iotxn_clone_partial(txn, txn->vmo_offset + offset, length, &clone); |
| if (status != ZX_OK) { |
| return status; |
| } |
| clone->complete_cb = ums_txn_complete; |
| |
| usb_protocol_data_t* pdata = iotxn_pdata(clone, usb_protocol_data_t); |
| memset(pdata, 0, sizeof(*pdata)); |
| pdata->ep_address = ep_address; |
| |
| completion_t completion = COMPLETION_INIT; |
| clone->cookie = &completion; |
| clone->protocol = ZX_PROTOCOL_USB; |
| ums_queue_request(ums, clone); |
| completion_wait(&completion, ZX_TIME_INFINITE); |
| |
| status = clone->status; |
| if (status == ZX_OK && clone->actual != length) { |
| status = ZX_ERR_IO; |
| } |
| |
| iotxn_release(clone); |
| return status; |
| } |
| |
| static ssize_t ums_read(ums_block_t* dev, iotxn_t* txn) { |
| ums_t* ums = block_to_ums(dev); |
| |
| uint64_t lba = txn->offset / dev->block_size; |
| if (lba > dev->total_blocks) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| uint32_t num_blocks = txn->length / dev->block_size; |
| if (lba + num_blocks >= dev->total_blocks) { |
| num_blocks = dev->total_blocks - lba; |
| if (num_blocks == 0) { |
| return 0; |
| } |
| } |
| |
| size_t blocks_transferred = 0; |
| size_t max_blocks = ums->max_transfer / dev->block_size; |
| zx_status_t status = ZX_OK; |
| |
| while (status == ZX_OK && blocks_transferred < num_blocks) { |
| size_t blocks = num_blocks - blocks_transferred; |
| if (blocks > max_blocks) { |
| blocks = max_blocks; |
| } |
| size_t length = blocks * dev->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(lba + blocks_transferred); |
| 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(lba + blocks_transferred); |
| 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(lba + blocks_transferred); |
| command.length = htobe32(blocks); |
| ums_send_cbw(ums, dev->lun, length, USB_DIR_IN, sizeof(command), &command); |
| } |
| |
| status = ums_data_transfer(ums, txn, blocks_transferred * dev->block_size, length, |
| ums->bulk_in_addr); |
| blocks_transferred += blocks; |
| |
| // receive CSW |
| uint32_t residue; |
| status = ums_read_csw(ums, &residue); |
| if (status == ZX_OK && residue) { |
| printf("unexpected residue in ums_read\n"); |
| status = ZX_ERR_IO; |
| } |
| } |
| |
| if (status == ZX_OK) { |
| return num_blocks * dev->block_size; |
| } else { |
| return status; |
| } |
| } |
| |
| static ssize_t ums_write(ums_block_t* dev, iotxn_t* txn) { |
| ums_t* ums = block_to_ums(dev); |
| |
| uint64_t lba = txn->offset / dev->block_size; |
| if (lba > dev->total_blocks) { |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| uint32_t num_blocks = txn->length / dev->block_size; |
| if (lba + num_blocks >= dev->total_blocks) { |
| num_blocks = dev->total_blocks - lba; |
| if (num_blocks == 0) { |
| return 0; |
| } |
| } |
| |
| size_t blocks_transferred = 0; |
| size_t max_blocks = ums->max_transfer / dev->block_size; |
| zx_status_t status = ZX_OK; |
| |
| while (status == ZX_OK && blocks_transferred < num_blocks) { |
| size_t blocks = num_blocks - blocks_transferred; |
| if (blocks > max_blocks) { |
| blocks = max_blocks; |
| } |
| size_t length = blocks * dev->block_size; |
| |
| // 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(lba + blocks_transferred); |
| 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(lba + blocks_transferred); |
| 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(lba + blocks_transferred); |
| command.length = htobe32(blocks); |
| ums_send_cbw(ums, dev->lun, length, USB_DIR_OUT, sizeof(command), &command); |
| } |
| |
| status = ums_data_transfer(ums, txn, blocks_transferred * dev->block_size, length, |
| ums->bulk_out_addr); |
| blocks_transferred += blocks; |
| |
| // receive CSW |
| uint32_t residue; |
| status = ums_read_csw(ums, &residue); |
| if (status == ZX_OK && residue) { |
| printf("unexpected residue in ums_write\n"); |
| status = ZX_ERR_IO; |
| } |
| } |
| |
| if (status == ZX_OK) { |
| return num_blocks * dev->block_size; |
| } else { |
| return status; |
| } |
| } |
| |
| static void ums_unbind(void* ctx) { |
| ums_t* ums = ctx; |
| |
| // terminate our worker thread |
| mtx_lock(&ums->iotxn_lock); |
| ums->dead = true; |
| mtx_unlock(&ums->iotxn_lock); |
| completion_signal(&ums->iotxn_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->mxdev); |
| } |
| } |
| |
| // remove our root device |
| device_remove(ums->mxdev); |
| } |
| |
| static void ums_release(void* ctx) { |
| ums_t* ums = ctx; |
| |
| if (ums->cbw_iotxn) { |
| iotxn_release(ums->cbw_iotxn); |
| } |
| if (ums->data_iotxn) { |
| iotxn_release(ums->data_iotxn); |
| } |
| if (ums->csw_iotxn) { |
| iotxn_release(ums->csw_iotxn); |
| } |
| |
| 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) { |
| printf("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) { |
| printf("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) { |
| printf("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) { |
| printf("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; |
| |
| 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 { |
| printf("UMS: device_add for block device failed %d\n", status); |
| } |
| } else if (!ready && dev->device_added) { |
| device_remove(dev->mxdev); |
| 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) { |
| printf("ums_inquiry failed for lun %d status: %d\n", lun, status); |
| goto fail; |
| } |
| uint8_t rmb = inquiry_data[1] & 0x80; // Removable Media Bit |
| if (rmb) { |
| ums->block_devs[lun].flags |= BLOCK_FLAG_REMOVABLE; |
| } |
| } |
| |
| // 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, |
| }; |
| |
| status = device_add(ums->usb_mxdev, &args, &ums->mxdev); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| bool wait = true; |
| while (1) { |
| if (wait) { |
| status = completion_wait(&ums->iotxn_completion, ZX_SEC(1)); |
| if (status == ZX_ERR_TIMED_OUT) { |
| if (ums_check_luns_ready(ums) != ZX_OK) { |
| return status; |
| } |
| continue; |
| } |
| completion_reset(&ums->iotxn_completion); |
| } |
| |
| mtx_lock(&ums->iotxn_lock); |
| if (ums->dead) { |
| mtx_unlock(&ums->iotxn_lock); |
| break; |
| } |
| iotxn_t* txn = list_remove_head_type(&ums->queued_iotxns, iotxn_t, node); |
| if (txn == NULL) { |
| mtx_unlock(&ums->iotxn_lock); |
| wait = true; |
| continue; |
| } |
| ums->curr_txn = txn; |
| mtx_unlock(&ums->iotxn_lock); |
| |
| ums_block_t* dev = txn->context; |
| |
| zx_status_t status; |
| if (txn->opcode == IOTXN_OP_READ) { |
| status = ums_read(dev, txn); |
| }else if (txn->opcode == IOTXN_OP_WRITE) { |
| status = ums_write(dev, txn); |
| } else { |
| status = ZX_ERR_INVALID_ARGS; |
| } |
| |
| mtx_lock(&ums->iotxn_lock); |
| // unblock calls to IOCTL_DEVICE_SYNC that are waiting for curr_txn to complete |
| ums_sync_node_t* sync_node; |
| ums_sync_node_t* temp; |
| list_for_every_entry_safe(&ums->sync_nodes, sync_node, temp, ums_sync_node_t, node) { |
| if (sync_node->iotxn == txn) { |
| list_delete(&sync_node->node); |
| completion_signal(&sync_node->completion); |
| } |
| } |
| ums->curr_txn = NULL; |
| // make sure we have processed all queued transactions before waiting again |
| wait = list_is_empty(&ums->queued_iotxns); |
| mtx_unlock(&ums->iotxn_lock); |
| |
| if (status >= 0) { |
| iotxn_complete(txn, ZX_OK, txn->length); |
| } else { |
| iotxn_complete(txn, status, 0); |
| } |
| } |
| |
| // complete any pending txns |
| list_node_t txns = LIST_INITIAL_VALUE(txns); |
| mtx_lock(&ums->iotxn_lock); |
| list_move(&ums->queued_iotxns, &txns); |
| mtx_unlock(&ums->iotxn_lock); |
| |
| iotxn_t* txn; |
| while ((txn = list_remove_head_type(&ums->queued_iotxns, iotxn_t, node)) != NULL) { |
| iotxn_complete(txn, ZX_ERR_IO_NOT_PRESENT, 0); |
| } |
| |
| return ZX_OK; |
| |
| fail: |
| printf("ums_worker_thread failed\n"); |
| ums_release(ums); |
| return status; |
| } |
| |
| static zx_status_t ums_bind(void* ctx, zx_device_t* device, void** cookie) { |
| usb_protocol_t usb; |
| if (device_get_protocol(device, ZX_PROTOCOL_USB, &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 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; |
| 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); |
| if (status != sizeof(max_lun)) { |
| return status; |
| } |
| |
| 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_iotxns); |
| list_initialize(&ums->sync_nodes); |
| completion_reset(&ums->iotxn_completion); |
| mtx_init(&ums->iotxn_lock, mtx_plain); |
| |
| ums->usb_mxdev = 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; |
| |
| 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->cbw_iotxn = usb_alloc_iotxn(bulk_out_addr, sizeof(ums_cbw_t)); |
| if (!ums->cbw_iotxn) { |
| status = ZX_ERR_NO_MEMORY; |
| goto fail; |
| } |
| ums->data_iotxn = usb_alloc_iotxn(bulk_in_addr, PAGE_SIZE); |
| if (!ums->data_iotxn) { |
| status = ZX_ERR_NO_MEMORY; |
| goto fail; |
| } |
| ums->csw_iotxn = usb_alloc_iotxn(bulk_in_addr, sizeof(ums_csw_t)); |
| if (!ums->csw_iotxn) { |
| status = ZX_ERR_NO_MEMORY; |
| goto fail; |
| } |
| |
| ums->cbw_iotxn->length = sizeof(ums_cbw_t); |
| ums->csw_iotxn->length = sizeof(ums_csw_t); |
| ums->cbw_iotxn->complete_cb = ums_txn_complete; |
| ums->data_iotxn->complete_cb = ums_txn_complete; |
| ums->csw_iotxn->complete_cb = ums_txn_complete; |
| |
| ums->tag_send = ums->tag_receive = 8; |
| |
| thrd_create_with_name(&ums->worker_thread, ums_worker_thread, ums, "ums_worker_thread"); |
| |
| return status; |
| |
| fail: |
| printf("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), |
| 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) |