Attached is an updated patch for supporting isochronous transfers in redirected 
host USB devices. The initial goal was supporting USB 1.1 Webcam. Tested on 
WinXP guest with several Webcams. Works on USB 1.1 Webcams, as well as most USB 
2.0 Webcams (backward compatibility) on low resolutions. Some jitter is visible 
in the video stream, and it will be fixed. 

Notice USE_ASYNCIO, which defines whether to use signal based async io or 
polling for receiving urbs. Currently it is disabled, so polling is used, but 
it does not seem to affect the performance because it uses the non-blocking 
USBDEVFS_REAPURBNDELAY ioctl. In order to use the signal based async io, the 
patch to usb-uhci.c should be applied.

The patch includes parts of previous patches posted in Qemu-devel: 
usb_host_update_interfaces (from qemu-0.9.0-usb-multi-configs.patch), 
usb_linux_update_endp_table (qemu-usb-host-async.patch) as well as some other 
lines of code.

I am currently working on the ehci emulation for fully supporting USB 2.0 
isochronous devices.

Waiting for your comments,
Arnon
diff --git a/qemu-head/usb-linux.c b/qemu/usb-linux.c
index 3a23301..70e2e69 100755
--- a/qemu-head/usb-linux.c
+++ b/qemu/usb-linux.c
@@ -28,6 +28,7 @@
 #include <sys/ioctl.h>
 #include <linux/usbdevice_fs.h>
 #include <linux/version.h>
