blob: 1bce5287484cce88ddaa1274b6eaa1802c838533 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/protocol/usb.h>
#include <ddk/protocol/usb-composite.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "usb-composite.h"
#include "usb-interface.h"
static void usb_interface_unbind(void* ctx) {
usb_interface_t* intf = ctx;
device_remove(intf->zxdev);
}
static void usb_interface_release(void* ctx) {
usb_interface_t* intf = ctx;
free(intf->descriptor);
free(intf);
}
zx_protocol_device_t usb_interface_proto = {
.version = DEVICE_OPS_VERSION,
.unbind = usb_interface_unbind,
.release = usb_interface_release,
};
#define NEXT_DESCRIPTOR(header) ((usb_descriptor_header_t*)((void*)header + header->bLength))
// for determining index into active_endpoints[]
// bEndpointAddress has 4 lower order bits, plus high bit to signify direction
// shift high bit to bit 4 so index is in range 0 - 31.
#define get_usb_endpoint_index(ep) (((ep)->bEndpointAddress & 0x0F) | ((ep)->bEndpointAddress >> 3))
zx_status_t usb_interface_configure_endpoints(usb_interface_t* intf, uint8_t interface_id,
uint8_t alt_setting) {
usb_endpoint_descriptor_t* new_endpoints[USB_MAX_EPS] = {};
bool interface_endpoints[USB_MAX_EPS] = {};
zx_status_t status = ZX_OK;
// iterate through our descriptors to find which endpoints should be active
usb_descriptor_header_t* header = intf->descriptor;
usb_descriptor_header_t* end = (usb_descriptor_header_t*)((void*)header +
intf->descriptor_length);
int cur_interface = -1;
bool enable_endpoints = false;
while (header < end) {
if (header->bDescriptorType == USB_DT_INTERFACE) {
usb_interface_descriptor_t* intf_desc = (usb_interface_descriptor_t*)header;
cur_interface = intf_desc->bInterfaceNumber;
enable_endpoints = (intf_desc->bAlternateSetting == alt_setting);
} else if (header->bDescriptorType == USB_DT_ENDPOINT && cur_interface == interface_id) {
usb_endpoint_descriptor_t* ep = (usb_endpoint_descriptor_t*)header;
int ep_index = get_usb_endpoint_index(ep);
interface_endpoints[ep_index] = true;
if (enable_endpoints) {
new_endpoints[ep_index] = ep;
}
}
header = NEXT_DESCRIPTOR(header);
}
// update to new set of endpoints
// FIXME - how do we recover if we fail half way through processing the endpoints?
for (size_t i = 0; i < countof(new_endpoints); i++) {
if (interface_endpoints[i]) {
usb_endpoint_descriptor_t* old_ep = intf->active_endpoints[i];
usb_endpoint_descriptor_t* new_ep = new_endpoints[i];
if (old_ep != new_ep) {
if (old_ep) {
zx_status_t ret = usb_enable_endpoint(&intf->comp->usb, old_ep, NULL, false);
if (ret != ZX_OK) status = ret;
}
if (new_ep) {
usb_ss_ep_comp_descriptor_t* ss_comp_desc = NULL;
usb_descriptor_header_t* next =
(usb_descriptor_header_t *)((void *)new_ep + new_ep->bLength);
if (next + sizeof(*ss_comp_desc) <= end
&& next->bDescriptorType == USB_DT_SS_EP_COMPANION) {
ss_comp_desc = (usb_ss_ep_comp_descriptor_t *)next;
}
zx_status_t ret = usb_enable_endpoint(&intf->comp->usb, new_ep, ss_comp_desc,
true);
if (ret != ZX_OK) {
status = ret;
}
}
intf->active_endpoints[i] = new_ep;
}
}
}
return status;
}
static zx_status_t usb_interface_req_alloc(void* ctx, usb_request_t** out, uint64_t data_size,
uint8_t ep_address) {
usb_interface_t* intf = ctx;
return usb_req_alloc(&intf->comp->usb, out, data_size, ep_address);
}
static zx_status_t usb_interface_req_alloc_vmo(void* ctx, usb_request_t** out,
zx_handle_t vmo_handle, uint64_t vmo_offset,
uint64_t length, uint8_t ep_address) {
usb_interface_t* intf = ctx;
return usb_req_alloc_vmo(&intf->comp->usb, out, vmo_handle, vmo_offset, length, ep_address);
}
static zx_status_t usb_interface_req_init(void* ctx, usb_request_t* req, zx_handle_t vmo_handle,
uint64_t vmo_offset, uint64_t length,
uint8_t ep_address) {
usb_interface_t* intf = ctx;
return usb_req_init(&intf->comp->usb, req, vmo_handle, vmo_offset, length, ep_address);
}
static ssize_t usb_interface_req_copy_from(void* ctx, usb_request_t* req, void* data,
size_t length, size_t offset) {
usb_interface_t* intf = ctx;
return usb_req_copy_from(&intf->comp->usb, req, data, length, offset);
}
static ssize_t usb_interface_req_copy_to(void* ctx, usb_request_t* req, const void* data,
size_t length, size_t offset) {
usb_interface_t* intf = ctx;
return usb_req_copy_to(&intf->comp->usb, req, data, length, offset);
}
static zx_status_t usb_interface_req_mmap(void* ctx, usb_request_t* req, void** data) {
usb_interface_t* intf = ctx;
return usb_req_mmap(&intf->comp->usb, req, data);
}
static zx_status_t usb_interface_req_cacheop(void* ctx, usb_request_t* req, uint32_t op,
size_t offset, size_t length) {
usb_interface_t* intf = ctx;
return usb_req_cacheop(&intf->comp->usb, req, op, offset, length);
}
static zx_status_t usb_interface_req_cache_flush(void* ctx, usb_request_t* req,
size_t offset, size_t length) {
usb_interface_t* intf = ctx;
return usb_req_cache_flush(&intf->comp->usb, req, offset, length);
}
static zx_status_t usb_interface_req_cache_flush_invalidate(void* ctx, usb_request_t* req,
zx_off_t offset, size_t length) {
usb_interface_t* intf = ctx;
return usb_req_cache_flush_invalidate(&intf->comp->usb, req, offset, length);
}
static zx_status_t usb_interface_req_physmap(void* ctx, usb_request_t* req) {
usb_interface_t* intf = ctx;
return usb_req_physmap(&intf->comp->usb, req);
}
static void usb_interface_req_release(void* ctx, usb_request_t* req) {
usb_interface_t* intf = ctx;
usb_req_release(&intf->comp->usb, req);
}
static void usb_interface_req_complete(void* ctx, usb_request_t* req,
zx_status_t status, zx_off_t actual) {
usb_interface_t* intf = ctx;
usb_req_complete(&intf->comp->usb, req, status, actual);
}
static void usb_interface_req_phys_iter_init(void* ctx, phys_iter_t* iter, usb_request_t* req,
size_t max_length) {
usb_interface_t* intf = ctx;
usb_req_phys_iter_init(&intf->comp->usb, iter, req, max_length);
}
static zx_status_t usb_interface_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) {
usb_interface_t* intf = ctx;
return usb_control(&intf->comp->usb, request_type, request, value, index, data, length,
timeout, out_length);
}
static void usb_interface_request_queue(void* ctx, usb_request_t* usb_request) {
usb_interface_t* intf = ctx;
usb_request_queue(&intf->comp->usb, usb_request);
}
static usb_speed_t usb_interface_get_speed(void* ctx) {
usb_interface_t* intf = ctx;
return usb_get_speed(&intf->comp->usb);
}
static zx_status_t usb_interface_set_interface(void* ctx, uint8_t interface_number,
uint8_t alt_setting) {
usb_interface_t* intf = ctx;
return usb_composite_set_interface(intf->comp, interface_number, alt_setting);
}
static uint8_t usb_interface_get_configuration(void* ctx) {
usb_interface_t* intf = ctx;
return usb_get_configuration(&intf->comp->usb);
}
static zx_status_t usb_interface_set_configuration(void* ctx, uint8_t configuration) {
usb_interface_t* intf = ctx;
return usb_set_configuration(&intf->comp->usb, configuration);
}
static zx_status_t usb_interface_enable_endpoint(void* ctx, usb_endpoint_descriptor_t* ep_desc,
usb_ss_ep_comp_descriptor_t* ss_comp_desc,
bool enable) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t usb_interface_reset_endpoint(void* ctx, uint8_t ep_address) {
usb_interface_t* intf = ctx;
return usb_reset_endpoint(&intf->comp->usb, ep_address);
}
static size_t usb_interface_get_max_transfer_size(void* ctx, uint8_t ep_address) {
usb_interface_t* intf = ctx;
return usb_get_max_transfer_size(&intf->comp->usb, ep_address);
}
static uint32_t usb_interface_get_device_id(void* ctx) {
usb_interface_t* intf = ctx;
return usb_get_device_id(&intf->comp->usb);
}
static void usb_interface_get_device_descriptor(void* ctx, usb_device_descriptor_t* out_desc) {
usb_interface_t* intf = ctx;
return usb_get_device_descriptor(&intf->comp->usb, out_desc);
}
static zx_status_t usb_interface_get_configuration_descriptor(void* ctx, uint8_t configuration,
usb_configuration_descriptor_t** out,
size_t* out_length) {
usb_interface_t* intf = ctx;
return usb_get_configuration_descriptor(&intf->comp->usb, configuration, out, out_length);
}
static zx_status_t usb_interface_get_descriptor_list(void* ctx, void** out_descriptors,
size_t* out_length) {
usb_interface_t* intf = ctx;
void* descriptors = malloc(intf->descriptor_length);
if (!descriptors) {
*out_descriptors = NULL;
*out_length = 0;
return ZX_ERR_NO_MEMORY;
}
memcpy(descriptors, intf->descriptor, intf->descriptor_length);
*out_descriptors = descriptors;
*out_length = intf->descriptor_length;
return ZX_OK;
}
static zx_status_t usb_interface_get_additional_descriptor_list(void* ctx, void** out_descriptors,
size_t* out_length) {
usb_interface_t* intf = ctx;
usb_composite_t* comp = intf->comp;
usb_configuration_descriptor_t* config = comp->config_desc;
usb_descriptor_header_t* header = NEXT_DESCRIPTOR(config);
usb_descriptor_header_t* end = (usb_descriptor_header_t*)((void*)config +
le16toh(config->wTotalLength));
usb_interface_descriptor_t* result = NULL;
while (header < end) {
if (header->bDescriptorType == USB_DT_INTERFACE) {
usb_interface_descriptor_t* test_intf = (usb_interface_descriptor_t*)header;
// We are only interested in descriptors past the last stored descriptor
// for the current interface.
if (test_intf->bAlternateSetting == 0 &&
test_intf->bInterfaceNumber > intf->last_interface_id) {
result = test_intf;
break;
}
}
header = NEXT_DESCRIPTOR(header);
}
if (!result) {
*out_descriptors = NULL;
*out_length = 0;
return ZX_OK;
}
size_t length = (void*)end - (void*)result;
void* descriptors = malloc(length);
if (!descriptors) {
*out_descriptors = NULL;
*out_length = 0;
return ZX_ERR_NO_MEMORY;
}
memcpy(descriptors, result, length);
*out_descriptors = descriptors;
*out_length = length;
return ZX_OK;
}
zx_status_t usb_interface_get_string_descriptor(void* ctx, uint8_t desc_id, uint16_t* inout_lang_id,
uint8_t* buf, size_t* inout_buflen) {
usb_interface_t* intf = ctx;
return usb_get_string_descriptor(&intf->comp->usb, desc_id, inout_lang_id, buf, inout_buflen);
}
static zx_status_t usb_interface_claim_device_interface(void* ctx,
usb_interface_descriptor_t* claim_intf,
size_t claim_length) {
usb_interface_t* intf = ctx;
zx_status_t status = usb_composite_do_claim_interface(intf->comp, claim_intf->bInterfaceNumber);
if (status != ZX_OK) {
return status;
}
// Copy claimed interface descriptors to end of descriptor array.
void* descriptors = realloc(intf->descriptor,
intf->descriptor_length + claim_length);
if (!descriptors) {
return ZX_ERR_NO_MEMORY;
}
memcpy(descriptors + intf->descriptor_length, claim_intf, claim_length);
intf->descriptor = descriptors;
intf->descriptor_length += claim_length;
if (claim_intf->bInterfaceNumber > intf->last_interface_id) {
intf->last_interface_id = claim_intf->bInterfaceNumber;
}
return ZX_OK;
}
static zx_status_t usb_interface_cancel_all(void* ctx, uint8_t ep_address) {
usb_interface_t* intf = ctx;
return usb_cancel_all(&intf->comp->usb, ep_address);
}
static uint64_t usb_interface_get_current_frame(void* ctx) {
usb_interface_t* intf = ctx;
return usb_get_current_frame(&intf->comp->usb);
}
usb_protocol_ops_t usb_device_protocol = {
.req_alloc = usb_interface_req_alloc,
.req_alloc_vmo = usb_interface_req_alloc_vmo,
.req_init = usb_interface_req_init,
.req_copy_from = usb_interface_req_copy_from,
.req_copy_to = usb_interface_req_copy_to,
.req_mmap = usb_interface_req_mmap,
.req_cacheop = usb_interface_req_cacheop,
.req_cache_flush = usb_interface_req_cache_flush,
.req_cache_flush_invalidate = usb_interface_req_cache_flush_invalidate,
.req_physmap = usb_interface_req_physmap,
.req_release = usb_interface_req_release,
.req_complete = usb_interface_req_complete,
.req_phys_iter_init = usb_interface_req_phys_iter_init,
.control = usb_interface_control,
.request_queue = usb_interface_request_queue,
.get_speed = usb_interface_get_speed,
.set_interface = usb_interface_set_interface,
.get_configuration = usb_interface_get_configuration,
.set_configuration = usb_interface_set_configuration,
.enable_endpoint = usb_interface_enable_endpoint,
.reset_endpoint = usb_interface_reset_endpoint,
.get_max_transfer_size = usb_interface_get_max_transfer_size,
.get_device_id = usb_interface_get_device_id,
.get_device_descriptor = usb_interface_get_device_descriptor,
.get_configuration_descriptor = usb_interface_get_configuration_descriptor,
.get_descriptor_list = usb_interface_get_descriptor_list,
.get_string_descriptor = usb_interface_get_string_descriptor,
.cancel_all = usb_interface_cancel_all,
.get_current_frame = usb_interface_get_current_frame,
};
usb_composite_protocol_ops_t composite_device_protocol = {
.get_additional_descriptor_list = usb_interface_get_additional_descriptor_list,
.claim_interface = usb_interface_claim_device_interface,
};
bool usb_interface_contains_interface(usb_interface_t* intf, uint8_t interface_id) {
usb_descriptor_header_t* header = intf->descriptor;
usb_descriptor_header_t* end = (usb_descriptor_header_t*)((void*)header +
intf->descriptor_length);
while (header < end) {
if (header->bDescriptorType == USB_DT_INTERFACE) {
usb_interface_descriptor_t* intf_desc = (usb_interface_descriptor_t*)header;
if (intf_desc->bInterfaceNumber == interface_id) {
return true;
}
}
header = NEXT_DESCRIPTOR(header);
}
return false;
}
zx_status_t usb_interface_set_alt_setting(usb_interface_t* intf, uint8_t interface_id,
uint8_t alt_setting) {
zx_status_t status = usb_interface_configure_endpoints(intf, interface_id, alt_setting);
if (status != ZX_OK) {
return status;
}
return usb_control(&intf->comp->usb, USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE,
USB_REQ_SET_INTERFACE, alt_setting, interface_id, NULL, 0, ZX_TIME_INFINITE,
NULL);
}