blob: 1b6b750b12bf89b3e749ffac3aee3a77ed6f13ff [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/debug.h>
#include <zircon/hw/usb-audio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <driver/usb.h>
#include "usb-device.h"
#include "usb-interface.h"
#include "util.h"
// 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 },
};
static zx_status_t usb_device_add_interfaces(usb_device_t* parent,
usb_configuration_descriptor_t* config);
zx_status_t usb_device_set_interface(usb_device_t* device, uint8_t interface_id,
uint8_t alt_setting) {
printf("usb_device_set_interface 1\n");
mtx_lock(&device->interface_mutex);
usb_interface_t* intf;
list_for_every_entry(&device->children, intf, usb_interface_t, node) {
if (usb_interface_contains_interface(intf, interface_id)) {
mtx_unlock(&device->interface_mutex);
printf("usb_device_set_interface 4\n");
return usb_interface_set_alt_setting(intf, interface_id, alt_setting);
}
}
mtx_unlock(&device->interface_mutex);
return ZX_ERR_INVALID_ARGS;
}
static usb_configuration_descriptor_t* get_config_desc(usb_device_t* dev, int config) {
int num_configurations = dev->device_desc.bNumConfigurations;
for (int i = 0; i < num_configurations; i++) {
usb_configuration_descriptor_t* desc = dev->config_descs[i];
if (desc->bConfigurationValue == config) {
return desc;
}
}
return NULL;
}
static void usb_device_remove_interfaces(usb_device_t* device) {
mtx_lock(&device->interface_mutex);
usb_interface_t* intf;
while ((intf = list_remove_head_type(&device->children, usb_interface_t, node)) != NULL) {
device_remove(intf->zxdev);
}
mtx_unlock(&device->interface_mutex);
}
zx_status_t usb_device_claim_interface(usb_device_t* device, uint8_t interface_id) {
mtx_lock(&device->interface_mutex);
interface_status_t status = device->interface_statuses[interface_id];
if (status == CLAIMED) {
// The interface has already been claimed by a different interface.
mtx_unlock(&device->interface_mutex);
return ZX_ERR_ALREADY_BOUND;
} else if (status == CHILD_DEVICE) {
bool removed = usb_device_remove_interface_by_id_locked(device, interface_id);
if (!removed) {
mtx_unlock(&device->interface_mutex);
return ZX_ERR_BAD_STATE;
}
}
device->interface_statuses[interface_id] = CLAIMED;
mtx_unlock(&device->interface_mutex);
return ZX_OK;
}
zx_status_t usb_device_set_configuration(usb_device_t* dev, int config) {
int num_configurations = dev->device_desc.bNumConfigurations;
usb_configuration_descriptor_t* config_desc = NULL;
int config_index = -1;
// validate config and get the new current_config_index
for (int i = 0; i < num_configurations; i++) {
usb_configuration_descriptor_t* desc = dev->config_descs[i];
if (desc->bConfigurationValue == config) {
config_desc = desc;
config_index = i;
break;
}
}
if (!config_desc) return ZX_ERR_INVALID_ARGS;
// set configuration
zx_status_t status = usb_device_control(dev,
USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
USB_REQ_SET_CONFIGURATION, config, 0,
NULL, 0);
if (status < 0) {
zxlogf(ERROR, "usb_device_set_configuration: USB_REQ_SET_CONFIGURATION failed\n");
return status;
}
dev->current_config_index = config_index;
// tear down and recreate the subdevices for our interfaces
usb_device_remove_interfaces(dev);
memset(dev->interface_statuses, 0,
config_desc->bNumInterfaces * sizeof(interface_status_t));
return usb_device_add_interfaces(dev, config_desc);
}
static zx_status_t usb_device_ioctl(void* ctx, uint32_t op, const void* in_buf, size_t in_len,
void* out_buf, size_t out_len, size_t* out_actual) {
usb_device_t* dev = ctx;
switch (op) {
case IOCTL_USB_GET_DEVICE_TYPE: {
int* reply = out_buf;
if (out_len < sizeof(*reply)) return ZX_ERR_BUFFER_TOO_SMALL;
*reply = USB_DEVICE_TYPE_DEVICE;
*out_actual = sizeof(*reply);
return ZX_OK;
}
case IOCTL_USB_GET_DEVICE_SPEED: {
int* reply = out_buf;
if (out_len < sizeof(*reply)) return ZX_ERR_BUFFER_TOO_SMALL;
*reply = dev->speed;
*out_actual = sizeof(*reply);
return ZX_OK;
}
case IOCTL_USB_GET_DEVICE_DESC: {
usb_device_descriptor_t* descriptor = &dev->device_desc;
if (out_len < sizeof(*descriptor)) return ZX_ERR_BUFFER_TOO_SMALL;
memcpy(out_buf, descriptor, sizeof(*descriptor));
*out_actual = sizeof(*descriptor);
return ZX_OK;
}
case IOCTL_USB_GET_CONFIG_DESC_SIZE: {
if (in_len != sizeof(int)) return ZX_ERR_INVALID_ARGS;
int config = *((int *)in_buf);
int* reply = out_buf;
usb_configuration_descriptor_t* descriptor = get_config_desc(dev, config);
if (!descriptor) {
return ZX_ERR_INVALID_ARGS;
}
*reply = le16toh(descriptor->wTotalLength);
*out_actual = sizeof(*reply);
return ZX_OK;
}
case IOCTL_USB_GET_DESCRIPTORS_SIZE: {
usb_configuration_descriptor_t* descriptor = dev->config_descs[dev->current_config_index];
int* reply = out_buf;
if (out_len < sizeof(*reply)) return ZX_ERR_BUFFER_TOO_SMALL;
*reply = le16toh(descriptor->wTotalLength);
*out_actual = sizeof(*reply);
return ZX_OK;
}
case IOCTL_USB_GET_CONFIG_DESC: {
if (in_len != sizeof(int)) return ZX_ERR_INVALID_ARGS;
int config = *((int *)in_buf);
usb_configuration_descriptor_t* descriptor = get_config_desc(dev, config);
if (!descriptor) {
return ZX_ERR_INVALID_ARGS;
}
size_t desc_length = le16toh(descriptor->wTotalLength);
if (out_len < desc_length) return ZX_ERR_BUFFER_TOO_SMALL;
memcpy(out_buf, descriptor, desc_length);
return desc_length;
*out_actual = desc_length;
return ZX_OK;
}
case IOCTL_USB_GET_DESCRIPTORS: {
usb_configuration_descriptor_t* descriptor = dev->config_descs[dev->current_config_index];
size_t desc_length = le16toh(descriptor->wTotalLength);
if (out_len < desc_length) return ZX_ERR_BUFFER_TOO_SMALL;
memcpy(out_buf, descriptor, desc_length);
*out_actual = desc_length;
return ZX_OK;
}
case IOCTL_USB_GET_STRING_DESC: {
if (in_len != sizeof(int)) return ZX_ERR_INVALID_ARGS;
if (out_len == 0) return 0;
int id = *((int *)in_buf);
char string[MAX_USB_STRING_LEN];
zx_status_t result = usb_device_get_string_descriptor(dev, id,
string, sizeof(string));
if (result < 0) return result;
size_t length = strlen(string) + 1;
if (length > out_len) {
// truncate the string
memcpy(out_buf, string, out_len - 1);
((char *)out_buf)[out_len - 1] = 0;
length = out_len;
} else {
memcpy(out_buf, string, length);
}
*out_actual = length;
return ZX_OK;
}
case IOCTL_USB_SET_INTERFACE: {
if (in_len != 2 * sizeof(int)) return ZX_ERR_INVALID_ARGS;
int* args = (int *)in_buf;
return usb_device_set_interface(dev, args[0], args[1]);
}
case IOCTL_USB_GET_CURRENT_FRAME: {
uint64_t* reply = out_buf;
if (out_len < sizeof(*reply)) return ZX_ERR_BUFFER_TOO_SMALL;
*reply = usb_hci_get_current_frame(&dev->hci);
*out_actual = sizeof(*reply);
return ZX_OK;
}
case IOCTL_USB_GET_DEVICE_ID: {
uint64_t* reply = out_buf;
if (out_len < sizeof(*reply)) return ZX_ERR_BUFFER_TOO_SMALL;
*reply = dev->device_id;
*out_actual = sizeof(*reply);
return ZX_OK;
}
case IOCTL_USB_GET_DEVICE_HUB_ID: {
uint64_t* reply = out_buf;
if (out_len < sizeof(*reply)) return ZX_ERR_BUFFER_TOO_SMALL;
*reply = dev->hub_id;
*out_actual = sizeof(*reply);
return ZX_OK;
}
case IOCTL_USB_GET_CONFIGURATION: {
int* reply = out_buf;
if (out_len != sizeof(*reply)) return ZX_ERR_INVALID_ARGS;
usb_configuration_descriptor_t* descriptor = dev->config_descs[dev->current_config_index];
*reply = descriptor->bConfigurationValue;
*out_actual = sizeof(*reply);
return ZX_OK;
}
case IOCTL_USB_SET_CONFIGURATION: {
if (in_len != sizeof(int)) return ZX_ERR_INVALID_ARGS;
int config = *((int *)in_buf);
zxlogf(TRACE, "IOCTL_USB_SET_CONFIGURATION %d\n", config);
return usb_device_set_configuration(dev, config);
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
static void usb_device_unbind(void* ctx) {
usb_device_t* dev = ctx;
usb_device_remove_interfaces(dev);
device_remove(dev->zxdev);
}
static void usb_device_release(void* ctx) {
usb_device_t* dev = ctx;
if (dev->config_descs) {
int num_configurations = dev->device_desc.bNumConfigurations;
for (int i = 0; i < num_configurations; i++) {
if (dev->config_descs[i]) free(dev->config_descs[i]);
}
free(dev->config_descs);
}
free(dev->interface_statuses);
free(dev);
}
static zx_protocol_device_t usb_device_proto = {
.version = DEVICE_OPS_VERSION,
.ioctl = usb_device_ioctl,
.release = usb_device_release,
};
#define NEXT_DESCRIPTOR(header) ((usb_descriptor_header_t*)((void*)header + header->bLength))
static zx_status_t usb_device_add_interfaces(usb_device_t* parent,
usb_configuration_descriptor_t* config) {
usb_device_descriptor_t* device_desc = &parent->device_desc;
zx_status_t result = ZX_OK;
// Iterate through interfaces in first configuration and create devices for them
usb_descriptor_header_t* header = NEXT_DESCRIPTOR(config);
usb_descriptor_header_t* end = (usb_descriptor_header_t*)((void*)config + le16toh(config->wTotalLength));
while (header < end) {
if (header->bDescriptorType == USB_DT_INTERFACE_ASSOCIATION) {
usb_interface_assoc_descriptor_t* assoc_desc = (usb_interface_assoc_descriptor_t*)header;
int interface_count = assoc_desc->bInterfaceCount;
// find end of this interface association
usb_descriptor_header_t* next = NEXT_DESCRIPTOR(assoc_desc);
while (next < end) {
if (next->bDescriptorType == USB_DT_INTERFACE_ASSOCIATION) {
break;
} else if (next->bDescriptorType == USB_DT_INTERFACE) {
usb_interface_descriptor_t* test_intf = (usb_interface_descriptor_t*)next;
if (test_intf->bAlternateSetting == 0) {
if (interface_count == 0) {
break;
}
interface_count--;
}
}
next = NEXT_DESCRIPTOR(next);
}
size_t length = (void *)next - (void *)assoc_desc;
usb_interface_assoc_descriptor_t* assoc_copy = malloc(length);
if (!assoc_copy) return ZX_ERR_NO_MEMORY;
memcpy(assoc_copy, assoc_desc, length);
zx_status_t status = usb_device_add_interface_association(parent, device_desc, assoc_copy, length);
if (status != ZX_OK) {
result = status;
}
header = next;
} else if (header->bDescriptorType == USB_DT_INTERFACE) {
usb_interface_descriptor_t* intf_desc = (usb_interface_descriptor_t*)header;
// find end of current interface descriptor
usb_descriptor_header_t* next = NEXT_DESCRIPTOR(intf_desc);
while (next < end) {
if (next->bDescriptorType == USB_DT_INTERFACE) {
usb_interface_descriptor_t* test_intf = (usb_interface_descriptor_t*)next;
// Iterate until we find the next top-level interface
// Include alternate interfaces in the current interface
if (test_intf->bAlternateSetting == 0) {
break;
}
}
next = NEXT_DESCRIPTOR(next);
}
// Only create a child device if no child interface has claimed this interface.
mtx_lock(&parent->interface_mutex);
interface_status_t intf_status = parent->interface_statuses[intf_desc->bInterfaceNumber];
mtx_unlock(&parent->interface_mutex);
size_t length = (void *)next - (void *)intf_desc;
if (intf_status == AVAILABLE) {
usb_interface_descriptor_t* intf_copy = malloc(length);
if (!intf_copy) return ZX_ERR_NO_MEMORY;
memcpy(intf_copy, intf_desc, length);
zx_status_t status = usb_device_add_interface(parent, device_desc,
intf_copy, length);
if (status != ZX_OK) {
result = status;
}
// The interface may have been claimed in the meanwhile, so we need to
// check the interface status again.
mtx_lock(&parent->interface_mutex);
if (parent->interface_statuses[intf_desc->bInterfaceNumber] == CLAIMED) {
bool removed = usb_device_remove_interface_by_id_locked(parent,
intf_desc->bInterfaceNumber);
if (!removed) {
mtx_unlock(&parent->interface_mutex);
return ZX_ERR_BAD_STATE;
}
} else {
parent->interface_statuses[intf_desc->bInterfaceNumber] = CHILD_DEVICE;
}
mtx_unlock(&parent->interface_mutex);
}
header = next;
} else {
header = NEXT_DESCRIPTOR(header);
}
}
return result;
}
zx_status_t usb_device_add(zx_device_t* hci_zxdev, usb_hci_protocol_t* hci_protocol,
zx_device_t* parent, 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_device_control requests.
memcpy(&dev->hci, hci_protocol, sizeof(usb_hci_protocol_t));
dev->device_id = device_id;
usb_request_pool_init(&dev->free_reqs);
// read device descriptor
usb_device_descriptor_t* device_desc = &dev->device_desc;
zx_status_t status = usb_device_get_descriptor(dev, USB_DT_DEVICE, 0, 0,
device_desc, sizeof(*device_desc));
if (status != sizeof(*device_desc)) {
zxlogf(ERROR, "usb_device_add: usb_device_get_descriptor failed\n");
free(dev);
return status;
}
int 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_device_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_device_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_device_get_descriptor(dev, USB_DT_CONFIG, config, 0,
config_desc, config_desc_size);
if (status != config_desc_size) {
zxlogf(ERROR, "usb_device_add: usb_device_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;
// set configuration
status = usb_device_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_add: 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);
list_initialize(&dev->children);
dev->hci_zxdev = hci_zxdev;
dev->hub_id = hub_id;
dev->speed = speed;
dev->config_descs = configs;
usb_configuration_descriptor_t* cur_config = configs[dev->current_config_index];
mtx_init(&dev->interface_mutex, mtx_plain);
dev->interface_statuses = calloc(cur_config->bNumInterfaces,
sizeof(interface_status_t));
if (!dev->interface_statuses) {
status = ZX_ERR_NO_MEMORY;
goto error_exit;
}
char name[16];
snprintf(name, sizeof(name), "%03d", device_id);
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = name,
.ctx = dev,
.ops = &usb_device_proto,
.proto_id = ZX_PROTOCOL_USB,
// Do not allow binding to root of a composite device.
// Clients will bind to the child interfaces instead.
.flags = DEVICE_ADD_NON_BINDABLE,
};
status = device_add(parent, &args, &dev->zxdev);
if (status == ZX_OK) {
*out_device = dev;
} else {
goto error_exit;
}
return usb_device_add_interfaces(dev, cur_config);
error_exit:
if (configs) {
for (int i = 0; i < num_configurations; i++) {
if (configs[i]) free(configs[i]);
}
free(configs);
}
free(dev->interface_statuses);
free(dev);
return status;
}