+#include <signal.h>
 
 /* We redefine it to avoid version problems */
 struct usb_ctrltransfer {
@@ -48,15 +49,171 @@ static int usb_host_find_device(int *pbus_num, int *paddr,
                                 const char *devname);
 
 //#define DEBUG
+//#define DEBUG_ISOCH
+//#define USE_ASYNCIO
 
 #define USBDEVFS_PATH "/proc/bus/usb"
 #define PRODUCT_NAME_SZ 32
+#define SIG_ISOCOMPLETE (SIGRTMIN+7)
+#define MAX_ENDPOINTS 16
 
+struct sigaction sigact;
+
+// endpoint association data
+struct endp_data {
+    uint8_t type;
+};
+
+// FIXME: move USBPacket to PendingURB
 typedef struct USBHostDevice {
     USBDevice dev;
     int fd;
+    USBPacket *packet;
+    struct endp_data endp_table[MAX_ENDPOINTS];
+    int configuration;
+    uint8_t descr[1024];
+    int descr_len;
+    int urbs_ready;
 } USBHostDevice;
 
+typedef struct PendingURB {
+    struct usbdevfs_urb *urb;
+    USBHostDevice *dev;
+    QEMUBH *bh;
+    int status;
+    struct PendingURB *next;
+} PendingURB;
+
+PendingURB *pending_urbs = NULL;
+
+int add_pending_urb(struct usbdevfs_urb *urb)
+{
+    PendingURB *purb = qemu_mallocz(sizeof(PendingURB));
+    if (purb) {
+        purb->urb = urb;
+        purb->dev = NULL;
+        purb->bh = NULL;
+        purb->status = 0;
+        purb->next = pending_urbs;
+        pending_urbs = purb;
+        return 1;
+    }
+    return 0;
+}
+
+int del_pending_urb(struct usbdevfs_urb *urb)
+{
+    PendingURB *purb = pending_urbs;
+    PendingURB *prev = NULL;
+
+    while (purb && purb->urb != urb) {
+        prev = purb;
+        purb = purb->next;
+    }
+
+    if (purb && purb->urb == urb) {
+        if (prev) {
+            prev->next = purb->next;
+        } else {
+            pending_urbs = purb->next;
+        }
+        qemu_free(purb);
+        return 1;
+    }
+    return 0;
+}
+
+PendingURB *get_pending_urb(struct usbdevfs_urb *urb)
+{
+    PendingURB *purb = pending_urbs;
+
+    while (purb && purb->urb != urb) {
+        purb = purb->next;
+    }
+
+    if (purb && purb->urb == urb) {
+        return purb;
+    }
+    return NULL;
+}
+
+static int usb_host_update_interfaces(USBHostDevice *dev, int configuration)
+{
+    int dev_descr_len, config_descr_len;
+    int interface, nb_interfaces, nb_configurations;
+    int ret, i;
+
+    if (configuration == 0) // address state - ignore
+        return 1;
+
+    i = 0;
+    dev_descr_len = dev->descr[0];
+    if (dev_descr_len > dev->descr_len)
+        goto fail;
+    nb_configurations = dev->descr[17];
+
+    i += dev_descr_len;
+    while (i < dev->descr_len) {
+#ifdef DEBUG
+        printf("i is %d, descr_len is %d, dl %d, dt %d\n", i, dev->descr_len,
+               dev->descr[i], dev->descr[i+1]);
+#endif
+        if (dev->descr[i+1] != USB_DT_CONFIG) {
+            i += dev->descr[i];
+            continue;
+        }
+        config_descr_len = dev->descr[i];
+
+        if (configuration == dev->descr[i + 5])
+            break;
+
+        i += config_descr_len;
+    }
+
+    if (i >= dev->descr_len) {
+        printf("usb_host: error - device has no matching configuration\n");
+        goto fail;
+    }
+    nb_interfaces = dev->descr[i + 4];
+
+#ifdef USBDEVFS_DISCONNECT
+    /* earlier Linux 2.4 do not support that */
+    {
+        struct usbdevfs_ioctl ctrl;
+        for (interface = 0; interface < nb_interfaces; interface++) {
+            ctrl.ioctl_code = USBDEVFS_DISCONNECT;
+            ctrl.ifno = interface;
+            ret = ioctl(dev->fd, USBDEVFS_IOCTL, &ctrl);
+            if (ret < 0 && errno != ENODATA) {
+                perror("USBDEVFS_DISCONNECT");
+                goto fail;
+            }
+        }
+    }
+#endif
+
+    /* XXX: only grab if all interfaces are free */
+    for (interface = 0; interface < nb_interfaces; interface++) {
+        ret = ioctl(dev->fd, USBDEVFS_CLAIMINTERFACE, &interface);
+        if (ret < 0) {
+            if (errno == EBUSY) {
+                fprintf(stderr, "usb_host: warning - device already 
grabbed\n");
+            } else {
+                perror("USBDEVFS_CLAIMINTERFACE");
+            }
+        fail:
+            return 0;
+        }
+    }
+
+#ifdef DEBUG
+    printf("usb_host: %d interfaces claimed for configuration %d\n", 
nb_interfaces,
+           configuration);
+#endif
+
+    return 1;
+}
+
 static void usb_host_handle_reset(USBDevice *dev)
 {
 #if 0
@@ -76,6 +233,8 @@ static void usb_host_handle_destroy(USBDevice *dev)
     qemu_free(s);
 }
 
+static int usb_linux_update_endp_table(USBHostDevice *s);
+
 static int usb_host_handle_control(USBDevice *dev,
                                    int request,
                                    int value,
@@ -85,13 +244,32 @@ static int usb_host_handle_control(USBDevice *dev,
 {
     USBHostDevice *s = (USBHostDevice *)dev;
     struct usb_ctrltransfer ct;
+    struct usbdevfs_setinterface si;
+    int intf_update_required = 0;
     int ret;
 
     if (request == (DeviceOutRequest | USB_REQ_SET_ADDRESS)) {
         /* specific SET_ADDRESS support */
         dev->addr = value;
         return 0;
+    } else if (request == ((USB_RECIP_INTERFACE << 8) | 
USB_REQ_SET_INTERFACE)) {
+        /* set alternate setting for the interface */
+        si.interface = index;
+        si.altsetting = value;
+        ret = ioctl(s->fd, USBDEVFS_SETINTERFACE, &si);
+        usb_linux_update_endp_table(s);
+    } else if (request == (DeviceOutRequest | USB_REQ_SET_CONFIGURATION)) {
+#ifdef DEBUG
+        printf("usb_host_handle_control: SET_CONFIGURATION request - config 
%d\n",
+               value & 0xff);
+#endif
+        if (s->configuration != (value & 0xff)) {
+            s->configuration = (value & 0xff);
+            intf_update_required = 1;
+        }
+        goto do_request;
     } else {
+    do_request:
         ct.bRequestType = request >> 8;
         ct.bRequest = request;
         ct.wValue = value;
@@ -100,19 +278,28 @@ static int usb_host_handle_control(USBDevice *dev,
         ct.timeout = 50;
         ct.data = data;
         ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
-        if (ret < 0) {
-            switch(errno) {
-            case ETIMEDOUT:
-                return USB_RET_NAK;
-            default:
-                return USB_RET_STALL;
-            }
-        } else {
-            return ret;
+    }
+
+    if (ret < 0) {
+        switch(errno) {
+        case ETIMEDOUT:
+            return USB_RET_NAK;
+        default:
+            return USB_RET_STALL;
         }
-   }
+    } else {
+        if (intf_update_required) {
+#ifdef DEBUG
+            printf("usb_host_handle_control: updating interfaces\n");
+#endif
+            usb_host_update_interfaces(s, value & 0xff);
+        }
+        return ret;
+    }
 }
 
+static int usb_host_handle_isoch(USBDevice *dev, USBPacket *p);
+
 static int usb_host_handle_data(USBDevice *dev, USBPacket *p)
 {
     USBHostDevice *s = (USBHostDevice *)dev;
@@ -120,6 +307,10 @@ static int usb_host_handle_data(USBDevice *dev, USBPacket 
*p)
     int ret;
     uint8_t devep = p->devep;
 
+    if (s->endp_table[p->devep - 1].type == USBDEVFS_URB_TYPE_ISO) {
+        return usb_host_handle_isoch(dev, p);
+    }
+
     /* XXX: optimize and handle all data types by looking at the
        config descriptor */
     if (p->pid == USB_TOKEN_IN)
@@ -145,18 +336,272 @@ static int usb_host_handle_data(USBDevice *dev, 
USBPacket *p)
     }
 }
 
+static void usb_linux_bh_cb(void *opaque);
+
+void isoch_done(int signum, siginfo_t *info, void *context) {
+    struct usbdevfs_urb *urb = (struct usbdevfs_urb *)info->si_addr;
+    USBHostDevice *s = (USBHostDevice *)urb->usercontext;
+    PendingURB *purb;
+
+    if (info->si_code != SI_ASYNCIO ||
+        info->si_signo != SIG_ISOCOMPLETE) {
+        return;
+    }
+
+    purb = get_pending_urb(urb);
+    if (purb) {
+        purb->bh = qemu_bh_new(usb_linux_bh_cb, purb);
+        if (purb->bh) {
+            purb->dev = s;
+            purb->status = info->si_errno;
+            qemu_bh_schedule(purb->bh);
+        }
+    }
+}
+
+static int usb_host_handle_isoch(USBDevice *dev, USBPacket *p)
+{
+    USBHostDevice *s = (USBHostDevice *)dev;
+    struct usbdevfs_urb *urb, *purb = NULL;
+    int ret;
+    uint8_t devep = p->devep;
+
+    if (p->pid == USB_TOKEN_IN)
+        devep |= 0x80;
+
+    urb = qemu_mallocz(sizeof(struct usbdevfs_urb) +
+                       sizeof(struct usbdevfs_iso_packet_desc));
+    if (!urb) {
+        printf("usb_host_handle_isoch: malloc failed\n");
+        return 0;
+    }
+
+    urb->type = USBDEVFS_URB_TYPE_ISO;
+    urb->endpoint = devep;
+    urb->status = 0;
+    urb->flags = USBDEVFS_URB_ISO_ASAP;
+    urb->buffer = p->data;
+    urb->buffer_length = p->len;
+    urb->actual_length = 0;
+    urb->start_frame = 0;
+    urb->error_count = 0;
+#ifdef USE_ASYNCIO
+    urb->signr = SIG_ISOCOMPLETE;
+#else
+    urb->signr = 0;
+#endif
+    urb->usercontext = s;
+    urb->number_of_packets = 1;
+    urb->iso_frame_desc[0].length = p->len;
+    urb->iso_frame_desc[0].actual_length = 0;
+    urb->iso_frame_desc[0].status = 0;
+    ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb);
+    if (ret == 0) {
+        if (!add_pending_urb(urb)) {
+            printf("usb_host_handle_isoch: add_pending_urb failed %p\n", urb);
+        }
+    } else {
+        printf("usb_host_handle_isoch: SUBMITURB ioctl=%d errno=%d\n", ret, 
errno);
+        qemu_free(urb);
+        switch(errno) {
+        case ETIMEDOUT:
+            return USB_RET_NAK;
+        case EPIPE:
+        default:
+            return USB_RET_STALL;
+        }
+    }
+#ifdef USE_ASYNCIO
+    // FIXME: handle urbs_ready together with sync io
+    // workaround for injecting the signaled urbs into current frame
+    if (s->urbs_ready > 0) {
+        ret = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &purb);
+        if (ret == 0) {
+            ret = purb->actual_length;
+            qemu_free(purb);
+            s->urbs_ready--;
+        }
+        return ret;
+    }
+    s->packet = p;
+    return USB_RET_ASYNC;
+#else
+    ret = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &purb);
+    if (ret == 0) {
+        if (del_pending_urb(purb)) {
+            ret = purb->actual_length;
+            qemu_free(purb);
+        } else {
+            printf("usb_host_handle_isoch: del_pending_urb failed %p\n", purb);
+        }
+    } else {
+#ifdef DEBUG_ISOCH
+        printf("usb_host_handle_isoch: REAPURBNDELAY ioctl=%d errno=%d\n", 
ret, errno);
+#endif
+    }
+    return ret;
+#endif
+}
+
+static void usb_linux_bh_cb(void *opaque)
+{
+    PendingURB *pending_urb = (PendingURB *)opaque;
+    USBHostDevice *s = pending_urb->dev;
+    struct usbdevfs_urb *purb = NULL;
+    USBPacket *p = s->packet;
+    int ret;
+
+    /* FIXME: handle purb->status */
+    qemu_free(pending_urb->bh);
+    del_pending_urb(pending_urb->urb);
+
+    if (!p) {
+        s->urbs_ready++;
+        return;
+    }
+
+    ret = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &purb);
+    if (ret < 0) {
+        printf("usb_linux_bh_cb: REAPURBNDELAY ioctl=%d errno=%d\n", ret, 
errno);
+        return;
+    }
+
+#ifdef DEBUG_ISOCH
+    if (purb == pending_urb->urb) {
+        printf("usb_linux_bh_cb: urb mismatch reaped=%p pending=%p\n", purb, 
urb);
+    }
+#endif
+
+    p->len = purb->actual_length;
+    usb_packet_complete(p);
+    qemu_free(purb);
+    s->packet = NULL;
+}
+
+// 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, configuration, alt_interface;
+    struct usb_ctrltransfer ct;
+    int interface, ret, length, i;
+
+    ct.bRequestType = USB_DIR_IN;
+    ct.bRequest = USB_REQ_GET_CONFIGURATION;
+    ct.wValue = 0;
+    ct.wIndex = 0;
+    ct.wLength = 1;
+    ct.data = &configuration;
+    ct.timeout = 50;
+
+    ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
+    if (ret < 0) {
+        perror("usb_linux_update_endp_table");
+        return 1;
+    }
+
+    // in address state
+    if (configuration == 0)
+        return 1;
+
+    // get the desired configuration, interface, and endpoint descriptors
+    // from device description
+    descriptors = &s->descr[18];
+    length = s->descr_len - 18;
+    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 = &alt_interface;
+        ct.timeout = 50;
+
+        ret = ioctl(s->fd, USBDEVFS_CONTROL, &ct);
+        if (ret < 0) {
+            perror("usb_linux_update_endp_table");
+            return 1;
+        }
+
+        // the current interface descriptor is the active interface
+        // and has endpoints
+        if (descriptors[i + 3] != alt_interface) {
+            i += descriptors[i];
+            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];
+        }
+    }
+    return 0;
+}
+
 /* XXX: exclude high speed devices or implement EHCI */
 USBDevice *usb_host_device_open(const char *devname)
 {
-    int fd, interface, ret, i;
-    USBHostDevice *dev;
+    int fd = -1, ret;
+    USBHostDevice *dev = NULL;
     struct usbdevfs_connectinfo ci;
-    uint8_t descr[1024];
     char buf[1024];
-    int descr_len, dev_descr_len, config_descr_len, nb_interfaces;
     int bus_num, addr;
     char product_name[PRODUCT_NAME_SZ];
 
+    dev = qemu_mallocz(sizeof(USBHostDevice));
+    if (!dev)
+        goto fail;
+
+#ifdef DEBUG_ISOCH
+    printf("usb_host_device_open %s\n", devname);
+#endif
     if (usb_host_find_device(&bus_num, &addr,
                              product_name, sizeof(product_name),
                              devname) < 0)
@@ -164,61 +609,35 @@ USBDevice *usb_host_device_open(const char *devname)
 
     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;
     }
 
