diff options
Diffstat (limited to 'libusb/descriptors.c')
-rw-r--r-- | libusb/descriptors.c | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/libusb/descriptors.c b/libusb/descriptors.c new file mode 100644 index 0000000..0146a82 --- /dev/null +++ b/libusb/descriptors.c @@ -0,0 +1,520 @@ +/* + * Parses descriptors + * + * Copyright (c) 2001 Johannes Erdfelt <johannes@erdfelt.com> + * + * This library is covered by the LGPL, read LICENSE for details. + */ + +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include "usbi.h" + +int usb_get_descriptor_by_endpoint(usb_dev_handle *udev, int ep, + unsigned char type, unsigned char index, void *buf, int size) +{ + memset(buf, 0, size); + + return usb_control_msg(udev, ep | USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, + (type << 8) + index, 0, buf, size, 1000); +} + +int usb_get_descriptor(usb_dev_handle *udev, unsigned char type, + unsigned char index, void *buf, int size) +{ + memset(buf, 0, size); + + return usb_control_msg(udev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, + (type << 8) + index, 0, buf, size, 1000); +} + +int usb_parse_descriptor(unsigned char *source, char *description, void *dest) +{ + unsigned char *sp = source, *dp = dest; + uint16_t w; + uint32_t d; + char *cp; + + for (cp = description; *cp; cp++) { + switch (*cp) { + case 'b': /* 8-bit byte */ + *dp++ = *sp++; + break; + case 'w': /* 16-bit word, convert from little endian to CPU */ + w = (sp[1] << 8) | sp[0]; sp += 2; + dp += ((unsigned long)dp & 1); /* Align to word boundary */ + *((uint16_t *)dp) = w; dp += 2; + break; + case 'd': /* 32-bit dword, convert from little endian to CPU */ + d = (sp[3] << 24) | (sp[2] << 16) | (sp[1] << 8) | sp[0]; sp += 4; + dp += ((unsigned long)dp & 2); /* Align to dword boundary */ + *((uint32_t *)dp) = d; dp += 4; + break; + /* These two characters are undocumented and just a hack for Linux */ + case 'W': /* 16-bit word, keep CPU endianess */ + dp += ((unsigned long)dp & 1); /* Align to word boundary */ + memcpy(dp, sp, 2); sp += 2; dp += 2; + break; + case 'D': /* 32-bit dword, keep CPU endianess */ + dp += ((unsigned long)dp & 2); /* Align to dword boundary */ + memcpy(dp, sp, 4); sp += 4; dp += 4; + break; + } + } + + return sp - source; +} + +/* + * This code looks surprisingly similar to the code I wrote for the Linux + * kernel. It's not a coincidence :) + */ + +static int usb_parse_endpoint(struct usb_endpoint_descriptor *endpoint, unsigned char *buffer, int size) +{ + struct usb_descriptor_header header; + unsigned char *begin; + int parsed = 0, len, numskipped; + + usb_parse_descriptor(buffer, "bb", &header); + + /* Everything should be fine being passed into here, but we sanity */ + /* check JIC */ + if (header.bLength > size) { + if (usb_debug >= 1) + fprintf(stderr, "ran out of descriptors parsing\n"); + return -1; + } + + if (header.bDescriptorType != USB_DT_ENDPOINT) { + if (usb_debug >= 2) + fprintf(stderr, "unexpected descriptor 0x%X, expecting endpoint descriptor, type 0x%X\n", + header.bDescriptorType, USB_DT_ENDPOINT); + return parsed; + } + + if (header.bLength >= ENDPOINT_AUDIO_DESC_LENGTH) + usb_parse_descriptor(buffer, "bbbbwbbb", endpoint); + else if (header.bLength >= ENDPOINT_DESC_LENGTH) + usb_parse_descriptor(buffer, "bbbbwb", endpoint); + + buffer += header.bLength; + size -= header.bLength; + parsed += header.bLength; + + /* Skip over the rest of the Class Specific or Vendor Specific */ + /* descriptors */ + begin = buffer; + numskipped = 0; + while (size >= DESC_HEADER_LENGTH) { + usb_parse_descriptor(buffer, "bb", &header); + + if (header.bLength < 2) { + if (usb_debug >= 1) + fprintf(stderr, "invalid descriptor length of %d\n", header.bLength); + return -1; + } + + /* If we find another "proper" descriptor then we're done */ + if ((header.bDescriptorType == USB_DT_ENDPOINT) || + (header.bDescriptorType == USB_DT_INTERFACE) || + (header.bDescriptorType == USB_DT_CONFIG) || + (header.bDescriptorType == USB_DT_DEVICE)) + break; + + if (usb_debug >= 1) + fprintf(stderr, "skipping descriptor 0x%X\n", header.bDescriptorType); + numskipped++; + + buffer += header.bLength; + size -= header.bLength; + parsed += header.bLength; + } + + if (numskipped && usb_debug >= 2) + fprintf(stderr, "skipped %d class/vendor specific endpoint descriptors\n", numskipped); + + /* Copy any unknown descriptors into a storage area for drivers */ + /* to later parse */ + len = (int)(buffer - begin); + if (!len) { + endpoint->extra = NULL; + endpoint->extralen = 0; + return parsed; + } + + endpoint->extra = malloc(len); + if (!endpoint->extra) { + if (usb_debug >= 1) + fprintf(stderr, "couldn't allocate memory for endpoint extra descriptors\n"); + endpoint->extralen = 0; + return parsed; + } + + memcpy(endpoint->extra, begin, len); + endpoint->extralen = len; + + return parsed; +} + +static int usb_parse_interface(struct usb_interface *interface, + unsigned char *buffer, int size) +{ + int i, len, numskipped, retval, parsed = 0; + struct usb_descriptor_header header; + struct usb_interface_descriptor *ifp; + unsigned char *begin; + + interface->num_altsetting = 0; + + while (size >= INTERFACE_DESC_LENGTH) { + interface->altsetting = realloc(interface->altsetting, sizeof(struct usb_interface_descriptor) * (interface->num_altsetting + 1)); + if (!interface->altsetting) { + if (usb_debug >= 1) + fprintf(stderr, "couldn't malloc interface->altsetting\n"); + return -1; + } + + ifp = interface->altsetting + interface->num_altsetting; + interface->num_altsetting++; + + usb_parse_descriptor(buffer, "bbbbbbbbb", ifp); + + /* Skip over the interface */ + buffer += ifp->bLength; + parsed += ifp->bLength; + size -= ifp->bLength; + + begin = buffer; + numskipped = 0; + + /* Skip over any interface, class or vendor descriptors */ + while (size >= DESC_HEADER_LENGTH) { + usb_parse_descriptor(buffer, "bb", &header); + + if (header.bLength < 2) { + if (usb_debug >= 1) + fprintf(stderr, "invalid descriptor length of %d\n", header.bLength); + return -1; + } + + /* If we find another "proper" descriptor then we're done */ + if ((header.bDescriptorType == USB_DT_INTERFACE) || + (header.bDescriptorType == USB_DT_ENDPOINT) || + (header.bDescriptorType == USB_DT_CONFIG) || + (header.bDescriptorType == USB_DT_DEVICE)) + break; + + numskipped++; + + buffer += header.bLength; + parsed += header.bLength; + size -= header.bLength; + } + + if (numskipped && usb_debug >= 2) + fprintf(stderr, "skipped %d class/vendor specific interface descriptors\n", numskipped); + + /* Copy any unknown descriptors into a storage area for */ + /* drivers to later parse */ + len = (int)(buffer - begin); + if (!len) { + ifp->extra = NULL; + ifp->extralen = 0; + } else { + ifp->extra = malloc(len); + if (!ifp->extra) { + if (usb_debug >= 1) + fprintf(stderr, "couldn't allocate memory for interface extra descriptors\n"); + ifp->extralen = 0; + return -1; + } + memcpy(ifp->extra, begin, len); + ifp->extralen = len; + } + + /* Did we hit an unexpected descriptor? */ + usb_parse_descriptor(buffer, "bb", &header); + if ((size >= DESC_HEADER_LENGTH) && + ((header.bDescriptorType == USB_DT_CONFIG) || + (header.bDescriptorType == USB_DT_DEVICE))) + return parsed; + + if (ifp->bNumEndpoints > USB_MAXENDPOINTS) { + if (usb_debug >= 1) + fprintf(stderr, "too many endpoints\n"); + return -1; + } + + if (ifp->bNumEndpoints > 0) { + ifp->endpoint = (struct usb_endpoint_descriptor *) + malloc(ifp->bNumEndpoints * + sizeof(struct usb_endpoint_descriptor)); + if (!ifp->endpoint) { + if (usb_debug >= 1) + fprintf(stderr, "couldn't allocate memory for ifp->endpoint\n"); + return -1; + } + + memset(ifp->endpoint, 0, ifp->bNumEndpoints * + sizeof(struct usb_endpoint_descriptor)); + + for (i = 0; i < ifp->bNumEndpoints; i++) { + usb_parse_descriptor(buffer, "bb", &header); + + if (header.bLength > size) { + if (usb_debug >= 1) + fprintf(stderr, "ran out of descriptors parsing\n"); + return -1; + } + + retval = usb_parse_endpoint(ifp->endpoint + i, buffer, size); + if (retval < 0) + return retval; + + buffer += retval; + parsed += retval; + size -= retval; + } + } else + ifp->endpoint = NULL; + + /* We check to see if it's an alternate to this one */ + ifp = (struct usb_interface_descriptor *)buffer; + if (size < USB_DT_INTERFACE_SIZE || + ifp->bDescriptorType != USB_DT_INTERFACE || + !ifp->bAlternateSetting) + return parsed; + } + + return parsed; +} + +int usb_parse_configuration(struct usb_config_descriptor *config, + unsigned char *buffer) +{ + int i, retval, size; + struct usb_descriptor_header header; + + usb_parse_descriptor(buffer, "bbwbbbbb", config); + size = config->wTotalLength; + + if (config->bNumInterfaces > USB_MAXINTERFACES) { + if (usb_debug >= 1) + fprintf(stderr, "too many interfaces\n"); + return -1; + } + + config->interface = (struct usb_interface *) + malloc(config->bNumInterfaces * + sizeof(struct usb_interface)); + if (!config->interface) { + if (usb_debug >= 1) + fprintf(stderr, "out of memory\n"); + return -1; + } + + memset(config->interface, 0, config->bNumInterfaces * sizeof(struct usb_interface)); + + buffer += config->bLength; + size -= config->bLength; + + config->extra = NULL; + config->extralen = 0; + + for (i = 0; i < config->bNumInterfaces; i++) { + int numskipped, len; + unsigned char *begin; + + /* Skip over the rest of the Class Specific or Vendor */ + /* Specific descriptors */ + begin = buffer; + numskipped = 0; + while (size >= DESC_HEADER_LENGTH) { + usb_parse_descriptor(buffer, "bb", &header); + + if ((header.bLength > size) || (header.bLength < DESC_HEADER_LENGTH)) { + if (usb_debug >= 1) + fprintf(stderr, "invalid descriptor length of %d\n", header.bLength); + return -1; + } + + /* If we find another "proper" descriptor then we're done */ + if ((header.bDescriptorType == USB_DT_ENDPOINT) || + (header.bDescriptorType == USB_DT_INTERFACE) || + (header.bDescriptorType == USB_DT_CONFIG) || + (header.bDescriptorType == USB_DT_DEVICE)) + break; + + if (usb_debug >= 2) + fprintf(stderr, "skipping descriptor 0x%X\n", header.bDescriptorType); + numskipped++; + + buffer += header.bLength; + size -= header.bLength; + } + + if (numskipped && usb_debug >= 2) + fprintf(stderr, "skipped %d class/vendor specific endpoint descriptors\n", numskipped); + + /* Copy any unknown descriptors into a storage area for */ + /* drivers to later parse */ + len = (int)(buffer - begin); + if (len) { + /* FIXME: We should realloc and append here */ + if (!config->extralen) { + config->extra = malloc(len); + if (!config->extra) { + if (usb_debug >= 1) + fprintf(stderr, "couldn't allocate memory for config extra descriptors\n"); + config->extralen = 0; + return -1; + } + + memcpy(config->extra, begin, len); + config->extralen = len; + } + } + + retval = usb_parse_interface(config->interface + i, buffer, size); + if (retval < 0) + return retval; + + buffer += retval; + size -= retval; + } + + return size; +} + +void usb_destroy_configuration(struct usb_device *dev) +{ + int c, i, j, k; + + if (!dev->config) + return; + + for (c = 0; c < dev->descriptor.bNumConfigurations; c++) { + struct usb_config_descriptor *cf = &dev->config[c]; + + if (!cf->interface) + continue; + + for (i = 0; i < cf->bNumInterfaces; i++) { + struct usb_interface *ifp = &cf->interface[i]; + + if (!ifp->altsetting) + continue; + + for (j = 0; j < ifp->num_altsetting; j++) { + struct usb_interface_descriptor *as = &ifp->altsetting[j]; + + if (as->extra) + free(as->extra); + + if (!as->endpoint) + continue; + + for (k = 0; k < as->bNumEndpoints; k++) { + if (as->endpoint[k].extra) + free(as->endpoint[k].extra); + } + free(as->endpoint); + } + + free(ifp->altsetting); + } + + free(cf->interface); + } + + free(dev->config); +} + +void usb_fetch_and_parse_descriptors(usb_dev_handle *udev) +{ + struct usb_device *dev = udev->device; + int i; + + if (dev->descriptor.bNumConfigurations > USB_MAXCONFIG) { + if (usb_debug >= 1) + fprintf(stderr, "Too many configurations (%d > %d)\n", dev->descriptor.bNumConfigurations, USB_MAXCONFIG); + return; + } + + if (dev->descriptor.bNumConfigurations < 1) { + if (usb_debug >= 1) + fprintf(stderr, "Not enough configurations (%d < %d)\n", dev->descriptor.bNumConfigurations, 1); + return; + } + + dev->config = (struct usb_config_descriptor *)malloc(dev->descriptor.bNumConfigurations * sizeof(struct usb_config_descriptor)); + if (!dev->config) { + if (usb_debug >= 1) + fprintf(stderr, "Unable to allocate memory for config descriptor\n"); + return; + } + + memset(dev->config, 0, dev->descriptor.bNumConfigurations * + sizeof(struct usb_config_descriptor)); + + for (i = 0; i < dev->descriptor.bNumConfigurations; i++) { + unsigned char buffer[8], *bigbuffer; + struct usb_config_descriptor config; + int res; + + /* Get the first 8 bytes so we can figure out what the total length is */ + res = usb_get_descriptor(udev, USB_DT_CONFIG, i, buffer, 8); + if (res < 8) { + if (usb_debug >= 1) { + if (res < 0) + fprintf(stderr, "Unable to get descriptor (%d)\n", res); + else + fprintf(stderr, "Config descriptor too short (expected %d, got %d)\n", 8, res); + } + + goto err; + } + + usb_parse_descriptor(buffer, "bbw", &config); + + bigbuffer = malloc(config.wTotalLength); + if (!bigbuffer) { + if (usb_debug >= 1) + fprintf(stderr, "Unable to allocate memory for descriptors\n"); + goto err; + } + + res = usb_get_descriptor(udev, USB_DT_CONFIG, i, bigbuffer, config.wTotalLength); + if (res < config.wTotalLength) { + if (usb_debug >= 1) { + if (res < 0) + fprintf(stderr, "Unable to get descriptor (%d)\n", res); + else + fprintf(stderr, "Config descriptor too short (expected %d, got %d)\n", config.wTotalLength, res); + } + + free(bigbuffer); + goto err; + } + + res = usb_parse_configuration(&dev->config[i], bigbuffer); + if (usb_debug >= 2) { + if (res > 0) + fprintf(stderr, "Descriptor data still left\n"); + else if (res < 0) + fprintf(stderr, "Unable to parse descriptors\n"); + } + + free(bigbuffer); + } + + return; + +err: + free(dev->config); + + dev->config = NULL; +} + |