| // 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 <threads.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/metadata.h> |
| #include <ddk/phys-iter.h> |
| #include <ddk/protocol/usb/dci.h> |
| #include <ddk/protocol/usb/function.h> |
| #include <ddk/protocol/usb/modeswitch.h> |
| #include <usb/usb-request.h> |
| #include <zircon/listnode.h> |
| #include <zircon/device/usb-peripheral.h> |
| #include <zircon/usb/peripheral/c/fidl.h> |
| #include <zircon/hw/usb-cdc.h> |
| #include <zircon/hw/usb.h> |
| |
| /* |
| THEORY OF OPERATION |
| |
| This driver is responsible for USB in the peripheral role, that is, |
| acting as a USB device to a USB host. |
| It serves as the central point of coordination for the peripheral role. |
| It is configured via ioctls in the ZX_PROTOCOL_USB_DEVICE protocol |
| (which is used by the usbctl command line program). |
| Based on this configuration, it creates one or more DDK devices with protocol |
| ZX_PROTOCOL_USB_FUNCTION. These devices are bind points for USB function drivers, |
| which implement USB interfaces for particular functions (like USB ethernet or mass storage). |
| This driver also binds to a device with protocol ZX_PROTOCOL_USB_DCI |
| (Device Controller Interface) which is implemented by a driver for the actual |
| USB controller hardware for the peripheral role. |
| |
| There are several steps needed to initialize and start USB in the peripheral role. |
| The first step is setting up the USB configuration via ioctls. |
| ioctl_usb_peripheral_set_device_desc() sets the USB device descriptor to be presented |
| to the host during enumeration. |
| Next, ioctl_usb_peripheral_add_function() can be called one or more times to add |
| descriptors for the USB functions to be included in the USB configuration. |
| Finally after all the functions have been added, ioctl_usb_peripheral_bind_functions() |
| tells this driver that configuration is complete and it is now possible to build |
| the configuration descriptor. Once we get to this point, usb_device_t.functions_bound |
| is set to true. |
| |
| Independent of this configuration process, ioctl_usb_peripheral_set_mode() can be used |
| to configure the role of the USB controller. If the role is set to USB_MODE_PERIPHERAL |
| and "functions_bound" is true, then we are ready to start USB in peripheral role. |
| At this point, we create DDK devices for our list of functions. |
| When the function drivers bind to these functions, they register an interface of type |
| usb_function_interface_t with this driver via the usb_function_register() API. |
| Once all of the function drivers have registered themselves this way, |
| usb_device_t.functions_registered is set to true. |
| |
| if the usb mode is set to USB_MODE_PERIPHERAL and "functions_registered" is true, |
| we are now finally ready to operate in the peripheral role. |
| At this point we can inform the DCI driver to start running in peripheral role |
| by calling usb_mode_switch_set_mode(USB_MODE_PERIPHERAL) on its ZX_PROTOCOL_USB_MODE_SWITCH |
| interface. Now the USB controller hardware is up and running as a USB peripheral. |
| |
| Teardown of the peripheral role one of two ways: |
| First, ioctl_usb_peripheral_clear_functions() will reset this device's list of USB functions. |
| Second, the USB mode can be set to something other than USB_MODE_PERIPHERAL. |
| In this second case, we will remove the DDK devices for the USB functions |
| so the function drivers will unbind, but the USB configuration remains ready to go |
| for when the USB mode is switched back to USB_MODE_PERIPHERAL. |
| */ |
| |
| #define MAX_INTERFACES 32 |
| |
| typedef zircon_usb_peripheral_FunctionDescriptor usb_function_descriptor_t; |
| |
| typedef struct { |
| zx_device_t* zxdev; |
| zx_device_t* dci_dev; |
| struct usb_device* dev; |
| list_node_t node; |
| usb_function_interface_t interface; |
| usb_function_descriptor_t desc; |
| usb_descriptor_header_t* descriptors; |
| size_t descriptors_length; |
| unsigned num_interfaces; |
| } usb_function_t; |
| |
| typedef struct usb_device { |
| // the device we publish |
| zx_device_t* zxdev; |
| // our parent device |
| zx_device_t* dci_dev; |
| // our parent's DCI protocol |
| usb_dci_protocol_t usb_dci; |
| // our parent's USB switch protocol |
| usb_mode_switch_protocol_t usb_mode_switch; |
| // true if our parent implements usb_mode_switch_protocol_t |
| bool has_usb_mode_switch; |
| // USB device descriptor set via ioctl_usb_peripheral_set_device_desc() |
| usb_device_descriptor_t device_desc; |
| // USB configuration descriptor, synthesized from our functions' descriptors |
| usb_configuration_descriptor_t* config_desc; |
| // map from interface number to function |
| usb_function_t* interface_map[MAX_INTERFACES]; |
| // map from endpoint index to function |
| usb_function_t* endpoint_map[USB_MAX_EPS]; |
| // strings for USB string descriptors |
| char* strings[256]; |
| // list of usb_function_t |
| list_node_t functions; |
| // mutex for protecting our state |
| mtx_t lock; |
| // current USB mode set via ioctl_usb_peripheral_set_mode() |
| usb_mode_t usb_mode; |
| // our parent's USB mode |
| usb_mode_t dci_usb_mode; |
| // set if ioctl_usb_peripheral_bind_functions() has been called |
| // and we have a complete list of our function. |
| bool functions_bound; |
| // set if all our functions have registered their usb_function_interface_t |
| bool functions_registered; |
| // true if we have added child devices for our functions |
| bool function_devs_added; |
| // true if we are connected to a host |
| bool connected; |
| // current configuration number selected via USB_REQ_SET_CONFIGURATION |
| // (will be 0 or 1 since we currently do not support multiple configurations) |
| uint8_t configuration; |
| // USB connection speed |
| usb_speed_t speed; |
| size_t parent_request_size; |
| } usb_device_t; |
| |
| // for mapping bEndpointAddress value to/from index in range 0 - 31 |
| // OUT endpoints are in range 1 - 15, IN endpoints are in range 17 - 31 |
| #define ep_address_to_index(addr) (((addr) & 0xF) | (((addr) & 0x80) >> 3)) |
| #define ep_index_to_address(index) (((index) & 0xF) | (((index) & 0x10) << 3)) |
| #define OUT_EP_START 1 |
| #define OUT_EP_END 15 |
| #define IN_EP_START 17 |
| #define IN_EP_END 31 |
| |
| static zx_status_t usb_dev_state_changed_locked(usb_device_t* dev); |
| |
| static zx_status_t usb_device_alloc_string_desc(usb_device_t* dev, const char* string, |
| uint8_t* out_index) { |
| |
| mtx_lock(&dev->lock); |
| |
| unsigned i; |
| for (i = 1; i < countof(dev->strings); i++) { |
| if (!dev->strings[i]) { |
| break; |
| } |
| } |
| if (i == countof(dev->strings)) { |
| mtx_unlock(&dev->lock); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| dev->strings[i] = strdup(string); |
| if (!dev->strings[i]) { |
| mtx_unlock(&dev->lock); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| mtx_unlock(&dev->lock); |
| |
| *out_index = i; |
| return ZX_OK; |
| } |
| |
| static zx_protocol_device_t function_proto = { |
| .version = DEVICE_OPS_VERSION, |
| // Note that we purposely do not have a release callback for USB functions. |
| // The functions are kept on a list when not active so they can be re-added |
| // when reentering device mode. |
| }; |
| |
| static zx_status_t usb_device_function_registered(usb_device_t* dev) { |
| mtx_lock(&dev->lock); |
| |
| if (dev->config_desc) { |
| zxlogf(ERROR, "usb_device_function_registered: already have configuration descriptor!\n"); |
| mtx_unlock(&dev->lock); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| // check to see if we have all our functions registered |
| // if so, we can build our configuration descriptor and tell the DCI driver we are ready |
| usb_function_t* function; |
| size_t length = sizeof(usb_configuration_descriptor_t); |
| list_for_every_entry(&dev->functions, function, usb_function_t, node) { |
| if (function->descriptors) { |
| length += function->descriptors_length; |
| } else { |
| // need to wait for more functions to register |
| mtx_unlock(&dev->lock); |
| return ZX_OK; |
| } |
| } |
| |
| // build our configuration descriptor |
| usb_configuration_descriptor_t* config_desc = malloc(length); |
| if (!config_desc) { |
| mtx_unlock(&dev->lock); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| config_desc->bLength = sizeof(*config_desc); |
| config_desc->bDescriptorType = USB_DT_CONFIG; |
| config_desc->wTotalLength = htole16(length); |
| config_desc->bNumInterfaces = 0; |
| config_desc->bConfigurationValue = 1; |
| config_desc->iConfiguration = 0; |
| // TODO(voydanoff) add a way to configure bmAttributes and bMaxPower |
| config_desc->bmAttributes = USB_CONFIGURATION_SELF_POWERED | USB_CONFIGURATION_RESERVED_7; |
| config_desc->bMaxPower = 0; |
| |
| void* dest = config_desc + 1; |
| list_for_every_entry(&dev->functions, function, usb_function_t, node) { |
| memcpy(dest, function->descriptors, function->descriptors_length); |
| dest += function->descriptors_length; |
| config_desc->bNumInterfaces += function->num_interfaces; |
| } |
| dev->config_desc = config_desc; |
| |
| zxlogf(TRACE, "usb_device_function_registered functions_registered = true\n"); |
| dev->functions_registered = true; |
| |
| zx_status_t status = usb_dev_state_changed_locked(dev); |
| mtx_unlock(&dev->lock); |
| return status; |
| } |
| |
| static zx_status_t usb_func_set_interface(void* ctx, const usb_function_interface_t* interface) { |
| usb_function_t* function = ctx; |
| usb_device_t* dev = function->dev; |
| usb_function_t** endpoint_map = dev->endpoint_map; |
| |
| size_t length = usb_function_interface_get_descriptors_size(interface); |
| void* descriptors = malloc(length); |
| if (!descriptors) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| size_t actual; |
| usb_function_interface_get_descriptors(interface, descriptors, length, &actual); |
| if (actual != length) { |
| zxlogf(ERROR, "usb_function_interface_get_descriptors failed\n"); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| usb_interface_descriptor_t* intf_desc = (usb_interface_descriptor_t *)descriptors; |
| if (intf_desc->bDescriptorType != USB_DT_INTERFACE || |
| intf_desc->bLength != sizeof(usb_interface_descriptor_t)) { |
| zxlogf(ERROR, "usb_func_set_interface: first descriptor not an interface descriptor\n"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| const usb_descriptor_header_t* end = (void *)descriptors + length; |
| const usb_descriptor_header_t* header = descriptors; |
| |
| while (header < end) { |
| if (header->bDescriptorType == USB_DT_INTERFACE) { |
| usb_interface_descriptor_t* desc = (usb_interface_descriptor_t *)header; |
| if (desc->bInterfaceNumber >= countof(dev->interface_map) || |
| dev->interface_map[desc->bInterfaceNumber] != function) { |
| zxlogf(ERROR, "usb_func_set_interface: bInterfaceNumber %u\n", |
| desc->bInterfaceNumber); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (desc->bAlternateSetting == 0) { |
| function->num_interfaces++; |
| } |
| } else if (header->bDescriptorType == USB_DT_ENDPOINT) { |
| usb_endpoint_descriptor_t* desc = (usb_endpoint_descriptor_t *)header; |
| unsigned index = ep_address_to_index(desc->bEndpointAddress); |
| if (index == 0 || index >= countof(dev->endpoint_map) || |
| endpoint_map[index] != function) { |
| zxlogf(ERROR, "usb_func_set_interface: bad endpoint address 0x%X\n", |
| desc->bEndpointAddress); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| if (header->bLength == 0) { |
| zxlogf(ERROR, "usb_func_set_interface: zero length descriptor\n"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| header = (void *)header + header->bLength; |
| } |
| |
| function->descriptors = malloc(length); |
| if (!function->descriptors) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| memcpy(function->descriptors, descriptors, length); |
| function->descriptors_length = length; |
| memcpy(&function->interface, interface, sizeof(function->interface)); |
| |
| return usb_device_function_registered(function->dev); |
| } |
| |
| static zx_status_t usb_func_alloc_interface(void* ctx, uint8_t* out_intf_num) { |
| usb_function_t* function = ctx; |
| usb_device_t* dev = function->dev; |
| |
| mtx_lock(&dev->lock); |
| |
| for (unsigned i = 0; i < countof(dev->interface_map); i++) { |
| if (dev->interface_map[i] == NULL) { |
| dev->interface_map[i] = function; |
| mtx_unlock(&dev->lock); |
| *out_intf_num = i; |
| return ZX_OK; |
| } |
| } |
| |
| mtx_unlock(&dev->lock); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| static zx_status_t usb_func_alloc_ep(void* ctx, uint8_t direction, uint8_t* out_address) { |
| unsigned start, end; |
| |
| if (direction == USB_DIR_OUT) { |
| start = OUT_EP_START; |
| end = OUT_EP_END; |
| } else if (direction == USB_DIR_IN) { |
| start = IN_EP_START; |
| end = IN_EP_END; |
| } else { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| usb_function_t* function = ctx; |
| usb_device_t* dev = function->dev; |
| usb_function_t** endpoint_map = dev->endpoint_map; |
| |
| mtx_lock(&dev->lock); |
| for (unsigned index = start; index <= end; index++) { |
| if (endpoint_map[index] == NULL) { |
| endpoint_map[index] = function; |
| mtx_unlock(&dev->lock); |
| *out_address = ep_index_to_address(index); |
| return ZX_OK; |
| } |
| } |
| |
| mtx_unlock(&dev->lock); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| static zx_status_t usb_func_config_ep(void* ctx, const usb_endpoint_descriptor_t* ep_desc, |
| const usb_ss_ep_comp_descriptor_t* ss_comp_desc) { |
| usb_function_t* function = ctx; |
| return usb_dci_config_ep(&function->dev->usb_dci, ep_desc, ss_comp_desc); |
| } |
| |
| static zx_status_t usb_func_disable_ep(void* ctx, uint8_t ep_addr) { |
| zxlogf(TRACE, "usb_func_disable_ep\n"); |
| usb_function_t* function = ctx; |
| return usb_dci_disable_ep(&function->dev->usb_dci, ep_addr); |
| } |
| |
| static zx_status_t usb_func_alloc_string_desc(void* ctx, const char* string, uint8_t* out_index) { |
| usb_function_t* function = ctx; |
| return usb_device_alloc_string_desc(function->dev, string, out_index); |
| } |
| |
| static void usb_func_request_queue(void* ctx, usb_request_t* req, const usb_request_complete_t* cb) { |
| usb_function_t* function = ctx; |
| usb_dci_request_queue(&function->dev->usb_dci, req, cb); |
| } |
| |
| static zx_status_t usb_func_ep_set_stall(void* ctx, uint8_t ep_address) { |
| usb_function_t* function = ctx; |
| return usb_dci_ep_set_stall(&function->dev->usb_dci, ep_address); |
| } |
| |
| static zx_status_t usb_func_ep_clear_stall(void* ctx, uint8_t ep_address) { |
| usb_function_t* function = ctx; |
| return usb_dci_ep_clear_stall(&function->dev->usb_dci, ep_address); |
| } |
| |
| static size_t usb_func_get_request_size(void* ctx) { |
| usb_function_t* function = ctx; |
| return function->dev->parent_request_size; |
| } |
| |
| usb_function_protocol_ops_t usb_function_proto = { |
| .set_interface = usb_func_set_interface, |
| .alloc_interface = usb_func_alloc_interface, |
| .alloc_ep = usb_func_alloc_ep, |
| .config_ep = usb_func_config_ep, |
| .disable_ep = usb_func_disable_ep, |
| .alloc_string_desc = usb_func_alloc_string_desc, |
| .request_queue = usb_func_request_queue, |
| .ep_set_stall = usb_func_ep_set_stall, |
| .ep_clear_stall = usb_func_ep_clear_stall, |
| .get_request_size = usb_func_get_request_size, |
| }; |
| |
| static zx_status_t usb_dev_get_descriptor(usb_device_t* dev, uint8_t request_type, |
| uint16_t value, uint16_t index, void* buffer, |
| size_t length, size_t* out_actual) { |
| uint8_t type = request_type & USB_TYPE_MASK; |
| |
| if (type == USB_TYPE_STANDARD) { |
| uint8_t desc_type = value >> 8; |
| if (desc_type == USB_DT_DEVICE && index == 0) { |
| const usb_device_descriptor_t* desc = &dev->device_desc; |
| if (desc->bLength == 0) { |
| zxlogf(ERROR, "usb_dev_get_descriptor: device descriptor not set\n"); |
| return ZX_ERR_INTERNAL; |
| } |
| if (length > sizeof(*desc)) length = sizeof(*desc); |
| memcpy(buffer, desc, length); |
| *out_actual = length; |
| return ZX_OK; |
| } else if (desc_type == USB_DT_CONFIG && index == 0) { |
| const usb_configuration_descriptor_t* desc = dev->config_desc; |
| if (!desc) { |
| zxlogf(ERROR, "usb_dev_get_descriptor: configuration descriptor not set\n"); |
| return ZX_ERR_INTERNAL; |
| } |
| uint16_t desc_length = letoh16(desc->wTotalLength); |
| if (length > desc_length) length =desc_length; |
| memcpy(buffer, desc, length); |
| *out_actual = length; |
| return ZX_OK; |
| } |
| else if (value >> 8 == USB_DT_STRING) { |
| uint8_t desc[255]; |
| usb_descriptor_header_t* header = (usb_descriptor_header_t *)desc; |
| header->bDescriptorType = USB_DT_STRING; |
| |
| uint8_t string_index = value & 0xFF; |
| if (string_index == 0) { |
| // special case - return language list |
| header->bLength = 4; |
| desc[2] = 0x09; // language ID |
| desc[3] = 0x04; |
| } else { |
| char* string = dev->strings[string_index]; |
| if (!string) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| unsigned index = 2; |
| |
| // convert ASCII to Unicode |
| if (string) { |
| while (*string && index < sizeof(desc) - 2) { |
| desc[index++] = *string++; |
| desc[index++] = 0; |
| } |
| } |
| header->bLength = index; |
| } |
| |
| if (header->bLength < length) length = header->bLength; |
| memcpy(buffer, desc, length); |
| *out_actual = length; |
| return ZX_OK; |
| } |
| } |
| |
| zxlogf(ERROR, "usb_device_get_descriptor unsupported value: %d index: %d\n", value, index); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t usb_dev_set_configuration(usb_device_t* dev, uint8_t configuration) { |
| zx_status_t status = ZX_OK; |
| bool configured = configuration > 0; |
| |
| mtx_lock(&dev->lock); |
| |
| usb_function_t* function; |
| list_for_every_entry(&dev->functions, function, usb_function_t, node) { |
| if (function->interface.ops) { |
| status = usb_function_interface_set_configured(&function->interface, configured, |
| dev->speed); |
| if (status != ZX_OK && configured) { |
| goto fail; |
| } |
| } |
| } |
| |
| dev->configuration = configuration; |
| |
| fail: |
| mtx_unlock(&dev->lock); |
| return status; |
| } |
| |
| static zx_status_t usb_dev_set_interface(usb_device_t* dev, unsigned interface, |
| unsigned alt_setting) { |
| usb_function_t* function = dev->interface_map[interface]; |
| if (function && function->interface.ops) { |
| return usb_function_interface_set_interface(&function->interface, interface, alt_setting); |
| } |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static zx_status_t usb_dev_control(void* ctx, const usb_setup_t* setup, const void* write_buffer, |
| size_t write_size, void* read_buffer, size_t read_size, |
| size_t* out_read_actual) { |
| usb_device_t* dev = ctx; |
| uint8_t request_type = setup->bmRequestType; |
| uint8_t direction = request_type & USB_DIR_MASK; |
| uint8_t request = setup->bRequest; |
| uint16_t value = le16toh(setup->wValue); |
| uint16_t index = le16toh(setup->wIndex); |
| uint16_t length = le16toh(setup->wLength); |
| |
| if (direction == USB_DIR_IN && length > read_size) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } else if (direction == USB_DIR_OUT && length > write_size) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| if ((write_size > 0 && write_buffer == NULL) || (read_size > 0 && read_buffer == NULL)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zxlogf(TRACE, "usb_dev_control type: 0x%02X req: %d value: %d index: %d length: %d\n", |
| request_type, request, value, index, length); |
| |
| switch (request_type & USB_RECIP_MASK) { |
| case USB_RECIP_DEVICE: |
| // handle standard device requests |
| if ((request_type & (USB_DIR_MASK | USB_TYPE_MASK)) == (USB_DIR_IN | USB_TYPE_STANDARD) && |
| request == USB_REQ_GET_DESCRIPTOR) { |
| return usb_dev_get_descriptor(dev, request_type, value, index, read_buffer, length, |
| out_read_actual); |
| } else if (request_type == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE) && |
| request == USB_REQ_SET_CONFIGURATION && length == 0) { |
| return usb_dev_set_configuration(dev, value); |
| } else if (request_type == (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) && |
| request == USB_REQ_GET_CONFIGURATION && length > 0) { |
| *((uint8_t *)read_buffer) = dev->configuration; |
| *out_read_actual = sizeof(uint8_t); |
| return ZX_OK; |
| } |
| break; |
| case USB_RECIP_INTERFACE: { |
| if (request_type == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) && |
| request == USB_REQ_SET_INTERFACE && length == 0) { |
| return usb_dev_set_interface(dev, index, value); |
| } else { |
| // delegate to the function driver for the interface |
| usb_function_t* function = dev->interface_map[index]; |
| if (function && function->interface.ops) { |
| return usb_function_interface_control(&function->interface, setup, write_buffer, |
| write_size, read_buffer, read_size, |
| out_read_actual); |
| } |
| } |
| break; |
| } |
| case USB_RECIP_ENDPOINT: { |
| // delegate to the function driver for the endpoint |
| index = ep_address_to_index(index); |
| if (index == 0 || index >= USB_MAX_EPS) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| usb_function_t* function = dev->endpoint_map[index]; |
| if (function && function->interface.ops) { |
| return usb_function_interface_control(&function->interface, setup, write_buffer, |
| write_size, read_buffer, read_size, |
| out_read_actual); |
| } |
| break; |
| } |
| case USB_RECIP_OTHER: |
| // TODO(voydanoff) - how to handle this? |
| default: |
| break; |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static void usb_dev_set_connected(void* ctx, bool connected) { |
| usb_device_t* dev = ctx; |
| if (dev->connected != connected) { |
| if (!connected) { |
| usb_function_t* function; |
| list_for_every_entry(&dev->functions, function, usb_function_t, node) { |
| if (function->interface.ops) { |
| usb_function_interface_set_configured(&function->interface, false, |
| USB_SPEED_UNDEFINED); |
| } |
| } |
| } |
| |
| dev->connected = connected; |
| } |
| } |
| |
| static void usb_dev_set_speed(void* ctx, usb_speed_t speed) { |
| usb_device_t* dev = ctx; |
| dev->speed = speed; |
| } |
| |
| usb_dci_interface_ops_t dci_ops = { |
| .control = usb_dev_control, |
| .set_connected = usb_dev_set_connected, |
| .set_speed = usb_dev_set_speed, |
| }; |
| |
| static zx_status_t usb_dev_set_device_desc(void* ctx, |
| const zircon_usb_peripheral_DeviceDescriptor* desc, |
| fidl_txn_t* txn) { |
| usb_device_t* dev = ctx; |
| |
| zx_status_t status; |
| if (desc->bNumConfigurations != 1) { |
| zxlogf(ERROR, "usb_device_ioctl: bNumConfigurations: %u, only 1 supported\n", |
| desc->bNumConfigurations); |
| status = ZX_ERR_INVALID_ARGS; |
| } else { |
| dev->device_desc.bLength = sizeof(usb_device_descriptor_t); |
| dev->device_desc.bDescriptorType = USB_DT_DEVICE; |
| dev->device_desc.bcdUSB = desc->bcdUSB; |
| dev->device_desc.bDeviceClass = desc->bDeviceClass; |
| dev->device_desc.bDeviceSubClass = desc->bDeviceSubClass; |
| dev->device_desc.bDeviceProtocol = desc->bDeviceProtocol; |
| dev->device_desc.bMaxPacketSize0 = desc->bMaxPacketSize0; |
| dev->device_desc.idVendor = desc->idVendor; |
| dev->device_desc.idProduct = desc->idProduct; |
| dev->device_desc.bcdDevice = desc->bcdDevice; |
| dev->device_desc.iManufacturer = desc->iManufacturer; |
| dev->device_desc.iProduct = desc->iProduct; |
| dev->device_desc.iSerialNumber = desc->iSerialNumber; |
| dev->device_desc.bNumConfigurations = desc->bNumConfigurations; |
| status = ZX_OK; |
| } |
| |
| return zircon_usb_peripheral_DeviceSetDeviceDescriptor_reply(txn, status); |
| } |
| |
| static zx_status_t usb_dev_alloc_string_desc(void* ctx, const char* name_data, size_t name_size, |
| fidl_txn_t* txn) { |
| usb_device_t* dev = ctx; |
| |
| uint8_t index = 0; |
| zx_status_t status = usb_device_alloc_string_desc(dev, name_data, &index); |
| return zircon_usb_peripheral_DeviceAllocStringDesc_reply(txn, status, index); |
| } |
| |
| static zx_status_t usb_dev_do_add_function(usb_device_t* dev, |
| const usb_function_descriptor_t* desc) { |
| if (dev->functions_bound) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| usb_function_t* function = calloc(1, sizeof(usb_function_t)); |
| if (!function) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| function->dci_dev = dev->dci_dev; |
| function->dev = dev; |
| memcpy(&function->desc, desc, sizeof(function->desc)); |
| list_add_tail(&dev->functions, &function->node); |
| |
| return ZX_OK; |
| } |
| |
| static zx_status_t usb_dev_add_function(void* ctx, const usb_function_descriptor_t* desc, |
| fidl_txn_t* txn) { |
| usb_device_t* dev = ctx; |
| |
| zx_status_t status = usb_dev_do_add_function(dev, desc); |
| return zircon_usb_peripheral_DeviceAddFunction_reply(txn, status); |
| } |
| |
| static void usb_dev_remove_function_devices_locked(usb_device_t* dev) { |
| zxlogf(TRACE, "usb_dev_remove_function_devices_locked\n"); |
| |
| usb_function_t* function; |
| list_for_every_entry(&dev->functions, function, usb_function_t, node) { |
| if (function->zxdev) { |
| // here we remove the function from the DDK device tree, |
| // but the storage for the function remains on our function list. |
| device_remove(function->zxdev); |
| function->zxdev = NULL; |
| } |
| } |
| |
| free(dev->config_desc); |
| dev->config_desc = NULL; |
| dev->functions_registered = false; |
| dev->function_devs_added = false; |
| } |
| |
| static zx_status_t usb_dev_add_function_devices_locked(usb_device_t* dev) { |
| zxlogf(TRACE, "usb_dev_add_function_devices_locked\n"); |
| if (dev->function_devs_added) { |
| return ZX_OK; |
| } |
| |
| usb_device_descriptor_t* device_desc = &dev->device_desc; |
| int index = 0; |
| usb_function_t* function; |
| list_for_every_entry(&dev->functions, function, usb_function_t, node) { |
| char name[16]; |
| snprintf(name, sizeof(name), "function-%03d", index); |
| |
| usb_function_descriptor_t* desc = &function->desc; |
| |
| zx_device_prop_t props[] = { |
| { BIND_PROTOCOL, 0, ZX_PROTOCOL_USB_FUNCTION }, |
| { BIND_USB_CLASS, 0, desc->interface_class }, |
| { BIND_USB_SUBCLASS, 0, desc->interface_subclass }, |
| { BIND_USB_PROTOCOL, 0, desc->interface_protocol }, |
| { BIND_USB_VID, 0, device_desc->idVendor }, |
| { BIND_USB_PID, 0, device_desc->idProduct }, |
| }; |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = name, |
| .ctx = function, |
| .ops = &function_proto, |
| .proto_id = ZX_PROTOCOL_USB_FUNCTION, |
| .proto_ops = &usb_function_proto, |
| .props = props, |
| .prop_count = countof(props), |
| }; |
| |
| zx_status_t status = device_add(dev->zxdev, &args, &function->zxdev); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "usb_dev_bind_functions add_device failed %d\n", status); |
| return status; |
| } |
| |
| index++; |
| } |
| |
| dev->function_devs_added = true; |
| return ZX_OK; |
| } |
| |
| static zx_status_t usb_dev_state_changed_locked(usb_device_t* dev) { |
| zxlogf(TRACE, "usb_dev_state_changed_locked usb_mode: %d dci_usb_mode: %d\n", dev->usb_mode, |
| dev->dci_usb_mode); |
| |
| usb_mode_t new_dci_usb_mode = dev->dci_usb_mode; |
| bool add_function_devs = (dev->usb_mode == USB_MODE_PERIPHERAL && dev->functions_bound); |
| zx_status_t status = ZX_OK; |
| |
| if (dev->usb_mode == USB_MODE_PERIPHERAL) { |
| if (dev->functions_registered) { |
| // switch DCI to device mode |
| new_dci_usb_mode = USB_MODE_PERIPHERAL; |
| } else { |
| new_dci_usb_mode = USB_MODE_NONE; |
| } |
| } else { |
| new_dci_usb_mode = dev->usb_mode; |
| } |
| |
| if (add_function_devs) { |
| // publish child devices if necessary |
| if (!dev->function_devs_added) { |
| status = usb_dev_add_function_devices_locked(dev); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| } |
| |
| if (dev->dci_usb_mode != new_dci_usb_mode) { |
| zxlogf(TRACE, "usb_dev_state_changed_locked set DCI mode %d\n", new_dci_usb_mode); |
| if (dev->has_usb_mode_switch) { |
| status = usb_mode_switch_set_mode(&dev->usb_mode_switch, new_dci_usb_mode); |
| if (status != ZX_OK) { |
| usb_mode_switch_set_mode(&dev->usb_mode_switch, USB_MODE_NONE); |
| new_dci_usb_mode = USB_MODE_NONE; |
| } |
| } |
| dev->dci_usb_mode = new_dci_usb_mode; |
| } |
| |
| if (!add_function_devs && dev->function_devs_added) { |
| usb_dev_remove_function_devices_locked(dev); |
| } |
| |
| return status; |
| } |
| |
| static zx_status_t usb_dev_do_bind_functions(usb_device_t* dev) { |
| mtx_lock(&dev->lock); |
| |
| if (dev->functions_bound) { |
| zxlogf(ERROR, "usb_dev_bind_functions: already bound!\n"); |
| mtx_unlock(&dev->lock); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| usb_device_descriptor_t* device_desc = &dev->device_desc; |
| if (device_desc->bLength == 0) { |
| zxlogf(ERROR, "usb_dev_bind_functions: device descriptor not set\n"); |
| mtx_unlock(&dev->lock); |
| return ZX_ERR_BAD_STATE; |
| } |
| if (list_is_empty(&dev->functions)) { |
| zxlogf(ERROR, "usb_dev_bind_functions: no functions to bind\n"); |
| mtx_unlock(&dev->lock); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| zxlogf(TRACE, "usb_dev_bind_functions functions_bound = true\n"); |
| dev->functions_bound = true; |
| zx_status_t status = usb_dev_state_changed_locked(dev); |
| mtx_unlock(&dev->lock); |
| |
| return status; |
| } |
| |
| static zx_status_t usb_dev_bind_functions(void* ctx, fidl_txn_t* txn) { |
| usb_device_t* dev = ctx; |
| |
| zx_status_t status = usb_dev_do_bind_functions(dev); |
| return zircon_usb_peripheral_DeviceBindFunctions_reply(txn, status); |
| } |
| |
| |
| static zx_status_t usb_dev_do_clear_functions(usb_device_t* dev) { |
| mtx_lock(&dev->lock); |
| |
| usb_function_t* function; |
| while ((function = list_remove_head_type(&dev->functions, usb_function_t, node)) != NULL) { |
| if (function->zxdev) { |
| device_remove(function->zxdev); |
| // device_remove will not actually free the function, so we free it here |
| free(function->descriptors); |
| free(function); |
| } |
| } |
| free(dev->config_desc); |
| dev->config_desc = NULL; |
| dev->functions_bound = false; |
| dev->functions_registered = false; |
| |
| memset(dev->interface_map, 0, sizeof(dev->interface_map)); |
| memset(dev->endpoint_map, 0, sizeof(dev->endpoint_map)); |
| for (unsigned i = 0; i < countof(dev->strings); i++) { |
| free(dev->strings[i]); |
| dev->strings[i] = NULL; |
| } |
| |
| zx_status_t status = usb_dev_state_changed_locked(dev); |
| mtx_unlock(&dev->lock); |
| |
| return status; |
| } |
| |
| static zx_status_t usb_dev_clear_functions(void* ctx, fidl_txn_t* txn) { |
| usb_device_t* dev = ctx; |
| |
| zxlogf(TRACE, "usb_dev_clear_functions\n"); |
| |
| zx_status_t status = usb_dev_do_clear_functions(dev); |
| return zircon_usb_peripheral_DeviceClearFunctions_reply(txn, status); |
| } |
| |
| static zx_status_t usb_dev_get_mode(void* ctx, fidl_txn_t* txn) { |
| usb_device_t* dev = ctx; |
| |
| mtx_lock(&dev->lock); |
| uint32_t mode = dev->usb_mode; |
| mtx_unlock(&dev->lock); |
| |
| return zircon_usb_peripheral_DeviceGetMode_reply(txn, ZX_OK, mode); |
| } |
| |
| static zx_status_t usb_dev_set_mode(void* ctx, uint32_t mode, fidl_txn_t* txn) { |
| usb_device_t* dev = ctx; |
| |
| mtx_lock(&dev->lock); |
| dev->usb_mode = mode; |
| zx_status_t status = usb_dev_state_changed_locked(dev); |
| mtx_unlock(&dev->lock); |
| |
| return zircon_usb_peripheral_DeviceSetMode_reply(txn, status); |
| } |
| |
| static zircon_usb_peripheral_Device_ops_t fidl_ops = { |
| .SetDeviceDescriptor = usb_dev_set_device_desc, |
| .AllocStringDesc = usb_dev_alloc_string_desc, |
| .AddFunction = usb_dev_add_function, |
| .BindFunctions = usb_dev_bind_functions, |
| .ClearFunctions = usb_dev_clear_functions, |
| .GetMode = usb_dev_get_mode, |
| .SetMode = usb_dev_set_mode, |
| }; |
| |
| zx_status_t usb_dev_message(void* ctx, fidl_msg_t* msg, fidl_txn_t* txn) { |
| return zircon_usb_peripheral_Device_dispatch(ctx, txn, msg, &fidl_ops); |
| } |
| |
| static void usb_dev_unbind(void* ctx) { |
| zxlogf(TRACE, "usb_dev_unbind\n"); |
| usb_device_t* dev = ctx; |
| usb_dev_do_clear_functions(dev); |
| device_remove(dev->zxdev); |
| } |
| |
| static void usb_dev_release(void* ctx) { |
| zxlogf(TRACE, "usb_dev_release\n"); |
| usb_device_t* dev = ctx; |
| free(dev->config_desc); |
| for (unsigned i = 0; i < countof(dev->strings); i++) { |
| free(dev->strings[i]); |
| } |
| free(dev); |
| } |
| |
| static zx_protocol_device_t device_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .message = usb_dev_message, |
| .unbind = usb_dev_unbind, |
| .release = usb_dev_release, |
| }; |
| |
| #if defined(USB_DEVICE_VID) && defined(USB_DEVICE_PID) && defined(USB_DEVICE_FUNCTIONS) |
| static zx_status_t usb_dev_set_default_config(usb_device_t* dev) { |
| usb_device_descriptor_t device_desc = { |
| .bLength = sizeof(usb_device_descriptor_t), |
| .bDescriptorType = USB_DT_DEVICE, |
| .bcdUSB = htole16(0x0200), |
| .bDeviceClass = 0, |
| .bDeviceSubClass = 0, |
| .bDeviceProtocol = 0, |
| .bMaxPacketSize0 = 64, |
| .idVendor = htole16(USB_DEVICE_VID), |
| .idProduct = htole16(USB_DEVICE_PID), |
| .bcdDevice = htole16(0x0100), |
| .bNumConfigurations = 1, |
| }; |
| |
| zx_status_t status = ZX_OK; |
| |
| #ifdef USB_DEVICE_MANUFACTURER |
| status = usb_device_alloc_string_desc(dev, USB_DEVICE_MANUFACTURER, &device_desc.iManufacturer); |
| if (status != ZX_OK) return status; |
| #endif |
| #ifdef USB_DEVICE_PRODUCT |
| usb_device_alloc_string_desc(dev, USB_DEVICE_PRODUCT, &device_desc.iProduct); |
| if (status != ZX_OK) return status; |
| #endif |
| #ifdef USB_DEVICE_SERIAL |
| usb_device_alloc_string_desc(dev, USB_DEVICE_SERIAL, &device_desc.iSerialNumber); |
| if (status != ZX_OK) return status; |
| #endif |
| |
| memcpy(&dev->device_desc, &device_desc, sizeof(dev->device_desc)); |
| |
| usb_function_descriptor_t function_desc; |
| if (strcasecmp(USB_DEVICE_FUNCTIONS, "cdc") == 0) { |
| function_desc.interface_class = USB_CLASS_COMM; |
| function_desc.interface_subclass = USB_CDC_SUBCLASS_ETHERNET; |
| function_desc.interface_protocol = 0; |
| } else if (strcasecmp(USB_DEVICE_FUNCTIONS, "ums") == 0) { |
| function_desc.interface_class = USB_CLASS_MSC; |
| function_desc.interface_subclass = USB_SUBCLASS_MSC_SCSI; |
| function_desc.interface_protocol = USB_PROTOCOL_MSC_BULK_ONLY; |
| } else if (strcasecmp(USB_DEVICE_FUNCTIONS, "test") == 0) { |
| function_desc.interface_class = USB_CLASS_VENDOR; |
| function_desc.interface_subclass = 0; |
| function_desc.interface_protocol = 0; |
| } else { |
| zxlogf(ERROR, "usb_dev_set_default_config: unknown function %s\n", USB_DEVICE_FUNCTIONS); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| status = usb_dev_do_add_function(dev, &function_desc); |
| if (status != ZX_OK) return status; |
| |
| return usb_dev_do_bind_functions(dev); |
| } |
| #endif // defined(USB_DEVICE_VID) && defined(USB_DEVICE_PID) && defined(USB_DEVICE_FUNCTIONS) |
| |
| zx_status_t usb_dev_bind(void* ctx, zx_device_t* parent) { |
| zxlogf(INFO, "usb_dev_bind\n"); |
| |
| usb_device_t* dev = calloc(1, sizeof(usb_device_t)); |
| if (!dev) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| list_initialize(&dev->functions); |
| mtx_init(&dev->lock, mtx_plain); |
| dev->dci_dev = parent; |
| |
| if (device_get_protocol(parent, ZX_PROTOCOL_USB_DCI, &dev->usb_dci)) { |
| free(dev); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| if (device_get_protocol(parent, ZX_PROTOCOL_USB_MODE_SWITCH, &dev->usb_mode_switch) == ZX_OK) { |
| dev->has_usb_mode_switch = true; |
| } |
| |
| // Starting USB mode is determined from device metadata. |
| // We read initial value and store it in dev->usb_mode, but do not actually |
| // enable it until after all of our functions have bound. |
| size_t actual; |
| zx_status_t status = device_get_metadata(parent, DEVICE_METADATA_USB_MODE, |
| &dev->usb_mode, sizeof(dev->usb_mode), &actual); |
| if (status == ZX_ERR_NOT_FOUND) { |
| // Assume peripheral mode by default. |
| dev->usb_mode = USB_MODE_PERIPHERAL; |
| } else if (status != ZX_OK || actual != sizeof(dev->usb_mode)) { |
| zxlogf(ERROR, "usb_dev_bind: DEVICE_METADATA_USB_MODE not found\n"); |
| free(dev); |
| return status; |
| } |
| // Set DCI mode to USB_MODE_NONE until we are ready |
| if (dev->has_usb_mode_switch) { |
| usb_mode_switch_set_mode(&dev->usb_mode_switch, USB_MODE_NONE); |
| } |
| dev->dci_usb_mode = USB_MODE_NONE; |
| dev->parent_request_size = usb_dci_get_request_size(&dev->usb_dci); |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = "usb-peripheral", |
| .ctx = dev, |
| .ops = &device_proto, |
| .proto_id = ZX_PROTOCOL_USB_PERIPHERAL, |
| .flags = DEVICE_ADD_NON_BINDABLE, |
| }; |
| |
| status = device_add(parent, &args, &dev->zxdev); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "usb_device_bind add_device failed %d\n", status); |
| free(dev); |
| return status; |
| } |
| |
| usb_dci_interface_t intf = { |
| .ops = &dci_ops, |
| .ctx = dev, |
| }; |
| usb_dci_set_interface(&dev->usb_dci, &intf); |
| |
| #if defined(USB_DEVICE_VID) && defined(USB_DEVICE_PID) && defined(USB_DEVICE_FUNCTIONS) |
| // set compile time configuration, if we have one |
| usb_dev_set_default_config(dev); |
| #endif |
| |
| return ZX_OK; |
| } |
| |
| static zx_driver_ops_t usb_device_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = usb_dev_bind, |
| }; |
| |
| // clang-format off |
| ZIRCON_DRIVER_BEGIN(usb_device, usb_device_ops, "zircon", "0.1", 1) |
| BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_USB_DCI), |
| ZIRCON_DRIVER_END(usb_device) |