-    /* read the config description */
-    descr_len = read(fd, descr, sizeof(descr));
-    if (descr_len <= 0) {
-        perror("read descr");
+    /* read the device description */
+    dev->descr_len = read(fd, dev->descr, sizeof(dev->descr));
+    if (dev->descr_len <= 0) {
+        perror("usb_host_update_interfaces: reading device data failed");
         goto fail;
     }
 
-    i = 0;
-    dev_descr_len = descr[0];
-    if (dev_descr_len > descr_len)
-        goto fail;
-    i += dev_descr_len;
-    config_descr_len = descr[i];
-    if (i + config_descr_len > descr_len)
-        goto fail;
-    nb_interfaces = descr[i + 4];
-    if (nb_interfaces != 1) {
-        /* NOTE: currently we grab only one interface */
-        fprintf(stderr, "usb_host: only one interface supported\n");
-        goto fail;
-    }
-
-#ifdef USBDEVFS_DISCONNECT
-    /* earlier Linux 2.4 do not support that */
+#ifdef DEBUG
     {
-        struct usbdevfs_ioctl ctrl;
-        ctrl.ioctl_code = USBDEVFS_DISCONNECT;
-        ctrl.ifno = 0;
-        ret = ioctl(fd, USBDEVFS_IOCTL, &ctrl);
-        if (ret < 0 && errno != ENODATA) {
-            perror("USBDEVFS_DISCONNECT");
-            goto fail;
-        }
+        int x;
+        printf("=== begin dumping device descriptor data ===\n");
+        for (x = 0; x < dev->descr_len; x++)
+            printf("%02x ", dev->descr[x]);
+        printf("\n=== end dumping device descriptor data ===\n");
     }
 #endif
 
-    /* XXX: only grab if all interfaces are free */
-    interface = 0;
-    ret = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &interface);
-    if (ret < 0) {
-        if (errno == EBUSY) {
-            fprintf(stderr, "usb_host: device already grabbed\n");
-        } else {
-            perror("USBDEVFS_CLAIMINTERFACE");
-        }
-    fail:
-        close(fd);
-        return NULL;
-    }
+    dev->fd = fd;
+    dev->configuration = 1;
+
+    // XXX - do something about initial configuration
+    if (!usb_host_update_interfaces(dev, 1))
+        goto fail;
 
     ret = ioctl(fd, USBDEVFS_CONNECTINFO, &ci);
     if (ret < 0) {
@@ -230,10 +649,12 @@ USBDevice *usb_host_device_open(const char *devname)
     printf("host USB device %d.%d grabbed\n", bus_num, addr);
 #endif
 
-    dev = qemu_mallocz(sizeof(USBHostDevice));
-    if (!dev)
+    ret = usb_linux_update_endp_table(dev);
+    if (ret) {
+        qemu_free(dev);
         goto fail;
-    dev->fd = fd;
+    }
+
     if (ci.slow)
         dev->dev.speed = USB_SPEED_LOW;
     else
@@ -252,7 +673,24 @@ USBDevice *usb_host_device_open(const char *devname)
         pstrcpy(dev->dev.devname, sizeof(dev->dev.devname),
                 product_name);
 
+#ifdef USE_ASYNCIO
+    /* set up the signal handlers */
+    sigemptyset(&sigact.sa_mask);
+    sigact.sa_sigaction = isoch_done;
+    sigact.sa_flags = SA_SIGINFO;
+    sigact.sa_restorer = 0;
+    ret = sigaction(SIG_ISOCOMPLETE, &sigact, NULL);
+    if (ret < 0) {
+        printf("sigaction SIG_ISOCOMPLETE=%d errno=%d\n", ret, errno);
+    }
+#endif
+    dev->urbs_ready = 0;
     return (USBDevice *)dev;
+fail:
+    if (dev)
+        qemu_free(dev);
+    close(fd);
+    return NULL;
 }
 
 static int get_tag_value(char *buf, int buf_size,
diff --git a/qemu-head/hw/usb-uhci.c b/qemu/hw/usb-uhci.c
index 5867409..99544ab 100755
--- a/qemu-head/hw/usb-uhci.c
+++ b/qemu/hw/usb-uhci.c
@@ -25,6 +25,7 @@
 
 //#define DEBUG
 //#define DEBUG_PACKET
+//#define DEBUG_ISOCH
 
 #define UHCI_CMD_FGR      (1 << 4)
 #define UHCI_CMD_EGSM     (1 << 3)
@@ -88,6 +89,7 @@ typedef struct UHCIState {
        other queues will not be processed until the next frame.  The solution
        is to allow multiple pending requests.  */
     uint32_t async_qh;
+    uint32_t async_frame_addr;
     USBPacket usb_packet;
     uint8_t usb_buf[2048];
 } UHCIState;
@@ -146,6 +148,58 @@ static void uhci_reset(UHCIState *s)
     }
 }
 
