From 06c65e1cf8d3efa2e8737df6facd0076ef21f5df Mon Sep 17 00:00:00 2001
From: Gerd Hoffmann <kraxel@redhat.com>
Date: Thu, 23 Jun 2011 12:40:44 -0300
Subject: [RHEL6 qemu-kvm PATCH 011/115] usb: data structs and helpers for usb descriptors.

RH-Author: Gerd Hoffmann <kraxel@redhat.com>
Message-id: <1308832951-8995-9-git-send-email-kraxel@redhat.com>
Patchwork-id: 27864
O-Subject: [RHEL-6.2 kvm PATCH 008/115] usb: data structs and helpers for usb descriptors.
Bugzilla: 561414 632299 645351 711354
RH-Acked-by: Hans de Goede <hdegoede@redhat.com>
RH-Acked-by: Jes Sorensen <Jes.Sorensen@redhat.com>
RH-Acked-by: Paolo Bonzini <pbonzini@redhat.com>
RH-Acked-by: Kevin Wolf <kwolf@redhat.com>

This patch adds hw/usb-desc.[ch] files.  They carry data structures
for various usb descriptors and helper functions to generate usb
packets from the structures.

The intention is to have a internal representation of the device
desription which is more usable than the current char array blobs,
so we can have common code handle common usb device emulation using
the device description.

The usage of this infrastructure is optional for usb drivers as there
are cases such as pass-through where it probably isn't very useful.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
(cherry picked from commit 37fb59d3032687b6f0d94c307bd0a846e0ca1fe0)

Conflicts:

	Makefile.objs
	trace-events
---
 Makefile      |    2 +-
 hw/usb-desc.c |  238 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/usb-desc.h |   86 +++++++++++++++++++++
 hw/usb.h      |    9 ++
 trace-events  |    5 +
 5 files changed, 339 insertions(+), 1 deletions(-)
 create mode 100644 hw/usb-desc.c
 create mode 100644 hw/usb-desc.h

Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
---
 Makefile      |    2 +-
 hw/usb-desc.c |  238 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/usb-desc.h |   86 +++++++++++++++++++++
 hw/usb.h      |    9 ++
 trace-events  |    5 +
 5 files changed, 339 insertions(+), 1 deletions(-)
 create mode 100644 hw/usb-desc.c
 create mode 100644 hw/usb-desc.h

diff --git a/Makefile b/Makefile
index 70a7c7f..a023624 100644
--- a/Makefile
+++ b/Makefile
@@ -178,7 +178,7 @@ obj-y += i2c.o smbus.o smbus_eeprom.o
 obj-y += eeprom93xx.o
 obj-y += cdrom.o
 obj-y += usb.o usb-hub.o usb-$(HOST_USB).o usb-hid.o usb-wacom.o
-obj-y += usb-serial.o usb-net.o usb-bus.o
+obj-y += usb-serial.o usb-net.o usb-bus.o usb-desc.o
 obj-$(CONFIG_SSI) += ssi.o
 obj-$(CONFIG_SSI_SD) += ssi-sd.o
 obj-$(CONFIG_SD) += sd.o
