blob: b2292ac94b267e480b96ab360a155cff1dfa8d8a [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 <stdlib.h>
#include <string.h>
#include <zircon/compiler.h>
#include <usb/usb.h>
// initializes a usb_desc_iter_t for iterating on descriptors past the
// interface's existing descriptors.
static zx_status_t usb_desc_iter_additional_init(usb_composite_protocol_t* comp,
usb_desc_iter_t* iter) {
memset(iter, 0, sizeof(*iter));
size_t length = usb_composite_get_additional_descriptor_length(comp);
uint8_t* descriptors = malloc(length);
if (!descriptors) {
return ZX_ERR_NO_MEMORY;
}
size_t actual;
zx_status_t status =
usb_composite_get_additional_descriptor_list(comp, descriptors, length, &actual);
if (status != ZX_OK) {
return status;
}
iter->desc = descriptors;
iter->desc_end = descriptors + length;
iter->current = descriptors;
return ZX_OK;
}
// helper function for claiming additional interfaces that satisfy the want_interface predicate,
// want_interface will be passed the supplied arg
__EXPORT zx_status_t usb_claim_additional_interfaces(
usb_composite_protocol_t* comp, bool (*want_interface)(usb_interface_descriptor_t*, void*),
void* arg) {
usb_desc_iter_t iter;
zx_status_t status = usb_desc_iter_additional_init(comp, &iter);
if (status != ZX_OK) {
return status;
}
usb_interface_descriptor_t* intf = usb_desc_iter_next_interface(&iter, true);
while (intf != NULL && want_interface(intf, arg)) {
// We need to find the start of the next interface to calculate the
// total length of the current one.
usb_interface_descriptor_t* next = usb_desc_iter_next_interface(&iter, true);
// If we're currently on the last interface, next will be NULL.
void* intf_end = next ? next : (void*)iter.desc_end;
size_t length = intf_end - (void*)intf;
ZX_ASSERT(length < UINT32_MAX);
status = usb_composite_claim_interface(comp, intf, (uint32_t)length);
if (status != ZX_OK) {
break;
}
intf = next;
}
usb_desc_iter_release(&iter);
return status;
}
// initializes a usb_desc_iter_t
__EXPORT zx_status_t usb_desc_iter_init(usb_protocol_t* usb, usb_desc_iter_t* iter) {
size_t length = usb_get_descriptors_length(usb);
void* descriptors = malloc(length);
if (!descriptors) {
return ZX_ERR_NO_MEMORY;
}
size_t actual;
usb_get_descriptors(usb, descriptors, length, &actual);
return usb_desc_iter_init_unowned(descriptors, length, iter);
}
// initializes a usb_desc_iter_t
__EXPORT zx_status_t usb_desc_iter_init_unowned(void* descriptors, size_t length,
usb_desc_iter_t* iter) {
memset(iter, 0, sizeof(*iter));
iter->desc = descriptors;
iter->desc_end = descriptors + length;
iter->current = descriptors;
return ZX_OK;
}
// clones a usb_desc_iter_t
zx_status_t usb_desc_iter_clone(const usb_desc_iter_t* src, usb_desc_iter_t* dest) {
size_t length = (size_t)(src->desc_end) - (size_t)(src->desc);
size_t offset = (size_t)(src->current) - (size_t)(src->desc);
void* descriptors = malloc(length);
if (!descriptors) {
return ZX_ERR_NO_MEMORY;
}
memcpy(descriptors, src->desc, length);
dest->desc = descriptors;
dest->current = ((unsigned char*)descriptors) + offset;
dest->desc_end = ((unsigned char*)descriptors) + length;
return ZX_OK;
}
bool safe_add(uint8_t* a, size_t b, uint8_t** sum) {
uint8_t* test_sum = a + b;
if (test_sum < a) {
return false;
}
*sum = test_sum;
return true;
}
// releases resources in a usb_desc_iter_t
__EXPORT void usb_desc_iter_release(usb_desc_iter_t* iter) {
free(iter->desc);
iter->desc = NULL;
}
// resets iterator to the beginning
__EXPORT void usb_desc_iter_reset(usb_desc_iter_t* iter) { iter->current = iter->desc; }
// increase the iterator to the next descriptor. If the current descriptor is not a valid descriptor
// header structure, returns false, otherwise, returns true. The iterator would not be increased
// if false is returned and user is expected to handle the error case and end the descriptor
// parsing.
__EXPORT bool usb_desc_iter_advance(usb_desc_iter_t* iter) {
usb_descriptor_header_t* header = usb_desc_iter_peek(iter);
if (!header)
return false;
iter->current += header->b_length;
return true;
}
// returns the descriptor header structure currently pointed by the iterator. If the current
// iterator does not point to a valid descriptor header structure, NULL would be returned and user
// is expected to handle the error case and end the descriptor parsing.
__EXPORT usb_descriptor_header_t* usb_desc_iter_peek(usb_desc_iter_t* iter) {
uint8_t* end;
if (!safe_add(iter->current, sizeof(usb_descriptor_header_t), &end)) {
return NULL;
}
if (end > iter->desc_end) {
return NULL;
}
usb_descriptor_header_t* header = (usb_descriptor_header_t*)iter->current;
if (!safe_add(iter->current, header->b_length, &end)) {
return NULL;
}
if (end > iter->desc_end) {
return NULL;
}
if (header->b_length == 0) {
// An descriptor must not have 0 length, otherwise, it might cause infinite loop.
return NULL;
}
return header;
}
// returns the expected structure with structure size currently pointed by the iterator. If the
// length of descriptor buffer current pointed by the iterator is not enough to hold the structure,
// NULL would be returned, user is expected to handle the error case.
__EXPORT void* usb_desc_iter_get_structure(usb_desc_iter_t* iter, size_t structure_size) {
uint8_t* start = (uint8_t*)iter->current;
uint8_t* end = 0;
if (!safe_add(start, structure_size, &end)) {
return NULL;
}
if (end > iter->desc_end) {
return NULL;
}
return (void*)start;
}
// returns the next interface descriptor, optionally skipping alternate interfaces. The last
// association descriptor pointer is filled in at assoc. If none are seen, assoc does not change.
__EXPORT usb_interface_descriptor_t* usb_desc_iter_next_interface_with_assoc(
usb_desc_iter_t* iter, bool skip_alt, usb_interface_assoc_descriptor_t** assoc) {
usb_descriptor_header_t* header;
while ((header = usb_desc_iter_peek(iter)) != NULL) {
if (assoc && header->b_descriptor_type == USB_DT_INTERFACE_ASSOCIATION) {
usb_interface_assoc_descriptor_t* desc =
usb_desc_iter_get_structure(iter, sizeof(usb_interface_assoc_descriptor_t));
if (!desc) {
return NULL;
}
*assoc = desc;
}
if (header->b_descriptor_type == USB_DT_INTERFACE) {
usb_interface_descriptor_t* desc =
usb_desc_iter_get_structure(iter, sizeof(usb_interface_descriptor_t));
if (desc == NULL) {
return NULL;
}
if (!skip_alt || desc->b_alternate_setting == 0) {
usb_desc_iter_advance(iter);
return desc;
}
}
usb_desc_iter_advance(iter);
}
// not found
return NULL;
}
__EXPORT usb_interface_descriptor_t* usb_desc_iter_next_interface(usb_desc_iter_t* iter,
bool skip_alt) {
return usb_desc_iter_next_interface_with_assoc(iter, skip_alt, NULL);
}
// returns the next endpoint descriptor within the current interface
__EXPORT usb_endpoint_descriptor_t* usb_desc_iter_next_endpoint(usb_desc_iter_t* iter) {
usb_descriptor_header_t* header;
while ((header = usb_desc_iter_peek(iter)) != NULL) {
if (header->b_descriptor_type == USB_DT_INTERFACE) {
// we are at end of previous interface
return NULL;
}
if (header->b_descriptor_type == USB_DT_ENDPOINT) {
usb_endpoint_descriptor_t* desc =
usb_desc_iter_get_structure(iter, sizeof(usb_endpoint_descriptor_t));
if (desc == NULL) {
return NULL;
}
usb_desc_iter_advance(iter);
return desc;
}
usb_desc_iter_advance(iter);
}
// not found
return NULL;
}
// returns the next ss-companion descriptor within the current interface.
// drivers may use usb_desc_iter_peek() to determine if an endpoint or ss_companion descriptor is
// expected.
__EXPORT usb_ss_ep_comp_descriptor_t* usb_desc_iter_next_ss_ep_comp(usb_desc_iter_t* iter) {
usb_descriptor_header_t* header;
while ((header = usb_desc_iter_peek(iter)) != NULL) {
uint8_t desc_type = header->b_descriptor_type;
if (desc_type == USB_DT_ENDPOINT || desc_type == USB_DT_INTERFACE) {
// we are either at next endpoint or end of previous interface
return NULL;
}
if (header->b_descriptor_type == USB_DT_SS_EP_COMPANION) {
usb_ss_ep_comp_descriptor_t* desc =
usb_desc_iter_get_structure(iter, sizeof(usb_ss_ep_comp_descriptor_t));
if (desc == NULL) {
return NULL;
}
usb_desc_iter_advance(iter);
return desc;
}
usb_desc_iter_advance(iter);
}
// not found
return NULL;
}