+static void uhci_save(QEMUFile *f, void *opaque)
+{
+    UHCIState *s = opaque;
+    uint8_t num_ports = NB_PORTS;
+    int i;
+
+    pci_device_save(&s->dev, f);
+
+    qemu_put_8s(f, &num_ports);
+    for (i = 0; i < num_ports; ++i)
+        qemu_put_be16s(f, &s->ports[i].ctrl);
+    qemu_put_be16s(f, &s->cmd);
+    qemu_put_be16s(f, &s->status);
+    qemu_put_be16s(f, &s->intr);
+    qemu_put_be16s(f, &s->frnum);
+    qemu_put_be32s(f, &s->fl_base_addr);
+    qemu_put_8s(f, &s->sof_timing);
+    qemu_put_8s(f, &s->status2);
+    qemu_put_timer(f, s->frame_timer);
+}
+
+static int uhci_load(QEMUFile* f,void* opaque,int version_id)
+{
+    UHCIState *s = opaque;
+    uint8_t num_ports;
+    int i, ret;
+
+    if (version_id > 1)
+        return -EINVAL;
+
+    ret = pci_device_load(&s->dev, f);
+    if (ret < 0)
+        return ret;
+
+    qemu_get_8s(f, &num_ports);
+    if (num_ports != NB_PORTS)
+        return -EINVAL;
+
+    for (i = 0; i < num_ports; ++i)
+        qemu_get_be16s(f, &s->ports[i].ctrl);
+    qemu_get_be16s(f, &s->cmd);
+    qemu_get_be16s(f, &s->status);
+    qemu_get_be16s(f, &s->intr);
+    qemu_get_be16s(f, &s->frnum);
+    qemu_get_be32s(f, &s->fl_base_addr);
+    qemu_get_8s(f, &s->sof_timing);
+    qemu_get_8s(f, &s->status2);
+    qemu_get_timer(f, s->frame_timer);
+
+    return 0;
+}
+
 static void uhci_ioport_writeb(void *opaque, uint32_t addr, uint32_t val)
 {
     UHCIState *s = opaque;
@@ -390,6 +444,7 @@ static void uhci_attach(USBPort *port1, USBDevice *dev)
     }
 }
 