diff --git a/hw/usb-desc.c b/hw/usb-desc.c
new file mode 100644
index 0000000..559ced7
--- /dev/null
+++ b/hw/usb-desc.c
@@ -0,0 +1,238 @@
+#include "usb.h"
+#include "usb-desc.h"
+#include "trace.h"
+
+/* ------------------------------------------------------------------ */
+
+static uint8_t usb_lo(uint16_t val)
+{
+    return val & 0xff;
+}
+
+static uint8_t usb_hi(uint16_t val)
+{
+    return (val >> 8) & 0xff;
+}
+
+int usb_desc_device(const USBDescID *id, const USBDescDevice *dev,
+                    uint8_t *dest, size_t len)
+{
+    uint8_t bLength = 0x12;
+
+    if (len < bLength) {
+        return -1;
+    }
+
+    dest[0x00] = bLength;
+    dest[0x01] = USB_DT_DEVICE;
+
+    dest[0x02] = usb_lo(dev->bcdUSB);
+    dest[0x03] = usb_hi(dev->bcdUSB);
+    dest[0x04] = dev->bDeviceClass;
+    dest[0x05] = dev->bDeviceSubClass;
+    dest[0x06] = dev->bDeviceProtocol;
+    dest[0x07] = dev->bMaxPacketSize0;
+
+    dest[0x08] = usb_lo(id->idVendor);
+    dest[0x09] = usb_hi(id->idVendor);
+    dest[0x0a] = usb_lo(id->idProduct);
+    dest[0x0b] = usb_hi(id->idProduct);
+    dest[0x0c] = usb_lo(id->bcdDevice);
+    dest[0x0d] = usb_hi(id->bcdDevice);
+    dest[0x0e] = id->iManufacturer;
+    dest[0x0f] = id->iProduct;
+    dest[0x10] = id->iSerialNumber;
+
+    dest[0x11] = dev->bNumConfigurations;
+
+    return bLength;
+}
+
+int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len)
+{
+    uint8_t  bLength = 0x09;
+    uint16_t wTotalLength = 0;
+    int i, rc, count;
+
+    if (len < bLength) {
+        return -1;
+    }
+
+    dest[0x00] = bLength;
+    dest[0x01] = USB_DT_CONFIG;
+    dest[0x04] = conf->bNumInterfaces;
+    dest[0x05] = conf->bConfigurationValue;
+    dest[0x06] = conf->iConfiguration;
+    dest[0x07] = conf->bmAttributes;
+    dest[0x08] = conf->bMaxPower;
+    wTotalLength += bLength;
+
+    count = conf->nif ? conf->nif : conf->bNumInterfaces;
+    for (i = 0; i < count; i++) {
+        rc = usb_desc_iface(conf->ifs + i, dest + wTotalLength, len - wTotalLength);
+        if (rc < 0) {
+            return rc;
+        }
+        wTotalLength += rc;
+    }
+
+    dest[0x02] = usb_lo(wTotalLength);
+    dest[0x03] = usb_hi(wTotalLength);
+    return wTotalLength;
+}
+
+int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len)
+{
+    uint8_t bLength = 0x09;
+    int i, rc, pos = 0;
+
+    if (len < bLength) {
+        return -1;
+    }
+
+    dest[0x00] = bLength;
+    dest[0x01] = USB_DT_INTERFACE;
+    dest[0x02] = iface->bInterfaceNumber;
+    dest[0x03] = iface->bAlternateSetting;
+    dest[0x04] = iface->bNumEndpoints;
+    dest[0x05] = iface->bInterfaceClass;
+    dest[0x06] = iface->bInterfaceSubClass;
+    dest[0x07] = iface->bInterfaceProtocol;
+    dest[0x08] = iface->iInterface;
+    pos += bLength;
+
+    for (i = 0; i < iface->ndesc; i++) {
+        rc = usb_desc_other(iface->descs + i, dest + pos, len - pos);
+        if (rc < 0) {
+            return rc;
+        }
+        pos += rc;
+    }
+
+    for (i = 0; i < iface->bNumEndpoints; i++) {
+        rc = usb_desc_endpoint(iface->eps + i, dest + pos, len - pos);
+        if (rc < 0) {
+            return rc;
+        }
+        pos += rc;
+    }
+
+    return pos;
+}
+
+int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len)
+{
+    uint8_t bLength = 0x07;
+
+    if (len < bLength) {
+        return -1;
+    }
+
+    dest[0x00] = bLength;
+    dest[0x01] = USB_DT_ENDPOINT;
+    dest[0x02] = ep->bEndpointAddress;
+    dest[0x03] = ep->bmAttributes;
+    dest[0x04] = usb_lo(ep->wMaxPacketSize);
+    dest[0x05] = usb_hi(ep->wMaxPacketSize);
+    dest[0x06] = ep->bInterval;
+
+    return bLength;
+}
+
+int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len)
+{
+    int bLength = desc->length ? desc->length : desc->data[0];
+
+    if (len < bLength) {
+        return -1;
+    }
+
+    memcpy(dest, desc->data, bLength);
+    return bLength;
+}
+
+int usb_desc_string(const char* const *str, int index, uint8_t *dest, size_t len)
+{
+    uint8_t bLength, pos, i;
+
+    if (len < 4) {
+        return -1;
+    }
+
+    if (index == 0) {
+        /* language ids */
+        dest[0] = 4;
+        dest[1] = USB_DT_STRING;
+        dest[2] = 0x09;
+        dest[3] = 0x04;
+        return 4;
+    }
+
+    if (str[index] == NULL) {
+        return 0;
+    }
+    bLength = strlen(str[index]) * 2 + 2;
+    dest[0] = bLength;
+    dest[1] = USB_DT_STRING;
+    i = 0; pos = 2;
+    while (pos+1 < bLength && pos+1 < len) {
+        dest[pos++] = str[index][i++];
+        dest[pos++] = 0;
+    }
+    return pos;
+}
+
+/* ------------------------------------------------------------------ */
+
+int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len)
+{
+    const USBDesc *desc = dev->info->usb_desc;
+    uint8_t buf[256];
+    uint8_t type = value >> 8;
+    uint8_t index = value & 0xff;
+    int ret = -1;
+
+    switch(type) {
+    case USB_DT_DEVICE:
+        ret = usb_desc_device(&desc->id, desc->full, buf, sizeof(buf));
+        trace_usb_desc_device(dev->addr, len, ret);
+        break;
+    case USB_DT_CONFIG:
+        if (index < desc->full->bNumConfigurations) {
+            ret = usb_desc_config(desc->full->confs + index, buf, sizeof(buf));
+        }
+        trace_usb_desc_config(dev->addr, index, len, ret);
+        break;
+    case USB_DT_STRING:
+        ret = usb_desc_string(desc->str, index, buf, sizeof(buf));
+        trace_usb_desc_string(dev->addr, index, len, ret);
+        break;
+    default:
+        fprintf(stderr, "%s: %d unknown type %d (len %zd)\n", __FUNCTION__,
+                dev->addr, type, len);
+        break;
+    }
+
+    if (ret > 0) {
+        if (ret > len) {
+            ret = len;
+        }
+        memcpy(dest, buf, ret);
+    }
+    return ret;
+}
+
+int usb_desc_handle_control(USBDevice *dev, int request, int value,
+                            int index, int length, uint8_t *data)
+{
+    const USBDesc *desc = dev->info->usb_desc;
+    int ret = -1;
+
+    assert(desc != NULL);
+    switch(request) {
+    case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
+        ret = usb_desc_get_descriptor(dev, value, data, length);
+        break;
+    }
+    return ret;
+}
diff --git a/hw/usb-desc.h b/hw/usb-desc.h
new file mode 100644
index 0000000..d80efdb
--- /dev/null
+++ b/hw/usb-desc.h
@@ -0,0 +1,86 @@
+#ifndef QEMU_HW_USB_DESC_H
+#define QEMU_HW_USB_DESC_H
+
+#include <inttypes.h>
+
+struct USBDescID {
+    uint16_t                  idVendor;
+    uint16_t                  idProduct;
+    uint16_t                  bcdDevice;
+    uint8_t                   iManufacturer;
+    uint8_t                   iProduct;
+    uint8_t                   iSerialNumber;
+};
+
+struct USBDescDevice {
+    uint16_t                  bcdUSB;
+    uint8_t                   bDeviceClass;
+    uint8_t                   bDeviceSubClass;
+    uint8_t                   bDeviceProtocol;
+    uint8_t                   bMaxPacketSize0;
+    uint8_t                   bNumConfigurations;
+
+    const USBDescConfig       *confs;
+};
+
+struct USBDescConfig {
+    uint8_t                   bNumInterfaces;
+    uint8_t                   bConfigurationValue;
+    uint8_t                   iConfiguration;
+    uint8_t                   bmAttributes;
+    uint8_t                   bMaxPower;
+
+    uint8_t                   nif;
+    const USBDescIface        *ifs;
+};
+
+struct USBDescIface {
+    uint8_t                   bInterfaceNumber;
+    uint8_t                   bAlternateSetting;
+    uint8_t                   bNumEndpoints;
+    uint8_t                   bInterfaceClass;
+    uint8_t                   bInterfaceSubClass;
+    uint8_t                   bInterfaceProtocol;
+    uint8_t                   iInterface;
+
+    uint8_t                   ndesc;
+    USBDescOther              *descs;
+    USBDescEndpoint           *eps;
+};
+
+struct USBDescEndpoint {
+    uint8_t                   bEndpointAddress;
+    uint8_t                   bmAttributes;
+    uint16_t                  wMaxPacketSize;
+    uint8_t                   bInterval;
+};
+
+struct USBDescOther {
+    uint8_t                   length;
+    uint8_t                   *data;
+};
+
+typedef const char *USBDescStrings[256];
+
+struct USBDesc {
+    USBDescID                 id;
+    const USBDescDevice       *full;
+    const USBDescDevice       *high;
+    const char* const         *str;
+};
+
+/* generate usb packages from structs */
+int usb_desc_device(const USBDescID *id, const USBDescDevice *dev,
+                    uint8_t *dest, size_t len);
+int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len);
+int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len);
+int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len);
+int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len);
+int usb_desc_string(const char* const *str, int index, uint8_t *dest, size_t len);
+
+/* control message emulation helpers */
+int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len);
+int usb_desc_handle_control(USBDevice *dev, int request, int value,
+                            int index, int length, uint8_t *data);
+
+#endif /* QEMU_HW_USB_DESC_H */
diff --git a/hw/usb.h b/hw/usb.h
index 5d10877..d6fe079 100644
--- a/hw/usb.h
+++ b/hw/usb.h
@@ -124,6 +124,14 @@ typedef struct USBDevice USBDevice;
 typedef struct USBDeviceInfo USBDeviceInfo;
 typedef struct USBPacket USBPacket;
 
