/* * Parses descriptors * * Copyright (c) 2001 Johannes Erdfelt * * This library is covered by the LGPL, read LICENSE for details. */ #include #include #include #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; }