Merge remote-tracking branch 'kraxel/usb.82' into staging

# By Gerd Hoffmann (3) and Hans de Goede (1)
# Via Gerd Hoffmann
* kraxel/usb.82:
  xhci: handle USB_RET_BABBLE
  uhci: Use an intermediate buffer for usb packet data
  usb-host: add usb_host_full_speed_compat
  usb-host: live migration support for the libusb version

Message-id: 1367920207-1404-1-git-send-email-kraxel@redhat.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c
index f8c4286..c85b203 100644
--- a/hw/usb/hcd-uhci.c
+++ b/hw/usb/hcd-uhci.c
@@ -119,7 +119,8 @@
 
 struct UHCIAsync {
     USBPacket packet;
-    QEMUSGList sgl;
+    uint8_t   static_buf[64]; /* 64 bytes is enough, except for isoc packets */
+    uint8_t   *buf;
     UHCIQueue *queue;
     QTAILQ_ENTRY(UHCIAsync) next;
     uint32_t  td_addr;
@@ -264,7 +265,6 @@
     async->queue = queue;
     async->td_addr = td_addr;
     usb_packet_init(&async->packet);
-    pci_dma_sglist_init(&async->sgl, &queue->uhci->dev, 1);
     trace_usb_uhci_packet_add(async->queue->token, async->td_addr);
 
     return async;
@@ -274,7 +274,9 @@
 {
     trace_usb_uhci_packet_del(async->queue->token, async->td_addr);
     usb_packet_cleanup(&async->packet);
-    qemu_sglist_destroy(&async->sgl);
+    if (async->buf != async->static_buf) {
+        g_free(async->buf);
+    }
     g_free(async);
 }
 
@@ -299,7 +301,6 @@
                                  async->done);
     if (!async->done)
         usb_cancel_packet(&async->packet);
-    usb_packet_unmap(&async->packet, &async->sgl);
     uhci_async_free(async);
 }
 