+typedef struct USBDesc USBDesc;
+typedef struct USBDescID USBDescID;
+typedef struct USBDescDevice USBDescDevice;
+typedef struct USBDescConfig USBDescConfig;
+typedef struct USBDescIface USBDescIface;
+typedef struct USBDescEndpoint USBDescEndpoint;
+typedef struct USBDescOther USBDescOther;
+
 /* definition of a USB device */
 struct USBDevice {
     DeviceState qdev;
@@ -186,6 +194,7 @@ struct USBDeviceInfo {
     int (*handle_data)(USBDevice *dev, USBPacket *p);
 
     const char *product_desc;
+    const USBDesc *usb_desc;
 
     /* handle legacy -usbdevice command line options */
     const char *usbdevice_name;
diff --git a/trace-events b/trace-events
index 35b6a3f..abdbd64 100644
--- a/trace-events
+++ b/trace-events
@@ -82,5 +82,10 @@ disable apic_reset_irq_delivered(int apic_irq_delivered) "old coalescing %d"
 disable apic_get_irq_delivered(int apic_irq_delivered) "returning coalescing %d"
 disable apic_set_irq(int apic_irq_delivered) "coalescing %d"
 
+# hw/usb-desc.c
+disable usb_desc_device(int addr, int len, int ret) "dev %d query device, len %d, ret %d"
+disable usb_desc_config(int addr, int index, int len, int ret) "dev %d query config %d, len %d, ret %d"
+disable usb_desc_string(int addr, int index, int len, int ret) "dev %d query string %d, len %d, ret %d"
+
 # vl.c
 disable vm_state_notify(int running, int reason) "running %d reason %d"
-- 
1.7.3.2

