| // Copyright 2017 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 <assert.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/protocol/usb/function.h> |
| #include <usb/usb-request.h> |
| #include <zircon/process.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/device/usb-peripheral.h> |
| #include <zircon/hw/usb/ums.h> |
| |
| #define BLOCK_SIZE 512 |
| #define STORAGE_SIZE (10 * 1024 * 1024) |
| #define BLOCK_COUNT (STORAGE_SIZE / BLOCK_SIZE) |
| #define DATA_REQ_SIZE 16384 |
| #define BULK_MAX_PACKET 512 |
| |
| typedef enum { |
| DATA_STATE_NONE, |
| DATA_STATE_READ, |
| DATA_STATE_WRITE, |
| } ums_data_state_t; |
| |
| static struct { |
| usb_interface_descriptor_t intf; |
| usb_endpoint_descriptor_t out_ep; |
| usb_endpoint_descriptor_t in_ep; |
| } descriptors = { |
| .intf = { |
| .bLength = sizeof(usb_interface_descriptor_t), |
| .bDescriptorType = USB_DT_INTERFACE, |
| // .bInterfaceNumber set later |
| .bAlternateSetting = 0, |
| .bNumEndpoints = 2, |
| .bInterfaceClass = USB_CLASS_MSC, |
| .bInterfaceSubClass = USB_SUBCLASS_MSC_SCSI, |
| .bInterfaceProtocol = USB_PROTOCOL_MSC_BULK_ONLY, |
| .iInterface = 0, |
| }, |
| .out_ep = { |
| .bLength = sizeof(usb_endpoint_descriptor_t), |
| .bDescriptorType = USB_DT_ENDPOINT, |
| // .bEndpointAddress set later |
| .bmAttributes = USB_ENDPOINT_BULK, |
| .wMaxPacketSize = htole16(BULK_MAX_PACKET), |
| .bInterval = 0, |
| }, |
| .in_ep = { |
| .bLength = sizeof(usb_endpoint_descriptor_t), |
| .bDescriptorType = USB_DT_ENDPOINT, |
| // .bEndpointAddress set later |
| .bmAttributes = USB_ENDPOINT_BULK, |
| .wMaxPacketSize = htole16(BULK_MAX_PACKET), |
| .bInterval = 0, |
| }, |
| }; |
| |
| typedef struct { |
| zx_device_t* zxdev; |
| usb_function_protocol_t function; |
| usb_request_t* cbw_req; |
| usb_request_t* data_req; |
| usb_request_t* csw_req; |
| |
| // vmo for backing storage |
| zx_handle_t storage_handle; |
| void* storage; |
| |
| // command we are currently handling |
| ums_cbw_t current_cbw; |
| // data transferred for the current command |
| uint32_t data_length; |
| |
| // state for data transfers |
| ums_data_state_t data_state; |
| // state for reads and writes |
| zx_off_t data_offset; |
| size_t data_remaining; |
| |
| uint8_t bulk_out_addr; |
| uint8_t bulk_in_addr; |
| size_t parent_req_size; |
| } usb_ums_t; |
| |
| static void ums_cbw_complete(void* ctx, usb_request_t* req); |
| static void ums_data_complete(void* ctx, usb_request_t* req); |
| static void ums_csw_complete(void* ctx, usb_request_t* req); |
| |
| static void ums_function_queue_data(usb_ums_t* ums, usb_request_t* req) { |
| ums->data_length += req->header.length; |
| req->header.ep_address = ums->current_cbw.bmCBWFlags & USB_DIR_IN ? |
| ums->bulk_in_addr : ums->bulk_out_addr; |
| usb_request_complete_t complete = { |
| .callback = ums_data_complete, |
| .ctx = ums, |
| }; |
| usb_function_request_queue(&ums->function, req, &complete); |
| } |
| |
| static void ums_queue_csw(usb_ums_t* ums, uint8_t status) { |
| // first queue next cbw so it is ready to go |
| usb_request_complete_t cbw_complete = { |
| .callback = ums_cbw_complete, |
| .ctx = ums, |
| }; |
| usb_function_request_queue(&ums->function, ums->cbw_req, &cbw_complete); |
| |
| usb_request_t* req = ums->csw_req; |
| ums_csw_t* csw; |
| usb_request_mmap(req, (void **)&csw); |
| |
| csw->dCSWSignature = htole32(CSW_SIGNATURE); |
| csw->dCSWTag = ums->current_cbw.dCBWTag; |
| csw->dCSWDataResidue = htole32(le32toh(ums->current_cbw.dCBWDataTransferLength) |
| - ums->data_length); |
| csw->bmCSWStatus = status; |
| |
| req->header.length = sizeof(ums_csw_t); |
| usb_request_complete_t csw_complete = { |
| .callback = ums_csw_complete, |
| .ctx = ums, |
| }; |
| usb_function_request_queue(&ums->function, ums->csw_req, &csw_complete); |
| } |
| |
| static void ums_continue_transfer(usb_ums_t* ums) { |
| usb_request_t* req = ums->data_req; |
| |
| size_t length = ums->data_remaining; |
| if (length > DATA_REQ_SIZE) { |
| length = DATA_REQ_SIZE; |
| } |
| req->header.length = length; |
| |
| if (ums->data_state == DATA_STATE_READ) { |
| usb_request_copy_to(req, ums->storage + ums->data_offset, length, 0); |
| ums_function_queue_data(ums, req); |
| } else if (ums->data_state == DATA_STATE_WRITE) { |
| ums_function_queue_data(ums, req); |
| } else { |
| zxlogf(ERROR, "ums_continue_transfer: bad data state %d\n", ums->data_state); |
| } |
| } |
| |
| static void ums_start_transfer(usb_ums_t* ums, ums_data_state_t state, uint64_t lba, |
| uint32_t blocks) { |
| zx_off_t offset = lba * BLOCK_SIZE; |
| size_t length = blocks * BLOCK_SIZE; |
| |
| if (offset + length > STORAGE_SIZE) { |
| zxlogf(ERROR, "ums_start_transfer: transfer out of range state: %d, lba: %zu blocks: %u\n", |
| state, lba, blocks); |
| // TODO(voydanoff) report error to host |
| return; |
| } |
| |
| ums->data_state = state; |
| ums->data_offset = offset; |
| ums->data_remaining = length; |
| |
| ums_continue_transfer(ums); |
| } |
| |
| static void ums_handle_inquiry(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_inquiry\n"); |
| |
| usb_request_t* req = ums->data_req; |
| uint8_t* buffer; |
| usb_request_mmap(req, (void **)&buffer); |
| memset(buffer, 0, UMS_INQUIRY_TRANSFER_LENGTH); |
| req->header.length = UMS_INQUIRY_TRANSFER_LENGTH; |
| |
| // fill in inquiry result |
| buffer[0] = 0; // Peripheral Device Type: Direct access block device |
| buffer[1] = 0x80; // Removable |
| buffer[2] = 6; // Version SPC-4 |
| buffer[3] = 0x12; // Response Data Format |
| memcpy(buffer + 8, "Google ", 8); |
| memcpy(buffer + 16, "Zircon UMS ", 16); |
| memcpy(buffer + 32, "1.00", 4); |
| |
| ums_function_queue_data(ums, req); |
| ums_queue_csw(ums, CSW_SUCCESS); |
| } |
| |
| static void ums_handle_test_unit_ready(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_test_unit_ready\n"); |
| |
| // no data phase here. Just return status OK |
| ums_queue_csw(ums, CSW_SUCCESS); |
| } |
| |
| static void ums_handle_request_sense(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_request_sense\n"); |
| |
| usb_request_t* req = ums->data_req; |
| uint8_t* buffer; |
| usb_request_mmap(req, (void **)&buffer); |
| memset(buffer, 0, UMS_REQUEST_SENSE_TRANSFER_LENGTH); |
| req->header.length = UMS_REQUEST_SENSE_TRANSFER_LENGTH; |
| |
| // TODO(voydanoff) This is a hack. Figure out correct values to return here. |
| buffer[0] = 0x70; // Response Code |
| buffer[2] = 5; // Illegal Request |
| buffer[7] = 10; // Additional Sense Length |
| buffer[12] = 0x20; // Additional Sense Code |
| |
| ums_function_queue_data(ums, req); |
| ums_queue_csw(ums, CSW_SUCCESS); |
| } |
| |
| static void ums_handle_read_capacity10(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_read_capacity10\n"); |
| |
| usb_request_t* req = ums->data_req; |
| scsi_read_capacity_10_t* data; |
| usb_request_mmap(req, (void **)&data); |
| |
| uint64_t lba = BLOCK_COUNT - 1; |
| if (lba > UINT32_MAX) { |
| data->lba = htobe32(UINT32_MAX); |
| } else { |
| data->lba = htobe32(lba); |
| } |
| data->block_length = htobe32(BLOCK_SIZE); |
| |
| req->header.length = sizeof(*data); |
| ums_function_queue_data(ums, req); |
| ums_queue_csw(ums, CSW_SUCCESS); |
| } |
| |
| static void ums_handle_read_capacity16(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_read_capacity16\n"); |
| |
| usb_request_t* req = ums->data_req; |
| scsi_read_capacity_16_t* data; |
| usb_request_mmap(req, (void **)&data); |
| memset(data, 0, sizeof(*data)); |
| |
| data->lba = htobe64(BLOCK_COUNT - 1); |
| data->block_length = htobe32(BLOCK_SIZE); |
| |
| req->header.length = sizeof(*data); |
| ums_function_queue_data(ums, req); |
| ums_queue_csw(ums, CSW_SUCCESS); |
| } |
| |
| static void ums_handle_mode_sense6(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_mode_sense6\n"); |
| |
| usb_request_t* req = ums->data_req; |
| scsi_mode_sense_6_data_t* data; |
| usb_request_mmap(req, (void **)&data); |
| memset(data, 0, sizeof(*data)); |
| |
| // TODO(voydanoff) fill in data here |
| |
| req->header.length = sizeof(*data); |
| ums_function_queue_data(ums, req); |
| ums_queue_csw(ums, CSW_SUCCESS); |
| } |
| |
| static void ums_handle_read10(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_read10\n"); |
| |
| scsi_command10_t* command = (scsi_command10_t *)cbw->CBWCB; |
| uint32_t lba = be32toh(command->lba); |
| uint32_t blocks = ((uint32_t)command->length_hi << 8) | (uint32_t)command->length_lo; |
| ums_start_transfer(ums, DATA_STATE_READ, lba, blocks); |
| } |
| |
| static void ums_handle_read12(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_read12\n"); |
| |
| scsi_command12_t* command = (scsi_command12_t *)cbw->CBWCB; |
| uint64_t lba = be32toh(command->lba); |
| uint32_t blocks = be32toh(command->length); |
| ums_start_transfer(ums, DATA_STATE_READ, lba, blocks); |
| } |
| |
| static void ums_handle_read16(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_read16\n"); |
| |
| scsi_command16_t* command = (scsi_command16_t *)cbw->CBWCB; |
| uint32_t lba = be64toh(command->lba); |
| uint32_t blocks = be32toh(command->length); |
| ums_start_transfer(ums, DATA_STATE_READ, lba, blocks); |
| } |
| |
| static void ums_handle_write10(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_write10\n"); |
| |
| scsi_command10_t* command = (scsi_command10_t *)cbw->CBWCB; |
| uint32_t lba = be32toh(command->lba); |
| uint32_t blocks = ((uint32_t)command->length_hi << 8) | (uint32_t)command->length_lo; |
| ums_start_transfer(ums, DATA_STATE_WRITE, lba, blocks); |
| } |
| |
| static void ums_handle_write12(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_write12\n"); |
| |
| scsi_command12_t* command = (scsi_command12_t *)cbw->CBWCB; |
| uint64_t lba = be32toh(command->lba); |
| uint32_t blocks = be32toh(command->length); |
| ums_start_transfer(ums, DATA_STATE_WRITE, lba, blocks); |
| } |
| |
| static void ums_handle_write16(usb_ums_t* ums, ums_cbw_t* cbw) { |
| zxlogf(TRACE, "ums_handle_write16\n"); |
| |
| scsi_command16_t* command = (scsi_command16_t *)cbw->CBWCB; |
| uint64_t lba = be64toh(command->lba); |
| uint32_t blocks = be32toh(command->length); |
| ums_start_transfer(ums, DATA_STATE_WRITE, lba, blocks); |
| } |
| |
| static void ums_handle_cbw(usb_ums_t* ums, ums_cbw_t* cbw) { |
| if (le32toh(cbw->dCBWSignature) != CBW_SIGNATURE) { |
| zxlogf(ERROR, "ums_handle_cbw: bad dCBWSignature 0x%x\n", le32toh(cbw->dCBWSignature)); |
| return; |
| } |
| |
| // reset data length for computing residue |
| ums->data_length = 0; |
| |
| // all SCSI commands have opcode in the same place, so using scsi_command6_t works here. |
| scsi_command6_t* command = (scsi_command6_t *)cbw->CBWCB; |
| switch (command->opcode) { |
| case UMS_INQUIRY: |
| ums_handle_inquiry(ums, cbw); |
| break; |
| case UMS_TEST_UNIT_READY: |
| ums_handle_test_unit_ready(ums, cbw); |
| break; |
| case UMS_REQUEST_SENSE: |
| ums_handle_request_sense(ums, cbw); |
| break; |
| case UMS_READ_CAPACITY10: |
| ums_handle_read_capacity10(ums, cbw); |
| break; |
| case UMS_READ_CAPACITY16: |
| ums_handle_read_capacity16(ums, cbw); |
| break; |
| case UMS_MODE_SENSE6: |
| ums_handle_mode_sense6(ums, cbw); |
| break; |
| case UMS_READ10: |
| ums_handle_read10(ums, cbw); |
| break; |
| case UMS_READ12: |
| ums_handle_read12(ums, cbw); |
| break; |
| case UMS_READ16: |
| ums_handle_read16(ums, cbw); |
| break; |
| case UMS_WRITE10: |
| ums_handle_write10(ums, cbw); |
| break; |
| case UMS_WRITE12: |
| ums_handle_write12(ums, cbw); |
| break; |
| case UMS_WRITE16: |
| ums_handle_write16(ums, cbw); |
| break; |
| default: |
| zxlogf(TRACE, "ums_handle_cbw: unsupported opcode %d\n", command->opcode); |
| if (cbw->dCBWDataTransferLength) { |
| // queue zero length packet to satisfy data phase |
| usb_request_t* req = ums->data_req; |
| req->header.length = 0; |
| ums_function_queue_data(ums, req); |
| } |
| ums_queue_csw(ums, CSW_FAILED); |
| break; |
| } |
| } |
| |
| static void ums_cbw_complete(void* ctx, usb_request_t* req) { |
| usb_ums_t* ums = ctx; |
| |
| zxlogf(TRACE, "ums_cbw_complete %d %ld\n", req->response.status, req->response.actual); |
| |
| if (req->response.status == ZX_OK && req->response.actual == sizeof(ums_cbw_t)) { |
| ums_cbw_t* cbw = &ums->current_cbw; |
| usb_request_copy_from(req, cbw, sizeof(*cbw), 0); |
| ums_handle_cbw(ums, cbw); |
| } |
| } |
| |
| static void ums_data_complete(void* ctx, usb_request_t* req) { |
| usb_ums_t* ums = ctx; |
| |
| zxlogf(TRACE, "ums_data_complete %d %ld\n", req->response.status, req->response.actual); |
| |
| if (ums->data_state == DATA_STATE_WRITE) { |
| usb_request_copy_from(req, ums->storage + ums->data_offset, req->response.actual, 0); |
| } else if (ums->data_state != DATA_STATE_READ) { |
| return; |
| } |
| |
| ums->data_offset += req->response.actual; |
| if (ums->data_remaining > req->response.actual) { |
| ums->data_remaining -= req->response.actual; |
| } else { |
| ums->data_remaining = 0; |
| } |
| |
| if (ums->data_remaining > 0) { |
| ums_continue_transfer(ums); |
| } else { |
| ums->data_state = DATA_STATE_NONE; |
| ums_queue_csw(ums, CSW_SUCCESS); |
| } |
| } |
| |
| static void ums_csw_complete(void* ctx, usb_request_t* req) { |
| zxlogf(TRACE, "ums_csw_complete %d %ld\n", req->response.status, req->response.actual); |
| } |
| |
| static size_t ums_get_descriptors_size(void* ctx) { |
| return sizeof(descriptors); |
| } |
| |
| static void ums_get_descriptors(void* ctx, void* buffer, size_t buffer_size, size_t* out_actual) { |
| size_t length = sizeof(descriptors); |
| if (length > buffer_size) { |
| length = buffer_size; |
| } |
| memcpy(buffer, &descriptors, length); |
| *out_actual = length; |
| } |
| |
| static zx_status_t ums_control(void* ctx, const usb_setup_t* setup, const void* write_buffer, |
| size_t write_size, void* out_read_buffer, size_t read_size, |
| size_t* out_read_actual) { |
| if (setup->bmRequestType == (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) && |
| setup->bRequest == USB_REQ_GET_MAX_LUN && setup->wValue == 0 && setup->wIndex == 0 && |
| setup->wLength >= sizeof(uint8_t)) { |
| *((uint8_t *)out_read_buffer) = 0; |
| *out_read_actual = sizeof(uint8_t); |
| return ZX_OK; |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t ums_set_configured(void* ctx, bool configured, usb_speed_t speed) { |
| zxlogf(TRACE, "ums_set_configured %d %d\n", configured, speed); |
| usb_ums_t* ums = ctx; |
| zx_status_t status; |
| |
| // TODO(voydanoff) fullspeed and superspeed support |
| if (configured) { |
| if ((status = usb_function_config_ep(&ums->function, &descriptors.out_ep, NULL)) != ZX_OK || |
| (status = usb_function_config_ep(&ums->function, &descriptors.in_ep, NULL)) != ZX_OK) { |
| zxlogf(ERROR, "ums_set_configured: usb_function_config_ep failed\n"); |
| } |
| } else { |
| if ((status = usb_function_disable_ep(&ums->function, ums->bulk_out_addr)) != ZX_OK || |
| (status = usb_function_disable_ep(&ums->function, ums->bulk_in_addr)) != ZX_OK) { |
| zxlogf(ERROR, "ums_set_configured: usb_function_disable_ep failed\n"); |
| } |
| } |
| |
| if (configured && status == ZX_OK) { |
| // queue first read on OUT endpoint |
| usb_request_complete_t cbw_complete = { |
| .callback = ums_cbw_complete, |
| .ctx = ums, |
| }; |
| usb_function_request_queue(&ums->function, ums->cbw_req, &cbw_complete); |
| } |
| return status; |
| } |
| |
| static zx_status_t ums_set_interface(void* ctx, uint8_t interface, uint8_t alt_setting) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| usb_function_interface_ops_t ums_device_ops = { |
| .get_descriptors_size = ums_get_descriptors_size, |
| .get_descriptors = ums_get_descriptors, |
| .control = ums_control, |
| .set_configured = ums_set_configured, |
| .set_interface = ums_set_interface, |
| }; |
| |
| static void usb_ums_unbind(void* ctx) { |
| zxlogf(TRACE, "usb_ums_unbind\n"); |
| usb_ums_t* ums = ctx; |
| device_remove(ums->zxdev); |
| } |
| |
| static void usb_ums_release(void* ctx) { |
| zxlogf(TRACE, "usb_ums_release\n"); |
| usb_ums_t* ums = ctx; |
| |
| if (ums->storage) { |
| zx_vmar_unmap(zx_vmar_root_self(), (uintptr_t)ums->storage, STORAGE_SIZE); |
| } |
| zx_handle_close(ums->storage_handle); |
| |
| if (ums->cbw_req) { |
| usb_request_release(ums->cbw_req); |
| } |
| if (ums->data_req) { |
| usb_request_release(ums->data_req); |
| } |
| if (ums->cbw_req) { |
| usb_request_release(ums->csw_req); |
| } |
| free(ums); |
| } |
| |
| static zx_protocol_device_t usb_ums_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .unbind = usb_ums_unbind, |
| .release = usb_ums_release, |
| }; |
| |
| zx_status_t usb_ums_bind(void* ctx, zx_device_t* parent) { |
| zxlogf(INFO, "usb_ums_bind\n"); |
| |
| usb_ums_t* ums = calloc(1, sizeof(usb_ums_t)); |
| if (!ums) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| ums->data_state = DATA_STATE_NONE; |
| |
| zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_USB_FUNCTION, &ums->function); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| ums->parent_req_size = usb_function_get_request_size(&ums->function); |
| ZX_DEBUG_ASSERT(ums->parent_req_size != 0); |
| |
| status = usb_function_alloc_interface(&ums->function, &descriptors.intf.bInterfaceNumber); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "usb_ums_bind: usb_function_alloc_interface failed\n"); |
| goto fail; |
| } |
| status = usb_function_alloc_ep(&ums->function, USB_DIR_OUT, &ums->bulk_out_addr); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "usb_ums_bind: usb_function_alloc_ep failed\n"); |
| goto fail; |
| } |
| status = usb_function_alloc_ep(&ums->function, USB_DIR_IN, &ums->bulk_in_addr); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "usb_ums_bind: usb_function_alloc_ep failed\n"); |
| goto fail; |
| } |
| |
| descriptors.out_ep.bEndpointAddress = ums->bulk_out_addr; |
| descriptors.in_ep.bEndpointAddress = ums->bulk_in_addr; |
| |
| status = usb_request_alloc(&ums->cbw_req, BULK_MAX_PACKET, |
| ums->bulk_out_addr, ums->parent_req_size); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| // Endpoint for data_req depends on current_cbw.bmCBWFlags, |
| // and will be set in ums_function_queue_data. |
| status = usb_request_alloc(&ums->data_req, DATA_REQ_SIZE, 0, ums->parent_req_size); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| status = usb_request_alloc(&ums->csw_req, BULK_MAX_PACKET, |
| ums->bulk_in_addr, ums->parent_req_size); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| // create and map a VMO |
| status = zx_vmo_create(STORAGE_SIZE, 0, &ums->storage_handle); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| status = zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, |
| 0, ums->storage_handle, 0, STORAGE_SIZE, (zx_vaddr_t *)&ums->storage); |
| if (status != ZX_OK) { |
| goto fail; |
| } |
| |
| ums->csw_req->header.length = sizeof(ums_csw_t); |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "usb-ums-function", |
| .ctx = ums, |
| .ops = &usb_ums_proto, |
| }; |
| |
| status = device_add(parent, &args, &ums->zxdev); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "usb_device_bind add_device failed %d\n", status); |
| goto fail; |
| } |
| |
| usb_function_set_interface(&ums->function, ums, &ums_device_ops); |
| |
| return ZX_OK; |
| |
| fail: |
| usb_ums_release(ums); |
| return status; |
| } |
| |
| static zx_driver_ops_t usb_ums_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = usb_ums_bind, |
| }; |
| |
| // clang-format off |
| ZIRCON_DRIVER_BEGIN(usb_ums, usb_ums_ops, "zircon", "0.1", 4) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB_FUNCTION), |
| 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_ums) |