blob: c8b0822a86734ce533b5da60305f0b81ec556b3d [file] [log] [blame]
// 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)