+
 static int uhci_broadcast_packet(UHCIState *s, USBPacket *p)
 {
     UHCIPort *port;
@@ -449,7 +504,7 @@ static void uhci_async_complete_packet(USBPacket * packet, 
void *opaque);
           0 if TD successful
           1 if TD unsuccessful or inactive
 */
-static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask)
+static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int *int_mask, int 
completion)
 {
     uint8_t pid;
     int len, max_len, err, ret;
@@ -465,7 +520,8 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int 
*int_mask)
     /* TD is active */
     max_len = ((td->token >> 21) + 1) & 0x7ff;
     pid = td->token & 0xff;
-    if (s->async_qh) {
+
+    if (completion && (s->async_qh || s->async_frame_addr)) {
         ret = s->usb_packet.len;
         if (ret >= 0) {
             len = ret;
@@ -481,7 +537,8 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int 
*int_mask)
             len = 0;
         }
         s->async_qh = 0;
-    } else {
+        s->async_frame_addr = 0;
+    } else if (!completion) {
         s->usb_packet.pid = pid;
         s->usb_packet.devaddr = (td->token >> 8) & 0x7f;
         s->usb_packet.devep = (td->token >> 15) & 0xf;
@@ -519,6 +576,7 @@ static int uhci_handle_td(UHCIState *s, UHCI_TD *td, int 
*int_mask)
             return -1;
         }
     }
