From 95efb20c714800de7eaf991f19dab6e7a29d5364 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Wed, 24 Apr 2013 14:29:08 +0200 Subject: [PATCH 1/4] usb-host: live migration support for the libusb version Signed-off-by: Gerd Hoffmann --- hw/usb/host-libusb.c | 54 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c index d1186b8a9f..ee67c4c23e 100644 --- a/hw/usb/host-libusb.c +++ b/hw/usb/host-libusb.c @@ -94,7 +94,8 @@ struct USBHostDevice { } ifs[USB_MAX_INTERFACES]; /* callbacks & friends */ - QEMUBH *bh; + QEMUBH *bh_nodev; + QEMUBH *bh_postld; Notifier exit; /* request queues */ @@ -835,10 +836,10 @@ static void usb_host_nodev_bh(void *opaque) 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 +1229,52 @@ static void usb_host_handle_reset(USBDevice *udev) 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() From c3268cc1135f41264cd26bcb269b3414d3c11453 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Mon, 6 May 2013 13:12:16 +0200 Subject: [PATCH 2/4] usb-host: add usb_host_full_speed_compat Alloes to pass through usb2 devices on usb1 host controllers if possible. Brings the libusb implementation to feature-parity with the linux usbfs code, so the usb-host implementation in 1.5 (libusb) doesn't regress compared to 1.4 (usbfs). Signed-off-by: Gerd Hoffmann --- hw/usb/host-libusb.c | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/hw/usb/host-libusb.c b/hw/usb/host-libusb.c index ee67c4c23e..f3de4591fd 100644 --- a/hw/usb/host-libusb.c +++ b/hw/usb/host-libusb.c @@ -667,6 +667,42 @@ static void usb_host_iso_data_out(USBHostDevice *s, USBPacket *p) /* ------------------------------------------------------------------------ */ +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[] = { @@ -758,11 +794,9 @@ static int usb_host_open(USBHostDevice *s, libusb_device *dev) 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, From 9822261ce3bfbc7aec8b2a3e378892dc368910ed Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 6 May 2013 10:48:57 +0200 Subject: [PATCH 3/4] uhci: Use an intermediate buffer for usb packet data Due to various unfortunate reasons we cannot reliable detect a guest cancelling a packet as soon as it happens, instead we detect cancels with some delay. When packets are handled async, and we directly pass the guest memory for the packet to the usb-device as iovec, this means that the usb-device can write to guest-memory which the guest has already re-used for other purposes -> not good! This patch fixes this by adding an intermediate buffer and writing back not only the result, but also the data, of async completed packets when scanning the schedule. Signed-off-by: Hans de Goede Signed-off-by: Gerd Hoffmann --- hw/usb/hcd-uhci.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c index f8c42864d1..c85b2038a2 100644 --- a/hw/usb/hcd-uhci.c +++ b/hw/usb/hcd-uhci.c @@ -119,7 +119,8 @@ struct UHCIPCIDeviceClass { 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 @@ static UHCIAsync *uhci_async_alloc(UHCIQueue *queue, uint32_t td_addr) 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 @@ static void uhci_async_free(UHCIAsync *async) { 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 @@ static void uhci_async_cancel(UHCIAsync *async) 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 @@ static int uhci_complete_td(UHCIState *s, UHCI_TD *td, UHCIAsync *async, uint32_ *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 @@ static int uhci_handle_td(UHCIState *s, UHCIQueue *q, uint32_t qh_addr, 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 @@ static int uhci_handle_td(UHCIState *s, UHCIQueue *q, uint32_t qh_addr, 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 @@ static int uhci_handle_td(UHCIState *s, UHCIQueue *q, uint32_t qh_addr, done: ret = uhci_complete_td(s, td, async, int_mask); - usb_packet_unmap(&async->packet, &async->sgl); uhci_async_free(async); return ret; } From 4e906d567a97ee45b4922001bf521dad0bde5268 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Mon, 6 May 2013 12:33:56 +0200 Subject: [PATCH 4/4] xhci: handle USB_RET_BABBLE Signed-off-by: Gerd Hoffmann --- hw/usb/hcd-xhci.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hw/usb/hcd-xhci.c b/hw/usb/hcd-xhci.c index 2c90e56c99..8813bdf904 100644 --- a/hw/usb/hcd-xhci.c +++ b/hw/usb/hcd-xhci.c @@ -1716,6 +1716,11 @@ static int xhci_complete_packet(XHCITransfer *xfer) 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);