Hi.  The attached patch allows qemu to dispatch usb data packets
asynchronously.

  The uhci controller gives poor performance with this patch.  It was
said on irc that allowing the uhci controller several pending requests
would improve this.  However the ohci controller performs very well
as-is.  The tested guests included windows xp and linux 2.6.  Both were
noticeably more responsive - cases included transfering files from a
flash drive attached to the host, using an interrupt endpoint based gps
device, and using a usb scanner to scan a few images.  There might be a
race condition for scheduling bh's from the signal handler.
--- qemu/usb-linux.c	2006-08-11 20:04:27.000000000 -0500
+++ qemu/usb-linux.c	2006-11-18 20:40:52.000000000 -0600
@@ -29,6 +29,8 @@
 #include <linux/compiler.h>
 #include <linux/usbdevice_fs.h>
 #include <linux/version.h>
+#include <signal.h>
+#include <fcntl.h>
 
 /* We redefine it to avoid version problems */
 struct usb_ctrltransfer {
@@ -53,11 +55,225 @@
 #define USBDEVFS_PATH "/proc/bus/usb"
 #define PRODUCT_NAME_SZ 32
 
+// endpoint association data
+struct endp_data {
+    uint8_t type;
+};
+
 typedef struct USBHostDevice {
     USBDevice dev;
     int fd;
+
+    /* for async completion - the 
+     * current controller model
+     * issues one packet per 
+     * controller.
+     */
+    struct usbdevfs_urb urb;
+    USBPacket *packet;
+    QEMUBH *bh;
+    int status;
+
+    struct endp_data endp_table[16];
 } USBHostDevice;
 
+// returns 1 on problem encountered or 0 for success
+static int usb_linux_update_endp_table(USBHostDevice *s)
+{
+    uint8_t descriptors[1024];
+    uint8_t buf[3];
+    uint8_t devep, type;
+    struct usb_ctrltransfer ct;
+    int configuration, interface, alt_interface;
+    int length, i, ret;
+
+    ct.bRequestType = USB_DIR_IN;
+    ct.bRequest = USB_REQ_GET_CONFIGURATION;
+    ct.wValue = 0;
+    ct.wIndex = 0;
+    ct.wLength = 1;
+    ct.data = buf;
+    ct.timeout = 50;
+
+    ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
+    if (ret < 0) {
+        perror("usb_linux_update_endp_table");
+        return 1;
+    }
+    configuration = buf[0];
+
+    // in address state
+    if (configuration == 0)
+        return 1;
+
+    /* get the desired configuration, interface, and endpoint
+     * descriptors in one shot - could also re-read all data from
+     * open file descriptor, go through sysfs entries, etc.
+     */
+    ct.bRequestType = USB_DIR_IN;
+    ct.bRequest = USB_REQ_GET_DESCRIPTOR;
+    ct.wValue = (USB_DT_CONFIG << 8) | (configuration - 1);
+    ct.wIndex = 0;
+    ct.wLength = 1024;
+    ct.data = descriptors;
+    ct.timeout = 50;
+
+    ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
+    if (ret < 0) {
+        perror("usb_linux_update_endp_table");
+        return 1;
+    }
+
+    length = ret;
+    i = 0;
+
+    if (descriptors[i + 1] != USB_DT_CONFIG ||
+        descriptors[i + 5] != configuration) {
+        printf("invalid descriptor data - configuration\n");
+        return 1;
+    }
+    i += descriptors[i];
+
+    while (i < length) {
+        if (descriptors[i + 1] != USB_DT_INTERFACE ||
+            (descriptors[i + 1] == USB_DT_INTERFACE &&
+             descriptors[i + 4] == 0)) {
+            i += descriptors[i];
+            continue;
+        }
+
+        interface = descriptors[i + 2];
+        ct.bRequestType = USB_DIR_IN | USB_RECIP_INTERFACE;
+        ct.bRequest = USB_REQ_GET_INTERFACE;
+        ct.wValue = 0;
+        ct.wIndex = interface;
+        ct.wLength = 1;
+        ct.data = buf;
+        ct.timeout = 50;
+
+        ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
+        if (ret < 0) {
+            perror("usb_linux_update_endp_table");
+            return 1;
+        }
+        alt_interface = buf[0];
+
+        // the current interface descriptor is the active interface
+        // and has endpoints
+        if (descriptors[i + 3] != alt_interface)
+            continue;
+
+        // advance to the endpoints
+        while (i < length && descriptors[i +1] != USB_DT_ENDPOINT)
+            i += descriptors[i];
+
+        if (i >= length)
+            break;
+
+        while (i < length) {
+            if (descriptors[i + 1] != USB_DT_ENDPOINT)
+                break;
+
+            devep = descriptors[i + 2];
+            switch (descriptors[i + 3] & 0x3) {
+            case 0x00:
+                type = USBDEVFS_URB_TYPE_CONTROL;
+                break;
+            case 0x01:
+                type = USBDEVFS_URB_TYPE_ISO;
+                break;
+            case 0x02:
+                type = USBDEVFS_URB_TYPE_BULK;
+                break;
+            case 0x03:
+                type = USBDEVFS_URB_TYPE_INTERRUPT;
+                break;
+            default:
+                printf("usb_host: malformed endpoint type\n");
+                type = USBDEVFS_URB_TYPE_BULK;
+            }
+            s->endp_table[(devep & 0xf) - 1].type = type;
+
+            i += descriptors[i];
+        }
+
+        i += descriptors[i];
+    }
+
+    return 0;
+}
+
+static void usb_linux_bh_cb(void *opaque)
+{
+    USBHostDevice *s = (USBHostDevice *)opaque;
+    struct usbdevfs_urb *context = NULL;
+    struct usbdevfs_urb *urb = &s->urb;
+    USBPacket *p = s->packet;
+    int ret;
+
+    if (!s || !p)
+        return;
+
+#ifdef DEBUG
+    printf("completion: devaddr %d - devep 0x%02x\n", p->devaddr, p->devep);
+#endif
+
+    switch (s->status) {
+    case 0:
+        ret = ioctl(s->fd, USBDEVFS_REAPURB, &context);
+        if (ret < 0) {
+            perror("USBDEVFS_REAPURB");
+            return;
+        }
+
+        if (context != urb) {
+            printf("this is not our urb\n");
+            return;
+        }
+
+        p->len = urb->actual_length;
+        break;
+    // case : // timeout uhci ?
+    case -ETIMEDOUT: // timeout ohci
+        printf("usb_host: timeout status detected\n");
+        p->len = USB_RET_NAK;
+        break;
+    case -EPIPE:
+        printf("usb_host: stall status detected\n");
+        p->len = USB_RET_STALL;
+        break;
+    case -EOVERFLOW:
+        printf("usb_host: babble status detected\n");
+        p->len = USB_RET_BABBLE;
+        break;
+    default:
+        printf("unhandled status code - urb status %d\n",
+                 s->status);
+        p->len = urb->actual_length;
+    }
+
+    s->packet = NULL;
+    usb_packet_complete(p);
+}
+
+/* SI_ASYNCIO handler routine */
+void usb_linux_sig_handler(int signum, siginfo_t *info, void *context)
+{
+    struct usbdevfs_urb *urb = (struct usbdevfs_urb *)info->si_addr;
+    USBHostDevice *s = (USBHostDevice *)urb->usercontext;
+
+    if (info->si_code != SI_ASYNCIO ||
+        info->si_signo != USB_ASYNC_COMPLETION_SIGNAL) {
+        return;
+    }
+
+    // si_errno is urb status upon completion (drivers/usb/core/devio.c)
+    // can find error codes for ohci host in usb/host/ohci.h (cc_to_error)
+    // can find error codes for uhci host in usb/host/uhci-q.c
+    s->status = info->si_errno;
+    qemu_bh_schedule(s->bh);
+}
+
 static void usb_host_handle_reset(USBDevice *dev)
 {
 #if 0
@@ -68,12 +284,31 @@
 #endif
 } 
 
+static void usb_host_cancel_io(USBPacket *p, void *opaque)
+{
+    USBHostDevice *s = (USBHostDevice *)opaque;
+
+#ifdef DEBUG
+    printf("packet %p canceled\n", p);
+#endif
+
+    if (p->devep != 0) {
+        qemu_bh_cancel(s->bh);
+        s->packet = NULL;
+        ioctl(s->fd, USBDEVFS_DISCARDURB, &s->urb);
+    }
+}
+
 static void usb_host_handle_destroy(USBDevice *dev)
 {
     USBHostDevice *s = (USBHostDevice *)dev;
 
-    if (s->fd >= 0)
+    qemu_bh_delete(s->bh);
+
+    if (s->fd >= 0) {
         close(s->fd);
+    }
+
     qemu_free(s);
 }
 
@@ -117,33 +352,39 @@
 static int usb_host_handle_data(USBDevice *dev, USBPacket *p)
 {
     USBHostDevice *s = (USBHostDevice *)dev;
-    struct usbdevfs_bulktransfer bt;
-    int ret;
     uint8_t devep = p->devep;
+    int ret;
 
-    /* XXX: optimize and handle all data types by looking at the
-       config descriptor */
     if (p->pid == USB_TOKEN_IN)
         devep |= 0x80;
-    bt.ep = devep;
-    bt.len = p->len;
-    bt.timeout = 50;
-    bt.data = p->data;
-    ret = ioctl(s->fd, USBDEVFS_BULK, &bt);
+
+    s->urb.type = s->endp_table[(devep & 0xf) - 1].type;
+    s->urb.endpoint = devep;
+    s->urb.flags = 0;
+    s->urb.buffer = p->data;
+    s->urb.buffer_length = p->len;
+    s->urb.actual_length = 0;
+    s->urb.signr = USB_ASYNC_COMPLETION_SIGNAL;
+    s->urb.number_of_packets = 0; // isochronous
+    s->urb.usercontext = s;
+
+    ret = ioctl(s->fd, USBDEVFS_SUBMITURB, &s->urb);
     if (ret < 0) {
-        switch(errno) {
-        case ETIMEDOUT:
-            return USB_RET_NAK;
-        case EPIPE:
+        printf("urb submission failed - errno %d\n", errno);
+        switch (errno) {
+        case ENODEV:
+            ret = USB_RET_NODEV;
+            break;
         default:
-#ifdef DEBUG
-            printf("handle_data: errno=%d\n", errno);
-#endif
-            return USB_RET_STALL;
+            ret = USB_RET_STALL;
         }
-    } else {
         return ret;
     }
+
+    usb_defer_packet(p, usb_host_cancel_io, s);
+    s->packet = p;
+
+    return USB_RET_ASYNC;
 }
 
 /* XXX: exclude high speed devices or implement EHCI */
@@ -165,7 +406,7 @@
     
     snprintf(buf, sizeof(buf), USBDEVFS_PATH "/%03d/%03d", 
              bus_num, addr);
-    fd = open(buf, O_RDWR);
+    fd = open(buf, O_RDWR | O_NONBLOCK);
     if (fd < 0) {
         perror(buf);
         return NULL;
@@ -174,7 +415,7 @@
     /* read the config description */
     descr_len = read(fd, descr, sizeof(descr));
     if (descr_len <= 0) {
-        perror("read descr");
+        perror("device_open - read descr");
         goto fail;
     }
     
@@ -235,10 +476,23 @@
     if (!dev)
         goto fail;
     dev->fd = fd;
+
+    ret = usb_linux_update_endp_table(dev);
+    if (ret) {
+        qemu_free(dev);
+        goto fail;
+    }
+
+    dev->bh = qemu_bh_new(usb_linux_bh_cb, dev);
+    if (!dev->bh) {
+        qemu_free(dev);
+        goto fail;
+    }
+
     if (ci.slow)
         dev->dev.speed = USB_SPEED_LOW;
     else
-        dev->dev.speed = USB_SPEED_HIGH;
+        dev->dev.speed = USB_SPEED_FULL;
     dev->dev.handle_packet = usb_generic_handle_packet;
 
     dev->dev.handle_reset = usb_host_handle_reset;
--- qemu/vl.c	2006-10-31 19:44:16.000000000 -0600
+++ qemu/vl.c	2006-11-18 20:32:20.000000000 -0600
@@ -3743,6 +3743,10 @@
 static USBPort *used_usb_ports;
 static USBPort *free_usb_ports;
 
+#if defined (__linux__)
+void usb_linux_sig_handler(int, siginfo_t *, void *);
+#endif
+
 /* ??? Maybe change this to register a hub to keep track of the topology.  */
 void qemu_register_usb_port(USBPort *port, void *opaque, int index,
                             usb_attachfn attach)
@@ -6775,6 +6779,19 @@
     socket_init();
 #endif
 
+#if defined (__linux__)
+if (usb_enabled) {
+        struct sigaction usb_linux_sa;
+        usb_linux_sa.sa_sigaction = usb_linux_sig_handler;
+        sigfillset(&usb_linux_sa.sa_mask);
+        usb_linux_sa.sa_flags = SA_SIGINFO;
+#if defined (TARGET_I386) && defined(USE_CODE_COPY)
+        usb_linux_sa.sa_flags |= SA_ONSTACK;
+#endif
+        sigaction(USB_ASYNC_COMPLETION_SIGNAL, &usb_linux_sa, NULL);
+}
+#endif
+
     /* init network clients */
     if (nb_net_clients == 0) {
         /* if no clients, we use a default config */
--- qemu/vl.h	2006-09-24 13:49:43.000000000 -0500
+++ qemu/vl.h	2006-11-18 20:18:30.000000000 -0600
@@ -1181,6 +1181,10 @@
 
 #include "hw/usb.h"
 
+#if defined(__linux__)
+#define USB_ASYNC_COMPLETION_SIGNAL (SIGRTMIN + 5)
+#endif
+
 /* usb ports of the VM */
 
 void qemu_register_usb_port(USBPort *port, void *opaque, int index,
_______________________________________________
Qemu-devel mailing list
Qemu-devel@nongnu.org
http://lists.nongnu.org/mailman/listinfo/qemu-devel

Reply via email to