+
     if (ret == USB_RET_ASYNC) {
         return 2;
     }
@@ -584,8 +642,42 @@ static void uhci_async_complete_packet(USBPacket * packet, 
void *opaque)
     uint32_t link;
     uint32_t old_td_ctrl;
     uint32_t val;
+    uint32_t frame_addr;
     int ret;
 
+    /* Handle async isochronous packet completion */
+    frame_addr = s->async_frame_addr;
+    if (frame_addr) {
+        cpu_physical_memory_read(frame_addr, (uint8_t *)&link, 4);
+        le32_to_cpus(&link);
+
+        cpu_physical_memory_read(link & ~0xf, (uint8_t *)&td, sizeof(td));
+        le32_to_cpus(&td.link);
+        le32_to_cpus(&td.ctrl);
+        le32_to_cpus(&td.token);
+        le32_to_cpus(&td.buffer);
+        old_td_ctrl = td.ctrl;
+        ret = uhci_handle_td(s, &td, &s->pending_int_mask, 1);
+
+        /* update the status bits of the TD */
+        if (old_td_ctrl != td.ctrl) {
+            val = cpu_to_le32(td.ctrl);
+            cpu_physical_memory_write((link & ~0xf) + 4,
+                                      (const uint8_t *)&val,
+                                      sizeof(val));
+        }
+        if (ret == 2) {
+            s->async_frame_addr = frame_addr;
+        } else if (ret == 0) {
+            /* update qh element link */
+            val = cpu_to_le32(td.link);
+            cpu_physical_memory_write(frame_addr,
+                                      (const uint8_t *)&val,
+                                      sizeof(val));
+        }
+        return;
+    }
+
     link = s->async_qh;
     if (!link) {
         /* This should never happen. It means a TD somehow got removed
@@ -604,7 +696,8 @@ static void uhci_async_complete_packet(USBPacket * packet, 
void *opaque)
         le32_to_cpus(&td.token);
         le32_to_cpus(&td.buffer);
         old_td_ctrl = td.ctrl;
-        ret = uhci_handle_td(s, &td, &s->pending_int_mask);
+        ret = uhci_handle_td(s, &td, &s->pending_int_mask, 1);
+
         /* update the status bits of the TD */
         if (old_td_ctrl != td.ctrl) {
             val = cpu_to_le32(td.ctrl);
@@ -697,7 +790,8 @@ static void uhci_frame_timer(void *opaque)
                 le32_to_cpus(&td.token);
                 le32_to_cpus(&td.buffer);
                 old_td_ctrl = td.ctrl;
-                ret = uhci_handle_td(s, &td, &int_mask);
+                ret = uhci_handle_td(s, &td, &int_mask, 0);
+
                 /* update the status bits of the TD */
                 if (old_td_ctrl != td.ctrl) {
                     val = cpu_to_le32(td.ctrl);
@@ -731,27 +825,23 @@ static void uhci_frame_timer(void *opaque)
             le32_to_cpus(&td.ctrl);
             le32_to_cpus(&td.token);
             le32_to_cpus(&td.buffer);
-            /* Ignore isochonous transfers while there is an async packet
-               pending.  This is wrong, but we don't implement isochronous
-               transfers anyway.  */
-            if (s->async_qh == 0) {
-                old_td_ctrl = td.ctrl;
-                ret = uhci_handle_td(s, &td, &int_mask);
-                /* update the status bits of the TD */
-                if (old_td_ctrl != td.ctrl) {
-                    val = cpu_to_le32(td.ctrl);
-                    cpu_physical_memory_write((link & ~0xf) + 4,
-                                              (const uint8_t *)&val,
-                                              sizeof(val));
-                }
-                if (ret < 0)
-                    break; /* interrupted frame */
-                if (ret == 2) {
-                    /* We can't handle async isochronous transfers.
-                       Cancel The packet.  */
-                    fprintf(stderr, "usb-uhci: Unimplemented async packet\n");
-                    usb_cancel_packet(&s->usb_packet);
-                }
+
+            /* Handle isochonous transfer  */
+            /* FIXME: might be more than one isoc in frame */
+            old_td_ctrl = td.ctrl;
+            ret = uhci_handle_td(s, &td, &int_mask, 0);
+
+            /* update the status bits of the TD */
+            if (old_td_ctrl != td.ctrl) {
+                val = cpu_to_le32(td.ctrl);
+                cpu_physical_memory_write((link & ~0xf) + 4,
+                                          (const uint8_t *)&val,
+                                          sizeof(val));
+            }
+            if (ret < 0)
+                break; /* interrupted frame */
+            if (ret == 2) {
+                s->async_frame_addr = frame_addr;
             }
             link = td.link;
         }
@@ -767,6 +857,7 @@ static void uhci_frame_timer(void *opaque)
         usb_cancel_packet(&s->usb_packet);
         s->async_qh = 0;
     }
+
     /* prepare the timer for the next frame */
     expire_time = qemu_get_clock(vm_clock) +
         (ticks_per_sec / FRAME_TIMER_FREQ);
@@ -855,4 +946,3 @@ void usb_uhci_piix4_init(PCIBus *bus, int devfn)
     pci_register_io_region(&s->dev, 4, 0x20,
                            PCI_ADDRESS_SPACE_IO, uhci_map);
 }
-

Reply via email to