summaryrefslogtreecommitdiffstats
path: root/hw
diff options
context:
space:
mode:
authorGerd Hoffmann <kraxel@redhat.com>2012-03-29 16:06:28 +0200
committerGerd Hoffmann <kraxel@redhat.com>2012-04-17 10:23:28 +0200
commit96dd9aac37d30f3425088f81523942e67b2d03ac (patch)
treeb418b8fe0461afceb2f9dcc93cc0d047959189ac /hw
parente36a20d329b4cf52ce3b269345527e7ebcc00885 (diff)
downloadhqemu-96dd9aac37d30f3425088f81523942e67b2d03ac.zip
hqemu-96dd9aac37d30f3425088f81523942e67b2d03ac.tar.gz
usb-host: rewrite usb_linux_update_endp_table
This patch carries a complete rewrite of the usb descriptor parser. Changes / improvements: * We are using the USBDescriptor struct instead of hard-coded offsets now to access descriptor data. * (debug) printfs are all gone, tracepoints have been added instead. * We don't try (and fail) to skip over unneeded descriptors. We parse them all one by one. We keep track of which configuration, interface and altsetting we are looking at and use this information to figure which desciptors are in use and which we can ignore. * On parse errors we clear all endpoint information, which will disallow any communication with the device, except control endpoint messages. This makes sure we don't end up with a silly device state where half of the endpoints got enabled and the other half was left disabled. * Some sanity checks have been added. The new parser is more robust and also leaves complete device information in the trace log if you enable the ush_host_parse_* tracepoints. Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Diffstat (limited to 'hw')
-rw-r--r--hw/usb/host-linux.c194
1 files changed, 101 insertions, 93 deletions
diff --git a/hw/usb/host-linux.c b/hw/usb/host-linux.c
index a382f0a..061a1b7 100644
--- a/hw/usb/host-linux.c
+++ b/hw/usb/host-linux.c
@@ -42,6 +42,7 @@
#include <linux/usbdevice_fs.h>
#include <linux/version.h>
#include "hw/usb.h"
+#include "hw/usb/desc.h"
/* We redefine it to avoid version problems */
struct usb_ctrltransfer {
@@ -1108,121 +1109,128 @@ static int usb_host_handle_control(USBDevice *dev, USBPacket *p,
return USB_RET_ASYNC;
}
-static uint8_t usb_linux_get_alt_setting(USBHostDevice *s,
- uint8_t configuration, uint8_t interface)
-{
- char device_name[64], line[1024];
- int alt_setting;
-
- sprintf(device_name, "%d-%s:%d.%d", s->bus_num, s->port,
- (int)configuration, (int)interface);
-
- if (!usb_host_read_file(line, sizeof(line), "bAlternateSetting",
- device_name)) {
- /* Assume alt 0 on error */
- return 0;
- }
- if (sscanf(line, "%d", &alt_setting) != 1) {
- /* Assume alt 0 on error */
- return 0;
- }
- return alt_setting;
-}
-
/* returns 1 on problem encountered or 0 for success */
static int usb_linux_update_endp_table(USBHostDevice *s)
{
- uint8_t *descriptors;
- uint8_t devep, type, alt_interface;
- uint16_t raw;
- int interface, length, i, ep, pid;
+ static const char *tname[] = {
+ [USB_ENDPOINT_XFER_CONTROL] = "control",
+ [USB_ENDPOINT_XFER_ISOC] = "isoc",
+ [USB_ENDPOINT_XFER_BULK] = "bulk",
+ [USB_ENDPOINT_XFER_INT] = "int",
+ };
+ uint8_t devep, type;
+ uint16_t mps, v, p;
+ int ep, pid;
+ unsigned int i, configuration = -1, interface = -1, altsetting = -1;
struct endp_data *epd;
+ USBDescriptor *d;
+ bool active = false;
usb_ep_init(&s->dev);
- if (s->dev.configuration == 0) {
- /* not configured yet -- leave all endpoints disabled */
- return 0;
- }
-
- /* get the desired configuration, interface, and endpoint descriptors
- * from device description */
- descriptors = &s->descr[18];
- length = s->descr_len - 18;
- i = 0;
-
- while (i < length) {
- if (descriptors[i + 1] != USB_DT_CONFIG) {
- fprintf(stderr, "invalid descriptor data\n");
- return 1;
- } else if (descriptors[i + 5] != s->dev.configuration) {
- DPRINTF("not requested configuration %d\n", s->dev.configuration);
- i += (descriptors[i + 3] << 8) + descriptors[i + 2];
- continue;
- }
- i += descriptors[i];
-
- if (descriptors[i + 1] != USB_DT_INTERFACE ||
- (descriptors[i + 1] == USB_DT_INTERFACE &&
- descriptors[i + 4] == 0)) {
- i += descriptors[i];
- continue;
+ for (i = 0;; i += d->bLength) {
+ if (i+2 >= s->descr_len) {
+ break;
}
-
- interface = descriptors[i + 2];
- alt_interface = usb_linux_get_alt_setting(s, s->dev.configuration,
- interface);
-
- /* the current interface descriptor is the active interface
- * and has endpoints */
- if (descriptors[i + 3] != alt_interface) {
- i += descriptors[i];
- continue;
+ d = (void *)(s->descr + i);
+ if (d->bLength < 2) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "descriptor too short");
+ goto error;
}
-
- /* advance to the endpoints */
- while (i < length && descriptors[i +1] != USB_DT_ENDPOINT) {
- i += descriptors[i];
+ if (i + d->bLength > s->descr_len) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "descriptor too long");
+ goto error;
}
-
- if (i >= length)
+ switch (d->bDescriptorType) {
+ case 0:
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "invalid descriptor type");
+ goto error;
+ case USB_DT_DEVICE:
+ if (d->bLength < 0x12) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "device descriptor too short");
+ goto error;
+ }
+ v = (d->u.device.idVendor_hi << 8) | d->u.device.idVendor_lo;
+ p = (d->u.device.idProduct_hi << 8) | d->u.device.idProduct_lo;
+ trace_usb_host_parse_device(s->bus_num, s->addr, v, p);
break;
-
- while (i < length) {
- if (descriptors[i + 1] != USB_DT_ENDPOINT) {
- break;
+ case USB_DT_CONFIG:
+ if (d->bLength < 0x09) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "config descriptor too short");
+ goto error;
}
-
- devep = descriptors[i + 2];
+ configuration = d->u.config.bConfigurationValue;
+ active = (configuration == s->dev.configuration);
+ trace_usb_host_parse_config(s->bus_num, s->addr,
+ configuration, active);
+ break;
+ case USB_DT_INTERFACE:
+ if (d->bLength < 0x09) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "interface descriptor too short");
+ goto error;
+ }
+ interface = d->u.interface.bInterfaceNumber;
+ altsetting = d->u.interface.bAlternateSetting;
+ active = (configuration == s->dev.configuration) &&
+ (altsetting == s->dev.altsetting[interface]);
+ trace_usb_host_parse_interface(s->bus_num, s->addr,
+ interface, altsetting, active);
+ break;
+ case USB_DT_ENDPOINT:
+ if (d->bLength < 0x07) {
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "endpoint descriptor too short");
+ goto error;
+ }
+ devep = d->u.endpoint.bEndpointAddress;
pid = (devep & USB_DIR_IN) ? USB_TOKEN_IN : USB_TOKEN_OUT;
ep = devep & 0xf;
if (ep == 0) {
- fprintf(stderr, "usb-linux: invalid ep descriptor, ep == 0\n");
- return 1;
+ trace_usb_host_parse_error(s->bus_num, s->addr,
+ "invalid endpoint address");
+ goto error;
}
- type = descriptors[i + 3] & 0x3;
- raw = descriptors[i + 4] + (descriptors[i + 5] << 8);
- usb_ep_set_max_packet_size(&s->dev, pid, ep, raw);
- assert(usb_ep_get_type(&s->dev, pid, ep) ==
- USB_ENDPOINT_XFER_INVALID);
- usb_ep_set_type(&s->dev, pid, ep, type);
- usb_ep_set_ifnum(&s->dev, pid, ep, interface);
- if ((s->options & (1 << USB_HOST_OPT_PIPELINE)) &&
- (type == USB_ENDPOINT_XFER_BULK)) {
- usb_ep_set_pipeline(&s->dev, pid, ep, true);
- }
+ type = d->u.endpoint.bmAttributes & 0x3;
+ mps = d->u.endpoint.wMaxPacketSize_lo |
+ (d->u.endpoint.wMaxPacketSize_hi << 8);
+ trace_usb_host_parse_endpoint(s->bus_num, s->addr, ep,
+ (devep & USB_DIR_IN) ? "in" : "out",
+ tname[type], active);
+
+ if (active) {
+ usb_ep_set_max_packet_size(&s->dev, pid, ep, mps);
+ assert(usb_ep_get_type(&s->dev, pid, ep) ==
+ USB_ENDPOINT_XFER_INVALID);
+ usb_ep_set_type(&s->dev, pid, ep, type);
+ usb_ep_set_ifnum(&s->dev, pid, ep, interface);
+ if ((s->options & (1 << USB_HOST_OPT_PIPELINE)) &&
+ (type == USB_ENDPOINT_XFER_BULK)) {
+ usb_ep_set_pipeline(&s->dev, pid, ep, true);
+ }
- epd = get_endp(s, pid, ep);
- epd->halted = 0;
+ epd = get_endp(s, pid, ep);
+ epd->halted = 0;
+ }
- i += descriptors[i];
+ break;
+ default:
+ trace_usb_host_parse_unknown(s->bus_num, s->addr,
+ d->bLength, d->bDescriptorType);
+ break;
}
}
-#ifdef DEBUG
- usb_ep_dump(&s->dev);
-#endif
return 0;
+
+error:
+ usb_ep_init(&s->dev);
+ return 1;
}
/*
OpenPOWER on IntegriCloud