summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAnthony Liguori <aliguori@us.ibm.com>2011-06-27 14:59:08 -0500
committerAnthony Liguori <aliguori@us.ibm.com>2011-06-27 14:59:08 -0500
commit656acddb39ee924b843df7f57d4c1798e49be058 (patch)
treeb55351243094158d8f7098e29fcd9ca79f55bb64
parentc45d1fc191bba333086f066247dd6096175db0da (diff)
parenta7fb71d1b2a992a0c4842f06e2b450889e2442ec (diff)
downloadfocaccia-qemu-656acddb39ee924b843df7f57d4c1798e49be058.tar.gz
focaccia-qemu-656acddb39ee924b843df7f57d4c1798e49be058.zip
Merge remote-tracking branch 'kraxel/usb.17' into staging
-rw-r--r--hw/usb-bus.c31
-rw-r--r--hw/usb-ccid.c1
-rw-r--r--hw/usb-desc.c14
-rw-r--r--hw/usb-ehci.c43
-rw-r--r--hw/usb-msd.c19
-rw-r--r--hw/usb-ohci.c17
-rw-r--r--hw/usb.h4
-rw-r--r--usb-bsd.c2
-rw-r--r--usb-linux.c124
9 files changed, 191 insertions, 64 deletions
diff --git a/hw/usb-bus.c b/hw/usb-bus.c
index 480956dfcf..2abce12de5 100644
--- a/hw/usb-bus.c
+++ b/hw/usb-bus.c
@@ -75,7 +75,7 @@ static int usb_qdev_init(DeviceState *qdev, DeviceInfo *base)
     QLIST_INIT(&dev->strings);
     rc = dev->info->init(dev);
     if (rc == 0 && dev->auto_attach)
-        usb_device_attach(dev);
+        rc = usb_device_attach(dev);
     return rc;
 }
 
@@ -121,7 +121,7 @@ USBDevice *usb_create(USBBus *bus, const char *name)
         bus = usb_bus_find(-1);
         if (!bus)
             return NULL;
-        fprintf(stderr, "%s: no bus specified, using \"%s\" for \"%s\"\n",
+        error_report("%s: no bus specified, using \"%s\" for \"%s\"\n",
                 __FUNCTION__, bus->qbus.name, name);
     }
 #endif
@@ -171,15 +171,20 @@ void usb_unregister_port(USBBus *bus, USBPort *port)
     bus->nfree--;
 }
 
-static void do_attach(USBDevice *dev)
+static int do_attach(USBDevice *dev)
 {
     USBBus *bus = usb_bus_from_device(dev);
     USBPort *port;
 
     if (dev->attached) {
-        fprintf(stderr, "Warning: tried to attach usb device %s twice\n",
+        error_report("Error: tried to attach usb device %s twice\n",
                 dev->product_desc);
-        return;
+        return -1;
+    }
+    if (bus->nfree == 0) {
+        error_report("Error: tried to attach usb device %s to a bus with no free ports\n",
+                dev->product_desc);
+        return -1;
     }
     if (dev->port_path) {
         QTAILQ_FOREACH(port, &bus->free, next) {
@@ -188,13 +193,18 @@ static void do_attach(USBDevice *dev)
             }
         }
         if (port == NULL) {
-            fprintf(stderr, "Warning: usb port %s (bus %s) not found\n",
+            error_report("Error: usb port %s (bus %s) not found\n",
                     dev->port_path, bus->qbus.name);
-            return;
+            return -1;
         }
     } else {
         port = QTAILQ_FIRST(&bus->free);
     }
+    if (!(port->speedmask & dev->speedmask)) {
+        error_report("Warning: speed mismatch trying to attach usb device %s to bus %s\n",
+                dev->product_desc, bus->qbus.name);
+        return -1;
+    }
 
     dev->attached++;
     QTAILQ_REMOVE(&bus->free, port, next);
@@ -204,6 +214,8 @@ static void do_attach(USBDevice *dev)
 
     QTAILQ_INSERT_TAIL(&bus->used, port, next);
     bus->nused++;
+
+    return 0;
 }
 
 int usb_device_attach(USBDevice *dev)
@@ -215,8 +227,7 @@ int usb_device_attach(USBDevice *dev)
            (unless a physical port location is specified). */
         usb_create_simple(bus, "usb-hub");
     }