@@ -774,6 +775,7 @@
         *int_mask |= 0x01;
 
     if (pid == USB_TOKEN_IN) {
+        pci_dma_write(&s->dev, td->buffer, async->buf, len);
         if ((td->ctrl & TD_CTRL_SPD) && len < max_len) {
             *int_mask |= 0x02;
             /* short packet: do not update QH */
@@ -881,12 +883,17 @@
     spd = (pid == USB_TOKEN_IN && (td->ctrl & TD_CTRL_SPD) != 0);
     usb_packet_setup(&async->packet, pid, q->ep, 0, td_addr, spd,
                      (td->ctrl & TD_CTRL_IOC) != 0);
-    qemu_sglist_add(&async->sgl, td->buffer, max_len);
-    usb_packet_map(&async->packet, &async->sgl);
+    if (max_len <= sizeof(async->static_buf)) {
+        async->buf = async->static_buf;
+    } else {
+        async->buf = g_malloc(max_len);
+    }
+    usb_packet_addbuf(&async->packet, async->buf, max_len);
 
     switch(pid) {
     case USB_TOKEN_OUT:
     case USB_TOKEN_SETUP:
+        pci_dma_read(&s->dev, td->buffer, async->buf, max_len);
         usb_handle_packet(q->ep->dev, &async->packet);
         if (async->packet.status == USB_RET_SUCCESS) {
             async->packet.actual_length = max_len;
@@ -899,7 +906,6 @@
 
     default:
         /* invalid pid : frame interrupted */
-        usb_packet_unmap(&async->packet, &async->sgl);
         uhci_async_free(async);
         s->status |= UHCI_STS_HCPERR;
         uhci_update_irq(s);
@@ -916,7 +922,6 @@
 
 done:
     ret = uhci_complete_td(s, td, async, int_mask);
-    usb_packet_unmap(&async->packet, &async->sgl);
     uhci_async_free(async);
     return ret;
 }
diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c
index 2c90e56..8813bdf 100644
--- a/hw/usb/hcd-xhci.c
+++ b/hw/usb/hcd-xhci.c
@@ -1716,6 +1716,11 @@
         xhci_xfer_report(xfer);
         xhci_stall_ep(xfer);
         break;
+    case USB_RET_BABBLE:
+        xfer->status = CC_BABBLE_DETECTED;
+        xhci_xfer_report(xfer);
+        xhci_stall_ep(xfer);
+        break;
     default:
         fprintf(stderr, "%s: FIXME: status = %d\n", __func__,
                 xfer->packet.status);
diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c
index d1186b8..f3de459 100644
--- a/hw/usb/host-libusb.c
+++ b/hw/usb/host-libusb.c
@@ -94,7 +94,8 @@
     } ifs[USB_MAX_INTERFACES];
 
     /* callbacks & friends */
-    QEMUBH                           *bh;
+    QEMUBH                           *bh_nodev;
+    QEMUBH                           *bh_postld;
     Notifier                         exit;
 
     /* request queues */
@@ -666,6 +667,42 @@
 
 /* ------------------------------------------------------------------------ */
 
+static bool usb_host_full_speed_compat(USBHostDevice *s)
+{
+    struct libusb_config_descriptor *conf;
+    const struct libusb_interface_descriptor *intf;
+    const struct libusb_endpoint_descriptor *endp;
+    uint8_t type;
+    int rc, c, i, a, e;
+
+    for (c = 0;; c++) {
+        rc = libusb_get_config_descriptor(s->dev, c, &conf);
+        if (rc != 0) {
+            break;
+        }
+        for (i = 0; i < conf->bNumInterfaces; i++) {
+            for (a = 0; a < conf->interface[i].num_altsetting; a++) {
+                intf = &conf->interface[i].altsetting[a];
+                for (e = 0; e < intf->bNumEndpoints; e++) {
+                    endp = &intf->endpoint[e];
+                    type = endp->bmAttributes & 0x3;
+                    switch (type) {
+                    case 0x01: /* ISO */
+                        return false;
+                    case 0x03: /* INTERRUPT */
+                        if (endp->wMaxPacketSize > 64) {
+                            return false;
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+        libusb_free_config_descriptor(conf);
+    }
+    return true;
+}
+
 static void usb_host_ep_update(USBHostDevice *s)
 {
     static const char *tname[] = {
@@ -757,11 +794,9 @@
 
     udev->speed     = speed_map[libusb_get_device_speed(dev)];
     udev->speedmask = (1 << udev->speed);
-#if 0
-    if (udev->speed == USB_SPEED_HIGH && usb_linux_full_speed_compat(dev)) {
+    if (udev->speed == USB_SPEED_HIGH && usb_host_full_speed_compat(s)) {
         udev->speedmask |= USB_SPEED_MASK_FULL;
     }
-#endif
 
     if (s->ddesc.iProduct) {
         libusb_get_string_descriptor_ascii(s->dh, s->ddesc.iProduct,
@@ -835,10 +870,10 @@
 
 static void usb_host_nodev(USBHostDevice *s)
 {
-    if (!s->bh) {
-        s->bh = qemu_bh_new(usb_host_nodev_bh, s);
+    if (!s->bh_nodev) {
+        s->bh_nodev = qemu_bh_new(usb_host_nodev_bh, s);
     }
-    qemu_bh_schedule(s->bh);
+    qemu_bh_schedule(s->bh_nodev);
 }
 
 static void usb_host_exit_notifier(struct Notifier *n, void *data)
@@ -1228,9 +1263,52 @@
     usb_host_ep_update(s);
 }
 
+/*
+ * This is *NOT* about restoring state.  We have absolutely no idea
+ * what state the host device is in at the moment and whenever it is
+ * still present in the first place.  Attemping to contine where we
+ * left off is impossible.
+ *
+ * What we are going to to to here is emulate a surprise removal of
+ * the usb device passed through, then kick host scan so the device
+ * will get re-attached (and re-initialized by the guest) in case it
+ * is still present.
+ *
+ * As the device removal will change the state of other devices (usb
+ * host controller, most likely interrupt controller too) we have to
+ * wait with it until *all* vmstate is loaded.  Thus post_load just
+ * kicks a bottom half which then does the actual work.
+ */
+static void usb_host_post_load_bh(void *opaque)
+{
+    USBHostDevice *dev = opaque;
+    USBDevice *udev = USB_DEVICE(dev);
+
+    if (dev->dh != NULL) {
+        usb_host_close(dev);
+    }
+    if (udev->attached) {
+        usb_device_detach(udev);
+    }
+    usb_host_auto_check(NULL);
+}
+
+static int usb_host_post_load(void *opaque, int version_id)
+{
+    USBHostDevice *dev = opaque;
+
+    if (!dev->bh_postld) {
+        dev->bh_postld = qemu_bh_new(usb_host_post_load_bh, dev);
+    }
+    qemu_bh_schedule(dev->bh_postld);
+    return 0;
+}
+
 static const VMStateDescription vmstate_usb_host = {
     .name = "usb-host",
-    .unmigratable = 1,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = usb_host_post_load,
     .fields = (VMStateField[]) {
         VMSTATE_USB_DEVICE(parent_obj, USBHostDevice),
         VMSTATE_END_OF_LIST()