| // 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/debug.h> |
| #include <ddk/metadata.h> |
| #include <ddk/usb/usb.h> |
| #include <zircon/usb/device/c/fidl.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include "usb-bus.h" |
| #include "usb-device.h" |
| #include "util.h" |
| |
| #define MAX(a, b) (((a) > (b)) ? (a) : (b)) |
| |
| static usb_protocol_ops_t _usb_protocol; |
| |
| static zx_status_t usb_device_set_interface(void* ctx, uint8_t interface_number, |
| uint8_t alt_setting); |
| static zx_status_t usb_device_set_configuration(void* ctx, uint8_t config); |
| |
| // By default we create devices for the interfaces on the first configuration. |
| // This table allows us to specify a different configuration for certain devices |
| // based on their VID and PID. |
| // |
| // TODO(voydanoff) Find a better way of handling this. For example, we could query to see |
| // if any interfaces on the first configuration have drivers that can bind to them. |
| // If not, then we could try the other configurations automatically instead of having |
| // this hard coded list of VID/PID pairs |
| typedef struct { |
| uint16_t vid; |
| uint16_t pid; |
| uint8_t configuration; |
| } usb_config_override_t; |
| |
| static const usb_config_override_t config_overrides[] = { |
| { 0x0bda, 0x8153, 2 }, // Realtek ethernet dongle has CDC interface on configuration 2 |
| { 0, 0, 0 }, |
| }; |
| |
| // This thread is for calling the usb request completion callback for requests received from our |
| // client. We do this on a separate thread because it is unsafe to call out on our own completion |
| // callback, which is called on the main thread of the USB HCI driver. |
| static int callback_thread(void* arg) { |
| usb_device_t* dev = (usb_device_t *)arg; |
| bool done = false; |
| |
| while (!done) { |
| // wait for new usb requests to complete or for signal to exit this thread |
| sync_completion_wait(&dev->callback_thread_completion, ZX_TIME_INFINITE); |
| |
| mtx_lock(&dev->callback_lock); |
| |
| sync_completion_reset(&dev->callback_thread_completion); |
| done = dev->callback_thread_stop; |
| |
| // copy completed requests to a temp list so we can process them outside of our lock |
| list_node_t temp_list = LIST_INITIAL_VALUE(temp_list); |
| list_move(&dev->completed_reqs, &temp_list); |
| |
| mtx_unlock(&dev->callback_lock); |
| |
| // call completion callbacks outside of the lock |
| usb_request_t* req; |
| usb_device_req_internal_t* req_int; |
| while ((req_int = list_remove_head_type(&temp_list, usb_device_req_internal_t, node))) { |
| req = DEV_INTERNAL_TO_USB_REQ(req_int, dev->parent_req_size); |
| usb_request_complete(req, req->response.status, req->response.actual, |
| req_int->complete_cb, req_int->cookie); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static void start_callback_thread(usb_device_t* dev) { |
| // TODO(voydanoff) Once we have a way of knowing when a driver has bound to us, move the thread |
| // start there so we don't have to start a thread unless we know we will need it. |
| thrd_create_with_name(&dev->callback_thread, callback_thread, dev, "usb-device-callback-thread"); |
| } |
| |
| static void stop_callback_thread(usb_device_t* dev) { |
| mtx_lock(&dev->callback_lock); |
| dev->callback_thread_stop = true; |
| mtx_unlock(&dev->callback_lock); |
| |
| sync_completion_signal(&dev->callback_thread_completion); |
| thrd_join(dev->callback_thread, NULL); |
| } |
| |
| // usb request completion for the requests passed down to the HCI driver |
| static void request_complete(void* ctx, usb_request_t* req) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| |
| mtx_lock(&dev->callback_lock); |
| // move original request to completed_reqs list so it can be completed on the callback_thread |
| usb_device_req_internal_t* req_int = USB_REQ_TO_DEV_INTERNAL(req, dev->parent_req_size); |
| list_add_tail(&dev->completed_reqs, &req_int->node); |
| mtx_unlock(&dev->callback_lock); |
| sync_completion_signal(&dev->callback_thread_completion); |
| } |
| |
| void usb_device_set_hub_interface(usb_device_t* device, const usb_hub_interface_t* hub_intf) { |
| if (hub_intf) { |
| memcpy(&device->hub_intf, hub_intf, sizeof(device->hub_intf)); |
| } else { |
| memset(&device->hub_intf, 0, sizeof(device->hub_intf)); |
| } |
| } |
| |
| static usb_configuration_descriptor_t* get_config_desc(usb_device_t* dev, int config) { |
| for (int i = 0; i < dev->num_configurations; i++) { |
| usb_configuration_descriptor_t* desc = dev->config_descs[i]; |
| if (desc->bConfigurationValue == config) { |
| return desc; |
| } |
| } |
| return NULL; |
| } |
| |
| static zx_status_t usb_device_get_protocol(void* ctx, uint32_t proto_id, void* protocol) { |
| if (proto_id == ZX_PROTOCOL_USB_OLD) { |
| usb_protocol_t* usb_proto = protocol; |
| usb_proto->ctx = ctx; |
| usb_proto->ops = &_usb_protocol; |
| return ZX_OK; |
| } else { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| static void usb_device_unbind(void* ctx) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| device_remove(dev->zxdev); |
| } |
| |
| static void usb_device_release(void* ctx) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| |
| stop_callback_thread(dev); |
| |
| if (dev->config_descs) { |
| for (int i = 0; i < dev->num_configurations; i++) { |
| if (dev->config_descs[i]) free(dev->config_descs[i]); |
| } |
| free(dev->config_descs); |
| } |
| free((void*)dev->lang_ids); |
| free(dev); |
| } |
| |
| static void usb_control_complete(void* ctx, usb_request_t* req) { |
| sync_completion_signal((sync_completion_t*)ctx); |
| } |
| |
| static zx_status_t usb_device_control(void* ctx, uint8_t request_type, uint8_t request, |
| uint16_t value, uint16_t index, void* data, size_t length, |
| zx_time_t timeout, size_t* out_length) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| |
| usb_request_t* req = NULL; |
| bool use_free_list = length == 0; |
| if (use_free_list) { |
| req = usb_request_pool_get(&dev->free_reqs, length); |
| } |
| |
| if (req == NULL) { |
| zx_status_t status = usb_request_alloc(&req, length, 0, dev->req_size); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| // fill in protocol data |
| usb_setup_t* setup = &req->setup; |
| setup->bmRequestType = request_type; |
| setup->bRequest = request; |
| setup->wValue = value; |
| setup->wIndex = index; |
| setup->wLength = length; |
| |
| bool out = !!((request_type & USB_DIR_MASK) == USB_DIR_OUT); |
| if (length > 0 && out) { |
| usb_request_copy_to(req, data, length, 0); |
| } |
| |
| sync_completion_t completion = SYNC_COMPLETION_INIT; |
| |
| req->header.device_id = dev->device_id; |
| req->header.length = length; |
| // We call this directly instead of via hci_queue, as it's safe to call our |
| // own completion callback, and prevents clients getting into odd deadlocks. |
| usb_request_complete_t complete = { |
| .callback = usb_control_complete, |
| .ctx = &completion, |
| }; |
| usb_hci_request_queue(&dev->hci, req, &complete); |
| zx_status_t status = sync_completion_wait(&completion, timeout); |
| |
| if (status == ZX_OK) { |
| status = req->response.status; |
| } else if (status == ZX_ERR_TIMED_OUT) { |
| // cancel transactions and wait for request to be completed |
| sync_completion_reset(&completion); |
| status = usb_hci_cancel_all(&dev->hci, dev->device_id, 0); |
| if (status == ZX_OK) { |
| sync_completion_wait(&completion, ZX_TIME_INFINITE); |
| status = ZX_ERR_TIMED_OUT; |
| } |
| } |
| if (status == ZX_OK) { |
| if (out_length != NULL) { |
| *out_length = req->response.actual; |
| } |
| |
| if (length > 0 && !out) { |
| usb_request_copy_from(req, data, req->response.actual, 0); |
| } |
| } |
| |
| if (use_free_list) { |
| if (usb_request_pool_add(&dev->free_reqs, req) != ZX_OK) { |
| zxlogf(TRACE, "Unable to add back request to the free pool\n"); |
| usb_request_release(req); |
| } |
| } else { |
| usb_request_release(req); |
| } |
| return status; |
| } |
| |
| static void usb_device_request_queue(void* ctx, usb_request_t* req, usb_request_complete_cb cb, |
| void* cookie) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| |
| usb_device_req_internal_t* req_int = USB_REQ_TO_DEV_INTERNAL(req, dev->parent_req_size); |
| req_int->complete_cb = cb; |
| req_int->cookie = cookie; |
| |
| req->header.device_id = dev->device_id; |
| // save the existing callback and cookie, so we can replace them |
| // with our own before passing the request to the HCI driver. |
| usb_request_complete_t complete = { |
| .callback = request_complete, |
| .ctx = dev, |
| }; |
| usb_hci_request_queue(&dev->hci, req, &complete); |
| } |
| |
| |
| static zx_status_t usb_device_configure_batch_callback(void* ctx, uint8_t ep_address, |
| usb_batch_complete_cb cb, void* cookie) { |
| // TODO(jocelyndang): implement this. |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| static usb_speed_t usb_device_get_speed(void* ctx) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return dev->speed; |
| } |
| |
| static zx_status_t usb_device_set_interface(void* ctx, uint8_t interface_number, |
| uint8_t alt_setting) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return usb_util_control(dev, USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE, |
| USB_REQ_SET_INTERFACE, alt_setting, interface_number, NULL, 0); |
| } |
| |
| static uint8_t usb_device_get_configuration(void* ctx) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return dev->config_descs[dev->current_config_index]->bConfigurationValue; |
| } |
| |
| static zx_status_t usb_device_set_configuration(void* ctx, uint8_t configuration) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| for (uint8_t i = 0; i < dev->num_configurations; i++) { |
| usb_configuration_descriptor_t* descriptor = dev->config_descs[i]; |
| if (descriptor->bConfigurationValue == configuration) { |
| zx_status_t status; |
| status = usb_util_control(dev, USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, |
| USB_REQ_SET_CONFIGURATION, configuration, 0, NULL, 0); |
| if (status == ZX_OK) { |
| dev->current_config_index = i; |
| } |
| return status; |
| } |
| } |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| static zx_status_t usb_device_enable_endpoint(void* ctx, usb_endpoint_descriptor_t* ep_desc, |
| usb_ss_ep_comp_descriptor_t* ss_comp_desc, |
| bool enable) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return usb_hci_enable_endpoint(&dev->hci, dev->device_id, ep_desc, ss_comp_desc, enable); |
| } |
| |
| static zx_status_t usb_device_reset_endpoint(void* ctx, uint8_t ep_address) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return usb_hci_reset_endpoint(&dev->hci, dev->device_id, ep_address); |
| } |
| |
| static size_t usb_device_get_max_transfer_size(void* ctx, uint8_t ep_address) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return usb_hci_get_max_transfer_size(&dev->hci, dev->device_id, ep_address); |
| } |
| |
| static uint32_t _usb_device_get_device_id(void* ctx) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return dev->device_id; |
| } |
| |
| static void usb_device_get_device_descriptor(void* ctx, usb_device_descriptor_t* out_desc) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| memcpy(out_desc, &dev->device_desc, sizeof(usb_device_descriptor_t)); |
| } |
| |
| static zx_status_t usb_device_get_configuration_descriptor(void* ctx, uint8_t configuration, |
| usb_configuration_descriptor_t** out, |
| size_t* out_length) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| for (int i = 0; i < dev->num_configurations; i++) { |
| usb_configuration_descriptor_t* config_desc = dev->config_descs[i]; |
| if (config_desc->bConfigurationValue == configuration) { |
| size_t length = le16toh(config_desc->wTotalLength); |
| usb_configuration_descriptor_t* descriptor = malloc(length); |
| if (!descriptor) { |
| *out = NULL; |
| *out_length = 0; |
| return ZX_ERR_NO_MEMORY; |
| } |
| memcpy(descriptor, config_desc, length); |
| *out = descriptor; |
| *out_length = length; |
| return ZX_OK; |
| } |
| } |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| static zx_status_t usb_device_get_descriptor_list(void* ctx, void** out_descriptors, |
| size_t* out_length) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| usb_configuration_descriptor_t* config_desc = dev->config_descs[dev->current_config_index]; |
| size_t length = le16toh(config_desc->wTotalLength); |
| |
| void* descriptors = malloc(length); |
| if (!descriptors) { |
| *out_descriptors = NULL; |
| *out_length = 0; |
| return ZX_ERR_NO_MEMORY; |
| } |
| memcpy(descriptors, config_desc, length); |
| *out_descriptors = descriptors; |
| *out_length = length; |
| return ZX_OK; |
| } |
| |
| zx_status_t usb_device_get_string_descriptor(void* ctx, uint8_t desc_id, uint16_t lang_id, |
| uint8_t* buf, size_t buflen, size_t* out_actual, |
| uint16_t* out_actual_lang_id) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return usb_util_get_string_descriptor(dev, desc_id, lang_id, buf, buflen, |
| out_actual, out_actual_lang_id); |
| } |
| |
| static zx_status_t usb_device_cancel_all(void* ctx, uint8_t ep_address) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return usb_hci_cancel_all(&dev->hci, dev->device_id, ep_address); |
| } |
| |
| static uint64_t usb_device_get_current_frame(void* ctx) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return usb_hci_get_current_frame(&dev->hci); |
| } |
| |
| static size_t usb_device_get_request_size(void* ctx) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return dev->req_size; |
| } |
| |
| static usb_protocol_ops_t _usb_protocol = { |
| .control = usb_device_control, |
| .request_queue = usb_device_request_queue, |
| .configure_batch_callback = usb_device_configure_batch_callback, |
| .get_speed = usb_device_get_speed, |
| .set_interface = usb_device_set_interface, |
| .get_configuration = usb_device_get_configuration, |
| .set_configuration = usb_device_set_configuration, |
| .enable_endpoint = usb_device_enable_endpoint, |
| .reset_endpoint = usb_device_reset_endpoint, |
| .get_max_transfer_size = usb_device_get_max_transfer_size, |
| .get_device_id = _usb_device_get_device_id, |
| .get_device_descriptor = usb_device_get_device_descriptor, |
| .get_configuration_descriptor = usb_device_get_configuration_descriptor, |
| .get_descriptor_list = usb_device_get_descriptor_list, |
| .get_string_descriptor = usb_device_get_string_descriptor, |
| .cancel_all = usb_device_cancel_all, |
| .get_current_frame = usb_device_get_current_frame, |
| .get_request_size = usb_device_get_request_size, |
| }; |
| |
| static zx_status_t fidl_GetDeviceSpeed(void* ctx, fidl_txn_t* txn) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return zircon_usb_device_DeviceGetDeviceSpeed_reply(txn, dev->speed); |
| } |
| |
| static zx_status_t fidl_GetDeviceDescriptor(void* ctx, fidl_txn_t* txn) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return zircon_usb_device_DeviceGetDeviceDescriptor_reply(txn, (uint8_t*)&dev->device_desc); |
| } |
| |
| static zx_status_t fidl_GetConfigurationDescriptorSize(void* ctx, uint8_t config, fidl_txn_t* txn) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| usb_configuration_descriptor_t* descriptor = get_config_desc(dev, config); |
| if (!descriptor) { |
| return zircon_usb_device_DeviceGetConfigurationDescriptorSize_reply(txn, |
| ZX_ERR_INVALID_ARGS, 0); |
| } |
| |
| size_t length = le16toh(descriptor->wTotalLength); |
| return zircon_usb_device_DeviceGetConfigurationDescriptorSize_reply(txn, ZX_OK, length); |
| } |
| |
| static zx_status_t fidl_GetConfigurationDescriptor(void* ctx, uint8_t config, fidl_txn_t* txn) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| usb_configuration_descriptor_t* descriptor = get_config_desc(dev, config); |
| if (!descriptor) { |
| return zircon_usb_device_DeviceGetConfigurationDescriptor_reply(txn, ZX_ERR_INVALID_ARGS, |
| NULL, 0); |
| } |
| |
| size_t length = le16toh(descriptor->wTotalLength); |
| return zircon_usb_device_DeviceGetConfigurationDescriptor_reply(txn, ZX_OK, |
| (uint8_t*)descriptor, length); |
| } |
| |
| static zx_status_t fidl_GetStringDescriptor(void* ctx, uint8_t desc_id, uint16_t lang_id, |
| fidl_txn_t* txn) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| uint8_t buffer[zircon_usb_device_MAX_STRING_DESC_SIZE]; |
| size_t actual; |
| zx_status_t status = usb_util_get_string_descriptor(dev, desc_id, lang_id, buffer, |
| sizeof(buffer), &actual, &lang_id); |
| return zircon_usb_device_DeviceGetStringDescriptor_reply(txn, status, buffer, actual, lang_id); |
| } |
| |
| static zx_status_t fidl_SetInterface(void* ctx, uint8_t interface_number, uint8_t alt_setting, |
| fidl_txn_t* txn) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| zx_status_t status = usb_device_set_interface(dev, interface_number, alt_setting); |
| return zircon_usb_device_DeviceSetInterface_reply(txn, status); |
| } |
| |
| static zx_status_t fidl_GetDeviceId(void* ctx, fidl_txn_t* txn) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return zircon_usb_device_DeviceGetDeviceId_reply(txn, dev->device_id); |
| } |
| |
| static zx_status_t fidl_GetHubDeviceId(void* ctx, fidl_txn_t* txn) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| return zircon_usb_device_DeviceGetHubDeviceId_reply(txn, dev->hub_id); |
| } |
| |
| static zx_status_t fidl_GetConfiguration(void* ctx, fidl_txn_t* txn) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| usb_configuration_descriptor_t* descriptor = dev->config_descs[dev->current_config_index]; |
| return zircon_usb_device_DeviceGetConfiguration_reply(txn, descriptor->bConfigurationValue); |
| } |
| |
| static zx_status_t fidl_SetConfiguration(void* ctx, uint8_t configuration, fidl_txn_t* txn) { |
| auto* dev = static_cast<usb_device_t*>(ctx); |
| zx_status_t status = usb_device_set_configuration(dev, configuration); |
| return zircon_usb_device_DeviceSetConfiguration_reply(txn, status); |
| } |
| |
| static zircon_usb_device_Device_ops_t fidl_ops = { |
| .GetDeviceSpeed = fidl_GetDeviceSpeed, |
| .GetDeviceDescriptor = fidl_GetDeviceDescriptor, |
| .GetConfigurationDescriptorSize = fidl_GetConfigurationDescriptorSize, |
| .GetConfigurationDescriptor = fidl_GetConfigurationDescriptor, |
| .GetStringDescriptor = fidl_GetStringDescriptor, |
| .SetInterface = fidl_SetInterface, |
| .GetDeviceId = fidl_GetDeviceId, |
| .GetHubDeviceId = fidl_GetHubDeviceId, |
| .GetConfiguration = fidl_GetConfiguration, |
| .SetConfiguration = fidl_SetConfiguration, |
| }; |
| |
| zx_status_t usb_device_message(void* ctx, fidl_msg_t* msg, fidl_txn_t* txn) { |
| return zircon_usb_device_Device_dispatch(ctx, txn, msg, &fidl_ops); |
| } |
| |
| static zx_protocol_device_t usb_device_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .get_protocol = usb_device_get_protocol, |
| .message = usb_device_message, |
| .release = usb_device_release, |
| }; |
| |
| zx_status_t usb_device_add(usb_bus_t* bus, uint32_t device_id, uint32_t hub_id, |
| usb_speed_t speed, usb_device_t** out_device) { |
| |
| usb_device_t* dev = calloc(1, sizeof(usb_device_t)); |
| if (!dev) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // Needed for usb_util_control requests. |
| memcpy(&dev->hci, &bus->hci, sizeof(usb_hci_protocol_t)); |
| dev->parent_req_size = usb_hci_get_request_size(&dev->hci); |
| dev->req_size = dev->parent_req_size + sizeof(usb_device_req_internal_t); |
| |
| dev->bus = bus; |
| dev->device_id = device_id; |
| mtx_init(&dev->callback_lock, mtx_plain); |
| sync_completion_reset(&dev->callback_thread_completion); |
| list_initialize(&dev->completed_reqs); |
| usb_request_pool_init(&dev->free_reqs, dev->parent_req_size + |
| offsetof(usb_device_req_internal_t, node)); |
| |
| // read device descriptor |
| usb_device_descriptor_t* device_desc = &dev->device_desc; |
| zx_status_t status = usb_util_get_descriptor(dev, USB_DT_DEVICE, 0, 0, device_desc, |
| sizeof(*device_desc)); |
| if (status != sizeof(*device_desc)) { |
| zxlogf(ERROR, "usb_device_add: usb_util_get_descriptor failed\n"); |
| free(dev); |
| return status; |
| } |
| |
| uint8_t num_configurations = device_desc->bNumConfigurations; |
| usb_configuration_descriptor_t** configs = calloc(num_configurations, |
| sizeof(usb_configuration_descriptor_t*)); |
| if (!configs) { |
| status = ZX_ERR_NO_MEMORY; |
| goto error_exit; |
| } |
| |
| for (int config = 0; config < num_configurations; config++) { |
| // read configuration descriptor header to determine size |
| usb_configuration_descriptor_t config_desc_header; |
| status = usb_util_get_descriptor(dev, USB_DT_CONFIG, config, 0, &config_desc_header, |
| sizeof(config_desc_header)); |
| if (status != sizeof(config_desc_header)) { |
| zxlogf(ERROR, "usb_device_add: usb_util_get_descriptor failed\n"); |
| goto error_exit; |
| } |
| uint16_t config_desc_size = letoh16(config_desc_header.wTotalLength); |
| usb_configuration_descriptor_t* config_desc = malloc(config_desc_size); |
| if (!config_desc) { |
| status = ZX_ERR_NO_MEMORY; |
| goto error_exit; |
| } |
| configs[config] = config_desc; |
| |
| // read full configuration descriptor |
| status = usb_util_get_descriptor(dev, USB_DT_CONFIG, config, 0, config_desc, |
| config_desc_size); |
| if (status != config_desc_size) { |
| zxlogf(ERROR, "usb_device_add: usb_util_get_descriptor failed\n"); |
| goto error_exit; |
| } |
| } |
| |
| // we will create devices for interfaces on the first configuration by default |
| uint8_t configuration = 1; |
| const usb_config_override_t* override = config_overrides; |
| while (override->configuration) { |
| if (override->vid == le16toh(device_desc->idVendor) && |
| override->pid == le16toh(device_desc->idProduct)) { |
| configuration = override->configuration; |
| break; |
| } |
| override++; |
| } |
| if (configuration > num_configurations) { |
| zxlogf(ERROR, "usb_device_add: override configuration number out of range\n"); |
| return ZX_ERR_INTERNAL; |
| } |
| dev->current_config_index = configuration - 1; |
| dev->num_configurations = num_configurations; |
| |
| // set configuration |
| status = usb_util_control(dev, USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE, |
| USB_REQ_SET_CONFIGURATION, |
| configs[dev->current_config_index]->bConfigurationValue, 0, NULL, 0); |
| if (status < 0) { |
| zxlogf(ERROR, "usb_device_set_configuration: USB_REQ_SET_CONFIGURATION failed\n"); |
| goto error_exit; |
| } |
| |
| zxlogf(INFO, "* found USB device (0x%04x:0x%04x, USB %x.%x) config %u\n", |
| device_desc->idVendor, device_desc->idProduct, device_desc->bcdUSB >> 8, |
| device_desc->bcdUSB & 0xff, configuration); |
| |
| dev->hci_zxdev = bus->hci_zxdev; |
| dev->hub_id = hub_id; |
| dev->speed = speed; |
| dev->config_descs = configs; |
| |
| // callback thread must be started before device_add() since it will recursively |
| // bind other drivers to us before it returns. |
| start_callback_thread(dev); |
| |
| char name[16]; |
| snprintf(name, sizeof(name), "%03d", device_id); |
| |
| zx_device_prop_t props[] = { |
| { BIND_USB_VID, 0, device_desc->idVendor }, |
| { BIND_USB_PID, 0, device_desc->idProduct }, |
| { BIND_USB_CLASS, 0, device_desc->bDeviceClass }, |
| { BIND_USB_SUBCLASS, 0, device_desc->bDeviceSubClass }, |
| { BIND_USB_PROTOCOL, 0, device_desc->bDeviceProtocol }, |
| }; |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = name, |
| .ctx = dev, |
| .ops = &usb_device_proto, |
| .proto_id = ZX_PROTOCOL_USB_DEVICE_OLD, |
| .proto_ops = &_usb_protocol, |
| .props = props, |
| .prop_count = countof(props), |
| }; |
| |
| status = device_add(bus->zxdev, &args, &dev->zxdev); |
| if (status == ZX_OK) { |
| return ZX_OK; |
| } else { |
| stop_callback_thread(dev); |
| // fall through |
| } |
| |
| error_exit: |
| if (configs) { |
| for (int i = 0; i < num_configurations; i++) { |
| if (configs[i]) free(configs[i]); |
| } |
| free(configs); |
| } |
| free(dev); |
| return status; |
| } |