-    do_attach(dev);
-    return 0;
+    return do_attach(dev);
 }
 
 int usb_device_detach(USBDevice *dev)
@@ -225,7 +236,7 @@ int usb_device_detach(USBDevice *dev)
     USBPort *port;
 
     if (!dev->attached) {
-        fprintf(stderr, "Warning: tried to detach unattached usb device %s\n",
+        error_report("Error: tried to detach unattached usb device %s\n",
                 dev->product_desc);
         return -1;
     }
diff --git a/hw/usb-ccid.c b/hw/usb-ccid.c
index 524b841da1..d3922998c5 100644
--- a/hw/usb-ccid.c
+++ b/hw/usb-ccid.c
@@ -1271,6 +1271,7 @@ static int ccid_initfn(USBDevice *dev)
     s->migration_target_ip = 0;
     s->migration_target_port = 0;
     s->dev.speed = USB_SPEED_FULL;
+    s->dev.speedmask = USB_SPEED_MASK_FULL;
     s->notify_slot_change = false;
     s->powered = true;
     s->pending_answers_num = 0;
diff --git a/hw/usb-desc.c b/hw/usb-desc.c
index e4a4680fee..bc6858f62f 100644
--- a/hw/usb-desc.c
+++ b/hw/usb-desc.c
@@ -242,7 +242,17 @@ static void usb_desc_setdefaults(USBDevice *dev)
 
 void usb_desc_init(USBDevice *dev)
 {
+    const USBDesc *desc = dev->info->usb_desc;
+
+    assert(desc != NULL);
     dev->speed = USB_SPEED_FULL;
+    dev->speedmask = 0;
+    if (desc->full) {
+        dev->speedmask |= USB_SPEED_MASK_FULL;
+    }
+    if (desc->high) {
+        dev->speedmask |= USB_SPEED_MASK_HIGH;
+    }
     usb_desc_setdefaults(dev);
 }
 
@@ -375,6 +385,10 @@ int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len
         trace_usb_desc_other_speed_config(dev->addr, index, len, ret);
         break;
 
+    case USB_DT_DEBUG:
+        /* ignore silently */
+        break;
+
     default:
         fprintf(stderr, "%s: %d unknown type %d (len %zd)\n", __FUNCTION__,
                 dev->addr, type, len);
diff --git a/hw/usb-ehci.c b/hw/usb-ehci.c
index e33e546b43..91fb7dea93 100644
--- a/hw/usb-ehci.c
+++ b/hw/usb-ehci.c
@@ -130,7 +130,7 @@
 #define PORTSC_CONNECT       (1 << 0)     // Current Connect Status
 
 #define FRAME_TIMER_FREQ 1000
-#define FRAME_TIMER_USEC (1000000 / FRAME_TIMER_FREQ)
+#define FRAME_TIMER_NS   (1000000000 / FRAME_TIMER_FREQ)
 
 #define NB_MAXINTRATE    8        // Max rate at which controller issues ints
 #define NB_PORTS         4        // Number of downstream ports
@@ -348,7 +348,8 @@ struct EHCIQueue {
     EHCIState *ehci;
     QTAILQ_ENTRY(EHCIQueue) next;
     bool async_schedule;
-    uint32_t seen, ts;
+    uint32_t seen;
+    uint64_t ts;
 
     /* cached data from guest - needs to be flushed
      * when guest removes an entry (doorbell, handshake sequence)
@@ -373,6 +374,11 @@ struct EHCIState {
     target_phys_addr_t mem_base;
     int mem;
     int num_ports;
+
+    /* properties */
+    uint32_t freq;
+    uint32_t maxframes;
+
     /*
      *  EHCI spec version 1.0 Section 2.3
      *  Host Controller Operational Registers
@@ -413,12 +419,11 @@ struct EHCIState {
     uint8_t ibuffer[BUFF_SIZE];
     int isoch_pause;
 
-    uint32_t last_run_usec;
-    uint32_t frame_end_usec;
+    uint64_t last_run_ns;
 };
 
 #define SET_LAST_RUN_CLOCK(s) \
-    (s)->last_run_usec = qemu_get_clock_ns(vm_clock) / 1000;
+    (s)->last_run_ns = qemu_get_clock_ns(vm_clock);
 
 /* nifty macros from Arnon's EHCI version  */
 #define get_field(data, field) \
@@ -685,10 +690,10 @@ static void ehci_queues_rip_unused(EHCIState *ehci)
     QTAILQ_FOREACH_SAFE(q, &ehci->queues, next, tmp) {
         if (q->seen) {
             q->seen = 0;
-            q->ts = ehci->last_run_usec;
+            q->ts = ehci->last_run_ns;
             continue;
         }
-        if (ehci->last_run_usec < q->ts + 250000) {
+        if (ehci->last_run_ns < q->ts + 250000000) {
             /* allow 0.25 sec idle */
             continue;
         }
@@ -2040,23 +2045,16 @@ static void ehci_frame_timer(void *opaque)
 {
     EHCIState *ehci = opaque;
     int64_t expire_time, t_now;
-    int usec_elapsed;
+    uint64_t ns_elapsed;
     int frames;
-    int usec_now;
     int i;
     int skipped_frames = 0;
 
-
     t_now = qemu_get_clock_ns(vm_clock);
-    expire_time = t_now + (get_ticks_per_sec() / FRAME_TIMER_FREQ);
-    if (expire_time == t_now) {
-        expire_time++;
-    }
+    expire_time = t_now + (get_ticks_per_sec() / ehci->freq);
 
-    usec_now = t_now / 1000;
-    usec_elapsed = usec_now - ehci->last_run_usec;
-    frames = usec_elapsed / FRAME_TIMER_USEC;
-    ehci->frame_end_usec = usec_now + FRAME_TIMER_USEC - 10;
+    ns_elapsed = t_now - ehci->last_run_ns;
+    frames = ns_elapsed / FRAME_TIMER_NS;
 
     for (i = 0; i < frames; i++) {
         if ( !(ehci->usbsts & USBSTS_HALT)) {
@@ -2073,13 +2071,13 @@ static void ehci_frame_timer(void *opaque)
             ehci->sofv &= 0x000003ff;
         }
 
-        if (frames - i > 10) {
+        if (frames - i > ehci->maxframes) {
             skipped_frames++;
         } else {
             ehci_advance_periodic_state(ehci);
         }
 
-        ehci->last_run_usec += FRAME_TIMER_USEC;
+        ehci->last_run_ns += FRAME_TIMER_NS;
     }
 
 #if 0
@@ -2146,6 +2144,11 @@ static PCIDeviceInfo ehci_info = {
     .device_id    = PCI_DEVICE_ID_INTEL_82801D,
     .revision     = 0x10,
     .class_id     = PCI_CLASS_SERIAL_USB,
+    .qdev.props   = (Property[]) {
+        DEFINE_PROP_UINT32("freq",      EHCIState, freq, FRAME_TIMER_FREQ),
+        DEFINE_PROP_UINT32("maxframes", EHCIState, maxframes, 128),
+        DEFINE_PROP_END_OF_LIST(),
+    },
 };
 
 static int usb_ehci_initfn(PCIDevice *dev)
diff --git a/hw/usb-msd.c b/hw/usb-msd.c
index c59797b27e..86582cc723 100644
--- a/hw/usb-msd.c
+++ b/hw/usb-msd.c
@@ -51,6 +51,7 @@ typedef struct {
     SCSIRequest *req;
     SCSIBus bus;
     BlockConf conf;
+    char *serial;
     SCSIDevice *scsi_dev;
     uint32_t removable;
     int result;
@@ -497,8 +498,9 @@ static void usb_msd_password_cb(void *opaque, int err)
     MSDState *s = opaque;
 
     if (!err)
-        usb_device_attach(&s->dev);
-    else
+        err = usb_device_attach(&s->dev);
+
+    if (err)
         qdev_unplug(&s->dev.qdev);
 }
 
@@ -531,9 +533,15 @@ static int usb_msd_initfn(USBDevice *dev)
     bdrv_detach(bs, &s->dev.qdev);
     s->conf.bs = NULL;
 
-    dinfo = drive_get_by_blockdev(bs);
-    if (dinfo && dinfo->serial) {
-        usb_desc_set_string(dev, STR_SERIALNUMBER, dinfo->serial);
+    if (!s->serial) {
+        /* try to fall back to value set with legacy -drive serial=... */
+        dinfo = drive_get_by_blockdev(bs);
+        if (*dinfo->serial) {
+            s->serial = strdup(dinfo->serial);
+        }
+    }
+    if (s->serial) {
+        usb_desc_set_string(dev, STR_SERIALNUMBER, s->serial);
     }
 
     usb_desc_init(dev);
@@ -632,6 +640,7 @@ static struct USBDeviceInfo msd_info = {
     .usbdevice_init = usb_msd_init,
     .qdev.props     = (Property[]) {
         DEFINE_BLOCK_PROPERTIES(MSDState, conf),
+        DEFINE_PROP_STRING("serial", MSDState, serial),
         DEFINE_PROP_BIT("removable", MSDState, removable, 0, false),
         DEFINE_PROP_END_OF_LIST(),
     },
diff --git a/hw/usb-ohci.c b/hw/usb-ohci.c
index 5d2ae01235..1c29b9fa6c 100644
--- a/hw/usb-ohci.c
+++ b/hw/usb-ohci.c
@@ -373,14 +373,25 @@ static void ohci_wakeup(USBDevice *dev)
     OHCIState *s = container_of(bus, OHCIState, bus);
     int portnum = dev->port->index;
     OHCIPort *port = &s->rhport[portnum];
+    uint32_t intr = 0;
     if (port->ctrl & OHCI_PORT_PSS) {
         DPRINTF("usb-ohci: port %d: wakeup\n", portnum);
         port->ctrl |= OHCI_PORT_PSSC;
         port->ctrl &= ~OHCI_PORT_PSS;
-        if ((s->ctl & OHCI_CTL_HCFS) == OHCI_USB_SUSPEND) {
-            ohci_set_interrupt(s, OHCI_INTR_RD);
-        }
+        intr = OHCI_INTR_RHSC;
+    }
+    /* Note that the controller can be suspended even if this port is not */
+    if ((s->ctl & OHCI_CTL_HCFS) == OHCI_USB_SUSPEND) {
+        DPRINTF("usb-ohci: remote-wakeup: SUSPEND->RESUME\n");
+        /* This is the one state transition the controller can do by itself */
+        s->ctl &= ~OHCI_CTL_HCFS;
+        s->ctl |= OHCI_USB_RESUME;
+        /* In suspend mode only ResumeDetected is possible, not RHSC:
+         * see the OHCI spec 5.1.2.3.
+         */
+        intr = OHCI_INTR_RD;
     }
+    ohci_set_interrupt(s, intr);
 }
 
 /* Reset the controller */
diff --git a/hw/usb.h b/hw/usb.h
index 06ce05826a..076e2ffce4 100644
--- a/hw/usb.h
+++ b/hw/usb.h
@@ -130,6 +130,7 @@
 #define USB_DT_ENDPOINT			0x05
 #define USB_DT_DEVICE_QUALIFIER         0x06
 #define USB_DT_OTHER_SPEED_CONFIG       0x07
+#define USB_DT_DEBUG                    0x0A
 #define USB_DT_INTERFACE_ASSOC          0x0B
 
 #define USB_ENDPOINT_XFER_CONTROL	0
@@ -168,7 +169,10 @@ struct USBDevice {
     char *port_path;
     void *opaque;
 
+    /* Actual connected speed */
     int speed;
+    /* Supported speeds, not in info because it may be variable (hostdevs) */
+    int speedmask;
     uint8_t addr;
     char product_desc[32];
     int auto_attach;
diff --git a/usb-bsd.c b/usb-bsd.c
index c1bcc4a1da..3b97eb491b 100644
--- a/usb-bsd.c
+++ b/usb-bsd.c
@@ -367,8 +367,10 @@ USBDevice *usb_host_device_open(const char *devname)
 
     if (dev_info.udi_speed == 1) {
         dev->dev.speed = USB_SPEED_LOW - 1;
+        dev->dev.speedmask = USB_SPEED_MASK_LOW;
     } else {
         dev->dev.speed = USB_SPEED_FULL - 1;
+        dev->dev.speedmask = USB_SPEED_MASK_FULL;
     }
 
     if (strncmp(dev_info.udi_product, "product", 7) != 0) {
diff --git a/usb-linux.c b/usb-linux.c
index 5d2ec5c5c7..1a2deb35c9 100644
--- a/usb-linux.c
+++ b/usb-linux.c
@@ -85,7 +85,6 @@ static int usb_fs_type;
 
 /* endpoint association data */
 #define ISO_FRAME_DESC_PER_URB 32
-#define ISO_URB_COUNT 3
 #define INVALID_EP_TYPE 255
 
 /* devio.c limits single requests to 16k */
@@ -101,6 +100,7 @@ struct endp_data {
     int iso_urb_idx;
     int iso_buffer_used;
     int max_packet_size;
+    int inflight;
 };
 
 struct USBAutoFilter {
@@ -120,6 +120,7 @@ typedef struct USBHostDevice {
     int       configuration;
     int       ninterfaces;
     int       closing;
+    uint32_t  iso_urb_count;
     Notifier  exit;
 
     struct endp_data endp_table[MAX_ENDPOINTS];
@@ -142,74 +143,91 @@ static void usb_host_auto_check(void *unused);
 static int usb_host_read_file(char *line, size_t line_size,
                             const char *device_file, const char *device_name);
 
+static struct endp_data *get_endp(USBHostDevice *s, int ep)
+{
+    return s->endp_table + ep - 1;
+}
+
 static int is_isoc(USBHostDevice *s, int ep)
 {
-    return s->endp_table[ep - 1].type == USBDEVFS_URB_TYPE_ISO;
+    return get_endp(s, ep)->type == USBDEVFS_URB_TYPE_ISO;
 }
 
 static int is_valid(USBHostDevice *s, int ep)
 {
-    return s->endp_table[ep - 1].type != INVALID_EP_TYPE;
+    return get_endp(s, ep)->type != INVALID_EP_TYPE;
 }
 
 static int is_halted(USBHostDevice *s, int ep)
 {
-    return s->endp_table[ep - 1].halted;
+    return get_endp(s, ep)->halted;
 }
 
 static void clear_halt(USBHostDevice *s, int ep)
 {
-    s->endp_table[ep - 1].halted = 0;
+    get_endp(s, ep)->halted = 0;
 }
 
 static void set_halt(USBHostDevice *s, int ep)
 {
-    s->endp_table[ep - 1].halted = 1;
+    get_endp(s, ep)->halted = 1;
 }
 
 static int is_iso_started(USBHostDevice *s, int ep)
 {
-    return s->endp_table[ep - 1].iso_started;
+    return get_endp(s, ep)->iso_started;
 }
 
 static void clear_iso_started(USBHostDevice *s, int ep)
 {
-    s->endp_table[ep - 1].iso_started = 0;
+    get_endp(s, ep)->iso_started = 0;
 }
 
 static void set_iso_started(USBHostDevice *s, int ep)
 {
-    s->endp_table[ep - 1].iso_started = 1;
+    struct endp_data *e = get_endp(s, ep);
+    if (!e->iso_started) {
+        e->iso_started = 1;
+        e->inflight = 0;
+    }
+}
+
+static int change_iso_inflight(USBHostDevice *s, int ep, int value)
+{
+    struct endp_data *e = get_endp(s, ep);
+
+    e->inflight += value;
+    return e->inflight;
 }
 
 static void set_iso_urb(USBHostDevice *s, int ep, AsyncURB *iso_urb)
 {
-    s->endp_table[ep - 1].iso_urb = iso_urb;
+    get_endp(s, ep)->iso_urb = iso_urb;
 }
 
 static AsyncURB *get_iso_urb(USBHostDevice *s, int ep)
 {
-    return s->endp_table[ep - 1].iso_urb;
+    return get_endp(s, ep)->iso_urb;
 }
 
 static void set_iso_urb_idx(USBHostDevice *s, int ep, int i)
 {
-    s->endp_table[ep - 1].iso_urb_idx = i;
+    get_endp(s, ep)->iso_urb_idx = i;
 }
 
 static int get_iso_urb_idx(USBHostDevice *s, int ep)
 {
-    return s->endp_table[ep - 1].iso_urb_idx;
+    return get_endp(s, ep)->iso_urb_idx;
 }
 
 static void set_iso_buffer_used(USBHostDevice *s, int ep, int i)
 {
-    s->endp_table[ep - 1].iso_buffer_used = i;
+    get_endp(s, ep)->iso_buffer_used = i;
 }
 
 static int get_iso_buffer_used(USBHostDevice *s, int ep)
 {
-    return s->endp_table[ep - 1].iso_buffer_used;
+    return get_endp(s, ep)->iso_buffer_used;
 }
 
 static void set_max_packet_size(USBHostDevice *s, int ep, uint8_t *descriptor)
@@ -223,14 +241,12 @@ static void set_max_packet_size(USBHostDevice *s, int ep, uint8_t *descriptor)
     case 2:  microframes = 3; break;
     default: microframes = 1; break;
     }
-    DPRINTF("husb: max packet size: 0x%x -> %d x %d\n",
-            raw, microframes, size);
-    s->endp_table[ep - 1].max_packet_size = size * microframes;
+    get_endp(s, ep)->max_packet_size = size * microframes;
 }
 
 static int get_max_packet_size(USBHostDevice *s, int ep)
 {
-    return s->endp_table[ep - 1].max_packet_size;
+    return get_endp(s, ep)->max_packet_size;
 }
 
 /*
@@ -279,6 +295,7 @@ static void async_complete(void *opaque)
 {
     USBHostDevice *s = opaque;
     AsyncURB *aurb;
+    int urbs = 0;
 
     while (1) {
         USBPacket *p;
@@ -286,6 +303,9 @@ static void async_complete(void *opaque)
         int r = ioctl(s->fd, USBDEVFS_REAPURBNDELAY, &aurb);
         if (r < 0) {
             if (errno == EAGAIN) {
+                if (urbs > 2) {
+                    fprintf(stderr, "husb: %d iso urbs finished at once\n", urbs);
+                }
                 return;
             }
             if (errno == ENODEV && !s->closing) {
@@ -303,10 +323,16 @@ static void async_complete(void *opaque)
         /* If this is a buffered iso urb mark it as complete and don't do
            anything else (it is handled further in usb_host_handle_iso_data) */
         if (aurb->iso_frame_idx == -1) {
+            int inflight;
             if (aurb->urb.status == -EPIPE) {
                 set_halt(s, aurb->urb.endpoint & 0xf);
             }
             aurb->iso_frame_idx = 0;
+            urbs++;
+            inflight = change_iso_inflight(s, aurb->urb.endpoint & 0xf, -1);
+            if (inflight == 0 && is_iso_started(s, aurb->urb.endpoint & 0xf)) {
+                fprintf(stderr, "husb: out of buffers for iso stream\n");
+            }
             continue;
         }
 
@@ -502,8 +528,8 @@ static AsyncURB *usb_host_alloc_iso(USBHostDevice *s, uint8_t ep, int in)
     AsyncURB *aurb;
     int i, j, len = get_max_packet_size(s, ep);
 
-    aurb = qemu_mallocz(ISO_URB_COUNT * sizeof(*aurb));
-    for (i = 0; i < ISO_URB_COUNT; i++) {
+    aurb = qemu_mallocz(s->iso_urb_count * sizeof(*aurb));
+    for (i = 0; i < s->iso_urb_count; i++) {
         aurb[i].urb.endpoint      = ep;
         aurb[i].urb.buffer_length = ISO_FRAME_DESC_PER_URB * len;
         aurb[i].urb.buffer        = qemu_malloc(aurb[i].urb.buffer_length);
@@ -533,7 +559,7 @@ static void usb_host_stop_n_free_iso(USBHostDevice *s, uint8_t ep)
         return;
     }
 
-    for (i = 0; i < ISO_URB_COUNT; i++) {
+    for (i = 0; i < s->iso_urb_count; i++) {
         /* in flight? */
         if (aurb[i].iso_frame_idx == -1) {
             ret = ioctl(s->fd, USBDEVFS_DISCARDURB, &aurb[i]);
@@ -551,7 +577,7 @@ static void usb_host_stop_n_free_iso(USBHostDevice *s, uint8_t ep)
         async_complete(s);
     }
 
-    for (i = 0; i < ISO_URB_COUNT; i++) {
+    for (i = 0; i < s->iso_urb_count; i++) {
         qemu_free(aurb[i].urb.buffer);
     }
 
@@ -636,7 +662,7 @@ static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in)
         }
         aurb[i].iso_frame_idx++;
         if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) {
-            i = (i + 1) % ISO_URB_COUNT;
+            i = (i + 1) % s->iso_urb_count;
             set_iso_urb_idx(s, p->devep, i);
         }
     } else {
@@ -649,7 +675,7 @@ static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in)
 
     if (is_iso_started(s, p->devep)) {
         /* (Re)-submit all fully consumed / filled urbs */
-        for (i = 0; i < ISO_URB_COUNT; i++) {
+        for (i = 0; i < s->iso_urb_count; i++) {
             if (aurb[i].iso_frame_idx == ISO_FRAME_DESC_PER_URB) {
                 ret = ioctl(s->fd, USBDEVFS_SUBMITURB, &aurb[i]);
                 if (ret < 0) {
@@ -667,6 +693,7 @@ static int usb_host_handle_iso_data(USBHostDevice *s, USBPacket *p, int in)
                     break;
                 }
                 aurb[i].iso_frame_idx = -1;
+                change_iso_inflight(s, p->devep, +1);
             }
         }
     }
@@ -1061,6 +1088,42 @@ static int usb_linux_update_endp_table(USBHostDevice *s)
     return 0;
 }
 
+/*
+ * Check if we can safely redirect a usb2 device to a usb1 virtual controller,
+ * this function assumes this is safe, if:
+ * 1) There are no isoc endpoints
+ * 2) There are no interrupt endpoints with a max_packet_size > 64
+ * Note bulk endpoints with a max_packet_size > 64 in theory also are not
+ * usb1 compatible, but in practice this seems to work fine.
+ */
+static int usb_linux_full_speed_compat(USBHostDevice *dev)
+{
+    int i, packet_size;
+
+    /*
+     * usb_linux_update_endp_table only registers info about ep in the current
+     * interface altsettings, so we need to parse the descriptors again.
+     */
+    for (i = 0; (i + 5) < dev->descr_len; i += dev->descr[i]) {
+        if (dev->descr[i + 1] == USB_DT_ENDPOINT) {
+            switch (dev->descr[i + 3] & 0x3) {
+            case 0x00: /* CONTROL */
+                break;
+            case 0x01: /* ISO */
+                return 0;
+            case 0x02: /* BULK */
+                break;
+            case 0x03: /* INTERRUPT */
+                packet_size = dev->descr[i + 4] + (dev->descr[i + 5] << 8);
+                if (packet_size > 64)
+                    return 0;
+                break;
+            }
+        }
+    }
+    return 1;
+}
+
 static int usb_host_open(USBHostDevice *dev, int bus_num,
                         int addr, char *port, const char *prod_name, int speed)
 {
@@ -1140,6 +1203,10 @@ static int usb_host_open(USBHostDevice *dev, int bus_num,
         }
     }
     dev->dev.speed = speed;
+    dev->dev.speedmask = (1 << speed);
+    if (dev->dev.speed == USB_SPEED_HIGH && usb_linux_full_speed_compat(dev)) {
+        dev->dev.speedmask |= USB_SPEED_MASK_FULL;
+    }
 
     printf("husb: grabbed usb device %d.%d\n", bus_num, addr);
 
@@ -1151,10 +1218,14 @@ static int usb_host_open(USBHostDevice *dev, int bus_num,
                 prod_name);
     }
 
+    ret = usb_device_attach(&dev->dev);
+    if (ret) {
+        goto fail;
+    }
+
     /* USB devio uses 'write' flag to check for async completions */
     qemu_set_fd_handler(dev->fd, NULL, async_complete, dev);
 
-    usb_device_attach(&dev->dev);
     return 0;
 
 fail:
@@ -1230,6 +1301,7 @@ static struct USBDeviceInfo usb_host_dev_info = {
         DEFINE_PROP_STRING("hostport", USBHostDevice, match.port),
         DEFINE_PROP_HEX32("vendorid",  USBHostDevice, match.vendor_id,  0),
         DEFINE_PROP_HEX32("productid", USBHostDevice, match.product_id, 0),
+        DEFINE_PROP_UINT32("isobufs",  USBHostDevice, iso_urb_count,    4),
         DEFINE_PROP_END_OF_LIST(),
     },
 };